diff --git a/linker/slashkit/emit/hw/project_gen.py b/linker/slashkit/emit/hw/project_gen.py index dd9fddfa..80fbef62 100644 --- a/linker/slashkit/emit/hw/project_gen.py +++ b/linker/slashkit/emit/hw/project_gen.py @@ -27,7 +27,7 @@ import shutil import subprocess import importlib.resources as resources -from typing import Optional, Dict +from typing import List, Optional, Dict from contextlib import ExitStack from slashkit.emit.metadata.report_util import convert_report_utilization_to_xml @@ -139,6 +139,14 @@ def _generate_top_wrapper_pdi_with_bootgen(impl_dir: Path) -> Path: return output_pdi +def _first_existing(candidates: List[str]) -> Optional[str]: + """Return the first path in candidates that exists, or None.""" + for candidate in candidates: + if Path(candidate).is_file(): + return candidate + return None + + def _environment_with_udev_ld_preload() -> Dict[str, str]: """ Create a dictionary of environment variables (based on the current one), @@ -147,13 +155,26 @@ def _environment_with_udev_ld_preload() -> Dict[str, str]: Details: https://adaptivesupport.amd.com/s/question/0D54U00005Sgst2SAB/failed-batch-mode-execution-in-linux-docker-running-under-windows-host?language=en_US https://community.flexera.com/t5/InstallAnywhere-Forum/Issues-when-running-Xilinx-tools-or-Other-vendor-tools-in-docker/m-p/245820#M10647 + + The preload is inherited by the bundled MicroBlaze cross-compiler that builds + the DDRMC firmware (phy_ddrmc.elf) under a restricted library path, so a + forced library must bring its own dependencies. On Ubuntu 24.04 libudev.so.1 + needs libcap.so.2, so preloading libudev alone makes that compiler abort + ("libcap.so.2: cannot open shared object file") and silently drops the ELF. + Preload libcap alongside it. On 22.04 libudev has no libcap dependency, so + preloading it there has no effect. """ - possible_paths = [ - Path("/lib/x86_64-linux-gnu/libudev.so.1"), Path("/lib64/libudev.so.1")] - existing_paths = [str(path) for path in possible_paths if path.is_file()] env = dict(os.environ) - if len(existing_paths) > 0: - env["LD_PRELOAD"] = ":".join(existing_paths) + libudev = _first_existing( + ["/lib/x86_64-linux-gnu/libudev.so.1", "/lib64/libudev.so.1"]) + if libudev is None: + return env + preload = [libudev] + libcap = _first_existing( + ["/lib/x86_64-linux-gnu/libcap.so.2", "/lib64/libcap.so.2"]) + if libcap is not None: + preload.append(libcap) + env["LD_PRELOAD"] = ":".join(preload) return env diff --git a/linker/test/emit/__init__.py b/linker/test/emit/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/linker/test/emit/hw/__init__.py b/linker/test/emit/hw/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/linker/test/emit/hw/test_project_gen.py b/linker/test/emit/hw/test_project_gen.py new file mode 100644 index 00000000..06a6cef4 --- /dev/null +++ b/linker/test/emit/hw/test_project_gen.py @@ -0,0 +1,60 @@ +# ################################################################################################## +# The MIT License (MIT) +# Copyright (c) 2025-2026 Advanced Micro Devices, Inc. All rights reserved. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of this software +# and associated documentation files (the "Software"), to deal in the Software without restriction, +# including without limitation the rights to use, copy, modify, merge, publish, distribute, +# sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all copies or +# substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +# NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# ################################################################################################## + +"""Tests for the LD_PRELOAD environment helper in emit.hw.project_gen.""" + +from pathlib import Path + +from slashkit.emit.hw import project_gen + +LIBUDEV = "/lib/x86_64-linux-gnu/libudev.so.1" +LIBCAP = "/lib/x86_64-linux-gnu/libcap.so.2" + + +def _only_present(monkeypatch, present): + """Make Path.is_file report True only for the given set of paths.""" + present_set = {str(Path(p)) for p in present} + monkeypatch.setattr( + project_gen.Path, "is_file", + lambda self: str(self) in present_set, raising=True) + + +def test_preload_includes_libcap_when_libudev_present(monkeypatch): + """On a 24.04-like host, libudev is preloaded together with libcap.""" + _only_present(monkeypatch, [LIBUDEV, LIBCAP]) + monkeypatch.delenv("LD_PRELOAD", raising=False) + env = project_gen._environment_with_udev_ld_preload() + assert env["LD_PRELOAD"] == f"{LIBUDEV}:{LIBCAP}" + + +def test_preload_libudev_only_when_libcap_absent(monkeypatch): + """On a 22.04-like host libudev has no libcap dependency, so libudev alone.""" + _only_present(monkeypatch, [LIBUDEV]) + monkeypatch.delenv("LD_PRELOAD", raising=False) + env = project_gen._environment_with_udev_ld_preload() + assert env["LD_PRELOAD"] == LIBUDEV + + +def test_no_preload_when_libudev_absent(monkeypatch): + """With no libudev the workaround does not apply and LD_PRELOAD is untouched.""" + _only_present(monkeypatch, [LIBCAP]) + monkeypatch.delenv("LD_PRELOAD", raising=False) + env = project_gen._environment_with_udev_ld_preload() + assert "LD_PRELOAD" not in env