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
28 INFRA_RE = re.compile("\$\(eval \$\(([a-z-]*)-package\)\)")
33 all_license_files = list()
35 def __init__(self, name, path):
39 self.has_license = False
40 self.has_license_files = False
46 return self.name.upper().replace("-", "_")
50 Fills in the .infras field
53 with open(self.path, 'r') as f:
56 match = INFRA_RE.match(l)
59 infra = match.group(1)
60 if infra.startswith("host-"):
61 self.infras.append(("host", infra[5:]))
63 self.infras.append(("target", infra))
65 def set_license(self):
67 Fills in the .has_license and .has_license_files fields
70 if var in self.all_licenses:
71 self.has_license = True
72 if var in self.all_license_files:
73 self.has_license_files = True
75 def set_hash_info(self):
77 Fills in the .has_hash field
79 hashpath = self.path.replace(".mk", ".hash")
80 self.has_hash = os.path.exists(hashpath)
82 def set_patch_count(self):
84 Fills in the .patch_count field
87 pkgdir = os.path.dirname(self.path)
88 for subdir, _, _ in os.walk(pkgdir):
89 self.patch_count += len(fnmatch.filter(os.listdir(subdir), '*.patch'))
91 def set_check_package_warnings(self):
93 Fills in the .warnings field
95 cmd = ["./utils/check-package"]
96 pkgdir = os.path.dirname(self.path)
97 for root, dirs, files in os.walk(pkgdir):
99 if f.endswith(".mk") or f.endswith(".hash") or f == "Config.in" or f == "Config.in.host":
100 cmd.append(os.path.join(root, f))
101 o = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()[1]
102 lines = o.splitlines()
104 m = re.match("^([0-9]*) warnings generated", line)
106 self.warnings = int(m.group(1))
109 def __eq__(self, other):
110 return self.path == other.path
112 def __lt__(self, other):
113 return self.path < other.path
116 return "%s (path='%s', license='%s', license_files='%s', hash='%s', patches=%d)" % \
117 (self.name, self.path, self.has_license, self.has_license_files, self.has_hash, self.patch_count)
120 def get_pkglist(npackages, package_list):
122 Builds the list of Buildroot packages, returning a list of Package
123 objects. Only the .name and .path fields of the Package object are
126 npackages: limit to N packages
127 package_list: limit to those packages in this list
129 WALK_USEFUL_SUBDIRS = ["boot", "linux", "package", "toolchain"]
130 WALK_EXCLUDES = ["boot/common.mk",
131 "linux/linux-ext-.*.mk",
132 "package/freescale-imx/freescale-imx.mk",
133 "package/gcc/gcc.mk",
134 "package/gstreamer/gstreamer.mk",
135 "package/gstreamer1/gstreamer1.mk",
136 "package/gtk2-themes/gtk2-themes.mk",
137 "package/matchbox/matchbox.mk",
138 "package/opengl/opengl.mk",
139 "package/qt5/qt5.mk",
140 "package/x11r7/x11r7.mk",
141 "package/doc-asciidoc.mk",
143 "package/nvidia-tegra23/nvidia-tegra23.mk",
144 "toolchain/toolchain-external/pkg-toolchain-external.mk",
145 "toolchain/toolchain-external/toolchain-external.mk",
146 "toolchain/toolchain.mk",
147 "toolchain/helpers.mk",
148 "toolchain/toolchain-wrapper.mk"]
151 for root, dirs, files in os.walk("."):
152 rootdir = root.split("/")
155 if rootdir[1] not in WALK_USEFUL_SUBDIRS:
158 if not f.endswith(".mk"):
162 if package_list and pkgname not in package_list:
164 pkgpath = os.path.join(root, f)
166 for exclude in WALK_EXCLUDES:
167 # pkgpath[2:] strips the initial './'
168 if re.match(exclude, pkgpath[2:]):
173 p = Package(pkgname, pkgpath)
176 if npackages and count == npackages:
181 def package_init_make_info():
183 o = subprocess.check_output(["make", "BR2_HAVE_DOT_CONFIG=y",
184 "-s", "printvars", "VARS=%_LICENSE"])
185 for l in o.splitlines():
186 # Get variable name and value
187 pkgvar, value = l.split("=")
189 # If present, strip HOST_ from variable name
190 if pkgvar.startswith("HOST_"):
196 # If value is "unknown", no license details available
197 if value == "unknown":
199 Package.all_licenses.append(pkgvar)
202 o = subprocess.check_output(["make", "BR2_HAVE_DOT_CONFIG=y",
203 "-s", "printvars", "VARS=%_LICENSE_FILES"])
204 for l in o.splitlines():
205 # Get variable name and value
206 pkgvar, value = l.split("=")
208 # If present, strip HOST_ from variable name
209 if pkgvar.startswith("HOST_"):
212 if pkgvar.endswith("_MANIFEST_LICENSE_FILES"):
215 # Strip _LICENSE_FILES
216 pkgvar = pkgvar[:-14]
218 Package.all_license_files.append(pkgvar)
221 def calculate_stats(packages):
222 stats = defaultdict(int)
224 # If packages have multiple infra, take the first one. For the
225 # vast majority of packages, the target and host infra are the
226 # same. There are very few packages that use a different infra
227 # for the host and target variants.
228 if len(pkg.infras) > 0:
229 infra = pkg.infras[0][1]
230 stats["infra-%s" % infra] += 1
232 stats["infra-unknown"] += 1
234 stats["license"] += 1
236 stats["no-license"] += 1
237 if pkg.has_license_files:
238 stats["license-files"] += 1
240 stats["no-license-files"] += 1
244 stats["no-hash"] += 1
245 stats["patches"] += pkg.patch_count
251 <script src=\"https://www.kryogenix.org/code/browser/sorttable/sorttable.js\"></script>
252 <style type=\"text/css\">
257 border: 1px solid black;
278 <title>Statistics of Buildroot packages</title>
281 <a href=\"#results\">Results</a><br/>
283 <p id=\"sortable_hint\"></p>
290 if (typeof sorttable === \"object\") {
291 document.getElementById(\"sortable_hint\").innerHTML =
292 \"hint: the table can be sorted by clicking the column headers\"
299 def infra_str(infra_list):
302 elif len(infra_list) == 1:
303 return "<b>%s</b><br/>%s" % (infra_list[0][1], infra_list[0][0])
304 elif infra_list[0][1] == infra_list[1][1]:
305 return "<b>%s</b><br/>%s + %s" % \
306 (infra_list[0][1], infra_list[0][0], infra_list[1][0])
308 return "<b>%s</b> (%s)<br/><b>%s</b> (%s)" % \
309 (infra_list[0][1], infra_list[0][0],
310 infra_list[1][1], infra_list[1][0])
320 def dump_html_pkg(f, pkg):
322 f.write(" <td>%s</td>\n" % pkg.path[2:])
325 td_class = ["centered"]
326 if pkg.patch_count == 0:
327 td_class.append("nopatches")
328 elif pkg.patch_count < 5:
329 td_class.append("somepatches")
331 td_class.append("lotsofpatches")
332 f.write(" <td class=\"%s\">%s</td>\n" %
333 (" ".join(td_class), str(pkg.patch_count)))
336 infra = infra_str(pkg.infras)
337 td_class = ["centered"]
338 if infra == "Unknown":
339 td_class.append("wrong")
341 td_class.append("correct")
342 f.write(" <td class=\"%s\">%s</td>\n" %
343 (" ".join(td_class), infra_str(pkg.infras)))
346 td_class = ["centered"]
348 td_class.append("correct")
350 td_class.append("wrong")
351 f.write(" <td class=\"%s\">%s</td>\n" %
352 (" ".join(td_class), boolean_str(pkg.has_license)))
355 td_class = ["centered"]
356 if pkg.has_license_files:
357 td_class.append("correct")
359 td_class.append("wrong")
360 f.write(" <td class=\"%s\">%s</td>\n" %
361 (" ".join(td_class), boolean_str(pkg.has_license_files)))
364 td_class = ["centered"]
366 td_class.append("correct")
368 td_class.append("wrong")
369 f.write(" <td class=\"%s\">%s</td>\n" %
370 (" ".join(td_class), boolean_str(pkg.has_hash)))
373 td_class = ["centered"]
374 if pkg.warnings == 0:
375 td_class.append("correct")
377 td_class.append("wrong")
378 f.write(" <td class=\"%s\">%d</td>\n" %
379 (" ".join(td_class), pkg.warnings))
384 def dump_html_all_pkgs(f, packages):
386 <table class=\"sortable\">
389 <td class=\"centered\">Patch count</td>
390 <td class=\"centered\">Infrastructure</td>
391 <td class=\"centered\">License</td>
392 <td class=\"centered\">License files</td>
393 <td class=\"centered\">Hash file</td>
394 <td class=\"centered\">Warnings</td>
397 for pkg in sorted(packages):
398 dump_html_pkg(f, pkg)
402 def dump_html_stats(f, stats):
403 f.write("<a id=\"results\"></a>\n")
405 infras = [infra[6:] for infra in stats.keys() if infra.startswith("infra-")]
407 f.write(" <tr><td>Packages using the <i>%s</i> infrastructure</td><td>%s</td></tr>\n" %
408 (infra, stats["infra-%s" % infra]))
409 f.write(" <tr><td>Packages having license information</td><td>%s</td></tr>\n" %
411 f.write(" <tr><td>Packages not having license information</td><td>%s</td></tr>\n" %
413 f.write(" <tr><td>Packages having license files information</td><td>%s</td></tr>\n" %
414 stats["license-files"])
415 f.write(" <tr><td>Packages not having license files information</td><td>%s</td></tr>\n" %
416 stats["no-license-files"])
417 f.write(" <tr><td>Packages having a hash file</td><td>%s</td></tr>\n" %
419 f.write(" <tr><td>Packages not having a hash file</td><td>%s</td></tr>\n" %
421 f.write(" <tr><td>Total number of patches</td><td>%s</td></tr>\n" %
423 f.write("</table>\n")
426 def dump_gen_info(f):
427 # Updated on Mon Feb 19 08:12:08 CET 2018, Git commit aa77030b8f5e41f1c53eb1c1ad664b8c814ba032
428 o = subprocess.check_output(["git", "log", "master", "-n", "1", "--pretty=format:%H"])
429 git_commit = o.splitlines()[0]
430 f.write("<p><i>Updated on %s, git commit %s</i></p>\n" %
431 (str(datetime.datetime.utcnow()), git_commit))
434 def dump_html(packages, stats, output):
435 with open(output, 'w') as f:
437 dump_html_all_pkgs(f, packages)
438 dump_html_stats(f, stats)
444 parser = argparse.ArgumentParser()
445 parser.add_argument('-o', dest='output', action='store', required=True,
446 help='HTML output file')
447 parser.add_argument('-n', dest='npackages', type=int, action='store',
448 help='Number of packages')
449 parser.add_argument('-p', dest='packages', action='store',
450 help='List of packages (comma separated)')
451 return parser.parse_args()
456 if args.npackages and args.packages:
457 print "ERROR: -n and -p are mutually exclusive"
460 package_list = args.packages.split(",")
463 print "Build package list ..."
464 packages = get_pkglist(args.npackages, package_list)
465 print "Getting package make info ..."
466 package_init_make_info()
467 print "Getting package details ..."
472 pkg.set_patch_count()
473 pkg.set_check_package_warnings()
474 print "Calculate stats"
475 stats = calculate_stats(packages)
477 dump_html(packages, stats, args.output)