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 from __future__
import print_function
28 from __future__
import unicode_literals
34 from argparse
import ArgumentParser
40 Could not find the module 'kconfiglib' in the PYTHONPATH:
42 message
+= "\n".join([" {0}".format(path
) for path
in sys
.path
])
45 Make sure the Kconfiglib directory is in the PYTHONPATH, then relaunch the
48 You can get kconfiglib from:
49 https://github.com/ulfalizer/Kconfiglib
53 sys
.stderr
.write(message
)
57 def get_symbol_subset(root
, filter_func
):
58 """ Return a generator of kconfig items.
60 :param root_item: Root item of the generated subset of items
61 :param filter_func: Filter function
64 if hasattr(root
, "get_items"):
65 get_items
= root
.get_items
66 elif hasattr(root
, "get_top_level_items"):
67 get_items
= root
.get_top_level_items
69 message
= "The symbol does not contain any subset of symbols"
70 raise Exception(message
)
71 for item
in get_items():
73 if not filter_func(item
):
76 elif item
.is_menu() or item
.is_choice():
77 for i
in get_symbol_subset(item
, filter_func
):
81 def get_symbol_parents(item
, root
=None, enable_choice
=False):
82 """ Return the list of the item's parents. The last item of the list is
83 the closest parent, the first the furthest.
85 :param item: Item from which the parent list is generated
86 :param root: Root item stopping the search (not included in the
88 :param enable_choice: Flag enabling choices to appear in the parent list
91 parent
= item
.get_parent()
93 while parent
and parent
!= root
:
95 parents
.append(parent
.get_title())
96 elif enable_choice
and parent
.is_choice():
97 parents
.append(parent
.get_prompts()[0])
98 parent
= parent
.get_parent()
99 if isinstance(root
, kconfiglib
.Menu
) or \
100 (enable_choice
and isinstance(root
, kconfiglib
.Choice
)):
101 parents
.append("") # Dummy empty parent to get a leading arrow ->
106 def format_asciidoc_table(root
, get_label_func
, filter_func
=lambda x
: True,
107 format_func
=lambda x
: x
,
108 enable_choice
=False, sorted=True,
110 """ Return the asciidoc formatted table of the items and their location.
112 :param root: Root item of the item subset
113 :param get_label_func: Item's label getter function
114 :param filter_func: Filter function to apply on the item subset
115 :param format_func: Function to format a symbol and the table header
116 :param enable_choice: Enable choices to appear as part of the item's
118 :param sorted: Flag to alphabetically sort the table
123 for item
in get_symbol_subset(root
, filter_func
):
124 lines
.append(format_func(what
="symbol", symbol
=item
, root
=root
,
125 get_label_func
=get_label_func
,
126 enable_choice
=enable_choice
))
128 lines
.sort(key
=lambda x
: x
.lower())
129 table
= ":halign: center\n\n"
130 width
, columns
= format_func(what
="layout")
131 table
= "[width=\"{0}\",cols=\"{1}\",options=\"header\"]\n".format(width
, columns
)
132 table
+= "|===================================================\n"
133 table
+= format_func(what
="header", header
=item_label
, root
=root
)
134 table
+= "\n" + "".join(lines
) + "\n"
135 table
+= "|===================================================\n"
140 """ Buildroot configuration object.
143 root_config
= "Config.in"
144 package_dirname
= "package"
145 package_prefixes
= ["BR2_PACKAGE_", "BR2_PACKAGE_HOST_"]
146 re_pkg_prefix
= re
.compile(r
"^(" + "|".join(package_prefixes
) + ").*")
147 deprecated_symbol
= "BR2_DEPRECATED"
150 // Automatically generated list for Buildroot manual.
158 'filename': "package-list",
159 'root_menu': "Target packages",
160 'filter': "_is_real_package",
161 'format': "_format_symbol_prompt_location",
165 'filename': "host-package-list",
166 'root_menu': "Host utilities",
167 'filter': "_is_real_package",
168 'format': "_format_symbol_prompt",
171 'virtual-packages': {
172 'filename': "virtual-package-list",
173 'root_menu': "Target packages",
174 'filter': "_is_virtual_package",
175 'format': "_format_symbol_virtual",
179 'filename': "deprecated-list",
181 'filter': "_is_deprecated_feature",
182 'format': "_format_symbol_prompt_location",
188 self
.base_dir
= os
.environ
.get("TOPDIR")
189 self
.output_dir
= os
.environ
.get("O")
190 self
.package_dir
= os
.path
.join(self
.base_dir
, self
.package_dirname
)
191 self
.config
= kconfiglib
.Config(os
.path
.join(self
.base_dir
,
194 self
._deprecated
= self
.config
.get_symbol(self
.deprecated_symbol
)
196 self
.gen_date
= datetime
.datetime
.utcnow()
197 self
.br_version_full
= os
.environ
.get("BR2_VERSION_FULL")
198 if self
.br_version_full
and self
.br_version_full
.endswith("-git"):
199 self
.br_version_full
= self
.br_version_full
[:-4]
200 if not self
.br_version_full
:
201 self
.br_version_full
= "undefined"
203 def _get_package_symbols(self
, package_name
):
204 """ Return a tuple containing the target and host package symbol.
207 symbols
= re
.sub("[-+.]", "_", package_name
)
208 symbols
= symbols
.upper()
209 symbols
= tuple([prefix
+ symbols
for prefix
in self
.package_prefixes
])
212 def _is_deprecated(self
, symbol
):
213 """ Return True if the symbol is marked as deprecated, otherwise False.
216 # This also catches BR2_DEPRECATED_SINCE_xxxx_xx
217 return bool([ symbol
for x
in symbol
.get_referenced_symbols()
218 if x
.get_name().startswith(self
._deprecated
.get_name()) ])
220 def _is_package(self
, symbol
, type='real'):
221 """ Return True if the symbol is a package or a host package, otherwise
224 :param symbol: The symbol to check
225 :param type: Limit to 'real' or 'virtual' types of packages,
226 with 'real' being the default.
227 Note: only 'real' is (implictly) handled for now
230 if not symbol
.is_symbol():
232 if type == 'real' and not symbol
.get_prompts():
234 if type == 'virtual' and symbol
.get_prompts():
236 if not self
.re_pkg_prefix
.match(symbol
.get_name()):
238 pkg_name
= self
._get
_pkg
_name
(symbol
)
240 pattern
= "^(HOST_)?" + pkg_name
+ "$"
241 pattern
= re
.sub("_", ".", pattern
)
242 pattern
= re
.compile(pattern
, re
.IGNORECASE
)
243 # Here, we cannot just check for the location of the Config.in because
244 # of the "virtual" package.
246 # So, to check that a symbol is a package (not a package option or
247 # anything else), we check for the existence of the package *.mk file.
249 # By the way, to actually check for a package, we should grep all *.mk
250 # files for the following regex:
251 # "\$\(eval \$\((host-)?(generic|autotools|cmake)-package\)\)"
253 # Implementation details:
255 # * The package list is generated from the *.mk file existence, the
256 # first time this function is called. Despite the memory consumption,
257 # this list is stored because the execution time of this script is
258 # noticeably shorter than rescanning the package sub-tree for each
260 if not hasattr(self
, "_package_list"):
262 for _
, _
, files
in os
.walk(self
.package_dir
):
263 for file_
in (f
for f
in files
if f
.endswith(".mk")):
264 pkg_list
.append(re
.sub(r
"(.*?)\.mk", r
"\1", file_
))
265 setattr(self
, "_package_list", pkg_list
)
266 for pkg
in getattr(self
, "_package_list"):
268 if pattern
.match(pkg
) and not self
._exists
_virt
_symbol
(pkg
):
270 if type == 'virtual':
271 if pattern
.match('has_' + pkg
):
275 def _is_real_package(self
, symbol
):
276 return self
._is
_package
(symbol
, 'real')
278 def _is_virtual_package(self
, symbol
):
279 return self
._is
_package
(symbol
, 'virtual')
281 def _is_deprecated_feature(self
, symbol
):
282 return symbol
.get_prompts() and self
._is
_deprecated
(symbol
)
284 def _exists_virt_symbol(self
, pkg_name
):
285 """ Return True if a symbol exists that defines the package as
286 a virtual package, False otherwise
288 :param pkg_name: The name of the package, for which to check if
289 a symbol exists defining it as a virtual package
292 virt_pattern
= "BR2_PACKAGE_HAS_" + pkg_name
+ "$"
293 virt_pattern
= re
.sub("_", ".", virt_pattern
)
294 virt_pattern
= re
.compile(virt_pattern
, re
.IGNORECASE
)
295 for sym
in self
.config
:
296 if virt_pattern
.match(sym
.get_name()):
300 def _get_pkg_name(self
, symbol
):
301 """ Return the package name of the specified symbol.
303 :param symbol: The symbol to get the package name of
307 return re
.sub("BR2_PACKAGE_(HOST_)?(.*)", r
"\2", symbol
.get_name())
309 def _get_symbol_label(self
, symbol
, mark_deprecated
=True):
310 """ Return the label (a.k.a. prompt text) of the symbol.
312 :param symbol: The symbol
313 :param mark_deprecated: Append a 'deprecated' to the label
316 label
= symbol
.get_prompts()[0]
317 if self
._is
_deprecated
(symbol
) and mark_deprecated
:
318 label
+= " *(deprecated)*"
321 def _format_symbol_prompt(self
, what
=None, symbol
=None, root
=None,
322 enable_choice
=False, header
=None,
323 get_label_func
=lambda x
: x
):
325 return ( "30%", "^1" )
328 return "| {0:<40}\n".format(header
)
331 return "| {0:<40}\n".format(get_label_func(symbol
))
333 message
= "Invalid argument 'what': '%s'\n" % str(what
)
334 message
+= "Allowed values are: 'layout', 'header' and 'symbol'"
335 raise Exception(message
)
337 def _format_symbol_prompt_location(self
, what
=None, symbol
=None, root
=None,
338 enable_choice
=False, header
=None,
339 get_label_func
=lambda x
: x
):
341 return ( "100%", "^1,4" )
344 if hasattr(root
, "get_title"):
345 loc_label
= get_symbol_parents(root
, None, enable_choice
=enable_choice
)
346 loc_label
+= [root
.get_title(), "..."]
348 loc_label
= ["Location"]
349 return "| {0:<40} <| {1}\n".format(header
, " -> ".join(loc_label
))
352 parents
= get_symbol_parents(symbol
, root
, enable_choice
)
353 return "| {0:<40} <| {1}\n".format(get_label_func(symbol
),
354 " -> ".join(parents
))
356 message
= "Invalid argument 'what': '%s'\n" % str(what
)
357 message
+= "Allowed values are: 'layout', 'header' and 'symbol'"
358 raise Exception(message
)
360 def _format_symbol_virtual(self
, what
=None, symbol
=None, root
=None,
361 enable_choice
=False, header
=None,
362 get_label_func
=lambda x
: "?"):
363 def _symbol_is_legacy(symbol
):
364 selects
= [ s
.get_name() for s
in symbol
.get_selected_symbols() ]
365 return ("BR2_LEGACY" in selects
)
367 def _get_parent_package(sym
):
368 if self
._is
_real
_package
(sym
):
370 # Trim the symbol name from its last component (separated with
371 # underscores), until we either find a symbol which is a real
372 # package, or until we have no component (i.e. just 'BR2')
373 name
= sym
.get_name()
375 name
= name
.rsplit("_", 1)[0]
376 s
= self
.config
.get_symbol(name
)
379 if self
._is
_real
_package
(s
):
383 def _get_providers(symbol
):
385 for sym
in self
.config
:
386 if not sym
.is_symbol():
388 if _symbol_is_legacy(sym
):
390 selects
= sym
.get_selected_symbols()
395 if sym
.get_prompts():
396 l
= self
._get
_symbol
_label
(sym
,False)
397 parent_pkg
= _get_parent_package(sym
)
398 if parent_pkg
is not None:
399 l
= self
._get
_symbol
_label
(parent_pkg
, False) \
403 providers
.extend(_get_providers(sym
))
407 return ( "100%", "^1,4,4" )
410 return "| {0:<20} <| {1:<32} <| Providers\n".format("Virtual packages", "Symbols")
413 pkg
= re
.sub(r
"^BR2_PACKAGE_HAS_(.+)$", r
"\1", symbol
.get_name())
414 providers
= _get_providers(symbol
)
416 return "| {0:<20} <| {1:<32} <| {2}\n".format(pkg
.lower(),
417 '+' + symbol
.get_name() + '+',
418 ", ".join(providers
))
420 message
= "Invalid argument 'what': '%s'\n" % str(what
)
421 message
+= "Allowed values are: 'layout', 'header' and 'symbol'"
422 raise Exception(message
)
425 def print_list(self
, list_type
, enable_choice
=True, enable_deprecated
=True,
426 dry_run
=False, output
=None):
427 """ Print the requested list. If not dry run, then the list is
428 automatically written in its own file.
430 :param list_type: The list type to be generated
431 :param enable_choice: Flag enabling choices to appear in the list
432 :param enable_deprecated: Flag enabling deprecated items to appear in
434 :param dry_run: Dry run (print the list in stdout instead of
435 writing the list file
438 def _get_menu(title
):
439 """ Return the first symbol menu matching the given title.
442 menus
= self
.config
.get_menus()
443 menu
= [m
for m
in menus
if m
.get_title().lower() == title
.lower()]
445 message
= "No such menu: '{0}'".format(title
)
446 raise Exception(message
)
449 list_config
= self
.list_info
[list_type
]
450 root_title
= list_config
.get('root_menu')
452 root_item
= _get_menu(root_title
)
454 root_item
= self
.config
455 filter_
= getattr(self
, list_config
.get('filter'))
456 filter_func
= lambda x
: filter_(x
)
457 format_func
= getattr(self
, list_config
.get('format'))
458 if not enable_deprecated
and list_type
!= "deprecated":
459 filter_func
= lambda x
: filter_(x
) and not self
._is
_deprecated
(x
)
460 mark_depr
= list_type
!= "deprecated"
461 get_label
= lambda x
: self
._get
_symbol
_label
(x
, mark_depr
)
462 item_label
= "Features" if list_type
== "deprecated" else "Packages"
464 table
= format_asciidoc_table(root_item
, get_label
,
465 filter_func
=filter_func
,
466 format_func
=format_func
,
467 enable_choice
=enable_choice
,
468 sorted=list_config
.get('sorted'),
469 item_label
=item_label
)
471 content
= self
.list_in
.format(table
=table
)
478 output_dir
= self
.output_dir
480 print("Warning: Undefined output directory.")
481 print("\tUse source directory as output location.")
482 output_dir
= self
.base_dir
483 output
= os
.path
.join(output_dir
,
484 list_config
.get('filename') + ".txt")
485 if not os
.path
.exists(os
.path
.dirname(output
)):
486 os
.makedirs(os
.path
.dirname(output
))
487 print("Writing the {0} list in:\n\t{1}".format(list_type
, output
))
488 with
open(output
, 'w') as fout
:
492 if __name__
== '__main__':
493 list_types
= ['target-packages', 'host-packages', 'virtual-packages', 'deprecated']
494 parser
= ArgumentParser()
495 parser
.add_argument("list_type", nargs
="?", choices
=list_types
,
497 Generate the given list (generate all lists if unspecified)""")
498 parser
.add_argument("-n", "--dry-run", dest
="dry_run", action
='store_true',
499 help="Output the generated list to stdout")
500 parser
.add_argument("--output-target", dest
="output_target",
501 help="Output target package file")
502 parser
.add_argument("--output-host", dest
="output_host",
503 help="Output host package file")
504 parser
.add_argument("--output-virtual", dest
="output_virtual",
505 help="Output virtual package file")
506 parser
.add_argument("--output-deprecated", dest
="output_deprecated",
507 help="Output deprecated file")
508 args
= parser
.parse_args()
509 lists
= [args
.list_type
] if args
.list_type
else list_types
510 buildroot
= Buildroot()
511 for list_name
in lists
:
512 output
= getattr(args
, "output_" + list_name
.split("-", 1)[0])
513 buildroot
.print_list(list_name
, dry_run
=args
.dry_run
, output
=output
)