I have an application where my initial platform was macosx but where I'm now adding multi-platform support. I'm not sure if this is yet stable enough to do a pull request into the guides, but I thought I'd put it here in case it's helpful to others. @chinedufn if you want to use any of this, please feel free to do so without any need for attribution.
My first attempt was just to bundle more targets into a single build artifact. I quickly discovered that lipo only allows a single artifact per architecture (aarch64 vs x86_64), and cannot combine multiple build targets on the same architecture. Therefor I decided to create a different artifact per target, and configure my XCode project to link to a specific file per target platform.
Note that I am using lipo from the coreutils package installed via Homebrew, and in the following snippets I'm replacing my real crate name with <rust-bridge> or <rust_bridge>.
My build-rust.sh script is as follows:
#!/usr/bin/env bash
##################################################
# We call this from an Xcode run script.
##################################################
set -e
export BRIDGE_CRATE=<rust-bridge>
export PATH="$HOME/.cargo/bin:$PATH:/opt/homebrew/bin"
export RUST_LIB_NAME=lib<rust_bridge>
export RUST_LIB="${RUST_LIB_NAME}.a"
### <snipped code to ensure rustc/cargo are in the PATH>
if [[ -z "${PROJECT_DIR}" ]]; then
PROJECT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
echo "PROJECT_DIR: ${PROJECT_DIR}"
fi
if [[ -z "$CONFIGURATION" ]]; then
CONFIGURATION="Debug"
fi
cd $PROJECT_DIR/..
TARGETS=""
NIGHTLY="NO"
if [[ "${PLATFORM_NAME}" = "macosx" ]]; then
TARGETS=(aarch64-apple-darwin x86_64-apple-darwin)
elif [[ "${PLATFORM_NAME}" = "iphonesimulator" ]]; then
TARGETS=(aarch64-apple-ios-sim x86_64-apple-ios)
elif [[ "${PLATFORM_NAME}" = "iphoneos" ]]; then
TARGETS=(aarch64-apple-ios)
elif [[ "${PLATFORM_NAME}" = "appletvsimulator" ]]; then
TARGETS=(aarch64-apple-tvos-sim)
NIGHTLY="YES"
elif [[ "${PLATFORM_NAME}" = "appletvos" ]]; then
TARGETS=(aarch64-apple-tvos x86_64-apple-tvos)
NIGHTLY="YES"
elif [[ "${PLATFORM_NAME}" = "watchsimulator" ]]; then
TARGETS=(aarch64-apple-watchos-sim)
NIGHTLY="YES"
elif [[ "${PLATFORM_NAME}" = "watchos" ]]; then
TARGETS=(aarch64-apple-watchos)
NIGHTLY="YES"
elif [[ "${PLATFORM_NAME}" = "xrsimulator" ]]; then
TARGETS=(aarch64-apple-visionos-sim)
NIGHTLY="YES"
elif [[ "${PLATFORM_NAME}" = "xros" ]]; then
TARGETS=(aarch64-apple-visionos)
NIGHTLY="YES"
else
echo "Unsupported platform \`${PLATFORM_NAME}\`" >&2
exit 1
fi
echo "Rust version ($(which rustc 2>&1)): $(rustc --version 2>&1)" >&2
echo "Rustup version ($(which rustup 2>&1)): $(rustup --version 2>&1)" >&2
echo "BUILDING configuration: ${CONFIGURATION}; platform: ${PLATFORM_NAME}"
## Allows x86_64 architecture in XCode Cloud to cross-compile to aarch64
export SDKROOT=$(xcrun -sdk macosx --show-sdk-path)
export MACOSX_DEPLOYMENT_TARGET=$(xcrun -sdk macosx --show-sdk-platform-version)
if [[ $CONFIGURATION == "Release" ]]; then
if [[ "${NIGHTLY}"] = "YES" ]]; then
for target in ${TARGETS[@]}; do
echo "BUILDING FOR RELEASE (${target})" >&2
RUSTFLAGS="$RUSTFLAGS -A dead_code" cargo +nightly build -Zbuild-std --package ${BRIDGE_CRATE} --no-default-features --release --target "${target}"
done
else
for target in ${TARGETS[@]}; do
echo "BUILDING FOR RELEASE (${target})" >&2
cargo build --package ${BRIDGE_CRATE} --release --target "${target}"
done
fi
UNIVERSAL_BUILD_TARGET="./target/universal/release"
declare -a COMPILED_LIBS
for target in "${TARGETS[@]}"; do
COMPILED_LIBS+=("./target/${target}/release/${RUST_LIB}")
done
elif [[ $CONFIGURATION == "Debug" ]]; then
if [[ "${NIGHTLY}" = "YES" ]]; then
for target in ${TARGETS[@]}; do
echo "BUILDING FOR DEBUG (${target})" >&2
RUSTFLAGS="$RUSTFLAGS -A dead_code" cargo +nightly build -Zbuild-std --package ${BRIDGE_CRATE} --no-default-features --target "${target}"
done
else
for target in ${TARGETS[@]}; do
echo "BUILDING FOR DEBUG (${target})" >&2
cargo build --package ${BRIDGE_CRATE} --target "${target}"
done
fi
UNIVERSAL_BUILD_TARGET="./target/universal/debug"
declare -a COMPILED_LIBS
for target in "${TARGETS[@]}"; do
COMPILED_LIBS+=("./target/${target}/debug/${RUST_LIB}")
done
fi
echo "BUILDING UNIVERSAL TARGET"
mkdir -p "${UNIVERSAL_BUILD_TARGET}"
cat << EOF
lipo
${COMPILED_LIBS[@]}
-create -output "${UNIVERSAL_BUILD_TARGET}/${RUST_LIB_NAME}_${PLATFORM_NAME}.a"
EOF
lipo \
${COMPILED_LIBS[@]} \
-create -output "${UNIVERSAL_BUILD_TARGET}/${RUST_LIB_NAME}_${PLATFORM_NAME}.a"
CHECKFILE="${PROJECT_DIR}/Generated/${BRIDGE_CRATE}/${BRIDGE_CRATE}.swift"
if [[ ! -f "${CHECKFILE}" ]]; then
echo "Failed to find expected generated file after compilation, but did not!" >&2
echo " Expected file: ${CHECKFILE}"
exit 1
fi
In my XCode project, rather than directly linking to the build product (which now includes the PLATFORM_NAME in the file name), I added a new Configuration Settings File with the following contents:
OTHER_LDFLAGS = $(inherited) -l"rust_bridge_$(PLATFORM_NAME)"
I go multiple years between having to configure linkers, so it took me a bit of fighting before I remembered that -l"name" links to libname.a, and I had to exclude lib from the ldflags.
I have an application where my initial platform was
macosxbut where I'm now adding multi-platform support. I'm not sure if this is yet stable enough to do a pull request into the guides, but I thought I'd put it here in case it's helpful to others. @chinedufn if you want to use any of this, please feel free to do so without any need for attribution.My first attempt was just to bundle more targets into a single build artifact. I quickly discovered that
lipoonly allows a single artifact per architecture (aarch64vsx86_64), and cannot combine multiple build targets on the same architecture. Therefor I decided to create a different artifact per target, and configure my XCode project to link to a specific file per target platform.Note that I am using
lipofrom thecoreutilspackage installed via Homebrew, and in the following snippets I'm replacing my real crate name with<rust-bridge>or<rust_bridge>.My
build-rust.shscript is as follows:In my XCode project, rather than directly linking to the build product (which now includes the
PLATFORM_NAMEin the file name), I added a newConfiguration Settings Filewith the following contents:I go multiple years between having to configure linkers, so it took me a bit of fighting before I remembered that
-l"name"links tolibname.a, and I had to excludelibfrom the ldflags.