X-Git-Url: https://review.fuel-infra.org/gitweb?a=blobdiff_plain;f=cirros-testvm%2Fsrc-cirros%2Fbuildroot-2015.05%2Fsupport%2Fscripts%2Fgen-manual-lists.py;fp=cirros-testvm%2Fsrc-cirros%2Fbuildroot-2015.05%2Fsupport%2Fscripts%2Fgen-manual-lists.py;h=ec59030d0cbde922dfbc94714c25543ad9d21809;hb=b0a0f15dfaa205161a7fcb20cf1b8cd4948c2ef3;hp=0000000000000000000000000000000000000000;hpb=c6ac3cd55ee2da956195eee393b0882105dfad4e;p=packages%2Ftrusty%2Fcirros-testvm.git diff --git a/cirros-testvm/src-cirros/buildroot-2015.05/support/scripts/gen-manual-lists.py b/cirros-testvm/src-cirros/buildroot-2015.05/support/scripts/gen-manual-lists.py new file mode 100644 index 0000000..ec59030 --- /dev/null +++ b/cirros-testvm/src-cirros/buildroot-2015.05/support/scripts/gen-manual-lists.py @@ -0,0 +1,517 @@ +## gen-manual-lists.py +## +## This script generates the following Buildroot manual appendices: +## - the package tables (one for the target, the other for host tools); +## - the deprecated items. +## +## Author(s): +## - Samuel Martin +## +## Copyright (C) 2013 Samuel Martin +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, write to the Free Software +## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +## + +## Note about python2. +## +## This script can currently only be run using python2 interpreter due to +## its kconfiglib dependency (which is not yet python3 friendly). + +from __future__ import print_function +from __future__ import unicode_literals + +import os +import re +import sys +import datetime +from argparse import ArgumentParser + +try: + import kconfiglib +except ImportError: + message = """ +Could not find the module 'kconfiglib' in the PYTHONPATH: +""" + message += "\n".join([" {0}".format(path) for path in sys.path]) + message += """ + +Make sure the Kconfiglib directory is in the PYTHONPATH, then relaunch the +script. + +You can get kconfiglib from: + https://github.com/ulfalizer/Kconfiglib + + +""" + sys.stderr.write(message) + raise + + +def get_symbol_subset(root, filter_func): + """ Return a generator of kconfig items. + + :param root_item: Root item of the generated subset of items + :param filter_func: Filter function + + """ + if hasattr(root, "get_items"): + get_items = root.get_items + elif hasattr(root, "get_top_level_items"): + get_items = root.get_top_level_items + else: + message = "The symbol does not contain any subset of symbols" + raise Exception(message) + for item in get_items(): + if item.is_symbol(): + if not filter_func(item): + continue + yield item + elif item.is_menu() or item.is_choice(): + for i in get_symbol_subset(item, filter_func): + yield i + + +def get_symbol_parents(item, root=None, enable_choice=False): + """ Return the list of the item's parents. The last item of the list is + the closest parent, the first the furthest. + + :param item: Item from which the parent list is generated + :param root: Root item stopping the search (not included in the + parent list) + :param enable_choice: Flag enabling choices to appear in the parent list + + """ + parent = item.get_parent() + parents = [] + while parent and parent != root: + if parent.is_menu(): + parents.append(parent.get_title()) + elif enable_choice and parent.is_choice(): + parents.append(parent.prompts[0][0]) + parent = parent.get_parent() + if isinstance(root, kconfiglib.Menu) or \ + (enable_choice and isinstance(root, kconfiglib.Choice)): + parents.append("") # Dummy empty parent to get a leading arrow -> + parents.reverse() + return parents + + +def format_asciidoc_table(root, get_label_func, filter_func=lambda x: True, + format_func=lambda x: x, + enable_choice=False, sorted=True, + item_label=None): + """ Return the asciidoc formatted table of the items and their location. + + :param root: Root item of the item subset + :param get_label_func: Item's label getter function + :param filter_func: Filter function to apply on the item subset + :param format_func: Function to format a symbol and the table header + :param enable_choice: Enable choices to appear as part of the item's + location + :param sorted: Flag to alphabetically sort the table + + """ + + lines = [] + for item in get_symbol_subset(root, filter_func): + lines.append(format_func(what="symbol", symbol=item, root=root, + get_label_func=get_label_func, + enable_choice=enable_choice)) + if sorted: + lines.sort(key=lambda x: x.lower()) + table = ":halign: center\n\n" + width, columns = format_func(what="layout") + table = "[width=\"{0}\",cols=\"{1}\",options=\"header\"]\n".format(width, columns) + table += "|===================================================\n" + table += format_func(what="header", header=item_label, root=root) + table += "\n" + "".join(lines) + "\n" + table += "|===================================================\n" + return table + + +class Buildroot: + """ Buildroot configuration object. + + """ + root_config = "Config.in" + package_dirname = "package" + package_prefixes = ["BR2_PACKAGE_", "BR2_PACKAGE_HOST_"] + re_pkg_prefix = re.compile(r"^(" + "|".join(package_prefixes) + ").*") + deprecated_symbol = "BR2_DEPRECATED" + list_in = """\ +// +// Automatically generated list for Buildroot manual. +// + +{table} +""" + + list_info = { + 'target-packages': { + 'filename': "package-list", + 'root_menu': "Target packages", + 'filter': "_is_real_package", + 'format': "_format_symbol_prompt_location", + 'sorted': True, + }, + 'host-packages': { + 'filename': "host-package-list", + 'root_menu': "Host utilities", + 'filter': "_is_real_package", + 'format': "_format_symbol_prompt", + 'sorted': True, + }, + 'virtual-packages': { + 'filename': "virtual-package-list", + 'root_menu': "Target packages", + 'filter': "_is_virtual_package", + 'format': "_format_symbol_virtual", + 'sorted': True, + }, + 'deprecated': { + 'filename': "deprecated-list", + 'root_menu': None, + 'filter': "_is_deprecated", + 'format': "_format_symbol_prompt_location", + 'sorted': False, + }, + } + + def __init__(self): + self.base_dir = os.environ.get("TOPDIR") + self.output_dir = os.environ.get("O") + self.package_dir = os.path.join(self.base_dir, self.package_dirname) + # The kconfiglib requires an environment variable named "srctree" to + # load the configuration, so set it. + os.environ.update({'srctree': self.base_dir}) + self.config = kconfiglib.Config(os.path.join(self.base_dir, + self.root_config)) + self._deprecated = self.config.get_symbol(self.deprecated_symbol) + + self.gen_date = datetime.datetime.utcnow() + self.br_version_full = os.environ.get("BR2_VERSION_FULL") + if self.br_version_full and self.br_version_full.endswith("-git"): + self.br_version_full = self.br_version_full[:-4] + if not self.br_version_full: + self.br_version_full = "undefined" + + def _get_package_symbols(self, package_name): + """ Return a tuple containing the target and host package symbol. + + """ + symbols = re.sub("[-+.]", "_", package_name) + symbols = symbols.upper() + symbols = tuple([prefix + symbols for prefix in self.package_prefixes]) + return symbols + + def _is_deprecated(self, symbol): + """ Return True if the symbol is marked as deprecated, otherwise False. + + """ + # This also catches BR2_DEPRECATED_SINCE_xxxx_xx + return bool([ symbol for x in symbol.get_referenced_symbols() + if x.get_name().startswith(self._deprecated.get_name()) ]) + + def _is_package(self, symbol, type='real'): + """ Return True if the symbol is a package or a host package, otherwise + False. + + :param symbol: The symbol to check + :param type: Limit to 'real' or 'virtual' types of packages, + with 'real' being the default. + Note: only 'real' is (implictly) handled for now + + """ + if not symbol.is_symbol(): + return False + if type == 'real' and not symbol.prompts: + return False + if type == 'virtual' and symbol.prompts: + return False + if not self.re_pkg_prefix.match(symbol.get_name()): + return False + pkg_name = self._get_pkg_name(symbol) + + pattern = "^(HOST_)?" + pkg_name + "$" + pattern = re.sub("_", ".", pattern) + pattern = re.compile(pattern, re.IGNORECASE) + # Here, we cannot just check for the location of the Config.in because + # of the "virtual" package. + # + # So, to check that a symbol is a package (not a package option or + # anything else), we check for the existence of the package *.mk file. + # + # By the way, to actually check for a package, we should grep all *.mk + # files for the following regex: + # "\$\(eval \$\((host-)?(generic|autotools|cmake)-package\)\)" + # + # Implementation details: + # + # * The package list is generated from the *.mk file existence, the + # first time this function is called. Despite the memory consumption, + # this list is stored because the execution time of this script is + # noticeably shorter than rescanning the package sub-tree for each + # symbol. + if not hasattr(self, "_package_list"): + pkg_list = [] + for _, _, files in os.walk(self.package_dir): + for file_ in (f for f in files if f.endswith(".mk")): + pkg_list.append(re.sub(r"(.*?)\.mk", r"\1", file_)) + setattr(self, "_package_list", pkg_list) + for pkg in getattr(self, "_package_list"): + if type == 'real': + if pattern.match(pkg) and not self._exists_virt_symbol(pkg): + return True + if type == 'virtual': + if pattern.match('has_' + pkg): + return True + return False + + def _is_real_package(self, symbol): + return self._is_package(symbol, 'real') + + def _is_virtual_package(self, symbol): + return self._is_package(symbol, 'virtual') + + def _exists_virt_symbol(self, pkg_name): + """ Return True if a symbol exists that defines the package as + a virtual package, False otherwise + + :param pkg_name: The name of the package, for which to check if + a symbol exists defining it as a virtual package + + """ + virt_pattern = "BR2_PACKAGE_HAS_" + pkg_name + "$" + virt_pattern = re.sub("_", ".", virt_pattern) + virt_pattern = re.compile(virt_pattern, re.IGNORECASE) + for sym in self.config: + if virt_pattern.match(sym.get_name()): + return True + return False + + def _get_pkg_name(self, symbol): + """ Return the package name of the specified symbol. + + :param symbol: The symbol to get the package name of + + """ + + return re.sub("BR2_PACKAGE_(HOST_)?(.*)", r"\2", symbol.get_name()) + + def _get_symbol_label(self, symbol, mark_deprecated=True): + """ Return the label (a.k.a. prompt text) of the symbol. + + :param symbol: The symbol + :param mark_deprecated: Append a 'deprecated' to the label + + """ + label = symbol.prompts[0][0] + if self._is_deprecated(symbol) and mark_deprecated: + label += " *(deprecated)*" + return label + + def _format_symbol_prompt(self, what=None, symbol=None, root=None, + enable_choice=False, header=None, + get_label_func=lambda x: x): + if what == "layout": + return ( "30%", "^1" ) + + if what == "header": + return "| {0:<40}\n".format(header) + + if what == "symbol": + return "| {0:<40}\n".format(get_label_func(symbol)) + + message = "Invalid argument 'what': '%s'\n" % str(what) + message += "Allowed values are: 'layout', 'header' and 'symbol'" + raise Exception(message) + + def _format_symbol_prompt_location(self, what=None, symbol=None, root=None, + enable_choice=False, header=None, + get_label_func=lambda x: x): + if what == "layout": + return ( "100%", "^1,4" ) + + if what == "header": + if hasattr(root, "get_title"): + loc_label = get_symbol_parents(root, None, enable_choice=enable_choice) + loc_label += [root.get_title(), "..."] + else: + loc_label = ["Location"] + return "| {0:<40} <| {1}\n".format(header, " -> ".join(loc_label)) + + if what == "symbol": + parents = get_symbol_parents(symbol, root, enable_choice) + return "| {0:<40} <| {1}\n".format(get_label_func(symbol), + " -> ".join(parents)) + + message = "Invalid argument 'what': '%s'\n" % str(what) + message += "Allowed values are: 'layout', 'header' and 'symbol'" + raise Exception(message) + + def _format_symbol_virtual(self, what=None, symbol=None, root=None, + enable_choice=False, header=None, + get_label_func=lambda x: "?"): + def _symbol_is_legacy(symbol): + selects = [ s.get_name() for s in symbol.get_selected_symbols() ] + return ("BR2_LEGACY" in selects) + + def _get_parent_package(sym): + if self._is_real_package(sym): + return None + # Trim the symbol name from its last component (separated with + # underscores), until we either find a symbol which is a real + # package, or until we have no component (i.e. just 'BR2') + name = sym.get_name() + while name != "BR2": + name = name.rsplit("_", 1)[0] + s = self.config.get_symbol(name) + if s is None: + continue + if self._is_real_package(s): + return s + return None + + def _get_providers(symbol): + providers = list() + for sym in self.config: + if not sym.is_symbol(): + continue + if _symbol_is_legacy(sym): + continue + selects = sym.get_selected_symbols() + if not selects: + continue + for s in selects: + if s == symbol: + if sym.prompts: + l = self._get_symbol_label(sym,False) + parent_pkg = _get_parent_package(sym) + if parent_pkg is not None: + l = self._get_symbol_label(parent_pkg, False) \ + + " (w/ " + l + ")" + providers.append(l) + else: + providers.extend(_get_providers(sym)) + return providers + + if what == "layout": + return ( "100%", "^1,4,4" ) + + if what == "header": + return "| {0:<20} <| {1:<32} <| Providers\n".format("Virtual packages", "Symbols") + + if what == "symbol": + pkg = re.sub(r"^BR2_PACKAGE_HAS_(.+)$", r"\1", symbol.get_name()) + providers = _get_providers(symbol) + + return "| {0:<20} <| {1:<32} <| {2}\n".format(pkg.lower(), + '+' + symbol.get_name() + '+', + ", ".join(providers)) + + message = "Invalid argument 'what': '%s'\n" % str(what) + message += "Allowed values are: 'layout', 'header' and 'symbol'" + raise Exception(message) + + + def print_list(self, list_type, enable_choice=True, enable_deprecated=True, + dry_run=False, output=None): + """ Print the requested list. If not dry run, then the list is + automatically written in its own file. + + :param list_type: The list type to be generated + :param enable_choice: Flag enabling choices to appear in the list + :param enable_deprecated: Flag enabling deprecated items to appear in + the package lists + :param dry_run: Dry run (print the list in stdout instead of + writing the list file + + """ + def _get_menu(title): + """ Return the first symbol menu matching the given title. + + """ + menus = self.config.get_menus() + menu = [m for m in menus if m.get_title().lower() == title.lower()] + if not menu: + message = "No such menu: '{0}'".format(title) + raise Exception(message) + return menu[0] + + list_config = self.list_info[list_type] + root_title = list_config.get('root_menu') + if root_title: + root_item = _get_menu(root_title) + else: + root_item = self.config + filter_ = getattr(self, list_config.get('filter')) + filter_func = lambda x: filter_(x) + format_func = getattr(self, list_config.get('format')) + if not enable_deprecated and list_type != "deprecated": + filter_func = lambda x: filter_(x) and not self._is_deprecated(x) + mark_depr = list_type != "deprecated" + get_label = lambda x: self._get_symbol_label(x, mark_depr) + item_label = "Features" if list_type == "deprecated" else "Packages" + + table = format_asciidoc_table(root_item, get_label, + filter_func=filter_func, + format_func=format_func, + enable_choice=enable_choice, + sorted=list_config.get('sorted'), + item_label=item_label) + + content = self.list_in.format(table=table) + + if dry_run: + print(content) + return + + if not output: + output_dir = self.output_dir + if not output_dir: + print("Warning: Undefined output directory.") + print("\tUse source directory as output location.") + output_dir = self.base_dir + output = os.path.join(output_dir, + list_config.get('filename') + ".txt") + if not os.path.exists(os.path.dirname(output)): + os.makedirs(os.path.dirname(output)) + print("Writing the {0} list in:\n\t{1}".format(list_type, output)) + with open(output, 'w') as fout: + fout.write(content) + + +if __name__ == '__main__': + list_types = ['target-packages', 'host-packages', 'virtual-packages', 'deprecated'] + parser = ArgumentParser() + parser.add_argument("list_type", nargs="?", choices=list_types, + help="""\ +Generate the given list (generate all lists if unspecified)""") + parser.add_argument("-n", "--dry-run", dest="dry_run", action='store_true', + help="Output the generated list to stdout") + parser.add_argument("--output-target", dest="output_target", + help="Output target package file") + parser.add_argument("--output-host", dest="output_host", + help="Output host package file") + parser.add_argument("--output-virtual", dest="output_virtual", + help="Output virtual package file") + parser.add_argument("--output-deprecated", dest="output_deprecated", + help="Output deprecated file") + args = parser.parse_args() + lists = [args.list_type] if args.list_type else list_types + buildroot = Buildroot() + for list_name in lists: + output = getattr(args, "output_" + list_name.split("-", 1)[0]) + buildroot.print_list(list_name, dry_run=args.dry_run, output=output)