| #!/usr/bin/python |
| # This Source Code Form is subject to the terms of the Mozilla Public |
| # License, v. 2.0. If a copy of the MPL was not distributed with this |
| # file, You can obtain one at http://mozilla.org/MPL/2.0/. |
| |
| # originally from http://hg.mozilla.org/build/tools/file/4ab9c1a4e05b/scripts/release/compare-mozconfigs.py |
| |
| from __future__ import unicode_literals |
| |
| import logging |
| import os |
| import site |
| import sys |
| import urllib2 |
| import difflib |
| |
| FAILURE_CODE = 1 |
| SUCCESS_CODE = 0 |
| |
| log = logging.getLogger(__name__) |
| |
| class ConfigError(Exception): |
| pass |
| |
| def make_hg_url(hgHost, repoPath, protocol='https', revision=None, |
| filename=None): |
| """construct a valid hg url from a base hg url (hg.mozilla.org), |
| repoPath, revision and possible filename""" |
| base = '%s://%s' % (protocol, hgHost) |
| repo = '/'.join(p.strip('/') for p in [base, repoPath]) |
| if not filename: |
| if not revision: |
| return repo |
| else: |
| return '/'.join([p.strip('/') for p in [repo, 'rev', revision]]) |
| else: |
| assert revision |
| return '/'.join([p.strip('/') for p in [repo, 'raw-file', revision, |
| filename]]) |
| |
| def readConfig(configfile, keys=[], required=[]): |
| c = {} |
| execfile(configfile, c) |
| for k in keys: |
| c = c[k] |
| items = c.keys() |
| err = False |
| for key in required: |
| if key not in items: |
| err = True |
| log.error("Required item `%s' missing from %s" % (key, c)) |
| if err: |
| raise ConfigError("Missing at least one item in config, see above") |
| return c |
| |
| def verify_mozconfigs(mozconfig_pair, nightly_mozconfig_pair, platform, |
| mozconfigWhitelist={}): |
| """Compares mozconfig to nightly_mozconfig and compare to an optional |
| whitelist of known differences. mozconfig_pair and nightly_mozconfig_pair |
| are pairs containing the mozconfig's identifier and the list of lines in |
| the mozconfig.""" |
| |
| # unpack the pairs to get the names, the names are just for |
| # identifying the mozconfigs when logging the error messages |
| mozconfig_name, mozconfig_lines = mozconfig_pair |
| nightly_mozconfig_name, nightly_mozconfig_lines = nightly_mozconfig_pair |
| |
| missing_args = mozconfig_lines == [] or nightly_mozconfig_lines == [] |
| if missing_args: |
| log.info("Missing mozconfigs to compare for %s" % platform) |
| return False |
| |
| success = True |
| |
| diff_instance = difflib.Differ() |
| diff_result = diff_instance.compare(mozconfig_lines, nightly_mozconfig_lines) |
| diff_list = list(diff_result) |
| |
| for line in diff_list: |
| clean_line = line[1:].strip() |
| if (line[0] == '-' or line[0] == '+') and len(clean_line) > 1: |
| # skip comment lines |
| if clean_line.startswith('#'): |
| continue |
| # compare to whitelist |
| message = "" |
| if line[0] == '-': |
| # handle lines that move around in diff |
| if '+' + line[1:] in diff_list: |
| continue |
| if platform in mozconfigWhitelist.get('release', {}): |
| if clean_line in \ |
| mozconfigWhitelist['release'][platform]: |
| continue |
| elif line[0] == '+': |
| if '-' + line[1:] in diff_list: |
| continue |
| if platform in mozconfigWhitelist.get('nightly', {}): |
| if clean_line in \ |
| mozconfigWhitelist['nightly'][platform]: |
| continue |
| else: |
| log.warning("%s not in %s %s!" % ( |
| clean_line, platform, |
| mozconfigWhitelist['nightly'][platform])) |
| else: |
| log.error("Skipping line %s!" % line) |
| continue |
| message = "found in %s but not in %s: %s" |
| if line[0] == '-': |
| log.error(message % (mozconfig_name, |
| nightly_mozconfig_name, clean_line)) |
| else: |
| log.error(message % (nightly_mozconfig_name, |
| mozconfig_name, clean_line)) |
| success = False |
| return success |
| |
| def get_mozconfig(path, options): |
| """Consumes a path and returns a list of lines from |
| the mozconfig file. If download is required, the path |
| specified should be relative to the root of the hg |
| repository e.g browser/config/mozconfigs/linux32/nightly""" |
| if options.no_download: |
| return open(path, 'r').readlines() |
| else: |
| url = make_hg_url(options.hghost, options.branch, 'http', |
| options.revision, path) |
| return urllib2.urlopen(url).readlines() |
| |
| if __name__ == '__main__': |
| from optparse import OptionParser |
| parser = OptionParser() |
| |
| parser.add_option('--branch', dest='branch') |
| parser.add_option('--revision', dest='revision') |
| parser.add_option('--hghost', dest='hghost', default='hg.mozilla.org') |
| parser.add_option('--whitelist', dest='whitelist') |
| parser.add_option('--no-download', action='store_true', dest='no_download', |
| default=False) |
| options, args = parser.parse_args() |
| |
| logging.basicConfig(level=logging.INFO) |
| |
| missing_args = options.branch is None or options.revision is None |
| if not options.no_download and missing_args: |
| logging.error('Not enough arguments to download mozconfigs') |
| sys.exit(FAILURE_CODE) |
| |
| mozconfig_whitelist = readConfig(options.whitelist, ['whitelist']) |
| |
| for arg in args: |
| platform, mozconfig_path, nightly_mozconfig_path = arg.split(',') |
| |
| mozconfig_lines = get_mozconfig(mozconfig_path, options) |
| nightly_mozconfig_lines = get_mozconfig(nightly_mozconfig_path, options) |
| |
| mozconfig_pair = (mozconfig_path, mozconfig_lines) |
| nightly_mozconfig_pair = (nightly_mozconfig_path, |
| nightly_mozconfig_lines) |
| |
| passed = verify_mozconfigs(mozconfig_pair, nightly_mozconfig_pair, |
| platform, mozconfig_whitelist) |
| |
| if passed: |
| logging.info('Mozconfig check passed!') |
| else: |
| logging.error('Mozconfig check failed!') |
| sys.exit(FAILURE_CODE) |
| sys.exit(SUCCESS_CODE) |