diff --git a/src/archs.nix b/src/archs.nix index c682c32..3c33a92 100644 --- a/src/archs.nix +++ b/src/archs.nix @@ -16,6 +16,8 @@ arm64 = { penguinName = "aarch64"; + # "arm64" is the legacy dylib/arch dir name the rest of penguin still uses. + compatNames = [ "arm64" ]; crossSystem = { config = "aarch64-linux-musl"; }; @@ -57,6 +59,8 @@ ppc64 = { penguinName = "powerpc64"; + # "ppc64" is the legacy dylib/arch dir name the rest of penguin still uses. + compatNames = [ "ppc64" ]; crossSystem = { config = "powerpc64-linux-musl"; gcc.abi = "elfv2"; @@ -65,7 +69,8 @@ ppc64el = { penguinName = "powerpc64le"; - compatNames = [ "powerpc64el" ]; + # "ppc64el" is the legacy dylib/arch dir name the rest of penguin uses. + compatNames = [ "powerpc64el" "ppc64el" ]; crossSystem = { config = "powerpc64le-linux-musl"; }; @@ -80,6 +85,8 @@ loongarch = { penguinName = "loongarch64"; + # "loongarch" is the legacy dylib/arch dir name the rest of penguin uses. + compatNames = [ "loongarch" ]; crossSystem = { config = "loongarch64-linux-musl"; }; diff --git a/src/mk-arch-bundle.nix b/src/mk-arch-bundle.nix index 56cd786..08d7f0e 100644 --- a/src/mk-arch-bundle.nix +++ b/src/mk-arch-bundle.nix @@ -113,25 +113,41 @@ pkgs.runCommand "penguin-tools-${penguinArch}" copy_dependency() { local source_path local source_real + local ref_name + local real_name local dest source_path="$1" source_real="$(readlink -f "$source_path")" + # The name the consumer references (a soname like libstdc++.so.6, or the + # interpreter ld-musl-.so.1) is usually a symlink to a differently + # named real file (libstdc++.so.6.0.34, libc.so). We copy the real file + # but must also expose it under the referenced name, or the musl loader -- + # which looks up the literal NEEDED/interp string -- can't find it. + ref_name="$(basename "$source_path")" + real_name="$(basename "$source_real")" - if [ -n "''${copied_dependencies[$source_real]:-}" ]; then - return 0 - fi - copied_dependencies[$source_real]=1 + dest="$dylib_dir/$real_name" + + if [ -z "''${copied_dependencies[$source_real]:-}" ]; then + copied_dependencies[$source_real]=1 - dest="$dylib_dir/$(basename "$source_real")" - cp -L "$source_real" "$dest" - chmod u+w "$dest" + cp -L "$source_real" "$dest" + chmod u+w "$dest" + + if is_elf "$dest"; then + normalize_elf "$dest" "$source_real" + fi - if is_elf "$dest"; then - normalize_elf "$dest" "$source_real" + chmod 0555 "$dest" || true fi - chmod 0555 "$dest" || true + # Always (re)create the reference-name alias, even when the real file was + # already copied for another consumer -- the same file may be reached + # under several names (e.g. ld-musl-.so.1 and libc.so). + if [ "$ref_name" != "$real_name" ]; then + ln -sfn "$real_name" "$dylib_dir/$ref_name" + fi } stage_binary() { @@ -199,12 +215,37 @@ pkgs.runCommand "penguin-tools-${penguinArch}" bad=1 fi + # Every staged ELF must be runnable on the guest from this tree alone: its + # interpreter and every NEEDED soname must resolve inside dylibs/. + # The musl loader looks up those literal names, so a missing alias means + # the binary silently fails to launch at runtime (not caught above). + local elf interp needed + while IFS= read -r elf; do + is_elf "$elf" || continue + interp="$(patchelf --print-interpreter "$elf" 2>/dev/null || true)" + if [ -n "$interp" ] && [ ! -e "$dylib_dir/$(basename "$interp")" ]; then + echo "Missing interpreter $(basename "$interp") for $elf" >&2 + bad=1 + fi + while IFS= read -r needed; do + [ -n "$needed" ] || continue + if [ ! -e "$dylib_dir/$needed" ]; then + echo "Missing NEEDED $needed for $elf" >&2 + bad=1 + fi + done < <(patchelf --print-needed "$elf" 2>/dev/null || true) + done < <(find "$arch_dir" "$dylib_dir" -type f) + if [ "$bad" -ne 0 ]; then exit 1 fi } - while IFS='|' read -r tool_name tool_mode tool_kind tool_path tool_link_target; do + # The "|| [ -n ... ]" keeps the last record even though the manifest has no + # trailing newline (writeText joins lines with concatStringsSep); without it + # `read` would consume the final tool into the variables but exit the loop + # before staging it, silently dropping whichever tool sorts last. + while IFS='|' read -r tool_name tool_mode tool_kind tool_path tool_link_target || [ -n "$tool_name" ]; do [ -n "$tool_name" ] || continue case "$tool_mode:$tool_kind" in copy:binary)