Subject: [PATCH 04/13] build: build Rust with Makefile and Meson

From: "Ezekiel Newren via GitGitGadget" <[email-masked]>
Date: Thu, 27 Nov 2025 01:10:26 +0000

From: Ezekiel Newren <[email-masked]>

Signed-off-by: Ezekiel Newren <[email-masked]>
---
 Makefile            | 91 ++++++++++++++++++++++++++++++++++++++-------
 meson.build         | 63 ++++++++++++++++++++++++++++++-
 rust/build-crate.sh | 58 +++++++++++++++++++++++++++++
 3 files changed, 197 insertions(+), 15 deletions(-)
 create mode 100755 rust/build-crate.sh

diff --git a/Makefile b/Makefile
index 28bc00e648..0aa6b3ae73 100644
--- a/Makefile
+++ b/Makefile
@@ -483,6 +483,14 @@ include shared.mak
 # Define LIBPCREDIR=/foo/bar if your PCRE header and library files are
 # in /foo/bar/include and /foo/bar/lib directories.
 #
+# == Optional Rust support ==
+#
+# Define WITH_RUST=false if you don't want to include features and subsystems
+# written in Rust into Git. For now, Rust is still an optional feature of the
+# build process. With Git 3.0 though, Rust will be mandatory.
+#
+# Building Rust code requires Cargo.
+#
 # == SHA-1 and SHA-256 defines ==
 #
 # === SHA-1 backend ===
@@ -919,9 +927,55 @@ TEST_SHELL_PATH = $(SHELL_PATH)
 
 LIB_FILE = libgit.a
 
-GITLIBS = common-main.o $(LIB_FILE)
 EXTLIBS =
 
+GIT_BUILD_DIR := $(CURDIR)
+export GIT_BUILD_DIR
+
+RUST_CRATES := gitcore
+.PHONY: rust-compile rust-clean
+
+WITH_RUST ?= true
+ifeq ($(WITH_RUST),true)
+
+ifeq ($(DEBUG), 1)
+	RUST_BUILD_MODE := debug
+else
+	RUST_BUILD_MODE := release
+endif
+
+RUST_LIBS := $(foreach c,$(RUST_CRATES),$(GIT_BUILD_DIR)/lib$(c).a)
+
+rust-compile:
+	@for c in $(RUST_CRATES); do \
+		echo "Building $$c..."; \
+		./rust/build-crate.sh $(GIT_BUILD_DIR) $(RUST_BUILD_MODE) $$c || exit $$?; \
+	done
+
+rust-clean:
+	$(RM) $(RUST_LIBS) Cargo.lock
+	cargo clean
+
+$(GIT_BUILD_DIR)/lib%.a:
+	echo $(RUST_LIBS)
+	./rust/build-crate.sh $(GIT_BUILD_DIR) $(RUST_BUILD_MODE) $*
+
+UNAME_S := $(shell uname -s)
+ifeq ($(UNAME_S),Linux)
+	EXTLIBS += -ldl
+endif
+
+else ifeq ($(WITH_RUST),false)
+rust-compile:
+	:
+rust-clean:
+	:
+else
+$(error 'WITH_RUST' must be true or false)
+endif
+
+GITLIBS = common-main.o $(LIB_FILE)
+
 GIT_USER_AGENT = git/$(GIT_VERSION)
 
 ifeq ($(wildcard sha1collisiondetection/lib/sha1.h),sha1collisiondetection/lib/sha1.h)
@@ -938,6 +992,11 @@ CC_LD_DYNPATH = -Wl,-rpath,
 BASIC_CFLAGS = -I.
 BASIC_LDFLAGS =
 
+ifeq ($(WITH_RUST),true)
+	BASIC_CFLAGS += -DWITH_RUST
+	BASIC_CFLAGS += -fPIE
+endif
+
 # library flags
 ARFLAGS = rcs
 PTHREAD_CFLAGS =
@@ -1303,7 +1362,9 @@ LIB_OBJS += urlmatch.o
 LIB_OBJS += usage.o
 LIB_OBJS += userdiff.o
 LIB_OBJS += utf8.o
+ifeq ($(WITH_RUST),false)
 LIB_OBJS += varint.o
+endif
 LIB_OBJS += version.o
 LIB_OBJS += versioncmp.o
 LIB_OBJS += walker.o
@@ -2409,6 +2470,10 @@ endif
 # from the dependency list, that would make each entry appear twice.
 LIBS = $(filter-out %.o, $(GITLIBS)) $(EXTLIBS)
 
+ifeq ($(WITH_RUST),true)
+LIBS += $(RUST_LIBS)
+endif
+
 BASIC_CFLAGS += $(COMPAT_CFLAGS)
 LIB_OBJS += $(COMPAT_OBJS)
 
@@ -2564,7 +2629,7 @@ git.sp git.s git.o: EXTRA_CPPFLAGS = \
 	'-DGIT_MAN_PATH="$(mandir_relative_SQ)"' \
 	'-DGIT_INFO_PATH="$(infodir_relative_SQ)"'
 
-git$X: git.o GIT-LDFLAGS $(BUILTIN_OBJS) $(GITLIBS)
+git$X: git.o GIT-LDFLAGS $(BUILTIN_OBJS) $(GITLIBS) rust-compile
 	$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) \
 		$(filter %.o,$^) $(LIBS)
 
@@ -2889,17 +2954,17 @@ headless-git.o: compat/win32/headless.c GIT-CFLAGS
 headless-git$X: headless-git.o git.res GIT-LDFLAGS
 	$(QUIET_LINK)$(CC) $(ALL_CFLAGS) $(ALL_LDFLAGS) -mwindows -o $@ $< git.res
 
-git-%$X: %.o GIT-LDFLAGS $(GITLIBS)
+git-%$X: %.o GIT-LDFLAGS $(GITLIBS) rust-compile
 	$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS)
 
-git-imap-send$X: imap-send.o $(IMAP_SEND_BUILDDEPS) GIT-LDFLAGS $(GITLIBS)
+git-imap-send$X: imap-send.o $(IMAP_SEND_BUILDDEPS) GIT-LDFLAGS $(GITLIBS) rust-compile
 	$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
 		$(IMAP_SEND_LDFLAGS) $(LIBS)
 
-git-http-fetch$X: http.o http-walker.o http-fetch.o GIT-LDFLAGS $(GITLIBS)
+git-http-fetch$X: http.o http-walker.o http-fetch.o GIT-LDFLAGS $(GITLIBS) rust-compile
 	$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
 		$(CURL_LIBCURL) $(LIBS)
-git-http-push$X: http.o http-push.o GIT-LDFLAGS $(GITLIBS)
+git-http-push$X: http.o http-push.o GIT-LDFLAGS $(GITLIBS) rust-compile
 	$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
 		$(CURL_LIBCURL) $(EXPAT_LIBEXPAT) $(LIBS)
 
@@ -2909,11 +2974,11 @@ $(REMOTE_CURL_ALIASES): $(REMOTE_CURL_PRIMARY)
 	ln -s $< $@ 2>/dev/null || \
 	cp $< $@
 
-$(REMOTE_CURL_PRIMARY): remote-curl.o http.o http-walker.o GIT-LDFLAGS $(GITLIBS)
+$(REMOTE_CURL_PRIMARY): remote-curl.o http.o http-walker.o GIT-LDFLAGS $(GITLIBS) rust-compile
 	$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
 		$(CURL_LIBCURL) $(EXPAT_LIBEXPAT) $(LIBS)
 
-scalar$X: scalar.o GIT-LDFLAGS $(GITLIBS)
+scalar$X: scalar.o GIT-LDFLAGS $(GITLIBS) rust-compile
 	$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) \
 		$(filter %.o,$^) $(LIBS)
 
@@ -3286,7 +3351,7 @@ perf: all
 
 t/helper/test-tool$X: $(patsubst %,t/helper/%,$(TEST_BUILTINS_OBJS)) $(UNIT_TEST_DIR)/test-lib.o
 
-t/helper/test-%$X: t/helper/test-%.o GIT-LDFLAGS $(GITLIBS)
+t/helper/test-%$X: t/helper/test-%.o GIT-LDFLAGS $(GITLIBS) rust-compile
 	$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(filter %.a,$^) $(LIBS)
 
 check-sha1:: t/helper/test-tool$X
@@ -3751,7 +3816,7 @@ cocciclean:
 	$(RM) -r .build/contrib/coccinelle
 	$(RM) contrib/coccinelle/*.cocci.patch
 
-clean: profile-clean coverage-clean cocciclean
+clean: profile-clean coverage-clean cocciclean rust-clean
 	$(RM) -r .build $(UNIT_TEST_BIN)
 	$(RM) GIT-TEST-SUITES
 	$(RM) po/git.pot po/git-core.pot
@@ -3907,13 +3972,13 @@ FUZZ_CXXFLAGS ?= $(ALL_CFLAGS)
 .PHONY: fuzz-all
 fuzz-all: $(FUZZ_PROGRAMS)
 
-$(FUZZ_PROGRAMS): %: %.o oss-fuzz/dummy-cmd-main.o $(GITLIBS) GIT-LDFLAGS
+$(FUZZ_PROGRAMS): %: %.o oss-fuzz/dummy-cmd-main.o $(GITLIBS) GIT-LDFLAGS rust-compile
 	$(QUIET_LINK)$(FUZZ_CXX) $(FUZZ_CXXFLAGS) -o $@ $(ALL_LDFLAGS) \
 		-Wl,--allow-multiple-definition \
 		$(filter %.o,$^) $(filter %.a,$^) $(LIBS) $(LIB_FUZZING_ENGINE)
 
 $(UNIT_TEST_PROGS): $(UNIT_TEST_BIN)/%$X: $(UNIT_TEST_DIR)/%.o $(UNIT_TEST_OBJS) \
-	$(GITLIBS) GIT-LDFLAGS
+	$(GITLIBS) GIT-LDFLAGS rust-compile
 	$(call mkdir_p_parent_template)
 	$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) \
 		$(filter %.o,$^) $(filter %.a,$^) $(LIBS)
@@ -3932,7 +3997,7 @@ $(UNIT_TEST_DIR)/clar.suite: $(UNIT_TEST_DIR)/clar-decls.h $(UNIT_TEST_DIR)/gene
 $(UNIT_TEST_DIR)/clar/clar.o: $(UNIT_TEST_DIR)/clar.suite
 $(CLAR_TEST_OBJS): $(UNIT_TEST_DIR)/clar-decls.h
 $(CLAR_TEST_OBJS): EXTRA_CPPFLAGS = -I$(UNIT_TEST_DIR)
-$(CLAR_TEST_PROG): $(UNIT_TEST_DIR)/clar.suite $(CLAR_TEST_OBJS) $(GITLIBS) GIT-LDFLAGS
+$(CLAR_TEST_PROG): $(UNIT_TEST_DIR)/clar.suite $(CLAR_TEST_OBJS) $(GITLIBS) GIT-LDFLAGS rust-compile
 	$(call mkdir_p_parent_template)
 	$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS)
 
diff --git a/meson.build b/meson.build
index 8966596ee8..94ec5e4ac0 100644
--- a/meson.build
+++ b/meson.build
@@ -267,6 +267,55 @@ version_gen_environment.set('GIT_DATE', get_option('build_date'))
 version_gen_environment.set('GIT_USER_AGENT', get_option('user_agent'))
 version_gen_environment.set('GIT_VERSION', get_option('version'))
 
+rust_crates = ['gitcore']
+
+rust_builds = []
+with_rust = get_option('with_rust')
+if with_rust
+  rustc = find_program('rustc', required : false)
+  cargo = find_program('cargo', required : false)
+
+  if not rustc.found() or not cargo.found()
+    error('Rust toolchain not found. Reconfigure with -Dwith_rust=false')
+  endif
+
+  if get_option('optimization') in ['2', '3', 's', 'z']
+    rust_build_profile = 'release'
+  else
+    rust_build_profile = 'debug'
+  endif
+
+  # Run `rustup show active-toolchain` and capture output
+  rustup_out = run_command('rustup', 'show', 'active-toolchain',
+                           check: true).stdout().strip()
+
+  rust_environment = script_environment
+
+  foreach crate : rust_crates
+    if rustup_out.contains('windows-msvc')
+      libfile = crate + '.lib'
+    else
+      libfile = 'lib' + crate + '.a'
+    endif
+
+    rust_builds += custom_target(
+      'rust_build_'+crate,
+      output: libfile,
+      build_by_default: true,
+      build_always_stale: true,
+      command: [
+        meson.project_source_root() / 'rust' / 'build-crate.sh',
+        meson.current_build_dir(), rust_build_profile, crate,
+      ],
+      install: false,
+      env: rust_environment,
+    )
+  endforeach
+else
+  message('Rust components disabled (-Dwith_rust=false)')
+endif
+
+
 compiler = meson.get_compiler('c')
 
 libgit_sources = [
@@ -548,6 +597,10 @@ libgit_sources = [
   'xdiff/xutils.c',
 ]
 
+if not with_rust
+  libgit_sources += 'varint.c'
+endif
+
 libgit_sources += custom_target(
   input: 'command-list.txt',
   output: 'command-list.h',
@@ -782,6 +835,10 @@ libgit_c_args = [
   '-DSHELL_PATH="' + fs.as_posix(target_shell.full_path()) + '"',
 ]
 
+if with_rust
+  libgit_c_args += '-DWITH_RUST'
+endif
+
 system_attributes = get_option('gitattributes')
 if system_attributes != ''
   libgit_c_args += '-DETC_GITATTRIBUTES="' + system_attributes + '"'
@@ -1274,6 +1331,7 @@ elif host_machine.system() == 'windows'
   ]
 
   libgit_dependencies += compiler.find_library('ntdll')
+  libgit_dependencies += compiler.find_library('userenv')
   libgit_include_directories += 'compat/win32'
   if compiler.get_id() == 'msvc'
     libgit_include_directories += 'compat/vcbuild/include'
@@ -1710,14 +1768,15 @@ version_def_h = custom_target(
 libgit_sources += version_def_h
 
 libgit = declare_dependency(
-  link_with: static_library('git',
+  link_with: [
+    static_library('git',
     sources: libgit_sources,
     c_args: libgit_c_args + [
       '-DGIT_VERSION_H="' + version_def_h.full_path() + '"',
     ],
     dependencies: libgit_dependencies,
     include_directories: libgit_include_directories,
-  ),
+  )] + rust_builds,
   compile_args: libgit_c_args,
   dependencies: libgit_dependencies,
   include_directories: libgit_include_directories,
diff --git a/rust/build-crate.sh b/rust/build-crate.sh
new file mode 100755
index 0000000000..b3debf70e5
--- /dev/null
+++ b/rust/build-crate.sh
@@ -0,0 +1,58 @@
+#!/bin/sh
+
+rustc -vV || exit $?
+cargo --version || exit $?
+
+dir_git_root=${0%/*}
+dir_build=$1
+rust_build_profile=$2
+crate=$3
+
+if [ "$dir_git_root" = "" ]; then
+  echo "did not specify the directory for the root of git"
+  exit 1
+fi
+
+if [ "$dir_build" = "" ]; then
+  echo "did not specify the build directory"
+  exit 1
+fi
+
+if [ "$rust_build_profile" = "" ]; then
+  echo "did not specify the rust_build_profile"
+  exit 1
+fi
+
+if [ "$rust_build_profile" = "release" ]; then
+  rust_args="--release"
+  export RUSTFLAGS=''
+elif [ "$rust_build_profile" = "debug" ]; then
+  rust_args=""
+  export RUSTFLAGS='-C debuginfo=2 -C opt-level=1 -C force-frame-pointers=yes'
+else
+  echo "illegal rust_build_profile value $rust_build_profile"
+  exit 1
+fi
+
+libfile="lib${crate}.a"
+if rustc -vV | grep windows-msvc; then
+  libfile="${crate}.lib"
+  PATH="$(echo $PATH | tr ':' '\n' | grep -Ev "^(/mingw64/bin|/usr/bin)$" | paste -sd: -):/mingw64/bin:/usr/bin"
+fi
+
+CARGO_TARGET_DIR=$dir_git_root/.build/rust/$crate
+export CARGO_TARGET_DIR
+
+cargo clean && pwd && USE_LINKING="false" cargo build -p $crate $rust_args
+
+src=$CARGO_TARGET_DIR/$rust_build_profile/$libfile
+dst=$dir_build/$libfile
+
+if [ ! -f $src ]; then
+  echo >&2 "::error:: cannot find path of static library $src is not a file or does not exist"
+  exit 5
+fi
+
+rm $dst 2>/dev/null
+echo mv $src $dst
+mv $src $dst
-- 
gitgitgadget