rust-playground icon indicating copy to clipboard operation
rust-playground copied to clipboard

Procedural macros in release mode rebuild all dependencies

Open shepmaster opened this issue 2 years ago • 0 comments

While taking to @dtolnay about #928, we realized that release mode procedural macros are rebuilding dependencies (and may always have been). They had this as a suggestion:

The goal is to get cargo build --release when proc-macro = true to reuse artifacts that got built previously when cargo build and cargo build --release were previously run with proc-macro set to false.

The artifacts that got built previously are:

  • everything in dev mode: opt-level = 0 and debug = true
  • everything in release mode: opt-level = 3 and debug = false

This means [profile.release.build-override] debug = true is not the right thing because that's gonna make cargo look for artifacts with opt-level = 3 and debug = true, which is not something that got built previously.

The simplest fix is to make release proc-macros reuse artifacts from the release non-macro build, by doing this:

[profile.release.build-override]
codegen-units = 1
opt-level = 3

I confirmed that fixes the issue. However that is not my preferred fix because that slows down your up-front cargo build --release invocation; it will spend more time compiling things like serde_derive than it used to, and there is no benefit to users from having the macros be optimized.

Sadly you can't make release proc-macros reuse artifacts from the dev non-macro build. Naively you might try:

[profile.release.build-override]
codegen-units = 1
debug = true
debug-assertions = true
opt-level = 0
overflow-checks = true

which makes the entire profile line up correctly with what was previously built by cargo build. Yet Cargo will still not reuse the existing artifacts only because it wants --release artifacts to go under target/release/, and the existing ones were put under target/debug/, and instead of saying "oh I have these already in the other directory, let me copy them" it will just rebuild the same artifacts in the new location.

But here is my preferred fix:

[profile.playground]
inherits = "dev"  # mutates to "release"
codegen-units = 1
incremental = false

[profile.playground.build-override]
codegen-units = 1
opt-level = 0
debug = true
debug-assertions = true
overflow-checks = true

Now instead of cargo build + cargo build --release, your container will instead run cargo build --profile=playground twice, changing the "inherits" value in between. And the Debug/Release dropdown will set the same "inherits" value instead of a --release argument.

This speeds up your docker build compared to before, because the second cargo build --profile=playground will be faster than the current cargo build --release. You'll be building a total of 1 copy of serde_derive etc, instead of 2 separate copies. Your image will also be smaller.

On my machine, building your current Cargo.toml from the main branch produces a target directory that is 2.8 GB, whereas building my suggested fix produces 2.3 GB, which is 16.2% smaller

shepmaster avatar May 04 '23 17:05 shepmaster