Print the -v banner on stderr and link with -version_details

ld-prime writes everything -v prints, its version banner and then the
library and framework search paths, on stderr and leaves stdout alone;
-v with nothing to link prints the banner and exits 0, whatever else is
given. -version_details prints its JSON on stdout and, with nothing to
link, exits 0; with inputs it does not stop there but prints the search
paths as -v does (without the banner) and links. mold printed the banner
on stdout and exited at -version_details wherever it stood.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Rui UeyamaandClaude Opus 5.5 committed 2026-10-01 13:35:39 +09:00
1 parent 3098b2c41e
commit 085d25033d
12 files changed
+77 -36

No files matched your search

+25 -14
View File
@@ -23,10 +23,16 @@ use crate::util::{display, os_str, page_align};
/// number (Xcode 26.6 ships ld-1267).
pub const LD64_COMPAT_VERSION: &str = "1267";
/// Prints the -v banner. ld-prime gives its own on stderr; mold's goes
/// to stdout, as mold's does on ELF.
/// Prints the -v banner on stderr, where ld-prime prints its own.
pub fn print_version() {
println!("mold-macho {} (compatible with Apple ld64)", env!("CARGO_PKG_VERSION"));
eprintln!("mold-macho {} (compatible with Apple ld64)", env!("CARGO_PKG_VERSION"));
}
/// Prints the -version_details JSON on stdout, as ld-prime does.
pub fn print_version_details() {
println!(
"{{\n\t\"version\": \"{LD64_COMPAT_VERSION}\",\n\t\"architectures\": [\n\t\t\"arm64\",\n\t\t\"x86_64\"\n\t]\n}}"
);
}
/// An input in command line order. Paths keep the bytes they were given
@@ -181,6 +187,9 @@ pub struct Args {
pub no_standard_dirs: bool,
/// -v: print the version and the search paths.
pub verbose: bool,
/// -version_details: print the linker's description in JSON and,
/// as -v does, the search paths.
pub version_details: bool,
/// -x: strip non-global symbols from the output symbol table.
pub strip_locals: bool,
/// Fold identical functions (on by default; -no_deduplicate turns
@@ -449,6 +458,7 @@ impl Default for Args {
flat_namespace: false,
no_standard_dirs: false,
verbose: false,
version_details: false,
strip_locals: false,
deduplicate: true,
function_starts: true,
@@ -1607,12 +1617,8 @@ pub fn parse_args(target: &TargetTraits, cmdline: &[Cow<'_, OsStr>]) -> Args {
// also reports its LTO and TAPI versions; they are ignored.
// We claim the ld64 version whose command line we implement
// so that Xcode drives us exactly as it drives ld-prime.
b"-version_details" => {
println!(
"{{\n\t\"version\": \"{LD64_COMPAT_VERSION}\",\n\t\"architectures\": [\n\t\t\"arm64\",\n\t\t\"x86_64\"\n\t]\n}}"
);
std::process::exit(0);
}
// With something to link, ld-prime goes on to link it.
b"-version_details" => args.version_details = true,
b"-noall_load" => args.all_load = false,
b"-ObjC" => args.load_objc = true,
b"-force_load" => args.inputs.push(InputArg::ForceLoad(path(next_arg(&mut i, name)))),
@@ -1868,12 +1874,17 @@ pub fn parse_args(target: &TargetTraits, cmdline: &[Cow<'_, OsStr>]) -> Args {
// `ld -v` with nothing to link just reports the version; build
// systems and configure scripts probe the linker that way. mold
// does the same for -v/--version with no inputs.
if args.verbose && args.inputs.is_empty() {
print_version();
std::process::exit(0);
}
// does the same for -v/--version with no inputs. So does
// -version_details, unless -v comes with it.
if args.inputs.is_empty() {
if args.verbose {
print_version();
std::process::exit(0);
}
if args.version_details {
print_version_details();
std::process::exit(0);
}
fatal!("no object files specified");
}
+7 -3
View File
@@ -36,16 +36,20 @@ const STANDARD_FRAMEWORK_DIRS: &[&str] = &["/Library/Frameworks", "/System/Libra
/// Settles the library and framework search paths, once the options
/// are checked, as ld-prime does: the -L (-F) directories, then, unless
/// -Z, the default ones (see search_dirs). -v prints the banner here,
/// then the paths on stderr, as ld-prime does.
/// -Z, the default ones (see search_dirs). -v prints the banner here and
/// -version_details its JSON; then either prints the paths on stderr,
/// as ld-prime does.
pub fn set_search_paths<E: Target>(ctx: &mut Context<E>) {
let args = &mut ctx.args;
if args.verbose {
crate::cmdline::print_version();
}
if args.version_details {
crate::cmdline::print_version_details();
}
args.library_paths = search_dirs(args, &args.library_paths, STANDARD_LIBRARY_DIRS);
args.framework_paths = search_dirs(args, &args.framework_paths, STANDARD_FRAMEWORK_DIRS);
if args.verbose {
if args.verbose || args.version_details {
let mut out = Vec::new();
for (title, dirs) in
[("Library", &args.library_paths), ("Framework", &args.framework_paths)]
+1 -1
View File
@@ -31,7 +31,7 @@ otool -l $t/lib.dylib | grep -A4 'sectname __init_offsets' | grep -q 'size 0x0*8
# Of an older image, which keeps __mod_init_func, ld-prime runs no
# -init function; ld64 named it in LC_ROUTINES_64, which dyld runs
# before the other initializers, and so do we.
if $mold -v 2> /dev/null | grep -q mold-macho; then
if $mold -v 2>&1 | grep -q mold-macho; then
$CC --ld-path=$mold -o $t/lib2.dylib -shared $t/a.o -Wl,-init,_init -mmacosx-version-min=11.0
[ "$($t/exe2 $t/lib2.dylib)" = 'main init ctor 1' ]
otool -l $t/lib2.dylib | grep -q 'cmd LC_ROUTINES_64'
+2 -2
View File
@@ -91,7 +91,7 @@ set_flags $t/c.o __DATA __got 2
# ld-prime fails an assertion on a slot a symbol names, as arm64 code
# names the slots it loads.
if [ $ARCH = x86_64 ] || $mold -v 2> /dev/null | grep -q mold-macho; then
if [ $ARCH = x86_64 ] || $mold -v 2>&1 | grep -q mold-macho; then
for obj in a c; do
$CC --ld-path=$mold -o $t/exe-$obj $t/$obj.o $t/b.o
$t/exe-$obj > $t/out
@@ -105,7 +105,7 @@ if [ $ARCH = x86_64 ] || $mold -v 2> /dev/null | grep -q mold-macho; then
done
fi
if $mold -v 2> /dev/null | grep -q mold-macho; then
if $mold -v 2>&1 | grep -q mold-macho; then
for obj in a c; do
$mold -r -arch $ARCH -o $t/r.o $t/$obj.o
otool -l $t/r.o | grep -A9 'sectname __got$' | grep -q 'flags 0x00000000'
+1 -1
View File
@@ -95,7 +95,7 @@ for n in a b; do
_y_$n: .long 0
EOF
done
if $mold -v 2> /dev/null | grep -q mold-macho; then
if $mold -v 2>&1 | grep -q mold-macho; then
$mold -r -arch $ARCH -o $t/r2.o $t/da.o $t/db.o
[ "$(sect $t/r2.o __TEXT __literal8)" = 0x0000000000000010 ]
objdump --macho -r $t/r2.o > $t/relocs
+1 -1
View File
@@ -45,7 +45,7 @@ int main() {
printf("%d %d\n", tz[5], td);
}
EOF
if $mold -v 2> /dev/null | grep -q mold-macho; then
if $mold -v 2>&1 | grep -q mold-macho; then
$CC --ld-path=$mold -o $t/exe2 $t/b.o -Wl,-no_zero_fill_sections
otool -l $t/exe2 > $t/lc2
sect $t/lc2 __thread_bss | grep -q ' flags 0x00000011 '
+1 -1
View File
@@ -121,7 +121,7 @@ otool -l $t/kext | grep -A10 'sectname __foo$' | grep -q 'flags 0x00000000'
# A -r output keeps the type and the relocations, and so is pointers
# to a later link (ld-prime names the slots in the indirect symbol
# table instead, which no link takes as input).
if $mold -v 2> /dev/null | grep -q mold-macho; then
if $mold -v 2>&1 | grep -q mold-macho; then
$mold -r -arch $ARCH -o $t/r.o $t/a.o
otool -l $t/r.o | grep -A10 'sectname __foo$' | grep -q 'flags 0x00000006'
objdump --macho -r $t/r.o | grep -q ' _puts$'
+1 -1
View File
@@ -49,7 +49,7 @@ not grep -q __objc_classrefs $t/lc
# A reference into the middle of an atom keeps its slot: ld-prime would
# point the load at the atom's start instead.
if $mold -v 2> /dev/null | grep -q mold-macho; then
if $mold -v 2>&1 | grep -q mold-macho; then
class b '.quad 7'
$CC --ld-path=$mold -o $t/exe2 $t/main.o $t/b.o -mmacosx-version-min=15.0
$t/exe2 | grep -q '^1$'
+1 -1
View File
@@ -84,7 +84,7 @@ grep -q "initializer pointer has no target in '/.*/$t/c.o'" $t/log4
# So does each entry of __objc_clsrolist, the list of Swift's class_ro_t
# records, which is an atom of its own. (ld-prime has the check, but an
# assertion that the entry has a relocation trips first.)
if $mold -v 2> /dev/null | grep -q mold-macho; then
if $mold -v 2>&1 | grep -q mold-macho; then
cat <<EOF | $CC -o $t/e.o -c -xassembler -
.section __DATA,__objc_const
.p2align 3
+1 -1
View File
@@ -138,7 +138,7 @@ $t/exe3 | grep -q '^5 0$'
# ld-prime writes an image dyld refuses for data after the template,
# and crashes with no template left.
if $mold -v 2> /dev/null | grep -q mold-macho; then
if $mold -v 2>&1 | grep -q mold-macho; then
not $CC --ld-path=$mold -o $t/exe3 $t/d.o $t/a.o \
-Wl,-rename_section,__DATA,__thread_data,__FOO,__bar 2> $t/log
grep -q 'thread-locals too large' $t/log
+27 -8
View File
@@ -1,17 +1,36 @@
#!/bin/bash
source "$(dirname "$0")"/common.inc
# `ld -v` alone prints the banner and exits 0; build systems probe the
# linker that way.
$mold -v > $t/v.txt
grep -q '[ms]old' $t/v.txt
# `ld -v` alone prints the banner (on stderr) and exits 0; build
# systems probe the linker that way.
banner='mold-macho\|PROGRAM:ld'
$mold -v 2> $t/v.txt
grep "$banner" $t/v.txt
# Xcode runs `ld -version_details` before the first link and needs JSON
# with an ld64 "version" and the supported "architectures".
$mold -version_details > $t/details.json
python3 - $t/details.json <<'EOF'
import json, sys
check_json() {
python3 - $1 <<'EOF'
import json, re, sys
d = json.load(open(sys.argv[1]))
assert d["version"].isdigit(), d
assert re.fullmatch(r"\d+(\.\d+)*", d["version"]), d
assert "arm64" in d["architectures"] and "x86_64" in d["architectures"], d
EOF
}
$mold -version_details > $t/details.json
check_json $t/details.json
# With nothing to link, -v wins: the banner alone, wherever
# -version_details stands.
$mold -version_details -v > $t/out2 2> $t/err2
[ ! -s $t/out2 ]
grep "$banner" $t/err2
# With something to link, -version_details prints the JSON and, as -v
# does, the search paths (but no banner), then links.
echo 'int main() { return 0; }' | $CC -o $t/a.o -c -xc -
$CC --ld-path=$mold -o $t/exe $t/a.o -Wl,-version_details > $t/details3.json 2> $t/err3
check_json $t/details3.json
grep '^Library search paths:' $t/err3
not grep "$banner" $t/err3
$t/exe
+9 -2
View File
@@ -1,7 +1,12 @@
#!/bin/bash
source "$(dirname "$0")"/common.inc
$mold -v | grep '[ms]old'
# The -v banner goes to stderr, as ld-prime's does, and stdout stays
# empty.
banner='mold-macho\|PROGRAM:ld'
$mold -v > $t/out 2> $t/err
[ ! -s $t/out ]
grep "$banner" $t/err
cat <<EOF | $CC -o $t/a.o -c -xc -
#include <stdio.h>
@@ -11,5 +16,7 @@ int main() {
}
EOF
$CC --ld-path=$mold -Wl,-v -o $t/exe $t/a.o | grep '[ms]old'
$CC --ld-path=$mold -Wl,-v -o $t/exe $t/a.o > $t/out2 2> $t/err2
[ ! -s $t/out2 ]
grep "$banner" $t/err2
$t/exe | grep 'Hello world'