From 23e83e2d4dbb57bb84a68ec5e61d5f074225b68e Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Mon, 25 Sep 2023 11:11:20 -0700 Subject: [PATCH] add `file_argument` function This allows for handling special arguments like `--version-script=`, where the `=` is required and may not be passed as two separate arguments. A new dsl function `file_argument` is added, and is used like this: ```meson executable( ..., c_args : [file_argument('--file-arg=', files('foo.arg'))] ) ``` This returns an opaque object, which meson will correctly add to either the build or link depends (depending on whether it was passed to a compile argument or to a link argument), and will convert the argument into `--file-arg=relative/path/to/foo.arg` --- docs/markdown/file_argument.md | 18 +++++++++++ docs/yaml/functions/file_argument.yaml | 32 +++++++++++++++++++ docs/yaml/objects/file_arg.yml | 3 ++ mesonbuild/build.py | 8 +++++ mesonbuild/interpreter/interpreter.py | 24 +++++++++++++- mesonbuild/interpreter/interpreterobjects.py | 3 ++ mesonbuild/interpreter/kwargs.py | 1 + mesonbuild/interpreter/type_checking.py | 28 ++++++++++------ .../linuxlike/3 linker script/meson.build | 6 +++- 9 files changed, 112 insertions(+), 11 deletions(-) create mode 100644 docs/markdown/file_argument.md create mode 100644 docs/yaml/functions/file_argument.yaml create mode 100644 docs/yaml/objects/file_arg.yml diff --git a/docs/markdown/file_argument.md b/docs/markdown/file_argument.md new file mode 100644 index 000000000000..10ff1743cc99 --- /dev/null +++ b/docs/markdown/file_argument.md @@ -0,0 +1,18 @@ +## New file_argument() function + +Sometimes arguments need to be passed as a single string, but that string needs +to contain a File as part of the string. Consider how linker scripts work with GCC: +`-Wl,--version-script,`. This is painful to deal with when the `` is +a `files()` object. with `file_argument()` this becomes easier. + +```meson +build_target( + ..., + c_args : [file_argument('--file-arg=', files('foo.file'))], + link_args : [file_argument('-Wl,--version-script,', file('file.map'))], +) +``` + +Meson will automatically create the correct strings, relative to the build +directory, and will automatically add the file to the correct depends, so that +compilation and/or linking will correctly depend on that file. diff --git a/docs/yaml/functions/file_argument.yaml b/docs/yaml/functions/file_argument.yaml new file mode 100644 index 000000000000..dacd0c984d80 --- /dev/null +++ b/docs/yaml/functions/file_argument.yaml @@ -0,0 +1,32 @@ +name: file_argument +since: 1.3.0 +returns: file_arg +description: | + Passes a string argument that must be joined to a file path a [[build_tgt]] + + This allows passing [[file]] objects to the `_args` and `link_args` + parameters of a [[build_tgt]]. Once passed they will be converted to a string + argument, and the [[file]] will be added to the appropriate dependencies + (either build or link). + +example: | + This is particularly useful when working with linker scripts: + + ```meson + tgt = build_tgt( + ..., + link_with : file_argument('-Wl,--linker-script,' files('link.map')), + ) + ``` + + In this case Meson will pass `-Wl,--linker-script,link.map` (or a relative + path for `link.map`), and will add it to the link dependencies for `tgt` + +pos_args: + arg: + type: str + description: The string part of the argument + + file: + type: file + description: The file part of the argument diff --git a/docs/yaml/objects/file_arg.yml b/docs/yaml/objects/file_arg.yml new file mode 100644 index 000000000000..1762e3dcba3a --- /dev/null +++ b/docs/yaml/objects/file_arg.yml @@ -0,0 +1,3 @@ +name: file_arg +long_name: file argument +description: Opaque object returned by [[file_argument]] diff --git a/mesonbuild/build.py b/mesonbuild/build.py index 63f4c1bee544..d662f328799c 100644 --- a/mesonbuild/build.py +++ b/mesonbuild/build.py @@ -143,6 +143,14 @@ def get_target_macos_dylib_install_name(ld) -> str: class InvalidArguments(MesonException): pass + +@dataclass +class FileArgument(HoldableObject): + + arg: str + file: File + + @dataclass(eq=False) class DependencyOverride(HoldableObject): dep: dependencies.Dependency diff --git a/mesonbuild/interpreter/interpreter.py b/mesonbuild/interpreter/interpreter.py index 1d63965fe5da..92e3560959a6 100644 --- a/mesonbuild/interpreter/interpreter.py +++ b/mesonbuild/interpreter/interpreter.py @@ -374,6 +374,7 @@ def build_func_dict(self) -> None: 'error': self.func_error, 'executable': self.func_executable, 'files': self.func_files, + 'file_argument': self.func_file_argument, 'find_program': self.func_find_program, 'generator': self.func_generator, 'get_option': self.func_get_option, @@ -435,6 +436,7 @@ def build_holder_map(self) -> None: # Meson types mesonlib.File: OBJ.FileHolder, + build.FileArgument: OBJ.FileArgumentHolder, build.SharedLibrary: OBJ.SharedLibraryHolder, build.StaticLibrary: OBJ.StaticLibraryHolder, build.BothLibraries: OBJ.BothLibrariesHolder, @@ -676,6 +678,12 @@ def func_import(self, node: mparser.BaseNode, args: T.Tuple[str], def func_files(self, node: mparser.FunctionNode, args: T.Tuple[T.List[str]], kwargs: 'TYPE_kwargs') -> T.List[mesonlib.File]: return self.source_strings_to_files(args[0]) + @FeatureNew('file_argument', '1.3.0') + @typed_pos_args('file_argument', str, mesonlib.File) + @noKwargs + def func_file_argument(self, node: mparser.FunctionNode, args: T.Tuple[str, mesonlib.File], kwargs: TYPE_kwargs) -> build.FileArgument: + return build.FileArgument(*args) + @noPosargs @typed_kwargs( 'declare_dependency', @@ -3224,7 +3232,7 @@ def build_library(self, node, args, kwargs): else: raise InterpreterException(f'Unknown default_library value: {default_library}.') - def __convert_file_args(self, raw: T.List[mesonlib.FileOrString]) -> T.Tuple[T.List[mesonlib.File], T.List[str]]: + def __convert_file_args(self, raw: T.List[T.Union[str, mesonlib.File, build.FileArgument]]) -> T.Tuple[T.List[mesonlib.File], T.List[str]]: """Convert raw target arguments from File | str to File. This removes files from the command line and replaces them with string @@ -3242,6 +3250,9 @@ def __convert_file_args(self, raw: T.List[mesonlib.FileOrString]) -> T.Tuple[T.L if isinstance(a, mesonlib.File): depend_files.append(a) args.append(a.rel_to_builddir(build_to_source)) + elif isinstance(a, build.FileArgument): + depend_files.append(a.file) + args.append(f'{a.arg}{a.file.rel_to_builddir(build_to_source)}') else: args.append(a) @@ -3307,6 +3318,17 @@ def build_target_decorator_caller(self, node, args, kwargs): raise RuntimeError('Unreachable code') self.kwarg_strings_to_includedirs(kwargs) self.__process_language_args(kwargs) + if 'link_args' in kwargs: + new_link_args: T.List[str] = [] + ld: T.List[mesonlib.File] = kwargs.setdefault('link_depends', []) + build_to_source = mesonlib.relpath(self.environment.source_dir, self.environment.build_dir) + for l in kwargs['link_args']: + if isinstance(l, build.FileArgument): + ld.append(l.file) + new_link_args.append(f'{l.arg}{l.file.rel_to_builddir(build_to_source)}') + else: + new_link_args.append(l) + kwargs['link_args'] = new_link_args # Filter out kwargs from other target types. For example 'soversion' # passed to library() when default_library == 'static'. diff --git a/mesonbuild/interpreter/interpreterobjects.py b/mesonbuild/interpreter/interpreterobjects.py index 46b4cc1e51c1..e3d6def1c972 100644 --- a/mesonbuild/interpreter/interpreterobjects.py +++ b/mesonbuild/interpreter/interpreterobjects.py @@ -696,6 +696,9 @@ class IncludeDirsHolder(ObjectHolder[build.IncludeDirs]): class FileHolder(ObjectHolder[mesonlib.File]): pass +class FileArgumentHolder(ObjectHolder[build.FileArgument]): + pass + class HeadersHolder(ObjectHolder[build.Headers]): pass diff --git a/mesonbuild/interpreter/kwargs.py b/mesonbuild/interpreter/kwargs.py index 35b8fb65bcf7..caccf4b50c5e 100644 --- a/mesonbuild/interpreter/kwargs.py +++ b/mesonbuild/interpreter/kwargs.py @@ -325,6 +325,7 @@ class _BaseBuildTarget(TypedDict): """ override_options: T.Dict[OptionKey, T.Union[str, int, bool, T.List[str]]] + link_args: T.List[T.Union[str, build.FileArgument]] class _BuildTarget(_BaseBuildTarget): diff --git a/mesonbuild/interpreter/type_checking.py b/mesonbuild/interpreter/type_checking.py index 2f06c1b9f564..24f54b53fecd 100644 --- a/mesonbuild/interpreter/type_checking.py +++ b/mesonbuild/interpreter/type_checking.py @@ -9,7 +9,7 @@ import typing as T from .. import compilers -from ..build import (CustomTarget, BuildTarget, +from ..build import (CustomTarget, BuildTarget, FileArgument, CustomTargetIndex, ExtractedObjects, GeneratedList, IncludeDirs, BothLibraries, SharedLibrary, StaticLibrary, Jar, Executable, StructuredSources) from ..coredata import UserFeatureOption @@ -513,19 +513,28 @@ def link_whole_validator(values: T.List[T.Union[StaticLibrary, CustomTarget, Cus since='1.3.0', validator=in_set_validator({'rust', 'c'})) +_BASE_LANG_KW: KwargInfo[T.List[T.Union[str, FileArgument]]] = KwargInfo( + 'UNKNOWN', + ContainerTypeInfo(list, (str, File, FileArgument)), + listify=True, + default=[], + since_values={FileArgument: '1.3.0'}, +) + _LANGUAGE_KWS: T.List[KwargInfo[T.List[str]]] = [ - KwargInfo(f'{lang}_args', ContainerTypeInfo(list, (str, File)), listify=True, default=[]) + _BASE_LANG_KW.evolve(name=f'{lang}_args') for lang in compilers.all_languages - {'rust', 'vala', 'java'} ] -_LANGUAGE_KWS.append(KwargInfo( - 'vala_args', ContainerTypeInfo(list, (str, File)), listify=True, default=[])) -_LANGUAGE_KWS.append(KwargInfo( - 'rust_args', ContainerTypeInfo(list, str), listify=True, default=[], since='0.41.0')) +_LANGUAGE_KWS.append(KwargInfo( # cannot use _BASE_LANG_KW because type is not evolveable + 'vala_args', ContainerTypeInfo(list, (str, File, FileArgument)), listify=True, default=[], since_values={FileArgument: '1.3.0'})) +_LANGUAGE_KWS.append(_BASE_LANG_KW.evolve(name='rust_args', since='0.41.0')) # We need this deprecated values more than the non-deprecated values. So we'll evolve them out elsewhere. -_JAVA_LANG_KW: KwargInfo[T.List[str]] = KwargInfo( - 'java_args', ContainerTypeInfo(list, str), listify=True, default=[], - deprecated='1.3.0', deprecated_message='This does not, and never has, done anything. It should be removed') +_JAVA_LANG_KW: KwargInfo[T.List[str]] = _BASE_LANG_KW.evolve( + name='java_args', + deprecated='1.3.0', + deprecated_message='This does not, and never has, done anything. It should be removed' +) # Applies to all build_target like classes _ALL_TARGET_KWS: T.List[KwargInfo] = [ @@ -536,6 +545,7 @@ def link_whole_validator(values: T.List[T.Union[StaticLibrary, CustomTarget, Cus _BUILD_TARGET_KWS: T.List[KwargInfo] = [ *_ALL_TARGET_KWS, *_LANGUAGE_KWS, + _BASE_LANG_KW.evolve(name='link_args'), RUST_CRATE_TYPE_KW, ] diff --git a/test cases/linuxlike/3 linker script/meson.build b/test cases/linuxlike/3 linker script/meson.build index 5901bf7cf716..5ba26b01f973 100644 --- a/test cases/linuxlike/3 linker script/meson.build +++ b/test cases/linuxlike/3 linker script/meson.build @@ -1,4 +1,4 @@ -project('linker script', 'c') +project('linker script', 'c', meson_version : '>= 1.3.0') # Solaris 11.4 ld supports --version-script only when you also specify # -z gnu-version-script-compat @@ -14,6 +14,10 @@ l = shared_library('bob', 'bob.c', link_args : vflag, link_depends : mapfile) e = executable('prog', 'prog.c', link_with : l) test('core', e) +l2 = shared_library('bob-file-argument', 'bob.c', link_args : file_argument('-Wl,--version-script,', files('bob.map'))) +e2 = executable('prog2', 'prog.c', link_with : l2) +test('using file_argument', e2) + # configure_file conf = configuration_data() conf.set('in', 'bobMcBob')