Skip to content

Commit 1ff1135

Browse files
committed
Update
[ghstack-poisoned]
2 parents 9e5dbd7 + 9fde789 commit 1ff1135

4 files changed

Lines changed: 61 additions & 11 deletions

File tree

.ci/scripts/wheel/test_cpp_sdk.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,8 @@ def test_cpp_consumer(work_dir: Path) -> None:
177177
subprocess.run([str(consumer)], check=True, env=environment)
178178
print("✓ C++ consumer builds and runs against the installed wheel")
179179

180+
_assert_runs_relocated(consumer, package_dir, work_dir, environment)
181+
180182
assert shutil.which("readelf") is not None, "readelf is required to check the ELF"
181183

182184
dynamic = subprocess.run(
@@ -193,6 +195,49 @@ def test_cpp_consumer(work_dir: Path) -> None:
193195
print("✓ consumer depends on the shipped runtime with a relocatable RUNPATH")
194196

195197

198+
def _assert_runs_relocated(consumer, package_dir, work_dir, environment) -> None:
199+
"""The app still runs after being moved away from the wheel.
200+
201+
Building in place leaves an absolute path to the wheel's lib directory in the
202+
binary's RUNPATH, which resolves the runtime no matter what `$ORIGIN` says.
203+
Copying the app next to a copy of the runtime, with that absolute entry
204+
removed, is what actually proves the package is relocatable.
205+
206+
The layout mirrors what the package config supports: the app in `bin/` with
207+
the libraries in a sibling `lib/`, which is what `$ORIGIN/../lib` resolves.
208+
"""
209+
if shutil.which("patchelf") is None:
210+
print("- patchelf not available, skipping the relocated run")
211+
return
212+
213+
deploy = work_dir / "deployed"
214+
(deploy / "bin").mkdir(parents=True, exist_ok=True)
215+
(deploy / "lib").mkdir(parents=True, exist_ok=True)
216+
moved = deploy / "bin" / consumer.name
217+
shutil.copy2(consumer, moved)
218+
for library in (package_dir / "lib").glob("*.so*"):
219+
shutil.copy2(library, deploy / "lib" / library.name)
220+
221+
# Keep only the $ORIGIN-relative entries, so nothing absolute can help.
222+
current = subprocess.run(
223+
["patchelf", "--print-rpath", str(moved)],
224+
capture_output=True,
225+
text=True,
226+
check=True,
227+
).stdout.strip()
228+
relative = [entry for entry in current.split(":") if entry.startswith("$ORIGIN")]
229+
assert relative, (
230+
"the consumer has no $ORIGIN-relative RUNPATH entry, so it cannot be "
231+
f"relocated; RUNPATH was: {current}"
232+
)
233+
subprocess.run(
234+
["patchelf", "--set-rpath", ":".join(relative), str(moved)], check=True
235+
)
236+
237+
subprocess.run([str(moved)], check=True, env=environment, cwd=str(deploy))
238+
print("✓ consumer still runs when deployed beside a copy of the runtime")
239+
240+
196241
def run_tests(work_dir: Path) -> None:
197242
test_single_backend_registry()
198243
test_single_threadpool()

CMakeLists.txt

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1289,17 +1289,10 @@ if(EXECUTORCH_BUILD_PYBIND)
12891289
strip_python_lib(data_loader)
12901290
target_include_directories(data_loader PRIVATE ${_common_include_directories})
12911291
target_compile_options(data_loader PUBLIC ${_pybind_compile_options})
1292-
# This module only exposes a pybind type; it calls into no runtime symbols, so
1293-
# it links the static core as before and does not need the shared runtime. The
1294-
# RPATH entry is still useful because sibling extensions in this directory do
1295-
# resolve libexecutorch.so from ../../lib.
1292+
# This module only exposes a pybind type and calls into no runtime symbols, so
1293+
# it links the static core as before and needs nothing from the shared
1294+
# runtime.
12961295
target_link_libraries(data_loader PRIVATE executorch)
1297-
if(EXECUTORCH_BUILD_SHARED AND NOT APPLE)
1298-
set_target_properties(
1299-
data_loader PROPERTIES BUILD_RPATH "$ORIGIN/../../lib"
1300-
INSTALL_RPATH "$ORIGIN/../../lib"
1301-
)
1302-
endif()
13031296
install(TARGETS data_loader
13041297
LIBRARY DESTINATION executorch/extension/pybindings
13051298
)

kernels/quantized/CMakeLists.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,16 @@ if(NOT CMAKE_GENERATOR STREQUAL "Xcode"
8686
gen_custom_ops_aot_lib(
8787
LIB_NAME "quantized_ops_aot_lib" KERNEL_SOURCES "${_quantized_sources}"
8888
)
89+
if(EXECUTORCH_BUILD_SHARED AND NOT APPLE)
90+
# The generated library resolves the runtime from libexecutorch.so, so it
91+
# needs the path to it whether or not pybindings are also being built.
92+
# This library lands in <site-packages>/executorch/kernels/quantized, two
93+
# levels below the wheel's lib/ directory.
94+
set_target_properties(
95+
quantized_ops_aot_lib PROPERTIES BUILD_RPATH "$ORIGIN/../../lib"
96+
INSTALL_RPATH "$ORIGIN/../../lib"
97+
)
98+
endif()
8999

90100
# Register quantized ops to portable_lib, so that they're available via
91101
# pybindings.

tools/cmake/Utils.cmake

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,9 @@ function(executorch_target_link_shared_runtime target_name)
251251
return()
252252
endif()
253253
if(APPLE OR MSVC)
254-
set(_runtime_flags "SHELL:$<TARGET_FILE:executorch_shared>")
254+
# TARGET_LINKER_FILE rather than TARGET_FILE: on Windows the linker needs
255+
# the import library, not the DLL itself.
256+
set(_runtime_flags "SHELL:$<TARGET_LINKER_FILE:executorch_shared>")
255257
else()
256258
# --no-as-needed keeps the runtime in DT_NEEDED even though no symbol has
257259
# been referenced yet at this point on the link line. It is wrapped in

0 commit comments

Comments
 (0)