diff options
author | Jussi Pakkanen <jpakkane@gmail.com> | 2021-01-14 15:17:49 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-01-14 15:17:49 +0000 |
commit | 4b3d48a8c94e28e12dc928fb343f52f3b669510d (patch) | |
tree | 5f0072f487f652df96b1a6266b1499220c08c485 | |
parent | e3bd45c7c35e56987836b4d675c306bbe035fb78 (diff) | |
parent | 1849a9e9b7218ecb695a8414eba9e15ebdc3fe1e (diff) | |
download | meson-4b3d48a8c94e28e12dc928fb343f52f3b669510d.zip meson-4b3d48a8c94e28e12dc928fb343f52f3b669510d.tar.gz meson-4b3d48a8c94e28e12dc928fb343f52f3b669510d.tar.bz2 |
Merge pull request #8192 from dcbaker/submit/minstall-type-annotations
Add type annotations to minstall (and some related cleanups)
-rw-r--r-- | mesonbuild/backend/backends.py | 63 | ||||
-rw-r--r-- | mesonbuild/build.py | 57 | ||||
-rw-r--r-- | mesonbuild/interpreter.py | 59 | ||||
-rw-r--r-- | mesonbuild/minstall.py | 183 | ||||
-rw-r--r-- | mesonbuild/modules/cmake.py | 4 | ||||
-rw-r--r-- | mesonbuild/modules/gnome.py | 2 | ||||
-rw-r--r-- | mesonbuild/modules/pkgconfig.py | 2 | ||||
-rw-r--r-- | mesonbuild/modules/unstable_external_project.py | 18 | ||||
-rw-r--r-- | mesonbuild/scripts/depfixer.py | 8 | ||||
-rwxr-xr-x | run_mypy.py | 1 | ||||
-rw-r--r-- | test cases/failing/69 install_data rename bad size/test.json | 2 |
11 files changed, 238 insertions, 161 deletions
diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py index 6dad189..92e6c15 100644 --- a/mesonbuild/backend/backends.py +++ b/mesonbuild/backend/backends.py @@ -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]]]]]]]: ''' diff --git a/mesonbuild/build.py b/mesonbuild/build.py index dacf68b..017b0f0 100644 --- a/mesonbuild/build.py +++ b/mesonbuild/build.py @@ -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): diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index aa0c58e..c7049c3 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -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']) diff --git a/mesonbuild/minstall.py b/mesonbuild/minstall.py index 153ac34..3e425eb 100644 --- a/mesonbuild/minstall.py +++ b/mesonbuild/minstall.py @@ -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') diff --git a/mesonbuild/modules/cmake.py b/mesonbuild/modules/cmake.py index f6afaf3..ef53d9f 100644 --- a/mesonbuild/modules/cmake.py +++ b/mesonbuild/modules/cmake.py @@ -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 diff --git a/mesonbuild/modules/gnome.py b/mesonbuild/modules/gnome.py index 21570bd..5cad9f5 100644 --- a/mesonbuild/modules/gnome.py +++ b/mesonbuild/modules/gnome.py @@ -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 = [] diff --git a/mesonbuild/modules/pkgconfig.py b/mesonbuild/modules/pkgconfig.py index 7d347a6..db589a3 100644 --- a/mesonbuild/modules/pkgconfig.py +++ b/mesonbuild/modules/pkgconfig.py @@ -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) diff --git a/mesonbuild/modules/unstable_external_project.py b/mesonbuild/modules/unstable_external_project.py index 809b590..1604e25 100644 --- a/mesonbuild/modules/unstable_external_project.py +++ b/mesonbuild/modules/unstable_external_project.py @@ -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'}) diff --git a/mesonbuild/scripts/depfixer.py b/mesonbuild/scripts/depfixer.py index e31f87d..8ce74ee 100644 --- a/mesonbuild/scripts/depfixer.py +++ b/mesonbuild/scripts/depfixer.py @@ -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 diff --git a/run_mypy.py b/run_mypy.py index 888403c..daf7431 100755 --- a/run_mypy.py +++ b/run_mypy.py @@ -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', diff --git a/test cases/failing/69 install_data rename bad size/test.json b/test cases/failing/69 install_data rename bad size/test.json index 1329fec..b99688a 100644 --- a/test cases/failing/69 install_data rename bad size/test.json +++ b/test cases/failing/69 install_data rename bad size/test.json @@ -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." } ] } |