I'm trying to build a portable rustfmt binary that will allow me to format rust code without needing to install rust on the machine. The usecase I have in mind is specifically formatting on our CI machines where we do not have rust installed.
We could install rustup on the machine and use it to install rust -- but that is a decent chunk of time that would be spent downloading and setting up rust just to run the formatter. A stand-alone binary could be a fraction of the size and thus allow us to be lighter and faster.
We can see that this binary is wired to look for librustc:
$ otool -L ~/Downloads/rustfmt_macos-x86_64_v1.6.0/rustfmt
~/Downloads/rustfmt_macos-x86_64_v1.6.0/rustfmt:
@rpath/librustc_driver-f2b62a1142f55552.dylib (compatibility version 0.0.0, current version 0.0.0)
@rpath/libstd-46630039a22cca11.dylib (compatibility version 0.0.0, current version 0.0.0)
/usr/lib/libiconv.2.dylib (compatibility version 7.0.0, current version 7.0.0)
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1319.0.0)
/usr/lib/libz.1.dylib (compatibility version 1.0.0, current version 1.2.11)
/usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 1300.36.0)
Which means that running it without a rust install crashes out:
$ ~/Downloads/rustfmt_macos-x86_64_v1.6.0/rustfmt
dyld[73925]: Library not loaded: @rpath/librustc_driver-f2b62a1142f55552.dylib
Referenced from: <D1A7C532-F20D-3F3C-9EC8-7223956328D4> ~/Downloads/rustfmt_macos-x86_64_v1.6.0/rustfmt
Reason: tried: '/usr/local/lib/librustc_driver-f2b62a1142f55552.dylib' (no such file), '/usr/lib/librustc_driver-f2b62a1142f55552.dylib' (no such file, not in dyld cache)
Is there any way that I can create a portable binary here that can be run without a rust install?
I'd be more than happy if we had to create this binary ourselves -- I just wasn't sure if it was even possible based on my poking around and rough understanding of how the builds worked.
I think you can download and install the rustfmt component directly. for example. download the channel manifest first, parse and extract the url of the rustfmt component from the manifest, and then download the rustfmt package.
Sadly this is just an isolated download of the rustfmt binary, but that binary is still dynamically linked to the installed rust libs:
$ otool -L ~/Downloads/rustfmt-1.81.0-aarch64-apple-darwin/rustfmt-preview/bin/rustfmt
~/Downloads/rustfmt-1.81.0-aarch64-apple-darwin/rustfmt-preview/bin/rustfmt:
@rpath/librustc_driver-e28c98d18becddb2.dylib (compatibility version 0.0.0, current version 0.0.0)
@rpath/libstd-0f9bda72675979e4.dylib (compatibility version 0.0.0, current version 0.0.0)
/usr/lib/libiconv.2.dylib (compatibility version 7.0.0, current version 7.0.0)
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1319.100.3)
/usr/lib/libz.1.dylib (compatibility version 1.0.0, current version 1.2.11)
/usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 1500.65.0)
So you still need to separately download and include the rust libs.
so rustfmt depends on certain internal rustc components. in that case, I don't know how (if possible) to build a completely statically linked binary. as far as I know, the rustc-dev components only contains shared libraries (along the necessary rmeta files) to be linked. maybe it's possible if you build a customized toolchain yourself, but I don't know much about this subject, you'll have to do your own research. the rustc-dev-guide should be a good starting point.
For now, at least, I've solved this by bundling rustfmt for our dependency management system along with the rust libs and including an executable which passes the dynamic link variable with the lib path.
VERSION="1.78.0"
ARCHITECTURES=(
"aarch64-apple-darwin"
"x86_64-apple-darwin"
"x86_64-unknown-linux-gnu"
)
for arch in "${ARCHITECTURES[@]}"; do
RUST_NAME="rust-$VERSION-$arch"
LANGUAGE_TARBALL="$RUST_NAME.tar.gz"
RUSTFMT_TARBALL="rustfmt.tar.gz"
curl -L -O "https://static.rust-lang.org/dist/$LANGUAGE_TARBALL"
# extract rust
mkdir -p ./rustlang_extract
tar -xvzf "$LANGUAGE_TARBALL" -C ./rustlang_extract
# repackage rust with just rustfmt
mkdir -p ./rustfmt/bin
cp "./rustlang_extract/$RUST_NAME/rustfmt-preview/bin/rustfmt" ./rustfmt/bin
cp -r "./rustlang_extract/$RUST_NAME/rustc/lib/" ./rustfmt/lib
rm -rf "./rustfmt/lib/rustlib"
# rustfmt (as with all rust binaries) dynamically link to the rust libs -- so we need to wrap
# the binaries with a shell scripts so that we can set the link path
if [[ $arch == *darwin ]]; then
LD_ENV_VAR_NAME="DYLD_FALLBACK_LIBRARY_PATH"
else
LD_ENV_VAR_NAME="LD_LIBRARY_PATH"
fi
cat > ./rustfmt/rustfmt <<EOL
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="\$(cd "\$(dirname "\${BASH_SOURCE[0]}")" && pwd)"
$LD_ENV_VAR_NAME="\$SCRIPT_DIR/lib:\${$LD_ENV_VAR_NAME:-}" exec "\$SCRIPT_DIR/bin/rustfmt" "\$@"
EOL
chmod +x ./rustfmt/rustfmt
tar -czvf "$RUSTFMT_TARBALL" -C ./rustfmt .
# <<< upload to our dep management cache here >>>
done