embed_resource/
lib.rs

1//! A [`Cargo` build script](http://doc.crates.io/build-script.html) library to handle compilation and inclusion of Windows
2//! resources in the most resilient fashion imaginable
3//!
4//! # Background
5//!
6//! Including Windows resources seems very easy at first, despite the build scripts' abhorrent documentation:
7//! [compile with `windres`, then make linkable with
8//! `ar`](https://github.com/nabijaczleweli/cargo-update/commit/ef4346c#diff-a7b0a2dee0126cddf994326e705a91ea).
9//!
10//! I was very happy with that solution until it was brought to my attention, that [MSVC uses something
11//! different](https://github.com/nabijaczleweli/cargo-update/commit/f57e9c3#diff-a7b0a2dee0126cddf994326e705a91ea),
12//! and now either `windres`-`ar` combo or `RC.EXE` would be used, which was OK.
13//!
14//! Later it transpired, that [MSVC is even more incompatible with everything
15//! else](https://github.com/nabijaczleweli/cargo-update/commit/39fa758#diff-a7b0a2dee0126cddf994326e705a91ea)
16//! by way of not having `RC.EXE` in `$PATH` (because it would only be reasonable to do so),
17//! so another MSVC artisan made the script [find the most likely places for `RC.EXE` to
18//! be](https://github.com/nabijaczleweli/cargo-update/pull/22), and the script grew yet again,
19//! now standing at 100 lines and 3.2 kB.
20//!
21//! After [copying the build script in its
22//! entirety](https://github.com/thecoshman/http/commit/98205a4#diff-a7b0a2dee0126cddf994326e705a91ea)
23//! and realising how error-prone that was, then being [nudged by
24//! Shepmaster](https://chat.stackoverflow.com/transcript/message/35378953#35378953)
25//! to extract it to a crate, here we are.
26//!
27//! # Usage
28//!
29//! For the purposes of the demonstration we will assume that the resource file's name
30//! is "checksums.rc", but it can be any name relative to the crate root.
31//!
32//! In `Cargo.toml`:
33//!
34//! ```toml
35//! # The general section with crate name, license, etc.
36//! build = "build.rs"
37//!
38//! [build-dependencies]
39//! embed-resource = "1.6"
40//! ```
41//!
42//! In `build.rs`:
43//!
44//! ```rust,no_run
45//! extern crate embed_resource;
46//!
47//! fn main() {
48//!     embed_resource::compile("checksums.rc");
49//! }
50//! ```
51//!
52//! # Cross-compilation
53//!
54//! It is possible to embed resources in Windows executables built on non-Windows hosts. There are two ways to do this:
55//!
56//! When targetting `*-pc-windows-gnu`, `*-w64-mingw32-windres` is attempted by default, for `*-pc-windows-msvc` it's `llvm-rc`,
57//! this can be overriden by setting `RC_$TARGET`, `RC_${TARGET//-/_}`, or `RC` environment variables.
58//!
59//! When compiling with LLVM-RC, an external C compiler is used to preprocess the resource,
60//! preloaded with configuration from
61//! [`cc`](https://github.com/alexcrichton/cc-rs#external-configuration-via-environment-variables).
62//!
63//! # Credit
64//!
65//! In chronological order:
66//!
67//! [@liigo](https://github.com/liigo) -- persistency in pestering me and investigating problems where I have failed
68//!
69//! [@mzji](https://github.com/mzji) -- MSVC lab rat
70//!
71//! [@TheCatPlusPlus](https://github.com/TheCatPlusPlus) -- knowledge and providing first iteration of manifest-embedding code
72//!
73//! [@azyobuzin](https://github.com/azyobuzin) -- providing code for finding places where RC.EXE could hide
74//!
75//! [@retep998](https://github.com/retep998) -- fixing MSVC support
76//!
77//! [@SonnyX](https://github.com/SonnyX) -- Windows cross-compilation support and testing
78//!
79//! [@MSxDOS](https://github.com/MSxDOS) -- finding and supplying RC.EXE its esoteric header include paths
80//!
81//! [@roblabla](https://github.com/roblabla) -- cross-compilation to Windows MSVC via LLVM-RC
82//!
83//! # Special thanks
84//!
85//! To all who support further development on [Patreon](https://patreon.com/nabijaczleweli), in particular:
86//!
87//!   * ThePhD
88//!   * Embark Studios
89//!   * Jasper Bekkers
90
91
92#[cfg(any(not(target_os = "windows"), all(target_os = "windows", target_env = "msvc")))]
93extern crate cc;
94#[cfg(not(target_os = "windows"))]
95extern crate memchr;
96#[cfg(all(target_os = "windows", target_env = "msvc"))]
97extern crate vswhom;
98#[cfg(all(target_os = "windows", target_env = "msvc"))]
99extern crate winreg;
100extern crate rustc_version;
101extern crate toml;
102
103#[cfg(not(target_os = "windows"))]
104mod non_windows;
105#[cfg(all(target_os = "windows", target_env = "msvc"))]
106mod windows_msvc;
107#[cfg(all(target_os = "windows", not(target_env = "msvc")))]
108mod windows_not_msvc;
109
110#[cfg(not(target_os = "windows"))]
111use self::non_windows::*;
112#[cfg(all(target_os = "windows", target_env = "msvc"))]
113use self::windows_msvc::*;
114#[cfg(all(target_os = "windows", not(target_env = "msvc")))]
115use self::windows_not_msvc::*;
116
117use std::{env, fs};
118use toml::Value as TomlValue;
119use toml::map::Map as TomlMap;
120use std::path::{Path, PathBuf};
121
122
123/// Compile the Windows resource file and update the cargo search path if building for Windows.
124///
125/// On non-Windows non-Windows-cross-compile-target this does nothing, on non-MSVC Windows and Windows cross-compile targets,
126/// this chains `windres` with `ar`,
127/// but on MSVC Windows, this will try its hardest to find `RC.EXE` in Windows Kits and/or SDK directories,
128/// falling back to [Jon Blow's VS discovery script](https://pastebin.com/3YvWQa5c),
129/// and on Windows 10 `%INCLUDE%` will be updated to help `RC.EXE` find `windows.h` and friends.
130///
131/// `$OUT_DIR` is added to the include search path.
132///
133/// Note that this does *nothing* if building with rustc before 1.50.0 and there's a library in the crate,
134/// since the resource is linked to the library, if any, instead of the binaries.
135///
136/// Since rustc 1.50.0, the resource is linked only to the binaries
137/// (unless there are none, in which case it's also linked to the library).
138///
139/// # Examples
140///
141/// In your build script, assuming the crate's name is "checksums":
142///
143/// ```rust,no_run
144/// extern crate embed_resource;
145///
146/// fn main() {
147///     // Compile and link checksums.rc
148///     embed_resource::compile("checksums.rc");
149/// }
150/// ```
151#[inline]
152pub fn compile<T: AsRef<Path>>(resource_file: T) {
153    compile_impl(resource_file.as_ref())
154}
155
156fn compile_impl(resource_file: &Path) {
157    let comp = ResourceCompiler::new();
158    if comp.is_supported() {
159        let prefix = &resource_file.file_stem().expect("resource_file has no stem").to_str().expect("resource_file's stem not UTF-8");
160        let out_dir = env::var("OUT_DIR").expect("No OUT_DIR env var");
161
162        let out_file = comp.compile_resource(&out_dir, &prefix, resource_file.to_str().expect("resource_file not UTF-8"));
163        let hasbins = fs::read_to_string("Cargo.toml")
164            .unwrap_or_else(|err| {
165                eprintln!("Couldn't read Cargo.toml: {}; assuming src/main.rs or S_ISDIR(src/bin/)", err);
166                String::new()
167            })
168            .parse::<TomlValue>()
169            .unwrap_or_else(|err| {
170                eprintln!("Couldn't parse Cargo.toml: {}; assuming src/main.rs or S_ISDIR(src/bin/)", err);
171                TomlValue::Table(TomlMap::new())
172            })
173            .as_table()
174            .map(|t| t.contains_key("bin"))
175            .unwrap_or(false) || (Path::new("src/main.rs").exists() || Path::new("src/bin").is_dir());
176        eprintln!("Final verdict: crate has binaries: {}", hasbins);
177
178        if hasbins && rustc_version::version().expect("couldn't get rustc version") >= rustc_version::Version::new(1, 50, 0) {
179            println!("cargo:rustc-link-arg-bins={}", out_file);
180        } else {
181            // Cargo pre-0.51.0 (rustc pre-1.50.0) compat
182            // Only links to the calling crate's library
183            println!("cargo:rustc-link-search=native={}", out_dir);
184            println!("cargo:rustc-link-lib=dylib={}", prefix);
185        }
186    }
187}
188
189/// Find MSVC build tools other than the compiler and linker
190///
191/// On Windows + MSVC this can be used try to find tools such as `MIDL.EXE` in Windows Kits and/or SDK directories.
192///
193/// The compilers and linkers can be better found with the `cc` or `vswhom` crates.
194/// This always returns `None` on non-MSVC targets.
195///
196/// # Examples
197///
198/// In your build script, find `midl.exe` and use it to compile an IDL file:
199///
200/// ```rust,no_run
201/// # #[cfg(all(target_os = "windows", target_env = "msvc"))]
202/// # {
203/// extern crate embed_resource;
204/// extern crate vswhom;
205/// # use std::env;
206/// # use std::process::Command;
207///
208/// let midl = embed_resource::find_windows_sdk_tool("midl.exe").unwrap();
209///
210/// // midl.exe uses cl.exe as a preprocessor, so it needs to be in PATH
211/// let vs_locations = vswhom::VsFindResult::search().unwrap();
212/// let output = Command::new(midl)
213///     .env("PATH", vs_locations.vs_exe_path.unwrap())
214///     .args(&["/out", &env::var("OUT_DIR").unwrap()])
215///     .arg("haka.pfx.idl").output().unwrap();
216///
217/// assert!(output.status.success());
218/// # }
219/// ```
220pub fn find_windows_sdk_tool<T: AsRef<str>>(tool: T) -> Option<PathBuf> {
221    find_windows_sdk_tool_impl(tool.as_ref())
222}