Skip to content
This repository has been archived by the owner on Feb 15, 2025. It is now read-only.

Commit

Permalink
install_{data,headers,subdir}: implement follow_symlinks
Browse files Browse the repository at this point in the history
This permits users who rely on following symlinks to stay on the old
default of following them.
  • Loading branch information
ArsenArsen authored and eli-schwartz committed Sep 14, 2023
1 parent 56ef698 commit 0af126f
Show file tree
Hide file tree
Showing 15 changed files with 139 additions and 24 deletions.
7 changes: 7 additions & 0 deletions docs/markdown/snippets/install_follow_symlink_arg.md
Original file line number Diff line number Diff line change
@@ -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.
8 changes: 8 additions & 0 deletions docs/yaml/functions/install_data.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
8 changes: 8 additions & 0 deletions docs/yaml/functions/install_headers.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
8 changes: 8 additions & 0 deletions docs/yaml/functions/install_subdir.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
14 changes: 9 additions & 5 deletions mesonbuild/backend/backends.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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


Expand Down Expand Up @@ -1832,7 +1834,7 @@ def generate_header_install(self, d: InstallData) -> None:
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:
Expand Down Expand Up @@ -1877,7 +1879,8 @@ def generate_data_install(self, d: InstallData) -> None:
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:
Expand Down Expand Up @@ -1908,7 +1911,8 @@ def generate_subdir_install(self, d: InstallData) -> None:
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']:
Expand Down
3 changes: 3 additions & 0 deletions mesonbuild/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down
20 changes: 14 additions & 6 deletions mesonbuild/interpreter/interpreter.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
INSTALL_KW,
INSTALL_DIR_KW,
INSTALL_MODE_KW,
INSTALL_FOLLOW_SYMLINKS,
LINK_WITH_KW,
LINK_WHOLE_KW,
CT_INSTALL_TAG_KW,
Expand Down Expand Up @@ -2238,6 +2239,7 @@ def add_test(self, node: mparser.BaseNode,
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']],
Expand All @@ -2264,7 +2266,8 @@ def func_install_headers(self, node: mparser.BaseNode,

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)

Expand Down Expand Up @@ -2459,6 +2462,7 @@ def _warn_kwarg_install_mode_sticky(self, mode: FileMode) -> None:
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']],
Expand Down Expand Up @@ -2486,15 +2490,16 @@ def func_install_data(self, node: mparser.BaseNode,

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:
Expand All @@ -2506,7 +2511,8 @@ def install_data_impl(self, sources: T.List[mesonlib.File], install_dir: str,
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)
Expand All @@ -2525,6 +2531,7 @@ def install_data_impl(self, sources: T.List[mesonlib.File], install_dir: str,
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:
Expand All @@ -2550,7 +2557,8 @@ def func_install_subdir(self, node: mparser.BaseNode, args: T.Tuple[str],
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

Expand Down
3 changes: 3 additions & 0 deletions mesonbuild/interpreter/kwargs.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -132,13 +133,15 @@ class FuncInstallData(TypedDict):
sources: T.List[FileOrString]
rename: T.List[str]
install_mode: FileMode
follow_symlinks: T.Optional[bool]


class FuncInstallHeaders(TypedDict):

install_dir: T.Optional[str]
install_mode: FileMode
subdir: T.Optional[str]
follow_symlinks: T.Optional[bool]


class FuncInstallMan(TypedDict):
Expand Down
6 changes: 6 additions & 0 deletions mesonbuild/interpreter/type_checking.py
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,12 @@ def _output_validator(outputs: T.List[str]) -> T.Optional[str]:

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(
Expand Down
31 changes: 18 additions & 13 deletions mesonbuild/minstall.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,11 @@ class ArgumentType(Protocol):
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] = []

Expand Down Expand Up @@ -389,7 +391,8 @@ def should_preserve_existing_file(self, from_file: str, to_file: str) -> bool:
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}')
Expand Down Expand Up @@ -417,10 +420,10 @@ def do_copyfile(self, from_file: str, to_file: str,
# 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)
Expand Down Expand Up @@ -454,7 +457,7 @@ def do_symlink(self, target: str, link: str, destdir: str, full_dst_dir: str, al

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.
Expand Down Expand Up @@ -519,7 +522,7 @@ def do_copydir(self, data: InstallData, src_dir: str, dst_dir: str,
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:
Expand Down Expand Up @@ -613,7 +616,8 @@ def install_subdirs(self, d: InstallData, dm: DirMaker, destdir: str, fullprefix
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:
Expand All @@ -622,7 +626,7 @@ def install_data(self, d: InstallData, dm: DirMaker, destdir: str, fullprefix: s
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)

Expand Down Expand Up @@ -668,7 +672,8 @@ def install_headers(self, d: InstallData, dm: DirMaker, destdir: str, fullprefix
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)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
test
Original file line number Diff line number Diff line change
@@ -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,
)
Original file line number Diff line number Diff line change
@@ -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"}
]
}

0 comments on commit 0af126f

Please sign in to comment.