install_{data,headers,subdir}: implement follow_symlinks

This permits users who rely on following symlinks to stay on the old
default of following them.
pull/12260/head
Arsen Arsenović 3 years ago committed by Eli Schwartz
parent 56ef698426
commit 0af126fec7
  1. 7
      docs/markdown/snippets/install_follow_symlink_arg.md
  2. 8
      docs/yaml/functions/install_data.yaml
  3. 8
      docs/yaml/functions/install_headers.yaml
  4. 8
      docs/yaml/functions/install_subdir.yaml
  5. 14
      mesonbuild/backend/backends.py
  6. 3
      mesonbuild/build.py
  7. 20
      mesonbuild/interpreter/interpreter.py
  8. 3
      mesonbuild/interpreter/kwargs.py
  9. 6
      mesonbuild/interpreter/type_checking.py
  10. 31
      mesonbuild/minstall.py
  11. 1
      test cases/common/266 install functions and follow_symlinks/foo/file1
  12. 1
      test cases/common/266 install functions and follow_symlinks/foo/link1
  13. 1
      test cases/common/266 install functions and follow_symlinks/foo/link2.h
  14. 38
      test cases/common/266 install functions and follow_symlinks/meson.build
  15. 14
      test cases/common/266 install functions and follow_symlinks/test.json

@ -0,0 +1,7 @@
## Added follow_symlinks arg to install_data, install_header, and install_subdir
The [[install_data]], [[install_headers]], [[install_subdir]] functions now
have an optional argument `follow_symlinks` that, if set to `true`, makes it so
symbolic links in the source are followed, rather than copied into the
destination tree, to match the old behavior. The default, which is currently
to follow links, is subject to change in the future.

@ -69,3 +69,11 @@ kwargs:
sources:
type: list[file | str]
description: Additional files to install.
follow_symlinks:
type: bool
since: 1.3.0
default: true
description: |
If true, dereferences links and copies their target instead. The default
value will become false in the future.

@ -73,3 +73,11 @@ kwargs:
Disable stripping child-directories from header files when installing.
This is equivalent to GNU Automake's `nobase` option.
follow_symlinks:
type: bool
since: 1.3.0
default: true
description: |
If true, dereferences links and copies their target instead. The default
value will become false in the future.

@ -106,3 +106,11 @@ kwargs:
description: |
Install directory contents.
If `strip_directory=true` only the last component of the source path is used.
follow_symlinks:
type: bool
since: 1.3.0
default: true
description: |
If true, dereferences links and copies their target instead. The default
value will become false in the future.

@ -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']:

@ -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:

@ -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

@ -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):

@ -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(

@ -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)

@ -0,0 +1,38 @@
project('install_data following symlinks')
install_data(
'foo/link1',
install_dir: get_option('datadir') / 'followed',
follow_symlinks: true,
)
install_headers(
'foo/link2.h',
follow_symlinks: true,
subdir: 'followed'
)
install_data(
'foo/link1',
install_dir: get_option('datadir'),
follow_symlinks: false,
)
install_headers(
'foo/link2.h',
follow_symlinks: false,
)
install_subdir(
'foo',
install_dir: get_option('datadir') / 'subdir',
strip_directory: true,
follow_symlinks: false,
)
install_subdir(
'foo',
install_dir: get_option('datadir') / 'subdir_followed',
strip_directory: true,
follow_symlinks: true,
)

@ -0,0 +1,14 @@
{
"installed": [
{"type": "link", "file": "usr/share/link1"},
{"type": "link", "file": "usr/include/link2.h"},
{"type": "file", "file": "usr/share/followed/link1"},
{"type": "file", "file": "usr/include/followed/link2.h"},
{"type": "link", "file": "usr/share/subdir/link1"},
{"type": "link", "file": "usr/share/subdir/link2.h"},
{"type": "file", "file": "usr/share/subdir/file1"},
{"type": "file", "file": "usr/share/subdir_followed/link1"},
{"type": "file", "file": "usr/share/subdir_followed/link2.h"},
{"type": "file", "file": "usr/share/subdir_followed/file1"}
]
}
Loading…
Cancel
Save