Source

FROM scratch AS build

ARG VERSION
ARG TARGETARCH
ARG MRUSTC_VERSION
ENV BOOTSTRAP_RUST_VERSION="1.90"

COPY --from=stagex/core-filesystem . /
COPY --from=stagex/core-busybox . /
# As `/bin/bash` is required by `mrustc`, wrap BusyBox's Bourne-inspired shell (`hush`) as `bash`
RUN --network=none printf "%s\n%s\n" '#!/bin/sh' 'hush "$@"' > /usr/bin/bash && chmod +x /usr/bin/bash

COPY --from=stagex/core-make . /
COPY --from=stagex/core-pkgconf . /
COPY --from=stagex/core-python . /

COPY --from=stagex/core-onetbb . /
COPY --from=stagex/core-mold . /

COPY --from=stagex/core-zlib . /
COPY --from=stagex/core-libzstd . /
COPY --from=stagex/core-llvm . /
COPY --from=stagex/core-llvm-libgcc . /
COPY --from=stagex/core-musl . /

COPY --from=stagex/core-ca-certificates . /
COPY --from=stagex/core-curl . /

COPY <<-'EOF' /etc/profile
	set -eux
	if [ "$TARGETARCH" = "amd64" ]; then
		export ARCH="x86_64"
	fi
	if [ "$TARGETARCH" = "arm64" ]; then
		export ARCH="aarch64"
	fi
	export TARGET=${ARCH}-unknown-linux-musl
	export MAKEFLAGS="-j$(nproc)"
	export LLVM_ROOT="/usr/lib/llvm${LLVM_VERSION}"
	if [ "$LLVM_VERSION" = "system" ]; then
		export LLVM_ROOT="/usr"
	fi
	export LLVM_BINDIR=${LLVM_ROOT}/bin
	export LLVM_CONFIG=${LLVM_BINDIR}/llvm-config

	export CC=${LLVM_BINDIR}/clang
	export CXX=clang++
	export CCC_OVERRIDE_OPTIONS="${CCC_OVERRIDE_OPTIONS:-}"
	export CLANG_LIB=/usr/lib/clang/$($CC -dumpversion | cut -d'.' -f1)/lib/${TARGET}
EOF
SHELL ["/bin/sh","-l","-c"]

# Rust defaults to static linking for `musl` targets when we want dynamic linking.
ENV RUSTFLAGS="-Ctarget-feature=-crt-static"
# Don't use the `openssl` vendored within the Rust source tarball
COPY --from=stagex/core-openssl . /
ENV OPENSSL_NO_VENDOR=1

ADD fetch/mrustc-${MRUSTC_VERSION}.tar.gz .
ADD fetch/*.patch .
ADD *.patch .
WORKDIR /mrustc-${MRUSTC_VERSION}
COPY fetch/rustc-${BOOTSTRAP_RUST_VERSION}.0-src.tar.gz .

ENV LLVM_VERSION="system"

# We start by applying our set of patches to `mrustc` and the bootstrapped Rust source
RUN --network=none <<-EOF
	set -eux

	export RUSTC_VERSION="${BOOTSTRAP_RUST_VERSION}.0"

	# Patch a segfault in type inference, fixed after the release was tagged
	patch -p1 -i ../aea331a4f60535eb6c146b82c50255334e4abf3a.patch
	# Patch undefined behavior `mrustc` relies on, `g++` graces, but `clang++` doesn't
	patch -p1 -i ../mrustc-undefined-behavior.patch
	# `inline` a `const static` so its value may be used from distinct objects which don't inherently
	# have visibility. Weirdly, `clang++` only requires this with `-O0` and not `-O1`. Presumably,
	# with even a minimal amount of optimizations, this will already be inlined as necessary.
	sed -i s/"static const unsigned PTR_BASE"/"inline static const unsigned PTR_BASE"/ src/hir/encoded_literal.hpp

	# Patch the patch file `mrustc` will apply itself, as it isn't accepted by `busybox` as-is due to misc oddities
	patch -p1 -i ../patch.patch
	# Apply `mrustc`'s patches to the `rustc` source so it'll build with `mrustc`
	make RUSTCSRC

	RUSTC_SRC=rustc-${BOOTSTRAP_RUST_VERSION}.0-src
	# Remove binaries shipped within the source tarball
	for file in $(find $RUSTC_SRC -type f | grep -E "\.(o|a|so|lib|dll|exe)$"); do
		rm $file
	done
	# Strip executable bits from everything marked executable
	# This doesn't erase such files outright as a variety of scripts are marked executable
	# This does stop outright execution of any compiled artifacts included within the source tarball
	for executable in $(find $RUSTC_SRC -type f -type f -executable); do
		chmod -x $executable
	done

	# `mrustc` wants to build the LLVM toolchain vendored within Rust. We don't
	# Shim the build directory to our existing LLVM toolchain
	ln -s $LLVM_ROOT $RUSTC_SRC/build
	# Stub a `Makefile` so `mrustc`'s `Makefile` thinks the target was created and it can itself build LLVM
	echo "all:" > $RUSTC_SRC/build/Makefile

	# `rustix`'s ASM (as transpiled by `mrustc`) is rejected by `clang`, so use the `libc` backend
	for rustix in $(ls $RUSTC_SRC/vendor | grep rustix); do
		for toml in $(find $RUSTC_SRC/vendor/$rustix -name "Cargo.toml"); do
			sed -i s/'default = \['/'default = \["use-libc",'/ $toml
		done
	done

	# Build a dynamically-linked `rustc`
	sed -i s/'RUSTFLAGS="'/'RUSTFLAGS="-Ctarget-feature=-crt-static '/ run_rustc/Makefile
EOF

# We now prepare our working tree by defining what will be Rust's self-contained libraries
RUN --network=none <<-EOF
	set -eux

	mkdir self-contained
	for file in rcrt1.o crti.o crtn.o; do
		cp /usr/lib/${file} self-contained/
	done
	for file in crtbegin crtend; do
		cp $CLANG_LIB/clang_rt.${file}.o self-contained/${file}S.o
	done
	# Populate each prefix `mrustc` will build with the self-contained toolchain
	for prefix in prefix-s prefix-2 prefix; do
		PREFIX_LIB=run_rustc/output/$prefix/lib/rustlib/$TARGET/lib
		mkdir -p $PREFIX_LIB
		ln -s $(realpath ./self-contained) $PREFIX_LIB/self-contained
	done
EOF

# Build `mrustc`, `minicargo`
RUN --network=none <<-EOF
	set -eux

	# TODO: Revisit why this requires disabling `libc++`'s assertions
	make CXXFLAGS="-g0 -O2 -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_NONE"
	make -f minicargo.mk bin/minicargo
EOF

# Build `rustc`, `cargo` with `mrustc`, `minicargo`
RUN --network=none <<-EOF
	set -eux

	# Because the following `Makefile`s don't observe `$LD`, install `mold` as `ld`
	ln -sf /usr/bin/mold /usr/bin/ld

	export RUSTC_TARGET="${TARGET}"
	export RUSTC_VERSION="${BOOTSTRAP_RUST_VERSION}.0"
	export RUSTC_INSTALL_BINDIR="bin"
	export OUTDIR_SUF=
	export MRUSTC_TARGET_VER="${BOOTSTRAP_RUST_VERSION}"

	# Don't compile any debug information
	export CCC_OVERRIDE_OPTIONS="$CCC_OVERRIDE_OPTIONS +-g0"
	# Remove `gcc`-specific flags since we're using `clang`
	# Limit optimizations due to `clang`-compiled `mrustc` output seemingly having undefined behavior
	export CCC_OVERRIDE_OPTIONS="$CCC_OVERRIDE_OPTIONS x-fno-tree-sra O2 +-fno-strict-return +-fno-delete-null-pointer-checks"

	# Explicitly disable usage of `rustix`'s' 'raw' (assembly) backend, which `mrustc` compiles to
	# an inline `__asm__` which `gcc` accepts yet `clang` does not. While we disable some vendored
	# libraries with `ENV`, we only do this here as it's a bootstrap-only limitation, not a
	# preference.
	export CARGO_CFG_RUSTIX_NO_LINUX_RAW=1

	# Compile `rustc`, `cargo`
	make -f minicargo.mk output/rustc
	make -f minicargo.mk output/cargo
EOF


RUN --network=none <<-EOF
	set -eux

	export RUSTC_TARGET="${TARGET}"
	export RUSTC_VERSION="${BOOTSTRAP_RUST_VERSION}.0"
	export RUSTC_INSTALL_BINDIR="bin"
	export OUTDIR_SUF=

	export CCC_OVERRIDE_OPTIONS="$CCC_OVERRIDE_OPTIONS +-g0"

	make -C run_rustc

	# Move the sysroot `mrustc` created to the format expected by the rest of this process
	mkdir /rust-${RUSTC_VERSION}
	cp -R run_rustc/output/prefix /rust-${RUSTC_VERSION}/usr
EOF

WORKDIR /

ENV CONFIGURE_FLAGS=
COPY --chmod=0755 <<-'EOF' build
	set -eux
	TARGET_VERSION=${1}
	BUILD_VERSION=${2}
	TOOLS=${3:-cargo}
	PREFIX=/rust-${TARGET_VERSION}/usr
	BUILD_PREFIX=/rust-${BUILD_VERSION}/usr
	export AS=${LLVM_BINDIR}/llvm-as
	export AR=${LLVM_BINDIR}/llvm-ar
	export NM=${LLVM_BINDIR}/llvm-nm
	export DWP=${LLVM_BINDIR}/llvm-dwp
	export RANLIB=${LLVM_BINDIR}/llvm-ranlib
	export READELF=${LLVM_BINDIR}/llvm-readelf
	export STRIP=${LLVM_BINDIR}/llvm-strip
	export OBJCOPY=${LLVM_BINDIR}/llvm-objcopy
	export OBJDUMP=${LLVM_BINDIR}/llvm-objdump
	export SIZE=${LLVM_BINDIR}/llvm-size
	export LIBCC="$CLANG_LIB/libclang_rt.builtins.a"
	ln -sf ${LLVM_BINDIR}/clang++ /usr/bin/c++
	ln -sf ${LLVM_BINDIR}/clang /usr/bin/cc
	ln -sf /usr/bin/mold /usr/bin/ld
	cd rustc-${TARGET_VERSION}-src

	# Remove binaries shipped within the source tarball
	for file in $(find . -type f | grep -E "\.(o|a|so|lib|dll|exe)$"); do
		rm $file
	done
	# Strip executable bits from everything marked executable
	# This doesn't erase such files outright as a variety of scripts are marked executable
	# This does stop outright execution of any compiled artifacts included within the source tarball
	for executable in $(find . -type f -type f -executable); do
		chmod -x $executable
	done
	# Remove all outstanding `cargo-checksum` files as we removed files they may or may not commit to
	for crate in $(ls ./vendor); do
		if [ -f "./vendor/$crate/.cargo-checksum.json" ]; then
			# Outright removal of this file will cause an error
			# It being a stub with no actual metadata won't however
			echo '{ "files": {} }' > "./vendor/$crate/.cargo-checksum.json"
		fi
	done
	# Remove all checksums from existing lockfiles as they've now been invalidated.
	for file in $(find . -type f -name "Cargo.lock"); do
		WITHOUT_CHECKSUMS=$(grep -v '^checksum = "' "$file")
		echo "$WITHOUT_CHECKSUMS" > $file
	done
	# Remove specification of the `--frozen` argument, saying not to use the exact existing lockfiles,
	# with the `--offline` argument, saying not to use the internet to decide the lockfile.
	for file in $(find ./src/bootstrap -type f); do
		sed -i s/'"--frozen"'/'"--offline"'/ "$file"
	done

	export RUSTFLAGS="${RUSTFLAGS:-}"

	# Tune how `rustc` is built for a faster build process.
	export RUSTFLAGS="-Clinker=clang -Clink-arg=-Wl,-fuse-ld=mold $RUSTFLAGS"
	# For intermediary compilers, which aren't part of the committed output, we're more aggressive.
	if [ ! "$TARGET_VERSION" = "$VERSION" ]; then
		# Disable generating/embedding LLVM bitcode within the objects as it won't be used
		export RUSTFLAGS="-Cembed-bitcode=false $RUSTFLAGS"

		# Enable native codegen as this intermediary step doesn't have to be reproducible
		export RUSTFLAGS="-Ctarget-cpu=native $RUSTFLAGS"
		# But handle the edge-case https://github.com/rust-lang/rust/pull/148841 presents
		if [ $(printf "%s" "$BUILD_VERSION" | cut -d'.' -f2) -lt 93 ]; then
			export RUSTFLAGS="$RUSTFLAGS -Ctarget-feature=-avx512f"
		fi

		# Set `codegen-units` to `nproc`, which will break reproducible builds as Rust builds are only
		# deterministic with a consistent amount of units.
		export RUSTFLAGS="-Ccodegen-units=$(nproc) $RUSTFLAGS"

		# Also, if the compiler is sufficiently modern, enable the threaded frontend. This was available
		# under nightly as soon ~1.75, but we delay enabling it until it was more tested and stable.
		# Note the usage of threads may produce a non-deterministic output without flags such as
		# `-Zcodegen-source-order` to sort the outputs after their compilation.
		if [ $(printf "%s" "$BUILD_VERSION" | cut -d'.' -f2) -ge 85 ]; then
			THREADS_TO_USE=8
			if [ $(nproc) -lt $THREADS_TO_USE ]; then
				THREADS_TO_USE=$(nproc)
			fi
			export RUSTFLAGS="-Zthreads=$THREADS_TO_USE $RUSTFLAGS"
		fi
	fi

	python3 ./src/bootstrap/configure.py \
		--build="${TARGET}" \
		--host="${TARGET}" \
		--target="${TARGET}" \
		--local-rust-root="${BUILD_PREFIX}" \
		--tools="${TOOLS}" \
		--llvm-root="${LLVM_ROOT}" \
		--llvm-libunwind="system" \
		--enable-local-rust \
		--enable-clang \
		--enable-option-checking \
		--enable-vendor \
		--dist-compression-formats=gz \
		--disable-docs \
		--python="python3" \
		--prefix="${PREFIX}/usr" \
		--sysconfdir="${PREFIX}/etc" \
		--release-channel="stable" \
		--set="install.prefix=${PREFIX}" \
		--set="target.${TARGET}.crt-static=false" \
		--set="target.${TARGET}.musl-root=/usr" \
		--set="target.${TARGET}.llvm-config=${LLVM_BINDIR}/llvm-config" \
		--set="rust.optimize=2" \
		--set="rust.lld=false" \
		--set="rust.llvm-tools=false" \
		$CONFIGURE_FLAGS

	if [ ! "${TARGET_VERSION}" = "${VERSION}" ]; then
		# We only build the first-stage Rust compiler, as sufficient for further bootstrapping.
		#
		# We specifically choose `library`, as in, the Rust standard library, as what to build up to.
		#
		# For more context on the changes made, see
		# https:/blog.rust-lang.org/inside-rust/2025/05/29/redesigning-the-initial-bootstrap-sequence.
		python3 x.py build --stage 1 library
		STAGE_FOR_CARGO=1
		if [ $(printf "%s" "$TARGET_VERSION" | cut -d'.' -f2) -lt 90 ]; then
			# We specify to build `cargo` with the original toolchain, as acceptable, as building with the
			# just-built toolchain causes Rust's bootstrap to try to flush it out _much_ more.
			STAGE_FOR_CARGO=0
		fi

		# We also build `cargo`, as required to continue the bootstrap.
		python3 x.py build --stage $STAGE_FOR_CARGO src/tools/cargo

		# Manually 'install' it
		mkdir -p ${PREFIX}
		mv build/host/stage1/* ${PREFIX}/
		mv $(find build/host/ -type f -name "cargo" | head -n1) ${PREFIX}/bin/
	else
		python3 x.py install
	fi

	cd /
	[ "${TARGET_VERSION}" == "${VERSION}" ] || rm -rf rustc-${TARGET_VERSION}-src /rust-${BUILD_VERSION}
EOF

ADD fetch/rustc-1.91.1-src.tar.gz .
RUN --network=none ./build 1.91.1 1.90.0 ""

ADD fetch/rustc-1.92.0-src.tar.gz .
RUN --network=none ./build 1.92.0 1.91.1 ""

ADD fetch/rustc-1.93.1-src.tar.gz .
RUN --network=none ./build 1.93.1 1.92.0 ""

ADD fetch/rustc-1.94.0-src.tar.gz .

# Patch the default for `musl` targets to be dynamically, not statically, linked
RUN --network=none <<-EOF
	set -eux
	for file in $(find ./rustc-${VERSION}-src/compiler/rustc_target/src/spec/targets/ -type f | grep "musl"); do
		sed -i s/"crt_static_default = true"/"crt_static_default = false"/ $file
	done
EOF

RUN --network=none ./build ${VERSION} 1.93.1 cargo,clippy,rustdoc,rustfmt,rust-demangler,src
RUN --network=none <<-EOF
	set -eux
	mv /rust-${VERSION} /rootfs
	find /rootfs -type f -name "*.log" -delete
	find /rootfs -type f -name "*.a" -delete
	for manifest in $(find /rootfs -type f -name "manifest-*"); do
		sort -o $manifest $manifest
	done
EOF

FROM stagex/core-filesystem AS package-rust
COPY --from=build /rootfs/ /
Copied to clipboard!