rust-lang/cargo · error

cannot specify both `metabuild` and `build`

Error message

cannot specify both `metabuild` and `build`

What it means

Cargo supports two mutually-exclusive build script mechanisms: a traditional `build.rs` (via `package.build`) and `metabuild` (a list of build-dependency crates that generate build scripts). The parser reads `package.normalized_build()` and checks `metabuild.is_some()` in the same branch; if both are present it bails immediately. Only one system may drive the build.

Solutions

  1. Remove the `build` key (or delete build.rs) if you intend to use `metabuild`.
  2. Remove the `metabuild` key if you intend to keep your traditional build script.
  3. Run `cargo check` after editing to confirm only one mechanism remains.

Example fix

# before
[package]
build = "build.rs"
metabuild = ["metabuild-crate"]

# after (metabuild chosen)
[package]
metabuild = ["metabuild-crate"]
Defensive patterns

Strategy: validation

Validate before calling

# Pre-flight TOML check before invoking cargo
use toml::Value;
fn has_both_build_and_metabuild(manifest: &Value) -> bool {
    let pkg = manifest.get("package");
    pkg.and_then(|p| p.get("build")).is_some()
        && pkg.and_then(|p| p.get("metabuild")).is_some()
}
// assert!(!has_both_build_and_metabuild(&parsed));

Prevention

When it happens

Trigger: Cargo.toml sets `[package] build = "build.rs"` (or relies on the default `build.rs` convention) AND also declares `[package] metabuild = ["some-crate"]`. The `if let Some(custom_build) ... { if metabuild.is_some() { bail! } }` block at targets.rs:100-105 fires.

Common situations: Migrating a crate from a hand-written build.rs to metabuild and forgetting to remove the old `build` key. Copy-pasting a metabuild example into a crate that already has a build script. Tooling or scaffolding templates that set both.

Related errors


AI-assisted analysis of rust-lang/cargo@eb98b54bc9 (2026-08-11). Data as JSON: /api/errors/f29cc8fc83ef0ecb. Report an issue: GitHub.

Appendix: source

Thrown at src/workspace/parser/targets.rs:105

    targets.extend(to_test_targets(
        normalized_toml.test.as_deref().unwrap_or_default(),
        package_root,
        edition,
        warnings,
    )?);

    targets.extend(to_bench_targets(
        normalized_toml.bench.as_deref().unwrap_or_default(),
        package_root,
        edition,
        warnings,
    )?);

    // processing the custom build script
    if let Some(custom_build) = package.normalized_build().expect("previously normalized") {
        if metabuild.is_some() {
            anyhow::bail!("cannot specify both `metabuild` and `build`");
        }
        validate_unique_build_scripts(custom_build)?;
        for script in custom_build {
            let script_path = Path::new(script);
            let name = format!(
                "build-script-{}",
                script_path
                    .file_stem()
                    .and_then(|s| s.to_str())
                    .expect("previously normalized")
            );
            targets.push(Target::custom_build_target(
                &name,
                package_root.join(script_path),
                edition,
            ));
        }
    }

View on GitHub (pinned to eb98b54bc9)