Add 'install_mode' to all installable targets

This makes it possible to customize permissions of all installable
targets, such as executable(), libraries, man pages, header files and
custom or generated targets.

This is useful, for instance, to install setuid/setgid binaries, which
was hard to accomplish without access to this attribute.
pull/3134/head
Filipe Brandenburger 7 years ago committed by Nirbheek Chauhan
parent 0ccc0e92d1
commit 05c43cdcd1
  1. 15
      mesonbuild/backend/ninjabackend.py
  2. 11
      mesonbuild/build.py
  3. 25
      mesonbuild/interpreter.py
  4. 24
      mesonbuild/scripts/meson_install.py

@ -724,6 +724,7 @@ int dummy;
"Pass 'false' for outputs that should not be installed and 'true' for\n" \ "Pass 'false' for outputs that should not be installed and 'true' for\n" \
'using the default installation directory for an output.' 'using the default installation directory for an output.'
raise MesonException(m.format(t.name, num_out, t.get_outputs(), num_outdirs)) 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) # Install the target output(s)
if isinstance(t, build.BuildTarget): if isinstance(t, build.BuildTarget):
should_strip = self.get_option_for_target('strip', t) should_strip = self.get_option_for_target('strip', t)
@ -731,7 +732,7 @@ int dummy;
# Done separately because of strip/aliases/rpath # Done separately because of strip/aliases/rpath
if outdirs[0] is not False: if outdirs[0] is not False:
i = [self.get_target_filename(t), outdirs[0], 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) d.targets.append(i)
# On toolchains/platforms that use an import library for # On toolchains/platforms that use an import library for
# linking (separate from the shared library with all the # linking (separate from the shared library with all the
@ -749,7 +750,7 @@ int dummy;
implib_install_dir, implib_install_dir,
# It has no aliases, should not be stripped, and # It has no aliases, should not be stripped, and
# doesn't have an install_rpath # doesn't have an install_rpath
{}, False, ''] {}, False, '', install_mode]
d.targets.append(i) d.targets.append(i)
# Install secondary outputs. Only used for Vala right now. # Install secondary outputs. Only used for Vala right now.
if num_outdirs > 1: if num_outdirs > 1:
@ -758,7 +759,7 @@ int dummy;
if outdir is False: if outdir is False:
continue continue
f = os.path.join(self.get_target_dir(t), output) 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): elif isinstance(t, build.CustomTarget):
# If only one install_dir is specified, assume that all # If only one install_dir is specified, assume that all
# outputs will be installed into it. This is for # outputs will be installed into it. This is for
@ -770,14 +771,14 @@ int dummy;
if num_outdirs == 1 and num_out > 1: if num_outdirs == 1 and num_out > 1:
for output in t.get_outputs(): for output in t.get_outputs():
f = os.path.join(self.get_target_dir(t), output) 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: else:
for output, outdir in zip(t.get_outputs(), outdirs): for output, outdir in zip(t.get_outputs(), outdirs):
# User requested that we not install this output # User requested that we not install this output
if outdir is False: if outdir is False:
continue continue
f = os.path.join(self.get_target_dir(t), output) 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): def generate_custom_install_script(self, d):
result = [] result = []
@ -809,7 +810,7 @@ int dummy;
msg = 'Invalid header type {!r} can\'t be installed' msg = 'Invalid header type {!r} can\'t be installed'
raise MesonException(msg.format(f)) raise MesonException(msg.format(f))
abspath = f.absolute_path(srcdir, builddir) abspath = f.absolute_path(srcdir, builddir)
i = [abspath, outdir] i = [abspath, outdir, h.get_custom_install_mode()]
d.headers.append(i) d.headers.append(i)
def generate_man_install(self, d): def generate_man_install(self, d):
@ -823,7 +824,7 @@ int dummy;
subdir = os.path.join(manroot, 'man' + num) subdir = os.path.join(manroot, 'man' + num)
srcabs = f.absolute_path(self.environment.get_source_dir(), self.environment.get_build_dir()) 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') 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) d.man.append(i)
def generate_data_install(self, d): def generate_data_install(self, d):

@ -64,6 +64,7 @@ buildtarget_kwargs = set([
'install', 'install',
'install_rpath', 'install_rpath',
'install_dir', 'install_dir',
'install_mode',
'name_prefix', 'name_prefix',
'name_suffix', 'name_suffix',
'native', 'native',
@ -668,6 +669,9 @@ class BuildTarget(Target):
def get_custom_install_dir(self): def get_custom_install_dir(self):
return self.install_dir return self.install_dir
def get_custom_install_mode(self):
return self.install_mode
def process_kwargs(self, kwargs, environment): def process_kwargs(self, kwargs, environment):
super().process_kwargs(kwargs) super().process_kwargs(kwargs)
self.copy_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 # the list index of that item will not be installed
self.install_dir = typeslistify(kwargs.get('install_dir', [None]), self.install_dir = typeslistify(kwargs.get('install_dir', [None]),
(str, bool)) (str, bool))
self.install_mode = kwargs.get('install_mode', None)
main_class = kwargs.get('main_class', '') main_class = kwargs.get('main_class', '')
if not isinstance(main_class, str): if not isinstance(main_class, str):
raise InvalidArguments('Main class must be a string') raise InvalidArguments('Main class must be a string')
@ -1626,6 +1631,7 @@ class CustomTarget(Target):
'capture', 'capture',
'install', 'install',
'install_dir', 'install_dir',
'install_mode',
'build_always', 'build_always',
'depends', 'depends',
'depend_files', 'depend_files',
@ -1774,9 +1780,11 @@ class CustomTarget(Target):
# If an item in this list is False, the output corresponding to # If an item in this list is False, the output corresponding to
# the list index of that item will not be installed # the list index of that item will not be installed
self.install_dir = typeslistify(kwargs['install_dir'], (str, bool)) self.install_dir = typeslistify(kwargs['install_dir'], (str, bool))
self.install_mode = kwargs.get('install_mode', None)
else: else:
self.install = False self.install = False
self.install_dir = [None] self.install_dir = [None]
self.install_mode = None
self.build_always = kwargs.get('build_always', False) self.build_always = kwargs.get('build_always', False)
if not isinstance(self.build_always, bool): if not isinstance(self.build_always, bool):
raise InvalidArguments('Argument build_always must be a boolean.') raise InvalidArguments('Argument build_always must be a boolean.')
@ -1803,6 +1811,9 @@ class CustomTarget(Target):
def get_custom_install_dir(self): def get_custom_install_dir(self):
return self.install_dir return self.install_dir
def get_custom_install_mode(self):
return self.install_mode
def get_outputs(self): def get_outputs(self):
return self.outputs return self.outputs

@ -577,6 +577,7 @@ class Headers(InterpreterObject):
self.sources = sources self.sources = sources
self.install_subdir = kwargs.get('subdir', '') self.install_subdir = kwargs.get('subdir', '')
self.custom_install_dir = kwargs.get('install_dir', None) 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 self.custom_install_dir is not None:
if not isinstance(self.custom_install_dir, str): if not isinstance(self.custom_install_dir, str):
raise InterpreterException('Custom_install_dir must be a string.') raise InterpreterException('Custom_install_dir must be a string.')
@ -593,6 +594,9 @@ class Headers(InterpreterObject):
def get_custom_install_dir(self): def get_custom_install_dir(self):
return self.custom_install_dir return self.custom_install_dir
def get_custom_install_mode(self):
return self.custom_install_mode
class DataHolder(InterpreterObject, ObjectHolder): class DataHolder(InterpreterObject, ObjectHolder):
def __init__(self, data): def __init__(self, data):
InterpreterObject.__init__(self) InterpreterObject.__init__(self)
@ -624,6 +628,7 @@ class Man(InterpreterObject):
self.sources = sources self.sources = sources
self.validate_sources() self.validate_sources()
self.custom_install_dir = kwargs.get('install_dir', None) 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): 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.') raise InterpreterException('Custom_install_dir must be a string.')
@ -639,6 +644,9 @@ class Man(InterpreterObject):
def get_custom_install_dir(self): def get_custom_install_dir(self):
return self.custom_install_dir return self.custom_install_dir
def get_custom_install_mode(self):
return self.custom_install_mode
def get_sources(self): def get_sources(self):
return self.sources return self.sources
@ -1716,8 +1724,8 @@ permitted_kwargs = {'add_global_arguments': {'language'},
'add_test_setup': {'exe_wrapper', 'gdb', 'timeout_multiplier', 'env'}, 'add_test_setup': {'exe_wrapper', 'gdb', 'timeout_multiplier', 'env'},
'benchmark': {'args', 'env', 'should_fail', 'timeout', 'workdir', 'suite'}, 'benchmark': {'args', 'env', 'should_fail', 'timeout', 'workdir', 'suite'},
'build_target': known_build_target_kwargs, 'build_target': known_build_target_kwargs,
'configure_file': {'input', 'output', 'configuration', 'command', 'copy', 'install_dir', 'capture', 'install', 'format', 'output_format'}, 'configure_file': {'input', 'output', 'configuration', 'command', 'copy', 'install_dir', 'install_mode', 'capture', 'install', 'format', 'output_format'},
'custom_target': {'input', 'output', 'command', 'install', 'install_dir', 'build_always', 'capture', 'depends', 'depend_files', 'depfile', 'build_by_default'}, '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'}, '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'}, 'declare_dependency': {'include_directories', 'link_with', 'sources', 'dependencies', 'compile_args', 'link_args', 'link_whole', 'version'},
'executable': build.known_exe_kwargs, 'executable': build.known_exe_kwargs,
@ -1725,8 +1733,8 @@ permitted_kwargs = {'add_global_arguments': {'language'},
'generator': {'arguments', 'output', 'depfile', 'capture', 'preserve_path_from'}, 'generator': {'arguments', 'output', 'depfile', 'capture', 'preserve_path_from'},
'include_directories': {'is_system'}, 'include_directories': {'is_system'},
'install_data': {'install_dir', 'install_mode', 'rename', 'sources'}, 'install_data': {'install_dir', 'install_mode', 'rename', 'sources'},
'install_headers': {'install_dir', 'subdir'}, 'install_headers': {'install_dir', 'install_mode', 'subdir'},
'install_man': {'install_dir'}, 'install_man': {'install_dir', 'install_mode'},
'install_subdir': {'exclude_files', 'exclude_directories', 'install_dir', 'install_mode', 'strip_directory'}, 'install_subdir': {'exclude_files', 'exclude_directories', 'install_dir', 'install_mode', 'strip_directory'},
'jar': build.known_jar_kwargs, 'jar': build.known_jar_kwargs,
'project': {'version', 'meson_version', 'default_options', 'license', 'subproject_dir'}, 'project': {'version', 'meson_version', 'default_options', 'license', 'subproject_dir'},
@ -2932,6 +2940,7 @@ root and issuing %s.
if len(args) != 1: if len(args) != 1:
raise InterpreterException('custom_target: Only one positional argument is allowed, and it must be a string name') raise InterpreterException('custom_target: Only one positional argument is allowed, and it must be a string name')
name = args[0] name = args[0]
kwargs['install_mode'] = self._get_kwarg_install_mode(kwargs)
tg = CustomTargetHolder(build.CustomTarget(name, self.subdir, self.subproject, kwargs), self) tg = CustomTargetHolder(build.CustomTarget(name, self.subdir, self.subproject, kwargs), self)
self.add_target(name, tg.held_object) self.add_target(name, tg.held_object)
return tg return tg
@ -3058,6 +3067,7 @@ root and issuing %s.
@permittedKwargs(permitted_kwargs['install_headers']) @permittedKwargs(permitted_kwargs['install_headers'])
def func_install_headers(self, node, args, kwargs): def func_install_headers(self, node, args, kwargs):
source_files = self.source_strings_to_files(args) source_files = self.source_strings_to_files(args)
kwargs['install_mode'] = self._get_kwarg_install_mode(kwargs)
h = Headers(source_files, kwargs) h = Headers(source_files, kwargs)
self.build.headers.append(h) self.build.headers.append(h)
return h return h
@ -3065,6 +3075,7 @@ root and issuing %s.
@permittedKwargs(permitted_kwargs['install_man']) @permittedKwargs(permitted_kwargs['install_man'])
def func_install_man(self, node, args, kwargs): def func_install_man(self, node, args, kwargs):
fargs = self.source_strings_to_files(args) fargs = self.source_strings_to_files(args)
kwargs['install_mode'] = self._get_kwarg_install_mode(kwargs)
m = Man(fargs, kwargs) m = Man(fargs, kwargs)
self.build.man.append(m) self.build.man.append(m)
return m return m
@ -3115,7 +3126,7 @@ root and issuing %s.
self.subdir = prev_subdir self.subdir = prev_subdir
def _get_kwarg_install_mode(self, kwargs): def _get_kwarg_install_mode(self, kwargs):
if 'install_mode' not in kwargs: if kwargs.get('install_mode', None) is None:
return None return None
install_mode = [] install_mode = []
mode = mesonlib.typeslistify(kwargs.get('install_mode', []), (str, int)) mode = mesonlib.typeslistify(kwargs.get('install_mode', []), (str, int))
@ -3358,7 +3369,8 @@ root and issuing %s.
idir = kwargs.get('install_dir', None) idir = kwargs.get('install_dir', None)
if isinstance(idir, str) and idir: if isinstance(idir, str) and idir:
cfile = mesonlib.File.from_built_file(ofile_path, ofile_fname) 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) return mesonlib.File.from_built_file(self.subdir, output)
@permittedKwargs(permitted_kwargs['include_directories']) @permittedKwargs(permitted_kwargs['include_directories'])
@ -3642,6 +3654,7 @@ different subdirectory.
sources = self.source_strings_to_files(sources) sources = self.source_strings_to_files(sources)
objs = extract_as_list(kwargs, 'objects') objs = extract_as_list(kwargs, 'objects')
kwargs['dependencies'] = extract_as_list(kwargs, 'dependencies') kwargs['dependencies'] = extract_as_list(kwargs, 'dependencies')
kwargs['install_mode'] = self._get_kwarg_install_mode(kwargs)
if 'extra_files' in kwargs: if 'extra_files' in kwargs:
ef = extract_as_list(kwargs, 'extra_files') ef = extract_as_list(kwargs, 'extra_files')
kwargs['extra_files'] = self.source_strings_to_files(ef) kwargs['extra_files'] = self.source_strings_to_files(ef)

@ -148,7 +148,7 @@ def do_copyfile(from_file, to_file):
selinux_updates.append(to_file) selinux_updates.append(to_file)
append_to_log(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. Copies the contents of directory @src_dir into @dst_dir.
@ -158,7 +158,7 @@ def do_copydir(data, src_dir, dst_dir, exclude):
excluded excluded
foobar foobar
file file
do_copydir(..., '/foo', '/dst/dir', {'bar/excluded'}) creates do_copydir(..., '/foo', '/dst/dir', {'bar/excluded'}, None) creates
/dst/ /dst/
dir/ dir/
bar/ bar/
@ -170,6 +170,7 @@ def do_copydir(data, src_dir, dst_dir, exclude):
dst_dir: str, absolute path to the destination directory dst_dir: str, absolute path to the destination directory
exclude: (set(str), set(str)), tuple of (exclude_files, exclude_dirs), exclude: (set(str), set(str)), tuple of (exclude_files, exclude_dirs),
each element of the set is a path relative to src_dir. 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): if not os.path.isabs(src_dir):
raise ValueError('src_dir must be absolute, got %s' % 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) os.mkdir(parent_dir)
shutil.copystat(os.path.dirname(abs_src), parent_dir) shutil.copystat(os.path.dirname(abs_src), parent_dir)
shutil.copy2(abs_src, abs_dst, follow_symlinks=False) 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) append_to_log(abs_dst)
def get_destdir_path(d, path): def get_destdir_path(d, path):
@ -263,8 +264,7 @@ def install_subdirs(d):
full_dst_dir = get_destdir_path(d, dst_dir) full_dst_dir = get_destdir_path(d, dst_dir)
print('Installing subdir %s to %s' % (src_dir, full_dst_dir)) print('Installing subdir %s to %s' % (src_dir, full_dst_dir))
d.dirmaker.makedirs(full_dst_dir, exist_ok=True) d.dirmaker.makedirs(full_dst_dir, exist_ok=True)
do_copydir(d, src_dir, full_dst_dir, exclude) do_copydir(d, src_dir, full_dst_dir, exclude, mode)
set_mode(full_dst_dir, mode, d.install_umask)
def install_data(d): def install_data(d):
for i in d.data: for i in d.data:
@ -283,6 +283,7 @@ def install_man(d):
outfilename = get_destdir_path(d, m[1]) outfilename = get_destdir_path(d, m[1])
outdir = os.path.dirname(outfilename) outdir = os.path.dirname(outfilename)
d.dirmaker.makedirs(outdir, exist_ok=True) d.dirmaker.makedirs(outdir, exist_ok=True)
install_mode = m[2]
print('Installing %s to %s' % (full_source_filename, outdir)) print('Installing %s to %s' % (full_source_filename, outdir))
if outfilename.endswith('.gz') and not full_source_filename.endswith('.gz'): if outfilename.endswith('.gz') and not full_source_filename.endswith('.gz'):
with open(outfilename, 'wb') as of: with open(outfilename, 'wb') as of:
@ -294,7 +295,7 @@ def install_man(d):
append_to_log(outfilename) append_to_log(outfilename)
else: else:
do_copyfile(full_source_filename, outfilename) do_copyfile(full_source_filename, outfilename)
sanitize_permissions(outfilename, d.install_umask) set_mode(outfilename, install_mode, d.install_umask)
def install_headers(d): def install_headers(d):
for t in d.headers: for t in d.headers:
@ -302,10 +303,11 @@ def install_headers(d):
fname = os.path.basename(fullfilename) fname = os.path.basename(fullfilename)
outdir = get_destdir_path(d, t[1]) outdir = get_destdir_path(d, t[1])
outfilename = os.path.join(outdir, fname) outfilename = os.path.join(outdir, fname)
install_mode = t[2]
print('Installing %s to %s' % (fname, outdir)) print('Installing %s to %s' % (fname, outdir))
d.dirmaker.makedirs(outdir, exist_ok=True) d.dirmaker.makedirs(outdir, exist_ok=True)
do_copyfile(fullfilename, outfilename) do_copyfile(fullfilename, outfilename)
sanitize_permissions(outfilename, d.install_umask) set_mode(outfilename, install_mode, d.install_umask)
def run_install_script(d): def run_install_script(d):
env = {'MESON_SOURCE_ROOT': d.source_dir, env = {'MESON_SOURCE_ROOT': d.source_dir,
@ -364,13 +366,14 @@ def install_targets(d):
aliases = t[2] aliases = t[2]
should_strip = t[3] should_strip = t[3]
install_rpath = t[4] install_rpath = t[4]
install_mode = t[5]
print('Installing %s to %s' % (fname, outname)) print('Installing %s to %s' % (fname, outname))
d.dirmaker.makedirs(outdir, exist_ok=True) d.dirmaker.makedirs(outdir, exist_ok=True)
if not os.path.exists(fname): if not os.path.exists(fname):
raise RuntimeError('File {!r} could not be found'.format(fname)) raise RuntimeError('File {!r} could not be found'.format(fname))
elif os.path.isfile(fname): elif os.path.isfile(fname):
do_copyfile(fname, outname) 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 should_strip and d.strip_bin is not None:
if fname.endswith('.jar'): if fname.endswith('.jar'):
print('Not stripping jar target:', os.path.basename(fname)) 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' pdb_outname = os.path.splitext(outname)[0] + '.pdb'
print('Installing pdb file %s to %s' % (pdb_filename, pdb_outname)) print('Installing pdb file %s to %s' % (pdb_filename, pdb_outname))
do_copyfile(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): elif os.path.isdir(fname):
fname = os.path.join(d.build_dir, fname.rstrip('/')) fname = os.path.join(d.build_dir, fname.rstrip('/'))
outname = os.path.join(outdir, os.path.basename(fname)) outname = os.path.join(outdir, os.path.basename(fname))
do_copydir(d, fname, outname, None) do_copydir(d, fname, outname, None, install_mode)
sanitize_permissions(outname, d.install_umask)
else: else:
raise RuntimeError('Unknown file type for {!r}'.format(fname)) raise RuntimeError('Unknown file type for {!r}'.format(fname))
printed_symlink_error = False printed_symlink_error = False

Loading…
Cancel
Save