[clang] [clang][driver][darwin] Support libtool arguments used by SwiftBuild (PR #218595)
Ian Anderson via cfe-commits
cfe-commits at lists.llvm.org
Tue Aug 25 01:11:12 PDT 2026
https://github.com/ian-twilightcoder updated https://github.com/llvm/llvm-project/pull/218595
>From 6649d4c0efd7b54c3d7471b2276a40646638bf76 Mon Sep 17 00:00:00 2001
From: Ian Anderson <iana at apple.com>
Date: Mon, 24 Aug 2026 23:26:03 -0700
Subject: [PATCH] [clang][driver][darwin] Support libtool arguments used by
SwiftBuild
SwiftBuild is considering using clang as the driver for the static library tool on Darwin, but clang doesn't support a lot of the options that SwiftBuild uses for libtool.
-arch_only is already a clang argument, but it's intentionally dropped, and it would be awkward to reconcile if -target or -arch was passed too. Add a --static-lib-target-arch-only to get the appropriate value for -arch_only from -target/-arch.
-D and -no_warning_for_no_symbols are currently passed by default, make positive/negative flags to suppress them.
Reuse the -syslibroot, -L, and -filelist arguments from the linker.
Add -Xstatic-lib for SwiftBuild to pass the rest of the arguments it uses.
Assisted-by: Claude Code
rdar://185468081
---
clang/include/clang/Options/Options.td | 24 +++++++++
clang/lib/Driver/ToolChains/Darwin.cpp | 72 +++++++++++++++++++-------
clang/lib/Driver/ToolChains/Darwin.h | 2 +
clang/test/Driver/darwin-static-lib.c | 71 +++++++++++++++++++++++++
4 files changed, 151 insertions(+), 18 deletions(-)
diff --git a/clang/include/clang/Options/Options.td b/clang/include/clang/Options/Options.td
index eb5a009b5628c..2a5f9372b7f53 100644
--- a/clang/include/clang/Options/Options.td
+++ b/clang/include/clang/Options/Options.td
@@ -1124,6 +1124,8 @@ def allowable__client : Separate<["-"], "allowable_client">;
def ansi : Flag<["-", "--"], "ansi">, Group<CompileOnly_Group>;
def arch__errors__fatal : Flag<["-"], "arch_errors_fatal">;
def arch : Separate<["-"], "arch">, Flags<[NoXarchOption,TargetSpecific]>;
+// Ignored option for gcc compatibility - not used for anything.
+// -arch_only is passed to libtool through --static-lib-target-arch-only
def arch__only : Separate<["-"], "arch_only">;
def autocomplete : Joined<["--"], "autocomplete=">;
def bind__at__load : Flag<["-"], "bind_at_load">;
@@ -1434,6 +1436,28 @@ defm cuda_short_ptr : BoolOptionWithoutMarshalling<
def emit_static_lib : Flag<["--"], "emit-static-lib">,
HelpText<"Enable linker job to emit a static library.">;
+// Options controlling the static library tool. Most of these are specific
+// to Apple's libtool.
+def static_lib_target_arch_only : Flag<["--"],
+ "static-lib-target-arch-only">,
+ Visibility<[ClangOption]>,
+ HelpText<"Restrict the static library to the target architecture, ignoring "
+ "other architectures present in universal inputs (Darwin only)">;
+defm static_lib_deterministic : BoolOptionWithoutMarshalling<"",
+ "static-lib-deterministic",
+ PosFlag<SetTrue, [], [ClangOption],
+ "Set static library member metadata to fixed values so that "
+ "identical inputs produce identical output (default)">,
+ NegFlag<SetFalse, [], [ClangOption]>, BothFlags<[]>, ["--"]>;
+defm static_lib_warn_no_symbols : BoolOptionWithoutMarshalling<"",
+ "static-lib-warn-no-symbols",
+ PosFlag<SetTrue, [], [ClangOption],
+ "Warn about static library members that define no symbols">,
+ NegFlag<SetFalse, [], [ClangOption]>, BothFlags<[]>, ["--"]>;
+def Xstatic_lib : Separate<["-"], "Xstatic-lib">,
+ Visibility<[ClangOption]>,
+ HelpText<"Pass <arg> to the static library tool">, MetaVarName<"<arg>">;
+
def mprintf_kind_EQ : Joined<["-"], "mprintf-kind=">, Group<m_Group>,
HelpText<"Specify the printf lowering scheme (AMDGPU only), allowed values are "
"\"hostcall\"(printing happens during kernel execution, this scheme "
diff --git a/clang/lib/Driver/ToolChains/Darwin.cpp b/clang/lib/Driver/ToolChains/Darwin.cpp
index 98ace0720343f..656bcc4d2145d 100644
--- a/clang/lib/Driver/ToolChains/Darwin.cpp
+++ b/clang/lib/Driver/ToolChains/Darwin.cpp
@@ -192,6 +192,39 @@ void darwin::MachOTool::AddMachOArch(const ArgList &Args,
CmdArgs.push_back("-force_cpusubtype_ALL");
}
+void darwin::MachOTool::AddMachOArchOnly(const ArgList &Args,
+ ArgStringList &CmdArgs) const {
+ const toolchains::MachO &MachOTC = getMachOToolChain();
+
+ StringRef ArchName;
+ if (MachOTC.getTriple().isOSFirmware())
+ // Firmware uses the full triple as the arch name.
+ ArchName = MachOTC.getEffectiveTriple().getTriple();
+ else
+ ArchName = MachOTC.getMachOArchName(Args);
+
+ CmdArgs.push_back("-arch_only");
+ CmdArgs.push_back(Args.MakeArgString(ArchName));
+}
+
+static void AddMachOSysLibRoot(Compilation &C, const ArgList &Args,
+ ArgStringList &CmdArgs) {
+ // Give --sysroot= preference, over the Apple specific behavior to also use
+ // --isysroot as the syslibroot.
+ // We check `OPT__sysroot_EQ` directly instead of `getSysRoot` to make sure we
+ // prioritise command line arguments over configuration of `DEFAULT_SYSROOT`.
+ if (const Arg *A = Args.getLastArg(options::OPT__sysroot_EQ)) {
+ CmdArgs.push_back("-syslibroot");
+ CmdArgs.push_back(A->getValue());
+ } else if (const Arg *A = Args.getLastArg(options::OPT_isysroot)) {
+ CmdArgs.push_back("-syslibroot");
+ CmdArgs.push_back(A->getValue());
+ } else if (StringRef sysroot = C.getSysRoot(); sysroot != "") {
+ CmdArgs.push_back("-syslibroot");
+ CmdArgs.push_back(C.getArgs().MakeArgString(sysroot));
+ }
+}
+
bool darwin::Linker::NeedsTempPath(const InputInfoList &Inputs) const {
// We only need to generate a temp path for LTO if we aren't compiling object
// files. When compiling source files, we run 'dsymutil' after linking. We
@@ -439,20 +472,7 @@ void darwin::Linker::AddLinkArgs(Compilation &C, const ArgList &Args,
Args.AddAllArgs(CmdArgs, options::OPT_sub__library);
Args.AddAllArgs(CmdArgs, options::OPT_sub__umbrella);
- // Give --sysroot= preference, over the Apple specific behavior to also use
- // --isysroot as the syslibroot.
- // We check `OPT__sysroot_EQ` directly instead of `getSysRoot` to make sure we
- // prioritise command line arguments over configuration of `DEFAULT_SYSROOT`.
- if (const Arg *A = Args.getLastArg(options::OPT__sysroot_EQ)) {
- CmdArgs.push_back("-syslibroot");
- CmdArgs.push_back(A->getValue());
- } else if (const Arg *A = Args.getLastArg(options::OPT_isysroot)) {
- CmdArgs.push_back("-syslibroot");
- CmdArgs.push_back(A->getValue());
- } else if (StringRef sysroot = C.getSysRoot(); sysroot != "") {
- CmdArgs.push_back("-syslibroot");
- CmdArgs.push_back(C.getArgs().MakeArgString(sysroot));
- }
+ AddMachOSysLibRoot(C, Args, CmdArgs);
Args.AddLastArg(CmdArgs, options::OPT_twolevel__namespace);
Args.AddLastArg(CmdArgs, options::OPT_twolevel__namespace__hints);
@@ -894,15 +914,31 @@ void darwin::StaticLibTool::ConstructJob(Compilation &C, const JobAction &JA,
// Silence warnings when linking C code with a C++ '-stdlib' argument.
Args.ClaimAllArgs(options::OPT_stdlib_EQ);
- // libtool <options> <output_file> <input_files>
ArgStringList CmdArgs;
- // Create and insert file members with a deterministic index.
CmdArgs.push_back("-static");
- CmdArgs.push_back("-D");
- CmdArgs.push_back("-no_warning_for_no_symbols");
+
+ if (Args.hasArg(options::OPT_static_lib_target_arch_only))
+ AddMachOArchOnly(Args, CmdArgs);
+
+ if (Args.hasFlag(options::OPT_static_lib_deterministic,
+ options::OPT_no_static_lib_deterministic,
+ /*Default=*/true))
+ CmdArgs.push_back("-D");
+
+ AddMachOSysLibRoot(C, Args, CmdArgs);
+ Args.AddAllArgs(CmdArgs, options::OPT_L);
+
+ if (!Args.hasFlag(options::OPT_static_lib_warn_no_symbols,
+ options::OPT_no_static_lib_warn_no_symbols,
+ /*Default=*/false))
+ CmdArgs.push_back("-no_warning_for_no_symbols");
+
+ Args.AddAllArgValues(CmdArgs, options::OPT_Xstatic_lib);
+
CmdArgs.push_back("-o");
CmdArgs.push_back(Output.getFilename());
+ Args.AddLastArg(CmdArgs, options::OPT_filelist);
for (const auto &II : Inputs) {
if (II.isFilename()) {
CmdArgs.push_back(II.getFilename());
diff --git a/clang/lib/Driver/ToolChains/Darwin.h b/clang/lib/Driver/ToolChains/Darwin.h
index c41bb6c2eead5..1bcc1492b7c3e 100644
--- a/clang/lib/Driver/ToolChains/Darwin.h
+++ b/clang/lib/Driver/ToolChains/Darwin.h
@@ -39,6 +39,8 @@ class LLVM_LIBRARY_VISIBILITY MachOTool : public Tool {
protected:
void AddMachOArch(const llvm::opt::ArgList &Args,
llvm::opt::ArgStringList &CmdArgs) const;
+ void AddMachOArchOnly(const llvm::opt::ArgList &Args,
+ llvm::opt::ArgStringList &CmdArgs) const;
const toolchains::MachO &getMachOToolChain() const {
return reinterpret_cast<const toolchains::MachO &>(getToolChain());
diff --git a/clang/test/Driver/darwin-static-lib.c b/clang/test/Driver/darwin-static-lib.c
index 74a010e9656f4..c69174fa490d4 100644
--- a/clang/test/Driver/darwin-static-lib.c
+++ b/clang/test/Driver/darwin-static-lib.c
@@ -1,5 +1,76 @@
// RUN: %clang -target i386-apple-darwin9 %s -### --emit-static-lib 2>&1 | FileCheck %s
// CHECK: "{{.*}}libtool" "-static" "-D" "-no_warning_for_no_symbols" "-o" "a.out" "{{.*o}}"
+// -D and -no_warning_for_no_symbols are on by default, adding the explicit flags
+// doesn't double up the arguments passed to libtool.
+// RUN: %clang -target i386-apple-darwin9 %s -### --emit-static-lib \
+// RUN: --static-lib-deterministic --no-static-lib-warn-no-symbols 2>&1 \
+// RUN: | FileCheck %s
+
+// -D and -no_warning_for_no_symbols can be turned off.
+// RUN: %clang -target i386-apple-darwin9 %s -### --emit-static-lib \
+// RUN: --no-static-lib-deterministic --static-lib-warn-no-symbols 2>&1 \
+// RUN: | FileCheck %s --check-prefix=NEITHER
+// NEITHER: "-static"
+// NEITHER-NOT: "-D"
+// NEITHER-NOT: "-no_warning_for_no_symbols"
+// NEITHER: "-o"
+
+// Last one wins with contradictory arguments.
+// RUN: %clang -target i386-apple-darwin9 %s -### --emit-static-lib \
+// RUN: --no-static-lib-deterministic --static-lib-deterministic 2>&1 \
+// RUN: | FileCheck %s
+
+// Explicit output file.
// RUN: %clang -target i386-apple-darwin9 %s -### --emit-static-lib -o libfoo.a 2>&1 | FileCheck %s --check-prefix=OUTPUT
// OUTPUT: "{{.*}}libtool" "-static" "-D" "-no_warning_for_no_symbols" "-o" "libfoo.a" "{{.*o}}"
+
+// -arch_only is derived from the target, and only when asked for. The Mach-O
+// arch name is used, not the clang one. i.e. the clang driver turns
+// armv7k-apple-watchos8.0 into thumbv7k-apple-watchos8.0.0, but -arch_only uses
+// the MachO name armv7k. -force_cpusubtype_ALL isn't used by libtool.
+// RUN: %clang -target armv7k-apple-watchos8.0 %s -### --emit-static-lib \
+// RUN: --static-lib-target-arch-only 2>&1 | FileCheck %s --check-prefix=ARMV7K
+// ARMV7K: "-static" "-arch_only" "armv7k" "-D"
+// ARMV7K-NOT: "-force_cpusubtype_ALL"
+
+// Firmware uses the full triple.
+// RUN: %clang -target armv7em-apple-firmware1.0 %s -### --emit-static-lib \
+// RUN: --static-lib-target-arch-only 2>&1 | FileCheck %s --check-prefix=FIRMWARE
+// FIRMWARE: "-arch_only" "thumbv7em-apple-firmware1.0.0"
+
+// -arch_only on the clang command line is ignored and not passed on.
+// RUN: %clang -target armv7k-apple-watchos8.0 %s -### --emit-static-lib \
+// RUN: --static-lib-target-arch-only -arch_only arm32_64 2>&1 | \
+// RUN: FileCheck %s --check-prefix=ARCH-ONLY
+// ARCH-ONLY: warning: argument unused during compilation: '-arch_only arm32_64'
+// ARCH-ONLY: "-static" "-arch_only"
+// ARCH-ONLY-NOT: "arm32_64"
+// ARCH-ONLY: "armv7k" "-D"
+
+// sysroot becomes -syslibroot, with --sysroot= taking priority over -isysroot.
+// RUN: %clang -target i386-apple-darwin9 %s -### --emit-static-lib \
+// RUN: -isysroot %S/Inputs/MacOSX15.1.sdk --sysroot=/tmp/sysroot 2>&1 \
+// RUN: | FileCheck %s --check-prefix=SYSROOT
+// SYSROOT: "-syslibroot" "/tmp/sysroot"
+
+// RUN: %clang -target i386-apple-darwin9 %s -### --emit-static-lib \
+// RUN: -isysroot %S/Inputs/MacOSX15.1.sdk 2>&1 | FileCheck %s --check-prefix=ISYSROOT
+// ISYSROOT: "-syslibroot" "{{.*}}MacOSX15.1.sdk"
+
+// -L, -filelist are forwarded as is. -filelist doesn't get doubled up as an input file. -Xstatic-lib passes through.
+// RUN: %clang -target i386-apple-darwin9 %s -### --emit-static-lib \
+// RUN: -L/tmp/first -L/tmp/second -Xstatic-lib -dependency_info -Xstatic-lib deps.dat \
+// RUN: -filelist objs.txt 2>&1 | FileCheck %s --check-prefix=PASSTHROUGH
+// PASSTHROUGH-DAG: "-L/tmp/first" "-L/tmp/second"
+// PASSTHROUGH-DAG: "-dependency_info" "deps.dat"
+// PASSTHROUGH-DAG: "-filelist" "objs.txt"
+// PASSTHROUGH-DAG: "{{.*}}darwin-static-lib-{{.*}}.o"
+// PASSTHROUGH-NOT: "-filelist"
+
+// Multiple -arch produces one libtool job per arch plus a lipo.
+// RUN: %clang -target x86_64-apple-macos14 %s -### --emit-static-lib \
+// RUN: -arch x86_64 -arch arm64 -o libfoo.a 2>&1 | FileCheck %s --check-prefix=UNIVERSAL
+// UNIVERSAL: "{{.*}}libtool" "-static"
+// UNIVERSAL: "{{.*}}libtool" "-static"
+// UNIVERSAL: "{{.*}}lipo" "-create" "-output" "libfoo.a"
More information about the cfe-commits
mailing list