JITs: Drop cargo and use just rustc for release combo build

So we don't expose builders to network flakiness which cannot be worked
around using cargo's --offline flag.
This commit is contained in:
Alan Wu 2025-12-08 22:25:05 -05:00
parent 121d0da025
commit 029a48176c
Notes: git 2025-12-10 21:35:48 +00:00
9 changed files with 108 additions and 46 deletions

View File

@ -1,6 +1,15 @@
# Using Cargo's workspace feature to build all the Rust code in
# into a single package.
# TODO(alan) notes about rust version requirements. Undecided yet.
# This is the root Cargo [workspace](https://doc.rust-lang.org/cargo/reference/workspaces.html)
# and the root package for all the rust code that are statically linked into ruby. Rust tooling
# limitations means all Rust code need to share a single archive library (staticlib) at the
# integration point with non-rust code. (See rustlang/rust#44322 and #104707 for a taste of
# the linking challenges.)
#
# Do not add required dependencies. This is a policy that helps downstream consumers and give
# us tight control over what we ship. All of the optional dependencies are used exclusively
# during development.
#
# Release builds avoid Cargo entirely because offline builds can fail even when none of the
# optional dependencies are built (rust-lang/cargo#10352).
[workspace]
members = ["zjit", "yjit", "jit"]

View File

@ -268,9 +268,8 @@ MAKE_LINK = $(MINIRUBY) -rfileutils -e "include FileUtils::Verbose" \
# For release builds
YJIT_RUSTC_ARGS = --crate-name=yjit \
--crate-type=staticlib \
$(JIT_RUST_FLAGS) \
--edition=2021 \
--cfg 'feature="stats_allocator"' \
-g \
-C lto=thin \
-C opt-level=3 \
@ -279,9 +278,8 @@ YJIT_RUSTC_ARGS = --crate-name=yjit \
'$(top_srcdir)/yjit/src/lib.rs'
ZJIT_RUSTC_ARGS = --crate-name=zjit \
--crate-type=staticlib \
$(JIT_RUST_FLAGS) \
--edition=2024 \
--cfg 'feature="stats_allocator"' \
-g \
-C lto=thin \
-C opt-level=3 \

View File

@ -3896,7 +3896,6 @@ AC_SUBST(INSTALL_STATIC_LIBRARY)
[begin]_group "JIT section" && {
AC_CHECK_PROG(RUSTC, [rustc], [rustc], [no]) dnl no ac_tool_prefix
AC_CHECK_TOOL(CARGO, [cargo], [no])
dnl check if rustc is recent enough to build YJIT (rustc >= 1.58.0)
JIT_RUSTC_OK=no
@ -3963,11 +3962,7 @@ AC_ARG_ENABLE(zjit,
# 1.85.0 is the first stable version that supports the 2024 edition.
AS_IF([test "$RUSTC" != "no" && echo "#[cfg(target_arch = \"$JIT_TARGET_ARCH\")] fn main() {}" |
$RUSTC - --edition=2024 --emit asm=/dev/null 2>/dev/null],
AS_IF([test "$gnumake" = "yes" -a \( "$YJIT_SUPPORT" = "no" -o "$CARGO" != "no" \)], [
# When only building ZJIT, we don't need cargo; it's required for YJIT+ZJIT build.
# Assume that if rustc is new enough, then cargo is also.
# TODO(alan): Get rid of dependency on cargo in YJIT+ZJIT build. Cargo's offline mode
# still too unreliable: https://github.com/rust-lang/cargo/issues/10352
AS_IF([test "$gnumake" = "yes"], [
rb_zjit_build_possible=yes
])
)
@ -4053,36 +4048,49 @@ AS_CASE(["${ZJIT_SUPPORT}"],
AC_DEFINE(USE_ZJIT, 0)
])
# if YJIT+ZJIT release build, or any build that requires Cargo
AS_IF([test x"$JIT_CARGO_SUPPORT" != "xno" -o \( x"$YJIT_SUPPORT" != "xno" -a x"$ZJIT_SUPPORT" != "xno" \)], [
AS_IF([test x"$CARGO" = "xno"],
AC_MSG_ERROR([this build configuration requires cargo. Installation instructions available at https://www.rust-lang.org/tools/install]))
JIT_RUST_FLAGS='--crate-type=staticlib --cfg feature=\"stats_allocator\"'
RLIB_DIR=
AS_CASE(["$JIT_CARGO_SUPPORT:$YJIT_SUPPORT:$ZJIT_SUPPORT"],
[no:yes:yes], [ # release build of YJIT+ZJIT
YJIT_LIBS=
ZJIT_LIBS=
JIT_RUST_FLAGS="--crate-type=rlib"
RLIB_DIR="target/release"
RUST_LIB="target/release/libruby.a"
],
[no:*], [],
[*], [ # JIT_CARGO_SUPPORT not "no" -- cargo required.
AC_CHECK_TOOL(CARGO, [cargo], [no])
AS_IF([test x"$CARGO" = "xno"],
AC_MSG_ERROR([this build configuration requires cargo. Installation instructions available at https://www.rust-lang.org/tools/install]))
YJIT_LIBS=
ZJIT_LIBS=
YJIT_LIBS=
ZJIT_LIBS=
# There's more processing below to get the feature set for the
# top-level crate, so capture at this point for feature set of
# just the zjit crate.
ZJIT_TEST_FEATURES="${rb_cargo_features}"
# There's more processing below to get the feature set for the
# top-level crate, so capture at this point for feature set of
# just the zjit crate.
ZJIT_TEST_FEATURES="${rb_cargo_features}"
AS_IF([test x"${YJIT_SUPPORT}" != x"no"], [
rb_cargo_features="$rb_cargo_features,yjit"
])
AS_IF([test x"${ZJIT_SUPPORT}" != x"no"], [
AC_SUBST(ZJIT_TEST_FEATURES)
rb_cargo_features="$rb_cargo_features,zjit"
])
# if YJIT and ZJIT release mode
AS_IF([test "${YJIT_SUPPORT}:${ZJIT_SUPPORT}" = "yes:yes"], [
JIT_CARGO_SUPPORT=release
])
CARGO_BUILD_ARGS="--profile ${JIT_CARGO_SUPPORT} --features ${rb_cargo_features}"
AS_IF([test "${JIT_CARGO_SUPPORT}" = "dev"], [
RUST_LIB="target/debug/libruby.a"
], [
RUST_LIB="target/${JIT_CARGO_SUPPORT}/libruby.a"
])
],
AS_IF([test x"${YJIT_SUPPORT}" != x"no"], [
rb_cargo_features="$rb_cargo_features,yjit"
])
AS_IF([test x"${ZJIT_SUPPORT}" != x"no"], [
AC_SUBST(ZJIT_TEST_FEATURES)
rb_cargo_features="$rb_cargo_features,zjit"
])
# if YJIT and ZJIT release mode
AS_IF([test "${YJIT_SUPPORT}:${ZJIT_SUPPORT}" = "yes:yes"], [
JIT_CARGO_SUPPORT=release
])
CARGO_BUILD_ARGS="--profile ${JIT_CARGO_SUPPORT} --features ${rb_cargo_features}"
AS_IF([test "${JIT_CARGO_SUPPORT}" = "dev"], [
RUST_LIB="target/debug/libruby.a"
], [
RUST_LIB="target/${JIT_CARGO_SUPPORT}/libruby.a"
])
])
# In case either we're linking rust code
@ -4098,6 +4106,7 @@ AS_IF([test -n "$RUST_LIB"], [
dnl These variables end up in ::RbConfig::CONFIG
AC_SUBST(RUSTC)dnl Rust compiler command
AC_SUBST(JIT_RUST_FLAGS)dnl the common rustc flags for JIT crates such as zjit
AC_SUBST(CARGO)dnl Cargo command for Rust builds
AC_SUBST(CARGO_BUILD_ARGS)dnl for selecting Rust build profiles
AC_SUBST(YJIT_SUPPORT)dnl what flavor of YJIT the Ruby build includes
@ -4108,6 +4117,7 @@ AC_SUBST(ZJIT_LIBS)dnl path to the .a library of ZJIT
AC_SUBST(ZJIT_OBJ)dnl for optionally building the C parts of ZJIT
AC_SUBST(JIT_OBJ)dnl for optionally building C glue code for Rust FFI
AC_SUBST(RUST_LIB)dnl path to the rust .a library that contains either or both JITs
AC_SUBST(RLIB_DIR)dnl subpath of build directory for .rlib files
AC_SUBST(JIT_CARGO_SUPPORT)dnl "no" or the cargo profile of the rust code
}

View File

@ -30,6 +30,31 @@ $(RUST_LIB): $(srcdir)/ruby.rs
MACOSX_DEPLOYMENT_TARGET=11.0 \
$(CARGO) $(CARGO_VERBOSE) build --manifest-path '$(top_srcdir)/Cargo.toml' $(CARGO_BUILD_ARGS)
$(RUST_LIB_TOUCH)
else ifneq ($(strip $(RLIB_DIR)),) # combo build
$(RUST_LIB): $(srcdir)/ruby.rs
$(ECHO) 'building $(@F)'
$(Q) $(RUSTC) --edition=2024 \
'-L$(@D)' \
--extern=yjit \
--extern=zjit \
--crate-type=staticlib \
--cfg 'feature="yjit"' \
--cfg 'feature="zjit"' \
'--out-dir=$(@D)' \
'$(top_srcdir)/ruby.rs'
# Absolute path to avoid VPATH ambiguity
JIT_RLIB = $(TOP_BUILD_DIR)/$(RLIB_DIR)/libjit.rlib
$(YJIT_RLIB): $(JIT_RLIB)
$(ZJIT_RLIB): $(JIT_RLIB)
$(JIT_RLIB):
$(ECHO) 'building $(@F)'
$(Q) $(RUSTC) --crate-name=jit \
--edition=2024 \
$(JIT_RUST_FLAGS) \
'--out-dir=$(@D)' \
'$(top_srcdir)/jit/src/lib.rs'
endif # ifneq ($(JIT_CARGO_SUPPORT),no)
RUST_LIB_SYMBOLS = $(RUST_LIB:.a=).symbols

View File

@ -114,6 +114,8 @@ JIT_CARGO_SUPPORT=@JIT_CARGO_SUPPORT@
CARGO_TARGET_DIR=@abs_top_builddir@/target
CARGO_BUILD_ARGS=@CARGO_BUILD_ARGS@
ZJIT_TEST_FEATURES=@ZJIT_TEST_FEATURES@
JIT_RUST_FLAGS=@JIT_RUST_FLAGS@
RLIB_DIR=@RLIB_DIR@
RUST_LIB=@RUST_LIB@
RUST_LIBOBJ = $(RUST_LIB:.a=.@OBJEXT@)
LDFLAGS = @STATIC@ $(CFLAGS) @LDFLAGS@

View File

@ -10,8 +10,8 @@ rust-version = "1.58.0" # Minimally supported rust version
publish = false # Don't publish to crates.io
[dependencies]
# No required dependencies to simplify build process. TODO: Link to yet to be
# written rationale. Optional For development and testing purposes
# No required dependencies to simplify build process.
# Optional For development and testing purposes.
capstone = { version = "0.13.0", optional = true }
jit = { version = "0.1.0", path = "../jit" }

View File

@ -19,7 +19,16 @@ yjit-libs: $(BUILD_YJIT_LIBS)
$(BUILD_YJIT_LIBS): $(YJIT_SRC_FILES)
$(ECHO) 'building Rust YJIT (release mode)'
$(Q) $(RUSTC) $(YJIT_RUSTC_ARGS)
endif
else ifneq ($(strip $(RLIB_DIR)),) # combo build
# Absolute path to avoid VPATH ambiguity
YJIT_RLIB = $(TOP_BUILD_DIR)/$(RLIB_DIR)/libyjit.rlib
$(YJIT_RLIB): $(YJIT_SRC_FILES)
$(ECHO) 'building $(@F)'
$(Q) $(RUSTC) '-L$(@D)' --extern=jit $(YJIT_RUSTC_ARGS)
$(RUST_LIB): $(YJIT_RLIB)
endif # ifneq ($(strip $(YJIT_LIBS)),)
ifneq ($(YJIT_SUPPORT),no)
$(RUST_LIB): $(YJIT_SRC_FILES)

View File

@ -6,8 +6,8 @@ rust-version = "1.85.0" # Minimally supported rust version
publish = false # Don't publish to crates.io
[dependencies]
# No required dependencies to simplify build process. TODO: Link to yet to be
# written rationale. Optional For development and testing purposes
# No required dependencies to simplify build process.
# Optional For development and testing purposes.
capstone = { version = "0.13.0", optional = true }
jit = { version = "0.1.0", path = "../jit" }

View File

@ -23,7 +23,16 @@ ifneq ($(strip $(ZJIT_LIBS)),)
$(BUILD_ZJIT_LIBS): $(ZJIT_SRC_FILES)
$(ECHO) 'building Rust ZJIT (release mode)'
$(Q) $(RUSTC) $(ZJIT_RUSTC_ARGS)
endif
else ifneq ($(strip $(RLIB_DIR)),) # combo build
# Absolute path to avoid VPATH ambiguity
ZJIT_RLIB = $(TOP_BUILD_DIR)/$(RLIB_DIR)/libzjit.rlib
$(ZJIT_RLIB): $(ZJIT_SRC_FILES)
$(ECHO) 'building $(@F)'
$(Q) $(RUSTC) '-L$(@D)' --extern=jit $(ZJIT_RUSTC_ARGS)
$(RUST_LIB): $(ZJIT_RLIB)
endif # ifneq ($(strip $(ZJIT_LIBS)),)
# By using ZJIT_BENCH_OPTS instead of RUN_OPTS, you can skip passing the options to `make install`
ZJIT_BENCH_OPTS = $(RUN_OPTS) --enable-gems