mirror of
https://github.com/mesonbuild/meson.git
synced 2026-11-06 05:16:42 +00:00
backend: substitute @OUTPUT@ in custom_target depfile
@OUTPUT@ in depfile is the output filename, not the full path. Joining the target directory with an unexpanded @OUTPUT@ token, then substituting the full output path, doubled the subdirectory in both @DEPFILE@ and ninja's depfile (include/include/foo.d). Fixes #14140
This commit is contained in:
1 parent
a7ce13c354
commit
078db80a47
8 files changed
+105
-10
No files matched your search
@@ -50,7 +50,10 @@ description: |
|
||||
this may be an absolute or a relative to current workdir path.
|
||||
|
||||
*(since 0.47.0)* The `depfile` keyword argument also accepts the
|
||||
`@BASENAME@` and `@PLAINNAME@` substitutions.
|
||||
`@BASENAME@` and `@PLAINNAME@` substitutions. `@OUTPUT@` and
|
||||
`@OUTPUT0@`, `@OUTPUT1@`, `...` are substituted with the output
|
||||
filename (without the target subdirectory), so `depfile: '@OUTPUT@.d'`
|
||||
is written next to the output file.
|
||||
|
||||
The returned object also has methods that are documented in [[@custom_tgt]].
|
||||
|
||||
@@ -186,7 +189,9 @@ kwargs:
|
||||
in any one of these files triggers a recompilation.
|
||||
|
||||
*(since 0.47.0)* the `@BASENAME@` and `@PLAINNAME@` substitutions
|
||||
are also accepted.
|
||||
are also accepted. `@OUTPUT@` / `@OUTPUT0@` `...` are substituted
|
||||
with the output filename so the depfile is not placed in a doubled
|
||||
subdirectory path.
|
||||
|
||||
input:
|
||||
type: array[str | file | program]
|
||||
|
||||
@@ -1705,7 +1705,7 @@ class Backend:
|
||||
msg = f'Custom target {target.name!r} has @DEPFILE@ but no depfile ' \
|
||||
'keyword argument.'
|
||||
raise MesonException(msg)
|
||||
dfilename = os.path.join(outdir, target.depfile)
|
||||
dfilename = os.path.join(outdir, target.get_dep_outname(inputs, target.get_outputs()))
|
||||
i = i.replace('@DEPFILE@', dfilename)
|
||||
if '@PRIVATE_DIR@' in i:
|
||||
assert isinstance(target, build.CustomTarget)
|
||||
|
||||
@@ -1334,7 +1334,7 @@ class NinjaBackend(backends.Backend):
|
||||
else:
|
||||
cmd_type = ''
|
||||
if target.depfile is not None:
|
||||
depfile = target.get_dep_outname(elem.infilenames)
|
||||
depfile = target.get_dep_outname(elem.infilenames, target.get_outputs())
|
||||
rel_dfile = os.path.join(self.get_target_dir(target), depfile)
|
||||
abs_pdir = os.path.join(self.environment.get_build_dir(), self.get_target_dir(target))
|
||||
os.makedirs(abs_pdir, exist_ok=True)
|
||||
|
||||
+19
-6
@@ -3244,17 +3244,30 @@ class CustomTarget(Target, CustomTargetBase, CommandTargetProto):
|
||||
def get_generated_sources(self) -> T.Iterable[GeneratedTypes]:
|
||||
return self.get_generated_lists()
|
||||
|
||||
def get_dep_outname(self, infilenames: list[str]) -> str:
|
||||
def get_dep_outname(self, infilenames: list[str],
|
||||
outfilenames: T.Optional[list[str]] = None) -> str:
|
||||
if self.depfile is None:
|
||||
raise InvalidArguments('Tried to get depfile name for custom_target that does not have depfile defined.')
|
||||
depfile = self.depfile
|
||||
if infilenames:
|
||||
plainname = os.path.basename(infilenames[0])
|
||||
basename = os.path.splitext(plainname)[0]
|
||||
return self.depfile.replace('@BASENAME@', basename).replace('@PLAINNAME@', plainname)
|
||||
else:
|
||||
if '@BASENAME@' in self.depfile or '@PLAINNAME@' in self.depfile:
|
||||
raise InvalidArguments('Substitution in depfile for custom_target that does not have an input file.')
|
||||
return self.depfile
|
||||
depfile = depfile.replace('@BASENAME@', basename).replace('@PLAINNAME@', plainname)
|
||||
elif '@BASENAME@' in depfile or '@PLAINNAME@' in depfile:
|
||||
raise InvalidArguments('Substitution in depfile for custom_target that does not have an input file.')
|
||||
# @OUTPUT@ in depfile is the output *filename*, not the full path.
|
||||
# The backend joins this with get_target_dir(), which already includes
|
||||
# the meson subdir and build_subdir. Substituting a full @OUTPUT@ path
|
||||
# would double both (issue #14140).
|
||||
outputs = outfilenames if outfilenames is not None else self.outputs
|
||||
if '@OUTPUT@' in depfile:
|
||||
if len(outputs) != 1:
|
||||
raise InvalidArguments("Depfile has '@OUTPUT@' as part of a "
|
||||
'string and more than one output file')
|
||||
depfile = depfile.replace('@OUTPUT@', os.path.basename(outputs[0]))
|
||||
for i, ofile in enumerate(outputs):
|
||||
depfile = depfile.replace(f'@OUTPUT{i}@', os.path.basename(ofile))
|
||||
return depfile
|
||||
|
||||
def is_linkable_output(self, output: str) -> bool:
|
||||
if output.endswith(('.a', '.dll', '.lib', '.so', '.dylib')):
|
||||
|
||||
@@ -71,3 +71,4 @@ endif
|
||||
assert(mytarget_disabled, 'Disabled custom target should not be found.')
|
||||
|
||||
subdir('depfile')
|
||||
subdir('outputdepfile')
|
||||
@@ -0,0 +1,17 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import sys
|
||||
|
||||
_, depfile, output = sys.argv
|
||||
|
||||
# Record the path Meson passed as @DEPFILE@ so tests can check it even
|
||||
# after ninja has ingested (and discarded) the gcc-style depfile.
|
||||
with open(output + '.path', 'w', encoding='utf-8') as f:
|
||||
f.write(depfile.replace('\\', '/'))
|
||||
|
||||
with open(output, 'w', encoding='utf-8') as f:
|
||||
f.write('generated\n')
|
||||
|
||||
quoted_dep = sys.argv[0].replace('\\', '/').replace(' ', r'\ ')
|
||||
with open(depfile, 'w', encoding='utf-8') as f:
|
||||
f.write('{}: {}\n'.format(output.replace('\\', '/'), quoted_dep))
|
||||
@@ -0,0 +1,17 @@
|
||||
# Issue #14140: depfile: '@OUTPUT@.d' in a subdirectory must not
|
||||
# double the subdirectory in @DEPFILE@ or in ninja's depfile path.
|
||||
custom_target('outputdepfile',
|
||||
output : 'out.dat',
|
||||
depfile : '@OUTPUT@.d',
|
||||
build_by_default : true,
|
||||
command : [find_program('gen.py'), '@DEPFILE@', '@OUTPUT@'],
|
||||
)
|
||||
|
||||
# build_subdir is part of the target directory, not of @OUTPUT@'s filename.
|
||||
custom_target('outputdepfile-build-subdir',
|
||||
output : 'out.dat',
|
||||
depfile : '@OUTPUT@.d',
|
||||
build_subdir : 'placed',
|
||||
build_by_default : true,
|
||||
command : [find_program('gen.py'), '@DEPFILE@', '@OUTPUT@'],
|
||||
)
|
||||
@@ -5711,6 +5711,48 @@ class AllPlatformTests(BasePlatformTests):
|
||||
self.assertIn('Generating file.txt with a custom command', out)
|
||||
self.assertIn('Generating subdir/file.txt with a custom command', out)
|
||||
|
||||
def test_custom_target_depfile_output_substitution(self):
|
||||
'''depfile: '@OUTPUT@.d' in a subdirectory must not double the path.
|
||||
|
||||
https://github.com/mesonbuild/meson/issues/14140
|
||||
'''
|
||||
testdir = os.path.join(self.common_test_dir, '49 custom target')
|
||||
self.init(testdir)
|
||||
self.build()
|
||||
output = os.path.join(self.builddir, 'outputdepfile', 'out.dat')
|
||||
self.assertPathExists(output)
|
||||
self.assertPathDoesNotExist(os.path.join(self.builddir, 'outputdepfile', 'outputdepfile', 'out.dat.d'))
|
||||
with open(output + '.path', encoding='utf-8') as f:
|
||||
depfile_arg = f.read().strip().replace('\\', '/')
|
||||
# Ninja passes a builddir-relative path; Visual Studio uses an
|
||||
# absolute path. Either is fine so long as the subdirectory is
|
||||
# not doubled (issue #14140).
|
||||
self.assertTrue(
|
||||
depfile_arg == 'outputdepfile/out.dat.d'
|
||||
or depfile_arg.endswith('/outputdepfile/out.dat.d'),
|
||||
depfile_arg)
|
||||
self.assertNotIn('outputdepfile/outputdepfile/', depfile_arg)
|
||||
|
||||
placed = os.path.join(self.builddir, 'outputdepfile', 'placed', 'out.dat')
|
||||
self.assertPathExists(placed)
|
||||
self.assertPathDoesNotExist(os.path.join(
|
||||
self.builddir, 'outputdepfile', 'placed', 'outputdepfile', 'placed', 'out.dat.d'))
|
||||
with open(placed + '.path', encoding='utf-8') as f:
|
||||
placed_arg = f.read().strip().replace('\\', '/')
|
||||
self.assertTrue(
|
||||
placed_arg == 'outputdepfile/placed/out.dat.d'
|
||||
or placed_arg.endswith('/outputdepfile/placed/out.dat.d'),
|
||||
placed_arg)
|
||||
self.assertNotIn('placed/outputdepfile/placed/', placed_arg)
|
||||
if self.backend is Backend.ninja:
|
||||
with open(os.path.join(self.builddir, 'build.ninja'), encoding='utf-8') as f:
|
||||
contents = f.read().replace('\\', '/')
|
||||
self.assertIn('outputdepfile/out.dat.d', contents)
|
||||
self.assertNotIn('outputdepfile/outputdepfile/out.dat.d', contents)
|
||||
self.assertIn('outputdepfile/placed/out.dat.d', contents)
|
||||
self.assertNotIn('outputdepfile/placed/outputdepfile/placed/out.dat.d', contents)
|
||||
self.assertNotIn('@OUTPUT@.d', contents)
|
||||
|
||||
def test_symlinked_subproject(self):
|
||||
testdir = os.path.join(self.unit_test_dir, '107 subproject symlink')
|
||||
subproject_dir = os.path.join(testdir, 'subprojects')
|
||||
|
||||
Reference in new issue
Block a user