diff --git a/docs/markdown/Reference-manual.md b/docs/markdown/Reference-manual.md index 802002b6f..cb8347a2e 100644 --- a/docs/markdown/Reference-manual.md +++ b/docs/markdown/Reference-manual.md @@ -1313,7 +1313,11 @@ The content is a series of key/value pairs grouped into sections. If the section keyword argument is omitted, those key/value pairs are implicitly grouped into a section with no title. key/value pairs can optionally be grouped into a dictionary, but keep in mind that dictionaries does not guarantee ordering. `key` must be string, -`value` can only be integer, boolean, string, or a list of those. +`value` can be: + +- an integer, boolean or string +- *since 0.57.0* an external program or a dependency +- a list of those. `summary()` can be called multiple times as long as the same section/key pair doesn't appear twice. All sections will be collected and printed at diff --git a/docs/markdown/snippets/summary_prog_dep.md b/docs/markdown/snippets/summary_prog_dep.md new file mode 100644 index 000000000..6f0262f2d --- /dev/null +++ b/docs/markdown/snippets/summary_prog_dep.md @@ -0,0 +1,4 @@ +## `summary()` accepts external programs or dependencies + +External program objects and dependency objects can be passed to +`summary()` as the value to be printed. diff --git a/mesonbuild/dependencies/base.py b/mesonbuild/dependencies/base.py index e72f34693..cd77b4b54 100644 --- a/mesonbuild/dependencies/base.py +++ b/mesonbuild/dependencies/base.py @@ -134,6 +134,13 @@ class Dependency: def is_built(self) -> bool: return False + def summary_value(self) -> T.Union[str, mlog.AnsiDecorator, mlog.AnsiText]: + if not self.found(): + return mlog.red('NO') + if not self.version: + return mlog.green('YES') + return mlog.AnsiText(mlog.green('YES'), ' ', mlog.cyan(self.version)) + def get_compile_args(self) -> T.List[str]: if self.include_type == 'system': converted = [] @@ -263,6 +270,11 @@ class InternalDependency(Dependency): setattr(result, k, copy.deepcopy(v, memo)) return result + def summary_value(self) -> mlog.AnsiDecorator: + # Omit the version. Most of the time it will be just the project + # version, which is uninteresting in the summary. + return mlog.green('YES') + def is_built(self) -> bool: if self.sources or self.libraries or self.whole_libraries: return True @@ -1888,6 +1900,11 @@ class ExternalProgram: else: mlog.log('Program', mlog.bold(name), 'found:', mlog.red('NO')) + def summary_value(self) -> T.Union[str, mlog.AnsiDecorator]: + if not self.found(): + return mlog.red('NO') + return self.path + def __repr__(self) -> str: r = '<{} {!r} -> {!r}>' return r.format(self.__class__.__name__, self.name, self.command) diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index 817204fd2..a3fa0504d 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -1854,7 +1854,7 @@ class Summary: self.sections = collections.defaultdict(dict) self.max_key_len = 0 - def add_section(self, section, values, kwargs): + def add_section(self, section, values, kwargs, subproject): bool_yn = kwargs.get('bool_yn', False) if not isinstance(bool_yn, bool): raise InterpreterException('bool_yn keyword argument must be boolean') @@ -1866,13 +1866,17 @@ class Summary: raise InterpreterException('Summary section {!r} already have key {!r}'.format(section, k)) formatted_values = [] for i in listify(v): - if not isinstance(i, (str, int)): - m = 'Summary value in section {!r}, key {!r}, must be string, integer or boolean' - raise InterpreterException(m.format(section, k)) - if bool_yn and isinstance(i, bool): + i = unholder(i) + if isinstance(i, bool) and bool_yn: formatted_values.append(mlog.green('YES') if i else mlog.red('NO')) - else: + elif isinstance(i, (str, int, bool)): formatted_values.append(str(i)) + elif isinstance(i, (ExternalProgram, Dependency)): + FeatureNew.single_use('dependency or external program in summary', '0.57.0', subproject) + formatted_values.append(i.summary_value()) + else: + m = 'Summary value in section {!r}, key {!r}, must be string, integer, boolean, dependency or external program' + raise InterpreterException(m.format(section, k)) self.sections[section][k] = (formatted_values, list_sep) self.max_key_len = max(self.max_key_len, len(k)) @@ -3284,7 +3288,7 @@ external dependencies (including libraries) must go to "dependencies".''') def summary_impl(self, section, values, kwargs): if self.subproject not in self.summary: self.summary[self.subproject] = Summary(self.active_projectname, self.project_version) - self.summary[self.subproject].add_section(section, values, kwargs) + self.summary[self.subproject].add_section(section, values, kwargs, self.subproject) def _print_summary(self): # Add automatic 'Supbrojects' section in main project. diff --git a/mesonbuild/mlog.py b/mesonbuild/mlog.py index a6aa2b563..20794df21 100644 --- a/mesonbuild/mlog.py +++ b/mesonbuild/mlog.py @@ -136,6 +136,18 @@ class AnsiDecorator: def __str__(self) -> str: return self.get_text(colorize_console()) + +class AnsiText: + def __init__(self, *args: T.List[T.Union[str, AnsiDecorator]]): + self.args = args + + def __len__(self) -> int: + return sum((len(x) for x in self.args)) + + def __str__(self) -> str: + return ''.join((str(x) for x in self.args)) + + def bold(text: str, quoted: bool = False) -> AnsiDecorator: return AnsiDecorator(text, "\033[1m", quoted=quoted) diff --git a/run_unittests.py b/run_unittests.py index da6d3290b..b244a07ea 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -4707,6 +4707,12 @@ class AllPlatformTests(BasePlatformTests): no : NO coma list : a, b, c + Stuff + missing prog : NO + existing prog : ''' + sys.executable + ''' + missing dep : NO + internal dep : YES + Plugins long coma list : alpha, alphacolor, apetag, audiofx, audioparsers, auparse, autodetect, avi diff --git a/test cases/unit/73 summary/meson.build b/test cases/unit/73 summary/meson.build index 1bc05ca71..50383b4d7 100644 --- a/test cases/unit/73 summary/meson.build +++ b/test cases/unit/73 summary/meson.build @@ -9,6 +9,11 @@ summary({'Some boolean': false, 'A list': ['string', 1, true], 'empty list': [], }, section: 'Configuration') +summary({'missing prog': find_program('xyzzy', required: false), + 'existing prog': import('python').find_installation(), + 'missing dep': dependency('', required: false), + 'internal dep': declare_dependency(), + }, section: 'Stuff') summary('A number', 1, section: 'Configuration') summary('yes', true, bool_yn : true, section: 'Configuration') summary('no', false, bool_yn : true, section: 'Configuration')