Search code examples
rustrust-cargotoml

How to create a target specific profile in Cargo.toml?


I would like to specify a separate [profile.release] for my library when cfg(target_os = "ios") is active. I tried this:

[profile.'cfg(not(target_os = "ios"))'.release]
lto = true # Use link-time optimization.

[profile.'cfg(target_os = "ios")'.release]
lto = true # Use link-time optimization.
opt-level = "z"  # Mobile devices are fast enough. Optimize for size instead.
strip = "symbols" # Not relevant for windows.

However when I now try to build my project / workspace I get the following error:

error: failed to parse manifest at `/path/to/Cargo.toml`

Caused by:
  invalid character `(` in profile name `cfg(not(target_os = "ios"))`
  Allowed characters are letters, numbers, underscore, and hyphen.

which is to be expected, because according to the documentation only [profile.<name>] is allowed.

Is there any method to achieve the desired behaviour?

P.S. The full target name would be aarch64-apple-ios in case this is needed.


Solution

  • This was requested in issue #4897, Per-target profiles?, but not yet implemented.

    In the meantime, you can use a script that checks the target and set environment variables to override config (for example, set CARGO_PROFILE_RELEASE_LTO or CARGO_PROFILE_RELEASE_OPT_LEVEL) then invokes Cargo with them.

    Here is an example, from users.rust-lang.org - How to modify profile.release only for a target?

    Create a binary in the workspace named e.g. custom_build. In custom_build/src/main.rs put:

    use std::env;
    use std::process::Command;
    
    fn main() {
        let mut cargo = Command::new(env::var_os("CARGO").unwrap());
        cargo.env("CARGO_CUSTOM_BUILD", "1"); // So we can disallow regular builds, to prevent mistakes
        cargo.args(env::args_os().skip(1));
    
        // You can determine the target OS by various way, but providing it manually to the build script is the simplest.
        let for_ios = env::var("build_ios").is_ok();
        if for_ios {
            cargo.env("CARGO_PROFILE_RELEASE_LTO", "true");
            cargo.env("CARGO_PROFILE_RELEASE_OPT_LEVEL", "z");
            cargo.env("CARGO_PROFILE_RELEASE_STRIP", "symbols");
        } else {
            cargo.env("CARGO_PROFILE_RELEASE_LTO", "true");
        }
    
        let cargo_succeeded = cargo.status().ok().map_or(false, |status| status.success());
        if !cargo_succeeded {
            std::process::exit(1);
        }
    }
    

    Then you can create a build.rs file to prevent manually running cargo:

    use std::env;
    
    fn main() {
        if !env::var("CARGO_CUSTOM_BUILD").ok().map_or(false, |s| s == "1") {
            panic!("Do not run `cargo ...`, run `cargo custom_build ...` instead")
        }
    }