Re: [PATCH v4 2/2] Makefile: support universal macOS builds via RUST_TARGETS
- From
Patrick Steinhardt <ps@pks.im>
- Date
- Jul 6, 2026, 10:49 UTC
- Message-ID
- <akuIQADP_aRb5pY6@pks.im>
- In-Reply-To
- <88fc2e0bd88756a07467bdaf75f6a344d2e58b41.1783188355.git.gitgitgadget@gmail.com>
On Sat, Jul 04, 2026 at 06:05:55PM +0000, Shardul Natu via GitGitGadget wrote:
Show 30 quoted lines
> From: Shardul Natu <snatu@google.com> > > On macOS, Universal Binaries contain native executable code for > multiple architectures (such as Intel x86_64 and Apple Silicon arm64) > bundled into a single file. This is standard practice for macOS > distribution and CI packaging (such as internal distribution packages > or tooling like Burrito/Homebrew), allowing a single build artifact > to run natively across all Macs without Rosetta emulation or > maintaining separate packages. > > When building Git C code for multiple architectures on macOS, the > Apple toolchain (clang) natively supports universal builds via > CFLAGS/LDFLAGS. When "-arch x86_64 -arch arm64" is passed, clang > automatically compiles and links universal binaries for all C object > files and executables out of the box. > > Cargo and rustc, however, do not support multiple "-arch" flags or > emitting universal binaries in a single invocation. Instead, Cargo > requires invoking each target triple independently (e.g., passing > "--target x86_64-apple-darwin" and "--target aarch64-apple-darwin"). > > To bridge this gap when Rust is enabled: > 1. Allow specifying space-separated target triples in RUST_TARGETS. > 2. Introduce declarative pattern rules (target/%/...) to compile > each target-specific library slice via Cargo. > 3. On macOS, if multiple targets are specified, use "lipo" (part of > the mandatory Xcode Command Line Tools) to combine the resulting > static libraries into target/release/libgitcore.a. > 4. Ensure target directory creation before invoking lipo via > mkdir_p_parent_template.
Nit: The last item really is quite uninteresting in the bigger scheme of things.
> Once $(RUST_LIB) is compiled into a universal static archive, the > standard C linker seamlessly links it with the C object files to > produce universal Git executables.
Okay, this overall reads a lot better now.
Show 10 quoted lines
> diff --git a/Makefile b/Makefile > index 7db38ecce9..ecada0acb4 100644 > --- a/Makefile > +++ b/Makefile > @@ -500,6 +500,14 @@ include shared.mak > # > # Building Rust code requires Cargo. > # > +# Define RUST_TARGETS if you want to cross-compile. If left unspecified, it uses > +# the default rust target on the system.
s/rust/Rust/
Show 17 quoted lines
> @@ -3022,8 +3031,30 @@ $(LIB_FILE): $(LIB_OBJS) > $(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^ > > ifndef NO_RUST > +ifeq ($(RUST_TARGETS),) > $(RUST_LIB): Cargo.toml $(RUST_SOURCES) $(LIB_FILE) > $(QUIET_CARGO)cargo build $(CARGO_ARGS) > +else > +ifneq ($(words $(RUST_TARGETS)),1) > +ifneq ($(uname_S),Darwin) > +$(error Building universal Rust libraries requires macOS (lipo is not available on $(uname_S))) > +endif > +endif > + > +RUST_MEMBER_LIBS = $(foreach target,$(RUST_TARGETS),target/$(target)/$(RUST_BUILD_CONFIG)/$(RUST_LIB_NAME)) > +$(RUST_MEMBER_LIBS): target/%/$(RUST_BUILD_CONFIG)/$(RUST_LIB_NAME): Cargo.toml $(RUST_SOURCES) $(LIB_FILE) > + $(QUIET_CARGO)cargo build $(CARGO_ARGS) --target $*
With this we now have both:
- target/$ARCH/$BUILD_CONFIG/
- target/$BUILD_CONFIG/
Is there any reason why we have to have those two different layouts instead of swapping the order in the first item so that all artifacts are in "target/$BUILD_CONFIG/"? Essentially, what I'm proposing instead is:
- "target/$BUILD_CONFIG/" for the final universal executable.
- "target/$BUILD_CONFIG/$ARCH" for the per-arch artifacts.
Patrick