diff_products.py revision 438217aaa20a8082a7dc924b5de295b7de967beb
1#!/usr/bin/env python
2#
3# Copyright (C) 2013 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the 'License');
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9#      http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an 'AS IS' BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16#
17
18# diff_products.py product_mk_1 [product_mk_2]
19# compare two product congifuraitons or analyze one product configuration.
20# List PRODUCT_PACKAGES, PRODUCT_COPY_FILES, and etc.
21
22
23import os
24import sys
25
26
27PRODUCT_KEYWORDS = [
28    "PRODUCT_PACKAGES",
29    "PRODUCT_COPY_FILES",
30    "PRODUCT_PROPERTY_OVERRIDES" ]
31
32# Top level data
33# { "PRODUCT_PACKAGES": {...}}
34# PRODCT_PACKAGES { "libstagefright": "path_to_the_mk_file" }
35
36def removeTrailingParen(path):
37    if path.endswith(")"):
38        return path[:-1]
39    else:
40        return path
41
42def substPathVars(path, parentPath):
43    path_ = path.replace("$(SRC_TARGET_DIR)", "build/target")
44    path__ = path_.replace("$(LOCAL_PATH)", os.path.dirname(parentPath))
45    return path__
46
47
48def parseLine(line, productData, productPath, overrideProperty = False):
49    #print "parse:" + line
50    words = line.split()
51    if len(words) < 2:
52        return
53    if words[0] in PRODUCT_KEYWORDS:
54        # Override only for include
55        if overrideProperty and words[1] == ":=":
56            if len(productData[words[0]]) != 0:
57                print "** Warning: overriding property " + words[0] + " that was:" + \
58                      productData[words[0]]
59            productData[words[0]] = {}
60        d = productData[words[0]]
61        for word in words[2:]:
62            # TODO: parsing those $( cases in better way
63            if word.startswith("$(foreach"): # do not parse complex calls
64                print "** Warning: parseLine too complex line in " + productPath + " : " + line
65                return
66            d[word] = productPath
67    elif words[0] == "$(call" and words[1].startswith("inherit-product"):
68        parseProduct(substPathVars(removeTrailingParen(words[2]), productPath), productData)
69    elif words[0] == "include":
70        parseProduct(substPathVars(words[1], productPath), productData, True)
71    elif words[0] == "-include":
72        parseProduct(substPathVars(words[1], productPath), productData, True)
73
74def parseProduct(productPath, productData, overrideProperty = False):
75    """parse given product mk file and add the result to productData dict"""
76    if not os.path.exists(productPath):
77        print "** Warning cannot find file " + productPath
78        return
79
80    for key in PRODUCT_KEYWORDS:
81        if not key in productData:
82            productData[key] = {}
83
84    multiLineBuffer = [] #for storing multiple lines
85    inMultiLine = False
86    for line in open(productPath):
87        line_ = line.strip()
88        if inMultiLine:
89            if line_.endswith("\\"):
90                multiLineBuffer.append(line_[:-1])
91            else:
92                parseLine(" ".join(multiLineBuffer), productData, productPath)
93                inMultiLine = False
94        else:
95            if line_.endswith("\\"):
96                inMultiLine = True
97                multiLineBuffer = []
98                multiLineBuffer.append(line_[:-1])
99            else:
100                parseLine(line_, productData, productPath)
101    #print productData
102
103def printConf(confList):
104    for key in PRODUCT_KEYWORDS:
105        print " *" + key
106        if key in confList:
107            for (k, path) in confList[key]:
108                print "  " + k + ": " + path
109
110def diffTwoProducts(productL, productR):
111    """compare two products and comapre in the order of common, left only, right only items.
112       productL and productR are dictionary"""
113    confCommon = {}
114    confLOnly = {}
115    confROnly = {}
116    for key in PRODUCT_KEYWORDS:
117        dL = productL[key]
118        dR = productR[key]
119        confCommon[key] = []
120        confLOnly[key] = []
121        confROnly[key] = []
122        for keyL in sorted(dL.keys()):
123            if keyL in dR:
124                if dL[keyL] == dR[keyL]:
125                    confCommon[key].append((keyL, dL[keyL]))
126                else:
127                    confCommon[key].append((keyL, dL[keyL] + "," + dR[keyL]))
128            else:
129                confLOnly[key].append((keyL, dL[keyL]))
130        for keyR in sorted(dR.keys()):
131            if not keyR in dL: # right only
132                confROnly[key].append((keyR, dR[keyR]))
133    print "==Common=="
134    printConf(confCommon)
135    print "==Left Only=="
136    printConf(confLOnly)
137    print "==Right Only=="
138    printConf(confROnly)
139
140def main(argv):
141    if len(argv) < 2:
142        print "diff_products.py product_mk_1 [product_mk_2]"
143        print " compare two product mk files (or just list single product)"
144        print " it must be executed from android source tree root."
145        print " ex) diff_products.py device/asus/grouper/full_grouper.mk " + \
146              " device/asus/tilapia/full_tilapia.mk"
147        sys.exit(1)
148
149    productLPath = argv[1]
150    productRPath = None
151    if len(argv) == 3:
152        productRPath = argv[2]
153
154    productL = {}
155    productR = {}
156    parseProduct(productLPath, productL)
157    if productRPath is None:
158        for key in PRODUCT_KEYWORDS:
159            productR[key] = {}
160
161    else:
162        parseProduct(productRPath, productR)
163
164    diffTwoProducts(productL, productR)
165
166
167if __name__ == '__main__':
168    main(sys.argv)
169