Cargo workspaces: Building `A+B` is different from building `A` and building `B`?

I've run into an odd case with cargo, and I'm trying to figure out what's causing it.

I have an embedded system that has many different crates in it. Many crates are libraries, while some are programs. One is the kernel, and one is the loader.

If I build the loader by itself, it works. If I build one of the programs by itself, it works. If I build the loader + a program, it fails.

More concretely:

$ cargo build --package loader --package shellchat --target riscv32imac-unknown-xous-elf --release
 Finished release [optimized + debuginfo] target(s) in 0.23s
$ cargo build --package shellchat --target riscv32imac-unknown-xous-elf --release
    Finished release [optimized + debuginfo] target(s) in 0.34s
$ cargo build --package loader --package shellchat --target riscv32imac-unknown-xous-elf --release
   Compiling loader v0.1.0 (E:\Code\Xous\Core\loader)
error: no global memory allocator found but one is required; link to std or add `#[global_allocator]` to a static item that implements the GlobalAlloc trait

error: `#[alloc_error_handler]` function required, but not found

note: Use `#![feature(default_alloc_error_handler)]` for a default error handler

error: could not compile `loader` due to 2 previous errors
$

Now granted, loader is a no_std program given that it is a bootloader. However, there's clearly something funky going on with the dependency tree. If I build it using --verbose then I see the hashes for the program names change as arguments to rustc, however nothing else is changing.

How can I track down this issue? Is it expected that cargo builds differently depending on which packages you specify? I'm using rust 1.58.0 and this workspace is using resolver version 2.

It might have to do with features. Cargo will build all dependencies (inside the workspace or out) with the set of features required for everything being built, rather than the minimum for each binary.

That's what I thought with resolver="1", however I'm using resolver="2" which means each package will get its own resolution tree.

https://doc.rust-lang.org/cargo/reference/resolver.html#features

When building multiple packages in a workspace (such as with --workspace or multiple -p flags), the features of the dependencies of all of those packages are unified. If you have a circumstance where you want to avoid that unification for different workspace members, you will need to build them via separate cargo invocations.

I don't think it is true that resolver=2 always gives both their own resolution tree. Dependency Resolution - The Cargo Book lists all the cases where they are not unified. This is not one of them.

Ah, thank you! That does seem to answer the question definitively: Different invocations of cargo can produce different versions of the library depending on what's being built, so the solution is to run it multiple times for things that clearly must be no_std.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.