Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions mesonbuild/backend/backends.py
Original file line number Diff line number Diff line change
Expand Up @@ -781,6 +781,10 @@ def eval_custom_target_command(self, target, absolute_outputs=False):
i = i.replace('@SOURCE_ROOT@', source_root)
elif '@BUILD_ROOT@' in i:
i = i.replace('@BUILD_ROOT@', build_root)
elif '@OUTDIR@' in i:
if outdir == '':
outdir = '.'
i = i.replace('@OUTDIR@', outdir)
elif '@DEPFILE@' in i:
if target.depfile is None:
msg = 'Custom target {!r} has @DEPFILE@ but no depfile ' \
Expand Down
24 changes: 15 additions & 9 deletions mesonbuild/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import copy, os, re
from collections import OrderedDict
import itertools
import pathlib

from . import environment
from . import dependencies
Expand Down Expand Up @@ -1051,10 +1052,10 @@ def process_kwargs(self, kwargs):
for rule in outputs:
if not isinstance(rule, str):
raise InvalidArguments('"output" may only contain strings.')
if '@BASENAME@' not in rule and '@PLAINNAME@' not in rule:
raise InvalidArguments('Every element of "output" must contain @BASENAME@ or @PLAINNAME@.')
if '/' in rule or '\\' in rule:
raise InvalidArguments('"outputs" must not contain a directory separator.')
if '@BASENAME@' not in rule and '@PLAINNAME@' not in rule and '@ROOTNAME@' not in rule:
raise InvalidArguments('Every element of "output" must contain @BASENAME@, @PLAINNAME@ or @ROOTNAME@.')
if '..' in pathlib.PurePath(rule).parts:
raise InvalidArguments('Output file path name must not contain a .. component.')
if len(outputs) > 1:
for o in outputs:
if '@OUTPUT@' in o:
Expand All @@ -1076,19 +1077,22 @@ def process_kwargs(self, kwargs):
def get_base_outnames(self, inname):
plainname = os.path.split(inname)[1]
basename = os.path.splitext(plainname)[0]
return [x.replace('@BASENAME@', basename).replace('@PLAINNAME@', plainname) for x in self.outputs]
rootname = os.path.splitext(inname)[0]
return [x.replace('@BASENAME@', basename).replace('@PLAINNAME@', plainname).replace('@ROOTNAME@', rootname) for x in self.outputs]

def get_dep_outname(self, inname):
if self.depfile is None:
raise InvalidArguments('Tried to get dep name for rule that does not have dependency file defined.')
plainname = os.path.split(inname)[1]
basename = os.path.splitext(plainname)[0]
return self.depfile.replace('@BASENAME@', basename).replace('@PLAINNAME@', plainname)
rootname = os.path.splitext(inname)[0]
return self.depfile.replace('@BASENAME@', basename).replace('@PLAINNAME@', plainname).replace('@ROOTNAME@', rootname)

def get_arglist(self, inname):
plainname = os.path.split(inname)[1]
basename = os.path.splitext(plainname)[0]
return [x.replace('@BASENAME@', basename).replace('@PLAINNAME@', plainname) for x in self.arglist]
rootname = os.path.splitext(inname)[0]
return [x.replace('@BASENAME@', basename).replace('@PLAINNAME@', plainname).replace('@ROOTNAME@', rootname) for x in self.arglist]

def process_files(self, name, files, state, extra_args=[]):
output = GeneratedList(self, extra_args=extra_args)
Expand Down Expand Up @@ -1585,8 +1589,10 @@ def process_kwargs(self, kwargs):
for i in self.outputs:
if not(isinstance(i, str)):
raise InvalidArguments('Output argument not a string.')
if '/' in i:
raise InvalidArguments('Output must not contain a path segment.')
if os.path.isabs(i):
raise InvalidArguments('Output file path name must not be an absolute path.')
if '..' in pathlib.PurePath(i).parts:
raise InvalidArguments('Output file path name must not contain a .. component.')
if '@INPUT@' in i or '@INPUT0@' in i:
m = 'Output cannot contain @INPUT@ or @INPUT0@, did you ' \
'mean @PLAINNAME@ or @BASENAME@?'
Expand Down
9 changes: 6 additions & 3 deletions mesonbuild/interpreter.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import os, sys, shutil, uuid
import re, shlex
from collections import namedtuple
import pathlib

import importlib

Expand Down Expand Up @@ -2617,17 +2618,19 @@ def func_configure_file(self, node, args, kwargs):
values = mesonlib.get_filenames_templates_dict([ifile_abs], None)
outputs = mesonlib.substitute_values([output], values)
output = outputs[0]
if os.path.split(output)[0] != '':
raise InterpreterException('Output file name must not contain a subdirectory.')
if os.path.isabs(output):
raise InterpreterException('Output file path name must not be an absolute path.')
if '..' in pathlib.PurePath(output).parts:
raise InterpreterException('Output file path name must not contain a .. component.')
(ofile_path, ofile_fname) = os.path.split(os.path.join(self.subdir, output))
ofile_abs = os.path.join(self.environment.build_dir, ofile_path, ofile_fname)
os.makedirs(os.path.join(self.environment.build_dir, ofile_path), exist_ok=True)
if 'configuration' in kwargs:
conf = kwargs['configuration']
if 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:
os.makedirs(os.path.join(self.environment.build_dir, self.subdir), exist_ok=True)
missing_variables = mesonlib.do_conf_file(ifile_abs, ofile_abs,
conf.held_object)
if missing_variables:
Expand Down
14 changes: 5 additions & 9 deletions mesonbuild/mesonlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -610,8 +610,8 @@ def iter_regexin_iter(regexiter, initer):

def _substitute_values_check_errors(command, values):
# Error checking
inregex = ('@INPUT([0-9]+)?@', '@PLAINNAME@', '@BASENAME@')
outregex = ('@OUTPUT([0-9]+)?@', '@OUTDIR@')
inregex = ('@INPUT([0-9]+)?@', '@PLAINNAME@', '@BASENAME@', '@ROOTNAME@')
outregex = ('@OUTPUT([0-9]+)?@',)
if '@INPUT@' not in values:
# Error out if any input-derived templates are present in the command
match = iter_regexin_iter(inregex, command)
Expand All @@ -620,7 +620,7 @@ def _substitute_values_check_errors(command, values):
raise MesonException(m.format(match))
else:
if len(values['@INPUT@']) > 1:
# Error out if @PLAINNAME@ or @BASENAME@ is present in the command
# Error out if @PLAINNAME@, @BASENAME@ or @ROOTNAME@ is present in the command
match = iter_regexin_iter(inregex[1:], command)
if match:
raise MesonException('Command cannot have {!r} when there is '
Expand Down Expand Up @@ -705,12 +705,12 @@ def get_filenames_templates_dict(inputs, outputs):

@INPUT@ - the full path to one or more input files, from @inputs
@OUTPUT@ - the full path to one or more output files, from @outputs
@OUTDIR@ - the full path to the directory containing the output files

If there is only one input file, the following keys are also created:

@PLAINNAME@ - the filename of the input file
@BASENAME@ - the filename of the input file with the extension removed
@ROOTNAME@ - the full path name of the input file with the extension removed

If there is more than one input file, the following keys are also created:

Expand All @@ -732,16 +732,12 @@ def get_filenames_templates_dict(inputs, outputs):
# Just one value, substitute @PLAINNAME@ and @BASENAME@
values['@PLAINNAME@'] = plain = os.path.split(inputs[0])[1]
values['@BASENAME@'] = os.path.splitext(plain)[0]
values['@ROOTNAME@'] = os.path.splitext(inputs[0])[0]
if outputs:
# Gather values derived from the outputs, similar to above.
values['@OUTPUT@'] = outputs
for (ii, vv) in enumerate(outputs):
values['@OUTPUT{}@'.format(ii)] = vv
# Outdir should be the same for all outputs
values['@OUTDIR@'] = os.path.split(outputs[0])[0]
# Many external programs fail on empty arguments.
if values['@OUTDIR@'] == '':
values['@OUTDIR@'] = '.'
return values


Expand Down
29 changes: 17 additions & 12 deletions run_unittests.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ def test_string_templates_substitution(self):
outputs = []
ret = dictfunc(inputs, outputs)
d = {'@INPUT@': inputs, '@INPUT0@': inputs[0],
'@PLAINNAME@': 'foo.c.in', '@BASENAME@': 'foo.c'}
'@PLAINNAME@': 'foo.c.in', '@BASENAME@': 'foo.c', '@ROOTNAME@': "bar/foo.c"}
# Check dictionary
self.assertEqual(ret, d)
# Check substitutions
Expand All @@ -235,6 +235,9 @@ def test_string_templates_substitution(self):
cmd = ['@INPUT@', '@[email protected]', 'strings']
self.assertEqual(substfunc(cmd, d),
inputs + [d['@BASENAME@'] + '.hah'] + cmd[2:])
cmd = ['@INPUT@', '@[email protected]', 'strings']
self.assertEqual(substfunc(cmd, d),
inputs + [d['@ROOTNAME@'] + '.bar'] + cmd[2:])
cmd = ['@OUTPUT@']
self.assertRaises(ME, substfunc, cmd, d)

Expand All @@ -243,8 +246,8 @@ def test_string_templates_substitution(self):
outputs = ['out.c']
ret = dictfunc(inputs, outputs)
d = {'@INPUT@': inputs, '@INPUT0@': inputs[0],
'@PLAINNAME@': 'foo.c.in', '@BASENAME@': 'foo.c',
'@OUTPUT@': outputs, '@OUTPUT0@': outputs[0], '@OUTDIR@': '.'}
'@PLAINNAME@': 'foo.c.in', '@BASENAME@': 'foo.c', '@ROOTNAME@': 'bar/foo.c',
'@OUTPUT@': outputs, '@OUTPUT0@': outputs[0]}
# Check dictionary
self.assertEqual(ret, d)
# Check substitutions
Expand All @@ -259,13 +262,16 @@ def test_string_templates_substitution(self):
cmd = ['@INPUT@', '@[email protected]', 'strings']
self.assertEqual(substfunc(cmd, d),
inputs + [d['@BASENAME@'] + '.hah'] + cmd[2:])
cmd = ['@INPUT@', '@[email protected]', 'strings']
self.assertEqual(substfunc(cmd, d),
inputs + [d['@ROOTNAME@'] + '.bar'] + cmd[2:])

# One input, one output with a subdir
outputs = ['dir/out.c']
ret = dictfunc(inputs, outputs)
d = {'@INPUT@': inputs, '@INPUT0@': inputs[0],
'@PLAINNAME@': 'foo.c.in', '@BASENAME@': 'foo.c',
'@OUTPUT@': outputs, '@OUTPUT0@': outputs[0], '@OUTDIR@': 'dir'}
'@PLAINNAME@': 'foo.c.in', '@BASENAME@': 'foo.c', '@ROOTNAME@': 'bar/foo.c',
'@OUTPUT@': outputs, '@OUTPUT0@': outputs[0]}
# Check dictionary
self.assertEqual(ret, d)

Expand Down Expand Up @@ -298,19 +304,19 @@ def test_string_templates_substitution(self):
self.assertRaises(ME, substfunc, cmd, d)
cmd = ['@BASENAME@']
self.assertRaises(ME, substfunc, cmd, d)
cmd = ['@ROOTNAME@']
self.assertRaises(ME, substfunc, cmd, d)
# No outputs
cmd = ['@OUTPUT@']
self.assertRaises(ME, substfunc, cmd, d)
cmd = ['@OUTPUT0@']
self.assertRaises(ME, substfunc, cmd, d)
cmd = ['@OUTDIR@']
self.assertRaises(ME, substfunc, cmd, d)

# Two inputs, one output
outputs = ['dir/out.c']
ret = dictfunc(inputs, outputs)
d = {'@INPUT@': inputs, '@INPUT0@': inputs[0], '@INPUT1@': inputs[1],
'@OUTPUT@': outputs, '@OUTPUT0@': outputs[0], '@OUTDIR@': 'dir'}
'@OUTPUT@': outputs, '@OUTPUT0@': outputs[0]}
# Check dictionary
self.assertEqual(ret, d)
# Check substitutions
Expand All @@ -336,8 +342,7 @@ def test_string_templates_substitution(self):
outputs = ['dir/out.c', 'dir/out2.c']
ret = dictfunc(inputs, outputs)
d = {'@INPUT@': inputs, '@INPUT0@': inputs[0], '@INPUT1@': inputs[1],
'@OUTPUT@': outputs, '@OUTPUT0@': outputs[0], '@OUTPUT1@': outputs[1],
'@OUTDIR@': 'dir'}
'@OUTPUT@': outputs, '@OUTPUT0@': outputs[0], '@OUTPUT1@': outputs[1]}
# Check dictionary
self.assertEqual(ret, d)
# Check substitutions
Expand All @@ -347,8 +352,8 @@ def test_string_templates_substitution(self):
self.assertEqual(substfunc(cmd, d), outputs + cmd[1:])
cmd = ['@OUTPUT0@', '@OUTPUT1@', 'strings']
self.assertEqual(substfunc(cmd, d), outputs + cmd[2:])
cmd = ['@[email protected]', '@[email protected]', '@OUTDIR@']
self.assertEqual(substfunc(cmd, d), [outputs[0] + '.out', inputs[1] + '.ok', 'dir'])
cmd = ['@[email protected]', '@[email protected]']
self.assertEqual(substfunc(cmd, d), [outputs[0] + '.out', inputs[1] + '.ok'])
# Many inputs, can't use @INPUT@ like this
cmd = ['@[email protected]', 'ordinary', 'strings']
self.assertRaises(ME, substfunc, cmd, d)
Expand Down
1 change: 1 addition & 0 deletions test cases/common/16 configure file/include/prjdir/foo.h
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
// An ordinary header.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
#define VERSION "@version@"
10 changes: 10 additions & 0 deletions test cases/common/16 configure file/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,13 @@ configs = [
foreach c : configs
test('@0@'.format(c), file_contains_py, args: [ c, test_string ])
endforeach

# Generally you don't want to install version.h directly under /usr/local/include,
# so we also test generating headers to a subdir of builddir
conf_version = configuration_data()
conf_version.set('version', '1.0.0')
configure_file(input : 'include/prjdir/version.h.in',
output : 'prjdir/version.h',
configuration: conf_version)
e5 = executable('prog5', 'prog5.c', include_directories: include_directories('include'))
test('inctest5', e5)
6 changes: 6 additions & 0 deletions test cases/common/16 configure file/prog5.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#include <prjdir/foo.h>
#include <prjdir/version.h>

int main(int argc, char **argv) {
return strcmp(VERSION, "1.0.0");
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
project('outdir path', 'c')

configure_file(input : 'foo.in',
output : 'subdir/foo',
output : '../subdir/foo',
configuration : configuration_data())
3 changes: 0 additions & 3 deletions test cases/frameworks/5 protocol buffers/asubdir/defs.proto

This file was deleted.

9 changes: 0 additions & 9 deletions test cases/frameworks/5 protocol buffers/asubdir/main.cpp

This file was deleted.

This file was deleted.

3 changes: 0 additions & 3 deletions test cases/frameworks/5 protocol buffers/defs.proto

This file was deleted.

9 changes: 0 additions & 9 deletions test cases/frameworks/5 protocol buffers/main.cpp

This file was deleted.

54 changes: 43 additions & 11 deletions test cases/frameworks/5 protocol buffers/meson.build
Original file line number Diff line number Diff line change
@@ -1,20 +1,52 @@
project('protocol buffer test', 'cpp')

protoc = find_program('protoc', required : false)
dep = dependency('protobuf', required : false)
protobuf_dep = dependency('protobuf')
protoc = find_program('protoc')

if not protoc.found() or not dep.found()
if not protoc.found() or not protobuf_dep.found()
error('MESON_SKIP_TEST: protoc tool and/or protobuf pkg-config dependency not found')
endif

# Builds a proto subtree from top_level meson.build.
# Currently build proto subtree from outside requires a little hack in order to get proto headers
# currectly on include paths. Building with subdir at the top level of proto subtree is a cleaner
# solution.
protos_basename = [
'messages/dummy_messages',
'dummy_service',
]
protos_src = []
protos_hdr = []
foreach p : protos_basename
cc_target = custom_target(
p.underscorify(),
input: 'proto/' + p + '.proto',
output: [p + '.pb.cc', p + '.pb.h'],
command: [protoc, '-I', '@CURRENT_SOURCE_DIR@/proto', '--cpp_out', '@OUTDIR@', '@INPUT@']
)
protos_src += cc_target[0]
protos_hdr += cc_target[1]
endforeach

gen = generator(protoc, \
output : ['@[email protected]', '@[email protected]'],
arguments : ['--proto_path=@SOURCE_DIR@', '--cpp_out=@BUILD_DIR@', '@INPUT@'])
# Builds another proto subtree in a subdir
subdir('proto_subdir')

generated = gen.process('defs.proto')
e = executable('prog', 'main.cpp', generated,
dependencies : dep)
test('prototest', e)
# Builds proto trees with generators. This acturally looks cleaner than the custom_target
# approach, but with some limits: it requires that the current source dir to be treated as
# proto root, instead of the top level of the proto tree. This may require changes of existing
# code.
proto_gen = generator(protoc,
arguments: ['-I', '@CURRENT_SOURCE_DIR@', '--cpp_out', '@BUILD_DIR@', '@INPUT@'],
output: ['@[email protected]', '@[email protected]'],
)
protos_name = [
'proto_generator/messages/dummy_messages3.proto',
'proto_generator/dummy_service3.proto',
]
protos_gen_src = proto_gen.process(protos_name)

subdir('asubdir')
dummy_bin = executable(
'prog',
['src/prog.cpp', protos_src, protos_hdr, protos_subdir_src, protos_subdir_hdr, protos_gen_src],
dependencies: protobuf_dep,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
syntax = "proto2";
package configure_file;
option cc_generic_services = true;
import "messages/dummy_messages.proto";

service DummyService {
rpc DummyMethod (DummyRequest) returns (DummyResponse) {};
}
Loading