The problem is that rustc emits synthetic libraries (symbols.o and lib.rmeta) which Apple's ld then chokes on when targetting aarch64-apple-ios-macabi, as they lack an LC_BUILD_VERSION load command to identify them as being intended for use with Catalyst.
The errors produced look like:
ld: in /Users/matthew/workspace/matrix-rust-sdk/target/aarch64-apple-ios-macabi/dbg/deps/libstatic_assertions-fdafb4b8ba800a8a.rlib(lib.rmeta), building for Mac Catalyst, but linking in object file built for , file '/Users/matthew/workspace/matrix-rust-sdk/target/aarch64-apple-ios-macabi/dbg/deps/libstatic_assertions-fdafb4b8ba800a8a.rlib' for architecture arm64
The key thing being that the lib.rmeta file here within the .rlib archive has a blank platform, which ld refuses to link in with the other Catalyst platform objects & libraries.
rustc generates object files using https://github.com/gimli-rs/object, which doesn't expose an API to add an LC_BUILD_VERSION on Mach-O objects. So I added one:
diff --git a/Cargo.toml b/Cargo.toml
index b08eec8..c840cf1 100755
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "object"
-version = "0.30.0"
+version = "0.29.0"
edition = "2018"
exclude = ["/.github", "/testfiles"]
keywords = ["object", "elf", "mach-o", "pe", "coff"]
diff --git a/src/lib.rs b/src/lib.rs
index 40f17c0..968496e 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -56,6 +56,10 @@
#![deny(missing_debug_implementations)]
#![no_std]
// Style.
+
+// let us be built directly by rustc as a path dependency
+#![allow(elided_lifetimes_in_paths, rustc::default_hash_types, explicit_outlives_requirements)]
+
#![allow(clippy::collapsible_if)]
#![allow(clippy::comparison_chain)]
#![allow(clippy::match_like_matches_macro)]
diff --git a/src/write/macho.rs b/src/write/macho.rs
index 8ef722f..628a05d 100644
--- a/src/write/macho.rs
+++ b/src/write/macho.rs
@@ -1,4 +1,5 @@
use core::mem;
+use core::convert::TryInto;
use crate::endian::*;
use crate::macho;
@@ -211,6 +212,15 @@ impl<'a> Object<'a> {
let mut ncmds = 0;
let command_offset = offset;
+ // Calculate size of build version
+ let build_version_offset = offset;
+ let mut build_version_len = 0;
+ if self.platform > 0 {
+ build_version_len = mem::size_of::<macho::BuildVersionCommand<Endianness>>();
+ offset += build_version_len;
+ ncmds += 1;
+ }
+
// Calculate size of segment command and section headers.
let segment_command_offset = offset;
let segment_command_len =
@@ -358,6 +368,18 @@ impl<'a> Object<'a> {
},
);
+ if self.platform > 0 {
+ debug_assert_eq!(build_version_offset, buffer.len());
+ buffer.write(&macho::BuildVersionCommand {
+ cmd: U32::new(endian, macho::LC_BUILD_VERSION),
+ cmdsize: U32::new(endian, build_version_len.try_into().unwrap()),
+ platform: U32::new(endian, self.platform),
+ minos: U32::new(endian, self.minos),
+ sdk: U32::new(endian, self.sdk),
+ ntools: U32::new(endian, 0),
+ });
+ }
+
// Write segment command.
debug_assert_eq!(segment_command_offset, buffer.len());
macho.write_segment_command(
diff --git a/src/write/mod.rs b/src/write/mod.rs
index aa4980b..ebdf7d7 100644
--- a/src/write/mod.rs
+++ b/src/write/mod.rs
@@ -70,6 +70,10 @@ pub struct Object<'a> {
pub mangling: Mangling,
/// Mach-O "_tlv_bootstrap" symbol.
tlv_bootstrap: Option<SymbolId>,
+ /// Mach-O platform details
+ platform: u32,
+ minos: u32,
+ sdk: u32,
}
impl<'a> Object<'a> {
@@ -88,6 +92,9 @@ impl<'a> Object<'a> {
flags: FileFlags::None,
mangling: Mangling::default(format, architecture),
tlv_bootstrap: None,
+ platform: 0,
+ minos: 0,
+ sdk: 0,
}
}
@@ -298,6 +305,13 @@ impl<'a> Object<'a> {
comdat_id
}
+ /// Add a build version load command (Mach-O only); needed for macabi to link.
+ pub fn set_build_version(&mut self, platform: u32, minos: u32, sdk: u32) {
+ self.platform = platform;
+ self.minos = minos;
+ self.sdk = sdk;
+ }
+
/// Get the `SymbolId` of the symbol with the given name.
pub fn symbol_id(&self, name: &[u8]) -> Option<SymbolId> {
self.symbol_map.get(name).cloned()
Then, you need to build a custom rustc toolchain against the local object
dependency:
diff --git a/Cargo.toml b/Cargo.toml
index 000c10a1f90..23a5753c1b1 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -105,6 +105,7 @@ object.debug = 0
# See comments in `src/tools/rustc-workspace-hack/README.md` for what's going on
# here
rustc-workspace-hack = { path = 'src/tools/rustc-workspace-hack' }
+object = { path = '/Users/matthew/workspace/object' }
# See comments in `library/rustc-std-workspace-core/README.md` for what's going on
# here
diff --git a/compiler/rustc_codegen_ssa/src/back/metadata.rs b/compiler/rustc_codegen_ssa/src/back/metadata.rs
index 51c5c375d51..953a48a0d9d 100644
--- a/compiler/rustc_codegen_ssa/src/back/metadata.rs
+++ b/compiler/rustc_codegen_ssa/src/back/metadata.rs
@@ -133,6 +133,16 @@ pub(crate) fn create_object_file(sess: &Session) -> Option<write::Object<'static
};
let mut file = write::Object::new(binary_format, architecture, endianness);
+
+ // macOS ld won't link macabi ABIs which are missing a build_version
+ if sess.target.llvm_target.ends_with("-macabi") {
+ file.set_build_version(
+ object::macho::PLATFORM_MACCATALYST,
+ 0x000E0000, // minOS 14.0
+ 0x00100200, // SDK 16.2
+ );
+ }
+
let e_flags = match architecture {
Architecture::Mips => {
let arch = match sess.target.options.cpu.as_ref() {
diff --git a/compiler/rustc_target/src/spec/x86_64_apple_ios_macabi.rs b/compiler/rustc_target/src/spec/x86_64_apple_ios_macabi.rs
index 0f3f8519963..5d307ac4f3a 100644
--- a/compiler/rustc_target/src/spec/x86_64_apple_ios_macabi.rs
+++ b/compiler/rustc_target/src/spec/x86_64_apple_ios_macabi.rs
@@ -2,7 +2,7 @@
use crate::spec::{Cc, LinkerFlavor, Lld, StackProbeType, Target, TargetOptions};
pub fn target() -> Target {
- let llvm_target = "x86_64-apple-ios13.0-macabi";
+ let llvm_target = "x86_64-apple-ios15.0-macabi";
let arch = Arch::X86_64_macabi;
let mut base = opts("ios", arch);
N.B. needing to downgrade the local object
checkout to 0.29.0 to persuade rustc to use it, as well as tweaking
its lint rules to let it build in rustc's strict environment.
To build rustc, you just ./x.py build && ./x.py install
To link the result as a custom toolchain, you rustup toolchain link rust-catalyst /usr/local/rust
or wherever.
Then to actually build the library under Catalyst (matrix-rust-sdk in this instance), you have to tell cargo
to use the right toolchain (if it's not the default) and build with -Z build-std
as there's no prebuilt
standard library for our custom toolchain.
For matrix-rust-sdk this poses another problem, because ruma-common depends on indexmap which chokes by default
if there's no std
. So you have to run a custom ruma with std
as an explicit feature:
diff --git a/crates/ruma-common/Cargo.toml b/crates/ruma-common/Cargo.toml
index 83f22461..2aa36f8a 100644
--- a/crates/ruma-common/Cargo.toml
+++ b/crates/ruma-common/Cargo.toml
@@ -54,7 +54,7 @@ form_urlencoded = "1.0.0"
getrandom = { version = "0.2.6", optional = true }
html5ever = { version = "0.25.2", optional = true }
http = { workspace = true, optional = true }
-indexmap = { version = "1.9.1", features = ["serde"] }
+indexmap = { version = "1.9.1", features = ["serde", "std"] }
itoa = "1.0.1"
js_int = { workspace = true, features = ["serde"] }
js_option = "0.1.0"
...and then link that in from matrix-rust-sdk with the tweaked framework build script like so:
diff --git a/Cargo.toml b/Cargo.toml
index 6d2b68fe..4f96eee9 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -19,8 +19,10 @@ resolver = "2"
rust-version = "1.65"
[workspace.dependencies]
-ruma = { git = "https://github.com/ruma/ruma", rev = "284b797e0513daf56859b64b8c7a506856fb11ec", features = ["client-api-c"] }
-ruma-common = { git = "https://github.com/ruma/ruma", rev = "284b797e0513daf56859b64b8c7a506856fb11ec" }
+#ruma = { git = "https://github.com/ruma/ruma", rev = "284b797e0513daf56859b64b8c7a506856fb11ec", features = ["client-api-c"] }
+ruma = { path = "/Users/matthew/workspace/ruma/crates/ruma", features = ["client-api-c"] }
+#ruma-common = { git = "https://github.com/ruma/ruma", rev = "284b797e0513daf56859b64b8c7a506856fb11ec" }
+ruma-common = { path = "/Users/matthew/workspace/ruma/crates/ruma-common" }
tracing = { version = "0.1.36", default-features = false, features = ["std"] }
uniffi = { git = "https://github.com/mozilla/uniffi-rs", rev = "249a78b6f3f35661f1530e53811134e1bf012608" }
uniffi_macros = { git = "https://github.com/mozilla/uniffi-rs", rev = "249a78b6f3f35661f1530e53811134e1bf012608" }
diff --git a/bindings/apple/README.md b/bindings/apple/README.md
index 710e891b..778db18a 100644
--- a/bindings/apple/README.md
+++ b/bindings/apple/README.md
@@ -27,6 +27,10 @@ For development purposes, it will additionally generate a `Package.swift` file i
When building the SDK for release you should pass the `--release` argument to the task, which will strip away any symbols and optimise the created binary.
+You can also pass in --only-target to speed things up and build a single target (e.g. aarch64-apple-ios)
+
+The resulting framework can then be added from xcode via the Package.swift found at the root of the repository.
+
## Building only the Crypto SDK
diff --git a/xtask/src/swift.rs b/xtask/src/swift.rs
index 31dbdb6b..14cf80c1 100644
--- a/xtask/src/swift.rs
+++ b/xtask/src/swift.rs
@@ -115,7 +115,7 @@ fn generate_uniffi(library_file: &Path, ffi_directory: &Path) -> Result<()> {
}
fn build_for_target(target: &str, profile: &str) -> Result<PathBuf> {
- cmd!("cargo build -p matrix-sdk-ffi --target {target} --profile {profile}").run()?;
+ cmd!("cargo +rust-catalyst build -p matrix-sdk-ffi -Z build-std --target {target} --profile {profile}").run()?;
// The builtin dev profile has its files stored under target/debug, all
// other targets have matching directory names
You can then build the catalyst framework with:
cargo xtask swift build-framework --only-target aarch64-apple-ios-macabi
Having imported the matrix-rust-sdk repo as a swift package into XCode, the final tweak needed to build Element X is to switch to using OIDExternalUserAgentCatalyst rather than OIDExternalUserAgentiOS.
Feb 2024 update:
I just built Element X for catalyst again. This time rustc worked (thanks to rust-lang/rust#106021 getting fixed - yay)
However, i hit some other problems; might as well document them here for posterity:
std
feature if building without a std library (as rust doesn't ship a prebuilt std library for catalyst). You can't enable thestd
feature at the top level Cargo.toml, as the workspace dependencies are overridden by the explicit crate dependencies, and you can't patch crates.io to enable features. So instead, the fix is to add the dep to a concrete crate under your control such as matrix-sdk-common - and then the feature will be applied to other transitive instances of the crate:Then, you can build the sdk for catalyst:
cargo xtask swift build-framework --only-target aarch64-apple-ios-macabi
...and have a script to build the framework for catalyst (derived from the main one, which targets iOS + sim):
(you probably don't need to actually expand & recompress the library and could hit it with perl/sed in situ, but i had it expanded anyway to check more carefully what was going on, and just in case
ar
does any LZ style compression these days).Then, I yanked out a few other libraries which hadn't been built for catalyst (MapLibreGL and SwiftOgg), and it built.