summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/markdown/snippets/build_subdir.md26
-rw-r--r--docs/yaml/functions/_build_target_base.yaml18
-rw-r--r--docs/yaml/functions/configure_file.yaml18
-rw-r--r--mesonbuild/backend/backends.py13
-rw-r--r--mesonbuild/build.py24
-rw-r--r--mesonbuild/interpreter/interpreter.py29
-rw-r--r--mesonbuild/interpreter/kwargs.py2
-rw-r--r--mesonbuild/interpreter/type_checking.py1
-rw-r--r--test cases/common/109 custom target capture/meson.build10
-rw-r--r--test cases/common/109 custom target capture/test.json3
-rw-r--r--test cases/common/117 shared module/meson.build8
-rw-r--r--test cases/common/117 shared module/test.json5
-rw-r--r--test cases/common/14 configure file/meson.build7
-rw-r--r--test cases/common/14 configure file/test.json3
-rw-r--r--test cases/failing/136 invalid build_subdir/existing-dir/config.h.in5
-rw-r--r--test cases/failing/136 invalid build_subdir/meson.build24
-rw-r--r--test cases/failing/136 invalid build_subdir/test.json7
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."
+ }
+ ]
+}