3 # Copyright (C) 2009 by Thomas Petazzoni <thomas.petazzoni@free-electrons.com>
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 2 of the License, or
8 # (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 # General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23 from collections import defaultdict
27 INFRA_RE = re.compile("\$\(eval \$\(([a-z-]*)-package\)\)")
32 all_license_files = list()
34 def __init__(self, name, path):
38 self.has_license = False
39 self.has_license_files = False
45 return self.name.upper().replace("-", "_")
49 Fills in the .infras field
52 with open(self.path, 'r') as f:
55 match = INFRA_RE.match(l)
58 infra = match.group(1)
59 if infra.startswith("host-"):
60 self.infras.append(("host", infra[5:]))
62 self.infras.append(("target", infra))
64 def set_license(self):
66 Fills in the .has_license and .has_license_files fields
69 if var in self.all_licenses:
70 self.has_license = True
71 if var in self.all_license_files:
72 self.has_license_files = True
74 def set_hash_info(self):
76 Fills in the .has_hash field
78 hashpath = self.path.replace(".mk", ".hash")
79 self.has_hash = os.path.exists(hashpath)
81 def set_patch_count(self):
83 Fills in the .patch_count field
86 pkgdir = os.path.dirname(self.path)
87 for subdir, _, _ in os.walk(pkgdir):
88 self.patch_count += len(fnmatch.filter(os.listdir(subdir), '*.patch'))
90 def set_check_package_warnings(self):
92 Fills in the .warnings field
94 cmd = ["./utils/check-package"]
95 pkgdir = os.path.dirname(self.path)
96 for root, dirs, files in os.walk(pkgdir):
98 if f.endswith(".mk") or f.endswith(".hash") or f == "Config.in" or f == "Config.in.host":
99 cmd.append(os.path.join(root, f))
100 o = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()[1]
101 lines = o.splitlines()
103 m = re.match("^([0-9]*) warnings generated", line)
105 self.warnings = int(m.group(1))
108 def __eq__(self, other):
109 return self.path == other.path
111 def __lt__(self, other):
112 return self.path < other.path
115 return "%s (path='%s', license='%s', license_files='%s', hash='%s', patches=%d)" % \
116 (self.name, self.path, self.has_license, self.has_license_files, self.has_hash, self.patch_count)
121 Builds the list of Buildroot packages, returning a list of Package
122 objects. Only the .name and .path fields of the Package object are
125 WALK_USEFUL_SUBDIRS = ["boot", "linux", "package", "toolchain"]
126 WALK_EXCLUDES = ["boot/common.mk",
127 "linux/linux-ext-.*.mk",
128 "package/freescale-imx/freescale-imx.mk",
129 "package/gcc/gcc.mk",
130 "package/gstreamer/gstreamer.mk",
131 "package/gstreamer1/gstreamer1.mk",
132 "package/gtk2-themes/gtk2-themes.mk",
133 "package/matchbox/matchbox.mk",
134 "package/opengl/opengl.mk",
135 "package/qt5/qt5.mk",
136 "package/x11r7/x11r7.mk",
137 "package/doc-asciidoc.mk",
139 "package/nvidia-tegra23/nvidia-tegra23.mk",
140 "toolchain/toolchain-external/pkg-toolchain-external.mk",
141 "toolchain/toolchain-external/toolchain-external.mk",
142 "toolchain/toolchain.mk",
143 "toolchain/helpers.mk",
144 "toolchain/toolchain-wrapper.mk"]
146 for root, dirs, files in os.walk("."):
147 rootdir = root.split("/")
150 if rootdir[1] not in WALK_USEFUL_SUBDIRS:
153 if not f.endswith(".mk"):
157 pkgpath = os.path.join(root, f)
159 for exclude in WALK_EXCLUDES:
160 # pkgpath[2:] strips the initial './'
161 if re.match(exclude, pkgpath[2:]):
166 p = Package(pkgname, pkgpath)
171 def package_init_make_info():
173 o = subprocess.check_output(["make", "BR2_HAVE_DOT_CONFIG=y",
174 "-s", "printvars", "VARS=%_LICENSE"])
175 for l in o.splitlines():
176 # Get variable name and value
177 pkgvar, value = l.split("=")
179 # If present, strip HOST_ from variable name
180 if pkgvar.startswith("HOST_"):
186 # If value is "unknown", no license details available
187 if value == "unknown":
189 Package.all_licenses.append(pkgvar)
192 o = subprocess.check_output(["make", "BR2_HAVE_DOT_CONFIG=y",
193 "-s", "printvars", "VARS=%_LICENSE_FILES"])
194 for l in o.splitlines():
195 # Get variable name and value
196 pkgvar, value = l.split("=")
198 # If present, strip HOST_ from variable name
199 if pkgvar.startswith("HOST_"):
202 if pkgvar.endswith("_MANIFEST_LICENSE_FILES"):
205 # Strip _LICENSE_FILES
206 pkgvar = pkgvar[:-14]
208 Package.all_license_files.append(pkgvar)
211 def calculate_stats(packages):
212 stats = defaultdict(int)
214 # If packages have multiple infra, take the first one. For the
215 # vast majority of packages, the target and host infra are the
216 # same. There are very few packages that use a different infra
217 # for the host and target variants.
218 if len(pkg.infras) > 0:
219 infra = pkg.infras[0][1]
220 stats["infra-%s" % infra] += 1
222 stats["infra-unknown"] += 1
224 stats["license"] += 1
226 stats["no-license"] += 1
227 if pkg.has_license_files:
228 stats["license-files"] += 1
230 stats["no-license-files"] += 1
234 stats["no-hash"] += 1
235 stats["patches"] += pkg.patch_count
241 <script src=\"https://www.kryogenix.org/code/browser/sorttable/sorttable.js\"></script>
242 <style type=\"text/css\">
247 border: 1px solid black;
268 <title>Statistics of Buildroot packages</title>
271 <a href=\"#results\">Results</a><br/>
273 <p id=\"sortable_hint\"></p>
280 if (typeof sorttable === \"object\") {
281 document.getElementById(\"sortable_hint\").innerHTML =
282 \"hint: the table can be sorted by clicking the column headers\"
289 def infra_str(infra_list):
292 elif len(infra_list) == 1:
293 return "<b>%s</b><br/>%s" % (infra_list[0][1], infra_list[0][0])
294 elif infra_list[0][1] == infra_list[1][1]:
295 return "<b>%s</b><br/>%s + %s" % \
296 (infra_list[0][1], infra_list[0][0], infra_list[1][0])
298 return "<b>%s</b> (%s)<br/><b>%s</b> (%s)" % \
299 (infra_list[0][1], infra_list[0][0],
300 infra_list[1][1], infra_list[1][0])
310 def dump_html_pkg(f, pkg):
312 f.write(" <td>%s</td>\n" % pkg.path[2:])
315 td_class = ["centered"]
316 if pkg.patch_count == 0:
317 td_class.append("nopatches")
318 elif pkg.patch_count < 5:
319 td_class.append("somepatches")
321 td_class.append("lotsofpatches")
322 f.write(" <td class=\"%s\">%s</td>\n" %
323 (" ".join(td_class), str(pkg.patch_count)))
326 infra = infra_str(pkg.infras)
327 td_class = ["centered"]
328 if infra == "Unknown":
329 td_class.append("wrong")
331 td_class.append("correct")
332 f.write(" <td class=\"%s\">%s</td>\n" %
333 (" ".join(td_class), infra_str(pkg.infras)))
336 td_class = ["centered"]
338 td_class.append("correct")
340 td_class.append("wrong")
341 f.write(" <td class=\"%s\">%s</td>\n" %
342 (" ".join(td_class), boolean_str(pkg.has_license)))
345 td_class = ["centered"]
346 if pkg.has_license_files:
347 td_class.append("correct")
349 td_class.append("wrong")
350 f.write(" <td class=\"%s\">%s</td>\n" %
351 (" ".join(td_class), boolean_str(pkg.has_license_files)))
354 td_class = ["centered"]
356 td_class.append("correct")
358 td_class.append("wrong")
359 f.write(" <td class=\"%s\">%s</td>\n" %
360 (" ".join(td_class), boolean_str(pkg.has_hash)))
363 td_class = ["centered"]
364 if pkg.warnings == 0:
365 td_class.append("correct")
367 td_class.append("wrong")
368 f.write(" <td class=\"%s\">%d</td>\n" %
369 (" ".join(td_class), pkg.warnings))
374 def dump_html_all_pkgs(f, packages):
376 <table class=\"sortable\">
379 <td class=\"centered\">Patch count</td>
380 <td class=\"centered\">Infrastructure</td>
381 <td class=\"centered\">License</td>
382 <td class=\"centered\">License files</td>
383 <td class=\"centered\">Hash file</td>
384 <td class=\"centered\">Warnings</td>
387 for pkg in sorted(packages):
388 dump_html_pkg(f, pkg)
392 def dump_html_stats(f, stats):
393 f.write("<a id=\"results\"></a>\n")
395 infras = [infra[6:] for infra in stats.keys() if infra.startswith("infra-")]
397 f.write(" <tr><td>Packages using the <i>%s</i> infrastructure</td><td>%s</td></tr>\n" %
398 (infra, stats["infra-%s" % infra]))
399 f.write(" <tr><td>Packages having license information</td><td>%s</td></tr>\n" %
401 f.write(" <tr><td>Packages not having license information</td><td>%s</td></tr>\n" %
403 f.write(" <tr><td>Packages having license files information</td><td>%s</td></tr>\n" %
404 stats["license-files"])
405 f.write(" <tr><td>Packages not having license files information</td><td>%s</td></tr>\n" %
406 stats["no-license-files"])
407 f.write(" <tr><td>Packages having a hash file</td><td>%s</td></tr>\n" %
409 f.write(" <tr><td>Packages not having a hash file</td><td>%s</td></tr>\n" %
411 f.write(" <tr><td>Total number of patches</td><td>%s</td></tr>\n" %
413 f.write("</table>\n")
416 def dump_gen_info(f):
417 # Updated on Mon Feb 19 08:12:08 CET 2018, Git commit aa77030b8f5e41f1c53eb1c1ad664b8c814ba032
418 o = subprocess.check_output(["git", "log", "master", "-n", "1", "--pretty=format:%H"])
419 git_commit = o.splitlines()[0]
420 f.write("<p><i>Updated on %s, git commit %s</i></p>\n" %
421 (str(datetime.datetime.utcnow()), git_commit))
424 def dump_html(packages, stats, output):
425 with open(output, 'w') as f:
427 dump_html_all_pkgs(f, packages)
428 dump_html_stats(f, stats)
434 parser = argparse.ArgumentParser()
435 parser.add_argument('-o', dest='output', action='store', required=True,
436 help='HTML output file')
437 return parser.parse_args()
442 print "Build package list ..."
443 packages = get_pkglist()
444 print "Getting package make info ..."
445 package_init_make_info()
446 print "Getting package details ..."
451 pkg.set_patch_count()
452 pkg.set_check_package_warnings()
453 print "Calculate stats"
454 stats = calculate_stats(packages)
456 dump_html(packages, stats, args.output)