Description
Problem
Hi,
I am attempting to get "hot reloading" working for a project, however a shared dependency that seemingly does not need to be recompiled is being recompiled. My project makes use of Bevy ECS, and the shared dependency is my "components" crate (components is an ECS term), which I do not which to be recompiled as this modifies the TypeId values of the structs contained within, confusing Bevy.
I've simplified down the project into the following hierarchy, and a link to this project can be found in the "steps" section of this issue:
- workspace
- game_bin
- components
- common
- fnv
- common
- fnv
- components
- hot
- components
- common
- fnv
- common
- components
- game_bin
In reality, "common" is bevy_reflect_derive - a proc-macro crate that is a dependency of a dependency, and thus I don't have much control over it. I've made "common" a proc-macro crate in my example.
"fnv" can be any crate as far as I'm aware, as long as it is present in both "common" and "game_bin". In reality for me it's the uuid crate, however I'm using fnv for this example as it is very simple, with no dependencies, to keep debugging easier.
If I compile game_bin
and then hot
, the dependencies are recompiled. Example output from cargo:
$ cargo b -p game_bin
Compiling fnv v1.0.7
Compiling common v0.1.0 (C:\Users\robys\git\simple\common)
Compiling components v0.1.0 (C:\Users\robys\git\simple\components)
Compiling game_bin v0.1.0 (C:\Users\robys\git\simple\game_bin)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.34s
$ cargo b -p hot
Compiling fnv v1.0.7
Compiling common v0.1.0 (C:\Users\robys\git\simple\common)
Compiling components v0.1.0 (C:\Users\robys\git\simple\components)
Compiling hot v0.1.0 (C:\Users\robys\git\simple\hot)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.35s
If I look into the target/debug/.fingerprint directory I see duplicates of common, components and fnv. And if I run game_bin and a separate process that loads hot.dll and calls the exported function I get different TypeId values for each (I can provide a script to do this if required, but I don't think it'll be useful as we can just look at the presence of fingerprint files to determine if something was recompiled).
If I remove "fnv" from either game_bin or common, or make common
no longer proc-macro, then nothing is recompiled:
$ cargo b -p game_bin
Compiling fnv v1.0.7
Compiling common v0.1.0 (C:\Users\robys\git\simple\common)
Compiling components v0.1.0 (C:\Users\robys\git\simple\components)
Compiling game_bin v0.1.0 (C:\Users\robys\git\simple\game_bin)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.24s
$ cargo b -p hot
Compiling hot v0.1.0 (C:\Users\robys\git\simple\hot)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.17s
Steps
git clone [email protected]:RedBreadcat/recompile.git
cd recompile
cargo b -p game_bin
cargo b -p hot # Note how this logs fnv, common and components being recompiled. You can also see duplicated fingerprints in target\debug\.fingerprint.
Possible Solution(s)
No response
Notes
To dig into the issue, I first set CARGO_LOG="cargo::core::compiler::fingerprint=trace"
. Here's what is logged during the cargo b -p hot
call.
0.011175200s DEBUG prepare_target{force=false package_id=hot v0.1.0 (C:\Users\foo\git\recompile\hot) target="hot"}: cargo::core::compiler::fingerprint: fingerprint at: C:\Users\foo\git\recompile\target\debug\.fingerprint\hot-3508d5915a918114\lib-hot
0.011396700s DEBUG prepare_target{force=false package_id=hot v0.1.0 (C:\Users\foo\git\recompile\hot) target="hot"}: cargo::core::compiler::fingerprint: failed to get mtime of "C:\\Users\\foo\\git\\recompile\\target\\debug\\deps\\libfnv-a05d1c57e54b212a.rlib": failed to load metadata for path `C:\Users\foo\git\recompile\target\debug\deps\libfnv-a05d1c57e54b212a.rlib`
0.011569100s DEBUG prepare_target{force=false package_id=hot v0.1.0 (C:\Users\foo\git\recompile\hot) target="hot"}: cargo::core::compiler::fingerprint: failed to get mtime of "C:\\Users\\foo\\git\\recompile\\target\\debug\\deps\\common-712e8f7a3e2ebcb6.dll": failed to load metadata for path `C:\Users\foo\git\recompile\target\debug\deps\common-712e8f7a3e2ebcb6.dll`
0.011689700s DEBUG prepare_target{force=false package_id=hot v0.1.0 (C:\Users\foo\git\recompile\hot) target="hot"}: cargo::core::compiler::fingerprint: failed to get mtime of "C:\\Users\\foo\\git\\recompile\\target\\debug\\deps\\libcomponents-60eddae38580c867.rlib": failed to load metadata for path `C:\Users\foo\git\recompile\target\debug\deps\libcomponents-60eddae38580c867.rlib`
0.011773000s DEBUG prepare_target{force=false package_id=hot v0.1.0 (C:\Users\foo\git\recompile\hot) target="hot"}: cargo::core::compiler::fingerprint: failed to get mtime of "C:\\Users\\foo\\git\\recompile\\target\\debug\\deps\\hot.dll": failed to load metadata for path `C:\Users\foo\git\recompile\target\debug\deps\hot.dll`
0.011839800s INFO prepare_target{force=false package_id=hot v0.1.0 (C:\Users\foo\git\recompile\hot) target="hot"}: cargo::core::compiler::fingerprint: fingerprint error for hot v0.1.0 (C:\Users\foo\git\recompile\hot)/Build/TargetInner { name_inferred: true, ..: lib_target("hot", ["dylib"], "C:\\Users\\foo\\git\\recompile\\hot\\src\\lib.rs", Edition2024) }
0.011903900s INFO prepare_target{force=false package_id=hot v0.1.0 (C:\Users\foo\git\recompile\hot) target="hot"}: cargo::core::compiler::fingerprint: err: failed to read `C:\Users\foo\git\recompile\target\debug\.fingerprint\hot-3508d5915a918114\lib-hot`
Caused by:
The system cannot find the file specified. (os error 2)
0.012483300s DEBUG prepare_target{force=false package_id=components v0.1.0 (C:\Users\foo\git\recompile\components) target="components"}: cargo::core::compiler::fingerprint: fingerprint at: C:\Users\foo\git\recompile\target\debug\.fingerprint\components-60eddae38580c867\lib-components
0.012614200s INFO prepare_target{force=false package_id=components v0.1.0 (C:\Users\foo\git\recompile\components) target="components"}: cargo::core::compiler::fingerprint: fingerprint error for components v0.1.0 (C:\Users\foo\git\recompile\components)/Build/TargetInner { name_inferred: true, ..: lib_target("components", ["lib"], "C:\\Users\\foo\\git\\recompile\\components\\src\\lib.rs", Edition2024) }
0.012707500s INFO prepare_target{force=false package_id=components v0.1.0 (C:\Users\foo\git\recompile\components) target="components"}: cargo::core::compiler::fingerprint: err: failed to read `C:\Users\foo\git\recompile\target\debug\.fingerprint\components-60eddae38580c867\lib-components`
Caused by:
The system cannot find the file specified. (os error 2)
0.013026600s DEBUG prepare_target{force=false package_id=common v0.1.0 (C:\Users\foo\git\recompile\common) target="common"}: cargo::core::compiler::fingerprint: fingerprint at: C:\Users\foo\git\recompile\target\debug\.fingerprint\common-712e8f7a3e2ebcb6\lib-common
0.013123300s INFO prepare_target{force=false package_id=common v0.1.0 (C:\Users\foo\git\recompile\common) target="common"}: cargo::core::compiler::fingerprint: fingerprint error for common v0.1.0 (C:\Users\foo\git\recompile\common)/Build/TargetInner { name_inferred: true, for_host: true, proc_macro: true, ..: lib_target("common", ["proc-macro"], "C:\\Users\\foo\\git\\recompile\\common\\src\\lib.rs", Edition2024) }
0.013181000s INFO prepare_target{force=false package_id=common v0.1.0 (C:\Users\foo\git\recompile\common) target="common"}: cargo::core::compiler::fingerprint: err: failed to read `C:\Users\foo\git\recompile\target\debug\.fingerprint\common-712e8f7a3e2ebcb6\lib-common`
Caused by:
The system cannot find the file specified. (os error 2)
0.013479400s DEBUG prepare_target{force=false package_id=fnv v1.0.7 target="fnv"}: cargo::core::compiler::fingerprint: fingerprint at: C:\Users\foo\git\recompile\target\debug\.fingerprint\fnv-a05d1c57e54b212a\lib-fnv
0.013589100s INFO prepare_target{force=false package_id=fnv v1.0.7 target="fnv"}: cargo::core::compiler::fingerprint: fingerprint error for fnv v1.0.7/Build/TargetInner { ..: lib_target("fnv", ["lib"], "C:\\Users\\foo\\.cargo\\registry\\src\\index.crates.io-1949cf8c6b5b557f\\fnv-1.0.7\\lib.rs", Edition2015) }
0.013658000s INFO prepare_target{force=false package_id=fnv v1.0.7 target="fnv"}: cargo::core::compiler::fingerprint: err: failed to read `C:\Users\foo\git\recompile\target\debug\.fingerprint\fnv-a05d1c57e54b212a\lib-fnv`
Caused by:
The system cannot find the file specified. (os error 2)
Compiling fnv v1.0.7
0.057486600s DEBUG cargo::core::compiler::fingerprint: write fingerprint (35b8201a9faf7669) : C:\Users\foo\git\recompile\target\debug\.fingerprint\fnv-a05d1c57e54b212a\lib-fnv
Compiling common v0.1.0 (C:\Users\foo\git\recompile\common)
0.146906400s DEBUG cargo::core::compiler::fingerprint: write fingerprint (b8df32d7c3a96588) : C:\Users\foo\git\recompile\target\debug\.fingerprint\common-712e8f7a3e2ebcb6\lib-common
Compiling components v0.1.0 (C:\Users\foo\git\recompile\components)
0.200354300s DEBUG cargo::core::compiler::fingerprint: write fingerprint (b52de2fb640ef34f) : C:\Users\foo\git\recompile\target\debug\.fingerprint\components-60eddae38580c867\lib-components
Compiling hot v0.1.0 (C:\Users\foo\git\recompile\hot)
0.376939800s DEBUG cargo::core::compiler::fingerprint: write fingerprint (5de95d45e59f1d48) : C:\Users\foo\git\recompile\target\debug\.fingerprint\hot-3508d5915a918114\lib-hot
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.37s
I expected to see a fingerprint "DirtyReason" message - and in fact I did see a UnitDependencyInfoChanged message previously in a larger not-as-simplified example project, but not any longer. Perhaps that means it was a red herring?
Regardless, I attempted to see why different fingerprints were created. I'm not at all familiar with cargo's code, but I hacked in some code to print the various parts that make up the fingerprint.
It all came down to the unit.profile
component of profile_hash:
cargo/src/cargo/core/compiler/fingerprint/mod.rs
Line 1534 in adf9b6a
And breaking it down further, the only change was to
debuginfo
for "fnv". For game_bin it's Resolved(Full)
, and for hot it's Resolved(None)
. If I remove proc-macro=true
from common, then both are Resolved(Full)
.Furthermore, it seems that the addition of
proc-macro=true
causes common
to be Resolved(None)
. I don't believe this causes the issue as it's the same for both game_bin and hot, but it's notable how it's different from the removed proc-macro version.
game_bin, with proc-macro = true
fnv: Profile { name: "dev", opt_level: "0", root: Debug, lto: Bool(false), codegen_backend: None, codegen_units: None, debuginfo: Resolved(Full), split_debuginfo: None, debug_assertions: true, overflow_checks: true, rpath: false, incremental: false, panic: Unwind, strip: Deferred(None), rustflags: [], trim_paths: None }
common: Profile { name: "dev", opt_level: "0", root: Debug, lto: Bool(false), codegen_backend: None, codegen_units: None, debuginfo: Resolved(None), split_debuginfo: None, debug_assertions: true, overflow_checks: true, rpath: false, incremental: true, panic: Unwind, strip: Deferred(None), rustflags: [], trim_paths: None }
components: Profile { name: "dev", opt_level: "0", root: Debug, lto: Bool(false), codegen_backend: None, codegen_units: None, debuginfo: Resolved(Full), split_debuginfo: None, debug_assertions: true, overflow_checks: true, rpath: false, incremental: true, panic: Unwind, strip: Deferred(None), rustflags: [], trim_paths: None }
game_bin: Profile { name: "dev", opt_level: "0", root: Debug, lto: Bool(false), codegen_backend: None, codegen_units: None, debuginfo: Resolved(Full), split_debuginfo: None, debug_assertions: true, overflow_checks: true, rpath: false, incremental: true, panic: Unwind, strip: Deferred(None), rustflags: [], trim_paths: None }
hot, with proc-macro = true
fnv: Profile { name: "dev", opt_level: "0", root: Debug, lto: Bool(false), codegen_backend: None, codegen_units: None, debuginfo: Resolved(None), split_debuginfo: None, debug_assertions: true, overflow_checks: true, rpath: false, incremental: false, panic: Unwind, strip: Deferred(None), rustflags: [], trim_paths: None }
common: Profile { name: "dev", opt_level: "0", root: Debug, lto: Bool(false), codegen_backend: None, codegen_units: None, debuginfo: Resolved(None), split_debuginfo: None, debug_assertions: true, overflow_checks: true, rpath: false, incremental: true, panic: Unwind, strip: Deferred(None), rustflags: [], trim_paths: None }
components: Profile { name: "dev", opt_level: "0", root: Debug, lto: Bool(false), codegen_backend: None, codegen_units: None, debuginfo: Resolved(Full), split_debuginfo: None, debug_assertions: true, overflow_checks: true, rpath: false, incremental: true, panic: Unwind, strip: Deferred(None), rustflags: [], trim_paths: None }
hot: Profile { name: "dev", opt_level: "0", root: Debug, lto: Bool(false), codegen_backend: None, codegen_units: None, debuginfo: Resolved(Full), split_debuginfo: None, debug_assertions: true, overflow_checks: true, rpath: false, incremental: true, panic: Unwind, strip: Deferred(None), rustflags: [], trim_paths: None }
Both game_bin and hot, with proc-macro = false
fnv: Profile { name: "dev", opt_level: "0", root: Debug, lto: Bool(false), codegen_backend: None, codegen_units: None, debuginfo: Resolved(Full), split_debuginfo: None, debug_assertions: true, overflow_checks: true, rpath: false, incremental: false, panic: Unwind, strip: Deferred(None), rustflags: [], trim_paths: None }
common: Profile { name: "dev", opt_level: "0", root: Debug, lto: Bool(false), codegen_backend: None, codegen_units: None, debuginfo: Resolved(Full), split_debuginfo: None, debug_assertions: true, overflow_checks: true, rpath: false, incremental: true, panic: Unwind, strip: Deferred(None), rustflags: [], trim_paths: None }
components: Profile { name: "dev", opt_level: "0", root: Debug, lto: Bool(false), codegen_backend: None, codegen_units: None, debuginfo: Resolved(Full), split_debuginfo: None, debug_assertions: true, overflow_checks: true, rpath: false, incremental: true, panic: Unwind, strip: Deferred(None), rustflags: [], trim_paths: None }
game_bin: Profile { name: "dev", opt_level: "0", root: Debug, lto: Bool(false), codegen_backend: None, codegen_units: None, debuginfo: Resolved(Full), split_debuginfo: None, debug_assertions: true, overflow_checks: true, rpath: false, incremental: true, panic: Unwind, strip: Deferred(None), rustflags: [], trim_paths: None }
That's as far as I've looked. I'm not sure what determines these profiles, or whether there's a way to work around this.
I am aware that if I run cargo b -p game_bin -p hot
then everything is compiled together, but for my original purposes game_bin will be executing at the time I run that command, as I'm only interested in recompiling "hot" for the purposes of hot reloading.
Version
cargo 1.85.0 (d73d2caf9 2024-12-31)
release: 1.85.0
commit-hash: d73d2caf9e41a39daf2a8d6ce60ec80bf354d2a7
commit-date: 2024-12-31
host: x86_64-pc-windows-msvc
libgit2: 1.8.1 (sys:0.19.0 vendored)
libcurl: 8.9.0-DEV (sys:0.4.74+curl-8.9.0 vendored ssl:Schannel)
os: Windows 10.0.26100 (Windows 11 Core) [64-bit]