From 0af126fec798d6dbb0d1ad52168cc1f3f1758acd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arsen=20Arsenovi=C4=87?= Date: Tue, 12 Jul 2022 15:26:22 +0200 Subject: install_{data,headers,subdir}: implement follow_symlinks This permits users who rely on following symlinks to stay on the old default of following them. --- mesonbuild/backend/backends.py | 14 +++++++++----- mesonbuild/build.py | 3 +++ mesonbuild/interpreter/interpreter.py | 20 ++++++++++++++------ mesonbuild/interpreter/kwargs.py | 3 +++ mesonbuild/interpreter/type_checking.py | 6 ++++++ mesonbuild/minstall.py | 31 ++++++++++++++++++------------- 6 files changed, 53 insertions(+), 24 deletions(-) (limited to 'mesonbuild') diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py index 62cf162..1d2283f 100644 --- a/mesonbuild/backend/backends.py +++ b/mesonbuild/backend/backends.py @@ -172,6 +172,7 @@ class InstallDataBase: subproject: str tag: T.Optional[str] = None data_type: T.Optional[str] = None + follow_symlinks: T.Optional[bool] = None @dataclass(eq=False) class InstallSymlinkData: @@ -186,8 +187,9 @@ class InstallSymlinkData: class SubdirInstallData(InstallDataBase): def __init__(self, path: str, install_path: str, install_path_name: str, install_mode: 'FileMode', exclude: T.Tuple[T.Set[str], T.Set[str]], - subproject: str, tag: T.Optional[str] = None, data_type: T.Optional[str] = None): - super().__init__(path, install_path, install_path_name, install_mode, subproject, tag, data_type) + subproject: str, tag: T.Optional[str] = None, data_type: T.Optional[str] = None, + follow_symlinks: T.Optional[bool] = None): + super().__init__(path, install_path, install_path_name, install_mode, subproject, tag, data_type, follow_symlinks) self.exclude = exclude @@ -1832,7 +1834,7 @@ class Backend: if not isinstance(f, File): raise MesonException(f'Invalid header type {f!r} can\'t be installed') abspath = f.absolute_path(srcdir, builddir) - i = InstallDataBase(abspath, outdir, outdir_name, h.get_custom_install_mode(), h.subproject, tag='devel') + i = InstallDataBase(abspath, outdir, outdir_name, h.get_custom_install_mode(), h.subproject, tag='devel', follow_symlinks=h.follow_symlinks) d.headers.append(i) def generate_man_install(self, d: InstallData) -> None: @@ -1877,7 +1879,8 @@ class Backend: dstdir_name = os.path.join(subdir_name, dst_name) tag = de.install_tag or self.guess_install_tag(dst_abs) i = InstallDataBase(src_file.absolute_path(srcdir, builddir), dst_abs, dstdir_name, - de.install_mode, de.subproject, tag=tag, data_type=de.data_type) + de.install_mode, de.subproject, tag=tag, data_type=de.data_type, + follow_symlinks=de.follow_symlinks) d.data.append(i) def generate_symlink_install(self, d: InstallData) -> None: @@ -1908,7 +1911,8 @@ class Backend: dst_dir = os.path.join(dst_dir, os.path.basename(src_dir)) dst_name = os.path.join(dst_name, os.path.basename(src_dir)) tag = sd.install_tag or self.guess_install_tag(os.path.join(sd.install_dir, 'dummy')) - i = SubdirInstallData(src_dir, dst_dir, dst_name, sd.install_mode, sd.exclude, sd.subproject, tag) + i = SubdirInstallData(src_dir, dst_dir, dst_name, sd.install_mode, sd.exclude, sd.subproject, tag, + follow_symlinks=sd.follow_symlinks) d.install_subdirs.append(i) def get_introspection_data(self, target_id: str, target: build.Target) -> T.List['TargetIntrospectionData']: diff --git a/mesonbuild/build.py b/mesonbuild/build.py index 0943703..fece0be 100644 --- a/mesonbuild/build.py +++ b/mesonbuild/build.py @@ -156,6 +156,7 @@ class Headers(HoldableObject): custom_install_dir: T.Optional[str] custom_install_mode: 'FileMode' subproject: str + follow_symlinks: T.Optional[bool] = None # TODO: we really don't need any of these methods, but they're preserved to # keep APIs relying on them working. @@ -214,6 +215,7 @@ class InstallDir(HoldableObject): subproject: str from_source_dir: bool = True install_tag: T.Optional[str] = None + follow_symlinks: T.Optional[bool] = None @dataclass(eq=False) class DepManifest: @@ -2973,6 +2975,7 @@ class Data(HoldableObject): rename: T.List[str] = None install_tag: T.Optional[str] = None data_type: str = None + follow_symlinks: T.Optional[bool] = None def __post_init__(self) -> None: if self.rename is None: diff --git a/mesonbuild/interpreter/interpreter.py b/mesonbuild/interpreter/interpreter.py index 838ad2f..6fcefd1 100644 --- a/mesonbuild/interpreter/interpreter.py +++ b/mesonbuild/interpreter/interpreter.py @@ -78,6 +78,7 @@ from .type_checking import ( INSTALL_KW, INSTALL_DIR_KW, INSTALL_MODE_KW, + INSTALL_FOLLOW_SYMLINKS, LINK_WITH_KW, LINK_WHOLE_KW, CT_INSTALL_TAG_KW, @@ -2238,6 +2239,7 @@ class Interpreter(InterpreterBase, HoldableObject): KwargInfo('subdir', (str, NoneType)), INSTALL_MODE_KW.evolve(since='0.47.0'), INSTALL_DIR_KW, + INSTALL_FOLLOW_SYMLINKS, ) def func_install_headers(self, node: mparser.BaseNode, args: T.Tuple[T.List['mesonlib.FileOrString']], @@ -2264,7 +2266,8 @@ class Interpreter(InterpreterBase, HoldableObject): for childdir in dirs: h = build.Headers(dirs[childdir], os.path.join(install_subdir, childdir), kwargs['install_dir'], - install_mode, self.subproject) + install_mode, self.subproject, + follow_symlinks=kwargs['follow_symlinks']) ret_headers.append(h) self.build.headers.append(h) @@ -2459,6 +2462,7 @@ class Interpreter(InterpreterBase, HoldableObject): INSTALL_TAG_KW.evolve(since='0.60.0'), INSTALL_DIR_KW, PRESERVE_PATH_KW.evolve(since='0.64.0'), + INSTALL_FOLLOW_SYMLINKS, ) def func_install_data(self, node: mparser.BaseNode, args: T.Tuple[T.List['mesonlib.FileOrString']], @@ -2486,15 +2490,16 @@ class Interpreter(InterpreterBase, HoldableObject): install_mode = self._warn_kwarg_install_mode_sticky(kwargs['install_mode']) return self.install_data_impl(sources, install_dir, install_mode, rename, kwargs['install_tag'], - preserve_path=kwargs['preserve_path']) + preserve_path=kwargs['preserve_path'], + follow_symlinks=kwargs['follow_symlinks']) def install_data_impl(self, sources: T.List[mesonlib.File], install_dir: str, install_mode: FileMode, rename: T.Optional[str], tag: T.Optional[str], install_data_type: T.Optional[str] = None, - preserve_path: bool = False) -> build.Data: + preserve_path: bool = False, + follow_symlinks: T.Optional[bool] = None) -> build.Data: install_dir_name = install_dir.optname if isinstance(install_dir, P_OBJ.OptionString) else install_dir - dirs = collections.defaultdict(list) if preserve_path: for file in sources: @@ -2506,7 +2511,8 @@ class Interpreter(InterpreterBase, HoldableObject): ret_data = [] for childdir, files in dirs.items(): d = build.Data(files, os.path.join(install_dir, childdir), os.path.join(install_dir_name, childdir), - install_mode, self.subproject, rename, tag, install_data_type) + install_mode, self.subproject, rename, tag, install_data_type, + follow_symlinks) ret_data.append(d) self.build.data.extend(ret_data) @@ -2525,6 +2531,7 @@ class Interpreter(InterpreterBase, HoldableObject): validator=lambda x: 'cannot be absolute' if any(os.path.isabs(d) for d in x) else None), INSTALL_MODE_KW.evolve(since='0.38.0'), INSTALL_TAG_KW.evolve(since='0.60.0'), + INSTALL_FOLLOW_SYMLINKS, ) def func_install_subdir(self, node: mparser.BaseNode, args: T.Tuple[str], kwargs: 'kwtypes.FuncInstallSubdir') -> build.InstallDir: @@ -2550,7 +2557,8 @@ class Interpreter(InterpreterBase, HoldableObject): exclude, kwargs['strip_directory'], self.subproject, - install_tag=kwargs['install_tag']) + install_tag=kwargs['install_tag'], + follow_symlinks=kwargs['follow_symlinks']) self.build.install_dirs.append(idir) return idir diff --git a/mesonbuild/interpreter/kwargs.py b/mesonbuild/interpreter/kwargs.py index 1aee414..e67ebf0 100644 --- a/mesonbuild/interpreter/kwargs.py +++ b/mesonbuild/interpreter/kwargs.py @@ -124,6 +124,7 @@ class FuncInstallSubdir(TypedDict): exclude_files: T.List[str] exclude_directories: T.List[str] install_mode: FileMode + follow_symlinks: T.Optional[bool] class FuncInstallData(TypedDict): @@ -132,6 +133,7 @@ class FuncInstallData(TypedDict): sources: T.List[FileOrString] rename: T.List[str] install_mode: FileMode + follow_symlinks: T.Optional[bool] class FuncInstallHeaders(TypedDict): @@ -139,6 +141,7 @@ class FuncInstallHeaders(TypedDict): install_dir: T.Optional[str] install_mode: FileMode subdir: T.Optional[str] + follow_symlinks: T.Optional[bool] class FuncInstallMan(TypedDict): diff --git a/mesonbuild/interpreter/type_checking.py b/mesonbuild/interpreter/type_checking.py index 28c9152..047aff8 100644 --- a/mesonbuild/interpreter/type_checking.py +++ b/mesonbuild/interpreter/type_checking.py @@ -368,6 +368,12 @@ CT_INSTALL_TAG_KW: KwargInfo[T.List[T.Union[str, bool]]] = KwargInfo( INSTALL_TAG_KW: KwargInfo[T.Optional[str]] = KwargInfo('install_tag', (str, NoneType)) +INSTALL_FOLLOW_SYMLINKS: KwargInfo[T.Optional[bool]] = KwargInfo( + 'follow_symlinks', + (bool, NoneType), + since='1.3.0', +) + INSTALL_KW = KwargInfo('install', bool, default=False) CT_INSTALL_DIR_KW: KwargInfo[T.List[T.Union[str, Literal[False]]]] = KwargInfo( diff --git a/mesonbuild/minstall.py b/mesonbuild/minstall.py index 0d397b2..5f8629b 100644 --- a/mesonbuild/minstall.py +++ b/mesonbuild/minstall.py @@ -64,9 +64,11 @@ if T.TYPE_CHECKING: strip: bool -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.''' +symlink_warning = '''\ +Warning: trying to copy a symlink that points to a file. This currently copies +the file by default, but will be changed in a future version of Meson to copy +the link instead. Set follow_symlinks to true to preserve current behavior, or +false to copy the link.''' selinux_updates: T.List[str] = [] @@ -389,7 +391,8 @@ class Installer: return from_time <= to_time def do_copyfile(self, from_file: str, to_file: str, - makedirs: T.Optional[T.Tuple[T.Any, str]] = None) -> bool: + makedirs: T.Optional[T.Tuple[T.Any, str]] = None, + follow_symlinks: T.Optional[bool] = None) -> bool: outdir = os.path.split(to_file)[0] if not os.path.isfile(from_file) and not os.path.islink(from_file): raise MesonException(f'Tried to install something that isn\'t a file: {from_file!r}') @@ -417,10 +420,10 @@ class Installer: # Dangling symlink. Replicate as is. self.copy(from_file, outdir, follow_symlinks=False) else: - # Remove this entire branch when changing the behaviour to duplicate - # symlinks rather than copying what they point to. - print(symlink_warning) - self.copy2(from_file, to_file) + if follow_symlinks is None: + follow_symlinks = True # TODO: change to False when removing the warning + print(symlink_warning) + self.copy2(from_file, to_file, follow_symlinks=follow_symlinks) else: self.copy2(from_file, to_file) selinux_updates.append(to_file) @@ -454,7 +457,7 @@ class Installer: 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: + install_mode: 'FileMode', dm: DirMaker, follow_symlinks: T.Optional[bool] = None) -> None: ''' Copies the contents of directory @src_dir into @dst_dir. @@ -519,7 +522,7 @@ class Installer: dm.makedirs(parent_dir) self.copystat(os.path.dirname(abs_src), parent_dir) # FIXME: what about symlinks? - self.do_copyfile(abs_src, abs_dst) + self.do_copyfile(abs_src, abs_dst, follow_symlinks=follow_symlinks) self.set_mode(abs_dst, install_mode, data.install_umask) def do_install(self, datafilename: str) -> None: @@ -613,7 +616,8 @@ class Installer: full_dst_dir = get_destdir_path(destdir, fullprefix, i.install_path) self.log(f'Installing subdir {i.path} to {full_dst_dir}') dm.makedirs(full_dst_dir, exist_ok=True) - self.do_copydir(d, i.path, full_dst_dir, i.exclude, i.install_mode, dm) + self.do_copydir(d, i.path, full_dst_dir, i.exclude, i.install_mode, dm, + follow_symlinks=i.follow_symlinks) def install_data(self, d: InstallData, dm: DirMaker, destdir: str, fullprefix: str) -> None: for i in d.data: @@ -622,7 +626,7 @@ class Installer: fullfilename = i.path outfilename = get_destdir_path(destdir, fullprefix, i.install_path) outdir = os.path.dirname(outfilename) - if self.do_copyfile(fullfilename, outfilename, makedirs=(dm, outdir)): + if self.do_copyfile(fullfilename, outfilename, makedirs=(dm, outdir), follow_symlinks=i.follow_symlinks): self.did_install_something = True self.set_mode(outfilename, i.install_mode, d.install_umask) @@ -668,7 +672,8 @@ class Installer: fname = os.path.basename(fullfilename) outdir = get_destdir_path(destdir, fullprefix, t.install_path) outfilename = os.path.join(outdir, fname) - if self.do_copyfile(fullfilename, outfilename, makedirs=(dm, outdir)): + if self.do_copyfile(fullfilename, outfilename, makedirs=(dm, outdir), + follow_symlinks=t.follow_symlinks): self.did_install_something = True self.set_mode(outfilename, t.install_mode, d.install_umask) -- cgit v1.1