diff options
author | Jussi Pakkanen <jpakkane@gmail.com> | 2017-01-23 22:03:14 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-01-23 22:03:14 +0200 |
commit | 4c9c14121fee9f733ab1f731b87a6714338174c6 (patch) | |
tree | 378eb2fb05d3f19dc57f58b9257b4d48a3cfa907 /mesonbuild | |
parent | 8ec9b0a71ffa1043b17a11e594219971c9288600 (diff) | |
parent | 7d6f628ed4c3c3dca32bef01b2581f2e9bcde189 (diff) | |
download | meson-4c9c14121fee9f733ab1f731b87a6714338174c6.zip meson-4c9c14121fee9f733ab1f731b87a6714338174c6.tar.gz meson-4c9c14121fee9f733ab1f731b87a6714338174c6.tar.bz2 |
Merge pull request #1302 from centricular/install-mode
Support file perms for install_data and install_subdir
Diffstat (limited to 'mesonbuild')
-rw-r--r-- | mesonbuild/backend/backends.py | 3 | ||||
-rw-r--r-- | mesonbuild/backend/ninjabackend.py | 4 | ||||
-rw-r--r-- | mesonbuild/build.py | 3 | ||||
-rw-r--r-- | mesonbuild/dependencies.py | 1 | ||||
-rw-r--r-- | mesonbuild/interpreter.py | 36 | ||||
-rw-r--r-- | mesonbuild/mesonlib.py | 104 | ||||
-rw-r--r-- | mesonbuild/scripts/meson_install.py | 32 |
7 files changed, 170 insertions, 13 deletions
diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py index e46c2c5..6f8a50e 100644 --- a/mesonbuild/backend/backends.py +++ b/mesonbuild/backend/backends.py @@ -459,7 +459,8 @@ class Backend: mfobj['projects'] = self.build.dep_manifest with open(ifilename, 'w') as f: f.write(json.dumps(mfobj)) - d.data.append([ifilename, ofilename]) + # Copy file from, to, and with mode unchanged + d.data.append([ifilename, ofilename, None]) def get_regen_filelist(self): '''List of all files whose alteration means that the build diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index e1a478c..628718f 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -700,7 +700,7 @@ int dummy; assert(isinstance(f, mesonlib.File)) plain_f = os.path.split(f.fname)[1] dstabs = os.path.join(subdir, plain_f) - i = [f.absolute_path(srcdir, builddir), dstabs] + i = [f.absolute_path(srcdir, builddir), dstabs, de.install_mode] d.data.append(i) def generate_subdir_install(self, d): @@ -715,7 +715,7 @@ int dummy; inst_dir = sd.installable_subdir src_dir = os.path.join(self.environment.get_source_dir(), subdir) dst_dir = os.path.join(self.environment.get_prefix(), sd.install_dir) - d.install_subdirs.append([src_dir, inst_dir, dst_dir]) + d.install_subdirs.append([src_dir, inst_dir, dst_dir, sd.install_mode]) def generate_tests(self, outfile): self.serialise_tests() diff --git a/mesonbuild/build.py b/mesonbuild/build.py index ceae49b..dc072a5 100644 --- a/mesonbuild/build.py +++ b/mesonbuild/build.py @@ -1502,9 +1502,10 @@ class ConfigurationData: # A bit poorly named, but this represents plain data files to copy # during install. class Data: - def __init__(self, sources, install_dir): + def __init__(self, sources, install_dir, install_mode=None): self.sources = sources self.install_dir = install_dir + self.install_mode = install_mode if not isinstance(self.sources, list): self.sources = [self.sources] for s in self.sources: diff --git a/mesonbuild/dependencies.py b/mesonbuild/dependencies.py index 32c61c1..6ae91d4 100644 --- a/mesonbuild/dependencies.py +++ b/mesonbuild/dependencies.py @@ -318,6 +318,7 @@ class WxDependency(Dependency): def __init__(self, environment, kwargs): Dependency.__init__(self, 'wx') self.is_found = False + self.modversion = 'none' if WxDependency.wx_found is None: self.check_wxconfig() if not WxDependency.wx_found: diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index b5cd0b6..cb5b617 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -22,7 +22,7 @@ from . import optinterpreter from . import compilers from .wrap import wrap from . import mesonlib -from .mesonlib import Popen_safe +from .mesonlib import FileMode, Popen_safe from .dependencies import InternalDependency, Dependency from .interpreterbase import InterpreterBase from .interpreterbase import check_stringlist, noPosargs, noKwargs, stringArgs @@ -453,11 +453,12 @@ class DataHolder(InterpreterObject): return self.held_object.install_dir class InstallDir(InterpreterObject): - def __init__(self, source_subdir, installable_subdir, install_dir): + def __init__(self, src_subdir, inst_subdir, install_dir, install_mode): InterpreterObject.__init__(self) - self.source_subdir = source_subdir - self.installable_subdir = installable_subdir + self.source_subdir = src_subdir + self.installable_subdir = inst_subdir self.install_dir = install_dir + self.install_mode = install_mode class Man(InterpreterObject): @@ -2141,6 +2142,25 @@ requirements use the version keyword argument instead.''') self.evaluate_codeblock(codeblock) self.subdir = prev_subdir + def _get_kwarg_install_mode(self, kwargs): + if 'install_mode' not in kwargs: + return None + install_mode = [] + mode = mesonlib.stringintlistify(kwargs.get('install_mode', [])) + for m in mode: + # We skip any arguments that are set to `false` + if m is False: + m = None + install_mode.append(m) + if len(install_mode) > 3: + raise InvalidArguments('Keyword argument install_mode takes at ' + 'most 3 arguments.') + if len(install_mode) > 0 and install_mode[0] is not None and \ + not isinstance(install_mode[0], str): + raise InvalidArguments('Keyword argument install_mode requires the ' + 'permissions arg to be a string or false') + return FileMode(*install_mode) + def func_install_data(self, node, args, kwargs): kwsource = mesonlib.stringlistify(kwargs.get('sources', [])) raw_sources = args + kwsource @@ -2153,7 +2173,10 @@ requirements use the version keyword argument instead.''') source_strings.append(s) sources += self.source_strings_to_files(source_strings) install_dir = kwargs.get('install_dir', None) - data = DataHolder(build.Data(sources, install_dir)) + if not isinstance(install_dir, (str, type(None))): + raise InvalidArguments('Keyword argument install_dir not a string.') + install_mode = self._get_kwarg_install_mode(kwargs) + data = DataHolder(build.Data(sources, install_dir, install_mode)) self.build.data.append(data.held_object) return data @@ -2166,7 +2189,8 @@ requirements use the version keyword argument instead.''') install_dir = kwargs['install_dir'] if not isinstance(install_dir, str): raise InvalidArguments('Keyword argument install_dir not a string.') - idir = InstallDir(self.subdir, args[0], install_dir) + install_mode = self._get_kwarg_install_mode(kwargs) + idir = InstallDir(self.subdir, args[0], install_dir, install_mode) self.build.install_dirs.append(idir) return idir diff --git a/mesonbuild/mesonlib.py b/mesonbuild/mesonlib.py index 2587d6f..2ad43c8 100644 --- a/mesonbuild/mesonlib.py +++ b/mesonbuild/mesonlib.py @@ -14,6 +14,7 @@ """A library of random helper functionality.""" +import stat import platform, subprocess, operator, os, shutil, re from glob import glob @@ -24,6 +25,97 @@ class MesonException(Exception): class EnvironmentException(MesonException): '''Exceptions thrown while processing and creating the build environment''' +class FileMode: + # The first triad is for owner permissions, the second for group permissions, + # and the third for others (everyone else). + # For the 1st character: + # 'r' means can read + # '-' means not allowed + # For the 2nd character: + # 'w' means can write + # '-' means not allowed + # For the 3rd character: + # 'x' means can execute + # 's' means can execute and setuid/setgid is set (owner/group triads only) + # 'S' means cannot execute and setuid/setgid is set (owner/group triads only) + # 't' means can execute and sticky bit is set ("others" triads only) + # 'T' means cannot execute and sticky bit is set ("others" triads only) + # '-' means none of these are allowed + # + # The meanings of 'rwx' perms is not obvious for directories; see: + # https://www.hackinglinuxexposed.com/articles/20030424.html + # + # For information on this notation such as setuid/setgid/sticky bits, see: + # https://en.wikipedia.org/wiki/File_system_permissions#Symbolic_notation + symbolic_perms_regex = re.compile('[r-][w-][xsS-]' # Owner perms + '[r-][w-][xsS-]' # Group perms + '[r-][w-][xtT-]') # Others perms + + def __init__(self, perms=None, owner=None, group=None): + self.perms_s = perms + self.perms = self.perms_s_to_bits(perms) + self.owner = owner + self.group = group + + def __repr__(self): + ret = '<FileMode: {!r} owner={} group={}' + return ret.format(self.perms_s, self.owner, self.group) + + @classmethod + def perms_s_to_bits(cls, perms_s): + ''' + Does the opposite of stat.filemode(), converts strings of the form + 'rwxr-xr-x' to st_mode enums which can be passed to os.chmod() + ''' + if perms_s is None: + # No perms specified, we will not touch the permissions + return -1 + eg = 'rwxr-xr-x' + if not isinstance(perms_s, str): + msg = 'Install perms must be a string. For example, {!r}' + raise MesonException(msg.format(eg)) + if len(perms_s) != 9 or not cls.symbolic_perms_regex.match(perms_s): + msg = 'File perms {!r} must be exactly 9 chars. For example, {!r}' + raise MesonException(msg.format(perms_s, eg)) + perms = 0 + # Owner perms + if perms_s[0] == 'r': + perms |= stat.S_IRUSR + if perms_s[1] == 'w': + perms |= stat.S_IWUSR + if perms_s[2] == 'x': + perms |= stat.S_IXUSR + elif perms_s[2] == 'S': + perms |= stat.S_ISUID + elif perms_s[2] == 's': + perms |= stat.S_IXUSR + perms |= stat.S_ISUID + # Group perms + if perms_s[3] == 'r': + perms |= stat.S_IRGRP + if perms_s[4] == 'w': + perms |= stat.S_IWGRP + if perms_s[5] == 'x': + perms |= stat.S_IXGRP + elif perms_s[5] == 'S': + perms |= stat.S_ISGID + elif perms_s[5] == 's': + perms |= stat.S_IXGRP + perms |= stat.S_ISGID + # Others perms + if perms_s[6] == 'r': + perms |= stat.S_IROTH + if perms_s[7] == 'w': + perms |= stat.S_IWOTH + if perms_s[8] == 'x': + perms |= stat.S_IXOTH + elif perms_s[8] == 'T': + perms |= stat.S_ISVTX + elif perms_s[8] == 't': + perms |= stat.S_IXOTH + perms |= stat.S_ISVTX + return perms + class File: def __init__(self, is_built, subdir, fname): self.is_built = is_built @@ -360,11 +452,21 @@ def replace_if_different(dst, dst_tmp): else: os.unlink(dst_tmp) +def stringintlistify(item): + if isinstance(item, (str, int)): + item = [item] + if not isinstance(item, list): + raise MesonException('Item must be a list, a string, or an int') + for i in item: + if not isinstance(i, (str, int, type(None))): + raise MesonException('List item must be a string or an int') + return item + def stringlistify(item): if isinstance(item, str): item = [item] if not isinstance(item, list): - raise MesonException('Item is not an array') + raise MesonException('Item is not a list') for i in item: if not isinstance(i, str): raise MesonException('List item not a string.') diff --git a/mesonbuild/scripts/meson_install.py b/mesonbuild/scripts/meson_install.py index 676a1e5..a74573e 100644 --- a/mesonbuild/scripts/meson_install.py +++ b/mesonbuild/scripts/meson_install.py @@ -16,10 +16,34 @@ import sys, pickle, os, shutil, subprocess, gzip, platform from glob import glob from . import depfixer from . import destdir_join -from ..mesonlib import Popen_safe +from ..mesonlib import is_windows, Popen_safe install_log_file = None +def set_mode(path, mode): + if mode is None: + # Keep mode unchanged + return + if (mode.perms_s or mode.owner or mode.group) is None: + # Nothing to set + return + # No chown() on Windows, and must set one of owner/group + if not is_windows() and (mode.owner or mode.group) is not None: + try: + shutil.chown(path, mode.owner, mode.group) + except PermissionError as e: + msg = '{!r}: Unable to set owner {!r} and group {!r}: {}, ignoring...' + print(msg.format(path, mode.owner, mode.group, e.strerror)) + # Must set permissions *after* setting owner/group otherwise the + # setuid/setgid bits will get wiped by chmod + # NOTE: On Windows you can set read/write perms; the rest are ignored + if mode.perms_s is not None: + try: + os.chmod(path, mode.perms) + except PermissionError as e: + msg = '{!r}: Unable to set permissions {!r}: {}, ignoring...' + print(msg.format(path, mode.perms_s, e.strerror)) + def append_to_log(line): install_log_file.write(line) if not line.endswith('\n'): @@ -96,7 +120,7 @@ def do_install(datafilename): run_install_script(d) def install_subdirs(data): - for (src_dir, inst_dir, dst_dir) in data.install_subdirs: + for (src_dir, inst_dir, dst_dir, mode) in data.install_subdirs: if src_dir.endswith('/') or src_dir.endswith('\\'): src_dir = src_dir[:-1] src_prefix = os.path.join(src_dir, inst_dir) @@ -105,15 +129,19 @@ def install_subdirs(data): if not os.path.exists(dst_dir): os.makedirs(dst_dir) do_copydir(src_prefix, src_dir, dst_dir) + dst_prefix = os.path.join(dst_dir, inst_dir) + set_mode(dst_prefix, mode) def install_data(d): for i in d.data: fullfilename = i[0] outfilename = get_destdir_path(d, i[1]) + mode = i[2] outdir = os.path.split(outfilename)[0] os.makedirs(outdir, exist_ok=True) print('Installing %s to %s.' % (fullfilename, outdir)) do_copyfile(fullfilename, outfilename) + set_mode(outfilename, mode) def install_man(d): for m in d.man: |