3 ## This script generates the following Buildroot manual appendices:
4 ## - the package tables (one for the target, the other for host tools);
5 ## - the deprecated items.
8 ## - Samuel Martin <s.martin49@gmail.com>
10 ## Copyright (C) 2013 Samuel Martin
12 ## This program is free software; you can redistribute it and/or modify
13 ## it under the terms of the GNU General Public License as published by
14 ## the Free Software Foundation; either version 2 of the License, or
15 ## (at your option) any later version.
17 ## This program is distributed in the hope that it will be useful,
18 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
19 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 ## GNU General Public License for more details.
22 ## You should have received a copy of the GNU General Public License
23 ## along with this program; if not, write to the Free Software
24 ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
27 ## Note about python2.
29 ## This script can currently only be run using python2 interpreter due to
30 ## its kconfiglib dependency (which is not yet python3 friendly).
32 from __future__ import print_function
33 from __future__ import unicode_literals
39 from argparse import ArgumentParser
45 Could not find the module 'kconfiglib' in the PYTHONPATH:
47 message += "\n".join([" {0}".format(path) for path in sys.path])
50 Make sure the Kconfiglib directory is in the PYTHONPATH, then relaunch the
53 You can get kconfiglib from:
54 https://github.com/ulfalizer/Kconfiglib
58 sys.stderr.write(message)
62 def get_symbol_subset(root, filter_func):
63 """ Return a generator of kconfig items.
65 :param root_item: Root item of the generated subset of items
66 :param filter_func: Filter function
69 if hasattr(root, "get_items"):
70 get_items = root.get_items
71 elif hasattr(root, "get_top_level_items"):
72 get_items = root.get_top_level_items
74 message = "The symbol does not contain any subset of symbols"
75 raise Exception(message)
76 for item in get_items():
78 if not filter_func(item):
81 elif item.is_menu() or item.is_choice():
82 for i in get_symbol_subset(item, filter_func):
86 def get_symbol_parents(item, root=None, enable_choice=False):
87 """ Return the list of the item's parents. The last item of the list is
88 the closest parent, the first the furthest.
90 :param item: Item from which the parent list is generated
91 :param root: Root item stopping the search (not included in the
93 :param enable_choice: Flag enabling choices to appear in the parent list
96 parent = item.get_parent()
98 while parent and parent != root:
100 parents.append(parent.get_title())
101 elif enable_choice and parent.is_choice():
102 parents.append(parent.prompts[0][0])
103 parent = parent.get_parent()
104 if isinstance(root, kconfiglib.Menu) or \
105 (enable_choice and isinstance(root, kconfiglib.Choice)):
106 parents.append("") # Dummy empty parent to get a leading arrow ->
111 def format_asciidoc_table(root, get_label_func, filter_func=lambda x: True,
112 format_func=lambda x: x,
113 enable_choice=False, sorted=True,
115 """ Return the asciidoc formatted table of the items and their location.
117 :param root: Root item of the item subset
118 :param get_label_func: Item's label getter function
119 :param filter_func: Filter function to apply on the item subset
120 :param format_func: Function to format a symbol and the table header
121 :param enable_choice: Enable choices to appear as part of the item's
123 :param sorted: Flag to alphabetically sort the table
128 for item in get_symbol_subset(root, filter_func):
129 lines.append(format_func(what="symbol", symbol=item, root=root,
130 get_label_func=get_label_func,
131 enable_choice=enable_choice))
133 lines.sort(key=lambda x: x.lower())
134 table = ":halign: center\n\n"
135 width, columns = format_func(what="layout")
136 table = "[width=\"{0}\",cols=\"{1}\",options=\"header\"]\n".format(width, columns)
137 table += "|===================================================\n"
138 table += format_func(what="header", header=item_label, root=root)
139 table += "\n" + "".join(lines) + "\n"
140 table += "|===================================================\n"
145 """ Buildroot configuration object.
148 root_config = "Config.in"
149 package_dirname = "package"
150 package_prefixes = ["BR2_PACKAGE_", "BR2_PACKAGE_HOST_"]
151 re_pkg_prefix = re.compile(r"^(" + "|".join(package_prefixes) + ").*")
152 deprecated_symbol = "BR2_DEPRECATED"
155 // Automatically generated list for Buildroot manual.
163 'filename': "package-list",
164 'root_menu': "Target packages",
165 'filter': "_is_real_package",
166 'format': "_format_symbol_prompt_location",
170 'filename': "host-package-list",
171 'root_menu': "Host utilities",
172 'filter': "_is_real_package",
173 'format': "_format_symbol_prompt",
176 'virtual-packages': {
177 'filename': "virtual-package-list",
178 'root_menu': "Target packages",
179 'filter': "_is_virtual_package",
180 'format': "_format_symbol_virtual",
184 'filename': "deprecated-list",
186 'filter': "_is_deprecated",
187 'format': "_format_symbol_prompt_location",
193 self.base_dir = os.environ.get("TOPDIR")
194 self.output_dir = os.environ.get("O")
195 self.package_dir = os.path.join(self.base_dir, self.package_dirname)
196 # The kconfiglib requires an environment variable named "srctree" to
197 # load the configuration, so set it.
198 os.environ.update({'srctree': self.base_dir})
199 self.config = kconfiglib.Config(os.path.join(self.base_dir,
201 self._deprecated = self.config.get_symbol(self.deprecated_symbol)
203 self.gen_date = datetime.datetime.utcnow()
204 self.br_version_full = os.environ.get("BR2_VERSION_FULL")
205 if self.br_version_full and self.br_version_full.endswith("-git"):
206 self.br_version_full = self.br_version_full[:-4]
207 if not self.br_version_full:
208 self.br_version_full = "undefined"
210 def _get_package_symbols(self, package_name):
211 """ Return a tuple containing the target and host package symbol.
214 symbols = re.sub("[-+.]", "_", package_name)
215 symbols = symbols.upper()
216 symbols = tuple([prefix + symbols for prefix in self.package_prefixes])
219 def _is_deprecated(self, symbol):
220 """ Return True if the symbol is marked as deprecated, otherwise False.
223 # This also catches BR2_DEPRECATED_SINCE_xxxx_xx
224 return bool([ symbol for x in symbol.get_referenced_symbols()
225 if x.get_name().startswith(self._deprecated.get_name()) ])
227 def _is_package(self, symbol, type='real'):
228 """ Return True if the symbol is a package or a host package, otherwise
231 :param symbol: The symbol to check
232 :param type: Limit to 'real' or 'virtual' types of packages,
233 with 'real' being the default.
234 Note: only 'real' is (implictly) handled for now
237 if not symbol.is_symbol():
239 if type == 'real' and not symbol.prompts:
241 if type == 'virtual' and symbol.prompts:
243 if not self.re_pkg_prefix.match(symbol.get_name()):
245 pkg_name = self._get_pkg_name(symbol)
247 pattern = "^(HOST_)?" + pkg_name + "$"
248 pattern = re.sub("_", ".", pattern)
249 pattern = re.compile(pattern, re.IGNORECASE)
250 # Here, we cannot just check for the location of the Config.in because
251 # of the "virtual" package.
253 # So, to check that a symbol is a package (not a package option or
254 # anything else), we check for the existence of the package *.mk file.
256 # By the way, to actually check for a package, we should grep all *.mk
257 # files for the following regex:
258 # "\$\(eval \$\((host-)?(generic|autotools|cmake)-package\)\)"
260 # Implementation details:
262 # * The package list is generated from the *.mk file existence, the
263 # first time this function is called. Despite the memory consumption,
264 # this list is stored because the execution time of this script is
265 # noticeably shorter than rescanning the package sub-tree for each
267 if not hasattr(self, "_package_list"):
269 for _, _, files in os.walk(self.package_dir):
270 for file_ in (f for f in files if f.endswith(".mk")):
271 pkg_list.append(re.sub(r"(.*?)\.mk", r"\1", file_))
272 setattr(self, "_package_list", pkg_list)
273 for pkg in getattr(self, "_package_list"):
275 if pattern.match(pkg) and not self._exists_virt_symbol(pkg):
277 if type == 'virtual':
278 if pattern.match('has_' + pkg):
282 def _is_real_package(self, symbol):
283 return self._is_package(symbol, 'real')
285 def _is_virtual_package(self, symbol):
286 return self._is_package(symbol, 'virtual')
288 def _exists_virt_symbol(self, pkg_name):
289 """ Return True if a symbol exists that defines the package as
290 a virtual package, False otherwise
292 :param pkg_name: The name of the package, for which to check if
293 a symbol exists defining it as a virtual package
296 virt_pattern = "BR2_PACKAGE_HAS_" + pkg_name + "$"
297 virt_pattern = re.sub("_", ".", virt_pattern)
298 virt_pattern = re.compile(virt_pattern, re.IGNORECASE)
299 for sym in self.config:
300 if virt_pattern.match(sym.get_name()):
304 def _get_pkg_name(self, symbol):
305 """ Return the package name of the specified symbol.
307 :param symbol: The symbol to get the package name of
311 return re.sub("BR2_PACKAGE_(HOST_)?(.*)", r"\2", symbol.get_name())
313 def _get_symbol_label(self, symbol, mark_deprecated=True):
314 """ Return the label (a.k.a. prompt text) of the symbol.
316 :param symbol: The symbol
317 :param mark_deprecated: Append a 'deprecated' to the label
320 label = symbol.prompts[0][0]
321 if self._is_deprecated(symbol) and mark_deprecated:
322 label += " *(deprecated)*"
325 def _format_symbol_prompt(self, what=None, symbol=None, root=None,
326 enable_choice=False, header=None,
327 get_label_func=lambda x: x):
329 return ( "30%", "^1" )
332 return "| {0:<40}\n".format(header)
335 return "| {0:<40}\n".format(get_label_func(symbol))
337 message = "Invalid argument 'what': '%s'\n" % str(what)
338 message += "Allowed values are: 'layout', 'header' and 'symbol'"
339 raise Exception(message)
341 def _format_symbol_prompt_location(self, what=None, symbol=None, root=None,
342 enable_choice=False, header=None,
343 get_label_func=lambda x: x):
345 return ( "100%", "^1,4" )
348 if hasattr(root, "get_title"):
349 loc_label = get_symbol_parents(root, None, enable_choice=enable_choice)
350 loc_label += [root.get_title(), "..."]
352 loc_label = ["Location"]
353 return "| {0:<40} <| {1}\n".format(header, " -> ".join(loc_label))
356 parents = get_symbol_parents(symbol, root, enable_choice)
357 return "| {0:<40} <| {1}\n".format(get_label_func(symbol),
358 " -> ".join(parents))
360 message = "Invalid argument 'what': '%s'\n" % str(what)
361 message += "Allowed values are: 'layout', 'header' and 'symbol'"
362 raise Exception(message)
364 def _format_symbol_virtual(self, what=None, symbol=None, root=None,
365 enable_choice=False, header=None,
366 get_label_func=lambda x: "?"):
367 def _symbol_is_legacy(symbol):
368 selects = [ s.get_name() for s in symbol.get_selected_symbols() ]
369 return ("BR2_LEGACY" in selects)
371 def _get_parent_package(sym):
372 if self._is_real_package(sym):
374 # Trim the symbol name from its last component (separated with
375 # underscores), until we either find a symbol which is a real
376 # package, or until we have no component (i.e. just 'BR2')
377 name = sym.get_name()
379 name = name.rsplit("_", 1)[0]
380 s = self.config.get_symbol(name)
383 if self._is_real_package(s):
387 def _get_providers(symbol):
389 for sym in self.config:
390 if not sym.is_symbol():
392 if _symbol_is_legacy(sym):
394 selects = sym.get_selected_symbols()
400 l = self._get_symbol_label(sym,False)
401 parent_pkg = _get_parent_package(sym)
402 if parent_pkg is not None:
403 l = self._get_symbol_label(parent_pkg, False) \
407 providers.extend(_get_providers(sym))
411 return ( "100%", "^1,4,4" )
414 return "| {0:<20} <| {1:<32} <| Providers\n".format("Virtual packages", "Symbols")
417 pkg = re.sub(r"^BR2_PACKAGE_HAS_(.+)$", r"\1", symbol.get_name())
418 providers = _get_providers(symbol)
420 return "| {0:<20} <| {1:<32} <| {2}\n".format(pkg.lower(),
421 '+' + symbol.get_name() + '+',
422 ", ".join(providers))
424 message = "Invalid argument 'what': '%s'\n" % str(what)
425 message += "Allowed values are: 'layout', 'header' and 'symbol'"
426 raise Exception(message)
429 def print_list(self, list_type, enable_choice=True, enable_deprecated=True,
430 dry_run=False, output=None):
431 """ Print the requested list. If not dry run, then the list is
432 automatically written in its own file.
434 :param list_type: The list type to be generated
435 :param enable_choice: Flag enabling choices to appear in the list
436 :param enable_deprecated: Flag enabling deprecated items to appear in
438 :param dry_run: Dry run (print the list in stdout instead of
439 writing the list file
442 def _get_menu(title):
443 """ Return the first symbol menu matching the given title.
446 menus = self.config.get_menus()
447 menu = [m for m in menus if m.get_title().lower() == title.lower()]
449 message = "No such menu: '{0}'".format(title)
450 raise Exception(message)
453 list_config = self.list_info[list_type]
454 root_title = list_config.get('root_menu')
456 root_item = _get_menu(root_title)
458 root_item = self.config
459 filter_ = getattr(self, list_config.get('filter'))
460 filter_func = lambda x: filter_(x)
461 format_func = getattr(self, list_config.get('format'))
462 if not enable_deprecated and list_type != "deprecated":
463 filter_func = lambda x: filter_(x) and not self._is_deprecated(x)
464 mark_depr = list_type != "deprecated"
465 get_label = lambda x: self._get_symbol_label(x, mark_depr)
466 item_label = "Features" if list_type == "deprecated" else "Packages"
468 table = format_asciidoc_table(root_item, get_label,
469 filter_func=filter_func,
470 format_func=format_func,
471 enable_choice=enable_choice,
472 sorted=list_config.get('sorted'),
473 item_label=item_label)
475 content = self.list_in.format(table=table)
482 output_dir = self.output_dir
484 print("Warning: Undefined output directory.")
485 print("\tUse source directory as output location.")
486 output_dir = self.base_dir
487 output = os.path.join(output_dir,
488 list_config.get('filename') + ".txt")
489 if not os.path.exists(os.path.dirname(output)):
490 os.makedirs(os.path.dirname(output))
491 print("Writing the {0} list in:\n\t{1}".format(list_type, output))
492 with open(output, 'w') as fout:
496 if __name__ == '__main__':
497 list_types = ['target-packages', 'host-packages', 'virtual-packages', 'deprecated']
498 parser = ArgumentParser()
499 parser.add_argument("list_type", nargs="?", choices=list_types,
501 Generate the given list (generate all lists if unspecified)""")
502 parser.add_argument("-n", "--dry-run", dest="dry_run", action='store_true',
503 help="Output the generated list to stdout")
504 parser.add_argument("--output-target", dest="output_target",
505 help="Output target package file")
506 parser.add_argument("--output-host", dest="output_host",
507 help="Output host package file")
508 parser.add_argument("--output-virtual", dest="output_virtual",
509 help="Output virtual package file")
510 parser.add_argument("--output-deprecated", dest="output_deprecated",
511 help="Output deprecated file")
512 args = parser.parse_args()
513 lists = [args.list_type] if args.list_type else list_types
514 buildroot = Buildroot()
515 for list_name in lists:
516 output = getattr(args, "output_" + list_name.split("-", 1)[0])
517 buildroot.print_list(list_name, dry_run=args.dry_run, output=output)