diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index c7e194e67..1aff06f98 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -724,6 +724,7 @@ int dummy; "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_mode = t.get_custom_install_mode() # Install the target output(s) if isinstance(t, build.BuildTarget): should_strip = self.get_option_for_target('strip', t) @@ -731,7 +732,7 @@ int dummy; # 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] + t.get_aliases(), should_strip, t.install_rpath, install_mode] d.targets.append(i) # On toolchains/platforms that use an import library for # linking (separate from the shared library with all the @@ -749,7 +750,7 @@ int dummy; implib_install_dir, # It has no aliases, should not be stripped, and # doesn't have an install_rpath - {}, False, ''] + {}, False, '', install_mode] d.targets.append(i) # Install secondary outputs. Only used for Vala right now. if num_outdirs > 1: @@ -758,7 +759,7 @@ int dummy; if outdir is False: continue f = os.path.join(self.get_target_dir(t), output) - d.targets.append([f, outdir, {}, False, None]) + d.targets.append([f, outdir, {}, False, None, install_mode]) elif isinstance(t, build.CustomTarget): # If only one install_dir is specified, assume that all # outputs will be installed into it. This is for @@ -770,14 +771,14 @@ int dummy; 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]) + d.targets.append([f, outdirs[0], {}, False, None, install_mode]) 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]) + d.targets.append([f, outdir, {}, False, None, install_mode]) def generate_custom_install_script(self, d): result = [] @@ -809,7 +810,7 @@ int dummy; msg = 'Invalid header type {!r} can\'t be installed' raise MesonException(msg.format(f)) abspath = f.absolute_path(srcdir, builddir) - i = [abspath, outdir] + i = [abspath, outdir, h.get_custom_install_mode()] d.headers.append(i) def generate_man_install(self, d): @@ -823,7 +824,7 @@ int dummy; subdir = os.path.join(manroot, 'man' + num) srcabs = f.absolute_path(self.environment.get_source_dir(), self.environment.get_build_dir()) dstabs = os.path.join(subdir, os.path.basename(f.fname) + '.gz') - i = [srcabs, dstabs] + i = [srcabs, dstabs, m.get_custom_install_mode()] d.man.append(i) def generate_data_install(self, d): diff --git a/mesonbuild/build.py b/mesonbuild/build.py index 0bcb98fb2..1e7f5fe02 100644 --- a/mesonbuild/build.py +++ b/mesonbuild/build.py @@ -64,6 +64,7 @@ buildtarget_kwargs = set([ 'install', 'install_rpath', 'install_dir', + 'install_mode', 'name_prefix', 'name_suffix', 'native', @@ -668,6 +669,9 @@ class BuildTarget(Target): def get_custom_install_dir(self): return self.install_dir + def get_custom_install_mode(self): + return self.install_mode + def process_kwargs(self, kwargs, environment): super().process_kwargs(kwargs) self.copy_kwargs(kwargs) @@ -745,6 +749,7 @@ This will become a hard error in a future Meson release.''') # the list index of that item will not be installed self.install_dir = typeslistify(kwargs.get('install_dir', [None]), (str, bool)) + self.install_mode = kwargs.get('install_mode', None) main_class = kwargs.get('main_class', '') if not isinstance(main_class, str): raise InvalidArguments('Main class must be a string') @@ -1626,6 +1631,7 @@ class CustomTarget(Target): 'capture', 'install', 'install_dir', + 'install_mode', 'build_always', 'depends', 'depend_files', @@ -1774,9 +1780,11 @@ class CustomTarget(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)) + self.install_mode = kwargs.get('install_mode', None) else: self.install = False self.install_dir = [None] + self.install_mode = 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.') @@ -1803,6 +1811,9 @@ class CustomTarget(Target): def get_custom_install_dir(self): return self.install_dir + def get_custom_install_mode(self): + return self.install_mode + def get_outputs(self): return self.outputs diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index 93fd237b6..b596ddc2d 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -577,6 +577,7 @@ class Headers(InterpreterObject): self.sources = sources self.install_subdir = kwargs.get('subdir', '') self.custom_install_dir = kwargs.get('install_dir', None) + self.custom_install_mode = kwargs.get('install_mode', None) if self.custom_install_dir is not None: if not isinstance(self.custom_install_dir, str): raise InterpreterException('Custom_install_dir must be a string.') @@ -593,6 +594,9 @@ class Headers(InterpreterObject): def get_custom_install_dir(self): return self.custom_install_dir + def get_custom_install_mode(self): + return self.custom_install_mode + class DataHolder(InterpreterObject, ObjectHolder): def __init__(self, data): InterpreterObject.__init__(self) @@ -624,6 +628,7 @@ class Man(InterpreterObject): self.sources = sources self.validate_sources() self.custom_install_dir = kwargs.get('install_dir', None) + self.custom_install_mode = kwargs.get('install_mode', None) if self.custom_install_dir is not None and not isinstance(self.custom_install_dir, str): raise InterpreterException('Custom_install_dir must be a string.') @@ -639,6 +644,9 @@ class Man(InterpreterObject): def get_custom_install_dir(self): return self.custom_install_dir + def get_custom_install_mode(self): + return self.custom_install_mode + def get_sources(self): return self.sources @@ -1716,8 +1724,8 @@ permitted_kwargs = {'add_global_arguments': {'language'}, 'add_test_setup': {'exe_wrapper', 'gdb', 'timeout_multiplier', 'env'}, 'benchmark': {'args', 'env', 'should_fail', 'timeout', 'workdir', 'suite'}, 'build_target': known_build_target_kwargs, - 'configure_file': {'input', 'output', 'configuration', 'command', 'copy', 'install_dir', 'capture', 'install', 'format', 'output_format'}, - 'custom_target': {'input', 'output', 'command', 'install', 'install_dir', 'build_always', 'capture', 'depends', 'depend_files', 'depfile', 'build_by_default'}, + 'configure_file': {'input', 'output', 'configuration', 'command', 'copy', 'install_dir', 'install_mode', 'capture', 'install', 'format', 'output_format'}, + 'custom_target': {'input', 'output', 'command', 'install', 'install_dir', 'install_mode', 'build_always', 'capture', 'depends', 'depend_files', 'depfile', 'build_by_default'}, 'dependency': {'default_options', 'fallback', 'language', 'main', 'method', 'modules', 'optional_modules', 'native', 'required', 'static', 'version', 'private_headers'}, 'declare_dependency': {'include_directories', 'link_with', 'sources', 'dependencies', 'compile_args', 'link_args', 'link_whole', 'version'}, 'executable': build.known_exe_kwargs, @@ -1725,8 +1733,8 @@ permitted_kwargs = {'add_global_arguments': {'language'}, 'generator': {'arguments', 'output', 'depfile', 'capture', 'preserve_path_from'}, 'include_directories': {'is_system'}, 'install_data': {'install_dir', 'install_mode', 'rename', 'sources'}, - 'install_headers': {'install_dir', 'subdir'}, - 'install_man': {'install_dir'}, + 'install_headers': {'install_dir', 'install_mode', 'subdir'}, + 'install_man': {'install_dir', 'install_mode'}, 'install_subdir': {'exclude_files', 'exclude_directories', 'install_dir', 'install_mode', 'strip_directory'}, 'jar': build.known_jar_kwargs, 'project': {'version', 'meson_version', 'default_options', 'license', 'subproject_dir'}, @@ -2932,6 +2940,7 @@ root and issuing %s. if len(args) != 1: raise InterpreterException('custom_target: Only one positional argument is allowed, and it must be a string name') name = args[0] + kwargs['install_mode'] = self._get_kwarg_install_mode(kwargs) tg = CustomTargetHolder(build.CustomTarget(name, self.subdir, self.subproject, kwargs), self) self.add_target(name, tg.held_object) return tg @@ -3058,6 +3067,7 @@ root and issuing %s. @permittedKwargs(permitted_kwargs['install_headers']) def func_install_headers(self, node, args, kwargs): source_files = self.source_strings_to_files(args) + kwargs['install_mode'] = self._get_kwarg_install_mode(kwargs) h = Headers(source_files, kwargs) self.build.headers.append(h) return h @@ -3065,6 +3075,7 @@ root and issuing %s. @permittedKwargs(permitted_kwargs['install_man']) def func_install_man(self, node, args, kwargs): fargs = self.source_strings_to_files(args) + kwargs['install_mode'] = self._get_kwarg_install_mode(kwargs) m = Man(fargs, kwargs) self.build.man.append(m) return m @@ -3115,7 +3126,7 @@ root and issuing %s. self.subdir = prev_subdir def _get_kwarg_install_mode(self, kwargs): - if 'install_mode' not in kwargs: + if kwargs.get('install_mode', None) is None: return None install_mode = [] mode = mesonlib.typeslistify(kwargs.get('install_mode', []), (str, int)) @@ -3358,7 +3369,8 @@ root and issuing %s. idir = kwargs.get('install_dir', None) if isinstance(idir, str) and idir: cfile = mesonlib.File.from_built_file(ofile_path, ofile_fname) - self.build.data.append(build.Data([cfile], idir)) + install_mode = self._get_kwarg_install_mode(kwargs) + self.build.data.append(build.Data([cfile], idir, install_mode)) return mesonlib.File.from_built_file(self.subdir, output) @permittedKwargs(permitted_kwargs['include_directories']) @@ -3642,6 +3654,7 @@ different subdirectory. sources = self.source_strings_to_files(sources) objs = extract_as_list(kwargs, 'objects') kwargs['dependencies'] = extract_as_list(kwargs, 'dependencies') + kwargs['install_mode'] = self._get_kwarg_install_mode(kwargs) if 'extra_files' in kwargs: ef = extract_as_list(kwargs, 'extra_files') kwargs['extra_files'] = self.source_strings_to_files(ef) diff --git a/mesonbuild/scripts/meson_install.py b/mesonbuild/scripts/meson_install.py index b51a351ed..eaa992b58 100644 --- a/mesonbuild/scripts/meson_install.py +++ b/mesonbuild/scripts/meson_install.py @@ -148,7 +148,7 @@ def do_copyfile(from_file, to_file): selinux_updates.append(to_file) append_to_log(to_file) -def do_copydir(data, src_dir, dst_dir, exclude): +def do_copydir(data, src_dir, dst_dir, exclude, install_mode): ''' Copies the contents of directory @src_dir into @dst_dir. @@ -158,7 +158,7 @@ def do_copydir(data, src_dir, dst_dir, exclude): excluded foobar file - do_copydir(..., '/foo', '/dst/dir', {'bar/excluded'}) creates + do_copydir(..., '/foo', '/dst/dir', {'bar/excluded'}, None) creates /dst/ dir/ bar/ @@ -170,6 +170,7 @@ def do_copydir(data, src_dir, dst_dir, exclude): dst_dir: str, absolute path to the destination directory exclude: (set(str), set(str)), tuple of (exclude_files, exclude_dirs), each element of the set is a path relative to src_dir. + install_mode: FileMode object, or None to use defaults. ''' if not os.path.isabs(src_dir): raise ValueError('src_dir must be absolute, got %s' % src_dir) @@ -212,7 +213,7 @@ def do_copydir(data, src_dir, dst_dir, exclude): os.mkdir(parent_dir) shutil.copystat(os.path.dirname(abs_src), parent_dir) shutil.copy2(abs_src, abs_dst, follow_symlinks=False) - sanitize_permissions(abs_dst, data.install_umask) + set_mode(abs_dst, install_mode, data.install_umask) append_to_log(abs_dst) def get_destdir_path(d, path): @@ -263,8 +264,7 @@ def install_subdirs(d): full_dst_dir = get_destdir_path(d, dst_dir) print('Installing subdir %s to %s' % (src_dir, full_dst_dir)) d.dirmaker.makedirs(full_dst_dir, exist_ok=True) - do_copydir(d, src_dir, full_dst_dir, exclude) - set_mode(full_dst_dir, mode, d.install_umask) + do_copydir(d, src_dir, full_dst_dir, exclude, mode) def install_data(d): for i in d.data: @@ -283,6 +283,7 @@ def install_man(d): outfilename = get_destdir_path(d, m[1]) outdir = os.path.dirname(outfilename) d.dirmaker.makedirs(outdir, exist_ok=True) + install_mode = m[2] print('Installing %s to %s' % (full_source_filename, outdir)) if outfilename.endswith('.gz') and not full_source_filename.endswith('.gz'): with open(outfilename, 'wb') as of: @@ -294,7 +295,7 @@ def install_man(d): append_to_log(outfilename) else: do_copyfile(full_source_filename, outfilename) - sanitize_permissions(outfilename, d.install_umask) + set_mode(outfilename, install_mode, d.install_umask) def install_headers(d): for t in d.headers: @@ -302,10 +303,11 @@ def install_headers(d): fname = os.path.basename(fullfilename) outdir = get_destdir_path(d, t[1]) outfilename = os.path.join(outdir, fname) + install_mode = t[2] print('Installing %s to %s' % (fname, outdir)) d.dirmaker.makedirs(outdir, exist_ok=True) do_copyfile(fullfilename, outfilename) - sanitize_permissions(outfilename, d.install_umask) + set_mode(outfilename, install_mode, d.install_umask) def run_install_script(d): env = {'MESON_SOURCE_ROOT': d.source_dir, @@ -364,13 +366,14 @@ def install_targets(d): aliases = t[2] should_strip = t[3] install_rpath = t[4] + install_mode = t[5] print('Installing %s to %s' % (fname, outname)) d.dirmaker.makedirs(outdir, exist_ok=True) if not os.path.exists(fname): raise RuntimeError('File {!r} could not be found'.format(fname)) elif os.path.isfile(fname): do_copyfile(fname, outname) - sanitize_permissions(outname, d.install_umask) + set_mode(outname, install_mode, d.install_umask) if should_strip and d.strip_bin is not None: if fname.endswith('.jar'): print('Not stripping jar target:', os.path.basename(fname)) @@ -387,12 +390,11 @@ def install_targets(d): pdb_outname = os.path.splitext(outname)[0] + '.pdb' print('Installing pdb file %s to %s' % (pdb_filename, pdb_outname)) do_copyfile(pdb_filename, pdb_outname) - sanitize_permissions(pdb_outname, d.install_umask) + set_mode(pdb_outname, install_mode, d.install_umask) elif os.path.isdir(fname): fname = os.path.join(d.build_dir, fname.rstrip('/')) outname = os.path.join(outdir, os.path.basename(fname)) - do_copydir(d, fname, outname, None) - sanitize_permissions(outname, d.install_umask) + do_copydir(d, fname, outname, None, install_mode) else: raise RuntimeError('Unknown file type for {!r}'.format(fname)) printed_symlink_error = False