From d2fa6d50809e6a5955999152f43a461595c6bc4c Mon Sep 17 00:00:00 2001 From: Xavier Claessens Date: Thu, 2 Sep 2021 11:04:19 -0400 Subject: [PATCH] Make custom_target() name argument optional --- docs/markdown/Reference-manual.md | 16 ++++++++++++++-- docs/markdown/snippets/custom_target_name.md | 4 ++++ mesonbuild/backend/ninjabackend.py | 3 ++- mesonbuild/build.py | 2 ++ mesonbuild/interpreter/interpreter.py | 13 ++++++++++--- .../unit/99 custom target name/file.txt.in | 0 .../unit/99 custom target name/meson.build | 14 ++++++++++++++ .../99 custom target name/subdir/meson.build | 9 +++++++++ unittests/allplatformstests.py | 8 ++++++++ 9 files changed, 63 insertions(+), 6 deletions(-) create mode 100644 docs/markdown/snippets/custom_target_name.md create mode 100644 test cases/unit/99 custom target name/file.txt.in create mode 100644 test cases/unit/99 custom target name/meson.build create mode 100644 test cases/unit/99 custom target name/subdir/meson.build diff --git a/docs/markdown/Reference-manual.md b/docs/markdown/Reference-manual.md index b3dbf6583..a91fa3e28 100644 --- a/docs/markdown/Reference-manual.md +++ b/docs/markdown/Reference-manual.md @@ -326,8 +326,20 @@ false otherwise. ``` Create a custom top level build target. The only positional argument -is the name of this target and the keyword arguments are the -following. +is the name of this target and cannot contain path separators (`/` or `\`). +The name of custom target might not be used by every backends, for instance with +the Ninja backend, `subdir/meson.build` containing the example below, +`ninja -C builddir foo` or `ninja -C builddir subdir/foo` won't work, +it is instead `ninja -C builddir subdir/file.txt`. Howerver, `meson compile subdir/foo` +is accepted. +```meson +custom_target('foo', output: 'file.txt', ...) +``` + +*Since 0.60.0* the name argument is optional and defaults to the basename of the first +output (`file.txt` in the example above). + +These are all the supported keyword arguments: - `build_by_default` *(since 0.38.0)*: causes, when set to true, to have this target be built by default. This means it will be built when diff --git a/docs/markdown/snippets/custom_target_name.md b/docs/markdown/snippets/custom_target_name.md new file mode 100644 index 000000000..5ad136de9 --- /dev/null +++ b/docs/markdown/snippets/custom_target_name.md @@ -0,0 +1,4 @@ +## Optional `custom_target()` name + +The name argument is now optional and defaults to the basename of the first +output. \ No newline at end of file diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index 5dbbf8bdc..80a1c5642 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -1012,8 +1012,9 @@ class NinjaBackend(backends.Backend): elem.add_item('DEPFILE', rel_dfile) if target.console: elem.add_item('pool', 'console') + full_name = Path(target.subdir, target.name).as_posix() elem.add_item('COMMAND', cmd) - elem.add_item('description', f'Generating {target.name} with a custom command{cmd_type}') + elem.add_item('description', f'Generating {full_name} with a custom command{cmd_type}') self.add_build(elem) self.processed_targets.add(target.get_id()) diff --git a/mesonbuild/build.py b/mesonbuild/build.py index 24eff8c24..e22f1b1b8 100644 --- a/mesonbuild/build.py +++ b/mesonbuild/build.py @@ -2360,6 +2360,8 @@ class CustomTarget(Target, CommandBase): "there is more than one input (we can't know which to use)" raise InvalidArguments(m) self.outputs = substitute_values(self.outputs, values) + if not self.name: + self.name = self.outputs[0] self.capture = kwargs.get('capture', False) if self.capture and len(self.outputs) != 1: raise InvalidArguments('Capturing can only output to a single file.') diff --git a/mesonbuild/interpreter/interpreter.py b/mesonbuild/interpreter/interpreter.py index c45225162..8bd68b86a 100644 --- a/mesonbuild/interpreter/interpreter.py +++ b/mesonbuild/interpreter/interpreter.py @@ -1633,8 +1633,8 @@ external dependencies (including libraries) must go to "dependencies".''') 'build_always', 'capture', 'depends', 'depend_files', 'depfile', 'build_by_default', 'build_always_stale', 'console', 'env', 'feed', 'install_tag'}) - @typed_pos_args('custom_target', str) - def func_custom_target(self, node: mparser.FunctionNode, args: T.Tuple[str], kwargs: 'TYPE_kwargs') -> build.CustomTarget: + @typed_pos_args('custom_target', optargs=[str]) + def func_custom_target(self, node: mparser.FunctionNode, args: T.Tuple[T.Optional[str]], kwargs: 'TYPE_kwargs') -> build.CustomTarget: if 'depfile' in kwargs and ('@BASENAME@' in kwargs['depfile'] or '@PLAINNAME@' in kwargs['depfile']): FeatureNew.single_use('substitutions in custom_target depfile', '0.47.0', self.subproject) return self._func_custom_target_impl(node, args, kwargs) @@ -1642,6 +1642,13 @@ external dependencies (including libraries) must go to "dependencies".''') def _func_custom_target_impl(self, node, args, kwargs): 'Implementation-only, without FeatureNew checks, for internal use' name = args[0] + if name is None: + # name will default to first output, but we cannot do that yet because + # they could need substitutions (e.g. @BASENAME@) first. CustomTarget() + # will take care of setting a proper default but name must be an empty + # string in the meantime. + FeatureNew('custom_target() with no name argument', '0.60.0').use(self.subproject) + name = '' kwargs['install_mode'] = self._get_kwarg_install_mode(kwargs) if 'input' in kwargs: try: @@ -1654,7 +1661,7 @@ This will become a hard error in the future.''' % kwargs['input'], location=self if isinstance(kwargs['command'][0], str): kwargs['command'][0] = self.func_find_program(node, kwargs['command'][0], {}) tg = build.CustomTarget(name, self.subdir, self.subproject, kwargs, backend=self.backend) - self.add_target(name, tg) + self.add_target(tg.name, tg) return tg @FeatureNewKwargs('run_target', '0.57.0', ['env']) diff --git a/test cases/unit/99 custom target name/file.txt.in b/test cases/unit/99 custom target name/file.txt.in new file mode 100644 index 000000000..e69de29bb diff --git a/test cases/unit/99 custom target name/meson.build b/test cases/unit/99 custom target name/meson.build new file mode 100644 index 000000000..8d148a88d --- /dev/null +++ b/test cases/unit/99 custom target name/meson.build @@ -0,0 +1,14 @@ +project('custom target name', 'c') + +python = import('python').find_installation() + +# Name argument is optional and should default to 'file.txt' +custom_target( + input: 'file.txt.in', + output: '@BASENAME@', + command: [python, '--version'], + build_by_default: true, + capture: true, +) + +subdir('subdir') diff --git a/test cases/unit/99 custom target name/subdir/meson.build b/test cases/unit/99 custom target name/subdir/meson.build new file mode 100644 index 000000000..785a7b366 --- /dev/null +++ b/test cases/unit/99 custom target name/subdir/meson.build @@ -0,0 +1,9 @@ +# Name argument is optional and should default to 'file.txt', but should be +# displayed as 'subdir/file.txt' by ninja. +custom_target( + input: '../file.txt.in', + output: ['@BASENAME@'], + command: [python, '--version'], + build_by_default: true, + capture: true, +) diff --git a/unittests/allplatformstests.py b/unittests/allplatformstests.py index e6ddab3f7..b8cdfb6a8 100644 --- a/unittests/allplatformstests.py +++ b/unittests/allplatformstests.py @@ -4061,3 +4061,11 @@ class AllPlatformTests(BasePlatformTests): with self.assertRaises(subprocess.CalledProcessError) as cm: self.build() self.assertIn('error: use of a blacklisted/placeholder name `foo`', cm.exception.stdout) + + def test_custom_target_name(self): + testdir = os.path.join(self.unit_test_dir, '99 custom target name') + self.init(testdir) + out = self.build() + if self.backend is Backend.ninja: + self.assertIn('Generating file.txt with a custom command', out) + self.assertIn('Generating subdir/file.txt with a custom command', out)