Skip to content

Commit 04d9881

Browse files
committed
Implement linux
Signed-off-by: Cristian Le <[email protected]>
1 parent 7133250 commit 04d9881

File tree

1 file changed

+133
-3
lines changed
  • src/scikit_build_core/repair_wheel

1 file changed

+133
-3
lines changed

src/scikit_build_core/repair_wheel/linux.py

+133-3
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,13 @@
44

55
from __future__ import annotations
66

7+
from pathlib import Path
78
from typing import TYPE_CHECKING
89

9-
import auditwheel # noqa: F401
10+
from auditwheel.elfutils import elf_read_rpaths
11+
from auditwheel.patcher import Patchelf
1012

13+
from .._logging import logger
1114
from . import WheelRepairer
1215

1316
if TYPE_CHECKING:
@@ -23,6 +26,133 @@ class LinuxWheelRepairer(WheelRepairer):
2326

2427
_platform = "Linux"
2528

29+
def patch_linux_library_rpath(self, artifact: Path, rpaths: list[str]) -> None:
30+
"""Patch the rpaths of a specific library."""
31+
# Flatten the current rpaths
32+
curr_rpaths = {
33+
path
34+
for dt_rpaths in elf_read_rpaths(artifact).values()
35+
for path in dt_rpaths
36+
}
37+
final_rpaths = set()
38+
# Patch pre-existing rpaths
39+
for rpath_str in curr_rpaths:
40+
# If the rpath is already relative keep it
41+
if rpath_str.startswith("$ORIGIN"):
42+
final_rpaths.add(rpath_str)
43+
continue
44+
# Otherwise check if we need to patch it
45+
rpath_path = Path(rpath_str)
46+
if not self.path_is_in_site_packages(rpath_path):
47+
# If it does not point to wheel install path, just keep it
48+
final_rpaths.add(rpath_str)
49+
continue
50+
# Otherwise change the RPATH to point use $ORIGIN
51+
new_rpath = self.path_relative_site_packages(rpath_path, artifact.parent)
52+
new_rpath_str = f"$ORIGIN/{new_rpath}"
53+
final_rpaths.add(new_rpath_str)
54+
# Merge with all the rpaths we were given
55+
final_rpaths = final_rpaths.union(rpaths)
56+
patcher = Patchelf()
57+
patcher.set_rpath(artifact, ":".join(final_rpaths))
58+
59+
def get_dependency_rpaths(self, target: Target, install_path: Path) -> list[str]:
60+
"""Get the rpaths due to target link dependencies."""
61+
target_path = self.install_dir / install_path
62+
rpaths = []
63+
for dep_target in self.get_library_dependencies(target):
64+
dep_install_paths = self.get_wheel_install_paths(dep_target)
65+
assert len(dep_install_paths) == 1
66+
dep_install_path = self.install_dir / next(iter(dep_install_paths))
67+
rpath = self.path_relative_site_packages(dep_install_path, target_path)
68+
new_rpath_str = f"$ORIGIN/{rpath}"
69+
rpaths.append(new_rpath_str)
70+
return rpaths
71+
72+
def get_package_rpaths(self, target: Target, install_path: Path) -> list[str]:
73+
"""
74+
Get the rpaths due to external package linkage.
75+
76+
Have to use the linker flags until the package targets are exposed.
77+
https://gitlab.kitware.com/cmake/cmake/-/issues/26755
78+
"""
79+
if not target.link:
80+
return []
81+
rpaths = []
82+
for link_command in target.link.commandFragments:
83+
if link_command.role == "flags":
84+
if not link_command.fragment:
85+
logger.debug(
86+
"Skipping {target} link-flags: {flags}",
87+
target=target.name,
88+
flags=link_command.fragment,
89+
)
90+
continue
91+
if link_command.role != "libraries":
92+
logger.warning(
93+
"File-api link role {role} is not supported. "
94+
"Target={target}, command={command}",
95+
target=target.name,
96+
role=link_command.role,
97+
command=link_command.fragment,
98+
)
99+
continue
100+
# Try to parse `-Wl,-rpath` flags
101+
if link_command.fragment.startswith("-Wl,-rpath,"):
102+
# removeprefix(`-Wl,-rpath,`) but compatible with Python 3.9
103+
check_rpaths = link_command.fragment[len("-Wl,-rpath,") :]
104+
for rpath_str in check_rpaths.split(":"):
105+
if not rpath_str:
106+
# Skip empty rpaths. Most likely will have on at the end
107+
continue
108+
rpath = Path(rpath_str)
109+
if not self.path_is_in_site_packages(rpath):
110+
# Skip any paths that cannot be handled. We do not check for paths in
111+
# the build directory, it should be covered by `get_dependency_rpaths`
112+
continue
113+
rpath = self.path_relative_site_packages(rpath, install_path)
114+
new_rpath_str = f"$ORIGIN/{rpath}"
115+
rpaths.append(new_rpath_str)
116+
# The remaining case should be a path
117+
try:
118+
# TODO: how to best catch if a string is a valid path?
119+
rpath = Path(link_command.fragment)
120+
if not rpath.is_absolute():
121+
# Relative paths should be handled by `get_dependency_rpaths`
122+
continue
123+
rpath = self.path_relative_site_packages(rpath, install_path)
124+
new_rpath_str = f"$ORIGIN/{rpath.parent}"
125+
rpaths.append(new_rpath_str)
126+
except Exception:
127+
logger.warning(
128+
"Could not parse link-library as a path: {fragment}",
129+
fragment=link_command.fragment,
130+
)
131+
continue
132+
return rpaths
133+
26134
def patch_target(self, target: Target) -> None:
27-
# TODO: Implement patching
28-
pass
135+
# Get the target install paths where the $ORIGIN is calculated from
136+
target_install_paths = self.get_wheel_install_paths(target)
137+
if not target_install_paths:
138+
logger.debug(
139+
"Skip patching {target} because all install paths are outside the wheel.",
140+
target=target.name,
141+
)
142+
return
143+
if len(set(target.artifacts)) != 1:
144+
logger.warning(
145+
"Unexpected multiple artifacts for target {target}: {artifacts}",
146+
target=target.name,
147+
artifacts=[item.path for item in target.artifacts],
148+
)
149+
return
150+
artifact = target.artifacts[0]
151+
for install_path in target_install_paths:
152+
target_path = self.install_dir / install_path
153+
dependency_rpaths = self.get_dependency_rpaths(target, install_path)
154+
package_rpaths = self.get_package_rpaths(target, install_path)
155+
self.patch_linux_library_rpath(
156+
artifact=target_path / artifact.path,
157+
rpaths=[*dependency_rpaths, *package_rpaths],
158+
)

0 commit comments

Comments
 (0)