-
Notifications
You must be signed in to change notification settings - Fork 2.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support for macOS Universal/fat binaries #8875
Comments
Seems reasonable to me to support! Cargo already had (unstable) support to build multiple targets at once, and it sounds like that's almost exactly what this wants (with just one final step). I think the first step here would be for a proposal to be made followed by an unstable implementation. |
To add to this, its seems currently when trying to link a universal binary it will fail with the following error:
It would be nice if if I could just link towards a universal binary and cargo would be able to link it. This will probably have a lot more edge cases since there are multiple ways too link a library. Currently the alternative is to add a build step using |
Can we also add a way to cross-compile to a macOS universal binary? I know of a few projects that build their macOS binaries on Linux - they would also want a way to build universal binaries. Specifically:
Does specifying |
Is this issue about being able to link to fat libraries, or about producing fat libraries without having to manually call lipo post-build in cargo? Both are a problem, but the most pressing one is just being able to link against fat libraries without the "file too small to be an archive" error. |
I've meant this as a feature request for building fat binaries. I think "failed to add native library" could be considered a bug/incompatibility, and handled separately. |
@kornelski you can use cargo-lipo today to simplify the process, but you'll still be forced to link against thin libraries because of a long-time linker issue. The linker currently doesn't handle the fat library header correctly, which was a very annoying problem for iOS, but now I guess it's going to become an even bigger problem with macOS and the ARM transition. It was a problem for macOS when we still made fat libraries for intel 32-bit and 64-bit, but since 32-bit was dropped most macOS libraries have become thin libraries. This is no longer the case because of ARM64, so we'd really appreciate a fix for the linker as a first step to make this more pleasant :) |
How to achieve this? In my opinion, it may require to build two binary files, and do some work after building is finished. Can we achieve this by adding a cargo script, to finish generating the output file after all binaries (in this case, same code but different target) are built? |
It is better to simply build once per architecture and then combine the single-arch binaries (thin) into a multi-arch binary (fat). See my previous answer for how it can be done today using cargo-lipo, but there is also a long-standing linker bug that prevents linking against fat libraries. |
I support this proposal and think that
At some point Xcode will remove x86_64 from their list of standard release architectures, and at that point (or a certain period after) |
I wonder at which level it should be done in Cargo, given that Rust has a concept of a target, and obviously it'd be very weird if I don't think So I suppose all the universal magic would have to be limited to invocation of |
@kornelski I second this, we shouldn't add a "universal" target, because it just makes everything much more complicated, without considering the fact that "universal" is not even a target, and could mean more than one thing (it is a combination of multiple targets, but we don't know which ones). I think the current cargo-lipo solution could simply be ported directly into cargo instead of being a separate tool: basically keep building for one target at a time, but make it possible to call cargo telling it to build for multiple targets and produce universal binaries. Under the hood all it does is build for each target + combine the multiple thin binaries into a single fat binary using lipo. The only downside is that this won't solve the problem of cross-compiling, but there are many other issues that make this quite difficult anyway, starting by the availability of tools like "lipo" outside of Xcode on macOS. This tool is open sourced by Apple as part of the cctools package, but they never bothered porting it to other platforms themselves. There exists a Linux port of cctools with a copy of lipo I have successfully used myself: https://github.com/tpoechtrager/cctools-port I don't think we need to go through all this trouble, a first improvement to include the functionality from cargo-lipo directly into cargo while relying on the presence of a "lipo" command-line tool (we could support it on Linux if you install the cctools-port) would already be more than good enough. |
FYI, I have implemented a "lipo" like crate recently: https://github.com/messense/fat-macho-rs |
I think we may have circled all the way back to @ alexcrichton's initial response at the top of the issue. #8176 is what he was referring to by existing unstable multi target support. You would just need a way to tell cargo to add the build step afterwards. I don't think it should be a default for all release builds on macOS. While you can run benchmarks with cargo, perhaps the most common reason for building in release mode is to test speed. I don't want to double the LLVM codegen time and linking time which is already significantly slower on macOS (ld64) without lld support for mach-o, just so people don't have to change their dist build script to add some flag. I'm pretty sure every single distribution of code to macOS has had to touch their release scripts. Raw suggestion which is quite extensive and a lot of work probably? But have a look: [lib]
...
[lib.targets.macos-universal]
crate-type = ["...", "..."]
targets = ["x86_64-apple-darwin", "aarch64-apple-darwin"]
# and either
post-build = "lipo_universal.rs"
# runs lipo on the outputs that it reads from env variables.
# Pretty easy to write this.
# Then you can use this for arbitrary post-processing including strip, etc...
# or supply a list of builtin post-processing steps.
post-build = ["lipo"]
# or both! Cargo's builtins run first. Need to name them differently. Or recognise file names ending with .rs and have them all in the one list. Some questions/thoughts:
Ok big pie on the sky idea here, but:
Ok, I'll stop there. |
Post build script is a more general way. On other platforms we also need a way to fuse code from different architecture, or we can do more work like what we already did in build.rs. |
Some further comments on reflection:
|
I think involving custom post-build scripts here is a mistake. This problem is not a custom job, it's a common requirement for an entire platform. Note that the baseline for this is something like:
cargo build --target=aarch64-apple-darwin
cargo build --target=x86_64-apple-darwin
lipo -create foo target/aarch64-apple-darwin/release/foo target/x86_64-apple-darwin/release/foo so if users had to write custom TOML config or custom scripts in Rust that implement the same thing, it wouldn't simplify anything. And trying to solve all the problems of wrangler, cbindgen, IDE integration, etc. at the same time will mean this issue will be paralyzed by additional incompatible requirements, scope creep, and won't get done (at least not before Apple drops x86 support making it moot ;) |
For configuration, I suggest:
It could be controlled with:
and [profiles.dev]
apple-universal = true
[profiles.release]
apple-universal = false or |
The
|
Anything new here? |
On deeper investigation the existing rustc bug (rust-lang/rust#55235) is probably the best place to track this -- ability to link static |
intel binary on aarch64 causes downstream software to mis-diagnose the platform and all heck breaks loose. on mac, building a universal binary will solve this: rust-lang/cargo#8875
This just used lipo libftd2xx.a -thin x86_64 -output x86_64/libftd2xx.a lipo libftd2xx.a -thin arm64 -output arm64/libftd2xx.a See rust-lang/cargo#8875 (comment)
@randomairborne came up with a simple tool to build universal binaries: |
For a quick fix, the Golang rewrite of Tools like cargo-lipo, and Tauri's bundler use I'm considering RIIR |
I think llvm-lipo is also another alternative to macOS lipo. |
This is a year late, but xcframeworks are for bundling multiple platforms together (like simulator and device, or macOS and iOS). Universal binaries are still the recommended way to have multiple architectures for one platform. |
To add to this, it would be nice if the universal binary only included a single copy of |
Intriguing. Do you know of a place where I can read more about this? |
Well the universal binary is basically an archive containing multiple binaries for target architectures. Given that each architecture is compiled separately and only at the end they are bundled, it's not surprising that the bytes are included twice. You can easily extract separate binaries from the universal one and they are completely standalone for given architecture |
macOS (and iOS) has a concept of universal binaries which contain code for multiple CPU architectures in the same file. Apple is migrating from x86_64 to aarch64 CPUs, so for the next few years it will be important for macOS developers to build "fat" binaries (executables and cdylibs).
Apple's Xcode has very helpful default behavior for this: when building in release mode, it automatically builds for both x86_64 and aarch64 together. In the debug mode, like Cargo, Xcode builds only the current native architecture.
Could
cargo --release
on macOS hosts automatically build bothx86_64-apple-darwin
andaarch64-apple-darwin
, and merge them into a single executable? Merging requires runninglipo -create -output universalbinary intelbinary armbinary
.I think it support for universal binaries should be built-in in Cargo:
cargo build --release
working for projects out of the box. Without building Universal binaries this becomes half-built, and insufficient for macOS developers.The text was updated successfully, but these errors were encountered: