Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

configure_file(): Allow multiple inputs in command mode #5895

Merged
merged 1 commit into from Sep 17, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 4 additions & 1 deletion docs/markdown/Reference-manual.md
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions docs/markdown/snippets/configure_file_enhancements.md
@@ -0,0 +1,3 @@
## Enhancements to `configure_file()`

`input:` now accepts multiple input file names for `command:`-configured file.
75 changes: 34 additions & 41 deletions mesonbuild/interpreter.py
Expand Up @@ -3673,30 +3673,20 @@ def func_configure_file(self, node, args, kwargs):
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)
Expand All @@ -3722,20 +3712,22 @@ def func_configure_file(self, node, args, kwargs):
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 '
Expand All @@ -3744,13 +3736,12 @@ def func_configure_file(self, node, args, kwargs):
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')
Expand All @@ -3763,26 +3754,28 @@ def func_configure_file(self, node, args, kwargs):
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.
Expand Down
14 changes: 14 additions & 0 deletions test cases/common/14 configure file/check_inputs.py
@@ -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")
6 changes: 6 additions & 0 deletions test cases/common/14 configure file/meson.build
Expand Up @@ -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')]
)