From 3a4a94d28d47bd142a27e53a5a0670fcf1dae5c4 Mon Sep 17 00:00:00 2001 From: Jussi Pakkanen Date: Mon, 13 Apr 2020 21:17:56 +0300 Subject: [PATCH] Add just enough backend to make the simple case work. --- mesonbuild/backend/ninjabackend.py | 34 +++++ mesonbuild/build.py | 125 +++++++++++++----- mesonbuild/interpreter.py | 29 +++- .../common/234 generator target/meson.build | 14 +- 4 files changed, 157 insertions(+), 45 deletions(-) diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index 9b895c908..b79c8eba1 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -483,8 +483,13 @@ int dummy; def generate_target(self, target): if isinstance(target, build.CustomTarget): self.generate_custom_target(target) + return if isinstance(target, build.RunTarget): self.generate_run_target(target) + return + if isinstance(target, build.GeneratorTarget): + self.generate_generatortarget_target(target) + return name = target.get_id() if name in self.processed_targets: return @@ -705,6 +710,35 @@ int dummy; self.add_build(elem) self.processed_targets[target.get_id()] = True + def generate_generatortarget_target(self, target): + '''Brought to you by the department of redundancy department.''' + self.processed_targets[target.get_id()] = True + assert(target.output.owning_gentarget is target) + genlist = target.output + generator = target.generator + assert(generator is genlist.get_generator()) + # FIXME. start simple, add multiple outputs later. + assert(len(genlist.get_outputs()) == len(genlist.get_inputs())) + # A generatortarget is special compared to other target types. + # Its output goes to a named subdirectory in the build dir + # that can contain an arbitrary number of files and subdirs. + output_subdir = os.path.join(target.subdir, target.name) + for input in genlist.get_inputs(): + ifile_str = input.rel_to_builddir(self.build_to_src) + cmd = generator.get_exe().get_command() + generator.get_arglist(ifile_str) + outputs = os.path.join(output_subdir, genlist.get_outputs_for(input)[0]) + e = NinjaBuildElement(self.all_outputs, outputs, 'CUSTOM_COMMAND', ifile_str) + # HACK, just to get something working. + cmd2 = [] + for c in cmd: + if c == '@INPUT@': + c = ifile_str + if c == '@OUTPUT@': + c = outputs + cmd2.append(c) + e.add_item('COMMAND', cmd2) + self.add_build(e) + def build_run_target_name(self, target): if target.subproject != '': subproject_prefix = '{}@@'.format(target.subproject) diff --git a/mesonbuild/build.py b/mesonbuild/build.py index 2b4b1b980..4fb977e7c 100644 --- a/mesonbuild/build.py +++ b/mesonbuild/build.py @@ -1413,8 +1413,15 @@ class Generator: relpath = pathlib.PurePath(trial).relative_to(parent) return relpath.parts[0] != '..' # For subdirs we can only go "down". - def process_files(self, name, files, state, preserve_path_from=None, extra_args=None): - output = GeneratedList(self, state.subdir, preserve_path_from, extra_args=extra_args if extra_args is not None else []) + def process_files(self, name, files, state, *, + preserve_path_from=None, + extra_args=None, + owning_gentarget=None): + output = GeneratedList(self, + state.subdir, + preserve_path_from=preserve_path_from, + extra_args=extra_args if extra_args is not None else [], + owning_gentarget=owning_gentarget) for f in files: if isinstance(f, str): f = File.from_source_file(state.environment.source_dir, state.subdir, f) @@ -1429,7 +1436,7 @@ class Generator: class GeneratedList: - def __init__(self, generator, subdir, preserve_path_from=None, extra_args=None): + def __init__(self, generator, subdir, *, preserve_path_from=None, extra_args=None, owning_gentarget=None): self.generator = unholder(generator) self.name = self.generator.exe self.subdir = subdir @@ -1448,6 +1455,11 @@ class GeneratedList: # Can only add a dependency on an external program which we # know the absolute path of self.depend_files.append(File.from_absolute_file(path)) + # If None, this generated list is of the old freestanding type. + # Its output goes in the target private directory. Otherwise it is + # the outcome of a generator_target and the output goes in its + # output dir. + self.owning_gentarget = owning_gentarget def add_preserved_path_segment(self, infile, outfiles, state): result = [] @@ -1993,8 +2005,41 @@ class SharedModule(SharedLibrary): def get_default_install_dir(self, environment): return environment.get_shared_module_dir() +class CustomMixin: + def __init__(self, *args, **kwargs): + self.dependencies = [] + self.extra_depends = [] + self.depend_files = [] # Files that this target depends on but are not on the command line. + self.depfile = None -class CustomTarget(Target): + def flatten_command(self, cmd): + cmd = unholder(listify(cmd)) + final_cmd = [] + for c in cmd: + if isinstance(c, str): + final_cmd.append(c) + elif isinstance(c, File): + self.depend_files.append(c) + final_cmd.append(c) + elif isinstance(c, dependencies.ExternalProgram): + if not c.found(): + raise InvalidArguments('Tried to use not-found external program in "command"') + path = c.get_path() + if os.path.isabs(path): + # Can only add a dependency on an external program which we + # know the absolute path of + self.depend_files.append(File.from_absolute_file(path)) + final_cmd += c.get_command() + elif isinstance(c, (BuildTarget, CustomTarget)): + self.dependencies.append(c) + final_cmd.append(c) + elif isinstance(c, list): + final_cmd += self.flatten_command(c) + else: + raise InvalidArguments('Argument {!r} in "command" is invalid'.format(c)) + return final_cmd + +class CustomTarget(Target, CustomMixin): known_kwargs = set([ 'input', 'output', @@ -2016,11 +2061,8 @@ class CustomTarget(Target): def __init__(self, name, subdir, subproject, kwargs, absolute_paths=False, backend=None): self.typename = 'custom' # TODO expose keyword arg to make MachineChoice.HOST configurable - super().__init__(name, subdir, subproject, False, MachineChoice.HOST) - self.dependencies = [] - self.extra_depends = [] - self.depend_files = [] # Files that this target depends on but are not on the command line. - self.depfile = None + Target.__init__(self, name, subdir, subproject, False, MachineChoice.HOST) + CustomMixin.__init__(self) self.process_kwargs(kwargs, backend) self.extra_files = [] # Whether to use absolute paths for all files on the commandline @@ -2065,33 +2107,6 @@ class CustomTarget(Target): bdeps.update(d.get_transitive_build_target_deps()) return bdeps - def flatten_command(self, cmd): - cmd = unholder(listify(cmd)) - final_cmd = [] - for c in cmd: - if isinstance(c, str): - final_cmd.append(c) - elif isinstance(c, File): - self.depend_files.append(c) - final_cmd.append(c) - elif isinstance(c, dependencies.ExternalProgram): - if not c.found(): - raise InvalidArguments('Tried to use not-found external program in "command"') - path = c.get_path() - if os.path.isabs(path): - # Can only add a dependency on an external program which we - # know the absolute path of - self.depend_files.append(File.from_absolute_file(path)) - final_cmd += c.get_command() - elif isinstance(c, (BuildTarget, CustomTarget)): - self.dependencies.append(c) - final_cmd.append(c) - elif isinstance(c, list): - final_cmd += self.flatten_command(c) - else: - raise InvalidArguments('Argument {!r} in "command" is invalid'.format(c)) - return final_cmd - def process_kwargs(self, kwargs, backend): self.process_kwargs_base(kwargs) self.sources = unholder(extract_as_list(kwargs, 'input')) @@ -2261,6 +2276,44 @@ class CustomTarget(Target): for i in self.outputs: yield CustomTargetIndex(self, i) +class GeneratorTarget(Target, CustomMixin): + + def __init__(self, name, state, kwargs, backend=None): + self.typename = 'gent' + Target.__init__(self, name, state.subdir, state.subproject, False, MachineChoice.HOST) + CustomMixin.__init__(self) + self.sources = unholder(extract_as_list(kwargs, 'input')) + self.process_kwargs(kwargs, backend) + self.extra_files = [] + self.output = self.generator.process_files('GeneratorTarget', + self.sources, + state, + owning_gentarget=self) + assert(isinstance(self.output, GeneratedList)) + + def __repr__(self): + repr_str = "<{0} {1}: {2}>" + return repr_str.format(self.__class__.__name__, self.get_id(), self.command) + + def type_suffix(self): + return "@gta" + + def get_dependencies(self): + return self.dependencies + + def process_kwargs(self, kwargs, backend): + if 'generator' not in kwargs: + raise InvalidArguments('Missing keyword argument "command".') + self.generator = unholder(kwargs['generator']) + if not isinstance(self.generator, Generator): + raise InvalidArguments("Generator argument is not a generator object.") + + def should_install(self): + return False + + def get_outputs(self): + return [] + class RunTarget(Target): def __init__(self, name, command, args, dependencies, subdir, subproject): self.typename = 'run' diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index a3e9dee6a..cce47fe39 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -638,8 +638,11 @@ class GeneratorHolder(InterpreterObject, ObjectHolder): raise InvalidArguments('Preserve_path_from must be an absolute path for now. Sorry.') else: preserve_path_from = None - gl = self.held_object.process_files('Generator', args, self.interpreter, - preserve_path_from, extra_args=extras) + gl = self.held_object.process_files('Generator', + args, + self.interpreter, + preserve_path_from=preserve_path_from, + extra_args=extras) return GeneratedListHolder(gl) @@ -953,6 +956,10 @@ class CustomTargetHolder(TargetHolder): return IncludeDirsHolder(build.IncludeDirs('', [], False, [os.path.join('@BUILD_ROOT@', self.interpreter.backend.get_target_dir(self.held_object))])) +class GeneratorTargetHolder(TargetHolder): + def __init__(self, target, interp): + super().__init__(target, interp) + class RunTargetHolder(TargetHolder): def __init__(self, target, interp): super().__init__(target, interp) @@ -2265,6 +2272,9 @@ permitted_kwargs = {'add_global_arguments': {'language', 'native'}, 'depfile', 'capture', 'preserve_path_from'}, + 'generator_target': {'generator', + 'output', + 'sources'}, 'include_directories': {'is_system'}, 'install_data': {'install_dir', 'install_mode', 'rename', 'sources'}, 'install_headers': {'install_dir', 'install_mode', 'subdir'}, @@ -2382,6 +2392,7 @@ class Interpreter(InterpreterBase): 'error': self.func_error, 'executable': self.func_executable, 'generator': self.func_generator, + 'generator_target': self.func_generator_target, 'gettext': self.func_gettext, 'get_option': self.func_get_option, 'get_variable': self.func_get_variable, @@ -3809,6 +3820,20 @@ This will become a hard error in the future.''' % kwargs['input'], location=self self.generators.append(gen) return gen + @stringArgs + @permittedKwargs(permitted_kwargs['generator_target']) + def func_generator_target(self, node, args, kwargs): + if len(args) != 1: + raise InterpreterException('custom_target: Only one positional argument is allowed, and it must be a string name') + + name = args[0] + gt = GeneratorTargetHolder(build.GeneratorTarget(name, + self, + kwargs), + self) + self.add_target(name, gt.held_object) + return gt + @FeatureNewKwargs('benchmark', '0.46.0', ['depends']) @FeatureNewKwargs('benchmark', '0.52.0', ['priority']) @permittedKwargs(permitted_kwargs['benchmark']) diff --git a/test cases/common/234 generator target/meson.build b/test cases/common/234 generator target/meson.build index 02f84d7a4..2cde4dcd8 100644 --- a/test cases/common/234 generator target/meson.build +++ b/test cases/common/234 generator target/meson.build @@ -1,11 +1,11 @@ project('generator target', 'c') subdir('gen1') -subdir('gen2') -subdir('genzip') -subdir('gensrc') -subdir('prog') +#subdir('gen2') +#subdir('genzip') +#subdir('gensrc') +#subdir('prog') -add_test('validate_zip', - find_program('validate_zip.py'), - args: [zip_output]) +#add_test('validate_zip', +# find_program('validate_zip.py'), +# args: [zip_output])