diff options
| -rw-r--r-- | docs/markdown/snippets/build_subdir.md | 26 | ||||
| -rw-r--r-- | docs/yaml/functions/_build_target_base.yaml | 18 | ||||
| -rw-r--r-- | docs/yaml/functions/configure_file.yaml | 18 | ||||
| -rw-r--r-- | mesonbuild/backend/backends.py | 13 | ||||
| -rw-r--r-- | mesonbuild/build.py | 24 | ||||
| -rw-r--r-- | mesonbuild/interpreter/interpreter.py | 29 | ||||
| -rw-r--r-- | mesonbuild/interpreter/kwargs.py | 2 | ||||
| -rw-r--r-- | mesonbuild/interpreter/type_checking.py | 1 | ||||
| -rw-r--r-- | test cases/common/109 custom target capture/meson.build | 10 | ||||
| -rw-r--r-- | test cases/common/109 custom target capture/test.json | 3 | ||||
| -rw-r--r-- | test cases/common/117 shared module/meson.build | 8 | ||||
| -rw-r--r-- | test cases/common/117 shared module/test.json | 5 | ||||
| -rw-r--r-- | test cases/common/14 configure file/meson.build | 7 | ||||
| -rw-r--r-- | test cases/common/14 configure file/test.json | 3 | ||||
| -rw-r--r-- | test cases/failing/136 invalid build_subdir/existing-dir/config.h.in | 5 | ||||
| -rw-r--r-- | test cases/failing/136 invalid build_subdir/meson.build | 24 | ||||
| -rw-r--r-- | test cases/failing/136 invalid build_subdir/test.json | 7 |
17 files changed, 188 insertions, 15 deletions
diff --git a/docs/markdown/snippets/build_subdir.md b/docs/markdown/snippets/build_subdir.md new file mode 100644 index 000000000..0c115d05d --- /dev/null +++ b/docs/markdown/snippets/build_subdir.md @@ -0,0 +1,26 @@ +## Added `build_subdir` arg to various targets + +`custom_target()`, `build_target()` and `configure_file()` now support +the `build_subdir` argument. This directs meson to place the build +result within the specified sub-directory path of the build directory. + +```meson +configure_file(input : files('config.h.in'), + output : 'config.h', + build_subdir : 'config-subdir', + install_dir : 'share/appdir', + configuration : conf) +``` + +This places the build result, `config.h`, in a sub-directory named +`config-subdir`, creating it if necessary. To prevent collisions +within the build directory, `build_subdir` is not allowed to match a +file or directory in the source directory nor contain '..' to refer to +the parent of the build directory. `build_subdir` does not affect the +install directory path at all; `config.h` will be installed as +`share/appdir/config.h`. `build_subdir` may contain multiple levels of +directory names. + +This allows construction of files within the build system that have +any required trailing path name components as well as building +multiple files with the same basename from the same source directory. diff --git a/docs/yaml/functions/_build_target_base.yaml b/docs/yaml/functions/_build_target_base.yaml index 4cd91affe..f1dc09df0 100644 --- a/docs/yaml/functions/_build_target_base.yaml +++ b/docs/yaml/functions/_build_target_base.yaml @@ -354,3 +354,21 @@ kwargs: description: | If set, generates a GIR file with the given name. If this is unset then no GIR file will be generated. + + build_subdir: + type: str + since: 1.10.0 + description: + Places the build results in a subdirectory of the given name + rather than directly into the build directory. This does not + affect the install directory, which uses install_dir. + + This allows inserting a directory name into the build path, + either when needed to use the build result while building other + targets or as a way to support multiple targets with the same + basename by using unique build_subdir values for each one. + + To prevent collisions within the build directory, build_subdir + is not allowed to match a file or directory in the source + directory, nor contain '..' to refer to the parent of the build + directory. diff --git a/docs/yaml/functions/configure_file.yaml b/docs/yaml/functions/configure_file.yaml index 2deeff445..32cb55964 100644 --- a/docs/yaml/functions/configure_file.yaml +++ b/docs/yaml/functions/configure_file.yaml @@ -155,3 +155,21 @@ kwargs: description: | When specified, macro guards will be used instead of '#pragma once'. The macro guard name will be the specified name. + + build_subdir: + type: str + since: 1.10.0 + description: + Places the build results in a subdirectory of the given name + rather than directly into the build directory. This does not + affect the install directory, which uses install_dir. + + This allows inserting a directory name into the build path, + either when needed to use the build result while building other + targets or as a way to support multiple targets with the same + basename by using unique build_subdir values for each one. + + To prevent collisions within the build directory, build_subdir + is not allowed to match a file or directory in the source + directory, nor contain '..' to refer to the parent of the build + directory. diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py index db7b5785c..545b41756 100644 --- a/mesonbuild/backend/backends.py +++ b/mesonbuild/backend/backends.py @@ -336,9 +336,9 @@ class Backend: def get_build_dir_include_args(self, target: build.BuildTarget, compiler: 'Compiler', *, absolute_path: bool = False) -> T.List[str]: if absolute_path: - curdir = os.path.join(self.build_dir, target.get_subdir()) + curdir = os.path.join(self.build_dir, target.get_builddir()) else: - curdir = target.get_subdir() + curdir = target.get_builddir() if curdir == '': curdir = '.' return compiler.get_include_args(curdir, False) @@ -373,9 +373,12 @@ class Backend: # this produces no output, only a dummy top-level name dirname = '' elif self.environment.coredata.optstore.get_value_for(OptionKey('layout')) == 'mirror': - dirname = target.get_subdir() + dirname = target.get_builddir() else: dirname = 'meson-out' + build_subdir = target.get_build_subdir() + if build_subdir: + dirname = os.path.join(dirname, build_subdir) return dirname def get_target_dir_relative_to(self, @@ -488,7 +491,7 @@ class Backend: for obj in objects: if isinstance(obj, str): o = os.path.join(proj_dir_to_build_root, - self.build_to_src, target.get_subdir(), obj) + self.build_to_src, target.get_builddir(), obj) obj_list.append(o) elif isinstance(obj, mesonlib.File): if obj.is_built: @@ -1229,7 +1232,7 @@ class Backend: ld_lib_path_libs.add(l) env_build_dir = self.environment.get_build_dir() - ld_lib_path: T.Set[str] = set(os.path.join(env_build_dir, l.get_subdir()) for l in ld_lib_path_libs) + ld_lib_path: T.Set[str] = set(os.path.join(env_build_dir, l.get_builddir()) for l in ld_lib_path_libs) if ld_lib_path: t_env.prepend('LD_LIBRARY_PATH', list(ld_lib_path), ':') diff --git a/mesonbuild/build.py b/mesonbuild/build.py index 777eca164..2e982aca4 100644 --- a/mesonbuild/build.py +++ b/mesonbuild/build.py @@ -165,6 +165,7 @@ swift_kwargs = {'swift_interoperability_mode', 'swift_module_name'} buildtarget_kwargs = { 'build_by_default', 'build_rpath', + 'build_subdir', 'dependencies', 'extra_files', 'gui_app', @@ -621,6 +622,7 @@ class Target(HoldableObject, metaclass=abc.ABCMeta): install: bool = False build_always_stale: bool = False extra_files: T.List[File] = field(default_factory=list) + build_subdir: str = '' @abc.abstractproperty def typename(self) -> str: @@ -638,6 +640,9 @@ class Target(HoldableObject, metaclass=abc.ABCMeta): Target "{self.name}" has a path separator in its name. This is not supported, it can cause unexpected failures and will become a hard error in the future.''')) + self.builddir = self.subdir + if self.build_subdir: + self.builddir = os.path.join(self.subdir, self.build_subdir) # dataclass comparators? def __lt__(self, other: object) -> bool: @@ -698,6 +703,12 @@ class Target(HoldableObject, metaclass=abc.ABCMeta): def get_typename(self) -> str: return self.typename + def get_build_subdir(self) -> str: + return self.build_subdir + + def get_builddir(self) -> str: + return self.builddir + @staticmethod def _get_id_hash(target_id: str) -> str: # We don't really need cryptographic security here. @@ -733,7 +744,7 @@ class Target(HoldableObject, metaclass=abc.ABCMeta): if getattr(self, 'name_suffix_set', False): name += '.' + self.suffix return self.construct_id_from_path( - self.subdir, name, self.type_suffix()) + self.builddir, name, self.type_suffix()) def get_id(self) -> str: return self.id @@ -776,7 +787,7 @@ class BuildTarget(Target): environment: Environment, compilers: T.Dict[str, 'Compiler'], kwargs: BuildTargetKeywordArguments): - super().__init__(name, subdir, subproject, True, for_machine, environment, install=kwargs.get('install', False)) + super().__init__(name, subdir, subproject, True, for_machine, environment, install=kwargs.get('install', False), build_subdir=kwargs.get('build_subdir', '')) self.all_compilers = compilers self.compilers: OrderedDict[str, Compiler] = OrderedDict() self.objects: T.List[ObjectTypes] = [] @@ -2915,10 +2926,11 @@ class CustomTarget(Target, CustomTargetBase, CommandBase): absolute_paths: bool = False, backend: T.Optional['Backend'] = None, description: str = 'Generating {} with a custom command', + build_subdir: str = '', ): # TODO expose keyword arg to make MachineChoice.HOST configurable super().__init__(name, subdir, subproject, False, MachineChoice.HOST, environment, - install, build_always_stale) + install, build_always_stale, build_subdir = build_subdir) self.sources = list(sources) self.outputs = substitute_values( outputs, get_filenames_templates_dict( @@ -3302,6 +3314,12 @@ class CustomTargetIndex(CustomTargetBase, HoldableObject): def get_subdir(self) -> str: return self.target.get_subdir() + def get_build_subdir(self) -> str: + return self.target.get_build_subdir() + + def get_builddir(self) -> str: + return self.target.get_builddir() + def get_filename(self) -> str: return self.output diff --git a/mesonbuild/interpreter/interpreter.py b/mesonbuild/interpreter/interpreter.py index 0f22c77a5..e2376bb47 100644 --- a/mesonbuild/interpreter/interpreter.py +++ b/mesonbuild/interpreter/interpreter.py @@ -2037,6 +2037,7 @@ class Interpreter(InterpreterBase, HoldableObject): KwargInfo('feed', bool, default=False, since='0.59.0'), KwargInfo('capture', bool, default=False), KwargInfo('console', bool, default=False, since='0.48.0'), + KwargInfo('build_subdir', str, default='', since='1.10.0'), ) def func_custom_target(self, node: mparser.FunctionNode, args: T.Tuple[str], kwargs: 'kwtypes.CustomTarget') -> build.CustomTarget: @@ -2127,7 +2128,8 @@ class Interpreter(InterpreterBase, HoldableObject): install_dir=kwargs['install_dir'], install_mode=install_mode, install_tag=kwargs['install_tag'], - backend=self.backend) + backend=self.backend, + build_subdir=kwargs['build_subdir']) self.add_target(tg.name, tg) return tg @@ -2581,6 +2583,13 @@ class Interpreter(InterpreterBase, HoldableObject): self.build.install_dirs.append(idir) return idir + def validate_build_subdir(self, build_subdir: str, target: str): + if build_subdir and build_subdir != '.': + if os.path.exists(os.path.join(self.source_root, self.subdir, build_subdir)): + raise InvalidArguments(f'Build subdir "{build_subdir}" in "{target}" exists in source tree.') + if '..' in build_subdir: + raise InvalidArguments(f'Build subdir "{build_subdir}" in "{target}" contains ..') + @noPosargs @typed_kwargs( 'configure_file', @@ -2617,6 +2626,7 @@ class Interpreter(InterpreterBase, HoldableObject): KwargInfo('output_format', str, default='c', since='0.47.0', since_values={'json': '1.3.0'}, validator=in_set_validator({'c', 'json', 'nasm'})), KwargInfo('macro_name', (str, NoneType), default=None, since='1.3.0'), + KwargInfo('build_subdir', str, default='', since='1.10.0'), ) def func_configure_file(self, node: mparser.BaseNode, args: T.List[TYPE_var], kwargs: kwtypes.ConfigureFile): @@ -2672,8 +2682,14 @@ class Interpreter(InterpreterBase, HoldableObject): mlog.warning('Output file', mlog.bold(ofile_rpath, True), 'for configure_file() at', current_call, 'overwrites configure_file() output at', first_call) else: self.configure_file_outputs[ofile_rpath] = self.current_node.lineno - (ofile_path, ofile_fname) = os.path.split(os.path.join(self.subdir, output)) + + # Validate build_subdir + build_subdir = kwargs['build_subdir'] + self.validate_build_subdir(build_subdir, output) + + (ofile_path, ofile_fname) = os.path.split(os.path.join(self.subdir, build_subdir, output)) ofile_abs = os.path.join(self.environment.build_dir, ofile_path, ofile_fname) + os.makedirs(os.path.split(ofile_abs)[0], exist_ok=True) # Perform the appropriate action if kwargs['configuration'] is not None: @@ -2689,7 +2705,6 @@ class Interpreter(InterpreterBase, HoldableObject): if len(inputs) > 1: raise InterpreterException('At most one input file can given in configuration mode') if inputs: - os.makedirs(os.path.join(self.environment.build_dir, self.subdir), exist_ok=True) file_encoding = kwargs['encoding'] missing_variables, confdata_useless = \ mesonlib.do_conf_file(inputs_abs[0], ofile_abs, conf, @@ -3212,11 +3227,17 @@ class Interpreter(InterpreterBase, HoldableObject): To define a target that builds in that directory you must define it in the meson.build file in that directory. ''')) + + # Make sure build_subdir doesn't exist in the source tree and + # doesn't contain .. + build_subdir = tobj.get_build_subdir() + self.validate_build_subdir(build_subdir, name) + self.validate_forbidden_targets(name) # To permit an executable and a shared library to have the # same name, such as "foo.exe" and "libfoo.a". idname = tobj.get_id() - subdir = tobj.get_subdir() + subdir = tobj.get_builddir() namedir = (name, subdir) if idname in self.build.targets: diff --git a/mesonbuild/interpreter/kwargs.py b/mesonbuild/interpreter/kwargs.py index aae2c3291..c0e259bc2 100644 --- a/mesonbuild/interpreter/kwargs.py +++ b/mesonbuild/interpreter/kwargs.py @@ -181,6 +181,7 @@ class CustomTarget(TypedDict): build_always: bool build_always_stale: T.Optional[bool] build_by_default: T.Optional[bool] + build_subdir: str capture: bool command: T.List[T.Union[str, build.BuildTargetTypes, ExternalProgram, File]] console: bool @@ -309,6 +310,7 @@ class ConfigureFile(TypedDict): input: T.List[FileOrString] configuration: T.Optional[T.Union[T.Dict[str, T.Union[str, int, bool]], build.ConfigurationData]] macro_name: T.Optional[str] + build_subdir: str class Subproject(ExtractRequired): diff --git a/mesonbuild/interpreter/type_checking.py b/mesonbuild/interpreter/type_checking.py index 3178cc607..86ead6c62 100644 --- a/mesonbuild/interpreter/type_checking.py +++ b/mesonbuild/interpreter/type_checking.py @@ -615,6 +615,7 @@ _ALL_TARGET_KWS: T.List[KwargInfo] = [ ('1.1.0', 'generated sources as positional "objects" arguments') }, ), + KwargInfo('build_subdir', str, default='', since='1.10.0') ] diff --git a/test cases/common/109 custom target capture/meson.build b/test cases/common/109 custom target capture/meson.build index b7622014a..9fd7e22ae 100644 --- a/test cases/common/109 custom target capture/meson.build +++ b/test cases/common/109 custom target capture/meson.build @@ -22,3 +22,13 @@ if not os.path.exists(sys.argv[1]): ''' test('capture-wrote', python3, args : ['-c', ct_output_exists, mytarget]) + +mytarget = custom_target('bindat', + output : 'data.dat', + input : 'data_source.txt', + build_subdir : 'subdir2', + capture : true, + command : [python3, comp, '@INPUT@'], + install : true, + install_dir : 'subdir2' +) diff --git a/test cases/common/109 custom target capture/test.json b/test cases/common/109 custom target capture/test.json index ba66b024a..663a8f320 100644 --- a/test cases/common/109 custom target capture/test.json +++ b/test cases/common/109 custom target capture/test.json @@ -1,5 +1,6 @@ { "installed": [ - {"type": "file", "file": "usr/subdir/data.dat"} + {"type": "file", "file": "usr/subdir/data.dat"}, + {"type": "file", "file": "usr/subdir2/data.dat"} ] } diff --git a/test cases/common/117 shared module/meson.build b/test cases/common/117 shared module/meson.build index 94d17a716..494ce42ee 100644 --- a/test cases/common/117 shared module/meson.build +++ b/test cases/common/117 shared module/meson.build @@ -34,6 +34,14 @@ test('import test', e, args : m) m2 = build_target('mymodule2', 'module.c', target_type: 'shared_module') test('import test 2', e, args : m2) +# Same as above, but built and installed in a sub directory +m2_subdir = build_target('mymodule2', 'module.c', + target_type: 'shared_module', + build_subdir: 'subdir', + install: true, + install_dir: join_paths(get_option('libdir'), 'modules/subdir')) +test('import test 2 subdir', e, args : m2_subdir) + # Shared module that does not export any symbols shared_module('nosyms', 'nosyms.c', override_options: ['werror=false'], diff --git a/test cases/common/117 shared module/test.json b/test cases/common/117 shared module/test.json index 33bfeff07..b24149cf3 100644 --- a/test cases/common/117 shared module/test.json +++ b/test cases/common/117 shared module/test.json @@ -2,6 +2,9 @@ "installed": [ {"type": "expr", "file": "usr/lib/modules/libnosyms?so"}, {"type": "implibempty", "file": "usr/lib/modules/libnosyms"}, - {"type": "pdb", "file": "usr/lib/modules/nosyms"} + {"type": "pdb", "file": "usr/lib/modules/nosyms"}, + {"type": "expr", "file": "usr/lib/modules/subdir/libmymodule2?so"}, + {"type": "implib", "file": "usr/lib/modules/subdir/libmymodule2"}, + {"type": "pdb", "file": "usr/lib/modules/subdir/mymodule2"} ] } diff --git a/test cases/common/14 configure file/meson.build b/test cases/common/14 configure file/meson.build index 3a4ff4dc9..80a5d8268 100644 --- a/test cases/common/14 configure file/meson.build +++ b/test cases/common/14 configure file/meson.build @@ -30,6 +30,13 @@ configure_file(input : files('config.h.in'), output : 'config2.h', configuration : conf) +# Test if build_subdir works +configure_file(input : files('config.h.in'), + output : 'config2.h', + build_subdir : 'config-subdir', + install_dir : 'share/appdir/config-subdir', + configuration : conf) + # Now generate a header file with an external script. genprog = import('python3').find_python() scriptfile = '@0@/generator.py'.format(meson.current_source_dir()) diff --git a/test cases/common/14 configure file/test.json b/test cases/common/14 configure file/test.json index 5a6ccd57a..51e677028 100644 --- a/test cases/common/14 configure file/test.json +++ b/test cases/common/14 configure file/test.json @@ -4,6 +4,7 @@ {"type": "file", "file": "usr/share/appdir/config2b.h"}, {"type": "file", "file": "usr/share/appdireh/config2-1.h"}, {"type": "file", "file": "usr/share/appdirok/config2-2.h"}, - {"type": "file", "file": "usr/share/configure file test/invalid-utf8-1.bin"} + {"type": "file", "file": "usr/share/configure file test/invalid-utf8-1.bin"}, + {"type": "file", "file": "usr/share/appdir/config-subdir/config2.h"} ] } diff --git a/test cases/failing/136 invalid build_subdir/existing-dir/config.h.in b/test cases/failing/136 invalid build_subdir/existing-dir/config.h.in new file mode 100644 index 000000000..14a155874 --- /dev/null +++ b/test cases/failing/136 invalid build_subdir/existing-dir/config.h.in @@ -0,0 +1,5 @@ +#define MESSAGE "@var@" +#define OTHER "@other@" "@second@" "@empty@" + +#mesondefine BE_TRUE +#mesondefine SHOULD_BE_UNDEF diff --git a/test cases/failing/136 invalid build_subdir/meson.build b/test cases/failing/136 invalid build_subdir/meson.build new file mode 100644 index 000000000..b54ec9a0b --- /dev/null +++ b/test cases/failing/136 invalid build_subdir/meson.build @@ -0,0 +1,24 @@ +project('invalid build_subdir test', 'c', meson_version : '>= 1.9.1') + +# This setup intentially tries to use a build_subdir +# with a name matching one in the source directory. +# produce a Ninja targets with the same name. It only works on +# unix, because on Windows the target has a '.exe' suffix. +# +# This test might fail to work on different backends or when +# output location is redirected. + +conf = configuration_data() + +conf.set('var', 'mystring') +conf.set('other', 'string 2') +conf.set('second', ' bonus') +conf.set('BE_TRUE', true) + +configure_file(input : files('existing-dir/config.h.in'), + output : 'config.h', + build_subdir : 'existing-dir', + install_dir : 'share/appdir/existing-dir', + configuration : conf) + +run_target('build_dir', command: ['echo', 'clash 1']) diff --git a/test cases/failing/136 invalid build_subdir/test.json b/test cases/failing/136 invalid build_subdir/test.json new file mode 100644 index 000000000..f8e56dae5 --- /dev/null +++ b/test cases/failing/136 invalid build_subdir/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/136 invalid build_subdir/meson.build:18:0: ERROR: Build subdir \"existing-dir\" in \"config.h\" exists in source tree." + } + ] +} |
