|
1 #!/usr/bin/python |
|
2 # This Source Code Form is subject to the terms of the Mozilla Public |
|
3 # License, v. 2.0. If a copy of the MPL was not distributed with this |
|
4 # file, You can obtain one at http://mozilla.org/MPL/2.0/. |
|
5 |
|
6 # originally from http://hg.mozilla.org/build/tools/file/4ab9c1a4e05b/scripts/release/compare-mozconfigs.py |
|
7 |
|
8 from __future__ import unicode_literals |
|
9 |
|
10 import logging |
|
11 import os |
|
12 import site |
|
13 import sys |
|
14 import urllib2 |
|
15 import difflib |
|
16 |
|
17 FAILURE_CODE = 1 |
|
18 SUCCESS_CODE = 0 |
|
19 |
|
20 log = logging.getLogger(__name__) |
|
21 |
|
22 class ConfigError(Exception): |
|
23 pass |
|
24 |
|
25 def make_hg_url(hgHost, repoPath, protocol='https', revision=None, |
|
26 filename=None): |
|
27 """construct a valid hg url from a base hg url (hg.mozilla.org), |
|
28 repoPath, revision and possible filename""" |
|
29 base = '%s://%s' % (protocol, hgHost) |
|
30 repo = '/'.join(p.strip('/') for p in [base, repoPath]) |
|
31 if not filename: |
|
32 if not revision: |
|
33 return repo |
|
34 else: |
|
35 return '/'.join([p.strip('/') for p in [repo, 'rev', revision]]) |
|
36 else: |
|
37 assert revision |
|
38 return '/'.join([p.strip('/') for p in [repo, 'raw-file', revision, |
|
39 filename]]) |
|
40 |
|
41 def readConfig(configfile, keys=[], required=[]): |
|
42 c = {} |
|
43 execfile(configfile, c) |
|
44 for k in keys: |
|
45 c = c[k] |
|
46 items = c.keys() |
|
47 err = False |
|
48 for key in required: |
|
49 if key not in items: |
|
50 err = True |
|
51 log.error("Required item `%s' missing from %s" % (key, c)) |
|
52 if err: |
|
53 raise ConfigError("Missing at least one item in config, see above") |
|
54 return c |
|
55 |
|
56 def verify_mozconfigs(mozconfig_pair, nightly_mozconfig_pair, platform, |
|
57 mozconfigWhitelist={}): |
|
58 """Compares mozconfig to nightly_mozconfig and compare to an optional |
|
59 whitelist of known differences. mozconfig_pair and nightly_mozconfig_pair |
|
60 are pairs containing the mozconfig's identifier and the list of lines in |
|
61 the mozconfig.""" |
|
62 |
|
63 # unpack the pairs to get the names, the names are just for |
|
64 # identifying the mozconfigs when logging the error messages |
|
65 mozconfig_name, mozconfig_lines = mozconfig_pair |
|
66 nightly_mozconfig_name, nightly_mozconfig_lines = nightly_mozconfig_pair |
|
67 |
|
68 missing_args = mozconfig_lines == [] or nightly_mozconfig_lines == [] |
|
69 if missing_args: |
|
70 log.info("Missing mozconfigs to compare for %s" % platform) |
|
71 return False |
|
72 |
|
73 success = True |
|
74 |
|
75 diff_instance = difflib.Differ() |
|
76 diff_result = diff_instance.compare(mozconfig_lines, nightly_mozconfig_lines) |
|
77 diff_list = list(diff_result) |
|
78 |
|
79 for line in diff_list: |
|
80 clean_line = line[1:].strip() |
|
81 if (line[0] == '-' or line[0] == '+') and len(clean_line) > 1: |
|
82 # skip comment lines |
|
83 if clean_line.startswith('#'): |
|
84 continue |
|
85 # compare to whitelist |
|
86 message = "" |
|
87 if line[0] == '-': |
|
88 if platform in mozconfigWhitelist.get('release', {}): |
|
89 if clean_line in \ |
|
90 mozconfigWhitelist['release'][platform]: |
|
91 continue |
|
92 elif line[0] == '+': |
|
93 if platform in mozconfigWhitelist.get('nightly', {}): |
|
94 if clean_line in \ |
|
95 mozconfigWhitelist['nightly'][platform]: |
|
96 continue |
|
97 else: |
|
98 log.warning("%s not in %s %s!" % ( |
|
99 clean_line, platform, |
|
100 mozconfigWhitelist['nightly'][platform])) |
|
101 else: |
|
102 log.error("Skipping line %s!" % line) |
|
103 continue |
|
104 message = "found in %s but not in %s: %s" |
|
105 if line[0] == '-': |
|
106 log.error(message % (mozconfig_name, |
|
107 nightly_mozconfig_name, clean_line)) |
|
108 else: |
|
109 log.error(message % (nightly_mozconfig_name, |
|
110 mozconfig_name, clean_line)) |
|
111 success = False |
|
112 return success |
|
113 |
|
114 def get_mozconfig(path, options): |
|
115 """Consumes a path and returns a list of lines from |
|
116 the mozconfig file. If download is required, the path |
|
117 specified should be relative to the root of the hg |
|
118 repository e.g browser/config/mozconfigs/linux32/nightly""" |
|
119 if options.no_download: |
|
120 return open(path, 'r').readlines() |
|
121 else: |
|
122 url = make_hg_url(options.hghost, options.branch, 'http', |
|
123 options.revision, path) |
|
124 return urllib2.urlopen(url).readlines() |
|
125 |
|
126 if __name__ == '__main__': |
|
127 from optparse import OptionParser |
|
128 parser = OptionParser() |
|
129 |
|
130 parser.add_option('--branch', dest='branch') |
|
131 parser.add_option('--revision', dest='revision') |
|
132 parser.add_option('--hghost', dest='hghost', default='hg.mozilla.org') |
|
133 parser.add_option('--whitelist', dest='whitelist') |
|
134 parser.add_option('--no-download', action='store_true', dest='no_download', |
|
135 default=False) |
|
136 options, args = parser.parse_args() |
|
137 |
|
138 logging.basicConfig(level=logging.INFO) |
|
139 |
|
140 missing_args = options.branch is None or options.revision is None |
|
141 if not options.no_download and missing_args: |
|
142 logging.error('Not enough arguments to download mozconfigs') |
|
143 sys.exit(FAILURE_CODE) |
|
144 |
|
145 mozconfig_whitelist = readConfig(options.whitelist, ['whitelist']) |
|
146 |
|
147 for arg in args: |
|
148 platform, mozconfig_path, nightly_mozconfig_path = arg.split(',') |
|
149 |
|
150 mozconfig_lines = get_mozconfig(mozconfig_path, options) |
|
151 nightly_mozconfig_lines = get_mozconfig(nightly_mozconfig_path, options) |
|
152 |
|
153 mozconfig_pair = (mozconfig_path, mozconfig_lines) |
|
154 nightly_mozconfig_pair = (nightly_mozconfig_path, |
|
155 nightly_mozconfig_lines) |
|
156 |
|
157 passed = verify_mozconfigs(mozconfig_pair, nightly_mozconfig_pair, |
|
158 platform, mozconfig_whitelist) |
|
159 |
|
160 if passed: |
|
161 logging.info('Mozconfig check passed!') |
|
162 else: |
|
163 logging.error('Mozconfig check failed!') |
|
164 sys.exit(FAILURE_CODE) |
|
165 sys.exit(SUCCESS_CODE) |