From cf5adf0c646474f0259d123fad60ca5ed38ec891 Mon Sep 17 00:00:00 2001 From: Charles Brunet Date: Fri, 17 Mar 2023 16:27:37 -0400 Subject: [PATCH] add json output format to configure file --- docs/markdown/snippets/json_output_format.md | 5 ++ docs/yaml/functions/configure_file.yaml | 2 +- mesonbuild/interpreter/interpreter.py | 4 +- mesonbuild/interpreter/kwargs.py | 2 +- mesonbuild/utils/universal.py | 50 +++++++++++-------- .../compare.py | 5 ++ .../expected/config.h | 21 ++++++++ .../expected/config.json | 1 + .../expected/config.nasm | 17 +++++++ .../meson.build | 38 ++++++++++++++ 10 files changed, 121 insertions(+), 24 deletions(-) create mode 100644 docs/markdown/snippets/json_output_format.md create mode 100644 test cases/common/269 configure file output format/compare.py create mode 100644 test cases/common/269 configure file output format/expected/config.h create mode 100644 test cases/common/269 configure file output format/expected/config.json create mode 100644 test cases/common/269 configure file output format/expected/config.nasm create mode 100644 test cases/common/269 configure file output format/meson.build diff --git a/docs/markdown/snippets/json_output_format.md b/docs/markdown/snippets/json_output_format.md new file mode 100644 index 000000000..626840f76 --- /dev/null +++ b/docs/markdown/snippets/json_output_format.md @@ -0,0 +1,5 @@ +## Added 'json' output_format to configure_file() + +When no input file is specified, [[configure_file]] can now +generate a `json` file from given [[@cfg_data]]. +Field descriptions are not preserved in the json file. diff --git a/docs/yaml/functions/configure_file.yaml b/docs/yaml/functions/configure_file.yaml index df37ff581..6fb972b3e 100644 --- a/docs/yaml/functions/configure_file.yaml +++ b/docs/yaml/functions/configure_file.yaml @@ -134,7 +134,7 @@ kwargs: The format of the output to generate when no input was specified. It defaults to `c`, in which case preprocessor directives will be prefixed with `#`, you can also use `nasm`, in which case the - prefix will be `%`. + prefix will be `%`. *(since 1.3.0)* `json` format can also be used. encoding: type: str diff --git a/mesonbuild/interpreter/interpreter.py b/mesonbuild/interpreter/interpreter.py index 4751af9e8..0ba8dc76c 100644 --- a/mesonbuild/interpreter/interpreter.py +++ b/mesonbuild/interpreter/interpreter.py @@ -2594,8 +2594,8 @@ class Interpreter(InterpreterBase, HoldableObject): KwargInfo('install_dir', (str, bool), default='', validator=lambda x: 'must be `false` if boolean' if x is True else None), OUTPUT_KW, - KwargInfo('output_format', str, default='c', since='0.47.0', - validator=in_set_validator({'c', 'nasm'})), + KwargInfo('output_format', str, default='c', since='0.47.0', since_values={'json': '1.3.0'}, + validator=in_set_validator({'c', 'json', 'nasm'})), ) def func_configure_file(self, node: mparser.BaseNode, args: T.List[TYPE_var], kwargs: kwtypes.ConfigureFile): diff --git a/mesonbuild/interpreter/kwargs.py b/mesonbuild/interpreter/kwargs.py index af5733f4e..0aee16432 100644 --- a/mesonbuild/interpreter/kwargs.py +++ b/mesonbuild/interpreter/kwargs.py @@ -286,7 +286,7 @@ class ConfigureFile(TypedDict): output: str capture: bool format: T.Literal['meson', 'cmake', 'cmake@'] - output_format: T.Literal['c', 'nasm'] + output_format: T.Literal['c', 'json', 'nasm'] depfile: T.Optional[str] install: T.Optional[bool] install_dir: T.Union[str, T.Literal[False]] diff --git a/mesonbuild/utils/universal.py b/mesonbuild/utils/universal.py index a39825faf..d44ec03a8 100644 --- a/mesonbuild/utils/universal.py +++ b/mesonbuild/utils/universal.py @@ -32,6 +32,7 @@ import textwrap import copy import pickle import errno +import json from mesonbuild import mlog from .core import MesonException, HoldableObject @@ -1361,34 +1362,43 @@ CONF_NASM_PRELUDE = '''; Autogenerated by the Meson build system. ''' -def dump_conf_header(ofilename: str, cdata: 'ConfigurationData', output_format: Literal['c', 'nasm']) -> None: +def _dump_c_header(ofile: T.TextIO, cdata: ConfigurationData, output_format: Literal['c', 'nasm']) -> None: + format_desc: T.Callable[[str], str] if output_format == 'c': prelude = CONF_C_PRELUDE prefix = '#' - else: + format_desc = lambda desc: f'/* {desc} */\n' + else: # nasm prelude = CONF_NASM_PRELUDE prefix = '%' + format_desc = lambda desc: '; ' + '\n; '.join(desc.splitlines()) + '\n' + + ofile.write(prelude) + for k in sorted(cdata.keys()): + (v, desc) = cdata.get(k) + if desc: + ofile.write(format_desc(desc)) + if isinstance(v, bool): + if v: + ofile.write(f'{prefix}define {k}\n\n') + else: + ofile.write(f'{prefix}undef {k}\n\n') + elif isinstance(v, (int, str)): + ofile.write(f'{prefix}define {k} {v}\n\n') + else: + raise MesonException('Unknown data type in configuration file entry: ' + k) + +def dump_conf_header(ofilename: str, cdata: ConfigurationData, + output_format: Literal['c', 'nasm', 'json']) -> None: ofilename_tmp = ofilename + '~' with open(ofilename_tmp, 'w', encoding='utf-8') as ofile: - ofile.write(prelude) - for k in sorted(cdata.keys()): - (v, desc) = cdata.get(k) - if desc: - if output_format == 'c': - ofile.write('/* %s */\n' % desc) - elif output_format == 'nasm': - for line in desc.split('\n'): - ofile.write('; %s\n' % line) - if isinstance(v, bool): - if v: - ofile.write(f'{prefix}define {k}\n\n') - else: - ofile.write(f'{prefix}undef {k}\n\n') - elif isinstance(v, (int, str)): - ofile.write(f'{prefix}define {k} {v}\n\n') - else: - raise MesonException('Unknown data type in configuration file entry: ' + k) + if output_format == 'json': + data = {k: v[0] for k, v in cdata.values.items()} + json.dump(data, ofile, sort_keys=True) + else: # c, nasm + _dump_c_header(ofile, cdata, output_format) + replace_if_different(ofilename, ofilename_tmp) diff --git a/test cases/common/269 configure file output format/compare.py b/test cases/common/269 configure file output format/compare.py new file mode 100644 index 000000000..5188b0237 --- /dev/null +++ b/test cases/common/269 configure file output format/compare.py @@ -0,0 +1,5 @@ +import sys + +with open(sys.argv[1], 'r', encoding='utf-8') as f, open(sys.argv[2], 'r', encoding='utf-8') as g: + if f.read() != g.read(): + sys.exit('contents are not equal') diff --git a/test cases/common/269 configure file output format/expected/config.h b/test cases/common/269 configure file output format/expected/config.h new file mode 100644 index 000000000..33cfd89b0 --- /dev/null +++ b/test cases/common/269 configure file output format/expected/config.h @@ -0,0 +1,21 @@ +/* + * Autogenerated by the Meson build system. + * Do not edit, your changes will be lost. + */ + +#pragma once + +#define bool + +#undef false + +/* ultimate question of life, the universe, and everything */ +#define int 42 + +/* This is +a multiline +description */ +#define str "hello world!" + +#define unquoted float + diff --git a/test cases/common/269 configure file output format/expected/config.json b/test cases/common/269 configure file output format/expected/config.json new file mode 100644 index 000000000..47d783259 --- /dev/null +++ b/test cases/common/269 configure file output format/expected/config.json @@ -0,0 +1 @@ +{"bool": true, "false": false, "int": 42, "str": "\"hello world!\"", "unquoted": "float"} \ No newline at end of file diff --git a/test cases/common/269 configure file output format/expected/config.nasm b/test cases/common/269 configure file output format/expected/config.nasm new file mode 100644 index 000000000..63c5c2221 --- /dev/null +++ b/test cases/common/269 configure file output format/expected/config.nasm @@ -0,0 +1,17 @@ +; Autogenerated by the Meson build system. +; Do not edit, your changes will be lost. + +%define bool + +%undef false + +; ultimate question of life, the universe, and everything +%define int 42 + +; This is +; a multiline +; description +%define str "hello world!" + +%define unquoted float + diff --git a/test cases/common/269 configure file output format/meson.build b/test cases/common/269 configure file output format/meson.build new file mode 100644 index 000000000..796b9d6a2 --- /dev/null +++ b/test cases/common/269 configure file output format/meson.build @@ -0,0 +1,38 @@ +project('configure file output format') + +data = configuration_data() +data.set_quoted('str', 'hello world!', description: '''This is +a multiline +description''') +data.set('unquoted', 'float') +data.set('int', 42, description: 'ultimate question of life, the universe, and everything') +data.set('bool', true) +data.set('false', false) + +config_h = configure_file( + configuration: data, + output_format: 'c', + output: 'config.h' +) + +config_nasm = configure_file( + configuration: data, + output_format: 'nasm', + output: 'config.nasm' +) + +config_json = configure_file( + configuration: data, + output_format: 'json', + output: 'config.json' +) + +py = find_program('python3') +compare_py = files('compare.py') +expected_config_h = files('expected/config.h') +expected_config_nasm = files('expected/config.nasm') +expected_config_json = files('expected/config.json') + +test('c_output', py, args: [compare_py, expected_config_h, config_h]) +test('nasm_output', py, args: [compare_py, expected_config_nasm, config_nasm]) +test('json_output', py, args: [compare_py, expected_config_json, config_json])