| #!/usr/bin/env python |
| # |
| # Copyright (C) 2013 The Android Open Source Project |
| # |
| # Licensed under the Apache License, Version 2.0 (the 'License'); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an 'AS IS' BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| # |
| |
| # diff_products.py product_mk_1 [product_mk_2] |
| # compare two product congifuraitons or analyze one product configuration. |
| # List PRODUCT_PACKAGES, PRODUCT_COPY_FILES, and etc. |
| |
| |
| import os |
| import sys |
| |
| |
| PRODUCT_KEYWORDS = [ |
| "PRODUCT_PACKAGES", |
| "PRODUCT_COPY_FILES", |
| "PRODUCT_PROPERTY_OVERRIDES", |
| "PRODUCT_BOOT_JARS" ] |
| |
| # Top level data |
| # { "PRODUCT_PACKAGES": {...}} |
| # PRODCT_PACKAGES { "libstagefright": "path_to_the_mk_file" } |
| |
| def removeTrailingParen(path): |
| if path.endswith(")"): |
| return path[:-1] |
| else: |
| return path |
| |
| def substPathVars(path, parentPath): |
| path_ = path.replace("$(SRC_TARGET_DIR)", "build/target") |
| path__ = path_.replace("$(LOCAL_PATH)", os.path.dirname(parentPath)) |
| return path__ |
| |
| |
| def parseLine(line, productData, productPath, overrideProperty = False): |
| #print "parse:" + line |
| words = line.split() |
| if len(words) < 2: |
| return |
| if words[0] in PRODUCT_KEYWORDS: |
| # Override only for include |
| if overrideProperty and words[1] == ":=": |
| if len(productData[words[0]]) != 0: |
| print "** Warning: overriding property " + words[0] + " that was:" + \ |
| productData[words[0]] |
| productData[words[0]] = {} |
| d = productData[words[0]] |
| for word in words[2:]: |
| # TODO: parsing those $( cases in better way |
| if word.startswith("$(foreach"): # do not parse complex calls |
| print "** Warning: parseLine too complex line in " + productPath + " : " + line |
| return |
| d[word] = productPath |
| elif words[0] == "$(call" and words[1].startswith("inherit-product"): |
| parseProduct(substPathVars(removeTrailingParen(words[2]), productPath), productData) |
| elif words[0] == "include": |
| parseProduct(substPathVars(words[1], productPath), productData, True) |
| elif words[0] == "-include": |
| parseProduct(substPathVars(words[1], productPath), productData, True) |
| |
| def parseProduct(productPath, productData, overrideProperty = False): |
| """parse given product mk file and add the result to productData dict""" |
| if not os.path.exists(productPath): |
| print "** Warning cannot find file " + productPath |
| return |
| |
| for key in PRODUCT_KEYWORDS: |
| if not key in productData: |
| productData[key] = {} |
| |
| multiLineBuffer = [] #for storing multiple lines |
| inMultiLine = False |
| for line in open(productPath): |
| line_ = line.strip() |
| if inMultiLine: |
| if line_.endswith("\\"): |
| multiLineBuffer.append(line_[:-1]) |
| else: |
| multiLineBuffer.append(line_) |
| parseLine(" ".join(multiLineBuffer), productData, productPath) |
| inMultiLine = False |
| else: |
| if line_.endswith("\\"): |
| inMultiLine = True |
| multiLineBuffer = [] |
| multiLineBuffer.append(line_[:-1]) |
| else: |
| parseLine(line_, productData, productPath) |
| #print productData |
| |
| def printConf(confList): |
| for key in PRODUCT_KEYWORDS: |
| print " *" + key |
| if key in confList: |
| for (k, path) in confList[key]: |
| print " " + k + ": " + path |
| |
| def diffTwoProducts(productL, productR): |
| """compare two products and comapre in the order of common, left only, right only items. |
| productL and productR are dictionary""" |
| confCommon = {} |
| confLOnly = {} |
| confROnly = {} |
| for key in PRODUCT_KEYWORDS: |
| dL = productL[key] |
| dR = productR[key] |
| confCommon[key] = [] |
| confLOnly[key] = [] |
| confROnly[key] = [] |
| for keyL in sorted(dL.keys()): |
| if keyL in dR: |
| if dL[keyL] == dR[keyL]: |
| confCommon[key].append((keyL, dL[keyL])) |
| else: |
| confCommon[key].append((keyL, dL[keyL] + "," + dR[keyL])) |
| else: |
| confLOnly[key].append((keyL, dL[keyL])) |
| for keyR in sorted(dR.keys()): |
| if not keyR in dL: # right only |
| confROnly[key].append((keyR, dR[keyR])) |
| print "==Common==" |
| printConf(confCommon) |
| print "==Left Only==" |
| printConf(confLOnly) |
| print "==Right Only==" |
| printConf(confROnly) |
| |
| def main(argv): |
| if len(argv) < 2: |
| print "diff_products.py product_mk_1 [product_mk_2]" |
| print " compare two product mk files (or just list single product)" |
| print " it must be executed from android source tree root." |
| print " ex) diff_products.py device/asus/grouper/full_grouper.mk " + \ |
| " device/asus/tilapia/full_tilapia.mk" |
| sys.exit(1) |
| |
| productLPath = argv[1] |
| productRPath = None |
| if len(argv) == 3: |
| productRPath = argv[2] |
| |
| productL = {} |
| productR = {} |
| parseProduct(productLPath, productL) |
| if productRPath is None: |
| for key in PRODUCT_KEYWORDS: |
| productR[key] = {} |
| |
| else: |
| parseProduct(productRPath, productR) |
| |
| diffTwoProducts(productL, productR) |
| |
| |
| if __name__ == '__main__': |
| main(sys.argv) |