aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJussi Pakkanen <jpakkane@gmail.com>2021-01-14 15:17:49 +0000
committerGitHub <noreply@github.com>2021-01-14 15:17:49 +0000
commit4b3d48a8c94e28e12dc928fb343f52f3b669510d (patch)
tree5f0072f487f652df96b1a6266b1499220c08c485
parente3bd45c7c35e56987836b4d675c306bbe035fb78 (diff)
parent1849a9e9b7218ecb695a8414eba9e15ebdc3fe1e (diff)
downloadmeson-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.py63
-rw-r--r--mesonbuild/build.py57
-rw-r--r--mesonbuild/interpreter.py59
-rw-r--r--mesonbuild/minstall.py183
-rw-r--r--mesonbuild/modules/cmake.py4
-rw-r--r--mesonbuild/modules/gnome.py2
-rw-r--r--mesonbuild/modules/pkgconfig.py2
-rw-r--r--mesonbuild/modules/unstable_external_project.py18
-rw-r--r--mesonbuild/scripts/depfixer.py8
-rwxr-xr-xrun_mypy.py1
-rw-r--r--test cases/failing/69 install_data rename bad size/test.json2
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."
}
]
}