From a85a23dcd83fc9882d8e77daa1e9a16ee3eb97fb Mon Sep 17 00:00:00 2001 From: Charles Brunet Date: Tue, 19 Sep 2023 12:03:26 -0400 Subject: [PATCH] fs configure_file_list function This function produces, at build time, a file containing the list of files given as argument. It is used when a command uses, as argument, a file containing the list of files to process. One usecase is to provide to `xgettext` the list of files to process, from the list of source files, when this list is too long to be provided to the command line as individual files. --- docs/markdown/Fs-module.md | 29 ++++++++ .../snippets/fs_configure_file_list.md | 7 ++ mesonbuild/modules/fs.py | 73 ++++++++++++++++++- test cases/common/220 fs module/meson.build | 15 ++++ .../common/220 fs module/testfilelist.py | 16 ++++ 5 files changed, 139 insertions(+), 1 deletion(-) create mode 100644 docs/markdown/snippets/fs_configure_file_list.md create mode 100644 test cases/common/220 fs module/testfilelist.py diff --git a/docs/markdown/Fs-module.md b/docs/markdown/Fs-module.md index 7ba4832a0073..f23f476c8f7f 100644 --- a/docs/markdown/Fs-module.md +++ b/docs/markdown/Fs-module.md @@ -264,3 +264,32 @@ returns: ```meson copy = fs.copyfile('input-file', 'output-file') ``` + +### configure_file_list + +*Since 1.5.0* + +Write paths from a list of files to a configuration file, to be used as argument +to a command. The result file is generated at build time, and is updated when +any input file is updated. + +Has the following positional arguments: + - name `str`: name of the resulting file + - sources `str | file | custom_tgt | custom_idx | build_tgt`: file paths to + write to the result file + +Has the following keyword arguments: + - quote `bool`: if `true`, add quote and escape chars to the file paths + (default `false`) + - separator `str`: characters used to separate the paths (default `\n`) + - end `str`: characters to put at the end of the file (default `\n`) + - relative_paths `bool`: if `true`, write relative paths, if `false`, write + absolute paths (default: `false`) + +returns: + - a [[custom_target]] object + +```meson +src_files = files(...) +file_list = fs.configure_file_list('filelist.txt', src_files) +``` diff --git a/docs/markdown/snippets/fs_configure_file_list.md b/docs/markdown/snippets/fs_configure_file_list.md new file mode 100644 index 000000000000..bd962c1657c0 --- /dev/null +++ b/docs/markdown/snippets/fs_configure_file_list.md @@ -0,0 +1,7 @@ +## `fs.configure_file_list()` + +This function produces, at build time, a file containing the list of files given +as argument. It is used when a command uses, as argument, a file containing the +list of files to process. One usecase is to provide to `xgettext` the list of +files to process, from the list of source files, when this list is too long to +be provided to the command line as individual files. diff --git a/mesonbuild/modules/fs.py b/mesonbuild/modules/fs.py index 1fa368e1fef3..5616af78b4c9 100644 --- a/mesonbuild/modules/fs.py +++ b/mesonbuild/modules/fs.py @@ -12,7 +12,7 @@ from ..build import BuildTarget, CustomTarget, CustomTargetIndex, InvalidArguments from ..interpreter.type_checking import INSTALL_KW, INSTALL_MODE_KW, INSTALL_TAG_KW, NoneType from ..interpreterbase import FeatureNew, KwargInfo, typed_kwargs, typed_pos_args, noKwargs -from ..mesonlib import File, MesonException, has_path_sep, path_is_in_root, relpath +from ..mesonlib import File, MesonException, has_path_sep, path_is_in_root, relpath, quote_arg if T.TYPE_CHECKING: from . import ModuleState @@ -37,6 +37,13 @@ class CopyKw(TypedDict): install_mode: FileMode install_tag: T.Optional[str] + class ConfigureFileListKw(TypedDict): + + quote: bool + separator: str + end: str + relative_paths: bool + class FSModule(ExtensionModule): @@ -46,6 +53,7 @@ def __init__(self, interpreter: 'Interpreter') -> None: super().__init__(interpreter) self.methods.update({ 'as_posix': self.as_posix, + 'configure_file_list': self.configure_file_list, 'copyfile': self.copyfile, 'exists': self.exists, 'expanduser': self.expanduser, @@ -317,6 +325,69 @@ def to_path(arg: T.Union[FileOrString, CustomTarget, CustomTargetIndex, BuildTar return relpath(t, f) + @FeatureNew('fs.configure_file_list', '1.5.0') + @typed_pos_args('fs.configure_file_list', str, varargs=(str, File, CustomTarget, CustomTargetIndex, BuildTarget)) + @typed_kwargs( + 'fs.configure_file_list', + KwargInfo('quote', bool, default=False), + KwargInfo('separator', str, default='\n'), + KwargInfo('end', str, default='\n'), + KwargInfo('relative_paths', bool, default=False), + ) + def configure_file_list(self, state: ModuleState, args: T.Tuple[str, T.List[T.Union[FileOrString, BuildTargetTypes]]], kwargs: ConfigureFileListKw) -> ModuleReturnValue: + current_build_dir = Path(state.environment.build_dir, state.subdir) + file_list_dir = current_build_dir / 'file_list.p' + file_list_dir.mkdir(exist_ok=True) + name = args[0] + + input_file = file_list_dir / name + output_file = current_build_dir / name + + separator = kwargs['separator'] + is_relative = kwargs['relative_paths'] + quote = kwargs['quote'] + + # We generate the output now because we know how... + sources = args[1].copy() + nargs = len(sources) + with input_file.open('w', encoding='utf-8') as f: + for i, a in enumerate(sources, 1): + if isinstance(a, str): + a = File(False, state.subdir, a) + + elif isinstance(a, (BuildTarget, CustomTarget, CustomTargetIndex)): + a = File(True, a.get_subdir(), a.get_outputs()[0]) + + assert isinstance(a, File), 'for mypy' + if is_relative: + line = a.relative_name() + else: + line = a.absolute_path(state.environment.source_dir, state.environment.build_dir) + + if quote: + line = quote_arg(line) + + if i == nargs: + separator = kwargs['end'] + print(line, end=separator, file=f) + + sources.insert(0, input_file.as_posix()) + + # ...but we copy it later, to ensure it is touched when an input is modified + ct = CustomTarget( + name, + state.subdir, + state.subproject, + state.environment, + state.environment.get_build_command() + ['--internal', 'copy', '@INPUT0@', '@OUTPUT@'], + sources, + [output_file.name], + build_by_default=True, + backend=state.backend, + description='Generating file list {}', + ) + return ModuleReturnValue(ct, [ct]) + def initialize(*args: T.Any, **kwargs: T.Any) -> FSModule: return FSModule(*args, **kwargs) diff --git a/test cases/common/220 fs module/meson.build b/test cases/common/220 fs module/meson.build index e5397eebc4b2..d116f3f09494 100644 --- a/test cases/common/220 fs module/meson.build +++ b/test cases/common/220 fs module/meson.build @@ -191,6 +191,21 @@ subdir('subdir') subproject('subbie') + +# configure_file_list +test_file_list = find_program('testfilelist.py') +cfl = fs.configure_file_list( + 'fl.txt', + 'meson.build', + subdirfiles, + [btgt, ctgt], +) +test( + 'fs.configure_file_list', + test_file_list, + args: cfl, +) + testcase expect_error('File notfound does not exist.') fs.read('notfound') endtestcase diff --git a/test cases/common/220 fs module/testfilelist.py b/test cases/common/220 fs module/testfilelist.py new file mode 100644 index 000000000000..379e82d17ae9 --- /dev/null +++ b/test cases/common/220 fs module/testfilelist.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 + +import sys +from pathlib import Path + +input_file = Path(sys.argv[-1]) +if not input_file.exists(): + sys.exit('Input file not found') + +with input_file.open('r', encoding='utf-8') as f: + for line in f: + line = line.strip() + if not Path(line).exists(): + sys.exit(f'File {line} not found') + +sys.exit(0)