From 01e92dc543fb7443fba8a33687ffd726f11433e8 Mon Sep 17 00:00:00 2001 From: Xavier Claessens Date: Wed, 12 Jan 2022 11:47:57 -0500 Subject: [PATCH] Fix default install tag for shared lib symlinks Versioned shared libraries should have .so file in devel, .so.1 and .so.1.2.3 in runtime. Fixes: #9811 --- mesonbuild/backend/backends.py | 19 ++++++++++------- mesonbuild/backend/ninjabackend.py | 3 +-- mesonbuild/build.py | 14 +++++++------ mesonbuild/minstall.py | 10 +++------ mesonbuild/mintro.py | 20 ++++++++++++------ .../unit/99 install all targets/meson.build | 6 ++++++ unittests/allplatformstests.py | 21 +++++++++++++++++++ 7 files changed, 65 insertions(+), 28 deletions(-) diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py index dae9c4702..b98eb0817 100644 --- a/mesonbuild/backend/backends.py +++ b/mesonbuild/backend/backends.py @@ -135,7 +135,6 @@ class TargetInstallData: fname: str outdir: str outdir_name: InitVar[str] - aliases: T.Dict[str, str] strip: bool install_name_mappings: T.Mapping[str, str] rpath_dirs_to_remove: T.Set[bytes] @@ -173,6 +172,7 @@ class InstallSymlinkData: install_path: str subproject: str tag: T.Optional[str] = None + allow_missing: bool = False # cannot use dataclass here because "exclude" is out of order class SubdirInstallData(InstallDataBase): @@ -1581,12 +1581,17 @@ class Backend: tag = t.install_tag[0] or ('devel' if isinstance(t, build.StaticLibrary) else 'runtime') mappings = t.get_link_deps_mapping(d.prefix, self.environment) i = TargetInstallData(self.get_target_filename(t), outdirs[0], - install_dir_name, t.get_aliases(), + install_dir_name, should_strip, mappings, t.rpath_dirs_to_remove, t.install_rpath, install_mode, t.subproject, tag=tag) d.targets.append(i) + for alias, to, tag in t.get_aliases(): + alias = os.path.join(outdirs[0], alias) + s = InstallSymlinkData(to, alias, outdirs[0], t.subproject, tag, allow_missing=True) + d.symlinks.append(s) + if isinstance(t, (build.SharedLibrary, build.SharedModule, build.Executable)): # On toolchains/platforms that use an import library for # linking (separate from the shared library with all the @@ -1602,7 +1607,7 @@ class Backend: # Install the import library; may not exist for shared modules i = TargetInstallData(self.get_target_filename_for_linking(t), implib_install_dir, install_dir_name, - {}, False, {}, set(), '', install_mode, + False, {}, set(), '', install_mode, t.subproject, optional=isinstance(t, build.SharedModule), tag='devel') d.targets.append(i) @@ -1611,7 +1616,7 @@ class Backend: debug_file = os.path.join(self.get_target_dir(t), t.get_debug_filename()) i = TargetInstallData(debug_file, outdirs[0], install_dir_name, - {}, False, {}, set(), '', + False, {}, set(), '', install_mode, t.subproject, optional=True, tag='devel') d.targets.append(i) @@ -1622,7 +1627,7 @@ class Backend: if outdir is False: continue f = os.path.join(self.get_target_dir(t), output) - i = TargetInstallData(f, outdir, install_dir_name, {}, False, {}, set(), None, + i = TargetInstallData(f, outdir, install_dir_name, False, {}, set(), None, install_mode, t.subproject, tag=tag) d.targets.append(i) @@ -1639,7 +1644,7 @@ class Backend: f = os.path.join(self.get_target_dir(t), output) if not install_dir_name: dir_name = os.path.join('{prefix}', outdirs[0]) - i = TargetInstallData(f, outdirs[0], dir_name, {}, + i = TargetInstallData(f, outdirs[0], dir_name, False, {}, set(), None, install_mode, t.subproject, optional=not t.build_by_default, tag=tag) @@ -1653,7 +1658,7 @@ class Backend: if not install_dir_name: dir_name = os.path.join('{prefix}', outdir) i = TargetInstallData(f, outdir, dir_name, - {}, False, {}, set(), None, install_mode, + False, {}, set(), None, install_mode, t.subproject, optional=not t.build_by_default, tag=tag) d.targets.append(i) diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index 560fe5335..a66111b01 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -3092,8 +3092,7 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485''')) return self.get_target_filename(t) def generate_shlib_aliases(self, target, outdir): - aliases = target.get_aliases() - for alias, to in aliases.items(): + for alias, to, tag in target.get_aliases(): aliasfile = os.path.join(self.environment.get_build_dir(), outdir, alias) try: os.remove(aliasfile) diff --git a/mesonbuild/build.py b/mesonbuild/build.py index bb57ec854..e0f07ab9e 100644 --- a/mesonbuild/build.py +++ b/mesonbuild/build.py @@ -1471,8 +1471,8 @@ You probably should put it in link_with instead.''') else: self.extra_args[language] = args - def get_aliases(self) -> T.Dict[str, str]: - return {} + def get_aliases(self) -> T.List[T.Tuple[str, str, str]]: + return [] def get_langs_used_by_deps(self) -> T.List[str]: ''' @@ -2240,7 +2240,7 @@ class SharedLibrary(BuildTarget): def get_all_link_deps(self): return [self] + self.get_transitive_link_deps() - def get_aliases(self) -> T.Dict[str, str]: + def get_aliases(self) -> T.List[T.Tuple[str, str, str]]: """ If the versioned library name is libfoo.so.0.100.0, aliases are: * libfoo.so.0 (soversion) -> libfoo.so.0.100.0 @@ -2248,7 +2248,7 @@ class SharedLibrary(BuildTarget): Same for dylib: * libfoo.dylib (unversioned; for linking) -> libfoo.0.dylib """ - aliases: T.Dict[str, str] = {} + aliases: T.List[T.Tuple[str, str, str]] = [] # Aliases are only useful with .so and .dylib libraries. Also if # there's no self.soversion (no versioning), we don't need aliases. if self.suffix not in ('so', 'dylib') or not self.soversion: @@ -2260,14 +2260,16 @@ class SharedLibrary(BuildTarget): if self.suffix == 'so' and self.ltversion and self.ltversion != self.soversion: alias_tpl = self.filename_tpl.replace('ltversion', 'soversion') ltversion_filename = alias_tpl.format(self) - aliases[ltversion_filename] = self.filename + tag = self.install_tag[0] or 'runtime' + aliases.append((ltversion_filename, self.filename, tag)) # libfoo.so.0/libfoo.0.dylib is the actual library else: ltversion_filename = self.filename # Unversioned alias: # libfoo.so -> libfoo.so.0 # libfoo.dylib -> libfoo.0.dylib - aliases[self.basic_filename_tpl.format(self)] = ltversion_filename + tag = self.install_tag[0] or 'devel' + aliases.append((self.basic_filename_tpl.format(self), ltversion_filename, tag)) return aliases def type_suffix(self): diff --git a/mesonbuild/minstall.py b/mesonbuild/minstall.py index 80b023971..b51d4f0db 100644 --- a/mesonbuild/minstall.py +++ b/mesonbuild/minstall.py @@ -428,11 +428,11 @@ class Installer: append_to_log(self.lf, to_file) return True - def do_symlink(self, target: str, link: str, full_dst_dir: str) -> bool: + def do_symlink(self, target: str, link: str, full_dst_dir: str, allow_missing: bool) -> 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): + if not os.path.exists(abs_target) and not allow_missing: raise MesonException(f'Tried to install symlink to missing file {abs_target}') if os.path.exists(link): if not os.path.islink(link): @@ -592,7 +592,7 @@ class Installer: 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): + if self.do_symlink(s.target, full_link_name, full_dst_dir, s.allow_missing): self.did_install_something = True def install_man(self, d: InstallData, dm: DirMaker, destdir: str, fullprefix: str) -> None: @@ -676,7 +676,6 @@ class Installer: 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)) - aliases = t.aliases should_strip = t.strip install_rpath = t.install_rpath install_name_mappings = t.install_name_mappings @@ -711,9 +710,6 @@ class Installer: self.do_copydir(d, fname, outname, None, install_mode, dm) else: raise RuntimeError(f'Unknown file type for {fname!r}') - 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/mesonbuild/mintro.py b/mesonbuild/mintro.py index 130cf9313..e3a613f59 100644 --- a/mesonbuild/mintro.py +++ b/mesonbuild/mintro.py @@ -107,9 +107,6 @@ def list_installed(installdata: backends.InstallData) -> T.Dict[str, str]: 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)) - for alias in t.aliases.keys(): - res[os.path.join(installdata.build_dir, alias)] = \ - os.path.join(installdata.prefix, t.outdir, os.path.basename(alias)) for i in installdata.data: res[i.path] = os.path.join(installdata.prefix, i.install_path) for i in installdata.headers: @@ -118,6 +115,9 @@ def list_installed(installdata: backends.InstallData) -> T.Dict[str, str]: res[i.path] = os.path.join(installdata.prefix, i.install_path) for i in installdata.install_subdirs: res[i.path] = os.path.join(installdata.prefix, i.install_path) + for s in installdata.symlinks: + basename = os.path.basename(s.name) + res[basename] = os.path.join(installdata.prefix, s.install_path, basename) return res def list_install_plan(installdata: backends.InstallData) -> T.Dict[str, T.Dict[str, T.Dict[str, T.Optional[str]]]]: @@ -216,9 +216,17 @@ def list_targets(builddata: build.Build, installdata: backends.InstallData, back # Fast lookup table for installation files install_lookuptable = {} for i in installdata.targets: - out = [os.path.join(installdata.prefix, i.outdir, os.path.basename(i.fname))] - out += [os.path.join(installdata.prefix, i.outdir, os.path.basename(x)) for x in i.aliases] - install_lookuptable[os.path.basename(i.fname)] = [str(PurePath(x)) for x in out] + basename = os.path.basename(i.fname) + install_lookuptable[basename] = [str(PurePath(installdata.prefix, i.outdir, basename))] + 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 + basename = os.path.basename(s.name) + try: + install_lookuptable[basename] = install_lookuptable[os.path.basename(s.target)] + install_lookuptable[basename].append(str(PurePath(installdata.prefix, s.install_path, basename))) + except KeyError: + pass for (idname, target) in builddata.get_targets().items(): if not isinstance(target, build.Target): diff --git a/test cases/unit/99 install all targets/meson.build b/test cases/unit/99 install all targets/meson.build index 94bd1fea2..3d131e66d 100644 --- a/test cases/unit/99 install all targets/meson.build +++ b/test cases/unit/99 install all targets/meson.build @@ -43,6 +43,12 @@ both_libraries('both', 'lib.c', install: true, ) +# Unversioned .so file should have 'devel' tag, others should have 'runtime' tag +shared_library('versioned_shared', 'lib.c', + install: true, + version: '1.2.3', +) + # Those files should have custom tag install_data('bar-custom.txt', install_dir: get_option('datadir'), diff --git a/unittests/allplatformstests.py b/unittests/allplatformstests.py index 3eea9a3fe..665e0c62d 100644 --- a/unittests/allplatformstests.py +++ b/unittests/allplatformstests.py @@ -3886,10 +3886,12 @@ class AllPlatformTests(BasePlatformTests): Path(installpath, 'usr/bin/both2.pdb'), Path(installpath, 'usr/bin/bothcustom.pdb'), Path(installpath, 'usr/bin/shared.pdb'), + Path(installpath, 'usr/bin/versioned_shared-1.pdb'), Path(installpath, 'usr/lib/both.lib'), Path(installpath, 'usr/lib/both2.lib'), Path(installpath, 'usr/lib/bothcustom.lib'), Path(installpath, 'usr/lib/shared.lib'), + Path(installpath, 'usr/lib/versioned_shared.lib'), } elif is_windows() or is_cygwin(): expected_devel |= { @@ -3897,6 +3899,11 @@ class AllPlatformTests(BasePlatformTests): Path(installpath, 'usr/lib/libboth2.dll.a'), Path(installpath, 'usr/lib/libshared.dll.a'), Path(installpath, 'usr/lib/libbothcustom.dll.a'), + Path(installpath, 'usr/lib/libversioned_shared.dll.a'), + } + else: + expected_devel |= { + Path(installpath, 'usr/' + shared_lib_name('versioned_shared')), } expected_runtime = expected_common | { @@ -3908,6 +3915,20 @@ class AllPlatformTests(BasePlatformTests): Path(installpath, 'usr/' + shared_lib_name('both2')), } + if is_windows() or is_cygwin(): + expected_runtime |= { + Path(installpath, 'usr/' + shared_lib_name('versioned_shared-1')), + } + elif is_osx(): + expected_runtime |= { + Path(installpath, 'usr/' + shared_lib_name('versioned_shared.1')), + } + else: + expected_runtime |= { + Path(installpath, 'usr/' + shared_lib_name('versioned_shared') + '.1'), + Path(installpath, 'usr/' + shared_lib_name('versioned_shared') + '.1.2.3'), + } + expected_custom = expected_common | { Path(installpath, 'usr/share'), Path(installpath, 'usr/share/bar-custom.txt'),