From 413a05ec454126bf922f1da473953f80df06f1a3 Mon Sep 17 00:00:00 2001 From: Jason S Zang Date: Sun, 5 Nov 2017 17:14:41 +0800 Subject: [PATCH 1/7] Allow configure_file to output to subdirectories of current build dir. --- mesonbuild/interpreter.py | 8 +++++--- mesonbuild/mesonlib.py | 13 +++++++++++++ 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index c7a0fb71c906..14a2784c8988 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -2617,17 +2617,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 mesonlib.path_norm_split(output): + 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: diff --git a/mesonbuild/mesonlib.py b/mesonbuild/mesonlib.py index 5c4c3747b6f7..3ef8bf39faf6 100644 --- a/mesonbuild/mesonlib.py +++ b/mesonbuild/mesonlib.py @@ -539,6 +539,19 @@ def expand_arguments(args): return None return expended_args +def path_norm_split(path): + ''' + Normalize a path and split into a list of components. First component of an absolute + path will be os.sep + Drive will be discarded. + ''' + _, path = os.path.splitdrive(path) + norm_path = os.path.normpath(path) + components = norm_path.split(os.sep) + if components[0] == '': + components[0] = os.sep + return components + def Popen_safe(args, write=None, stderr=subprocess.PIPE, **kwargs): if sys.version_info < (3, 6) or not sys.stdout.encoding: return Popen_safe_legacy(args, write=write, stderr=stderr, **kwargs) From 4c75b5097776d5d8401843ad6d79d48ca410aa00 Mon Sep 17 00:00:00 2001 From: Jason S Zang Date: Sun, 12 Nov 2017 01:23:55 +0800 Subject: [PATCH 2/7] Allow custom_target to output to subdirectories of current build dir. This should allow correct and efficient compilation of protobuf and other source generators that assumes its output is a directory tree instead of a flatten dir. Should also import Java source generation since we can now write java code to subdirectories according to its package. --- mesonbuild/backend/backends.py | 5 +++++ mesonbuild/build.py | 7 +++++-- mesonbuild/mesonlib.py | 6 ------ run_unittests.py | 15 ++++++--------- 4 files changed, 16 insertions(+), 17 deletions(-) diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py index 61f75358664e..b04321946883 100644 --- a/mesonbuild/backend/backends.py +++ b/mesonbuild/backend/backends.py @@ -781,6 +781,11 @@ 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: + outdir = target.subdir + 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 ' \ diff --git a/mesonbuild/build.py b/mesonbuild/build.py index 12f4bdb6eef9..024f79a8d555 100644 --- a/mesonbuild/build.py +++ b/mesonbuild/build.py @@ -22,6 +22,7 @@ from .mesonlib import File, MesonException, listify, extract_as_list from .mesonlib import typeslistify, stringlistify, classify_unity_sources from .mesonlib import get_filenames_templates_dict, substitute_values +from .mesonlib import path_norm_split from .environment import for_windows, for_darwin, for_cygwin from .compilers import is_object, clike_langs, sort_clike, lang_suffixes @@ -1585,8 +1586,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 path_norm_split(i): + 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@?' diff --git a/mesonbuild/mesonlib.py b/mesonbuild/mesonlib.py index 3ef8bf39faf6..a9ae316c27b6 100644 --- a/mesonbuild/mesonlib.py +++ b/mesonbuild/mesonlib.py @@ -718,7 +718,6 @@ 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: @@ -750,11 +749,6 @@ def get_filenames_templates_dict(inputs, outputs): 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 diff --git a/run_unittests.py b/run_unittests.py index fa8f049fd13c..43b3fa6a227a 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -244,7 +244,7 @@ def test_string_templates_substitution(self): ret = dictfunc(inputs, outputs) d = {'@INPUT@': inputs, '@INPUT0@': inputs[0], '@PLAINNAME@': 'foo.c.in', '@BASENAME@': 'foo.c', - '@OUTPUT@': outputs, '@OUTPUT0@': outputs[0], '@OUTDIR@': '.'} + '@OUTPUT@': outputs, '@OUTPUT0@': outputs[0]} # Check dictionary self.assertEqual(ret, d) # Check substitutions @@ -265,7 +265,7 @@ def test_string_templates_substitution(self): ret = dictfunc(inputs, outputs) d = {'@INPUT@': inputs, '@INPUT0@': inputs[0], '@PLAINNAME@': 'foo.c.in', '@BASENAME@': 'foo.c', - '@OUTPUT@': outputs, '@OUTPUT0@': outputs[0], '@OUTDIR@': 'dir'} + '@OUTPUT@': outputs, '@OUTPUT0@': outputs[0]} # Check dictionary self.assertEqual(ret, d) @@ -303,14 +303,12 @@ def test_string_templates_substitution(self): 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 @@ -336,8 +334,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 @@ -347,8 +344,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 = ['@OUTPUT0@.out', '@INPUT1@.ok', '@OUTDIR@'] - self.assertEqual(substfunc(cmd, d), [outputs[0] + '.out', inputs[1] + '.ok', 'dir']) + cmd = ['@OUTPUT0@.out', '@INPUT1@.ok'] + self.assertEqual(substfunc(cmd, d), [outputs[0] + '.out', inputs[1] + '.ok']) # Many inputs, can't use @INPUT@ like this cmd = ['@INPUT@.out', 'ordinary', 'strings'] self.assertRaises(ME, substfunc, cmd, d) From 31c754764b4a87e5ef99dfa91d0603af5e4563b3 Mon Sep 17 00:00:00 2001 From: Jason S Zang Date: Sun, 12 Nov 2017 15:30:18 +0800 Subject: [PATCH 3/7] Allow generator to output to subdirectories under output dir. Also add a @ROOTNAME@ substitution which is replaced by full input path name with extensions removed. --- mesonbuild/build.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/mesonbuild/build.py b/mesonbuild/build.py index 024f79a8d555..07fef13870c3 100644 --- a/mesonbuild/build.py +++ b/mesonbuild/build.py @@ -1052,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 path_norm_split(rule): + raise InvalidArguments('Output file path name must not contain a .. component.') if len(outputs) > 1: for o in outputs: if '@OUTPUT@' in o: @@ -1077,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) From a657964697c479080f561ae7fdcf19c94d1d5478 Mon Sep 17 00:00:00 2001 From: Jason S Zang Date: Sun, 12 Nov 2017 16:11:24 +0800 Subject: [PATCH 4/7] Make @ROOTNAME@ also available to custom_targets and a minor bug fix. --- mesonbuild/mesonlib.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/mesonbuild/mesonlib.py b/mesonbuild/mesonlib.py index a9ae316c27b6..0c419a117573 100644 --- a/mesonbuild/mesonlib.py +++ b/mesonbuild/mesonlib.py @@ -623,8 +623,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) @@ -633,7 +633,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 ' @@ -723,6 +723,7 @@ def get_filenames_templates_dict(inputs, outputs): @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: @@ -744,6 +745,7 @@ 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 From a7ac10ffab3e21b65156bd48f2e7d5773efdcba9 Mon Sep 17 00:00:00 2001 From: Jason S Zang Date: Sun, 12 Nov 2017 18:41:26 +0800 Subject: [PATCH 5/7] Add tests for new features. Tests added: - unittest for @ROOTNAME@ substitution, configure_file - test case for configure_file into subdirectories - replace over-simplified protobuf test case with a complex one to test custom_target and generator that outputs to subdirs --- run_unittests.py | 14 +++-- .../16 configure file/include/prjdir/foo.h | 1 + .../include/prjdir/version.h.in | 1 + .../common/16 configure file/meson.build | 10 ++++ test cases/common/16 configure file/prog5.c | 6 +++ .../5 protocol buffers/asubdir/defs.proto | 3 -- .../5 protocol buffers/asubdir/main.cpp | 9 ---- .../5 protocol buffers/asubdir/meson.build | 8 --- .../frameworks/5 protocol buffers/defs.proto | 3 -- .../frameworks/5 protocol buffers/main.cpp | 9 ---- .../frameworks/5 protocol buffers/meson.build | 54 +++++++++++++++---- .../proto/dummy_service.proto | 8 +++ .../proto/messages/dummy_messages.proto | 8 +++ .../proto_generator/dummy_service3.proto | 8 +++ .../messages/dummy_messages3.proto | 8 +++ .../proto_subdir/dummy_service2.proto | 8 +++ .../proto_subdir/meson.build | 17 ++++++ .../messages/bits/dummy_enum.proto | 7 +++ .../messages/dummy_messages2.proto | 11 ++++ .../5 protocol buffers/src/prog.cpp | 11 ++++ 20 files changed, 158 insertions(+), 46 deletions(-) create mode 100644 test cases/common/16 configure file/include/prjdir/foo.h create mode 100644 test cases/common/16 configure file/include/prjdir/version.h.in create mode 100644 test cases/common/16 configure file/prog5.c delete mode 100644 test cases/frameworks/5 protocol buffers/asubdir/defs.proto delete mode 100644 test cases/frameworks/5 protocol buffers/asubdir/main.cpp delete mode 100644 test cases/frameworks/5 protocol buffers/asubdir/meson.build delete mode 100644 test cases/frameworks/5 protocol buffers/defs.proto delete mode 100644 test cases/frameworks/5 protocol buffers/main.cpp create mode 100644 test cases/frameworks/5 protocol buffers/proto/dummy_service.proto create mode 100644 test cases/frameworks/5 protocol buffers/proto/messages/dummy_messages.proto create mode 100644 test cases/frameworks/5 protocol buffers/proto_generator/dummy_service3.proto create mode 100644 test cases/frameworks/5 protocol buffers/proto_generator/messages/dummy_messages3.proto create mode 100644 test cases/frameworks/5 protocol buffers/proto_subdir/dummy_service2.proto create mode 100644 test cases/frameworks/5 protocol buffers/proto_subdir/meson.build create mode 100644 test cases/frameworks/5 protocol buffers/proto_subdir/messages/bits/dummy_enum.proto create mode 100644 test cases/frameworks/5 protocol buffers/proto_subdir/messages/dummy_messages2.proto create mode 100644 test cases/frameworks/5 protocol buffers/src/prog.cpp diff --git a/run_unittests.py b/run_unittests.py index 43b3fa6a227a..678f93e81129 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -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 @@ -235,6 +235,9 @@ def test_string_templates_substitution(self): cmd = ['@INPUT@', '@BASENAME@.hah', 'strings'] self.assertEqual(substfunc(cmd, d), inputs + [d['@BASENAME@'] + '.hah'] + cmd[2:]) + cmd = ['@INPUT@', '@ROOTNAME@.bar', 'strings'] + self.assertEqual(substfunc(cmd, d), + inputs + [d['@ROOTNAME@'] + '.bar'] + cmd[2:]) cmd = ['@OUTPUT@'] self.assertRaises(ME, substfunc, cmd, d) @@ -243,7 +246,7 @@ 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', + '@PLAINNAME@': 'foo.c.in', '@BASENAME@': 'foo.c', '@ROOTNAME@': 'bar/foo.c', '@OUTPUT@': outputs, '@OUTPUT0@': outputs[0]} # Check dictionary self.assertEqual(ret, d) @@ -259,12 +262,15 @@ def test_string_templates_substitution(self): cmd = ['@INPUT@', '@BASENAME@.hah', 'strings'] self.assertEqual(substfunc(cmd, d), inputs + [d['@BASENAME@'] + '.hah'] + cmd[2:]) + cmd = ['@INPUT@', '@ROOTNAME@.bar', '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', + '@PLAINNAME@': 'foo.c.in', '@BASENAME@': 'foo.c', '@ROOTNAME@': 'bar/foo.c', '@OUTPUT@': outputs, '@OUTPUT0@': outputs[0]} # Check dictionary self.assertEqual(ret, d) @@ -298,6 +304,8 @@ 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) diff --git a/test cases/common/16 configure file/include/prjdir/foo.h b/test cases/common/16 configure file/include/prjdir/foo.h new file mode 100644 index 000000000000..574f62d5e935 --- /dev/null +++ b/test cases/common/16 configure file/include/prjdir/foo.h @@ -0,0 +1 @@ +// An ordinary header. diff --git a/test cases/common/16 configure file/include/prjdir/version.h.in b/test cases/common/16 configure file/include/prjdir/version.h.in new file mode 100644 index 000000000000..eaab0180b5cf --- /dev/null +++ b/test cases/common/16 configure file/include/prjdir/version.h.in @@ -0,0 +1 @@ +#define VERSION "@version@" diff --git a/test cases/common/16 configure file/meson.build b/test cases/common/16 configure file/meson.build index 9dc5fb55e662..8e181feb637c 100644 --- a/test cases/common/16 configure file/meson.build +++ b/test cases/common/16 configure file/meson.build @@ -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) diff --git a/test cases/common/16 configure file/prog5.c b/test cases/common/16 configure file/prog5.c new file mode 100644 index 000000000000..fa6b0726cbc7 --- /dev/null +++ b/test cases/common/16 configure file/prog5.c @@ -0,0 +1,6 @@ +#include +#include + +int main(int argc, char **argv) { + return strcmp(VERSION, "1.0.0"); +} diff --git a/test cases/frameworks/5 protocol buffers/asubdir/defs.proto b/test cases/frameworks/5 protocol buffers/asubdir/defs.proto deleted file mode 100644 index f7956517c018..000000000000 --- a/test cases/frameworks/5 protocol buffers/asubdir/defs.proto +++ /dev/null @@ -1,3 +0,0 @@ -message Dummy { - required string text = 1; -} diff --git a/test cases/frameworks/5 protocol buffers/asubdir/main.cpp b/test cases/frameworks/5 protocol buffers/asubdir/main.cpp deleted file mode 100644 index f6566d52e663..000000000000 --- a/test cases/frameworks/5 protocol buffers/asubdir/main.cpp +++ /dev/null @@ -1,9 +0,0 @@ -#include "defs.pb.h" - -int main(int argc, char **argv) { - GOOGLE_PROTOBUF_VERIFY_VERSION; - Dummy *d = new Dummy; - delete d; - google::protobuf::ShutdownProtobufLibrary(); - return 0; -} diff --git a/test cases/frameworks/5 protocol buffers/asubdir/meson.build b/test cases/frameworks/5 protocol buffers/asubdir/meson.build deleted file mode 100644 index 972716590599..000000000000 --- a/test cases/frameworks/5 protocol buffers/asubdir/meson.build +++ /dev/null @@ -1,8 +0,0 @@ -subdirgen = generator(protoc, \ - output : ['@BASENAME@.pb.cc', '@BASENAME@.pb.h'], - arguments : ['--proto_path=@CURRENT_SOURCE_DIR@', '--cpp_out=@BUILD_DIR@', '@INPUT@']) - -generated = subdirgen.process('defs.proto') -e = executable('subdir-prog', 'main.cpp', generated, - dependencies : dep) -test('subdir-prototest', e) diff --git a/test cases/frameworks/5 protocol buffers/defs.proto b/test cases/frameworks/5 protocol buffers/defs.proto deleted file mode 100644 index f7956517c018..000000000000 --- a/test cases/frameworks/5 protocol buffers/defs.proto +++ /dev/null @@ -1,3 +0,0 @@ -message Dummy { - required string text = 1; -} diff --git a/test cases/frameworks/5 protocol buffers/main.cpp b/test cases/frameworks/5 protocol buffers/main.cpp deleted file mode 100644 index f6566d52e663..000000000000 --- a/test cases/frameworks/5 protocol buffers/main.cpp +++ /dev/null @@ -1,9 +0,0 @@ -#include "defs.pb.h" - -int main(int argc, char **argv) { - GOOGLE_PROTOBUF_VERIFY_VERSION; - Dummy *d = new Dummy; - delete d; - google::protobuf::ShutdownProtobufLibrary(); - return 0; -} diff --git a/test cases/frameworks/5 protocol buffers/meson.build b/test cases/frameworks/5 protocol buffers/meson.build index 58666f9b924c..7c758ea01d7c 100644 --- a/test cases/frameworks/5 protocol buffers/meson.build +++ b/test cases/frameworks/5 protocol buffers/meson.build @@ -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 : ['@BASENAME@.pb.cc', '@BASENAME@.pb.h'], - 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: ['@ROOTNAME@.pb.cc', '@ROOTNAME@.pb.h'], +) +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, +) diff --git a/test cases/frameworks/5 protocol buffers/proto/dummy_service.proto b/test cases/frameworks/5 protocol buffers/proto/dummy_service.proto new file mode 100644 index 000000000000..e870851017d2 --- /dev/null +++ b/test cases/frameworks/5 protocol buffers/proto/dummy_service.proto @@ -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) {}; +} \ No newline at end of file diff --git a/test cases/frameworks/5 protocol buffers/proto/messages/dummy_messages.proto b/test cases/frameworks/5 protocol buffers/proto/messages/dummy_messages.proto new file mode 100644 index 000000000000..7b539684cc96 --- /dev/null +++ b/test cases/frameworks/5 protocol buffers/proto/messages/dummy_messages.proto @@ -0,0 +1,8 @@ +syntax = "proto2"; +package configure_file; + +message DummyRequest { +} + +message DummyResponse { +} diff --git a/test cases/frameworks/5 protocol buffers/proto_generator/dummy_service3.proto b/test cases/frameworks/5 protocol buffers/proto_generator/dummy_service3.proto new file mode 100644 index 000000000000..4ae15dfa9f36 --- /dev/null +++ b/test cases/frameworks/5 protocol buffers/proto_generator/dummy_service3.proto @@ -0,0 +1,8 @@ +syntax = "proto2"; +package configure_file; +option cc_generic_services = true; +import "proto_generator/messages/dummy_messages3.proto"; + +service DummyService3 { + rpc DummyMethod (DummyRequest3) returns (DummyResponse3) {}; +} \ No newline at end of file diff --git a/test cases/frameworks/5 protocol buffers/proto_generator/messages/dummy_messages3.proto b/test cases/frameworks/5 protocol buffers/proto_generator/messages/dummy_messages3.proto new file mode 100644 index 000000000000..7b328b8f5ddc --- /dev/null +++ b/test cases/frameworks/5 protocol buffers/proto_generator/messages/dummy_messages3.proto @@ -0,0 +1,8 @@ +syntax = "proto2"; +package configure_file; + +message DummyRequest3 { +} + +message DummyResponse3 { +} diff --git a/test cases/frameworks/5 protocol buffers/proto_subdir/dummy_service2.proto b/test cases/frameworks/5 protocol buffers/proto_subdir/dummy_service2.proto new file mode 100644 index 000000000000..5a04517a45e2 --- /dev/null +++ b/test cases/frameworks/5 protocol buffers/proto_subdir/dummy_service2.proto @@ -0,0 +1,8 @@ +syntax = "proto2"; +package configure_file; +option cc_generic_services = true; +import "messages/dummy_messages2.proto"; + +service DummyService2 { + rpc DummyMethod (DummyRequest2) returns (DummyResponse2) {}; +} \ No newline at end of file diff --git a/test cases/frameworks/5 protocol buffers/proto_subdir/meson.build b/test cases/frameworks/5 protocol buffers/proto_subdir/meson.build new file mode 100644 index 000000000000..9aea85473b78 --- /dev/null +++ b/test cases/frameworks/5 protocol buffers/proto_subdir/meson.build @@ -0,0 +1,17 @@ +protos_subdir_files = [ + 'messages/bits/dummy_enum.proto', + 'messages/dummy_messages2.proto', + 'dummy_service2.proto', +] +protos_subdir_src = [] +protos_subdir_hdr = [] +foreach p : protos_subdir_files + cc_target = custom_target( + p.underscorify(), + input: p, + output: ['@ROOTNAME@.pb.cc', '@ROOTNAME@.pb.h'], + command: [protoc, '-I', '@CURRENT_SOURCE_DIR@', '--cpp_out', '@OUTDIR@', '@INPUT@'] + ) + protos_subdir_src += cc_target[0] + protos_subdir_hdr += cc_target[1] +endforeach diff --git a/test cases/frameworks/5 protocol buffers/proto_subdir/messages/bits/dummy_enum.proto b/test cases/frameworks/5 protocol buffers/proto_subdir/messages/bits/dummy_enum.proto new file mode 100644 index 000000000000..d9a4bfebb750 --- /dev/null +++ b/test cases/frameworks/5 protocol buffers/proto_subdir/messages/bits/dummy_enum.proto @@ -0,0 +1,7 @@ +syntax = "proto2"; +package configure_file; + +enum DummyEnum { + DUMMY_OK = 1; + DUMMY_ERROR = 2; +} \ No newline at end of file diff --git a/test cases/frameworks/5 protocol buffers/proto_subdir/messages/dummy_messages2.proto b/test cases/frameworks/5 protocol buffers/proto_subdir/messages/dummy_messages2.proto new file mode 100644 index 000000000000..371f8dfeb5c2 --- /dev/null +++ b/test cases/frameworks/5 protocol buffers/proto_subdir/messages/dummy_messages2.proto @@ -0,0 +1,11 @@ +syntax = "proto2"; +package configure_file; +import "messages/bits/dummy_enum.proto"; + +message DummyRequest2 { + optional DummyEnum e = 1; +} + +message DummyResponse2 { + optional DummyEnum e = 1; +} diff --git a/test cases/frameworks/5 protocol buffers/src/prog.cpp b/test cases/frameworks/5 protocol buffers/src/prog.cpp new file mode 100644 index 000000000000..9c99d5fadca6 --- /dev/null +++ b/test cases/frameworks/5 protocol buffers/src/prog.cpp @@ -0,0 +1,11 @@ +/** + * dummy.cpp + */ + +#include +#include +#include + +int main() { + return 0; +} From 6424f10613fbc153734fc7c0098e92178d21e60e Mon Sep 17 00:00:00 2001 From: Jason S Zang Date: Sun, 12 Nov 2017 20:47:45 +0800 Subject: [PATCH 6/7] Replace failing test case "output subdir" with "output escaping" as outputting to subdir is now allowed with this patch. Test "output escaping" make sure people cannot make their output escape by adding '..' in output path name. --- .../failing/{27 output subdir => 27 output escaping}/foo.in | 0 .../{27 output subdir => 27 output escaping}/meson.build | 2 +- .../{27 output subdir => 27 output escaping}/subdir/dummy.txt | 0 3 files changed, 1 insertion(+), 1 deletion(-) rename test cases/failing/{27 output subdir => 27 output escaping}/foo.in (100%) rename test cases/failing/{27 output subdir => 27 output escaping}/meson.build (78%) rename test cases/failing/{27 output subdir => 27 output escaping}/subdir/dummy.txt (100%) diff --git a/test cases/failing/27 output subdir/foo.in b/test cases/failing/27 output escaping/foo.in similarity index 100% rename from test cases/failing/27 output subdir/foo.in rename to test cases/failing/27 output escaping/foo.in diff --git a/test cases/failing/27 output subdir/meson.build b/test cases/failing/27 output escaping/meson.build similarity index 78% rename from test cases/failing/27 output subdir/meson.build rename to test cases/failing/27 output escaping/meson.build index 282d0b70aa60..f82590bc1512 100644 --- a/test cases/failing/27 output subdir/meson.build +++ b/test cases/failing/27 output escaping/meson.build @@ -1,5 +1,5 @@ project('outdir path', 'c') configure_file(input : 'foo.in', - output : 'subdir/foo', + output : '../subdir/foo', configuration : configuration_data()) diff --git a/test cases/failing/27 output subdir/subdir/dummy.txt b/test cases/failing/27 output escaping/subdir/dummy.txt similarity index 100% rename from test cases/failing/27 output subdir/subdir/dummy.txt rename to test cases/failing/27 output escaping/subdir/dummy.txt From ebb79708c36296799e037d1a4992d9e2e1b8d024 Mon Sep 17 00:00:00 2001 From: Jason S Zang Date: Mon, 13 Nov 2017 01:43:22 +0800 Subject: [PATCH 7/7] Use pathlib instead of os.path to check for '..' and fix a bug breaking --layout flat builds. Note that currently building complex protobuf with --layout flat still WON'T work, since @CURRENT_SOURCE_DIR@ will be incorrectly substituted with '../meson-out', which is obviously not our source dir. Backend.get_target_source_dir seems a little buggy right now. It seems not taking layout --flat into consideration. Maybe I should also try to fix it? --- mesonbuild/backend/backends.py | 1 - mesonbuild/build.py | 6 +++--- mesonbuild/interpreter.py | 3 ++- mesonbuild/mesonlib.py | 13 ------------- 4 files changed, 5 insertions(+), 18 deletions(-) diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py index b04321946883..8611ca2688ee 100644 --- a/mesonbuild/backend/backends.py +++ b/mesonbuild/backend/backends.py @@ -782,7 +782,6 @@ def eval_custom_target_command(self, target, absolute_outputs=False): elif '@BUILD_ROOT@' in i: i = i.replace('@BUILD_ROOT@', build_root) elif '@OUTDIR@' in i: - outdir = target.subdir if outdir == '': outdir = '.' i = i.replace('@OUTDIR@', outdir) diff --git a/mesonbuild/build.py b/mesonbuild/build.py index 07fef13870c3..a8b2f5adbbda 100644 --- a/mesonbuild/build.py +++ b/mesonbuild/build.py @@ -15,6 +15,7 @@ import copy, os, re from collections import OrderedDict import itertools +import pathlib from . import environment from . import dependencies @@ -22,7 +23,6 @@ from .mesonlib import File, MesonException, listify, extract_as_list from .mesonlib import typeslistify, stringlistify, classify_unity_sources from .mesonlib import get_filenames_templates_dict, substitute_values -from .mesonlib import path_norm_split from .environment import for_windows, for_darwin, for_cygwin from .compilers import is_object, clike_langs, sort_clike, lang_suffixes @@ -1054,7 +1054,7 @@ def process_kwargs(self, kwargs): raise InvalidArguments('"output" may only contain strings.') 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 path_norm_split(rule): + 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: @@ -1591,7 +1591,7 @@ def process_kwargs(self, kwargs): raise InvalidArguments('Output argument not a string.') if os.path.isabs(i): raise InvalidArguments('Output file path name must not be an absolute path.') - if '..' in path_norm_split(i): + 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 ' \ diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index 14a2784c8988..90f9dfd34707 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -33,6 +33,7 @@ import os, sys, shutil, uuid import re, shlex from collections import namedtuple +import pathlib import importlib @@ -2619,7 +2620,7 @@ def func_configure_file(self, node, args, kwargs): output = outputs[0] if os.path.isabs(output): raise InterpreterException('Output file path name must not be an absolute path.') - if '..' in mesonlib.path_norm_split(output): + 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) diff --git a/mesonbuild/mesonlib.py b/mesonbuild/mesonlib.py index 0c419a117573..53cf8315a491 100644 --- a/mesonbuild/mesonlib.py +++ b/mesonbuild/mesonlib.py @@ -539,19 +539,6 @@ def expand_arguments(args): return None return expended_args -def path_norm_split(path): - ''' - Normalize a path and split into a list of components. First component of an absolute - path will be os.sep - Drive will be discarded. - ''' - _, path = os.path.splitdrive(path) - norm_path = os.path.normpath(path) - components = norm_path.split(os.sep) - if components[0] == '': - components[0] = os.sep - return components - def Popen_safe(args, write=None, stderr=subprocess.PIPE, **kwargs): if sys.version_info < (3, 6) or not sys.stdout.encoding: return Popen_safe_legacy(args, write=write, stderr=stderr, **kwargs)