configure_file(): Allow multiple inputs in command mode

Closes: #5893
pull/5937/head
Xavier Claessens 5 years ago committed by Xavier Claessens
parent 5d0ced220c
commit ef3992f1cc
  1. 5
      docs/markdown/Reference-manual.md
  2. 3
      docs/markdown/snippets/configure_file_enhancements.md
  3. 75
      mesonbuild/interpreter.py
  4. 14
      test cases/common/14 configure file/check_inputs.py
  5. 6
      test cases/common/14 configure file/meson.build

@ -251,7 +251,10 @@ These are all the supported keyword arguments:
`output`. Available since v0.41.0.
- `command` as explained above, if specified, Meson does not create
the file itself but rather runs the specified command, which allows
you to do fully custom file generation.
you to do fully custom file generation. Since *0.52.0* the command can contain
file objects and more than one file can be passed to the `input` keyword
argument, see [`custom_target()`](#custom_target) for details about string
substitutions.
- `copy` *(added 0.47.0)* as explained above, if specified Meson only
copies the file from input to output.
- `format` *(added 0.46.0)* the format of defines. It defaults to `meson`, and so substitutes

@ -0,0 +1,3 @@
## Enhancements to `configure_file()`
`input:` now accepts multiple input file names for `command:`-configured file.

@ -3673,30 +3673,20 @@ This will become a hard error in the future.''' % kwargs['input'], location=self
raise InterpreterException('"format" possible values are "c" or "nasm".')
# Validate input
inputfile = None
ifile_abs = None
if 'input' in kwargs:
inputfile = kwargs['input']
if isinstance(inputfile, list):
if len(inputfile) != 1:
m = "Keyword argument 'input' requires exactly one file"
raise InterpreterException(m)
inputfile = inputfile[0]
if not isinstance(inputfile, (str, mesonlib.File)):
raise InterpreterException('Input must be a string or a file')
if isinstance(inputfile, str):
inputfile = mesonlib.File.from_source_file(self.environment.source_dir,
self.subdir, inputfile)
ifile_abs = inputfile.absolute_path(self.environment.source_dir,
self.environment.build_dir)
elif 'command' in kwargs and '@INPUT@' in kwargs['command']:
raise InterpreterException('@INPUT@ used as command argument, but no input file specified.')
inputs = self.source_strings_to_files(extract_as_list(kwargs, 'input'))
inputs_abs = []
for f in inputs:
if isinstance(f, mesonlib.File):
inputs_abs.append(f.absolute_path(self.environment.source_dir,
self.environment.build_dir))
else:
raise InterpreterException('Inputs can only be strings or file objects')
# Validate output
output = kwargs['output']
if not isinstance(output, str):
raise InterpreterException('Output file name must be a string')
if ifile_abs:
values = mesonlib.get_filenames_templates_dict([ifile_abs], None)
if inputs_abs:
values = mesonlib.get_filenames_templates_dict(inputs_abs, None)
outputs = mesonlib.substitute_values([output], values)
output = outputs[0]
ofile_rpath = os.path.join(self.subdir, output)
@ -3722,20 +3712,22 @@ This will become a hard error in the future.''' % kwargs['input'], location=self
elif not isinstance(conf, ConfigurationDataHolder):
raise InterpreterException('Argument "configuration" is not of type configuration_data')
mlog.log('Configuring', mlog.bold(output), 'using configuration')
if inputfile is not None:
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.setdefault('encoding', 'utf-8')
missing_variables, confdata_useless = \
mesonlib.do_conf_file(ifile_abs, ofile_abs, conf.held_object,
mesonlib.do_conf_file(inputs_abs[0], ofile_abs, conf.held_object,
fmt, file_encoding)
if missing_variables:
var_list = ", ".join(map(repr, sorted(missing_variables)))
mlog.warning(
"The variable(s) %s in the input file '%s' are not "
"present in the given configuration data." % (
var_list, inputfile), location=node)
var_list, inputs[0]), location=node)
if confdata_useless:
ifbase = os.path.basename(ifile_abs)
ifbase = os.path.basename(inputs_abs[0])
mlog.warning('Got an empty configuration_data() object and found no '
'substitutions in the input file {!r}. If you want to '
'copy a file to the build dir, use the \'copy:\' keyword '
@ -3744,13 +3736,12 @@ This will become a hard error in the future.''' % kwargs['input'], location=self
mesonlib.dump_conf_header(ofile_abs, conf.held_object, output_format)
conf.mark_used()
elif 'command' in kwargs:
if len(inputs) > 1:
FeatureNew('multiple inputs in configure_file()', '0.52.0').use(self.subproject)
# We use absolute paths for input and output here because the cwd
# that the command is run from is 'unspecified', so it could change.
# Currently it's builddir/subdir for in_builddir else srcdir/subdir.
if ifile_abs:
values = mesonlib.get_filenames_templates_dict([ifile_abs], [ofile_abs])
else:
values = mesonlib.get_filenames_templates_dict(None, [ofile_abs])
values = mesonlib.get_filenames_templates_dict(inputs_abs, [ofile_abs])
# Substitute @INPUT@, @OUTPUT@, etc here.
cmd = mesonlib.substitute_values(kwargs['command'], values)
mlog.log('Configuring', mlog.bold(output), 'with command')
@ -3763,26 +3754,28 @@ This will become a hard error in the future.''' % kwargs['input'], location=self
file_encoding = kwargs.setdefault('encoding', 'utf-8')
with open(dst_tmp, 'w', encoding=file_encoding) as f:
f.writelines(res.stdout)
if ifile_abs:
shutil.copymode(ifile_abs, dst_tmp)
if inputs_abs:
shutil.copymode(inputs_abs[0], dst_tmp)
mesonlib.replace_if_different(ofile_abs, dst_tmp)
elif 'copy' in kwargs:
if len(inputs_abs) != 1:
raise InterpreterException('Exactly one input file must be given in copy mode')
os.makedirs(os.path.join(self.environment.build_dir, self.subdir), exist_ok=True)
shutil.copyfile(ifile_abs, ofile_abs)
shutil.copymode(ifile_abs, ofile_abs)
shutil.copyfile(inputs_abs[0], ofile_abs)
shutil.copymode(inputs_abs[0], ofile_abs)
else:
# Not reachable
raise AssertionError
# If the input is a source file, add it to the list of files that we
# need to reconfigure on when they change. FIXME: Do the same for
# files() objects in the command: kwarg.
if inputfile and not inputfile.is_built:
# Normalize the path of the conffile (relative to the
# source root) to avoid duplicates. This is especially
# important to convert '/' to '\' on Windows
conffile = os.path.normpath(inputfile.relative_name())
if conffile not in self.build_def_files:
self.build_def_files.append(conffile)
# need to reconfigure on when they change.
for f in chain(inputs, kwargs.get('command', [])):
if isinstance(f, mesonlib.File) and not f.is_built:
# Normalize the path of the conffile (relative to the
# source root) to avoid duplicates. This is especially
# important to convert '/' to '\' on Windows
conffile = os.path.normpath(f.relative_name())
if conffile not in self.build_def_files:
self.build_def_files.append(conffile)
# Install file if requested, we check for the empty string
# for backwards compatibility. That was the behaviour before
# 0.45.0 so preserve it.

@ -0,0 +1,14 @@
#!/usr/bin/env python3
import sys
from pathlib import Path
files = [Path(f) for f in sys.argv[1:]]
names = [f.name for f in files]
assert names == ['check_inputs.txt', 'prog.c', 'prog.c', 'prog2.c', 'prog4.c', 'prog5.c']
for f in files[1:]:
assert f.exists()
with files[0].open('w') as ofile:
ofile.write("#define ZERO_RESULT 0\n")

@ -283,3 +283,9 @@ configure_file(output : 'config9b.h',
)
test('test9', executable('prog9', 'prog9.c'))
check_inputs = find_program('check_inputs.py')
configure_file(output : 'check_inputs.txt',
input : ['prog.c', files('prog2.c', 'prog4.c')],
command : [check_inputs, '@OUTPUT@', '@INPUT0@', '@INPUT@', files('prog5.c')]
)

Loading…
Cancel
Save