aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPablo Correa Gómez <ablocorrea@hotmail.com>2021-11-22 15:46:15 +0100
committerEli Schwartz <eschwartz93@gmail.com>2021-12-01 13:59:54 -0500
commit4f882ff8ec81cbc42b097d3aee8ca4a8013f538b (patch)
tree9cef0e48e71fda1e116023ca8f0791cdfea59b2c
parentbb5a09de45a99b657b16edef7a9be423735aec79 (diff)
downloadmeson-4f882ff8ec81cbc42b097d3aee8ca4a8013f538b.zip
meson-4f882ff8ec81cbc42b097d3aee8ca4a8013f538b.tar.gz
meson-4f882ff8ec81cbc42b097d3aee8ca4a8013f538b.tar.bz2
add install_symlink function
Allows installing symlinks directly from meson, which can become useful in multiple scenarios. Current main use is to help moving forward #9557
-rw-r--r--data/syntax-highlighting/vim/syntax/meson.vim1
-rw-r--r--docs/markdown/snippets/install_symlink.md11
-rw-r--r--docs/yaml/functions/install_symlink.yaml34
-rw-r--r--mesonbuild/ast/interpreter.py1
-rw-r--r--mesonbuild/backend/backends.py20
-rw-r--r--mesonbuild/build.py16
-rw-r--r--mesonbuild/interpreter/interpreter.py22
-rw-r--r--mesonbuild/interpreter/interpreterobjects.py3
-rw-r--r--mesonbuild/minstall.py62
-rw-r--r--test cases/common/247 install_symlink/datafile.dat1
-rw-r--r--test cases/common/247 install_symlink/meson.build9
-rw-r--r--test cases/common/247 install_symlink/test.json7
-rw-r--r--test cases/failing/118 pathsep in install_symlink/meson.build3
-rw-r--r--test cases/failing/118 pathsep in install_symlink/test.json7
14 files changed, 180 insertions, 17 deletions
diff --git a/data/syntax-highlighting/vim/syntax/meson.vim b/data/syntax-highlighting/vim/syntax/meson.vim
index 0af0d77..15b47cd 100644
--- a/data/syntax-highlighting/vim/syntax/meson.vim
+++ b/data/syntax-highlighting/vim/syntax/meson.vim
@@ -99,6 +99,7 @@ syn keyword mesonBuiltin
\ install_headers
\ install_man
\ install_subdir
+ \ install_symlink
\ install_emptydir
\ is_disabler
\ is_variable
diff --git a/docs/markdown/snippets/install_symlink.md b/docs/markdown/snippets/install_symlink.md
new file mode 100644
index 0000000..752c422
--- /dev/null
+++ b/docs/markdown/snippets/install_symlink.md
@@ -0,0 +1,11 @@
+## install_symlink function
+
+It is now possible to request for symbolic links to be installed during
+installation. The `install_symlink` function takes a positional argument to
+the link name, and installs a symbolic link pointing to `pointing_to` target.
+The link will be created under `install_dir` directory and cannot contain path
+separators.
+
+```meson
+install_symlink('target', pointing_to: '../bin/target', install_dir: '/usr/sbin')
+```
diff --git a/docs/yaml/functions/install_symlink.yaml b/docs/yaml/functions/install_symlink.yaml
new file mode 100644
index 0000000..d9f0de6
--- /dev/null
+++ b/docs/yaml/functions/install_symlink.yaml
@@ -0,0 +1,34 @@
+name: install_symlink
+returns: void
+since: 0.61.0
+description: |
+ Installs a symbolic link to `pointing_to` target under install_dir.
+
+posargs:
+ link_name:
+ type: str
+ description: |
+ Name of the created link under `install_dir`.
+ It cannot contain path separators. Those should go in `install_dir`.
+
+kwargs:
+ pointing_to:
+ type: str
+ required: true
+ description: |
+ Target to point the link to.
+ Can be absolute or relative and that will be respected when creating the link.
+
+ install_dir:
+ type: str
+ required: true
+ description: |
+ The absolute or relative path to the installation directory for the links.
+ If this is a relative path, it is assumed to be relative to the prefix.
+
+ install_tag:
+ type: str
+ description: |
+ A string used by the `meson install --tags` command
+ to install only a subset of the files. By default these files have no install
+ tag which means they are not being installed when `--tags` argument is specified.
diff --git a/mesonbuild/ast/interpreter.py b/mesonbuild/ast/interpreter.py
index ec93ff5..f5a1e5e 100644
--- a/mesonbuild/ast/interpreter.py
+++ b/mesonbuild/ast/interpreter.py
@@ -103,6 +103,7 @@ class AstInterpreter(InterpreterBase):
'install_man': self.func_do_nothing,
'install_data': self.func_do_nothing,
'install_subdir': self.func_do_nothing,
+ 'install_symlink': self.func_do_nothing,
'install_emptydir': self.func_do_nothing,
'configuration_data': self.func_do_nothing,
'configure_file': self.func_do_nothing,
diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py
index c9cf6fd..fbb3065 100644
--- a/mesonbuild/backend/backends.py
+++ b/mesonbuild/backend/backends.py
@@ -123,6 +123,7 @@ class InstallData:
self.man: T.List[InstallDataBase] = []
self.emptydir: T.List[InstallEmptyDir] = []
self.data: T.List[InstallDataBase] = []
+ self.symlinks: T.List[InstallSymlinkData] = []
self.install_scripts: T.List[ExecutableSerialisation] = []
self.install_subdirs: T.List[SubdirInstallData] = []
self.mesonintrospect = mesonintrospect
@@ -168,6 +169,15 @@ class InstallDataBase:
self.tag = tag
self.data_type = data_type
+class InstallSymlinkData:
+ def __init__(self, target: str, name: str, install_path: str,
+ subproject: str, tag: T.Optional[str] = None):
+ self.target = target
+ self.name = name
+ self.install_path = install_path
+ self.subproject = subproject
+ self.tag = tag
+
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]],
@@ -1497,6 +1507,7 @@ class Backend:
self.generate_man_install(d)
self.generate_emptydir_install(d)
self.generate_data_install(d)
+ self.generate_symlink_install(d)
self.generate_custom_install_script(d)
self.generate_subdir_install(d)
return d
@@ -1717,6 +1728,15 @@ class Backend:
de.install_mode, de.subproject, tag=tag, data_type=de.data_type)
d.data.append(i)
+ def generate_symlink_install(self, d: InstallData) -> None:
+ links: T.List[build.SymlinkData] = self.build.get_symlinks()
+ for l in links:
+ assert isinstance(l, build.SymlinkData)
+ install_dir = l.install_dir
+ name_abs = os.path.join(install_dir, l.name)
+ s = InstallSymlinkData(l.target, name_abs, install_dir, l.subproject, l.install_tag)
+ d.symlinks.append(s)
+
def generate_subdir_install(self, d: InstallData) -> None:
for sd in self.build.get_install_subdirs():
if sd.from_source_dir:
diff --git a/mesonbuild/build.py b/mesonbuild/build.py
index 545575c..89c158e 100644
--- a/mesonbuild/build.py
+++ b/mesonbuild/build.py
@@ -251,6 +251,7 @@ class Build:
self.man: T.List[Man] = []
self.emptydir: T.List[EmptyDir] = []
self.data: T.List[Data] = []
+ self.symlinks: T.List[SymlinkData] = []
self.static_linker: PerMachine[StaticLinker] = PerMachine(None, None)
self.subprojects = {}
self.subproject_dir = ''
@@ -329,6 +330,9 @@ class Build:
def get_data(self) -> T.List['Data']:
return self.data
+ def get_symlinks(self) -> T.List['SymlinkData']:
+ return self.symlinks
+
def get_emptydir(self) -> T.List['EmptyDir']:
return self.emptydir
@@ -2802,6 +2806,18 @@ class Data(HoldableObject):
self.subproject = subproject
self.data_type = data_type
+class SymlinkData(HoldableObject):
+ def __init__(self, target: str, name: str, install_dir: str,
+ subproject: str, install_tag: T.Optional[str] = None):
+ self.target = target
+ if name != os.path.basename(name):
+ raise InvalidArguments(f'Link name is "{name}", but link names cannot contain path separators. '
+ 'The dir part should be in install_dir.')
+ self.name = name
+ self.install_dir = install_dir
+ self.subproject = subproject
+ self.install_tag = install_tag
+
class TestSetup:
def __init__(self, exe_wrapper: T.List[str], gdb: bool,
timeout_multiplier: int, env: EnvironmentVariables,
diff --git a/mesonbuild/interpreter/interpreter.py b/mesonbuild/interpreter/interpreter.py
index 97e1a06..ae6be3b 100644
--- a/mesonbuild/interpreter/interpreter.py
+++ b/mesonbuild/interpreter/interpreter.py
@@ -364,6 +364,7 @@ class Interpreter(InterpreterBase, HoldableObject):
'install_headers': self.func_install_headers,
'install_man': self.func_install_man,
'install_subdir': self.func_install_subdir,
+ 'install_symlink': self.func_install_symlink,
'is_disabler': self.func_is_disabler,
'is_variable': self.func_is_variable,
'jar': self.func_jar,
@@ -425,6 +426,7 @@ class Interpreter(InterpreterBase, HoldableObject):
build.Man: OBJ.ManHolder,
build.EmptyDir: OBJ.EmptyDirHolder,
build.Data: OBJ.DataHolder,
+ build.SymlinkData: OBJ.SymlinkDataHolder,
build.InstallDir: OBJ.InstallDirHolder,
build.IncludeDirs: OBJ.IncludeDirsHolder,
build.EnvironmentVariables: OBJ.EnvironmentVariablesHolder,
@@ -476,6 +478,8 @@ class Interpreter(InterpreterBase, HoldableObject):
self.build.install_scripts.append(v)
elif isinstance(v, build.Data):
self.build.data.append(v)
+ elif isinstance(v, build.SymlinkData):
+ self.build.symlinks.append(v)
elif isinstance(v, dependencies.InternalDependency):
# FIXME: This is special cased and not ideal:
# The first source is our new VapiTarget, the rest are deps
@@ -1968,6 +1972,24 @@ This will become a hard error in the future.''', location=node)
return d
+ @FeatureNew('install_symlink', '0.61.0')
+ @typed_pos_args('symlink_name', str)
+ @typed_kwargs(
+ 'install_symlink',
+ KwargInfo('pointing_to', str, required=True),
+ KwargInfo('install_dir', str, required=True),
+ KwargInfo('install_tag', (str, NoneType)),
+ )
+ def func_install_symlink(self, node: mparser.BaseNode,
+ args: T.Tuple[T.List[str]],
+ kwargs) -> build.SymlinkData:
+ name = args[0] # Validation while creating the SymlinkData object
+ target = kwargs['pointing_to']
+ l = build.SymlinkData(target, name, kwargs['install_dir'],
+ self.subproject, kwargs['install_tag'])
+ self.build.symlinks.append(l)
+ return l
+
@typed_pos_args('subdir', str)
@typed_kwargs(
'subdir',
diff --git a/mesonbuild/interpreter/interpreterobjects.py b/mesonbuild/interpreter/interpreterobjects.py
index 602623a..6ff4b0f 100644
--- a/mesonbuild/interpreter/interpreterobjects.py
+++ b/mesonbuild/interpreter/interpreterobjects.py
@@ -641,6 +641,9 @@ class HeadersHolder(ObjectHolder[build.Headers]):
class DataHolder(ObjectHolder[build.Data]):
pass
+class SymlinkDataHolder(ObjectHolder[build.SymlinkData]):
+ pass
+
class InstallDirHolder(ObjectHolder[build.InstallDir]):
pass
diff --git a/mesonbuild/minstall.py b/mesonbuild/minstall.py
index 7979fe6..60d533c 100644
--- a/mesonbuild/minstall.py
+++ b/mesonbuild/minstall.py
@@ -26,7 +26,10 @@ import sys
import typing as T
from . import environment
-from .backend.backends import InstallData, InstallDataBase, InstallEmptyDir, TargetInstallData, ExecutableSerialisation
+from .backend.backends import (
+ InstallData, InstallDataBase, InstallEmptyDir, InstallSymlinkData,
+ TargetInstallData, ExecutableSerialisation
+)
from .coredata import major_versions_differ, MesonVersionMismatchException
from .coredata import version as coredata_version
from .mesonlib import Popen_safe, RealPathAction, is_windows
@@ -317,6 +320,7 @@ class Installer:
def __init__(self, options: 'ArgumentType', lf: T.TextIO):
self.did_install_something = False
+ self.printed_symlink_error = False
self.options = options
self.lf = lf
self.preserved_file_count = 0
@@ -394,7 +398,9 @@ class Installer:
return run_exe(*args, **kwargs)
return 0
- def should_install(self, d: T.Union[TargetInstallData, InstallEmptyDir, InstallDataBase, ExecutableSerialisation]) -> bool:
+ def should_install(self, d: T.Union[TargetInstallData, InstallEmptyDir,
+ InstallDataBase, InstallSymlinkData,
+ ExecutableSerialisation]) -> bool:
if d.subproject and (d.subproject in self.skip_subprojects or '*' in self.skip_subprojects):
return False
if self.tags and d.tag not in self.tags:
@@ -452,6 +458,29 @@ class Installer:
append_to_log(self.lf, to_file)
return True
+ def do_symlink(self, target: str, link: str, full_dst_dir: str) -> bool:
+ abs_target = target
+ if not os.path.isabs(target):
+ abs_target = os.path.join(full_dst_dir, target)
+ if not os.path.exists(abs_target):
+ raise RuntimeError(f'Tried to install symlink to missing file {abs_target}')
+ if os.path.exists(link):
+ if not os.path.islink(link):
+ raise RuntimeError(f'Destination {link!r} already exists and is not a symlink')
+ self.remove(link)
+ if not self.printed_symlink_error:
+ self.log(f'Installing symlink pointing to {target} to {link}')
+ try:
+ self.symlink(target, link, target_is_directory=os.path.isdir(abs_target))
+ except (NotImplementedError, OSError):
+ if not self.printed_symlink_error:
+ print("Symlink creation does not work on this platform. "
+ "Skipping all symlinking.")
+ self.printed_symlink_error = True
+ return False
+ append_to_log(self.lf, link)
+ return True
+
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:
@@ -558,6 +587,7 @@ class Installer:
self.install_man(d, dm, destdir, fullprefix)
self.install_emptydir(d, dm, destdir, fullprefix)
self.install_data(d, dm, destdir, fullprefix)
+ self.install_symlinks(d, dm, destdir, fullprefix)
self.restore_selinux_contexts(destdir)
self.apply_ldconfig(dm, destdir, libdir)
self.run_install_script(d, destdir, fullprefix)
@@ -596,6 +626,16 @@ class Installer:
self.did_install_something = True
self.set_mode(outfilename, i.install_mode, d.install_umask)
+ def install_symlinks(self, d: InstallData, dm: DirMaker, destdir: str, fullprefix: str) -> None:
+ for s in d.symlinks:
+ if not self.should_install(s):
+ continue
+ full_dst_dir = get_destdir_path(destdir, fullprefix, s.install_path)
+ full_link_name = get_destdir_path(destdir, fullprefix, s.name)
+ dm.makedirs(full_dst_dir, exist_ok=True)
+ if self.do_symlink(s.target, full_link_name, full_dst_dir):
+ self.did_install_something = True
+
def install_man(self, d: InstallData, dm: DirMaker, destdir: str, fullprefix: str) -> None:
for m in d.man:
if not self.should_install(m):
@@ -712,21 +752,9 @@ class Installer:
self.do_copydir(d, fname, outname, None, install_mode, dm)
else:
raise RuntimeError(f'Unknown file type for {fname!r}')
- printed_symlink_error = False
- for alias, to in aliases.items():
- try:
- symlinkfilename = os.path.join(outdir, alias)
- try:
- self.remove(symlinkfilename)
- except FileNotFoundError:
- pass
- self.symlink(to, symlinkfilename)
- append_to_log(self.lf, symlinkfilename)
- except (NotImplementedError, OSError):
- if not printed_symlink_error:
- print("Symlink creation does not work on this platform. "
- "Skipping all symlinking.")
- printed_symlink_error = True
+ for alias, target in aliases.items():
+ symlinkfilename = os.path.join(outdir, alias)
+ self.do_symlink(target, symlinkfilename, outdir)
if file_copied:
self.did_install_something = True
try:
diff --git a/test cases/common/247 install_symlink/datafile.dat b/test cases/common/247 install_symlink/datafile.dat
new file mode 100644
index 0000000..ff3104b
--- /dev/null
+++ b/test cases/common/247 install_symlink/datafile.dat
@@ -0,0 +1 @@
+this is a data file
diff --git a/test cases/common/247 install_symlink/meson.build b/test cases/common/247 install_symlink/meson.build
new file mode 100644
index 0000000..ae30382
--- /dev/null
+++ b/test cases/common/247 install_symlink/meson.build
@@ -0,0 +1,9 @@
+project('install_emptydir')
+
+if build_machine.system() == 'windows' and meson.backend() == 'ninja'
+ error('MESON_SKIP_TEST windows does not support symlinks unless root or in development mode')
+endif
+
+install_data('datafile.dat', install_dir: 'share/progname/C')
+install_symlink('datafile.dat', pointing_to: '../C/datafile.dat', install_dir: 'share/progname/es')
+install_symlink('rename_datafile.dat', pointing_to: '../C/datafile.dat', install_dir: 'share/progname/fr')
diff --git a/test cases/common/247 install_symlink/test.json b/test cases/common/247 install_symlink/test.json
new file mode 100644
index 0000000..33aa76e
--- /dev/null
+++ b/test cases/common/247 install_symlink/test.json
@@ -0,0 +1,7 @@
+{
+ "installed": [
+ {"type": "file", "file": "usr/share/progname/C/datafile.dat"},
+ {"type": "file", "file": "usr/share/progname/es/datafile.dat"},
+ {"type": "file", "file": "usr/share/progname/fr/rename_datafile.dat"}
+ ]
+}
diff --git a/test cases/failing/118 pathsep in install_symlink/meson.build b/test cases/failing/118 pathsep in install_symlink/meson.build
new file mode 100644
index 0000000..cce82c2
--- /dev/null
+++ b/test cases/failing/118 pathsep in install_symlink/meson.build
@@ -0,0 +1,3 @@
+project('symlink_pathsep')
+
+install_symlink('foo/bar', pointing_to: '/usr/baz/bar', install_dir: '/usr')
diff --git a/test cases/failing/118 pathsep in install_symlink/test.json b/test cases/failing/118 pathsep in install_symlink/test.json
new file mode 100644
index 0000000..e3f3a4a
--- /dev/null
+++ b/test cases/failing/118 pathsep in install_symlink/test.json
@@ -0,0 +1,7 @@
+{
+ "stdout": [
+ {
+ "line": "test cases/failing/118 pathsep in install_symlink/meson.build:3:0: ERROR: Link name is \"foo/bar\", but link names cannot contain path separators. The dir part should be in install_dir."
+ }
+ ]
+}