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
pull/9344/head
Eli Schwartz 3 years ago
parent 54e17ad597
commit 108bd996ee
No known key found for this signature in database
GPG Key ID: CEB167EFB5722BD6
  1. 1
      data/syntax-highlighting/vim/syntax/meson.vim
  2. 18
      docs/markdown/snippets/install_emptydir.md
  3. 27
      docs/yaml/functions/install_emptydir.yaml
  4. 9
      docs/yaml/functions/install_subdir.yaml
  5. 1
      mesonbuild/ast/interpreter.py
  6. 15
      mesonbuild/backend/backends.py
  7. 14
      mesonbuild/build.py
  8. 13
      mesonbuild/interpreter/interpreter.py
  9. 3
      mesonbuild/interpreter/interpreterobjects.py
  10. 18
      mesonbuild/minstall.py
  11. 4
      test cases/common/246 install_emptydir/meson.build
  12. 7
      test cases/common/246 install_emptydir/test.json

@ -97,6 +97,7 @@ syn keyword mesonBuiltin
\ install_headers
\ install_man
\ install_subdir
\ install_emptydir
\ is_disabler
\ is_variable
\ jar

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

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

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

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

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

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

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

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

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

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

@ -0,0 +1,7 @@
{
"installed": [
{ "type": "dir", "file": "usr/share/new_directory" },
{ "type": "dir", "file": "usr/share/new_directory/subdir" }
]
}
Loading…
Cancel
Save