From 2c0eaf5c4f4493146355eeb8521c17a3c2ef5acd Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Thu, 16 Apr 2020 21:42:38 -0700 Subject: [PATCH 1/3] interpreter: Allow install_script to use additional input types This adds support for Files, CustomTarget, Indexs of CustomTargets, ConfigureFiles, ExternalPrograms, and Executables. Fixes: #1234 Fixes: #3552 Fixes: #6175 --- mesonbuild/interpreter.py | 70 ++++++++++++++++--- .../common/56 install script/customtarget.py | 19 +++++ .../common/56 install script/meson.build | 26 +++++++ .../common/56 install script/myinstall.py | 29 ++++++-- .../common/56 install script/src/a file.txt | 0 test cases/common/56 install script/src/exe.c | 24 +++++++ .../common/56 install script/src/meson.build | 4 ++ .../common/56 install script/src/myinstall.py | 4 +- test cases/common/56 install script/test.json | 10 ++- test cases/common/56 install script/wrap.py | 6 ++ 10 files changed, 174 insertions(+), 18 deletions(-) create mode 100755 test cases/common/56 install script/customtarget.py create mode 100644 test cases/common/56 install script/src/a file.txt create mode 100644 test cases/common/56 install script/src/exe.c create mode 100755 test cases/common/56 install script/wrap.py diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index dd1e57bad..2b699f8fa 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -1893,31 +1893,79 @@ class MesonMain(InterpreterObject): 'backend': self.backend_method, }) - def _find_source_script(self, name, args): + def _find_source_script(self, prog: T.Union[str, ExecutableHolder], args): + if isinstance(prog, ExecutableHolder): + prog_path = self.interpreter.backend.get_target_filename(prog.held_object) + return build.RunScript([prog_path], args) + elif isinstance(prog, ExternalProgramHolder): + return build.RunScript(prog.get_command(), args) + # Prefer scripts in the current source directory search_dir = os.path.join(self.interpreter.environment.source_dir, self.interpreter.subdir) - key = (name, search_dir) + key = (prog, search_dir) if key in self._found_source_scripts: found = self._found_source_scripts[key] else: - found = dependencies.ExternalProgram(name, search_dir=search_dir) + found = dependencies.ExternalProgram(prog, search_dir=search_dir) if found.found(): self._found_source_scripts[key] = found else: m = 'Script or command {!r} not found or not executable' - raise InterpreterException(m.format(name)) + raise InterpreterException(m.format(prog)) return build.RunScript(found.get_command(), args) - @permittedKwargs({}) - def add_install_script_method(self, args, kwargs): + def _process_script_args( + self, name: str, args: T.List[T.Union[ + str, mesonlib.File, CustomTargetHolder, + CustomTargetIndexHolder, ConfigureFileHolder, + ExternalProgramHolder, ExecutableHolder, + ]]) -> T.List[str]: + script_args = [] # T.List[str] + new = False + for a in args: + a = unholder(a) + if isinstance(a, str): + script_args.append(a) + elif isinstance(a, mesonlib.File): + new = True + script_args.append(a.rel_to_builddir(self.interpreter.environment.source_dir)) + elif isinstance(a, (build.BuildTarget, build.CustomTarget, build.CustomTargetIndex)): + new = True + script_args.extend([os.path.join(a.get_subdir(), o) for o in a.get_outputs()]) + + # This feels really hacky, but I'm not sure how else to fix + # this without completely rewriting install script handling. + # This is complicated by the fact that the install target + # depends on all. + if isinstance(a, build.CustomTargetIndex): + a.target.build_by_default = True + else: + a.build_by_default = True + elif isinstance(a, build.ConfigureFile): + new = True + script_args.append(os.path.join(a.subdir, a.targetname)) + elif isinstance(a, dependencies.ExternalProgram): + script_args.extend(a.command) + new = True + else: + raise InterpreterException( + 'Arguments to {} must be strings, Files, CustomTargets, ' + 'Indexes of CustomTargets, or ConfigureFiles'.format(name)) + if new: + FeatureNew('Calling "{}" with File, CustomTaget, Index of CustomTarget, ConfigureFile, Executable, or ExternalProgram'.format(name), '0.55.0').use( + self.interpreter.subproject) + return script_args + + @permittedKwargs(set()) + def add_install_script_method(self, args: 'T.Tuple[T.Union[str, ExecutableHolder], T.Union[str, mesonlib.File, CustomTargetHolder, CustomTargetIndexHolder, ConfigureFileHolder], ...]', kwargs): if len(args) < 1: raise InterpreterException('add_install_script takes one or more arguments') - check_stringlist(args, 'add_install_script args must be strings') - script = self._find_source_script(args[0], args[1:]) + script_args = self._process_script_args('add_install_script', args[1:]) + script = self._find_source_script(args[0], script_args) self.build.install_scripts.append(script) - @permittedKwargs({}) + @permittedKwargs(set()) def add_postconf_script_method(self, args, kwargs): if len(args) < 1: raise InterpreterException('add_postconf_script takes one or more arguments') @@ -1925,13 +1973,13 @@ class MesonMain(InterpreterObject): script = self._find_source_script(args[0], args[1:]) self.build.postconf_scripts.append(script) - @permittedKwargs({}) + @permittedKwargs(set()) def add_dist_script_method(self, args, kwargs): if len(args) < 1: raise InterpreterException('add_dist_script takes one or more arguments') if len(args) > 1: FeatureNew('Calling "add_dist_script" with multiple arguments', '0.49.0').use(self.interpreter.subproject) - check_stringlist(args, 'add_dist_script argument must be a string') + check_stringlist(args, 'add_dist_script argumetn must be a string') if self.interpreter.subproject != '': raise InterpreterException('add_dist_script may not be used in a subproject.') script = self._find_source_script(args[0], args[1:]) diff --git a/test cases/common/56 install script/customtarget.py b/test cases/common/56 install script/customtarget.py new file mode 100755 index 000000000..e28373a39 --- /dev/null +++ b/test cases/common/56 install script/customtarget.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 + +import argparse +import os + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument('dirname') + args = parser.parse_args() + + with open(os.path.join(args.dirname, '1.txt'), 'w') as f: + f.write('') + with open(os.path.join(args.dirname, '2.txt'), 'w') as f: + f.write('') + + +if __name__ == "__main__": + main() diff --git a/test cases/common/56 install script/meson.build b/test cases/common/56 install script/meson.build index 6351518fb..e80e66673 100644 --- a/test cases/common/56 install script/meson.build +++ b/test cases/common/56 install script/meson.build @@ -5,3 +5,29 @@ meson.add_install_script('myinstall.py', 'diiba/daaba', 'file.dat') meson.add_install_script('myinstall.py', 'this/should', 'also-work.dat') subdir('src') + +meson.add_install_script('myinstall.py', 'dir', afile, '--mode=copy') + +data = configuration_data() +data.set10('foo', true) +conf = configure_file( + configuration : data, + output : 'conf.txt' +) + +meson.add_install_script('myinstall.py', 'dir', conf, '--mode=copy') + +t = custom_target( + 'ct', + command : [find_program('customtarget.py'), '@OUTDIR@'], + output : ['1.txt', '2.txt'], +) + +meson.add_install_script('myinstall.py', 'customtarget', t, '--mode=copy') +meson.add_install_script('myinstall.py', 'customtargetindex', t[0], '--mode=copy') + +meson.add_install_script(exe, 'generated.txt') +wrap = find_program('wrap.py') +# Yes, these are getting silly +meson.add_install_script(wrap, exe, 'wrapped.txt') +meson.add_install_script(wrap, wrap, exe, 'wrapped2.txt') diff --git a/test cases/common/56 install script/myinstall.py b/test cases/common/56 install script/myinstall.py index 812561e0e..a57334242 100644 --- a/test cases/common/56 install script/myinstall.py +++ b/test cases/common/56 install script/myinstall.py @@ -1,12 +1,31 @@ #!/usr/bin/env python3 +import argparse import os -import sys +import shutil prefix = os.environ['MESON_INSTALL_DESTDIR_PREFIX'] -dirname = os.path.join(prefix, sys.argv[1]) -os.makedirs(dirname) -with open(os.path.join(dirname, sys.argv[2]), 'w') as f: - f.write('') +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument('dirname') + parser.add_argument('files', nargs='+') + parser.add_argument('--mode', action='store', default='create', choices=['create', 'copy']) + args = parser.parse_args() + + dirname = os.path.join(prefix, args.dirname) + if not os.path.exists(dirname): + os.makedirs(dirname) + + if args.mode == 'create': + for name in args.files: + with open(os.path.join(dirname, name), 'w') as f: + f.write('') + else: + for name in args.files: + shutil.copy(name, dirname) + + +if __name__ == "__main__": + main() diff --git a/test cases/common/56 install script/src/a file.txt b/test cases/common/56 install script/src/a file.txt new file mode 100644 index 000000000..e69de29bb diff --git a/test cases/common/56 install script/src/exe.c b/test cases/common/56 install script/src/exe.c new file mode 100644 index 000000000..b573b9186 --- /dev/null +++ b/test cases/common/56 install script/src/exe.c @@ -0,0 +1,24 @@ +#include +#include +#include + +int main(int argc, char * argv[]) { + if (argc != 2) { + fprintf(stderr, "Takes exactly 2 arguments\n"); + return 1; + } + + char * dirname = getenv("MESON_INSTALL_DESTDIR_PREFIX"); + char * fullname = malloc(strlen(dirname) + 1 + strlen(argv[1]) + 1); + strcpy(fullname, dirname); + strcat(fullname, "/"); + strcat(fullname, argv[1]); + + FILE * fp = fopen(fullname, "w"); + fputs("Some text\n", fp); + fclose(fp); + + free(fullname); + + return 0; +} diff --git a/test cases/common/56 install script/src/meson.build b/test cases/common/56 install script/src/meson.build index b23574ac3..1db424fa6 100644 --- a/test cases/common/56 install script/src/meson.build +++ b/test cases/common/56 install script/src/meson.build @@ -1 +1,5 @@ meson.add_install_script('myinstall.py', 'this/does', 'something-different.dat') + +afile = files('a file.txt') + +exe = executable('exe', 'exe.c', install : false, native : true) diff --git a/test cases/common/56 install script/src/myinstall.py b/test cases/common/56 install script/src/myinstall.py index 3b7ce37d4..3a9d89b50 100644 --- a/test cases/common/56 install script/src/myinstall.py +++ b/test cases/common/56 install script/src/myinstall.py @@ -7,6 +7,8 @@ prefix = os.environ['MESON_INSTALL_DESTDIR_PREFIX'] dirname = os.path.join(prefix, sys.argv[1]) -os.makedirs(dirname) +if not os.path.exists(dirname): + os.makedirs(dirname) + with open(os.path.join(dirname, sys.argv[2] + '.in'), 'w') as f: f.write('') diff --git a/test cases/common/56 install script/test.json b/test cases/common/56 install script/test.json index d17625fc7..b2a597138 100644 --- a/test cases/common/56 install script/test.json +++ b/test cases/common/56 install script/test.json @@ -4,6 +4,14 @@ {"type": "pdb", "file": "usr/bin/prog"}, {"type": "file", "file": "usr/diiba/daaba/file.dat"}, {"type": "file", "file": "usr/this/should/also-work.dat"}, - {"type": "file", "file": "usr/this/does/something-different.dat.in"} + {"type": "file", "file": "usr/this/does/something-different.dat.in"}, + {"type": "file", "file": "usr/dir/a file.txt"}, + {"type": "file", "file": "usr/dir/conf.txt"}, + {"type": "file", "file": "usr/customtarget/1.txt"}, + {"type": "file", "file": "usr/customtarget/2.txt"}, + {"type": "file", "file": "usr/customtargetindex/1.txt"}, + {"type": "file", "file": "usr/generated.txt"}, + {"type": "file", "file": "usr/wrapped.txt"}, + {"type": "file", "file": "usr/wrapped2.txt"} ] } diff --git a/test cases/common/56 install script/wrap.py b/test cases/common/56 install script/wrap.py new file mode 100755 index 000000000..87508e008 --- /dev/null +++ b/test cases/common/56 install script/wrap.py @@ -0,0 +1,6 @@ +#!/usr/bin/env python3 + +import subprocess +import sys + +subprocess.run(sys.argv[1:]) From c239ce31f55579cfe1e29b769a8bda97deca2166 Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Wed, 29 Apr 2020 13:00:41 -0700 Subject: [PATCH 2/3] allow postconf and dist scripts to use Files, ExternalPrograms, and ConfigureFiles These things are all known to be ready when these scripts are run, and thus they can safely consume them. --- mesonbuild/interpreter.py | 14 ++++++++------ .../common/104 postconf with args/meson.build | 7 ++++++- test cases/unit/35 dist script/meson.build | 1 + 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index 2b699f8fa..b5256902b 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -1920,7 +1920,7 @@ class MesonMain(InterpreterObject): str, mesonlib.File, CustomTargetHolder, CustomTargetIndexHolder, ConfigureFileHolder, ExternalProgramHolder, ExecutableHolder, - ]]) -> T.List[str]: + ]], allow_built: bool = False) -> T.List[str]: script_args = [] # T.List[str] new = False for a in args: @@ -1931,6 +1931,8 @@ class MesonMain(InterpreterObject): new = True script_args.append(a.rel_to_builddir(self.interpreter.environment.source_dir)) elif isinstance(a, (build.BuildTarget, build.CustomTarget, build.CustomTargetIndex)): + if not allow_built: + raise InterpreterException('Arguments to {} cannot be built'.format(name)) new = True script_args.extend([os.path.join(a.get_subdir(), o) for o in a.get_outputs()]) @@ -1961,7 +1963,7 @@ class MesonMain(InterpreterObject): def add_install_script_method(self, args: 'T.Tuple[T.Union[str, ExecutableHolder], T.Union[str, mesonlib.File, CustomTargetHolder, CustomTargetIndexHolder, ConfigureFileHolder], ...]', kwargs): if len(args) < 1: raise InterpreterException('add_install_script takes one or more arguments') - script_args = self._process_script_args('add_install_script', args[1:]) + script_args = self._process_script_args('add_install_script', args[1:], allow_built=True) script = self._find_source_script(args[0], script_args) self.build.install_scripts.append(script) @@ -1969,8 +1971,8 @@ class MesonMain(InterpreterObject): def add_postconf_script_method(self, args, kwargs): if len(args) < 1: raise InterpreterException('add_postconf_script takes one or more arguments') - check_stringlist(args, 'add_postconf_script arguments must be strings') - script = self._find_source_script(args[0], args[1:]) + script_args = self._process_script_args('add_postconf_script', args[1:], allow_built=True) + script = self._find_source_script(args[0], script_args) self.build.postconf_scripts.append(script) @permittedKwargs(set()) @@ -1979,10 +1981,10 @@ class MesonMain(InterpreterObject): raise InterpreterException('add_dist_script takes one or more arguments') if len(args) > 1: FeatureNew('Calling "add_dist_script" with multiple arguments', '0.49.0').use(self.interpreter.subproject) - check_stringlist(args, 'add_dist_script argumetn must be a string') if self.interpreter.subproject != '': raise InterpreterException('add_dist_script may not be used in a subproject.') - script = self._find_source_script(args[0], args[1:]) + script_args = self._process_script_args('add_dist_script', args[1:], allow_built=True) + script = self._find_source_script(args[0], script_args) self.build.dist_scripts.append(script) @noPosargs diff --git a/test cases/common/104 postconf with args/meson.build b/test cases/common/104 postconf with args/meson.build index 8510c5b81..a34502c33 100644 --- a/test cases/common/104 postconf with args/meson.build +++ b/test cases/common/104 postconf with args/meson.build @@ -1,5 +1,10 @@ project('postconf script', 'c') -meson.add_postconf_script('postconf.py', '5', '33') +conf = configure_file( + configuration : configuration_data(), + output : 'out' +) + +meson.add_postconf_script(find_program('postconf.py'), '5', '33', conf) test('post', executable('prog', 'prog.c')) diff --git a/test cases/unit/35 dist script/meson.build b/test cases/unit/35 dist script/meson.build index fd672a9b8..2ae943845 100644 --- a/test cases/unit/35 dist script/meson.build +++ b/test cases/unit/35 dist script/meson.build @@ -5,3 +5,4 @@ exe = executable('comparer', 'prog.c') test('compare', exe) meson.add_dist_script('replacer.py', '"incorrect"', '"correct"') +meson.add_dist_script(find_program('replacer.py'), '"incorrect"', '"correct"') From 81f35f15492a35bcb294f7a516459141b1bd73ab Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Wed, 29 Apr 2020 13:14:10 -0700 Subject: [PATCH 3/3] docs: Update documentation for add_*_script --- docs/markdown/Reference-manual.md | 7 ++++++ .../snippets/add_foo_script_type_additions.md | 24 +++++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 docs/markdown/snippets/add_foo_script_type_additions.md diff --git a/docs/markdown/Reference-manual.md b/docs/markdown/Reference-manual.md index 963af9d65..48e165a4f 100644 --- a/docs/markdown/Reference-manual.md +++ b/docs/markdown/Reference-manual.md @@ -1735,6 +1735,8 @@ the following methods. 0.49.0, the function only accepted a single argument. Since 0.54.0 the `MESON_SOURCE_ROOT` and `MESON_BUILD_ROOT` environment variables are set when dist scripts are run. + *(Since 0.55.0)* The output of `configure_file`, `files`, and `find_program` + as well as strings. - `add_install_script(script_name, arg1, arg2, ...)` causes the script given as an argument to be run during the install step, this script @@ -1742,6 +1744,9 @@ the following methods. `MESON_BUILD_ROOT`, `MESON_INSTALL_PREFIX`, `MESON_INSTALL_DESTDIR_PREFIX`, and `MESONINTROSPECT` set. All positional arguments are passed as parameters. + *(Since 0.55.0)* The output of `configure_file`, `files`, `find_program`, + `custom_target`, indexes of `custom_target`, `executable`, `library`, and + other built targets as well as strings. *(added 0.54)* If `meson install` is called with the `--quiet` option, the environment variable `MESON_INSTALL_QUIET` will be set. @@ -1772,6 +1777,8 @@ the following methods. executable given as an argument after all project files have been generated. This script will have the environment variables `MESON_SOURCE_ROOT` and `MESON_BUILD_ROOT` set. + *(Since 0.55.0)* The output of `configure_file`, `files`, and `find_program` + as well as strings. - `backend()` *(added 0.37.0)* returns a string representing the current backend: `ninja`, `vs2010`, `vs2015`, `vs2017`, `vs2019`, diff --git a/docs/markdown/snippets/add_foo_script_type_additions.md b/docs/markdown/snippets/add_foo_script_type_additions.md new file mode 100644 index 000000000..88a88b226 --- /dev/null +++ b/docs/markdown/snippets/add_foo_script_type_additions.md @@ -0,0 +1,24 @@ +## meson.add_*_script methods accept new types + +All three (`add_install_script`, `add_dist_script`, and +`add_postconf_script`) now accept ExternalPrograms (as returned by +`find_program`), Files, and the output of `configure_file`. The dist and +postconf methods cannot accept other types because of when they are run. +While dist could, in theory, take other dependencies, it would require more +extensive changes, particularly to the backend. + +```meson +meson.add_install_script(find_program('foo'), files('bar')) +meson.add_dist_script(find_program('foo'), files('bar')) +meson.add_postconf_script(find_program('foo'), files('bar')) +``` + +The install script variant is also able to accept custom_targets, +custom_target indexes, and build targets (executables, libraries), and can +use built executables a the script to run + +```meson +installer = executable('installer', ...) +meson.add_install_script(installer, ...) +meson.add_install_script('foo.py', installer) +```