diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py index bc4996694..05a3565f5 100644 --- a/mesonbuild/backend/backends.py +++ b/mesonbuild/backend/backends.py @@ -92,6 +92,7 @@ class Backend(): raise RuntimeError('No specified compiler can handle file ' + src) def get_target_filename(self, target): + assert(isinstance(target, (build.BuildTarget, build.CustomTarget))) targetdir = self.get_target_dir(target) fname = target.get_filename() if isinstance(fname, list): @@ -101,6 +102,9 @@ class Backend(): filename = os.path.join(targetdir, fname) return filename + def get_target_filename_abs(self, target): + return os.path.join(self.environment.get_build_dir(), self.get_target_filename(target)) + def get_target_filename_for_linking(self, target): # On some platforms (msvc for instance), the file that is used for # dynamic linking is not the same as the dynamic library itself. This diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index b583e6290..9bc4842df 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -347,11 +347,8 @@ int dummy; if isinstance(s, build.GeneratedList): self.generate_genlist_for_target(s, target, outfile) - def generate_custom_target(self, target, outfile): - self.custom_target_generator_inputs(target, outfile) - (srcs, ofilenames, cmd) = self.eval_custom_target_command(target) + def unwrap_dep_list(self, target): deps = [] - desc = 'Generating {0} with a {1} command.' for i in target.get_dependencies(): # FIXME, should not grab element at zero but rather expand all. if isinstance(i, list): @@ -360,6 +357,13 @@ int dummy; if isinstance(fname, list): fname = fname[0] deps.append(os.path.join(self.get_target_dir(i), fname)) + return deps + + def generate_custom_target(self, target, outfile): + self.custom_target_generator_inputs(target, outfile) + (srcs, ofilenames, cmd) = self.eval_custom_target_command(target) + deps = self.unwrap_dep_list(target) + desc = 'Generating {0} with a {1} command.' if target.build_always: deps.append('PHONY') elem = NinjaBuildElement(self.all_outputs, ofilenames, 'CUSTOM_COMMAND', srcs) @@ -397,19 +401,19 @@ int dummy; def generate_run_target(self, target, outfile): runnerscript = [sys.executable, self.environment.get_build_command(), '--internal', 'commandrunner'] - deps = [] + deps = self.unwrap_dep_list(target) arg_strings = [] for i in target.args: if isinstance(i, str): arg_strings.append(i) elif isinstance(i, (build.BuildTarget, build.CustomTarget)): relfname = self.get_target_filename(i) - deps.append(relfname) arg_strings.append(os.path.join(self.environment.get_build_dir(), relfname)) else: mlog.debug(str(i)) raise MesonException('Unreachable code in generate_run_target.') elem = NinjaBuildElement(self.all_outputs, target.name, 'CUSTOM_COMMAND', deps) + elem.add_dep(deps) cmd = runnerscript + [self.environment.get_source_dir(), self.environment.get_build_dir(), target.subdir] texe = target.command try: diff --git a/mesonbuild/build.py b/mesonbuild/build.py index bfdbca8aa..c60f37f15 100644 --- a/mesonbuild/build.py +++ b/mesonbuild/build.py @@ -937,10 +937,11 @@ class CustomTarget: return "@cus" class RunTarget: - def __init__(self, name, command, args, subdir): + def __init__(self, name, command, args, dependencies, subdir): self.name = name self.command = command self.args = args + self.dependencies = dependencies self.subdir = subdir def get_id(self): @@ -950,7 +951,7 @@ class RunTarget: return self.name def get_dependencies(self): - return [] + return self.dependencies def get_generated_sources(self): return [] diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index c997e0e61..5281be599 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -23,11 +23,13 @@ from . import compilers from .wrap import wrap from . import mesonlib -import os, sys, platform, subprocess, shutil, uuid, re +import os, sys, subprocess, shutil, uuid, re from functools import wraps import importlib +run_depr_printed = False + class InterpreterException(mesonlib.MesonException): pass @@ -473,6 +475,7 @@ class BuildTargetHolder(InterpreterObject): 'extract_all_objects' : self.extract_all_objects_method, 'get_id': self.get_id_method, 'outdir' : self.outdir_method, + 'full_path' : self.full_path_method, 'private_dir_include' : self.private_dir_include_method, }) @@ -483,6 +486,9 @@ class BuildTargetHolder(InterpreterObject): return IncludeDirsHolder(build.IncludeDirs('', [], False, [self.interpreter.backend.get_target_private_dir(self.held_object)])) + def full_path_method(self, args, kwargs): + return self.interpreter.backend.get_target_filename_abs(self.held_object) + def outdir_method(self, args, kwargs): return self.interpreter.backend.get_target_dir(self.held_object) @@ -514,19 +520,19 @@ class JarHolder(BuildTargetHolder): super().__init__(target, interp) class CustomTargetHolder(InterpreterObject): - def __init__(self, object_to_hold): + def __init__(self, object_to_hold, interp): + super().__init__() self.held_object = object_to_hold + self.interpreter = interp + self.methods.update({'full_path' : self.full_path_method, + }) - def is_cross(self): - return self.held_object.is_cross() - - def extract_objects_method(self, args, kwargs): - gobjs = self.held_object.extract_objects(args) - return GeneratedObjectsHolder(gobjs) + def full_path_method(self, args, kwargs): + return self.interpreter.backend.get_target_filename_abs(self.held_object) class RunTargetHolder(InterpreterObject): - def __init__(self, name, command, args, subdir): - self.held_object = build.RunTarget(name, command, args, subdir) + def __init__(self, name, command, args, dependencies, subdir): + self.held_object = build.RunTarget(name, command, args, dependencies, subdir) class Test(InterpreterObject): def __init__(self, name, suite, exe, is_parallel, cmd_args, env, should_fail, valgrind_args, timeout, workdir): @@ -1069,7 +1075,7 @@ class Interpreter(): for v in invalues: if isinstance(v, build.CustomTarget): self.add_target(v.name, v) - outvalues.append(CustomTargetHolder(v)) + outvalues.append(CustomTargetHolder(v, self)) elif isinstance(v, int) or isinstance(v, str): outvalues.append(v) elif isinstance(v, build.Executable): @@ -1754,16 +1760,32 @@ class Interpreter(): if len(args) != 1: raise InterpreterException('Incorrect number of arguments') name = args[0] - tg = CustomTargetHolder(build.CustomTarget(name, self.subdir, kwargs)) + tg = CustomTargetHolder(build.CustomTarget(name, self.subdir, kwargs), self) self.add_target(name, tg.held_object) return tg - @noKwargs def func_run_target(self, node, args, kwargs): - if len(args) < 2: - raise InterpreterException('Incorrect number of arguments') + global run_depr_printed + if len(args) > 1: + if not run_depr_printed: + mlog.log(mlog.red('DEPRECATION'), 'positional version of run_target is deprecated, use the keyword version instead.') + run_depr_printed = True + if 'command' in kwargs: + raise InterpreterException('Can not have command both in positional and keyword arguments.') + all_args = args[1:] + deps = [] + elif len(args) == 1: + if not 'command' in kwargs: + raise InterpreterException('Missing "command" keyword argument') + all_args = kwargs['command'] + deps = kwargs.get('depends', []) + if not isinstance(deps, list): + deps = [deps] + else: + raise InterpreterException('Run_target needs at least one positional argument.') + cleaned_args = [] - for i in args: + for i in all_args: try: i = i.held_object except AttributeError: @@ -1772,10 +1794,21 @@ class Interpreter(): mlog.debug('Wrong type:', str(i)) raise InterpreterException('Invalid argument to run_target.') cleaned_args.append(i) - name = cleaned_args[0] - command = cleaned_args[1] - cmd_args = cleaned_args[2:] - tg = RunTargetHolder(name, command, cmd_args, self.subdir) + name = args[0] + if not isinstance(name, str): + raise InterpreterException('First argument must be a string.') + cleaned_deps = [] + for d in deps: + try: + d = d.held_object + except AttributeError: + pass + if not isinstance(d, (build.BuildTarget, build.CustomTarget)): + raise InterpreterException('Depends items must be build targets.') + cleaned_deps.append(d) + command = cleaned_args[0] + cmd_args = cleaned_args[1:] + tg = RunTargetHolder(name, command, cmd_args, cleaned_deps, self.subdir) self.add_target(name, tg.held_object) return tg diff --git a/mesonbuild/modules/i18n.py b/mesonbuild/modules/i18n.py index 4b529c704..33874fa6a 100644 --- a/mesonbuild/modules/i18n.py +++ b/mesonbuild/modules/i18n.py @@ -26,9 +26,9 @@ class I18nModule: raise coredata.MesonException('List of languages empty.') extra_args = mesonlib.stringlistify(kwargs.get('args', [])) potargs = [state.environment.get_build_command(), '--internal', 'gettext', 'pot', packagename] + extra_args - pottarget = build.RunTarget(packagename + '-pot', sys.executable, potargs, state.subdir) + pottarget = build.RunTarget(packagename + '-pot', sys.executable, potargs, [], state.subdir) gmoargs = [state.environment.get_build_command(), '--internal', 'gettext', 'gen_gmo'] + languages - gmotarget = build.RunTarget(packagename + '-gmo', sys.executable, gmoargs, state.subdir) + gmotarget = build.RunTarget(packagename + '-gmo', sys.executable, gmoargs, [], state.subdir) installcmd = [sys.executable, state.environment.get_build_command(), '--internal', diff --git a/test cases/common/58 run target/converter.py b/test cases/common/58 run target/converter.py new file mode 100644 index 000000000..6acbc84e1 --- /dev/null +++ b/test cases/common/58 run target/converter.py @@ -0,0 +1,5 @@ +#!/usr/bin/env python3 + +import sys + +open(sys.argv[2], 'wb').write(open(sys.argv[1], 'rb').read()) diff --git a/test cases/common/58 run target/fakeburner.py b/test cases/common/58 run target/fakeburner.py new file mode 100755 index 000000000..a100a6f66 --- /dev/null +++ b/test cases/common/58 run target/fakeburner.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python3 + +import sys + +plain_arg = sys.argv[1] +_, filename, _ = plain_arg.split(':') +try: + content = open(filename, 'rb').read() +except FileNotFoundError: + print('Could not open file. Missing dependency?') + sys.exit(1) +print('File opened, pretending to send it somewhere.') +print(len(content), 'bytes uploaded') diff --git a/test cases/common/58 run target/meson.build b/test cases/common/58 run target/meson.build index fa25f2a8d..0febe3565 100644 --- a/test cases/common/58 run target/meson.build +++ b/test cases/common/58 run target/meson.build @@ -1,12 +1,36 @@ project('run target', 'c') -run_target('mycommand', 'scripts/script.sh') +# deprecated format, fix once we remove support for it. +run_target('mycommand','scripts/script.sh') # Make it possible to run built programs. # In cross builds exe_wrapper should be added if it exists. exe = executable('helloprinter', 'helloprinter.c') -run_target('runhello', exe, 'argument') +run_target('runhello', + command : [exe, 'argument']) + +converter = find_program('converter.py') + +hex = custom_target('exe.hex', + input : exe, + output : 'exe.hex', + command : [converter, '@INPUT@', '@OUTPUT@', + ], +) + +# These emulates the Arduino flasher application. It sandwiches the filename inside +# a packed argument. Thus we need to declare it manually. +run_target('upload', + command : ['fakeburner.py', 'x:@0@:y'.format(exe.full_path())], + depends : exe, +) + +run_target('upload2', + command : ['fakeburner.py', 'x:@0@:y'.format(hex.full_path())], + depends : hex, +) python3 = find_program('python3') -run_target('py3hi', python3, '-c', 'print("I am Python3.")') +run_target('py3hi', + command : [python3, '-c', 'print("I am Python3.")'])