From 57cb1f9aad0928e167018ba886e2d8c06225b515 Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Mon, 13 Mar 2017 03:45:10 +0530 Subject: [PATCH 1/4] Support multiple install dirs for built/custom targets You can now pass a list of strings to the install_dir: kwarg to build_target and custom_target. Custom Targets: =============== Allows you to specify the installation directory for each corresponding output. For example: custom_target('different-install-dirs', output : ['first.file', 'second.file'], ... install : true, install_dir : ['somedir', 'otherdir]) This would install first.file to somedir and second.file to otherdir. If only one install_dir is provided, all outputs are installed there (same behaviour as before). To only install some outputs, pass `false` for the outputs that you don't want installed. For example: custom_target('only-install-second', output : ['first.file', 'second.file'], ... install : true, install_dir : [false, 'otherdir]) This would install second.file to otherdir and not install first.file. Build Targets: ============== With build_target() (which includes executable(), library(), etc), usually there is only one primary output. However some types of targets have multiple outputs. For example, while generating Vala libraries, valac also generates a header and a .vapi file both of which often need to be installed. This allows you to specify installation directories for those too. # This will only install the library (same as before) shared_library('somevalalib', 'somesource.vala', ... install : true) # This will install the library, the header, and the vapi into the # respective directories shared_library('somevalalib', 'somesource.vala', ... install : true, install_dir : ['libdir', 'incdir', 'vapidir']) # This will install the library into the default libdir and # everything else into the specified directories shared_library('somevalalib', 'somesource.vala', ... install : true, install_dir : [true, 'incdir', 'vapidir']) # This will NOT install the library, and will install everything # else into the specified directories shared_library('somevalalib', 'somesource.vala', ... install : true, install_dir : [false, 'incdir', 'vapidir']) true/false can also be used for secondary outputs in the same way. Valac can also generate a GIR file for libraries when the `vala_gir:` keyword argument is passed to library(). In that case, `install_dir:` must be given a list with four elements, one for each output. Includes tests for all these. Closes https://github.com/mesonbuild/meson/issues/705 Closes https://github.com/mesonbuild/meson/issues/891 Closes https://github.com/mesonbuild/meson/issues/892 Closes https://github.com/mesonbuild/meson/issues/1178 Closes https://github.com/mesonbuild/meson/issues/1193 --- mesonbuild/backend/backends.py | 4 +- mesonbuild/backend/ninjabackend.py | 70 ++++++++++++++----- mesonbuild/build.py | 49 +++++++------ mesonbuild/interpreter.py | 2 +- mesonbuild/mesonlib.py | 23 +++--- mesonbuild/modules/gnome.py | 4 +- mesonbuild/modules/pkgconfig.py | 5 +- .../generator.py | 14 ++++ .../installed_files.txt | 6 ++ .../meson.build | 28 ++++++++ .../generator.py | 16 +++++ .../installed_files.txt | 6 ++ .../meson.build | 13 ++++ .../vala/7 shared library/installed_files.txt | 8 +++ .../vala/7 shared library/lib/meson.build | 32 +++++++++ 15 files changed, 224 insertions(+), 56 deletions(-) create mode 100755 test cases/common/139 custom target multiple outputs/generator.py create mode 100644 test cases/common/139 custom target multiple outputs/installed_files.txt create mode 100644 test cases/common/139 custom target multiple outputs/meson.build create mode 100755 test cases/failing/43 custom target outputs not matching install_dirs/generator.py create mode 100644 test cases/failing/43 custom target outputs not matching install_dirs/installed_files.txt create mode 100644 test cases/failing/43 custom target outputs not matching install_dirs/meson.build create mode 100644 test cases/vala/7 shared library/installed_files.txt diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py index 5517fbd99..9b541e991 100644 --- a/mesonbuild/backend/backends.py +++ b/mesonbuild/backend/backends.py @@ -599,7 +599,7 @@ class Backend: for t in target.get_generated_sources(): if not isinstance(t, build.CustomTarget): continue - for f in t.output: + for f in t.get_outputs(): if self.environment.is_library(f): libs.append(os.path.join(self.get_target_dir(t), f)) return libs @@ -640,7 +640,7 @@ class Backend: build_root = self.environment.get_source_dir() outdir = os.path.join(self.environment.get_build_dir(), outdir) outputs = [] - for i in target.output: + for i in target.get_outputs(): outputs.append(os.path.join(outdir, i)) inputs = self.get_custom_target_sources(target) # Evaluate the command list diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index bc51ace8f..77b2d1d55 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -630,15 +630,17 @@ int dummy; for t in self.build.get_targets().values(): if t.should_install(): should_strip = self.get_option_for_target('strip', t) - # Find the installation directory. FIXME: Currently only one - # installation directory is supported for each target - outdir = t.get_custom_install_dir() - if outdir is not None: + # Find the installation directory. + outdirs = t.get_custom_install_dir() + if outdirs[0] is not None and outdirs[0] is not True: + # Either the value is set, or is set to False which means + # we want this specific output out of many outputs to not + # be installed. pass elif isinstance(t, build.SharedLibrary): - # For toolchains/platforms that need an import library for + # On toolchains/platforms that use an import library for # linking (separate from the shared library with all the - # code), we need to install the import library (dll.a/.lib) + # code), we need to install that too (dll.a/.lib). if t.get_import_filename(): # Install the import library. i = [self.get_target_filename_for_linking(t), @@ -647,22 +649,56 @@ int dummy; # doesn't have an install_rpath {}, False, ''] d.targets.append(i) - outdir = self.environment.get_shared_lib_dir() + outdirs[0] = self.environment.get_shared_lib_dir() elif isinstance(t, build.StaticLibrary): - outdir = self.environment.get_static_lib_dir() + outdirs[0] = self.environment.get_static_lib_dir() elif isinstance(t, build.Executable): - outdir = self.environment.get_bindir() + outdirs[0] = self.environment.get_bindir() else: + assert(isinstance(t, build.BuildTarget)) # XXX: Add BuildTarget-specific install dir cases here - outdir = self.environment.get_libdir() + outdirs[0] = self.environment.get_libdir() + # Sanity-check the outputs and install_dirs + num_outdirs, num_out = len(outdirs), len(t.get_outputs()) + if num_outdirs != 1 and num_outdirs != num_out: + raise MesonException('Target {!r} has {} outputs, but only ' + '{} "install_dir"s were specified' + ''.format(t.name, num_out, num_outdirs)) + # Install the target output(s) if isinstance(t, build.BuildTarget): - i = [self.get_target_filename(t), outdir, t.get_aliases(), - should_strip, t.install_rpath] - d.targets.append(i) + # Install primary build output (library/executable/jar, etc) + # Done separately because of strip/aliases/rpath + if outdirs[0] is not False: + i = [self.get_target_filename(t), outdirs[0], + t.get_aliases(), should_strip, t.install_rpath] + d.targets.append(i) + # Install secondary outputs. Only used for Vala right now. + if num_outdirs > 1: + for output, outdir in zip(t.get_outputs()[1:], outdirs[1:]): + # User requested that we not install this output + if outdir is False: + continue + f = os.path.join(self.get_target_dir(t), output) + d.targets.append([f, outdir, {}, False, None]) elif isinstance(t, build.CustomTarget): - for output in t.get_outputs(): - f = os.path.join(self.get_target_dir(t), output) - d.targets.append([f, outdir, {}, False, None]) + # If only one install_dir is specified, assume that all + # outputs will be installed into it. This is for + # backwards-compatibility and because it makes sense to + # avoid repetition since this is a common use-case. + # + # To selectively install only some outputs, pass `false` as + # the install_dir for the corresponding output by index + if num_outdirs == 1 and num_out > 1: + for output in t.get_outputs(): + f = os.path.join(self.get_target_dir(t), output) + d.targets.append([f, outdirs[0], {}, False, None]) + else: + for output, outdir in zip(t.get_outputs(), outdirs): + # User requested that we not install this output + if outdir is False: + continue + f = os.path.join(self.get_target_dir(t), output) + d.targets.append([f, outdir, {}, False, None]) def generate_custom_install_script(self, d): d.install_scripts = self.build.install_scripts @@ -1029,10 +1065,12 @@ int dummy; # Without this, it will write it inside c_out_dir args += ['--vapi', os.path.join('..', target.vala_vapi)] valac_outputs.append(vapiname) + target.outputs += [target.vala_header, target.vala_vapi] if isinstance(target.vala_gir, str): girname = os.path.join(self.get_target_dir(target), target.vala_gir) args += ['--gir', os.path.join('..', target.vala_gir)] valac_outputs.append(girname) + target.outputs.append(target.vala_gir) if self.get_option_for_target('werror', target): args += valac.get_werror_args() for d in target.get_external_deps(): diff --git a/mesonbuild/build.py b/mesonbuild/build.py index 1246f3e7d..ef5ea5f8c 100644 --- a/mesonbuild/build.py +++ b/mesonbuild/build.py @@ -19,7 +19,7 @@ from . import environment from . import dependencies from . import mlog from .mesonlib import File, MesonException -from .mesonlib import flatten, stringlistify, classify_unity_sources +from .mesonlib import flatten, typeslistify, stringlistify, classify_unity_sources from .mesonlib import get_filenames_templates_dict, substitute_values from .environment import for_windows, for_darwin from .compilers import is_object, clike_langs, sort_clike, lang_suffixes @@ -318,6 +318,9 @@ class BuildTarget(Target): self.name_prefix_set = False self.name_suffix_set = False self.filename = 'no_name' + # The list of all files outputted by this target. Useful in cases such + # as Vala which generates .vapi and .h besides the compiled output. + self.outputs = [self.filename] self.need_install = False self.pch = {} self.extra_args = {} @@ -544,7 +547,7 @@ class BuildTarget(Target): return result def get_custom_install_dir(self): - return self.custom_install_dir + return self.install_dir def process_kwargs(self, kwargs, environment): super().process_kwargs(kwargs) @@ -591,7 +594,7 @@ class BuildTarget(Target): if not isinstance(self, Executable): self.vala_header = kwargs.get('vala_header', self.name + '.h') self.vala_vapi = kwargs.get('vala_vapi', self.name + '.vapi') - self.vala_gir = kwargs.get('vala_gir', None) + self.vala_gir = kwargs.get('vala_gir', None) dlist = stringlistify(kwargs.get('d_args', [])) self.add_compiler_args('d', dlist) self.link_args = kwargs.get('link_args', []) @@ -617,10 +620,10 @@ class BuildTarget(Target): if not isinstance(deplist, list): deplist = [deplist] self.add_deps(deplist) - self.custom_install_dir = kwargs.get('install_dir', None) - if self.custom_install_dir is not None: - if not isinstance(self.custom_install_dir, str): - raise InvalidArguments('Custom_install_dir must be a string') + # If an item in this list is False, the output corresponding to + # the list index of that item will not be installed + self.install_dir = typeslistify(kwargs.get('install_dir', [None]), + (str, bool)) main_class = kwargs.get('main_class', '') if not isinstance(main_class, str): raise InvalidArguments('Main class must be a string') @@ -691,7 +694,7 @@ class BuildTarget(Target): return self.filename def get_outputs(self): - return [self.filename] + return self.outputs def get_extra_args(self, language): return self.extra_args.get(language, []) @@ -1004,6 +1007,7 @@ class Executable(BuildTarget): self.filename = self.name if self.suffix: self.filename += '.' + self.suffix + self.outputs = [self.filename] def type_suffix(self): return "@exe" @@ -1031,6 +1035,7 @@ class StaticLibrary(BuildTarget): else: self.suffix = 'a' self.filename = self.prefix + self.name + '.' + self.suffix + self.outputs = [self.filename] def type_suffix(self): return "@sta" @@ -1147,6 +1152,7 @@ class SharedLibrary(BuildTarget): if self.suffix is None: self.suffix = suffix self.filename = self.filename_tpl.format(self) + self.outputs = [self.filename] def process_kwargs(self, kwargs, environment): super().process_kwargs(kwargs, environment) @@ -1321,13 +1327,13 @@ class CustomTarget(Target): self.sources = [self.sources] if 'output' not in kwargs: raise InvalidArguments('Missing keyword argument "output".') - self.output = kwargs['output'] - if not isinstance(self.output, list): - self.output = [self.output] + self.outputs = kwargs['output'] + if not isinstance(self.outputs, list): + self.outputs = [self.outputs] # This will substitute values from the input into output and return it. inputs = get_sources_string_names(self.sources) values = get_filenames_templates_dict(inputs, []) - for i in self.output: + for i in self.outputs: if not(isinstance(i, str)): raise InvalidArguments('Output argument not a string.') if '/' in i: @@ -1342,9 +1348,9 @@ class CustomTarget(Target): m = "Output cannot contain @PLAINNAME@ or @BASENAME@ when " \ "there is more than one input (we can't know which to use)" raise InvalidArguments(m) - self.output = substitute_values(self.output, values) + self.outputs = substitute_values(self.outputs, values) self.capture = kwargs.get('capture', False) - if self.capture and len(self.output) != 1: + if self.capture and len(self.outputs) != 1: raise InvalidArguments('Capturing can only output to a single file.') if 'command' not in kwargs: raise InvalidArguments('Missing keyword argument "command".') @@ -1366,12 +1372,14 @@ class CustomTarget(Target): raise InvalidArguments('"install" must be boolean.') if self.install: if 'install_dir' not in kwargs: - raise InvalidArguments('"install_dir" not specified.') - self.install_dir = kwargs['install_dir'] - if not(isinstance(self.install_dir, str)): - raise InvalidArguments('"install_dir" must be a string.') + raise InvalidArguments('"install_dir" must be specified ' + 'when installing a target') + # If an item in this list is False, the output corresponding to + # the list index of that item will not be installed + self.install_dir = typeslistify(kwargs['install_dir'], (str, bool)) else: self.install = False + self.install_dir = [None] self.build_always = kwargs.get('build_always', False) if not isinstance(self.build_always, bool): raise InvalidArguments('Argument build_always must be a boolean.') @@ -1404,10 +1412,10 @@ class CustomTarget(Target): return self.install_dir def get_outputs(self): - return self.output + return self.outputs def get_filename(self): - return self.output[0] + return self.outputs[0] def get_sources(self): return self.sources @@ -1466,6 +1474,7 @@ class Jar(BuildTarget): if not s.endswith('.java'): raise InvalidArguments('Jar source %s is not a java file.' % s) self.filename = self.name + '.jar' + self.outputs = [self.filename] self.java_args = kwargs.get('java_args', []) def get_main_class(self): diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index 0c6d9809a..945de6b28 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -2221,7 +2221,7 @@ class Interpreter(InterpreterBase): if 'install_mode' not in kwargs: return None install_mode = [] - mode = mesonlib.stringintlistify(kwargs.get('install_mode', [])) + mode = mesonlib.typeslistify(kwargs.get('install_mode', []), (str, int)) for m in mode: # We skip any arguments that are set to `false` if m is False: diff --git a/mesonbuild/mesonlib.py b/mesonbuild/mesonlib.py index 7a5b96266..ef169c1f7 100644 --- a/mesonbuild/mesonlib.py +++ b/mesonbuild/mesonlib.py @@ -454,25 +454,22 @@ def replace_if_different(dst, dst_tmp): else: os.unlink(dst_tmp) -def stringintlistify(item): - if isinstance(item, (str, int)): +def typeslistify(item, types): + ''' + Ensure that type(@item) is one of @types or a + list of items all of which are of type @types + ''' + if isinstance(item, types): item = [item] if not isinstance(item, list): - raise MesonException('Item must be a list, a string, or an int') + raise MesonException('Item must be a list or one of {!r}'.format(types)) for i in item: - if not isinstance(i, (str, int, type(None))): - raise MesonException('List item must be a string or an int') + if i is not None and not isinstance(i, types): + raise MesonException('List item must be one of {!r}'.format(types)) return item def stringlistify(item): - if isinstance(item, str): - item = [item] - if not isinstance(item, list): - raise MesonException('Item is not a list') - for i in item: - if not isinstance(i, str): - raise MesonException('List item not a string.') - return item + return typeslistify(item, str) def expand_arguments(args): expended_args = [] diff --git a/mesonbuild/modules/gnome.py b/mesonbuild/modules/gnome.py index 4b366bf23..2f5bfb97a 100644 --- a/mesonbuild/modules/gnome.py +++ b/mesonbuild/modules/gnome.py @@ -999,7 +999,7 @@ class GnomeModule(ExtensionModule): target.get_subdir()) outdir = os.path.join(state.environment.get_build_dir(), target.get_subdir()) - outfile = target.output[0][:-5] # Strip .vapi + outfile = target.get_outputs()[0][:-5] # Strip .vapi ret.append('--vapidir=' + outdir) ret.append('--girdir=' + outdir) ret.append('--pkg=' + outfile) @@ -1066,7 +1066,7 @@ class GnomeModule(ExtensionModule): link_with += self._get_vapi_link_with(i.held_object) subdir = os.path.join(state.environment.get_build_dir(), i.held_object.get_subdir()) - gir_file = os.path.join(subdir, i.held_object.output[0]) + gir_file = os.path.join(subdir, i.held_object.get_outputs()[0]) cmd.append(gir_file) else: raise MesonException('Input must be a str or GirTarget') diff --git a/mesonbuild/modules/pkgconfig.py b/mesonbuild/modules/pkgconfig.py index e46c23925..e79371f79 100644 --- a/mesonbuild/modules/pkgconfig.py +++ b/mesonbuild/modules/pkgconfig.py @@ -76,8 +76,9 @@ class PkgConfigModule(ExtensionModule): if isinstance(l, str): yield l else: - if l.custom_install_dir: - yield '-L${prefix}/%s ' % l.custom_install_dir + install_dir = l.get_custom_install_dir()[0] + if install_dir: + yield '-L${prefix}/%s ' % install_dir else: yield '-L${libdir}' lname = self._get_lname(l, msg, pcfile) diff --git a/test cases/common/139 custom target multiple outputs/generator.py b/test cases/common/139 custom target multiple outputs/generator.py new file mode 100755 index 000000000..39dbd11c4 --- /dev/null +++ b/test cases/common/139 custom target multiple outputs/generator.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python3 + +import sys, os + +if len(sys.argv) != 3: + print(sys.argv[0], '', '') + +name = sys.argv[1] +odir = sys.argv[2] + +with open(os.path.join(odir, name + '.h'), 'w') as f: + f.write('int func();\n') +with open(os.path.join(odir, name + '.sh'), 'w') as f: + f.write('#!/bin/bash') diff --git a/test cases/common/139 custom target multiple outputs/installed_files.txt b/test cases/common/139 custom target multiple outputs/installed_files.txt new file mode 100644 index 000000000..21e1249b1 --- /dev/null +++ b/test cases/common/139 custom target multiple outputs/installed_files.txt @@ -0,0 +1,6 @@ +usr/include/diff.h +usr/include/first.h +usr/bin/diff.sh +usr/bin/second.sh +opt/same.h +opt/same.sh diff --git a/test cases/common/139 custom target multiple outputs/meson.build b/test cases/common/139 custom target multiple outputs/meson.build new file mode 100644 index 000000000..64128641e --- /dev/null +++ b/test cases/common/139 custom target multiple outputs/meson.build @@ -0,0 +1,28 @@ +project('multiple outputs install', 'c') + +gen = find_program('generator.py') + +custom_target('different-install-dirs', + output : ['diff.h', 'diff.sh'], + command : [gen, 'diff', '@OUTDIR@'], + install : true, + install_dir : [join_paths(get_option('prefix'), get_option('includedir')), + join_paths(get_option('prefix'), get_option('bindir'))]) + +custom_target('same-install-dir', + output : ['same.h', 'same.sh'], + command : [gen, 'same', '@OUTDIR@'], + install : true, + install_dir : '/opt') + +custom_target('only-install-first', + output : ['first.h', 'first.sh'], + command : [gen, 'first', '@OUTDIR@'], + install : true, + install_dir : [join_paths(get_option('prefix'), get_option('includedir')), false]) + +custom_target('only-install-second', + output : ['second.h', 'second.sh'], + command : [gen, 'second', '@OUTDIR@'], + install : true, + install_dir : [false, join_paths(get_option('prefix'), get_option('bindir'))]) diff --git a/test cases/failing/43 custom target outputs not matching install_dirs/generator.py b/test cases/failing/43 custom target outputs not matching install_dirs/generator.py new file mode 100755 index 000000000..4ac61795b --- /dev/null +++ b/test cases/failing/43 custom target outputs not matching install_dirs/generator.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 + +import sys, os + +if len(sys.argv) != 3: + print(sys.argv[0], '', '') + +name = sys.argv[1] +odir = sys.argv[2] + +with open(os.path.join(odir, name + '.h'), 'w') as f: + f.write('int func();\n') +with open(os.path.join(odir, name + '.c'), 'w') as f: + f.write('int main(int argc, char *argv[]) { return 0; }') +with open(os.path.join(odir, name + '.sh'), 'w') as f: + f.write('#!/bin/bash') diff --git a/test cases/failing/43 custom target outputs not matching install_dirs/installed_files.txt b/test cases/failing/43 custom target outputs not matching install_dirs/installed_files.txt new file mode 100644 index 000000000..21e1249b1 --- /dev/null +++ b/test cases/failing/43 custom target outputs not matching install_dirs/installed_files.txt @@ -0,0 +1,6 @@ +usr/include/diff.h +usr/include/first.h +usr/bin/diff.sh +usr/bin/second.sh +opt/same.h +opt/same.sh diff --git a/test cases/failing/43 custom target outputs not matching install_dirs/meson.build b/test cases/failing/43 custom target outputs not matching install_dirs/meson.build new file mode 100644 index 000000000..45bd7b397 --- /dev/null +++ b/test cases/failing/43 custom target outputs not matching install_dirs/meson.build @@ -0,0 +1,13 @@ +project('outputs not matching install_dirs', 'c') + +gen = find_program('generator.py') + +if meson.backend() != 'ninja' + error('Failing manually, test is only for the ninja backend') +endif + +custom_target('too-few-install-dirs', + output : ['toofew.h', 'toofew.c', 'toofew.sh'], + command : [gen, 'toofew', '@OUTDIR@'], + install : true, + install_dir : [join_paths(get_option('prefix'), get_option('includedir')), false]) diff --git a/test cases/vala/7 shared library/installed_files.txt b/test cases/vala/7 shared library/installed_files.txt new file mode 100644 index 000000000..16ea442d2 --- /dev/null +++ b/test cases/vala/7 shared library/installed_files.txt @@ -0,0 +1,8 @@ +usr/lib/libinstalled_vala_lib.so +usr/lib/libinstalled_vala_all.so +usr/include/installed_vala_all.h +usr/include/installed_vala_all_nolib.h +usr/include/installed_vala_onlyh.h +usr/share/vala/vapi/installed_vala_all.vapi +usr/share/vala/vapi/installed_vala_all_nolib.vapi +usr/share/vala/vapi/installed_vala_onlyvapi.vapi diff --git a/test cases/vala/7 shared library/lib/meson.build b/test cases/vala/7 shared library/lib/meson.build index 8eca0d49c..bb1e800c5 100644 --- a/test cases/vala/7 shared library/lib/meson.build +++ b/test cases/vala/7 shared library/lib/meson.build @@ -1 +1,33 @@ l = shared_library('valalib', 'mylib.vala', dependencies : valadeps) + +shared_library('installed_vala_lib', 'mylib.vala', + dependencies : valadeps, + install : true) + +shared_library('installed_vala_all', 'mylib.vala', + dependencies : valadeps, + install : true, + install_dir : [true, + join_paths(get_option('prefix'), get_option('includedir')), + join_paths(get_option('prefix'), get_option('datadir'), 'vala', 'vapi')]) + +shared_library('installed_vala_all_nolib', 'mylib.vala', + dependencies : valadeps, + install : true, + install_dir : [false, + join_paths(get_option('prefix'), get_option('includedir')), + join_paths(get_option('prefix'), get_option('datadir'), 'vala', 'vapi')]) + +shared_library('installed_vala_onlyh', 'mylib.vala', + dependencies : valadeps, + install : true, + install_dir : [false, + join_paths(get_option('prefix'), get_option('includedir')), + false]) + +shared_library('installed_vala_onlyvapi', 'mylib.vala', + dependencies : valadeps, + install : true, + install_dir : [false, + false, + join_paths(get_option('prefix'), get_option('datadir'), 'vala', 'vapi')]) From 98b1ce1cd9f3220ec6a06ef2bfa122518a9245ed Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Tue, 14 Mar 2017 15:15:13 +0530 Subject: [PATCH 2/4] Fix custom directory installation of import library When install_dir was set for a shared_library, the import library would not be installed at all, which is unintended. Instead, install it into the custom directory if it is set, otherwise install it in the default import library installation directory. Includes a test for this. --- mesonbuild/backend/ninjabackend.py | 32 ++++++++++++------- .../installed_files.txt | 2 ++ .../7 mingw dll versioning/meson.build | 4 +++ .../8 msvc dll versioning/installed_files.txt | 2 ++ .../windows/8 msvc dll versioning/meson.build | 4 +++ 5 files changed, 32 insertions(+), 12 deletions(-) diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index 77b2d1d55..464cdcb0c 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -632,23 +632,13 @@ int dummy; should_strip = self.get_option_for_target('strip', t) # Find the installation directory. outdirs = t.get_custom_install_dir() + custom_install_dir = False if outdirs[0] is not None and outdirs[0] is not True: # Either the value is set, or is set to False which means # we want this specific output out of many outputs to not # be installed. - pass + custom_install_dir = True elif isinstance(t, build.SharedLibrary): - # On toolchains/platforms that use an import library for - # linking (separate from the shared library with all the - # code), we need to install that too (dll.a/.lib). - if t.get_import_filename(): - # Install the import library. - i = [self.get_target_filename_for_linking(t), - self.environment.get_import_lib_dir(), - # It has no aliases, should not be stripped, and - # doesn't have an install_rpath - {}, False, ''] - d.targets.append(i) outdirs[0] = self.environment.get_shared_lib_dir() elif isinstance(t, build.StaticLibrary): outdirs[0] = self.environment.get_static_lib_dir() @@ -672,6 +662,24 @@ int dummy; i = [self.get_target_filename(t), outdirs[0], t.get_aliases(), should_strip, t.install_rpath] d.targets.append(i) + # On toolchains/platforms that use an import library for + # linking (separate from the shared library with all the + # code), we need to install that too (dll.a/.lib). + if isinstance(t, build.SharedLibrary) and t.get_import_filename(): + if custom_install_dir: + # If the DLL is installed into a custom directory, + # install the import library into the same place so + # it doesn't go into a surprising place + implib_install_dir = outdirs[0] + else: + implib_install_dir = self.environment.get_import_lib_dir() + # Install the import library. + i = [self.get_target_filename_for_linking(t), + implib_install_dir, + # It has no aliases, should not be stripped, and + # doesn't have an install_rpath + {}, False, ''] + d.targets.append(i) # Install secondary outputs. Only used for Vala right now. if num_outdirs > 1: for output, outdir in zip(t.get_outputs()[1:], outdirs[1:]): diff --git a/test cases/windows/7 mingw dll versioning/installed_files.txt b/test cases/windows/7 mingw dll versioning/installed_files.txt index ebad9e422..f02b4547b 100644 --- a/test cases/windows/7 mingw dll versioning/installed_files.txt +++ b/test cases/windows/7 mingw dll versioning/installed_files.txt @@ -6,3 +6,5 @@ usr/bin/libonlyversion-1.dll usr/lib/libonlyversion.dll.a usr/bin/libonlysoversion-5.dll usr/lib/libonlysoversion.dll.a +usr/libexec/libcustomdir.dll +usr/libexec/libcustomdir.dll.a diff --git a/test cases/windows/7 mingw dll versioning/meson.build b/test cases/windows/7 mingw dll versioning/meson.build index d1fe73a9c..7f65532c3 100644 --- a/test cases/windows/7 mingw dll versioning/meson.build +++ b/test cases/windows/7 mingw dll versioning/meson.build @@ -47,3 +47,7 @@ test('manually linked 3', executable('manuallink3', out, test('manually linked 4', executable('manuallink4', out, link_args : ['-L.', '-lonlysoversion'])) + +shared_library('customdir', 'lib.c', + install : true, + install_dir : get_option('libexecdir')) diff --git a/test cases/windows/8 msvc dll versioning/installed_files.txt b/test cases/windows/8 msvc dll versioning/installed_files.txt index ae0fa1f67..1a735e284 100644 --- a/test cases/windows/8 msvc dll versioning/installed_files.txt +++ b/test cases/windows/8 msvc dll versioning/installed_files.txt @@ -8,3 +8,5 @@ usr/bin/onlyversion-1.dll usr/lib/onlyversion.lib usr/bin/onlysoversion-5.dll usr/lib/onlysoversion.lib +usr/libexec/customdir.dll +usr/libexec/customdir.lib diff --git a/test cases/windows/8 msvc dll versioning/meson.build b/test cases/windows/8 msvc dll versioning/meson.build index b72c5ec0f..eea41d90c 100644 --- a/test cases/windows/8 msvc dll versioning/meson.build +++ b/test cases/windows/8 msvc dll versioning/meson.build @@ -48,3 +48,7 @@ test('manually linked 3', executable('manuallink3', out, test('manually linked 4', executable('manuallink4', out, link_args : ['-L.', '-lonlysoversion'])) + +shared_library('customdir', 'lib.c', + install : true, + install_dir : get_option('libexecdir')) From a4255d74f54f6d74b12f0cadcaa29a7584079503 Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Tue, 14 Mar 2017 19:17:24 +0530 Subject: [PATCH 3/4] Don't generate import library for shared modules Also add a test for this on all platforms. --- mesonbuild/build.py | 1 + test cases/linuxlike/7 library versions/installed_files.txt | 1 + test cases/linuxlike/7 library versions/meson.build | 2 ++ test cases/osx/2 library versions/installed_files.txt | 1 + test cases/osx/2 library versions/meson.build | 2 ++ test cases/windows/7 mingw dll versioning/installed_files.txt | 1 + test cases/windows/7 mingw dll versioning/meson.build | 2 ++ test cases/windows/8 msvc dll versioning/installed_files.txt | 1 + test cases/windows/8 msvc dll versioning/meson.build | 2 ++ 9 files changed, 13 insertions(+) diff --git a/mesonbuild/build.py b/mesonbuild/build.py index ef5ea5f8c..973d8e9a3 100644 --- a/mesonbuild/build.py +++ b/mesonbuild/build.py @@ -1246,6 +1246,7 @@ class SharedModule(SharedLibrary): if 'soversion' in kwargs: raise MesonException('Shared modules must not specify the soversion kwarg.') super().__init__(name, subdir, subproject, is_cross, sources, objects, environment, kwargs) + self.import_filename = None class CustomTarget(Target): known_kwargs = {'input': True, diff --git a/test cases/linuxlike/7 library versions/installed_files.txt b/test cases/linuxlike/7 library versions/installed_files.txt index b997e5392..763a90709 100644 --- a/test cases/linuxlike/7 library versions/installed_files.txt +++ b/test cases/linuxlike/7 library versions/installed_files.txt @@ -7,3 +7,4 @@ usr/lib/libonlyversion.so.1 usr/lib/libonlyversion.so.1.4.5 usr/lib/libonlysoversion.so usr/lib/libonlysoversion.so.5 +usr/lib/libmodule.so diff --git a/test cases/linuxlike/7 library versions/meson.build b/test cases/linuxlike/7 library versions/meson.build index 48b75ad35..f16221038 100644 --- a/test cases/linuxlike/7 library versions/meson.build +++ b/test cases/linuxlike/7 library versions/meson.build @@ -43,3 +43,5 @@ test('manually linked 3', executable('manuallink3', out, test('manually linked 4', executable('manuallink4', out, link_args : ['-L.', '-lonlysoversion', rpath_arg])) + +shared_module('module', 'lib.c', install : true) diff --git a/test cases/osx/2 library versions/installed_files.txt b/test cases/osx/2 library versions/installed_files.txt index fc76046ab..de7b078dc 100644 --- a/test cases/osx/2 library versions/installed_files.txt +++ b/test cases/osx/2 library versions/installed_files.txt @@ -5,3 +5,4 @@ usr/lib/libonlyversion.dylib usr/lib/libonlyversion.1.dylib usr/lib/libonlysoversion.dylib usr/lib/libonlysoversion.5.dylib +usr/lib/libmodule.dylib diff --git a/test cases/osx/2 library versions/meson.build b/test cases/osx/2 library versions/meson.build index b1962ca00..9624998e3 100644 --- a/test cases/osx/2 library versions/meson.build +++ b/test cases/osx/2 library versions/meson.build @@ -39,3 +39,5 @@ test('manually linked 3', executable('manuallink3', out, test('manually linked 4', executable('manuallink4', out, link_args : ['-L.', '-lonlysoversion'])) + +shared_module('module', 'lib.c', install : true) diff --git a/test cases/windows/7 mingw dll versioning/installed_files.txt b/test cases/windows/7 mingw dll versioning/installed_files.txt index f02b4547b..56187a6ac 100644 --- a/test cases/windows/7 mingw dll versioning/installed_files.txt +++ b/test cases/windows/7 mingw dll versioning/installed_files.txt @@ -8,3 +8,4 @@ usr/bin/libonlysoversion-5.dll usr/lib/libonlysoversion.dll.a usr/libexec/libcustomdir.dll usr/libexec/libcustomdir.dll.a +usr/lib/libmodule.dll diff --git a/test cases/windows/7 mingw dll versioning/meson.build b/test cases/windows/7 mingw dll versioning/meson.build index 7f65532c3..1d6562c58 100644 --- a/test cases/windows/7 mingw dll versioning/meson.build +++ b/test cases/windows/7 mingw dll versioning/meson.build @@ -51,3 +51,5 @@ test('manually linked 4', executable('manuallink4', out, shared_library('customdir', 'lib.c', install : true, install_dir : get_option('libexecdir')) + +shared_module('module', 'lib.c', install : true) diff --git a/test cases/windows/8 msvc dll versioning/installed_files.txt b/test cases/windows/8 msvc dll versioning/installed_files.txt index 1a735e284..df4334381 100644 --- a/test cases/windows/8 msvc dll versioning/installed_files.txt +++ b/test cases/windows/8 msvc dll versioning/installed_files.txt @@ -10,3 +10,4 @@ usr/bin/onlysoversion-5.dll usr/lib/onlysoversion.lib usr/libexec/customdir.dll usr/libexec/customdir.lib +usr/lib/module.dll diff --git a/test cases/windows/8 msvc dll versioning/meson.build b/test cases/windows/8 msvc dll versioning/meson.build index eea41d90c..407474790 100644 --- a/test cases/windows/8 msvc dll versioning/meson.build +++ b/test cases/windows/8 msvc dll versioning/meson.build @@ -52,3 +52,5 @@ test('manually linked 4', executable('manuallink4', out, shared_library('customdir', 'lib.c', install : true, install_dir : get_option('libexecdir')) + +shared_module('module', 'lib.c', install : true) From aa3480dabaaf8fe164ae9fa5115cc092277245f5 Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Thu, 16 Mar 2017 20:33:50 +0530 Subject: [PATCH 4/4] Reduce an indent level in the install for loop if not t.should_install(): continue No logic changes at all. Also improve the message when erroring out about insufficient install_dir: list elements. --- mesonbuild/backend/ninjabackend.py | 156 +++++++++++++++-------------- 1 file changed, 79 insertions(+), 77 deletions(-) diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index 464cdcb0c..9378a564e 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -628,85 +628,87 @@ int dummy; def generate_target_install(self, d): for t in self.build.get_targets().values(): - if t.should_install(): + if not t.should_install(): + continue + # Find the installation directory. + outdirs = t.get_custom_install_dir() + custom_install_dir = False + if outdirs[0] is not None and outdirs[0] is not True: + # Either the value is set, or is set to False which means + # we want this specific output out of many outputs to not + # be installed. + custom_install_dir = True + elif isinstance(t, build.SharedLibrary): + outdirs[0] = self.environment.get_shared_lib_dir() + elif isinstance(t, build.StaticLibrary): + outdirs[0] = self.environment.get_static_lib_dir() + elif isinstance(t, build.Executable): + outdirs[0] = self.environment.get_bindir() + else: + assert(isinstance(t, build.BuildTarget)) + # XXX: Add BuildTarget-specific install dir cases here + outdirs[0] = self.environment.get_libdir() + # Sanity-check the outputs and install_dirs + num_outdirs, num_out = len(outdirs), len(t.get_outputs()) + if num_outdirs != 1 and num_outdirs != num_out: + m = 'Target {!r} has {} outputs: {!r}, but only {} "install_dir"s were found.\n' \ + "Pass 'false' for outputs that should not be installed and 'true' for\n" \ + 'using the default installation directory for an output.' + raise MesonException(m.format(t.name, num_out, t.get_outputs(), num_outdirs)) + # Install the target output(s) + if isinstance(t, build.BuildTarget): should_strip = self.get_option_for_target('strip', t) - # Find the installation directory. - outdirs = t.get_custom_install_dir() - custom_install_dir = False - if outdirs[0] is not None and outdirs[0] is not True: - # Either the value is set, or is set to False which means - # we want this specific output out of many outputs to not - # be installed. - custom_install_dir = True - elif isinstance(t, build.SharedLibrary): - outdirs[0] = self.environment.get_shared_lib_dir() - elif isinstance(t, build.StaticLibrary): - outdirs[0] = self.environment.get_static_lib_dir() - elif isinstance(t, build.Executable): - outdirs[0] = self.environment.get_bindir() - else: - assert(isinstance(t, build.BuildTarget)) - # XXX: Add BuildTarget-specific install dir cases here - outdirs[0] = self.environment.get_libdir() - # Sanity-check the outputs and install_dirs - num_outdirs, num_out = len(outdirs), len(t.get_outputs()) - if num_outdirs != 1 and num_outdirs != num_out: - raise MesonException('Target {!r} has {} outputs, but only ' - '{} "install_dir"s were specified' - ''.format(t.name, num_out, num_outdirs)) - # Install the target output(s) - if isinstance(t, build.BuildTarget): - # Install primary build output (library/executable/jar, etc) - # Done separately because of strip/aliases/rpath - if outdirs[0] is not False: - i = [self.get_target_filename(t), outdirs[0], - t.get_aliases(), should_strip, t.install_rpath] + # Install primary build output (library/executable/jar, etc) + # Done separately because of strip/aliases/rpath + if outdirs[0] is not False: + i = [self.get_target_filename(t), outdirs[0], + t.get_aliases(), should_strip, t.install_rpath] + d.targets.append(i) + # On toolchains/platforms that use an import library for + # linking (separate from the shared library with all the + # code), we need to install that too (dll.a/.lib). + if isinstance(t, build.SharedLibrary) and t.get_import_filename(): + if custom_install_dir: + # If the DLL is installed into a custom directory, + # install the import library into the same place so + # it doesn't go into a surprising place + implib_install_dir = outdirs[0] + else: + implib_install_dir = self.environment.get_import_lib_dir() + # Install the import library. + i = [self.get_target_filename_for_linking(t), + implib_install_dir, + # It has no aliases, should not be stripped, and + # doesn't have an install_rpath + {}, False, ''] d.targets.append(i) - # On toolchains/platforms that use an import library for - # linking (separate from the shared library with all the - # code), we need to install that too (dll.a/.lib). - if isinstance(t, build.SharedLibrary) and t.get_import_filename(): - if custom_install_dir: - # If the DLL is installed into a custom directory, - # install the import library into the same place so - # it doesn't go into a surprising place - implib_install_dir = outdirs[0] - else: - implib_install_dir = self.environment.get_import_lib_dir() - # Install the import library. - i = [self.get_target_filename_for_linking(t), - implib_install_dir, - # It has no aliases, should not be stripped, and - # doesn't have an install_rpath - {}, False, ''] - d.targets.append(i) - # Install secondary outputs. Only used for Vala right now. - if num_outdirs > 1: - for output, outdir in zip(t.get_outputs()[1:], outdirs[1:]): - # User requested that we not install this output - if outdir is False: - continue - f = os.path.join(self.get_target_dir(t), output) - d.targets.append([f, outdir, {}, False, None]) - elif isinstance(t, build.CustomTarget): - # If only one install_dir is specified, assume that all - # outputs will be installed into it. This is for - # backwards-compatibility and because it makes sense to - # avoid repetition since this is a common use-case. - # - # To selectively install only some outputs, pass `false` as - # the install_dir for the corresponding output by index - if num_outdirs == 1 and num_out > 1: - for output in t.get_outputs(): - f = os.path.join(self.get_target_dir(t), output) - d.targets.append([f, outdirs[0], {}, False, None]) - else: - for output, outdir in zip(t.get_outputs(), outdirs): - # User requested that we not install this output - if outdir is False: - continue - f = os.path.join(self.get_target_dir(t), output) - d.targets.append([f, outdir, {}, False, None]) + # Install secondary outputs. Only used for Vala right now. + if num_outdirs > 1: + for output, outdir in zip(t.get_outputs()[1:], outdirs[1:]): + # User requested that we not install this output + if outdir is False: + continue + f = os.path.join(self.get_target_dir(t), output) + d.targets.append([f, outdir, {}, False, None]) + elif isinstance(t, build.CustomTarget): + # If only one install_dir is specified, assume that all + # outputs will be installed into it. This is for + # backwards-compatibility and because it makes sense to + # avoid repetition since this is a common use-case. + # + # To selectively install only some outputs, pass `false` as + # the install_dir for the corresponding output by index + if num_outdirs == 1 and num_out > 1: + for output in t.get_outputs(): + f = os.path.join(self.get_target_dir(t), output) + d.targets.append([f, outdirs[0], {}, False, None]) + else: + for output, outdir in zip(t.get_outputs(), outdirs): + # User requested that we not install this output + if outdir is False: + continue + f = os.path.join(self.get_target_dir(t), output) + d.targets.append([f, outdir, {}, False, None]) def generate_custom_install_script(self, d): d.install_scripts = self.build.install_scripts