]>
Commit | Line | Data |
---|---|---|
83f82bd6 | 1 | from __future__ import print_function |
183d9b65 TP |
2 | import os |
3 | import re | |
183d9b65 TP |
4 | import glob |
5 | import subprocess | |
83f82bd6 | 6 | import sys |
e6358364 | 7 | import unittest |
183d9b65 TP |
8 | |
9 | # | |
10 | # Patch parsing functions | |
11 | # | |
12 | ||
13 | FIND_INFRA_IN_PATCH = re.compile("^\+\$\(eval \$\((host-)?([^-]*)-package\)\)$") | |
14 | ||
49ffceef | 15 | |
183d9b65 TP |
16 | def analyze_patch(patch): |
17 | """Parse one patch and return the list of files modified, added or | |
18 | removed by the patch.""" | |
19 | files = set() | |
20 | infras = set() | |
1b0df8f2 RB |
21 | for line in patch: |
22 | # If the patch is adding a package, find which infra it is | |
23 | m = FIND_INFRA_IN_PATCH.match(line) | |
24 | if m: | |
25 | infras.add(m.group(2)) | |
26 | if not line.startswith("+++ "): | |
27 | continue | |
28 | line.strip() | |
49ffceef | 29 | fname = line[line.find("/") + 1:].strip() |
1b0df8f2 RB |
30 | if fname == "dev/null": |
31 | continue | |
32 | files.add(fname) | |
183d9b65 TP |
33 | return (files, infras) |
34 | ||
49ffceef | 35 | |
183d9b65 TP |
36 | FIND_INFRA_IN_MK = re.compile("^\$\(eval \$\((host-)?([^-]*)-package\)\)$") |
37 | ||
49ffceef | 38 | |
183d9b65 TP |
39 | def fname_get_package_infra(fname): |
40 | """Checks whether the file name passed as argument is a Buildroot .mk | |
41 | file describing a package, and find the infrastructure it's using.""" | |
42 | if not fname.endswith(".mk"): | |
43 | return None | |
44 | ||
45 | if not os.path.exists(fname): | |
46 | return None | |
47 | ||
48 | with open(fname, "r") as f: | |
49ffceef RM |
49 | for line in f: |
50 | line = line.strip() | |
51 | m = FIND_INFRA_IN_MK.match(line) | |
183d9b65 TP |
52 | if m: |
53 | return m.group(2) | |
54 | return None | |
55 | ||
49ffceef | 56 | |
183d9b65 TP |
57 | def get_infras(files): |
58 | """Search in the list of files for .mk files, and collect the package | |
59 | infrastructures used by those .mk files.""" | |
60 | infras = set() | |
61 | for fname in files: | |
62 | infra = fname_get_package_infra(fname) | |
63 | if infra: | |
64 | infras.add(infra) | |
65 | return infras | |
66 | ||
49ffceef | 67 | |
183d9b65 TP |
68 | def analyze_patches(patches): |
69 | """Parse a list of patches and returns the list of files modified, | |
70 | added or removed by the patches, as well as the list of package | |
71 | infrastructures used by those patches (if any)""" | |
72 | allfiles = set() | |
73 | allinfras = set() | |
74 | for patch in patches: | |
75 | (files, infras) = analyze_patch(patch) | |
76 | allfiles = allfiles | files | |
77 | allinfras = allinfras | infras | |
78 | allinfras = allinfras | get_infras(allfiles) | |
79 | return (allfiles, allinfras) | |
80 | ||
49ffceef | 81 | |
a0a066a8 | 82 | # |
e6358364 | 83 | # Unit-test parsing functions |
a0a066a8 | 84 | # |
e6358364 VH |
85 | |
86 | def get_all_test_cases(suite): | |
87 | """Generate all test-cases from a given test-suite. | |
88 | :return: (test.module, test.name)""" | |
89 | if issubclass(type(suite), unittest.TestSuite): | |
90 | for test in suite: | |
91 | for res in get_all_test_cases(test): | |
92 | yield res | |
93 | else: | |
94 | yield (suite.__module__, suite.__class__.__name__) | |
95 | ||
96 | ||
97 | def list_unittests(path): | |
98 | """Use the unittest module to retreive all test cases from a given | |
99 | directory""" | |
100 | loader = unittest.TestLoader() | |
101 | suite = loader.discover(path) | |
102 | tests = {} | |
103 | for module, test in get_all_test_cases(suite): | |
104 | module_path = os.path.join(path, *module.split('.')) | |
105 | tests.setdefault(module_path, []).append('%s.%s' % (module, test)) | |
106 | return tests | |
107 | ||
108 | ||
109 | unittests = {} | |
110 | ||
a0a066a8 | 111 | |
183d9b65 TP |
112 | # |
113 | # DEVELOPERS file parsing functions | |
114 | # | |
115 | ||
116 | class Developer: | |
117 | def __init__(self, name, files): | |
118 | self.name = name | |
119 | self.files = files | |
120 | self.packages = parse_developer_packages(files) | |
121 | self.architectures = parse_developer_architectures(files) | |
122 | self.infras = parse_developer_infras(files) | |
e6358364 | 123 | self.runtime_tests = parse_developer_runtime_tests(files) |
caead542 | 124 | self.defconfigs = parse_developer_defconfigs(files) |
183d9b65 TP |
125 | |
126 | def hasfile(self, f): | |
127 | f = os.path.abspath(f) | |
128 | for fs in self.files: | |
129 | if f.startswith(fs): | |
130 | return True | |
131 | return False | |
132 | ||
bb5576db VH |
133 | def __repr__(self): |
134 | name = '\'' + self.name.split(' <')[0][:20] + '\'' | |
135 | things = [] | |
136 | if len(self.files): | |
137 | things.append('{} files'.format(len(self.files))) | |
138 | if len(self.packages): | |
139 | things.append('{} pkgs'.format(len(self.packages))) | |
140 | if len(self.architectures): | |
141 | things.append('{} archs'.format(len(self.architectures))) | |
142 | if len(self.infras): | |
143 | things.append('{} infras'.format(len(self.infras))) | |
e6358364 VH |
144 | if len(self.runtime_tests): |
145 | things.append('{} tests'.format(len(self.runtime_tests))) | |
caead542 VH |
146 | if len(self.defconfigs): |
147 | things.append('{} defconfigs'.format(len(self.defconfigs))) | |
bb5576db VH |
148 | if things: |
149 | return 'Developer <{} ({})>'.format(name, ', '.join(things)) | |
150 | else: | |
151 | return 'Developer <' + name + '>' | |
152 | ||
49ffceef | 153 | |
183d9b65 TP |
154 | def parse_developer_packages(fnames): |
155 | """Given a list of file patterns, travel through the Buildroot source | |
156 | tree to find which packages are implemented by those file | |
157 | patterns, and return a list of those packages.""" | |
158 | packages = set() | |
159 | for fname in fnames: | |
160 | for root, dirs, files in os.walk(fname): | |
161 | for f in files: | |
162 | path = os.path.join(root, f) | |
163 | if fname_get_package_infra(path): | |
164 | pkg = os.path.splitext(f)[0] | |
165 | packages.add(pkg) | |
166 | return packages | |
167 | ||
49ffceef | 168 | |
183d9b65 TP |
169 | def parse_arches_from_config_in(fname): |
170 | """Given a path to an arch/Config.in.* file, parse it to get the list | |
171 | of BR2_ARCH values for this architecture.""" | |
172 | arches = set() | |
173 | with open(fname, "r") as f: | |
174 | parsing_arches = False | |
49ffceef RM |
175 | for line in f: |
176 | line = line.strip() | |
177 | if line == "config BR2_ARCH": | |
183d9b65 TP |
178 | parsing_arches = True |
179 | continue | |
180 | if parsing_arches: | |
49ffceef | 181 | m = re.match("^\s*default \"([^\"]*)\".*", line) |
183d9b65 TP |
182 | if m: |
183 | arches.add(m.group(1)) | |
184 | else: | |
185 | parsing_arches = False | |
186 | return arches | |
187 | ||
49ffceef | 188 | |
183d9b65 TP |
189 | def parse_developer_architectures(fnames): |
190 | """Given a list of file names, find the ones starting by | |
191 | 'arch/Config.in.', and use that to determine the architecture a | |
192 | developer is working on.""" | |
193 | arches = set() | |
194 | for fname in fnames: | |
195 | if not re.match("^.*/arch/Config\.in\..*$", fname): | |
196 | continue | |
197 | arches = arches | parse_arches_from_config_in(fname) | |
198 | return arches | |
199 | ||
49ffceef | 200 | |
183d9b65 TP |
201 | def parse_developer_infras(fnames): |
202 | infras = set() | |
203 | for fname in fnames: | |
204 | m = re.match("^package/pkg-([^.]*).mk$", fname) | |
205 | if m: | |
206 | infras.add(m.group(1)) | |
207 | return infras | |
208 | ||
49ffceef | 209 | |
caead542 VH |
210 | def parse_developer_defconfigs(fnames): |
211 | """Given a list of file names, returns the config names | |
212 | corresponding to defconfigs.""" | |
213 | return {os.path.basename(fname[:-10]) | |
214 | for fname in fnames | |
215 | if fname.endswith('_defconfig')} | |
216 | ||
217 | ||
e6358364 VH |
218 | def parse_developer_runtime_tests(fnames): |
219 | """Given a list of file names, returns the runtime tests | |
220 | corresponding to the file.""" | |
221 | all_files = [] | |
222 | # List all files recursively | |
223 | for fname in fnames: | |
224 | if os.path.isdir(fname): | |
225 | for root, _dirs, files in os.walk(fname): | |
226 | all_files += [os.path.join(root, f) for f in files] | |
227 | else: | |
228 | all_files.append(fname) | |
229 | ||
230 | # Get all runtime tests | |
231 | runtimes = set() | |
232 | for f in all_files: | |
233 | name = os.path.splitext(f)[0] | |
234 | if name in unittests: | |
235 | runtimes |= set(unittests[name]) | |
236 | return runtimes | |
237 | ||
238 | ||
183d9b65 TP |
239 | def parse_developers(basepath=None): |
240 | """Parse the DEVELOPERS file and return a list of Developer objects.""" | |
241 | developers = [] | |
242 | linen = 0 | |
49ffceef | 243 | if basepath is None: |
183d9b65 | 244 | basepath = os.getcwd() |
e6358364 VH |
245 | global unittests |
246 | unittests = list_unittests(os.path.join(basepath, 'support/testing')) | |
183d9b65 TP |
247 | with open(os.path.join(basepath, "DEVELOPERS"), "r") as f: |
248 | files = [] | |
249 | name = None | |
49ffceef RM |
250 | for line in f: |
251 | line = line.strip() | |
252 | if line.startswith("#"): | |
183d9b65 | 253 | continue |
49ffceef | 254 | elif line.startswith("N:"): |
183d9b65 | 255 | if name is not None or len(files) != 0: |
83f82bd6 PK |
256 | print("Syntax error in DEVELOPERS file, line %d" % linen, |
257 | file=sys.stderr) | |
49ffceef RM |
258 | name = line[2:].strip() |
259 | elif line.startswith("F:"): | |
260 | fname = line[2:].strip() | |
183d9b65 TP |
261 | dev_files = glob.glob(os.path.join(basepath, fname)) |
262 | if len(dev_files) == 0: | |
83f82bd6 PK |
263 | print("WARNING: '%s' doesn't match any file" % fname, |
264 | file=sys.stderr) | |
183d9b65 | 265 | files += dev_files |
49ffceef | 266 | elif line == "": |
183d9b65 TP |
267 | if not name: |
268 | continue | |
269 | developers.append(Developer(name, files)) | |
270 | files = [] | |
271 | name = None | |
272 | else: | |
83f82bd6 PK |
273 | print("Syntax error in DEVELOPERS file, line %d: '%s'" % (linen, line), |
274 | file=sys.stderr) | |
183d9b65 TP |
275 | return None |
276 | linen += 1 | |
277 | # handle last developer | |
278 | if name is not None: | |
279 | developers.append(Developer(name, files)) | |
280 | return developers | |
281 | ||
49ffceef | 282 | |
183d9b65 TP |
283 | def check_developers(developers, basepath=None): |
284 | """Look at the list of files versioned in Buildroot, and returns the | |
285 | list of files that are not handled by any developer""" | |
49ffceef | 286 | if basepath is None: |
183d9b65 TP |
287 | basepath = os.getcwd() |
288 | cmd = ["git", "--git-dir", os.path.join(basepath, ".git"), "ls-files"] | |
289 | files = subprocess.check_output(cmd).strip().split("\n") | |
290 | unhandled_files = [] | |
291 | for f in files: | |
292 | handled = False | |
293 | for d in developers: | |
294 | if d.hasfile(os.path.join(basepath, f)): | |
295 | handled = True | |
296 | break | |
297 | if not handled: | |
298 | unhandled_files.append(f) | |
299 | return unhandled_files |