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                multiLineBuffer.append(line_)
93                parseLine(" ".join(multiLineBuffer), productData, productPath)
94                inMultiLine = False
95        else:
96            if line_.endswith("\\"):
97                inMultiLine = True
98                multiLineBuffer = []
99                multiLineBuffer.append(line_[:-1])
100            else:
101                parseLine(line_, productData, productPath)
102    #print productData
103
104def printConf(confList):
105    for key in PRODUCT_KEYWORDS:
106        print " *" + key
107        if key in confList:
108            for (k, path) in confList[key]:
109                print "  " + k + ": " + path
110
111def diffTwoProducts(productL, productR):
112    """compare two products and comapre in the order of common, left only, right only items.
113       productL and productR are dictionary"""
114    confCommon = {}
115    confLOnly = {}
116    confROnly = {}
117    for key in PRODUCT_KEYWORDS:
118        dL = productL[key]
119        dR = productR[key]
120        confCommon[key] = []
121        confLOnly[key] = []
122        confROnly[key] = []
123        for keyL in sorted(dL.keys()):
124            if keyL in dR:
125                if dL[keyL] == dR[keyL]:
126                    confCommon[key].append((keyL, dL[keyL]))
127                else:
128                    confCommon[key].append((keyL, dL[keyL] + "," + dR[keyL]))
129            else:
130                confLOnly[key].append((keyL, dL[keyL]))
131        for keyR in sorted(dR.keys()):
132            if not keyR in dL: # right only
133                confROnly[key].append((keyR, dR[keyR]))
134    print "==Common=="
135    printConf(confCommon)
136    print "==Left Only=="
137    printConf(confLOnly)
138    print "==Right Only=="
139    printConf(confROnly)
140
141def main(argv):
142    if len(argv) < 2:
143        print "diff_products.py product_mk_1 [product_mk_2]"
144        print " compare two product mk files (or just list single product)"
145        print " it must be executed from android source tree root."
146        print " ex) diff_products.py device/asus/grouper/full_grouper.mk " + \
147              " device/asus/tilapia/full_tilapia.mk"
148        sys.exit(1)
149
150    productLPath = argv[1]
151    productRPath = None
152    if len(argv) == 3:
153        productRPath = argv[2]
154
155    productL = {}
156    productR = {}
157    parseProduct(productLPath, productL)
158    if productRPath is None:
159        for key in PRODUCT_KEYWORDS:
160            productR[key] = {}
161
162    else:
163        parseProduct(productRPath, productR)
164
165    diffTwoProducts(productL, productR)
166
167
168if __name__ == '__main__':
169    main(sys.argv)
170