Skip to content

Commit

Permalink
build: Support 'rename' in build_target and custom_target
Browse files Browse the repository at this point in the history
This allows built results to be renamed during installation, just as
with install_data.

This feature is motivated by the need to install multiple files from a
single source directory into different install directories but with
the same basename. In my case, I build dozens of 'libc.a' files which
need to be installed into the toolchain's multilib heirarchy
correctly.

Signed-off-by: Keith Packard <[email protected]>
  • Loading branch information
keith-packard committed Nov 26, 2024
1 parent 9f3f88f commit ecff598
Show file tree
Hide file tree
Showing 12 changed files with 99 additions and 20 deletions.
9 changes: 9 additions & 0 deletions docs/yaml/functions/_build_target_base.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -328,3 +328,12 @@ kwargs:
This allows renaming similar to the dependency renaming feature of cargo
or `extern crate foo as bar` inside rust code.
rename:
type: list[str]
since: 1.6.99
description: |
If specified renames each output into corresponding file from
`rename` list during install. Nested paths are allowed and they
are joined with `install_dir`. Length of `rename` list must be
equal to the number of outputs.
9 changes: 9 additions & 0 deletions docs/yaml/functions/custom_target.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -235,3 +235,12 @@ kwargs:
their input from a file and instead read it from standard input. When this
argument is set to `true`, Meson feeds the input file to `stdin`. Note that
your argument list may not contain `@INPUT@` when feed mode is active.
rename:
type: list[str]
since: 1.6.99
description: |
If specified renames each output into corresponding file from
`rename` list during install. Nested paths are allowed and they
are joined with `install_dir`. Length of `rename` list must be
equal to the number of outputs.
18 changes: 11 additions & 7 deletions mesonbuild/backend/backends.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,11 +142,14 @@ class TargetInstallData:
optional: bool = False
tag: T.Optional[str] = None
can_strip: bool = False
out_fname: T.Optional[str] = None

def __post_init__(self, outdir_name: T.Optional[str]) -> None:
if outdir_name is None:
outdir_name = os.path.join('{prefix}', self.outdir)
self.out_name = os.path.join(outdir_name, os.path.basename(self.fname))
if self.out_fname is None:
self.out_fname = os.path.basename(self.fname)
self.out_name = os.path.join(outdir_name, self.out_fname)

@dataclass(eq=False)
class InstallEmptyDir:
Expand Down Expand Up @@ -1709,6 +1712,7 @@ def generate_target_install(self, d: InstallData) -> None:
if not t.should_install():
continue
outdirs, install_dir_names, custom_install_dir = t.get_install_dir()
rename = t.get_rename()
# Sanity-check the outputs and install_dirs
num_outdirs, num_out = len(outdirs), len(t.get_outputs())
if num_outdirs not in {1, num_out}:
Expand Down Expand Up @@ -1749,7 +1753,7 @@ def generate_target_install(self, d: InstallData) -> None:
first_outdir_name,
should_strip, mappings, t.rpath_dirs_to_remove,
t.install_rpath, install_mode, t.subproject,
tag=tag, can_strip=can_strip)
tag=tag, can_strip=can_strip, out_fname=rename[0])
d.targets.append(i)

for alias, to, tag in t.get_aliases():
Expand Down Expand Up @@ -1787,14 +1791,14 @@ def generate_target_install(self, d: InstallData) -> None:
d.targets.append(i)
# Install secondary outputs. Only used for Vala right now.
if num_outdirs > 1:
for output, outdir, outdir_name, tag in zip(t.get_outputs()[1:], outdirs[1:], install_dir_names[1:], t.install_tag[1:]):
for output, outdir, outdir_name, tag, out_fname in zip(t.get_outputs()[1:], outdirs[1:], install_dir_names[1:], t.install_tag[1:], rename[1:]):
# User requested that we not install this output
if outdir is False:
continue
f = os.path.join(self.get_target_dir(t), output)
i = TargetInstallData(f, outdir, outdir_name, False, {}, set(), None,
install_mode, t.subproject,
tag=tag)
tag=tag, out_fname=out_fname)
d.targets.append(i)
elif isinstance(t, build.CustomTarget):
# If only one install_dir is specified, assume that all
Expand All @@ -1815,10 +1819,10 @@ def generate_target_install(self, d: InstallData) -> None:
i = TargetInstallData(f, first_outdir, first_outdir_name,
False, {}, set(), None, install_mode,
t.subproject, optional=not t.build_by_default,
tag=tag)
tag=tag, out_fname=rename[0])
d.targets.append(i)
else:
for output, outdir, outdir_name, tag in zip(t.get_outputs(), outdirs, install_dir_names, t.install_tag):
for output, outdir, outdir_name, tag, out_fname in zip(t.get_outputs(), outdirs, install_dir_names, t.install_tag, rename):
# User requested that we not install this output
if outdir is False:
continue
Expand All @@ -1827,7 +1831,7 @@ def generate_target_install(self, d: InstallData) -> None:
i = TargetInstallData(f, outdir, outdir_name,
False, {}, set(), None, install_mode,
t.subproject, optional=not t.build_by_default,
tag=tag)
tag=tag, out_fname=out_fname)
d.targets.append(i)

def generate_custom_install_script(self, d: InstallData) -> None:
Expand Down
14 changes: 14 additions & 0 deletions mesonbuild/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ class DFeatures(TypedDict):
'native',
'objects',
'override_options',
'rename',
'sources',
'gnu_symbol_visibility',
'link_language',
Expand Down Expand Up @@ -1127,6 +1128,11 @@ def get_custom_install_dir(self) -> T.List[T.Union[str, Literal[False]]]:
def get_custom_install_mode(self) -> T.Optional['FileMode']:
return self.install_mode

def get_rename(self) -> T.List[str]:
if self.rename:
return self.rename
return [None] * len(self.outputs)

def process_kwargs(self, kwargs):
self.process_kwargs_base(kwargs)
self.original_kwargs = kwargs
Expand Down Expand Up @@ -1164,6 +1170,7 @@ def process_kwargs(self, kwargs):
(str, bool))
self.install_mode = kwargs.get('install_mode', None)
self.install_tag = stringlistify(kwargs.get('install_tag', [None]))
self.rename = kwargs.get('rename', None)
if not isinstance(self, Executable):
# build_target will always populate these as `None`, which is fine
if kwargs.get('gui_app') is not None:
Expand Down Expand Up @@ -2621,6 +2628,7 @@ def __init__(self,
feed: bool = False,
install: bool = False,
install_dir: T.Optional[T.List[T.Union[str, Literal[False]]]] = None,
rename: T.Optional[T.List[str]] = None,
install_mode: T.Optional[FileMode] = None,
install_tag: T.Optional[T.List[T.Optional[str]]] = None,
absolute_paths: bool = False,
Expand All @@ -2647,6 +2655,7 @@ def __init__(self,
self.extra_depends = list(extra_depends or [])
self.feed = feed
self.install_dir = list(install_dir or [])
self.rename = rename
self.install_mode = install_mode
self.install_tag = _process_install_tag(install_tag, len(self.outputs))
self.name = name if name else self.outputs[0]
Expand Down Expand Up @@ -2766,6 +2775,11 @@ def get_link_dep_subdirs(self) -> T.AbstractSet[str]:
def get_all_link_deps(self):
return []

def get_rename(self) -> T.List[str]:
if self.rename:
return self.rename
return [None] * len(self.outputs)

def is_internal(self) -> bool:
'''
Returns True if this is a not installed static library.
Expand Down
13 changes: 12 additions & 1 deletion mesonbuild/interpreter/interpreter.py
Original file line number Diff line number Diff line change
Expand Up @@ -2056,6 +2056,7 @@ def _validate_custom_target_outputs(self, has_multi_in: bool, outputs: T.Iterabl
INSTALL_MODE_KW.evolve(since='0.47.0'),
KwargInfo('feed', bool, default=False, since='0.59.0'),
KwargInfo('capture', bool, default=False),
KwargInfo('rename', ContainerTypeInfo(list, str), default=[], listify=True, since='1.6.0'),
KwargInfo('console', bool, default=False, since='0.48.0'),
)
def func_custom_target(self, node: mparser.FunctionNode, args: T.Tuple[str],
Expand Down Expand Up @@ -2121,7 +2122,11 @@ def func_custom_target(self, node: mparser.FunctionNode, args: T.Tuple[str],
'or the same number of elements as the output keyword argument. '
f'(there are {len(kwargs["install_tag"])} install_tags, '
f'and {len(kwargs["output"])} outputs)')

if len(kwargs['rename']) not in {0, 1, len(kwargs['output'])}:
raise InvalidArguments('custom_target: rename argument must have 0 or 1 outputs, '
'or the same number of elements as the output keyword argument. '
f'(there are {len(kwargs["rename"])} rename, '
f'and {len(kwargs["output"])} outputs)')
for t in kwargs['output']:
self.validate_forbidden_targets(t)
self._validate_custom_target_outputs(len(inputs) > 1, kwargs['output'], "custom_target")
Expand All @@ -2147,6 +2152,7 @@ def func_custom_target(self, node: mparser.FunctionNode, args: T.Tuple[str],
install_dir=kwargs['install_dir'],
install_mode=install_mode,
install_tag=kwargs['install_tag'],
rename=kwargs['rename'],
backend=self.backend)
self.add_target(tg.name, tg)
return tg
Expand Down Expand Up @@ -3482,6 +3488,11 @@ def build_target(self, node: mparser.BaseNode, args: T.Tuple[str, SourcesVarargs
target = targetclass(name, self.subdir, self.subproject, for_machine, srcs, struct, objs,
self.environment, self.compilers[for_machine], kwargs)

if len(kwargs['rename']) not in {0, 1, len(target.outputs)}:
raise InvalidArguments('build_target: rename argument must have 0 or 1 outputs, '
'or same as the number of outputs. '
f'(there are {len(kwargs["rename"])} rename, '
f'and {len(target.outputs)} outputs)')
self.add_target(name, target)
self.project_args_frozen = True
return target
Expand Down
7 changes: 7 additions & 0 deletions mesonbuild/interpreter/type_checking.py
Original file line number Diff line number Diff line change
Expand Up @@ -584,6 +584,13 @@ def _objects_validator(vals: T.List[ObjectTypes]) -> T.Optional[str]:
('1.1.0', 'generated sources as positional "objects" arguments')
},
),
KwargInfo(
'rename',
ContainerTypeInfo(list, str),
listify=True,
default=[],
since='1.6.99',
),
]


Expand Down
22 changes: 13 additions & 9 deletions mesonbuild/minstall.py
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,10 @@ def do_copyfile(self, from_file: str, to_file: str,
makedirs: T.Optional[T.Tuple[T.Any, str]] = None,
follow_symlinks: T.Optional[bool] = None) -> bool:
outdir = os.path.split(to_file)[0]
if os.path.basename(from_file) == os.path.basename(to_file):
outpath = outdir
else:
outpath = to_file
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}')
# copyfile fails if the target file already exists, so remove it to
Expand All @@ -405,10 +409,10 @@ def do_copyfile(self, from_file: str, to_file: str,
append_to_log(self.lf, f'# Preserving old file {to_file}\n')
self.preserved_file_count += 1
return False
self.log(f'Installing {from_file} to {outdir}')
self.log(f'Installing {from_file} to {outpath}')
self.remove(to_file)
else:
self.log(f'Installing {from_file} to {outdir}')
self.log(f'Installing {from_file} to {outpath}')
if makedirs:
# Unpack tuple
dirmaker, outdir = makedirs
Expand Down Expand Up @@ -734,20 +738,21 @@ def install_targets(self, d: InstallData, dm: DirMaker, destdir: str, fullprefix
raise MesonException(f'File {t.fname!r} could not be found')
file_copied = False # not set when a directory is copied
fname = check_for_stampfile(t.fname)
out_fname = t.out_fname
outdir = get_destdir_path(destdir, fullprefix, t.outdir)
outname = os.path.join(outdir, os.path.basename(fname))
final_path = os.path.join(d.prefix, t.outdir, os.path.basename(fname))
outname = os.path.join(outdir, out_fname)
final_path = os.path.join(d.prefix, t.outdir, out_fname)
should_strip = t.strip or (t.can_strip and self.options.strip)
install_rpath = t.install_rpath
install_name_mappings = t.install_name_mappings
install_mode = t.install_mode
if not os.path.exists(fname):
raise MesonException(f'File {fname!r} could not be found')
elif os.path.isfile(fname):
file_copied = self.do_copyfile(fname, outname, makedirs=(dm, outdir))
file_copied = self.do_copyfile(fname, outname, makedirs=(dm, os.path.dirname(outname)))
if should_strip and d.strip_bin is not None:
if fname.endswith('.jar'):
self.log('Not stripping jar target: {}'.format(os.path.basename(fname)))
if out_fname.endswith('.jar'):
self.log('Not stripping jar target: {}'.format(os.path.basename(out_fname)))
continue
self.do_strip(d.strip_bin, fname, outname)
if fname.endswith('.js'):
Expand All @@ -759,8 +764,7 @@ def install_targets(self, d: InstallData, dm: DirMaker, destdir: str, fullprefix
file_copied = self.do_copyfile(wasm_source, wasm_output)
elif os.path.isdir(fname):
fname = os.path.join(d.build_dir, fname.rstrip('/'))
outname = os.path.join(outdir, os.path.basename(fname))
dm.makedirs(outdir, exist_ok=True)
dm.makedirs(os.path.dirname(outname), exist_ok=True)
self.do_copydir(d, fname, outname, None, install_mode, dm)
else:
raise RuntimeError(f'Unknown file type for {fname!r}')
Expand Down
4 changes: 2 additions & 2 deletions mesonbuild/mintro.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ def list_installed(installdata: backends.InstallData) -> T.Dict[str, str]:
if installdata is not None:
for t in installdata.targets:
res[os.path.join(installdata.build_dir, t.fname)] = \
os.path.join(installdata.prefix, t.outdir, os.path.basename(t.fname))
os.path.join(installdata.prefix, t.out_name)
for i in installdata.data:
res[i.path] = os.path.join(installdata.prefix, i.install_path)
for i in installdata.headers:
Expand Down Expand Up @@ -233,7 +233,7 @@ def list_targets(builddata: build.Build, installdata: backends.InstallData, back
install_lookuptable = {}
for i in installdata.targets:
basename = os.path.basename(i.fname)
install_lookuptable[basename] = [str(PurePath(installdata.prefix, i.outdir, basename))]
install_lookuptable[basename] = [str(PurePath(installdata.prefix, i.out_name))]
for s in installdata.symlinks:
# Symlink's target must already be in the table. They share the same list
# to support symlinks to symlinks recursively, such as .so -> .so.0 -> .so.1.2.3
Expand Down
12 changes: 12 additions & 0 deletions test cases/common/109 custom target capture/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,15 @@ if not os.path.exists(sys.argv[1]):
'''

test('capture-wrote', python3, args : ['-c', ct_output_exists, mytarget])

mytarget2 = custom_target('bindat2',
output : 'data2.dat',
input : 'data_source.txt',
rename : 'subdir2/data.dat',
capture : true,
command : [python3, comp, '@INPUT@'],
install : true,
install_dir : 'subdir'
)

test('capture-wrote2', python3, args : ['-c', ct_output_exists, mytarget2])
3 changes: 2 additions & 1 deletion test cases/common/109 custom target capture/test.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"installed": [
{"type": "file", "file": "usr/subdir/data.dat"}
{"type": "file", "file": "usr/subdir/data.dat"},
{"type": "file", "file": "usr/subdir/subdir2/data.dat"}
]
}
7 changes: 7 additions & 0 deletions test cases/common/117 shared module/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,13 @@ test('import test', e, args : m)
m2 = build_target('mymodule2', 'module.c', target_type: 'shared_module')
test('import test 2', e, args : m2)

# Same as above, but installed into a sub directory
m3 = shared_module('mymodule3', 'module.c',
install : true,
install_dir : join_paths(get_option('libdir'), 'modules'),
rename: 'subdir/libmymodule.so')
test('import test 3', e, args : m3)

# Shared module that does not export any symbols
shared_module('nosyms', 'nosyms.c',
override_options: ['werror=false'],
Expand Down
1 change: 1 addition & 0 deletions test cases/common/117 shared module/test.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"installed": [
{"type": "expr", "file": "usr/lib/modules/libnosyms?so"},
{"type": "expr", "file": "usr/lib/modules/subdir/libmymodule.so"},
{"type": "implibempty", "file": "usr/lib/modules/libnosyms"},
{"type": "pdb", "file": "usr/lib/modules/nosyms"}
]
Expand Down

0 comments on commit ecff598

Please sign in to comment.