aboutsummaryrefslogtreecommitdiff
path: root/mesonbuild
diff options
context:
space:
mode:
authorJussi Pakkanen <jpakkane@gmail.com>2017-01-23 22:03:14 +0200
committerGitHub <noreply@github.com>2017-01-23 22:03:14 +0200
commit4c9c14121fee9f733ab1f731b87a6714338174c6 (patch)
tree378eb2fb05d3f19dc57f58b9257b4d48a3cfa907 /mesonbuild
parent8ec9b0a71ffa1043b17a11e594219971c9288600 (diff)
parent7d6f628ed4c3c3dca32bef01b2581f2e9bcde189 (diff)
downloadmeson-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.py3
-rw-r--r--mesonbuild/backend/ninjabackend.py4
-rw-r--r--mesonbuild/build.py3
-rw-r--r--mesonbuild/dependencies.py1
-rw-r--r--mesonbuild/interpreter.py36
-rw-r--r--mesonbuild/mesonlib.py104
-rw-r--r--mesonbuild/scripts/meson_install.py32
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: