aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEli Schwartz <eschwartz@archlinux.org>2021-10-03 14:35:30 -0400
committerEli Schwartz <eschwartz@archlinux.org>2021-10-08 14:35:00 -0400
commit108bd996ee72b16c14f0ee0ca86959b142582394 (patch)
tree2cc138363c7139ce3fa2ae918e75ac5b81d19c77
parent54e17ad5975252242712dc3b613bdbd0a2504e23 (diff)
downloadmeson-108bd996ee72b16c14f0ee0ca86959b142582394.zip
meson-108bd996ee72b16c14f0ee0ca86959b142582394.tar.gz
meson-108bd996ee72b16c14f0ee0ca86959b142582394.tar.bz2
add install_emptydir function
This replaces the absolute hack of using ``` install_subdir('nonexisting', install_dir: 'share') ``` which requires you to make sure you don't accidentally or deliberately have a completely different directory with the same name in your source tree that is full of files you don't want installed. It also avoids splitting the name in two and listing them in the wrong order. You can also set the install mode of each directory component by listing them one at a time in order, and in fact create nested structures at all. Fixes #1604 Properly fixes #2904
-rw-r--r--data/syntax-highlighting/vim/syntax/meson.vim1
-rw-r--r--docs/markdown/snippets/install_emptydir.md18
-rw-r--r--docs/yaml/functions/install_emptydir.yaml27
-rw-r--r--docs/yaml/functions/install_subdir.yaml9
-rw-r--r--mesonbuild/ast/interpreter.py1
-rw-r--r--mesonbuild/backend/backends.py15
-rw-r--r--mesonbuild/build.py14
-rw-r--r--mesonbuild/interpreter/interpreter.py13
-rw-r--r--mesonbuild/interpreter/interpreterobjects.py3
-rw-r--r--mesonbuild/minstall.py18
-rw-r--r--test cases/common/246 install_emptydir/meson.build4
-rw-r--r--test cases/common/246 install_emptydir/test.json7
12 files changed, 125 insertions, 5 deletions
diff --git a/data/syntax-highlighting/vim/syntax/meson.vim b/data/syntax-highlighting/vim/syntax/meson.vim
index 15f5232..3a6a7de 100644
--- a/data/syntax-highlighting/vim/syntax/meson.vim
+++ b/data/syntax-highlighting/vim/syntax/meson.vim
@@ -97,6 +97,7 @@ syn keyword mesonBuiltin
\ install_headers
\ install_man
\ install_subdir
+ \ install_emptydir
\ is_disabler
\ is_variable
\ jar
diff --git a/docs/markdown/snippets/install_emptydir.md b/docs/markdown/snippets/install_emptydir.md
new file mode 100644
index 0000000..baedf58
--- /dev/null
+++ b/docs/markdown/snippets/install_emptydir.md
@@ -0,0 +1,18 @@
+## install_emptydir function
+
+It is now possible to define a directory which will be created during
+installation, without creating it as a side effect of installing files into it.
+This replaces custom `meson.add_install_script()` routines. For example:
+
+```meson
+meson.add_install_script('sh', '-c', 'mkdir -p "$DESTDIR/@0@"'.format(path))
+```
+
+can be replaced by:
+
+```meson
+install_emptydir(path)
+```
+
+and as a bonus this works reliably on Windows, prints a sensible progress
+message, will be uninstalled by `ninja uninstall`, etc.
diff --git a/docs/yaml/functions/install_emptydir.yaml b/docs/yaml/functions/install_emptydir.yaml
new file mode 100644
index 0000000..9c874e2
--- /dev/null
+++ b/docs/yaml/functions/install_emptydir.yaml
@@ -0,0 +1,27 @@
+name: install_emptydir
+returns: void
+since: 0.60.0
+description: |
+ Installs a new directory entry to the location specified by the positional
+ argument. If the directory exists and is not empty, the contents are left in
+ place.
+
+varargs:
+ name: dirpath
+ type: str
+ description: Directory to create during installation.
+
+kwargs:
+ install_mode:
+ type: list[str | int]
+ description: |
+ Specify the file mode in symbolic format and optionally the owner/uid and
+ group/gid for the created directory.
+
+ See the `install_mode` kwarg of [[install_data]] for more information.
+ install_tag:
+ type: str
+ description: |
+ A string used by the `meson install --tags` command to install only a
+ subset of the files. By default this directory has no install tag which
+ means it is not installed when the `--tags` argument is specified.
diff --git a/docs/yaml/functions/install_subdir.yaml b/docs/yaml/functions/install_subdir.yaml
index 83df782..90baed2 100644
--- a/docs/yaml/functions/install_subdir.yaml
+++ b/docs/yaml/functions/install_subdir.yaml
@@ -5,9 +5,12 @@ description: |
source tree to the location specified by the keyword argument
`install_dir`.
- If the subdirectory does not exist in the source tree, an empty directory is
- created in the specified location. *(since 0.45.0)* A newly created
- subdirectory may only be created in the keyword argument `install_dir`.
+ *(since 0.45.0, deprecated since 0.60.0)* If the subdirectory does not exist
+ in the source tree, an empty directory is created in the specified location.
+ A newly created subdirectory may only be created in the keyword argument
+ `install_dir`. There are a number of flaws with this method, and it was never
+ intentionally designed to work this way, please use [[install_emptydir]]
+ instead.
example: |
For a given directory `foo`:
diff --git a/mesonbuild/ast/interpreter.py b/mesonbuild/ast/interpreter.py
index 7954b9b..ec93ff5 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_emptydir': self.func_do_nothing,
'configuration_data': self.func_do_nothing,
'configure_file': self.func_do_nothing,
'find_program': self.func_do_nothing,
diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py
index 6218156..3d8654e 100644
--- a/mesonbuild/backend/backends.py
+++ b/mesonbuild/backend/backends.py
@@ -120,6 +120,7 @@ class InstallData:
self.targets: T.List[TargetInstallData] = []
self.headers: T.List[InstallDataBase] = []
self.man: T.List[InstallDataBase] = []
+ self.emptydir: T.List[InstallEmptyDir] = []
self.data: T.List[InstallDataBase] = []
self.install_scripts: T.List[ExecutableSerialisation] = []
self.install_subdirs: T.List[SubdirInstallData] = []
@@ -147,6 +148,13 @@ class TargetInstallData:
self.optional = optional
self.tag = tag
+class InstallEmptyDir:
+ def __init__(self, path: str, install_mode: 'FileMode', subproject: str, tag: T.Optional[str] = None):
+ self.path = path
+ self.install_mode = install_mode
+ self.subproject = subproject
+ self.tag = tag
+
class InstallDataBase:
def __init__(self, path: str, install_path: str, install_path_name: str,
install_mode: 'FileMode', subproject: str, tag: T.Optional[str] = None,
@@ -1470,6 +1478,7 @@ class Backend:
self.generate_target_install(d)
self.generate_header_install(d)
self.generate_man_install(d)
+ self.generate_emptydir_install(d)
self.generate_data_install(d)
self.generate_custom_install_script(d)
self.generate_subdir_install(d)
@@ -1665,6 +1674,12 @@ class Backend:
i = InstallDataBase(srcabs, dstabs, dstname, m.get_custom_install_mode(), m.subproject, tag='man')
d.man.append(i)
+ def generate_emptydir_install(self, d: InstallData) -> None:
+ emptydir: T.List[build.EmptyDir] = self.build.get_emptydir()
+ for e in emptydir:
+ i = InstallEmptyDir(e.path, e.install_mode, e.subproject, e.install_tag)
+ d.emptydir.append(i)
+
def generate_data_install(self, d: InstallData) -> None:
data = self.build.get_data()
srcdir = self.environment.get_source_dir()
diff --git a/mesonbuild/build.py b/mesonbuild/build.py
index 311732e..ef2210e 100644
--- a/mesonbuild/build.py
+++ b/mesonbuild/build.py
@@ -187,6 +187,16 @@ class Man(HoldableObject):
return self.sources
+class EmptyDir(HoldableObject):
+
+ def __init__(self, path: str, install_mode: 'FileMode', subproject: str,
+ install_tag: T.Optional[str] = None):
+ self.path = path
+ self.install_mode = install_mode
+ self.subproject = subproject
+ self.install_tag = install_tag
+
+
class InstallDir(HoldableObject):
def __init__(self, src_subdir: str, inst_subdir: str, install_dir: str,
@@ -239,6 +249,7 @@ class Build:
self.benchmarks: T.List['Test'] = []
self.headers: T.List[Headers] = []
self.man: T.List[Man] = []
+ self.emptydir: T.List[EmptyDir] = []
self.data: T.List[Data] = []
self.static_linker: PerMachine[StaticLinker] = PerMachine(None, None)
self.subprojects = {}
@@ -316,6 +327,9 @@ class Build:
def get_data(self) -> T.List['Data']:
return self.data
+ def get_emptydir(self) -> T.List['EmptyDir']:
+ return self.emptydir
+
def get_install_subdirs(self) -> T.List['InstallDir']:
return self.install_dirs
diff --git a/mesonbuild/interpreter/interpreter.py b/mesonbuild/interpreter/interpreter.py
index 9a89b0c..8ac92c8 100644
--- a/mesonbuild/interpreter/interpreter.py
+++ b/mesonbuild/interpreter/interpreter.py
@@ -349,6 +349,7 @@ class Interpreter(InterpreterBase, HoldableObject):
'install_data': self.func_install_data,
'install_headers': self.func_install_headers,
'install_man': self.func_install_man,
+ 'install_emptydir': self.func_install_emptydir,
'install_subdir': self.func_install_subdir,
'is_disabler': self.func_is_disabler,
'is_variable': self.func_is_variable,
@@ -410,6 +411,7 @@ class Interpreter(InterpreterBase, HoldableObject):
build.AliasTarget: OBJ.AliasTargetHolder,
build.Headers: OBJ.HeadersHolder,
build.Man: OBJ.ManHolder,
+ build.EmptyDir: OBJ.EmptyDirHolder,
build.Data: OBJ.DataHolder,
build.InstallDir: OBJ.InstallDirHolder,
build.IncludeDirs: OBJ.IncludeDirsHolder,
@@ -1896,6 +1898,17 @@ This will become a hard error in the future.''' % kwargs['input'], location=self
return m
+ @FeatureNew('install_emptydir', '0.60.0')
+ @typed_kwargs(
+ 'install_emptydir',
+ INSTALL_MODE_KW
+ )
+ def func_install_emptydir(self, node: mparser.BaseNode, args: T.Tuple[str], kwargs) -> None:
+ d = build.EmptyDir(args[0], kwargs['install_mode'], self.subproject)
+ self.build.emptydir.append(d)
+
+ return d
+
@FeatureNewKwargs('subdir', '0.44.0', ['if_found'])
@permittedKwargs({'if_found'})
@typed_pos_args('subdir', str)
diff --git a/mesonbuild/interpreter/interpreterobjects.py b/mesonbuild/interpreter/interpreterobjects.py
index 072d2f0..b0447e6 100644
--- a/mesonbuild/interpreter/interpreterobjects.py
+++ b/mesonbuild/interpreter/interpreterobjects.py
@@ -646,6 +646,9 @@ class InstallDirHolder(ObjectHolder[build.InstallDir]):
class ManHolder(ObjectHolder[build.Man]):
pass
+class EmptyDirHolder(ObjectHolder[build.EmptyDir]):
+ pass
+
class GeneratedObjectsHolder(ObjectHolder[build.ExtractedObjects]):
pass
diff --git a/mesonbuild/minstall.py b/mesonbuild/minstall.py
index 6284f95..7864742 100644
--- a/mesonbuild/minstall.py
+++ b/mesonbuild/minstall.py
@@ -25,7 +25,7 @@ import sys
import typing as T
from . import environment
-from .backend.backends import InstallData, InstallDataBase, TargetInstallData, ExecutableSerialisation
+from .backend.backends import InstallData, InstallDataBase, InstallEmptyDir, TargetInstallData, ExecutableSerialisation
from .coredata import major_versions_differ, MesonVersionMismatchException
from .coredata import version as coredata_version
from .mesonlib import Popen_safe, RealPathAction, is_windows
@@ -370,7 +370,7 @@ class Installer:
return run_exe(*args, **kwargs)
return 0
- def should_install(self, d: T.Union[TargetInstallData, InstallDataBase, ExecutableSerialisation]) -> bool:
+ def should_install(self, d: T.Union[TargetInstallData, InstallEmptyDir, InstallDataBase, 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:
@@ -531,6 +531,7 @@ class Installer:
self.install_targets(d, dm, destdir, fullprefix)
self.install_headers(d, dm, destdir, fullprefix)
self.install_man(d, dm, destdir, fullprefix)
+ self.install_emptydir(d, dm, destdir, fullprefix)
self.install_data(d, dm, destdir, fullprefix)
self.restore_selinux_contexts(destdir)
self.apply_ldconfig(destdir)
@@ -581,6 +582,19 @@ class Installer:
self.did_install_something = True
self.set_mode(outfilename, m.install_mode, d.install_umask)
+ def install_emptydir(self, d: InstallData, dm: DirMaker, destdir: str, fullprefix: str) -> None:
+ for e in d.emptydir:
+ if not self.should_install(e):
+ continue
+ self.did_install_something = True
+ full_dst_dir = get_destdir_path(destdir, fullprefix, e.path)
+ self.log(f'Installing new directory {full_dst_dir}')
+ if os.path.isfile(full_dst_dir):
+ print(f'Tried to create directory {full_dst_dir} but a file of that name already exists.')
+ sys.exit(1)
+ dm.makedirs(full_dst_dir, exist_ok=True)
+ self.set_mode(full_dst_dir, e.install_mode, d.install_umask)
+
def install_headers(self, d: InstallData, dm: DirMaker, destdir: str, fullprefix: str) -> None:
for t in d.headers:
if not self.should_install(t):
diff --git a/test cases/common/246 install_emptydir/meson.build b/test cases/common/246 install_emptydir/meson.build
new file mode 100644
index 0000000..a5eb046
--- /dev/null
+++ b/test cases/common/246 install_emptydir/meson.build
@@ -0,0 +1,4 @@
+project('install_emptydir')
+
+install_emptydir(get_option('datadir')/'new_directory', install_mode: 'rwx------')
+install_emptydir(get_option('datadir')/'new_directory/subdir', install_mode: 'rwxr-----')
diff --git a/test cases/common/246 install_emptydir/test.json b/test cases/common/246 install_emptydir/test.json
new file mode 100644
index 0000000..17abe74
--- /dev/null
+++ b/test cases/common/246 install_emptydir/test.json
@@ -0,0 +1,7 @@
+{
+ "installed": [
+ { "type": "dir", "file": "usr/share/new_directory" },
+ { "type": "dir", "file": "usr/share/new_directory/subdir" }
+ ]
+}
+