Files
derek.holloway 7502bf64a4
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

241 lines
7.6 KiB
Python

#!/usr/bin/env python
# Copyright Advanced Micro Devices, Inc.
# SPDX-License-Identifier: MIT
"""Performs various surgical operations on linux shared libraries."""
import argparse
import glob
from pathlib import Path
import re
import shlex
import shutil
import subprocess
import sys
def run_command(args: list[str | Path], cwd: Path):
args = [str(arg) for arg in args]
print(f"++ Exec [{cwd}]$ {shlex.join(args)}")
subprocess.check_call(args, cwd=str(cwd), stdin=subprocess.DEVNULL)
def capture(args: list[str | Path], cwd: Path) -> str:
args = [str(arg) for arg in args]
print(f"++ Exec [{cwd}]$ {shlex.join(args)}")
return subprocess.check_output(
args, cwd=str(cwd), stdin=subprocess.DEVNULL
).decode()
def resolve_symlinks(lib_path: Path):
all_paths: list[Path] = [lib_path]
all_paths.extend([Path(p) for p in glob.glob(f"{str(lib_path)}.*")])
return all_paths
def update_library_links(
libfile: Path, linker_name: str, patchelf: str = "patchelf"
) -> None:
"""
Normalize a shared library so that its real file is named exactly as its ELF SONAME,
and ensure a canonical linker-visible symlink exists.
This function is used when a library has been installed under a prefixed or
non-standard filename (e.g., librocm_sysdeps_elf.so).
It performs the following operations:
- Extracts the library's SONAME using `patchelf --print-soname`.
- Resolves the underlying real file (following symlinks).
- Renames the real file to match its SONAME if it does not already.
- Creates or updates a symlink named `linker_name` pointing to the SONAME file.
- Removes or renames the original file or symlink as appropriate.
Final layout example:
- libhwloc.so → librocm_sysdeps_hwloc.so.5
- librocm_sysdeps_hwloc.so.5 (real file)
Parameters
----------
libfile : Path
Path to the library file or symlink to normalize.
Example: /prefix/lib/librocm_sysdeps_elf.so
linker_name : str
The desired linker-visible filename to create in the same directory.
Example: libelf.so
patchelf : str, optional
Path to the `patchelf` executable used to extract the SONAME.
Defaults to "patchelf".
"""
if not libfile.exists():
raise FileNotFoundError(f"File '{libfile}' not found")
dir_path = libfile.parent
# Get SONAME
try:
lib_soname = subprocess.check_output(
[patchelf, "--print-soname", str(libfile)],
stderr=subprocess.DEVNULL,
text=True,
).strip()
except subprocess.CalledProcessError as e:
raise RuntimeError(f"patchelf --print-soname failed for '{libfile}'") from e
if not lib_soname:
raise RuntimeError(f"Empty SONAME returned by patchelf for '{libfile}'")
# Resolve real file path
try:
realname = libfile.resolve(strict=True)
except FileNotFoundError:
print(f"Error: resolve() failed for '{libfile}'", flush=True)
return
target_real = dir_path / lib_soname
symlink_path = dir_path / linker_name
if realname != target_real:
# Move real file to $dir/$soname
shutil.move(str(realname), str(target_real))
# Create/update linker symlink
if symlink_path.exists() or symlink_path.is_symlink():
symlink_path.unlink()
symlink_path.symlink_to(lib_soname)
# Remove the original symlink or file
if libfile.is_symlink() or libfile.exists():
libfile.unlink()
else:
# Rename symlink in the same directory
if symlink_path.exists():
symlink_path.unlink()
libfile.rename(symlink_path)
def relativize_pc_file(pc_file: Path) -> None:
"""Make a .pc file relocatable by using pcfiledir-relative paths.
Replaces the absolute prefix= line with a pcfiledir-relative path,
then replaces all other occurrences of the absolute prefix with ${prefix}.
Also removes absolute -L flags from Libs.private that may have leaked from
build-time LDFLAGS.
Assumes the .pc file is located at $PREFIX/lib/pkgconfig/.
Parameters
----------
pc_file : Path
Path to the .pc file to make relocatable.
"""
content = pc_file.read_text()
# Find the original absolute prefix value.
original_prefix = None
for line in content.splitlines():
if line.startswith("prefix="):
original_prefix = line[len("prefix=") :]
break
if not original_prefix:
return
# Replace the prefix line with pcfiledir-relative path.
# .pc files are in $PREFIX/lib/pkgconfig, so go up 2 levels.
content = content.replace(f"prefix={original_prefix}", "prefix=${pcfiledir}/../..")
# Replace all other occurrences of the absolute path with ${prefix}.
# Use trailing / to avoid partial matches.
content = content.replace(f"{original_prefix}/", "${prefix}/")
# Remove absolute -L paths from Libs.private (these leak from build-time LDFLAGS)
# Match patterns like: -L/absolute/path/to/lib
content = re.sub(r"-L/[^\s]+", "", content)
pc_file.write_text(content)
def add_prefix(args: argparse.Namespace):
all_libs: list[Path] = args.so_files
updated_libs: list[Path] = []
soname_updates: dict[str, str] = {}
# First update the SONAME of all requested libraries. This presumes that
# the libraries are in typical symlink form.
for lib_path in all_libs:
orig_paths = resolve_symlinks(lib_path)
lib_path_canon = lib_path.resolve()
orig_soname = capture(
[args.patchelf, "--print-soname", str(lib_path)], cwd=Path.cwd()
).strip()
soname_prefix = ""
soname_stem = orig_soname
if orig_soname.startswith("lib"):
soname_prefix = "lib"
soname_stem = orig_soname[len("lib") :]
new_soname = f"{soname_prefix}{args.add_prefix}{soname_stem}"
new_lib_path = lib_path.parent / f"{new_soname}"
if new_lib_path.exists():
new_lib_path.unlink()
print(f"Prefixing SONAME {orig_soname} -> {new_soname} for {lib_path_canon}")
lib_path_canon.rename(new_lib_path)
run_command(
[
args.patchelf,
"--set-soname",
new_soname,
new_lib_path,
],
cwd=Path.cwd(),
)
updated_libs.append(new_lib_path)
soname_updates[orig_soname] = new_soname
# Remove old links.
for orig_path in orig_paths:
if orig_path.is_symlink():
print(f"Removing original link: {orig_path}")
orig_path.unlink()
# Establish new dev symlink.
lib_path.symlink_to(new_lib_path.name)
# Now go back and replace updated sonames.
for soname_from, soname_to in soname_updates.items():
for updated_lib in updated_libs:
run_command(
[
args.patchelf,
"--replace-needed",
soname_from,
soname_to,
updated_lib,
],
cwd=Path.cwd(),
)
def run(args: argparse.Namespace):
if args.add_prefix:
add_prefix(args)
def main(argv):
p = argparse.ArgumentParser()
p.add_argument("--patchelf", default="patchelf", help="Patchelf command")
p.add_argument(
"so_files", type=Path, nargs="*", help="Shared library files to patch"
)
p.add_argument(
"--add-prefix",
help="Add a prefix to all shared libraries (and update all of their "
"DT_NEEDED to match)",
)
args = p.parse_args(argv)
run(args)
if __name__ == "__main__":
main(sys.argv[1:])