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    "PRODUCT_BOOT_JARS" ]
32
33# Top level data
34# { "PRODUCT_PACKAGES": {...}}
35# PRODCT_PACKAGES { "libstagefright": "path_to_the_mk_file" }
36
37def removeTrailingParen(path):
38    if path.endswith(")"):
39        return path[:-1]
40    else:
41        return path
42
43def substPathVars(path, parentPath):
44    path_ = path.replace("$(SRC_TARGET_DIR)", "build/target")
45    path__ = path_.replace("$(LOCAL_PATH)", os.path.dirname(parentPath))
46    return path__
47
48
49def parseLine(line, productData, productPath, overrideProperty = False):
50    #print "parse:" + line
51    words = line.split()
52    if len(words) < 2:
53        return
54    if words[0] in PRODUCT_KEYWORDS:
55        # Override only for include
56        if overrideProperty and words[1] == ":=":
57            if len(productData[words[0]]) != 0:
58                print "** Warning: overriding property " + words[0] + " that was:" + \
59                      productData[words[0]]
60            productData[words[0]] = {}
61        d = productData[words[0]]
62        for word in words[2:]:
63            # TODO: parsing those $( cases in better way
64            if word.startswith("$(foreach"): # do not parse complex calls
65                print "** Warning: parseLine too complex line in " + productPath + " : " + line
66                return
67            d[word] = productPath
68    elif words[0] == "$(call" and words[1].startswith("inherit-product"):
69        parseProduct(substPathVars(removeTrailingParen(words[2]), productPath), productData)
70    elif words[0] == "include":
71        parseProduct(substPathVars(words[1], productPath), productData, True)
72    elif words[0] == "-include":
73        parseProduct(substPathVars(words[1], productPath), productData, True)
74
75def parseProduct(productPath, productData, overrideProperty = False):
76    """parse given product mk file and add the result to productData dict"""
77    if not os.path.exists(productPath):
78        print "** Warning cannot find file " + productPath
79        return
80
81    for key in PRODUCT_KEYWORDS:
82        if not key in productData:
83            productData[key] = {}
84
85    multiLineBuffer = [] #for storing multiple lines
86    inMultiLine = False
87    for line in open(productPath):
88        line_ = line.strip()
89        if inMultiLine:
90            if line_.endswith("\\"):
91                multiLineBuffer.append(line_[:-1])
92            else:
93                multiLineBuffer.append(line_)
94                parseLine(" ".join(multiLineBuffer), productData, productPath)
95                inMultiLine = False
96        else:
97            if line_.endswith("\\"):
98                inMultiLine = True
99                multiLineBuffer = []
100                multiLineBuffer.append(line_[:-1])
101            else:
102                parseLine(line_, productData, productPath)
103    #print productData
104
105def printConf(confList):
106    for key in PRODUCT_KEYWORDS:
107        print " *" + key
108        if key in confList:
109            for (k, path) in confList[key]:
110                print "  " + k + ": " + path
111
112def diffTwoProducts(productL, productR):
113    """compare two products and comapre in the order of common, left only, right only items.
114       productL and productR are dictionary"""
115    confCommon = {}
116    confLOnly = {}
117    confROnly = {}
118    for key in PRODUCT_KEYWORDS:
119        dL = productL[key]
120        dR = productR[key]
121        confCommon[key] = []
122        confLOnly[key] = []
123        confROnly[key] = []
124        for keyL in sorted(dL.keys()):
125            if keyL in dR:
126                if dL[keyL] == dR[keyL]:
127                    confCommon[key].append((keyL, dL[keyL]))
128                else:
129                    confCommon[key].append((keyL, dL[keyL] + "," + dR[keyL]))
130            else:
131                confLOnly[key].append((keyL, dL[keyL]))
132        for keyR in sorted(dR.keys()):
133            if not keyR in dL: # right only
134                confROnly[key].append((keyR, dR[keyR]))
135    print "==Common=="
136    printConf(confCommon)
137    print "==Left Only=="
138    printConf(confLOnly)
139    print "==Right Only=="
140    printConf(confROnly)
141
142def main(argv):
143    if len(argv) < 2:
144        print "diff_products.py product_mk_1 [product_mk_2]"
145        print " compare two product mk files (or just list single product)"
146        print " it must be executed from android source tree root."
147        print " ex) diff_products.py device/asus/grouper/full_grouper.mk " + \
148              " device/asus/tilapia/full_tilapia.mk"
149        sys.exit(1)
150
151    productLPath = argv[1]
152    productRPath = None
153    if len(argv) == 3:
154        productRPath = argv[2]
155
156    productL = {}
157    productR = {}
158    parseProduct(productLPath, productL)
159    if productRPath is None:
160        for key in PRODUCT_KEYWORDS:
161            productR[key] = {}
162
163    else:
164        parseProduct(productRPath, productR)
165
166    diffTwoProducts(productL, productR)
167
168
169if __name__ == '__main__':
170    main(sys.argv)
171