static_vcruntime/lib.rs
1//! Statically link the VCRuntime when using the MSVC toolchain
2//!
3//! By default, Rust requires programs to deploy `vcruntime140.dll`
4//! (or equivalent) when redistributing binaries. This crate statically links
5//! the library instead.
6//!
7//! # Usage
8//!
9//! Add this to your `Cargo.toml`:
10//!
11//! ```toml
12//! [build-dependencies]
13//! static_vcruntime = "2.0"
14//! ```
15//!
16//! And in your [build script]:
17//!
18//! ```rust,ignore
19//! fn main() {
20//! static_vcruntime::metabuild();
21//! }
22//! ```
23//!
24//! That is all. Then when you build a release binary, the runtime will be
25//! statically linked:
26//!
27//! ```text
28//! cargo build --release
29//! ```
30//!
31//! # Issues
32//!
33//! If you have problems then you may need to clean the build directory before rebuilding:
34//!
35//! ```text
36//! cargo clean
37//! ```
38//!
39//! If all else fails then, in the same directory as your Cargo.toml, create a folder called `.cargo`. In that folder create the file `config.toml` and add the following:
40//!
41//! ```ini
42//! [target.'cfg(all(windows, target_env = "msvc"))']
43//! rustflags = ["-C", "target-feature=+crt-static"]
44//! ```
45//!
46//! This makes it easier to override the defaults.
47//!
48//! [build script]: https://doc.rust-lang.org/cargo/reference/build-scripts.html
49
50use std::{env, fs, io::Write, path::Path};
51
52/// Use dynamically linked ucrt with a statically linked vcruntime.
53///
54/// This must be called from a [build script], like so:
55///
56/// ```rust,ignore
57/// // build.rs
58/// fn main() {
59/// static_vcruntime::metabuild();
60/// }
61/// ```
62///
63/// [build script]: https://doc.rust-lang.org/cargo/reference/build-scripts.html
64pub fn metabuild() {
65 if env::var("CARGO_MANIFEST_DIR").is_err() {
66 panic!("`metabuild` must be called from a build script");
67 }
68 println!("cargo:rerun-if-env-changed=PROFILE");
69
70 // Early exit if not msvc or release
71 if env::var("CARGO_CFG_TARGET_ENV").as_deref() != Ok("msvc") || env::var("PROFILE").as_deref() != Ok("release") {
72 return;
73 }
74
75 override_msvcrt_lib();
76
77 // Disable conflicting libraries that aren't hard coded by Rust.
78 println!("cargo:rustc-link-arg=/NODEFAULTLIB:libvcruntimed.lib");
79 println!("cargo:rustc-link-arg=/NODEFAULTLIB:vcruntime.lib");
80 println!("cargo:rustc-link-arg=/NODEFAULTLIB:vcruntimed.lib");
81 println!("cargo:rustc-link-arg=/NODEFAULTLIB:libcmtd.lib");
82 println!("cargo:rustc-link-arg=/NODEFAULTLIB:msvcrt.lib");
83 println!("cargo:rustc-link-arg=/NODEFAULTLIB:msvcrtd.lib");
84 println!("cargo:rustc-link-arg=/NODEFAULTLIB:libucrt.lib");
85 println!("cargo:rustc-link-arg=/NODEFAULTLIB:libucrtd.lib");
86 // Set the libraries we want.
87 println!("cargo:rustc-link-arg=/DEFAULTLIB:libcmt.lib");
88 println!("cargo:rustc-link-arg=/DEFAULTLIB:libvcruntime.lib");
89 println!("cargo:rustc-link-arg=/DEFAULTLIB:ucrt.lib");
90}
91
92/// Override the hard-coded msvcrt.lib by replacing it with a (mostly) empty object file.
93fn override_msvcrt_lib() {
94 // Get the right machine type for the empty library.
95 let arch = std::env::var("CARGO_CFG_TARGET_ARCH");
96 let machine: &[u8] = if arch.as_deref() == Ok("x86_64") {
97 &[0x64, 0x86]
98 } else if arch.as_deref() == Ok("x86") {
99 &[0x4C, 0x01]
100 } else {
101 return;
102 };
103 let bytes: &[u8] = &[
104 1, 0, 94, 3, 96, 98, 60, 0, 0, 0, 1, 0, 0, 0, 0, 0, 132, 1, 46, 100, 114, 101, 99, 116,
105 118, 101, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 60, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
106 0, 0, 10, 16, 0, 46, 100, 114, 101, 99, 116, 118, 101, 0, 0, 0, 0, 1, 0, 0, 0, 3, 0, 4, 0,
107 0, 0,
108 ];
109
110 // Write the empty "msvcrt.lib" to the output directory.
111 let out_dir = env::var("OUT_DIR").unwrap();
112 let path = Path::new(&out_dir).join("msvcrt.lib");
113 let f = fs::OpenOptions::new()
114 .write(true)
115 .create_new(true)
116 .open(&path);
117 if let Ok(mut f) = f {
118 f.write_all(machine).unwrap();
119 f.write_all(bytes).unwrap();
120 }
121 // Add the output directory to the native library path.
122 println!("cargo:rustc-link-search=native={}", out_dir);
123}