Merge pull request #8192 from dcbaker/submit/minstall-type-annotations

Add type annotations to minstall (and some related cleanups)
pull/7848/head
Jussi Pakkanen 4 years ago committed by GitHub
commit 4b3d48a8c9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 63
      mesonbuild/backend/backends.py
  2. 57
      mesonbuild/build.py
  3. 59
      mesonbuild/interpreter.py
  4. 183
      mesonbuild/minstall.py
  5. 4
      mesonbuild/modules/cmake.py
  6. 2
      mesonbuild/modules/gnome.py
  7. 2
      mesonbuild/modules/pkgconfig.py
  8. 18
      mesonbuild/modules/unstable_external_project.py
  9. 8
      mesonbuild/scripts/depfixer.py
  10. 1
      run_mypy.py
  11. 2
      test cases/failing/69 install_data rename bad size/test.json

@ -39,7 +39,10 @@ if T.TYPE_CHECKING:
from ..arglist import CompilerArgs
from ..compilers import Compiler
from ..interpreter import Interpreter, Test
from ..mesonlib import FileMode
InstallType = T.List[T.Tuple[str, str, T.Optional['FileMode']]]
InstallSubdirsType = T.List[T.Tuple[str, str, T.Optional['FileMode'], T.Tuple[T.Set[str], T.Set[str]]]]
class TestProtocol(enum.Enum):
@ -80,26 +83,31 @@ class CleanTrees:
self.trees = trees
class InstallData:
def __init__(self, source_dir, build_dir, prefix, strip_bin,
install_umask, mesonintrospect, version):
def __init__(self, source_dir: str, build_dir: str, prefix: str,
strip_bin: T.List[str], install_umask: T.Union[str, int],
mesonintrospect: T.List[str], version: str):
# TODO: in python 3.8 or with typing_Extensions install_umask could be:
# `T.Union[T.Literal['preserve'], int]`, which would be more accurate.
self.source_dir = source_dir
self.build_dir = build_dir
self.prefix = prefix
self.strip_bin = strip_bin
self.install_umask = install_umask
self.targets = []
self.headers = []
self.man = []
self.data = []
self.po_package_name = ''
self.targets: T.List[TargetInstallData] = []
self.headers: 'InstallType' = []
self.man: 'InstallType' = []
self.data: 'InstallType' = []
self.po_package_name: str = ''
self.po = []
self.install_scripts = []
self.install_subdirs = []
self.install_scripts: T.List[build.RunScript] = []
self.install_subdirs: 'InstallSubdirsType' = []
self.mesonintrospect = mesonintrospect
self.version = version
class TargetInstallData:
def __init__(self, fname, outdir, aliases, strip, install_name_mappings, rpath_dirs_to_remove, install_rpath, install_mode, optional=False):
def __init__(self, fname: str, outdir: str, aliases: T.Dict[str, str], strip: bool,
install_name_mappings: T.Dict, rpath_dirs_to_remove: T.Set[bytes],
install_rpath: str, install_mode: 'FileMode', optional: bool = False):
self.fname = fname
self.outdir = outdir
self.aliases = aliases
@ -545,14 +553,14 @@ class Backend:
paths.append(libdir)
return paths
def determine_rpath_dirs(self, target):
def determine_rpath_dirs(self, target: build.BuildTarget) -> T.Tuple[str, ...]:
if self.environment.coredata.get_option(OptionKey('layout')) == 'mirror':
result = target.get_link_dep_subdirs()
result: OrderedSet[str] = target.get_link_dep_subdirs()
else:
result = OrderedSet()
result.add('meson-out')
result.update(self.rpaths_for_bundled_shared_libraries(target))
target.rpath_dirs_to_remove.update([d.encode('utf8') for d in result])
target.rpath_dirs_to_remove.update([d.encode('utf-8') for d in result])
return tuple(result)
@staticmethod
@ -908,7 +916,7 @@ class Backend:
abs_path = self.get_target_filename_abs(a)
return os.path.relpath(abs_path, workdir)
def generate_depmf_install(self, d):
def generate_depmf_install(self, d: InstallData) -> None:
if self.build.dep_manifest_name is None:
return
ifilename = os.path.join(self.environment.get_build_dir(), 'depmf.json')
@ -917,7 +925,7 @@ class Backend:
with open(ifilename, 'w') as f:
f.write(json.dumps(mfobj))
# Copy file from, to, and with mode unchanged
d.data.append([ifilename, ofilename, None])
d.data.append((ifilename, ofilename, None))
def get_regen_filelist(self):
'''List of all files whose alteration means that the build
@ -1205,7 +1213,7 @@ class Backend:
with open(install_data_file, 'wb') as ofile:
pickle.dump(self.create_install_data(), ofile)
def generate_target_install(self, d):
def generate_target_install(self, d: InstallData) -> None:
for t in self.build.get_targets().values():
if not t.should_install():
continue
@ -1234,6 +1242,7 @@ class Backend:
# TODO: Create GNUStrip/AppleStrip/etc. hierarchy for more
# fine-grained stripping of static archives.
should_strip = not isinstance(t, build.StaticLibrary) and self.get_option_for_target(OptionKey('strip'), t)
assert isinstance(should_strip, bool), 'for mypy'
# Install primary build output (library/executable/jar, etc)
# Done separately because of strip/aliases/rpath
if outdirs[0] is not False:
@ -1301,8 +1310,8 @@ class Backend:
optional=not t.build_by_default)
d.targets.append(i)
def generate_custom_install_script(self, d):
result = []
def generate_custom_install_script(self, d: InstallData) -> None:
result: T.List[build.RunScript] = []
srcdir = self.environment.get_source_dir()
builddir = self.environment.get_build_dir()
for i in self.build.install_scripts:
@ -1316,7 +1325,7 @@ class Backend:
result.append(build.RunScript(exe, fixed_args))
d.install_scripts = result
def generate_header_install(self, d):
def generate_header_install(self, d: InstallData) -> None:
incroot = self.environment.get_includedir()
headers = self.build.get_headers()
@ -1331,10 +1340,10 @@ class Backend:
msg = 'Invalid header type {!r} can\'t be installed'
raise MesonException(msg.format(f))
abspath = f.absolute_path(srcdir, builddir)
i = [abspath, outdir, h.get_custom_install_mode()]
i = (abspath, outdir, h.get_custom_install_mode())
d.headers.append(i)
def generate_man_install(self, d):
def generate_man_install(self, d: InstallData) -> None:
manroot = self.environment.get_mandir()
man = self.build.get_man()
for m in man:
@ -1345,10 +1354,10 @@ class Backend:
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))
i = [srcabs, dstabs, m.get_custom_install_mode()]
i = (srcabs, dstabs, m.get_custom_install_mode())
d.man.append(i)
def generate_data_install(self, d):
def generate_data_install(self, d: InstallData):
data = self.build.get_data()
srcdir = self.environment.get_source_dir()
builddir = self.environment.get_build_dir()
@ -1360,10 +1369,10 @@ class Backend:
for src_file, dst_name in zip(de.sources, de.rename):
assert(isinstance(src_file, mesonlib.File))
dst_abs = os.path.join(subdir, dst_name)
i = [src_file.absolute_path(srcdir, builddir), dst_abs, de.install_mode]
i = (src_file.absolute_path(srcdir, builddir), dst_abs, de.install_mode)
d.data.append(i)
def generate_subdir_install(self, d):
def generate_subdir_install(self, d: InstallData) -> None:
for sd in self.build.get_install_subdirs():
if sd.from_source_dir:
from_dir = self.environment.get_source_dir()
@ -1376,8 +1385,8 @@ class Backend:
sd.install_dir)
if not sd.strip_directory:
dst_dir = os.path.join(dst_dir, os.path.basename(src_dir))
d.install_subdirs.append([src_dir, dst_dir, sd.install_mode,
sd.exclude])
d.install_subdirs.append(
(src_dir, dst_dir, sd.install_mode, sd.exclude))
def get_introspection_data(self, target_id: str, target: build.Target) -> T.List[T.Dict[str, T.Union[bool, str, T.List[T.Union[str, T.Dict[str, T.Union[str, T.List[str], bool]]]]]]]:
'''

@ -171,6 +171,21 @@ class Man:
return self.sources
class InstallDir:
def __init__(self, src_subdir: str, inst_subdir: str, install_dir: str,
install_mode: T.Optional['FileMode'],
exclude: T.Tuple[T.Set[str], T.Set[str]],
strip_directory: bool, from_source_dir: bool = True):
self.source_subdir = src_subdir
self.installable_subdir = inst_subdir
self.install_dir = install_dir
self.install_mode = install_mode
self.exclude = exclude
self.strip_directory = strip_directory
self.from_source_dir = from_source_dir
class Build:
"""A class that holds the status of one build including
all dependencies and so on.
@ -198,7 +213,7 @@ class Build:
self.install_scripts = []
self.postconf_scripts = []
self.dist_scripts = []
self.install_dirs = []
self.install_dirs: T.List[InstallDir] = []
self.dep_manifest_name = None
self.dep_manifest = {}
self.stdlibs = PerMachine({}, {})
@ -404,6 +419,9 @@ class EnvironmentVariables:
return env
class Target:
# TODO: should Target be an abc.ABCMeta?
def __init__(self, name: str, subdir: str, subproject: str, build_by_default: bool, for_machine: MachineChoice):
if has_path_sep(name):
# Fix failing test 53 when this becomes an error.
@ -443,7 +461,10 @@ a hard error in the future.'''.format(name))
return NotImplemented
return self.get_id() >= other.get_id()
def get_install_dir(self, environment):
def get_default_install_dir(self, env: environment.Environment) -> str:
raise NotImplementedError
def get_install_dir(self, environment: environment.Environment) -> T.Tuple[T.Any, bool]:
# Find the installation directory.
default_install_dir = self.get_default_install_dir(environment)
outdirs = self.get_custom_install_dir()
@ -554,7 +575,7 @@ class BuildTarget(Target):
self.external_deps = []
self.include_dirs = []
self.link_language = kwargs.get('link_language')
self.link_targets = []
self.link_targets: T.List[BuildTarget] = []
self.link_whole_targets = []
self.link_depends = []
self.added_deps = set()
@ -572,7 +593,7 @@ class BuildTarget(Target):
self.pic = False
self.pie = False
# Track build_rpath entries so we can remove them at install time
self.rpath_dirs_to_remove = set()
self.rpath_dirs_to_remove: T.Set[bytes] = set()
# Sources can be:
# 1. Pre-existing source files in the source tree
# 2. Pre-existing sources generated by configure_file in the build tree
@ -884,7 +905,7 @@ class BuildTarget(Target):
result.update(i.get_link_dep_subdirs())
return result
def get_default_install_dir(self, environment):
def get_default_install_dir(self, environment: environment.Environment) -> str:
return environment.get_libdir()
def get_custom_install_dir(self):
@ -995,7 +1016,7 @@ This will become a hard error in a future Meson release.''')
if not(os.path.isfile(trial)):
raise InvalidArguments('Tried to add non-existing extra file {}.'.format(i))
self.extra_files = extra_files
self.install_rpath = kwargs.get('install_rpath', '')
self.install_rpath: str = kwargs.get('install_rpath', '')
if not isinstance(self.install_rpath, str):
raise InvalidArguments('Install_rpath is not a string.')
self.build_rpath = kwargs.get('build_rpath', '')
@ -1299,7 +1320,7 @@ You probably should put it in link_with instead.''')
else:
self.extra_args[language] = args
def get_aliases(self):
def get_aliases(self) -> T.Dict[str, str]:
return {}
def get_langs_used_by_deps(self) -> T.List[str]:
@ -1673,7 +1694,7 @@ class Executable(BuildTarget):
# Only linkwithable if using export_dynamic
self.is_linkwithable = self.export_dynamic
def get_default_install_dir(self, environment):
def get_default_install_dir(self, environment: environment.Environment) -> str:
return environment.get_bindir()
def description(self):
@ -1802,8 +1823,8 @@ class SharedLibrary(BuildTarget):
self.basic_filename_tpl = '{0.prefix}{0.name}.{0.suffix}'
self.determine_filenames(environment)
def get_link_deps_mapping(self, prefix, environment):
result = {}
def get_link_deps_mapping(self, prefix, environment) -> T.Dict[str, str]:
result: T.Dict[str, str] = {}
mappings = self.get_transitive_link_deps_mapping(prefix, environment)
old = get_target_macos_dylib_install_name(self)
if old not in mappings:
@ -2053,7 +2074,7 @@ class SharedLibrary(BuildTarget):
def get_all_link_deps(self):
return [self] + self.get_transitive_link_deps()
def get_aliases(self):
def get_aliases(self) -> T.Dict[str, str]:
"""
If the versioned library name is libfoo.so.0.100.0, aliases are:
* libfoo.so.0 (soversion) -> libfoo.so.0.100.0
@ -2061,11 +2082,11 @@ class SharedLibrary(BuildTarget):
Same for dylib:
* libfoo.dylib (unversioned; for linking) -> libfoo.0.dylib
"""
aliases = {}
aliases: T.Dict[str, str] = {}
# Aliases are only useful with .so and .dylib libraries. Also if
# there's no self.soversion (no versioning), we don't need aliases.
if self.suffix not in ('so', 'dylib') or not self.soversion:
return {}
return aliases
# With .so libraries, the minor and micro versions are also in the
# filename. If ltversion != soversion we create an soversion alias:
# libfoo.so.0 -> libfoo.so.0.100.0
@ -2567,19 +2588,15 @@ class ConfigurationData:
# A bit poorly named, but this represents plain data files to copy
# during install.
class Data:
def __init__(self, sources, install_dir, install_mode=None, rename=None):
def __init__(self, sources: T.List[File], install_dir: str,
install_mode: T.Optional['FileMode'] = None, rename: T.List[str] = None):
self.sources = sources
self.install_dir = install_dir
self.install_mode = install_mode
self.sources = listify(self.sources)
for s in self.sources:
assert(isinstance(s, File))
if rename is None:
self.rename = [os.path.basename(f.fname) for f in self.sources]
else:
self.rename = stringlistify(rename)
if len(self.rename) != len(self.sources):
raise MesonException('Size of rename argument is different from number of sources')
self.rename = rename
class RunScript(dict):
def __init__(self, script, args):

@ -757,17 +757,11 @@ class DataHolder(InterpreterObject, ObjectHolder):
def get_install_dir(self):
return self.held_object.install_dir
class InstallDir(InterpreterObject):
def __init__(self, src_subdir, inst_subdir, install_dir, install_mode,
exclude, strip_directory, from_source_dir=True):
class InstallDirHolder(InterpreterObject, ObjectHolder):
def __init__(self, obj: build.InstallDir):
InterpreterObject.__init__(self)
self.source_subdir = src_subdir
self.installable_subdir = inst_subdir
self.install_dir = install_dir
self.install_mode = install_mode
self.exclude = exclude
self.strip_directory = strip_directory
self.from_source_dir = from_source_dir
ObjectHolder.__init__(self, obj)
class ManHolder(InterpreterObject, ObjectHolder):
@ -2591,8 +2585,8 @@ class Interpreter(InterpreterBase):
# FIXME: This is special cased and not ideal:
# The first source is our new VapiTarget, the rest are deps
self.process_new_values(v.sources[0])
elif isinstance(v, InstallDir):
self.build.install_dirs.append(v)
elif isinstance(v, InstallDirHolder):
self.build.install_dirs.append(v.held_object)
elif isinstance(v, Test):
self.build.tests.append(v)
elif hasattr(v, 'held_object'):
@ -4300,11 +4294,11 @@ This will become a hard error in the future.''' % kwargs['input'], location=self
@FeatureNewKwargs('install_data', '0.46.0', ['rename'])
@FeatureNewKwargs('install_data', '0.38.0', ['install_mode'])
@permittedKwargs(permitted_kwargs['install_data'])
def func_install_data(self, node, args, kwargs):
def func_install_data(self, node, args: T.List, kwargs: T.Dict[str, T.Any]):
kwsource = mesonlib.stringlistify(kwargs.get('sources', []))
raw_sources = args + kwsource
sources = []
source_strings = []
sources: T.List[mesonlib.File] = []
source_strings: T.List[str] = []
for s in raw_sources:
if isinstance(s, mesonlib.File):
sources.append(s)
@ -4313,11 +4307,18 @@ This will become a hard error in the future.''' % kwargs['input'], location=self
else:
raise InvalidArguments('Argument must be string or file.')
sources += self.source_strings_to_files(source_strings)
install_dir = kwargs.get('install_dir', None)
if not isinstance(install_dir, (str, type(None))):
install_dir: T.Optional[str] = kwargs.get('install_dir', None)
if install_dir is not None and not isinstance(install_dir, str):
raise InvalidArguments('Keyword argument install_dir not a string.')
install_mode = self._get_kwarg_install_mode(kwargs)
rename = kwargs.get('rename', None)
rename: T.Optional[T.List[str]] = kwargs.get('rename', None)
if rename is not None:
rename = mesonlib.stringlistify(rename)
if len(rename) != len(sources):
raise InvalidArguments(
'"rename" and "sources" argument lists must be the same length if "rename" is given. '
f'Rename has {len(rename)} elements and sources has {len(sources)}.')
data = DataHolder(build.Data(sources, install_dir, install_mode, rename))
self.build.data.append(data.held_object)
return data
@ -4329,43 +4330,45 @@ This will become a hard error in the future.''' % kwargs['input'], location=self
def func_install_subdir(self, node, args, kwargs):
if len(args) != 1:
raise InvalidArguments('Install_subdir requires exactly one argument.')
subdir = args[0]
subdir: str = args[0]
if not isinstance(subdir, str):
raise InvalidArguments('install_subdir positional argument 1 must be a string.')
if 'install_dir' not in kwargs:
raise InvalidArguments('Missing keyword argument install_dir')
install_dir = kwargs['install_dir']
install_dir: str = kwargs['install_dir']
if not isinstance(install_dir, str):
raise InvalidArguments('Keyword argument install_dir not a string.')
if 'strip_directory' in kwargs:
if not isinstance(kwargs['strip_directory'], bool):
strip_directory: bool = kwargs['strip_directory']
if not isinstance(strip_directory, bool):
raise InterpreterException('"strip_directory" keyword must be a boolean.')
strip_directory = kwargs['strip_directory']
else:
strip_directory = False
if 'exclude_files' in kwargs:
exclude = extract_as_list(kwargs, 'exclude_files')
exclude: T.List[str] = extract_as_list(kwargs, 'exclude_files')
for f in exclude:
if not isinstance(f, str):
raise InvalidArguments('Exclude argument not a string.')
elif os.path.isabs(f):
raise InvalidArguments('Exclude argument cannot be absolute.')
exclude_files = set(exclude)
exclude_files: T.Set[str] = set(exclude)
else:
exclude_files = set()
if 'exclude_directories' in kwargs:
exclude = extract_as_list(kwargs, 'exclude_directories')
exclude: T.List[str] = extract_as_list(kwargs, 'exclude_directories')
for d in exclude:
if not isinstance(d, str):
raise InvalidArguments('Exclude argument not a string.')
elif os.path.isabs(d):
raise InvalidArguments('Exclude argument cannot be absolute.')
exclude_directories = set(exclude)
exclude_directories: T.Set[str] = set(exclude)
else:
exclude_directories = set()
exclude = (exclude_files, exclude_directories)
install_mode = self._get_kwarg_install_mode(kwargs)
idir = InstallDir(self.subdir, subdir, install_dir, install_mode, exclude, strip_directory)
idir = build.InstallDir(self.subdir, subdir, install_dir, install_mode, exclude, strip_directory)
self.build.install_dirs.append(idir)
return idir
return InstallDirHolder(idir)
@FeatureNewKwargs('configure_file', '0.47.0', ['copy', 'output_format', 'install_mode', 'encoding'])
@FeatureNewKwargs('configure_file', '0.46.0', ['format'])

@ -12,19 +12,24 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import sys, pickle, os, shutil, subprocess, errno
import argparse
import shlex
from glob import glob
from pathlib import Path
import argparse
import errno
import os
import pickle
import shlex
import shutil
import subprocess
import sys
import typing as T
from . import environment
from .scripts import depfixer
from .scripts import destdir_join
from .mesonlib import is_windows, Popen_safe
from .backend.backends import InstallData
from .coredata import major_versions_differ, MesonVersionMismatchException
from .coredata import version as coredata_version
from .mesonlib import is_windows, Popen_safe
from .scripts import depfixer, destdir_join
try:
from __main__ import __file__ as main_file
except ImportError:
@ -32,13 +37,30 @@ except ImportError:
# This is only used for pkexec which is not, so this is fine.
main_file = None
if T.TYPE_CHECKING:
from .mesonlib import FileMode
try:
from typing import Protocol
except AttributeError:
from typing_extensions import Protocol # type: ignore
class ArgumentType(Protocol):
"""Typing information for the object returned by argparse."""
no_rebuild: bool
only_changed: bool
profile: bool
quiet: bool
wd: str
symlink_warning = '''Warning: trying to copy a symlink that points to a file. This will copy the file,
but this will be changed in a future version of Meson to copy the symlink as is. Please update your
build definitions so that it will not break when the change happens.'''
selinux_updates = []
selinux_updates: T.List[str] = []
def add_arguments(parser):
def add_arguments(parser: argparse.Namespace) -> None:
parser.add_argument('-C', default='.', dest='wd',
help='directory to cd into before running')
parser.add_argument('--profile-self', action='store_true', dest='profile',
@ -51,11 +73,11 @@ def add_arguments(parser):
help='Do not print every file that was installed.')
class DirMaker:
def __init__(self, lf):
def __init__(self, lf: T.TextIO):
self.lf = lf
self.dirs = []
self.dirs: T.List[str] = []
def makedirs(self, path, exist_ok=False):
def makedirs(self, path: str, exist_ok: bool = False) -> None:
dirname = os.path.normpath(path)
dirs = []
while dirname != os.path.dirname(dirname):
@ -72,25 +94,29 @@ class DirMaker:
dirs.reverse()
self.dirs += dirs
def __enter__(self):
def __enter__(self) -> 'DirMaker':
return self
def __exit__(self, exception_type, value, traceback):
def __exit__(self, exception_type: T.Type[Exception], value: T.Any, traceback: T.Any) -> None:
self.dirs.reverse()
for d in self.dirs:
append_to_log(self.lf, d)
def is_executable(path, follow_symlinks=False):
def is_executable(path: str, follow_symlinks: bool = False) -> bool:
'''Checks whether any of the "x" bits are set in the source file mode.'''
return bool(os.stat(path, follow_symlinks=follow_symlinks).st_mode & 0o111)
def append_to_log(lf, line):
def append_to_log(lf: T.TextIO, line: str) -> None:
lf.write(line)
if not line.endswith('\n'):
lf.write('\n')
lf.flush()
def set_chown(path, user=None, group=None, dir_fd=None, follow_symlinks=True):
def set_chown(path: str, user: T.Optional[str] = None, group: T.Optional[str] = None,
dir_fd: T.Optional[int] = None, follow_symlinks: bool = True) -> None:
# shutil.chown will call os.chown without passing all the parameters
# and particularly follow_symlinks, thus we replace it temporary
# with a lambda with all the parameters so that follow_symlinks will
@ -98,26 +124,39 @@ def set_chown(path, user=None, group=None, dir_fd=None, follow_symlinks=True):
# Not nice, but better than actually rewriting shutil.chown until
# this python bug is fixed: https://bugs.python.org/issue18108
real_os_chown = os.chown
def chown(path: T.Union[int, str, 'os.PathLike[str]', bytes, 'os.PathLike[bytes]'],
uid: int, gid: int, *, dir_fd: T.Optional[int] = dir_fd,
follow_symlinks: bool = follow_symlinks) -> None:
"""Override the default behavior of os.chown
Use a real function rather than a lambda to help mypy out. Also real
functions are faster.
"""
real_os_chown(path, gid, uid, dir_fd=dir_fd, follow_symlinks=follow_symlinks)
try:
os.chown = lambda p, u, g: real_os_chown(p, u, g,
dir_fd=dir_fd,
follow_symlinks=follow_symlinks)
os.chown = chown
shutil.chown(path, user, group)
except Exception:
raise
finally:
os.chown = real_os_chown
def set_chmod(path, mode, dir_fd=None, follow_symlinks=True):
def set_chmod(path: str, mode: int, dir_fd: T.Optional[int] = None,
follow_symlinks: bool = True) -> None:
try:
os.chmod(path, mode, dir_fd=dir_fd, follow_symlinks=follow_symlinks)
except (NotImplementedError, OSError, SystemError):
if not os.path.islink(path):
os.chmod(path, mode, dir_fd=dir_fd)
def sanitize_permissions(path, umask):
def sanitize_permissions(path: str, umask: T.Union[str, int]) -> None:
# TODO: with python 3.8 or typing_extensions we could replace this with
# `umask: T.Union[T.Literal['preserve'], int]`, which would be mroe correct
if umask == 'preserve':
return
assert isinstance(umask, int), 'umask should only be "preserver" or an integer'
new_perms = 0o777 if is_executable(path, follow_symlinks=False) else 0o666
new_perms &= ~umask
try:
@ -126,7 +165,8 @@ def sanitize_permissions(path, umask):
msg = '{!r}: Unable to set permissions {!r}: {}, ignoring...'
print(msg.format(path, new_perms, e.strerror))
def set_mode(path, mode, default_umask):
def set_mode(path: str, mode: T.Optional['FileMode'], default_umask: T.Union[str, int]) -> None:
if mode is None or (mode.perms_s or mode.owner or mode.group) is None:
# Just sanitize permissions with the default umask
sanitize_permissions(path, default_umask)
@ -159,7 +199,8 @@ def set_mode(path, mode, default_umask):
else:
sanitize_permissions(path, default_umask)
def restore_selinux_contexts():
def restore_selinux_contexts() -> None:
'''
Restores the SELinux context for files in @selinux_updates
@ -189,15 +230,15 @@ def restore_selinux_contexts():
'Standard error:', err.decode(), sep='\n')
def get_destdir_path(d, path):
def get_destdir_path(destdir: str, fullprefix: str, path: str) -> str:
if os.path.isabs(path):
output = destdir_join(d.destdir, path)
output = destdir_join(destdir, path)
else:
output = os.path.join(d.fullprefix, path)
output = os.path.join(fullprefix, path)
return output
def check_for_stampfile(fname):
def check_for_stampfile(fname: str) -> str:
'''Some languages e.g. Rust have output files
whose names are not known at configure time.
Check if this is the case and return the real
@ -222,19 +263,20 @@ def check_for_stampfile(fname):
return files[0]
return fname
class Installer:
def __init__(self, options, lf):
def __init__(self, options: 'ArgumentType', lf: T.TextIO):
self.did_install_something = False
self.options = options
self.lf = lf
self.preserved_file_count = 0
def log(self, msg):
def log(self, msg: str) -> None:
if not self.options.quiet:
print(msg)
def should_preserve_existing_file(self, from_file, to_file):
def should_preserve_existing_file(self, from_file: str, to_file: str) -> bool:
if not self.options.only_changed:
return False
# Always replace danging symlinks
@ -244,7 +286,8 @@ class Installer:
to_time = os.stat(to_file).st_mtime
return from_time <= to_time
def do_copyfile(self, from_file, to_file, makedirs=None):
def do_copyfile(self, from_file: str, to_file: str,
makedirs: T.Optional[T.Tuple[T.Any, str]] = None) -> bool:
outdir = os.path.split(to_file)[0]
if not os.path.isfile(from_file) and not os.path.islink(from_file):
raise RuntimeError('Tried to install something that isn\'t a file:'
@ -282,7 +325,9 @@ class Installer:
append_to_log(self.lf, to_file)
return True
def do_copydir(self, data, src_dir, dst_dir, exclude, install_mode):
def do_copydir(self, data: InstallData, src_dir: str, dst_dir: str,
exclude: T.Optional[T.Tuple[T.Set[str], T.Set[str]]],
install_mode: 'FileMode', dm: DirMaker) -> None:
'''
Copies the contents of directory @src_dir into @dst_dir.
@ -328,7 +373,7 @@ class Installer:
if os.path.exists(abs_dst):
print('Tried to copy directory {} but a file of that name already exists.'.format(abs_dst))
sys.exit(1)
data.dirmaker.makedirs(abs_dst)
dm.makedirs(abs_dst)
shutil.copystat(abs_src, abs_dst)
sanitize_permissions(abs_dst, data.install_umask)
for f in files:
@ -356,34 +401,34 @@ class Installer:
raise MesonVersionMismatchException(obj.version, coredata_version)
return obj
def do_install(self, datafilename):
def do_install(self, datafilename: str) -> None:
with open(datafilename, 'rb') as ifile:
d = self.check_installdata(pickle.load(ifile))
d.destdir = os.environ.get('DESTDIR', '')
d.fullprefix = destdir_join(d.destdir, d.prefix)
destdir = os.environ.get('DESTDIR', '')
fullprefix = destdir_join(destdir, d.prefix)
if d.install_umask != 'preserve':
assert isinstance(d.install_umask, int)
os.umask(d.install_umask)
self.did_install_something = False
try:
d.dirmaker = DirMaker(self.lf)
with d.dirmaker:
self.install_subdirs(d) # Must be first, because it needs to delete the old subtree.
self.install_targets(d)
self.install_headers(d)
self.install_man(d)
self.install_data(d)
with DirMaker(self.lf) as dm:
self.install_subdirs(d, dm, destdir, fullprefix) # Must be first, because it needs to delete the old subtree.
self.install_targets(d, dm, destdir, fullprefix)
self.install_headers(d, dm, destdir, fullprefix)
self.install_man(d, dm, destdir, fullprefix)
self.install_data(d, dm, destdir, fullprefix)
restore_selinux_contexts()
self.run_install_script(d)
self.run_install_script(d, fullprefix)
if not self.did_install_something:
self.log('Nothing to install.')
if not self.options.quiet and self.preserved_file_count > 0:
self.log('Preserved {} unchanged files, see {} for the full list'
.format(self.preserved_file_count, os.path.normpath(self.lf.name)))
except PermissionError:
if shutil.which('pkexec') is not None and 'PKEXEC_UID' not in os.environ and d.destdir is None:
if shutil.which('pkexec') is not None and 'PKEXEC_UID' not in os.environ and destdir is None:
print('Installation failed due to insufficient permissions.')
print('Attempting to use polkit to gain elevated privileges...')
os.execlp('pkexec', 'pkexec', sys.executable, main_file, *sys.argv[1:],
@ -391,50 +436,50 @@ class Installer:
else:
raise
def install_subdirs(self, d):
def install_subdirs(self, d: InstallData, dm: DirMaker, destdir: str, fullprefix: str) -> None:
for (src_dir, dst_dir, mode, exclude) in d.install_subdirs:
self.did_install_something = True
full_dst_dir = get_destdir_path(d, dst_dir)
full_dst_dir = get_destdir_path(destdir, fullprefix, dst_dir)
self.log('Installing subdir {} to {}'.format(src_dir, full_dst_dir))
d.dirmaker.makedirs(full_dst_dir, exist_ok=True)
self.do_copydir(d, src_dir, full_dst_dir, exclude, mode)
dm.makedirs(full_dst_dir, exist_ok=True)
self.do_copydir(d, src_dir, full_dst_dir, exclude, mode, dm)
def install_data(self, d):
def install_data(self, d: InstallData, dm: DirMaker, destdir: str, fullprefix: str) -> None:
for i in d.data:
fullfilename = i[0]
outfilename = get_destdir_path(d, i[1])
outfilename = get_destdir_path(destdir, fullprefix, i[1])
mode = i[2]
outdir = os.path.dirname(outfilename)
if self.do_copyfile(fullfilename, outfilename, makedirs=(d.dirmaker, outdir)):
if self.do_copyfile(fullfilename, outfilename, makedirs=(dm, outdir)):
self.did_install_something = True
set_mode(outfilename, mode, d.install_umask)
def install_man(self, d):
def install_man(self, d: InstallData, dm: DirMaker, destdir: str, fullprefix: str) -> None:
for m in d.man:
full_source_filename = m[0]
outfilename = get_destdir_path(d, m[1])
outfilename = get_destdir_path(destdir, fullprefix, m[1])
outdir = os.path.dirname(outfilename)
install_mode = m[2]
if self.do_copyfile(full_source_filename, outfilename, makedirs=(d.dirmaker, outdir)):
if self.do_copyfile(full_source_filename, outfilename, makedirs=(dm, outdir)):
self.did_install_something = True
set_mode(outfilename, install_mode, d.install_umask)
def install_headers(self, d):
def install_headers(self, d: InstallData, dm: DirMaker, destdir: str, fullprefix: str) -> None:
for t in d.headers:
fullfilename = t[0]
fname = os.path.basename(fullfilename)
outdir = get_destdir_path(d, t[1])
outdir = get_destdir_path(destdir, fullprefix, t[1])
outfilename = os.path.join(outdir, fname)
install_mode = t[2]
if self.do_copyfile(fullfilename, outfilename, makedirs=(d.dirmaker, outdir)):
if self.do_copyfile(fullfilename, outfilename, makedirs=(dm, outdir)):
self.did_install_something = True
set_mode(outfilename, install_mode, d.install_umask)
def run_install_script(self, d):
def run_install_script(self, d: InstallData, fullprefix: str) -> None:
env = {'MESON_SOURCE_ROOT': d.source_dir,
'MESON_BUILD_ROOT': d.build_dir,
'MESON_INSTALL_PREFIX': d.prefix,
'MESON_INSTALL_DESTDIR_PREFIX': d.fullprefix,
'MESON_INSTALL_DESTDIR_PREFIX': fullprefix,
'MESONINTROSPECT': ' '.join([shlex.quote(x) for x in d.mesonintrospect]),
}
if self.options.quiet:
@ -459,7 +504,7 @@ class Installer:
print('FAILED: install script \'{}\' exit code {}, stopped'.format(name, rc))
sys.exit(rc)
def install_targets(self, d):
def install_targets(self, d: InstallData, dm: DirMaker, destdir: str, fullprefix: str) -> None:
for t in d.targets:
if not os.path.exists(t.fname):
# For example, import libraries of shared modules are optional
@ -470,7 +515,7 @@ class Installer:
raise RuntimeError('File {!r} could not be found'.format(t.fname))
file_copied = False # not set when a directory is copied
fname = check_for_stampfile(t.fname)
outdir = get_destdir_path(d, t.outdir)
outdir = get_destdir_path(destdir, fullprefix, t.outdir)
outname = os.path.join(outdir, os.path.basename(fname))
final_path = os.path.join(d.prefix, t.outdir, os.path.basename(fname))
aliases = t.aliases
@ -481,7 +526,7 @@ class Installer:
if not os.path.exists(fname):
raise RuntimeError('File {!r} could not be found'.format(fname))
elif os.path.isfile(fname):
file_copied = self.do_copyfile(fname, outname, makedirs=(d.dirmaker, outdir))
file_copied = self.do_copyfile(fname, outname, makedirs=(dm, outdir))
set_mode(outname, install_mode, d.install_umask)
if should_strip and d.strip_bin is not None:
if fname.endswith('.jar'):
@ -504,8 +549,8 @@ class Installer:
elif os.path.isdir(fname):
fname = os.path.join(d.build_dir, fname.rstrip('/'))
outname = os.path.join(outdir, os.path.basename(fname))
d.dirmaker.makedirs(outdir, exist_ok=True)
self.do_copydir(d, fname, outname, None, install_mode)
dm.makedirs(outdir, exist_ok=True)
self.do_copydir(d, fname, outname, None, install_mode, dm)
else:
raise RuntimeError('Unknown file type for {!r}'.format(fname))
printed_symlink_error = False
@ -534,6 +579,7 @@ class Installer:
else:
raise
def rebuild_all(wd: str) -> bool:
if not (Path(wd) / 'build.ninja').is_file():
print('Only ninja backend is supported to rebuild the project before installation.')
@ -551,7 +597,8 @@ def rebuild_all(wd: str) -> bool:
return True
def run(opts):
def run(opts: 'ArgumentType') -> int:
datafilename = 'meson-private/install.dat'
private_dir = os.path.dirname(datafilename)
log_dir = os.path.join(private_dir, '../meson-logs')

@ -285,7 +285,7 @@ class CmakeModule(ExtensionModule):
}
mesonlib.do_conf_file(template_file, version_file, conf, 'meson')
res = build.Data(mesonlib.File(True, state.environment.get_scratch_dir(), version_file), pkgroot)
res = build.Data([mesonlib.File(True, state.environment.get_scratch_dir(), version_file)], pkgroot)
return ModuleReturnValue(res, [res])
def create_package_file(self, infile, outfile, PACKAGE_RELATIVE_PATH, extra, confdata):
@ -370,7 +370,7 @@ class CmakeModule(ExtensionModule):
if conffile not in interpreter.build_def_files:
interpreter.build_def_files.append(conffile)
res = build.Data(mesonlib.File(True, ofile_path, ofile_fname), install_dir)
res = build.Data([mesonlib.File(True, ofile_path, ofile_fname)], install_dir)
interpreter.build.data.append(res)
return res

@ -1629,7 +1629,7 @@ G_END_DECLS'''
with open(fname, 'w') as ofile:
for package in packages:
ofile.write(package + '\n')
return build.Data(mesonlib.File(True, outdir, fname), install_dir)
return build.Data([mesonlib.File(True, outdir, fname)], install_dir)
def _get_vapi_link_with(self, target):
link_with = []

@ -539,7 +539,7 @@ class PkgConfigModule(ExtensionModule):
self.generate_pkgconfig_file(state, deps, subdirs, name, description, url,
version, pcfile, conflicts, variables,
False, dataonly)
res = build.Data(mesonlib.File(True, state.environment.get_scratch_dir(), pcfile), pkgroot)
res = build.Data([mesonlib.File(True, state.environment.get_scratch_dir(), pcfile)], pkgroot)
variables = self.interpreter.extract_variables(kwargs, argname='uninstalled_variables', dict_new=True)
variables = parse_variable_list(variables)

@ -22,7 +22,7 @@ from ..mesonlib import (MesonException, Popen_safe, MachineChoice,
get_variable_regex, do_replacement)
from ..interpreterbase import InterpreterObject, InterpreterException, FeatureNew
from ..interpreterbase import stringArgs, permittedKwargs
from ..interpreter import Interpreter, DependencyHolder, InstallDir
from ..interpreter import Interpreter, DependencyHolder, InstallDirHolder
from ..compilers.compilers import CFLAGS_MAPPING, CEXE_MAPPING
from ..dependencies.base import InternalDependency, PkgConfigDependency
from ..environment import Environment
@ -192,15 +192,15 @@ class ExternalProject(InterpreterObject):
self.subproject,
target_kwargs)
idir = InstallDir(self.subdir.as_posix(),
Path('dist', self.rel_prefix).as_posix(),
install_dir='.',
install_mode=None,
exclude=None,
strip_directory=True,
from_source_dir=False)
idir = build.InstallDir(self.subdir.as_posix(),
Path('dist', self.rel_prefix).as_posix(),
install_dir='.',
install_mode=None,
exclude=None,
strip_directory=True,
from_source_dir=False)
return [self.target, idir]
return [self.target, InstallDirHolder(idir)]
@stringArgs
@permittedKwargs({'subdir'})

@ -294,13 +294,13 @@ class Elf(DataSizes):
self.bf.seek(offset)
self.bf.write(newname)
def fix_rpath(self, rpath_dirs_to_remove: T.List[bytes], new_rpath: bytes) -> None:
def fix_rpath(self, rpath_dirs_to_remove: T.Set[bytes], new_rpath: bytes) -> None:
# The path to search for can be either rpath or runpath.
# Fix both of them to be sure.
self.fix_rpathtype_entry(rpath_dirs_to_remove, new_rpath, DT_RPATH)
self.fix_rpathtype_entry(rpath_dirs_to_remove, new_rpath, DT_RUNPATH)
def fix_rpathtype_entry(self, rpath_dirs_to_remove: T.List[bytes], new_rpath: bytes, entrynum: int) -> None:
def fix_rpathtype_entry(self, rpath_dirs_to_remove: T.Set[bytes], new_rpath: bytes, entrynum: int) -> None:
rp_off = self.get_entry_offset(entrynum)
if rp_off is None:
if self.verbose:
@ -365,7 +365,7 @@ class Elf(DataSizes):
entry.write(self.bf)
return None
def fix_elf(fname: str, rpath_dirs_to_remove: T.List[bytes], new_rpath: T.Optional[bytes], verbose: bool = True) -> None:
def fix_elf(fname: str, rpath_dirs_to_remove: T.Set[bytes], new_rpath: T.Optional[bytes], verbose: bool = True) -> None:
with Elf(fname, verbose) as e:
if new_rpath is None:
e.print_rpath()
@ -452,7 +452,7 @@ def fix_jar(fname: str) -> None:
f.truncate()
subprocess.check_call(['jar', 'ufm', fname, 'META-INF/MANIFEST.MF'])
def fix_rpath(fname: str, rpath_dirs_to_remove: T.List[bytes], new_rpath: T.Union[str, bytes], final_path: str, install_name_mappings: T.Dict[str, str], verbose: bool = True) -> None:
def fix_rpath(fname: str, rpath_dirs_to_remove: T.Set[bytes], new_rpath: T.Union[str, bytes], final_path: str, install_name_mappings: T.Dict[str, str], verbose: bool = True) -> None:
global INSTALL_NAME_TOOL
# Static libraries, import libraries, debug information, headers, etc
# never have rpaths

@ -26,6 +26,7 @@ modules = [
'mesonbuild/mcompile.py',
'mesonbuild/mesonlib.py',
'mesonbuild/minit.py',
'mesonbuild/minstall.py',
'mesonbuild/mintro.py',
'mesonbuild/mlog.py',
'mesonbuild/modules/fs.py',

@ -1,7 +1,7 @@
{
"stdout": [
{
"line": "test cases/failing/69 install_data rename bad size/meson.build:3:0: ERROR: Size of rename argument is different from number of sources"
"line": "test cases/failing/69 install_data rename bad size/meson.build:3:0: ERROR: \"rename\" and \"sources\" argument lists must be the same length if \"rename\" is given. Rename has 1 elements and sources has 2."
}
]
}

Loading…
Cancel
Save