#!/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 https://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)