Guard Linux speech runtime dependency closure and relocatable lookup

This commit is contained in:
karl committed 2026-10-13 21:27:37 +02:00
1 parent 210f2b0136
commit e71cace91f
5 files changed
+80 -1

No files matched your search

+9 -1
View File
@@ -33,6 +33,12 @@ else()
set(GGML_METAL OFF CACHE BOOL "" FORCE)
endif()
endif()
if(UNIX AND NOT APPLE AND NOT SEARCHI_INTERNAL_CPU)
# The staged worker and every dynamic backend live together. RUNPATH is
# non-transitive, so each shared target needs its own relocatable lookup.
set(CMAKE_INSTALL_RPATH "$ORIGIN")
set(CMAKE_BUILD_WITH_INSTALL_RPATH ON)
endif()
add_subdirectory("${WHISPER_SOURCE}" whisper)
target_compile_features(whisper PRIVATE cxx_std_17)
add_executable(searchi-speech-worker worker.cpp)
@@ -44,6 +50,8 @@ endif()
if(MSVC)
target_compile_options(searchi-speech-worker PRIVATE /utf-8)
endif()
if(UNIX)
if(APPLE)
set_target_properties(searchi-speech-worker PROPERTIES BUILD_RPATH "$ORIGIN;@loader_path" INSTALL_RPATH "$ORIGIN;@loader_path")
elseif(UNIX)
set_target_properties(searchi-speech-worker PROPERTIES BUILD_RPATH "$ORIGIN" INSTALL_RPATH "$ORIGIN")
endif()
+5
View File
@@ -20,6 +20,11 @@ inventory files before launch and incorporates the inventory hash in its source
completion fingerprint. A changed backend DLL therefore invalidates reuse.
The builder compiles only; native Linux/macOS loading, full system-dependency
closure, actual GPU speed/fallback and release packaging still require evidence.
Normal Linux builds reject staged ELF files with build-tree RUNPATH or undeclared
dependencies. Each shared target uses `$ORIGIN` for relocation; a system Vulkan
loader is permitted only for the dynamically loaded Vulkan backend. The declared
system C/C++ libraries remain a distribution compatibility requirement. A full
relocated recognition test is still required after compilation.
Windows auto builds reuse the verified Vulkan SDK/loader provisioning and selected
Visual Studio CRT bundling helpers. The isolated runtime inventories its own
`vulkan-1.dll`, full release CRT set and their redistribution notices. Shared helper
+20
View File
@@ -40,6 +40,24 @@ def sha(path):
return result.hexdigest()
def verify_linux_closure(directory):
"""Reject build-tree lookup and undeclared ELF dependencies before release."""
helper = shared_helper("build_runtime.py")
directory = Path(directory)
artifacts = [directory / "searchi-speech-worker"] + sorted(directory.glob("*.so*"))
names = {path.name for path in artifacts}
for path in artifacts:
metadata = helper.elf_dynamic(path)
if metadata.get("RPATH") or metadata.get("RUNPATH") != ["$ORIGIN"]:
raise ValueError(f"Linux speech runtime requires origin-only RUNPATH: {path.name}")
allowed = names | helper.LINUX_SYSTEM_LIBRARIES
if path.name == "libggml-vulkan.so":
allowed = allowed | {"libvulkan.so.1"}
missing = set(metadata.get("NEEDED", [])) - allowed
if missing:
raise ValueError(f"Unpackaged Linux speech dependency for {path.name}: {sorted(missing)}")
def validate_source(source):
revision = subprocess.check_output(["git", "-C", str(source), "rev-parse", "HEAD"], text=True).strip()
dirty = subprocess.check_output(["git", "-C", str(source), "status", "--porcelain"], text=True).strip()
@@ -113,6 +131,8 @@ def main():
raise ValueError("Ambiguous speech dependency basename")
shutil.copy2(path, stage / path.name)
records[path.name] = {"path": path.name, "bytes": path.stat().st_size, "sha256": digest}
if args.device == "auto" and sys.platform.startswith("linux"):
verify_linux_closure(stage)
manifest = {"schema": 1, "upstream_commit": UPSTREAM, "device": args.device,
"distribution": args.device == "auto", "source_hashes": source_hashes,
"files": list(records.values()), "native_os_gpu_checks": "unexecuted"}
+21
View File
@@ -28,6 +28,27 @@ class BuildContract(unittest.TestCase):
self.assertTrue(callable(build.shared_helper("build_runtime.py").bundle_windows_crt))
self.assertTrue(callable(build.shared_helper("provision_vulkan.py").provision))
def test_linux_closure_rejects_build_paths_and_missing_dependencies(self):
from types import SimpleNamespace
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
for name in ['searchi-speech-worker', 'libwhisper.so.1', 'libggml-vulkan.so']:
(root / name).write_bytes(b'fixture')
metadata = {'RUNPATH': ['$ORIGIN'], 'NEEDED': ['libwhisper.so.1', 'libc.so.6']}
helper = SimpleNamespace(elf_dynamic=lambda _: metadata, LINUX_SYSTEM_LIBRARIES={'libc.so.6'})
with patch.object(build, 'shared_helper', return_value=helper):
build.verify_linux_closure(root)
metadata['RUNPATH'] = ['/tmp/build']
with self.assertRaisesRegex(ValueError, 'origin-only'):
build.verify_linux_closure(root)
metadata['RUNPATH'] = ['$ORIGIN']
metadata['NEEDED'] = ['libmissing.so']
with self.assertRaisesRegex(ValueError, 'Unpackaged'):
build.verify_linux_closure(root)
metadata['NEEDED'] = ['libvulkan.so.1']
with self.assertRaisesRegex(ValueError, 'Unpackaged'):
build.verify_linux_closure(root)
if __name__ == "__main__":
unittest.main()