Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use of pre-built library binaries prevents static build of bevy apps #8

Open
GrygrFlzr opened this issue Jan 8, 2021 · 1 comment

Comments

@GrygrFlzr
Copy link
Contributor

GrygrFlzr commented Jan 8, 2021

Motivation
A statically built binary allows us to run bevy games without the additional installation of runtimes on the user end.

Reproduction steps

  1. Clone bevy master
  2. Add the following to .cargo/config:
[target.x86_64-pc-windows-msvc]
rustflags = ["-C", "target-feature=+crt-static"]
  1. Attempt to run a bevy example:
cargo run --release --target x86_64-pc-windows-msvc --example shader_custom_material
  1. Linker error: LNK2038: mismatch detected for 'RuntimeLibrary': value 'MD_DynamicRelease' doesn't match value 'MT_StaticRelease'

Details
This linker error no longer occurs if I modify glslang to build x86_64-pc-windows-msvc from source like i686-pc-windows-msvc.

The RUSTFLAGS environment variable is not available at build time. I do not believe cargo has a standard way of telling build.rs whether or not the user is requesting a static or dynamic build, which means we cannot automatically switch to building from source whenever a static build is requested.

This places us in an awkward situation for 64-bit MSVC (and potentially all platforms where it uses the prebuilt libraries), because building from source adds to the compilation time, and not everyone should have to pay for optional static linking support (especially since bevy docs recommends a setup with dynamic linking!).

Issues with Attempted Solution
I can make minimal code changes to conditionally compile glsl-to-spirv when the build-from-source feature flag is enabled:

let bin_dir = match target {
    // conditionally build from source
    "x86_64-pc-windows-msvc" => {
        if cfg!(feature = "build-from-source") {
            build::build_libraries(&target)
        } else {
            cargo_dir.join("build").join(&target)
        }
    }
    // other targets
};

Unfortunately, cargo stable will always attempt to enable the feature. This means instead of build-from-source being an opt-in feature, cargo stable will instead prevent opting out.

Nightly allows us to opt out, as mentioned in #7, by using a flag which is stabilized as resolver version 2, and to be made default in rust edition 2021 (see rust-lang/cargo#9048):

# don't eagerly enable features
cargo +nightly run -Z features=itarget --release --target x86_64-pc-windows-msvc --example shader_custom_material

In which case the user can properly opt-in by requesting the feature directly in their Cargo.toml:

[dependencies]
bevy-glsl-to-spirv-builder = { version = "0.1.0", features = ["build-from-source"] }

Potential Impact on Dynamic Builds (until cargo resolver 2 is stable)
Since building from source occurs in parallel with compilation of other bevy dependencies, measurements must not be isolated to building glsl-to-spirv alone. I use the shader_custom_material as a baseline, and cargo clean before every run. The following was run on a Ryzen 7 3700X CPU.

# dynamic build, using pre-compiled libraries
$ cargo +nightly run -Z features=itarget --release --target x86_64-pc-windows-msvc --example shader_custom_material
   Compiling dependencies...
   Compiling bevy v0.4.0 (C:\Users\GrygrFlzr\Documents\projects\clean-bevy)
    Finished release [optimized] target(s) in 1m 45s
     Running `target\x86_64-pc-windows-msvc\release\examples\shader_custom_material.exe`

# dynamic build, using explicit build-from-source
$ cargo +nightly run -Z features=itarget --release --target x86_64-pc-windows-msvc --example shader_custom_material
   Compiling dependencies...
   Compiling bevy v0.4.0 (C:\Users\GrygrFlzr\Documents\projects\clean-bevy)
    Finished release [optimized] target(s) in 2m 13s
     Running `target\x86_64-pc-windows-msvc\release\examples\shader_custom_material.exe`

This is a 28s addition to what was a 105s build process (up by 26.7%) for first time builds and all subsequent builds post-cargo clean.

Temporary Conclusions
Although this cost is only incurred once for the user as it will reuse the libraries on subsequent runs, the additional compile time may be a significant turn-off for new users who just want to get started, and does not give a good impression. Further more, any uses of cargo clean immediately wipes the slate and requires building from source again, as published crates are not allowed to place libraries outside of OUT_DIR.

Once cargo defaults to using resolver version 2, we can make this an opt-in feature without any drawbacks to dynamic builds by default.

Current Workarounds
You could target i686-pc-windows-msvc when static linking, since that target always has to build anyway, but that comes with all the limitations of a 32-bit application vs a 64-bit application.

GNU toolchains currently require dynamically linking stdc++ (unless someone can figure out how to statically link that), so they are not an alternative to MSVC if you are looking for static builds.

@GrygrFlzr
Copy link
Contributor Author

GrygrFlzr commented Jan 8, 2021

Considered Alternatives
We can gate this behind the debug/release flag instead:

let bin_dir = match target {
    // conditionally build from source
    "x86_64-pc-windows-msvc" => {
        if cfg!(debug_assertions) {
            cargo_dir.join("build").join(&target)
        } else {
            build::build_libraries(&target)
        }
    }
    // other targets
};

This means dynamic builds only need to start paying for compilation cost when --release is requested.

Unfortunately, this has its own problems:

  • Many people use release mode when working on their games because debug mode is too slow, so they'd be hit with the one-time compilation cost anyway.
  • You would be unable to build statically linked games in debug mode, only in release mode.

I also tested replacing the pre-compiled dynamic library with a pre-compiled statically linked one. The dynamic build of bevy does not like the statically generated libraries, so that would not be a viable solution.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant