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