]> Git Repo - buildroot-mgba.git/blame - utils/getdeveloperlib.py
utils/genrandconfig: test per-package directories
[buildroot-mgba.git] / utils / getdeveloperlib.py
CommitLineData
83f82bd6 1from __future__ import print_function
183d9b65
TP
2import os
3import re
183d9b65
TP
4import glob
5import subprocess
83f82bd6 6import sys
e6358364 7import unittest
183d9b65
TP
8
9#
10# Patch parsing functions
11#
12
13FIND_INFRA_IN_PATCH = re.compile("^\+\$\(eval \$\((host-)?([^-]*)-package\)\)$")
14
49ffceef 15
183d9b65
TP
16def 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
36FIND_INFRA_IN_MK = re.compile("^\$\(eval \$\((host-)?([^-]*)-package\)\)$")
37
49ffceef 38
183d9b65
TP
39def 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
57def 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
68def 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
86def 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
97def 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
109unittests = {}
110
a0a066a8 111
183d9b65
TP
112#
113# DEVELOPERS file parsing functions
114#
115
116class 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
154def 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
169def 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
189def 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
201def 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
210def 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
218def 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
239def 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
283def 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
This page took 0.3784 seconds and 4 git commands to generate.