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 = "3.0"
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", embed_resource::NONE).manifest_optional().unwrap();
49//! // or
50//! embed_resource::compile("checksums.rc", &["VERSION=000901"]).manifest_required().unwrap();
51//! // or
52//! embed_resource::compile("checksums.rc", embed_resource::ParamsMacrosAndIncludeDirs(
53//! &["VERSION=000901"], &["src/include"])).manifest_required().unwrap();
54//! // or
55//! embed_resource::compile("checksums.rc", embed_resource::ParamsIncludeDirs(
56//! &["src/include"])).manifest_required().unwrap();
57//! }
58//! ```
59//!
60//! Use `.manifest_optional().unwrap()` if the manifest is cosmetic (like an icon).<br />
61//! Use `.manifest_required().unwrap()` if the manifest is required (security, entry point, &c.).
62//!
63//! Parameters that look like `&["string"]` or `embed_resource::NONE` in the example above
64//! can be anything that satisfies `IntoIterator<AsRef<OsStr>>`:
65//! `&[&str]`, of course, but also `Option<PathBuf>`, `Vec<OsString>`, `BTreeSet<&Path>`, &c.
66//!
67//! ## Errata
68//!
69//! If no `cargo:rerun-if-changed` annotations are generated, Cargo scans the entire build root by default.
70//! Because the first step in building a manifest is an unspecified C preprocessor step with-out the ability to generate the
71//! equivalent of `cc -MD`, we do *not* output said annotation.
72//!
73//! If scanning is prohibitively expensive, or you have something else that generates the annotations, you may want to spec the
74//! full non-system dependency list for your manifest manually, so:
75//! ```rust,no_run
76//! println!("cargo:rerun-if-changed=app-name-manifest.rc");
77//! embed_resource::compile("app-name-manifest.rc", embed_resource::NONE);
78//! ```
79//! for the above example (cf. [#41](https://github.com/nabijaczleweli/rust-embed-resource/issues/41)).
80//!
81//! # Cross-compilation
82//!
83//! It is possible to embed resources in Windows executables built on non-Windows hosts. There are two ways to do this:
84//!
85//! When targetting `*-pc-windows-gnu`, `*-w64-mingw32-windres` is attempted by default, for `*-pc-windows-msvc` it's `llvm-rc`,
86//! this can be overriden by setting `RC_$TARGET`, `RC_${TARGET//-/_}`, or `RC` environment variables.
87//!
88//! When compiling with LLVM-RC, an external C compiler is used to preprocess the resource,
89//! preloaded with configuration from
90//! [`cc`](https://github.com/alexcrichton/cc-rs#external-configuration-via-environment-variables).
91//!
92//! ## Migration
93//! ### 2.x
94//!
95//! Add `embed_resource::NONE` as the last argument to `embed_resource::compile()` and `embed_resource::compile_for()`.
96//!
97//! ### 3.x
98//!
99//! Add `.manifest_optional().unwrap()` or `.manifest_required().unwrap()` to all [`compile()`] and `compile_for*()` calls.
100//! `CompilationResult` is `#[must_use]` so should be highlighted automatically.
101//!
102//! Embed-resource <3.x always behaves like `.manifest_optional().unwrap()`.
103//!
104//! # Credit
105//!
106//! In chronological order:
107//!
108//! [@liigo](https://github.com/liigo) -- persistency in pestering me and investigating problems where I have failed
109//!
110//! [@mzji](https://github.com/mzji) -- MSVC lab rat
111//!
112//! [@TheCatPlusPlus](https://github.com/TheCatPlusPlus) -- knowledge and providing first iteration of manifest-embedding code
113//!
114//! [@azyobuzin](https://github.com/azyobuzin) -- providing code for finding places where RC.EXE could hide
115//!
116//! [@retep998](https://github.com/retep998) -- fixing MSVC support
117//!
118//! [@SonnyX](https://github.com/SonnyX) -- Windows cross-compilation support and testing
119//!
120//! [@MSxDOS](https://github.com/MSxDOS) -- finding and supplying RC.EXE its esoteric header include paths
121//!
122//! [@roblabla](https://github.com/roblabla) -- cross-compilation to Windows MSVC via LLVM-RC
123//!
124//! # Special thanks
125//!
126//! To all who support further development on [Patreon](https://patreon.com/nabijaczleweli), in particular:
127//!
128//! * ThePhD
129//! * Embark Studios
130//! * Lars Strojny
131//! * EvModder
132
133#![allow(private_bounds)]
134
135
136extern crate cc;
137#[cfg(any(not(target_os = "windows"), all(target_os = "windows", not(target_env = "msvc"))))]
138extern crate memchr;
139#[cfg(all(target_os = "windows", target_env = "msvc"))]
140extern crate vswhom;
141#[cfg(all(target_os = "windows", target_env = "msvc"))]
142extern crate winreg;
143extern crate rustc_version;
144extern crate toml;
145
146#[cfg(not(target_os = "windows"))]
147mod non_windows;
148#[cfg(all(target_os = "windows", target_env = "msvc"))]
149mod windows_msvc;
150#[cfg(all(target_os = "windows", not(target_env = "msvc")))]
151mod windows_not_msvc;
152
153#[cfg(not(target_os = "windows"))]
154use self::non_windows::*;
155#[cfg(all(target_os = "windows", target_env = "msvc"))]
156use self::windows_msvc::*;
157#[cfg(all(target_os = "windows", not(target_env = "msvc")))]
158use self::windows_not_msvc::*;
159
160use std::{env, fs};
161use std::ffi::{OsString, OsStr};
162use std::borrow::Cow;
163use std::process::Command;
164use toml::Table as TomlTable;
165use std::fmt::{self, Display};
166use std::path::{Path, PathBuf};
167
168
169/// Empty slice, properly-typed for [`compile()`] and `compile_for*()` to mean "no additional parameters".
170///
171/// Rust helpfully forbids default type parameters on functions, so just passing `[]` doesn't work :)
172pub const NONE: &[&OsStr] = &[];
173
174
175// This is all of the parameters and it's non-public:
176// the only way users can construct this is via From<Mi> (same as From<ParamsMacros>), From<ParamsIncludeDirs>,
177// and From<ParamsMacrosAndIncludeDirs>
178#[derive(PartialEq, Eq, Debug)] // only for tests
179struct ParameterBundle<Ms: AsRef<OsStr>, Mi: IntoIterator<Item = Ms>, Is: AsRef<OsStr>, Ii: IntoIterator<Item = Is>> {
180 macros: Mi,
181 include_dirs: Ii,
182}
183
184impl<Ms: AsRef<OsStr>, Mi: IntoIterator<Item = Ms>> From<Mi> for ParameterBundle<Ms, Mi, &'static &'static OsStr, &'static [&'static OsStr]> {
185 fn from(macros: Mi) -> Self {
186 ParamsMacros(macros).into()
187 }
188}
189
190/// Give this to [`compile()`] or `compile_for*()` to add some macro definitions (`-D`/`/D`).
191///
192/// Every value must be in the form `MACRO=value` or `MACRO`. An empty iterator is a no-op.
193pub struct ParamsMacros<Ms: AsRef<OsStr>, Mi: IntoIterator<Item = Ms>>(pub Mi);
194impl<Ms: AsRef<OsStr>, Mi: IntoIterator<Item = Ms>> From<ParamsMacros<Ms, Mi>> for ParameterBundle<Ms, Mi, &'static &'static OsStr, &'static [&'static OsStr]> {
195 fn from(macros: ParamsMacros<Ms, Mi>) -> Self {
196 ParamsMacrosAndIncludeDirs(macros.0, NONE).into()
197 }
198}
199
200/// Give this to [`compile()`] or `compile_for*()` to add include directories (`-I`/`/I`).
201///
202/// An empty iterator is a no-op.
203pub struct ParamsIncludeDirs<Is: AsRef<OsStr>, Ii: IntoIterator<Item = Is>>(pub Ii);
204impl<Is: AsRef<OsStr>, Ii: IntoIterator<Item = Is>> From<ParamsIncludeDirs<Is, Ii>>
205 for ParameterBundle<&'static &'static OsStr, &'static [&'static OsStr], Is, Ii> {
206 fn from(include_dirs: ParamsIncludeDirs<Is, Ii>) -> Self {
207 ParamsMacrosAndIncludeDirs(NONE, include_dirs.0).into()
208 }
209}
210
211/// Give this to [`compile()`] or `compile_for*()` to add some macro definitions (`-D`/`/D`) and include directories
212/// (`-I`/`/I`).
213///
214/// Every macro value must be in the form `MACRO=value` or `MACRO`.
215///
216/// Empty iterators are no-ops.
217pub struct ParamsMacrosAndIncludeDirs<Ms: AsRef<OsStr>, Mi: IntoIterator<Item = Ms>, Is: AsRef<OsStr>, Ii: IntoIterator<Item = Is>>(pub Mi, pub Ii);
218impl<Ms: AsRef<OsStr>, Mi: IntoIterator<Item = Ms>, Is: AsRef<OsStr>, Ii: IntoIterator<Item = Is>> From<ParamsMacrosAndIncludeDirs<Ms, Mi, Is, Ii>>
219 for ParameterBundle<Ms, Mi, Is, Ii> {
220 fn from(maid: ParamsMacrosAndIncludeDirs<Ms, Mi, Is, Ii>) -> Self {
221 Self {
222 macros: maid.0,
223 include_dirs: maid.1,
224 }
225 }
226}
227
228
229/// https://101010.pl/@nabijaczleweli/115226665478478763
230#[cfg(test)]
231#[allow(dead_code)]
232fn compat_3_0_5() {
233 use std::collections::BTreeSet;
234
235 // these spellings of the macros argument taken from GitHub "embed_resource::" search
236 // and https://crates.io/crates/embed-resource/reverse_dependencies on 2025-09-18
237 let _ = compile("", std::iter::empty::<&str>());
238 let _ = compile("", None::<&str>);
239 let marcos = &[format!("VERSION_PATCH={}", env!("CARGO_PKG_VERSION_PATCH"))];
240 let _ = compile("", marcos);
241 let marcos = vec![format!("VERSION_PATCH={}", env!("CARGO_PKG_VERSION_PATCH"))];
242 let _ = compile("", marcos);
243
244 // these weren't
245 let _ = compile("", [""]);
246 let _ = compile("", &[""]);
247 let _ = compile("", vec![""]);
248 let _ = compile("", vec![Path::new("gaming=baming")].into_iter().collect::<BTreeSet<_>>());
249 let _ = compile("", vec![Path::new("gaming=baming").to_owned()].into_iter().collect::<BTreeSet<_>>());
250 let _ = compile("", [PathBuf::from("gaming=baming")].iter());
251 let _ = compile("", [PathBuf::from("gaming=baming")].iter().collect::<BTreeSet<_>>());
252
253 // this is new
254 let _ = compile("", ParamsIncludeDirs(&[Path::new("include_dir")]));
255 let _ = compile("", ParamsIncludeDirs([PathBuf::from("include_dir")]));
256 let _ = compile("", ParamsIncludeDirs(vec![Path::new("include_dir1"), Path::new("include_dir2")]));
257
258 let _ = compile("", ParamsMacrosAndIncludeDirs(NONE, NONE));
259 let _ = compile("", ParamsMacrosAndIncludeDirs([""], [""]));
260}
261
262#[test]
263fn argument_bundle_into() {
264 assert_eq!(ParameterBundle::from(NONE),
265 ParameterBundle {
266 macros: NONE,
267 include_dirs: NONE,
268 });
269 assert_eq!(ParameterBundle::from([""]),
270 ParameterBundle {
271 macros: [""],
272 include_dirs: NONE,
273 });
274
275 assert_eq!(ParameterBundle::from(ParamsMacros(NONE)),
276 ParameterBundle {
277 macros: NONE,
278 include_dirs: NONE,
279 });
280 assert_eq!(ParameterBundle::from(ParamsMacros([""])),
281 ParameterBundle {
282 macros: [""],
283 include_dirs: NONE,
284 });
285
286 assert_eq!(ParameterBundle::from(ParamsIncludeDirs(NONE)),
287 ParameterBundle {
288 macros: NONE,
289 include_dirs: NONE,
290 });
291 assert_eq!(ParameterBundle::from(ParamsIncludeDirs([""])),
292 ParameterBundle {
293 macros: NONE,
294 include_dirs: [""],
295 });
296
297 assert_eq!(ParameterBundle::from(ParamsMacrosAndIncludeDirs(NONE, NONE)),
298 ParameterBundle {
299 macros: NONE,
300 include_dirs: NONE,
301 });
302 assert_eq!(ParameterBundle::from(ParamsMacrosAndIncludeDirs([""], NONE)),
303 ParameterBundle {
304 macros: [""],
305 include_dirs: NONE,
306 });
307 assert_eq!(ParameterBundle::from(ParamsMacrosAndIncludeDirs(NONE, [""])),
308 ParameterBundle {
309 macros: NONE,
310 include_dirs: [""],
311 });
312 assert_eq!(ParameterBundle::from(ParamsMacrosAndIncludeDirs([""], [""])),
313 ParameterBundle {
314 macros: [""],
315 include_dirs: [""],
316 });
317}
318
319
320/// Result of [`compile()`] and `compile_for*()`
321///
322/// Turn this into a `Result` with `manifest_optional()` if the manifest is nice, but isn't required, like when embedding an
323/// icon or some other cosmetic.
324///
325/// Turn this into a `Result` with `manifest_required()` if the manifest is mandatory, like when configuring entry points or
326/// security.
327#[must_use]
328#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
329pub enum CompilationResult {
330 /// not building for windows
331 NotWindows,
332 /// built, linked
333 Ok,
334 /// building for windows, but the environment can't compile a resource (most likely due to a missing compiler)
335 NotAttempted(Cow<'static, str>),
336 /// environment can compile a resource, but has failed to do so
337 Failed(Cow<'static, str>),
338}
339impl CompilationResult {
340 /// `Ok(())` if `NotWindows`, `Ok`, or `NotAttempted`; `Err(self)` if `Failed`
341 pub fn manifest_optional(self) -> Result<(), CompilationResult> {
342 match self {
343 CompilationResult::NotWindows |
344 CompilationResult::Ok |
345 CompilationResult::NotAttempted(..) => Ok(()),
346 err @ CompilationResult::Failed(..) => Err(err),
347 }
348 }
349
350 /// `Ok(())` if `NotWindows`, `Ok`; `Err(self)` if `NotAttempted` or `Failed`
351 pub fn manifest_required(self) -> Result<(), CompilationResult> {
352 match self {
353 CompilationResult::NotWindows |
354 CompilationResult::Ok => Ok(()),
355 err @ CompilationResult::NotAttempted(..) |
356 err @ CompilationResult::Failed(..) => Err(err),
357 }
358 }
359}
360impl Display for CompilationResult {
361 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
362 f.write_str("embed-resource: ")?;
363 match self {
364 CompilationResult::NotWindows => f.write_str("not building for windows"),
365 CompilationResult::Ok => f.write_str("OK"),
366 CompilationResult::NotAttempted(why) => {
367 f.write_str("compilation not attempted: ")?;
368 if !why.contains(' ') {
369 f.write_str("missing compiler: ")?;
370 }
371 f.write_str(why)
372 }
373 CompilationResult::Failed(err) => f.write_str(err),
374 }
375 }
376}
377impl std::error::Error for CompilationResult {}
378
379macro_rules! try_compile_impl {
380 ($expr:expr) => {
381 match $expr {
382 Result::Ok(val) => val,
383 Result::Err(err) => return err,
384 }
385 };
386}
387
388
389/// Compile the Windows resource file and update the cargo search path if building for Windows.
390///
391/// On non-Windows non-Windows-cross-compile-target this does nothing, on non-MSVC Windows and Windows cross-compile targets,
392/// this chains `windres` with `ar`,
393/// but on MSVC Windows, this will try its hardest to find `RC.EXE` in Windows Kits and/or SDK directories,
394/// falling back to [Jon Blow's VS discovery script](https://pastebin.com/3YvWQa5c),
395/// and on Windows 10 `%INCLUDE%` will be updated to help `RC.EXE` find `windows.h` and friends.
396///
397/// `$OUT_DIR` is added to the include search path.
398///
399/// Note that this does *nothing* if building with rustc before 1.50.0 and there's a library in the crate,
400/// since the resource is linked to the library, if any, instead of the binaries.
401///
402/// Since rustc 1.50.0, the resource is linked only to the binaries
403/// (unless there are none, in which case it's also linked to the library).
404///
405/// `parameters` are a list of macros to define (directly or via [`ParamsMacros`]), in standard `NAME`/`NAME=VALUE` format,
406/// [`ParamsIncludeDirs`], or [`ParamsMacrosAndIncludeDirs`].
407///
408/// # Examples
409///
410/// In your build script, assuming the crate's name is "checksums":
411///
412/// ```rust,no_run
413/// extern crate embed_resource;
414///
415/// fn main() {
416/// // Compile and link checksums.rc
417/// embed_resource::compile("checksums.rc", embed_resource::NONE);
418/// }
419/// ```
420pub fn compile<T: AsRef<Path>,
421 Ms: AsRef<OsStr>,
422 Mi: IntoIterator<Item = Ms>,
423 Is: AsRef<OsStr>,
424 Ii: IntoIterator<Item = Is>,
425 P: Into<ParameterBundle<Ms, Mi, Is, Ii>>>(
426 resource_file: T, parameters: P)
427 -> CompilationResult {
428 let (prefix, out_dir, out_file) = try_compile_impl!(compile_impl(resource_file.as_ref(), parameters.into()));
429 let hasbins = fs::read_to_string("Cargo.toml")
430 .unwrap_or_else(|err| {
431 eprintln!("Couldn't read Cargo.toml: {}; assuming src/main.rs or S_ISDIR(src/bin/)", err);
432 String::new()
433 })
434 .parse::<TomlTable>()
435 .unwrap_or_else(|err| {
436 eprintln!("Couldn't parse Cargo.toml: {}; assuming src/main.rs or S_ISDIR(src/bin/)", err);
437 TomlTable::new()
438 })
439 .contains_key("bin") || (Path::new("src/main.rs").exists() || Path::new("src/bin").is_dir());
440 eprintln!("Final verdict: crate has binaries: {}", hasbins);
441
442 if hasbins && rustc_version::version().expect("couldn't get rustc version") >= rustc_version::Version::new(1, 50, 0) {
443 println!("cargo:rustc-link-arg-bins={}", out_file);
444 } else {
445 // Cargo pre-0.51.0 (rustc pre-1.50.0) compat
446 // Only links to the calling crate's library
447 println!("cargo:rustc-link-search=native={}", out_dir);
448 println!("cargo:rustc-link-lib=dylib={}", prefix);
449 }
450 CompilationResult::Ok
451}
452
453/// Likewise, but only for select binaries.
454///
455/// Only available since rustc 1.55.0, does nothing before.
456///
457/// # Examples
458///
459/// ```rust,no_run
460/// extern crate embed_resource;
461///
462/// fn main() {
463/// embed_resource::compile_for("assets/poke-a-mango.rc", &["poke-a-mango", "poke-a-mango-installer"],
464/// &["VERSION=\"0.5.0\""]);
465/// embed_resource::compile_for("assets/uninstaller.rc", &["unins001"], embed_resource::NONE);
466/// }
467/// ```
468pub fn compile_for<T: AsRef<Path>,
469 J: Display,
470 I: IntoIterator<Item = J>,
471 Ms: AsRef<OsStr>,
472 Mi: IntoIterator<Item = Ms>,
473 Is: AsRef<OsStr>,
474 Ii: IntoIterator<Item = Is>,
475 P: Into<ParameterBundle<Ms, Mi, Is, Ii>>>(
476 resource_file: T, for_bins: I, parameters: P)
477 -> CompilationResult {
478 let (_, _, out_file) = try_compile_impl!(compile_impl(resource_file.as_ref(), parameters.into()));
479 for bin in for_bins {
480 println!("cargo:rustc-link-arg-bin={}={}", bin, out_file);
481 }
482 CompilationResult::Ok
483}
484
485/// Likewise, but only link the resource to test binaries (select types only. unclear which (and likely to change). you may
486/// prefer [`compile_for_everything()`]).
487///
488/// Only available since rustc 1.60.0, does nothing before.
489pub fn compile_for_tests<T: AsRef<Path>,
490 Ms: AsRef<OsStr>,
491 Mi: IntoIterator<Item = Ms>,
492 Is: AsRef<OsStr>,
493 Ii: IntoIterator<Item = Is>,
494 P: Into<ParameterBundle<Ms, Mi, Is, Ii>>>(
495 resource_file: T, parameters: P)
496 -> CompilationResult {
497 let (_, _, out_file) = try_compile_impl!(compile_impl(resource_file.as_ref(), parameters.into()));
498 println!("cargo:rustc-link-arg-tests={}", out_file);
499 CompilationResult::Ok
500}
501
502/// Likewise, but only link the resource to benchmarks.
503///
504/// Only available since rustc 1.60.0, does nothing before.
505pub fn compile_for_benchmarks<T: AsRef<Path>,
506 Ms: AsRef<OsStr>,
507 Mi: IntoIterator<Item = Ms>,
508 Is: AsRef<OsStr>,
509 Ii: IntoIterator<Item = Is>,
510 P: Into<ParameterBundle<Ms, Mi, Is, Ii>>>(
511 resource_file: T, parameters: P)
512 -> CompilationResult {
513 let (_, _, out_file) = try_compile_impl!(compile_impl(resource_file.as_ref(), parameters.into()));
514 println!("cargo:rustc-link-arg-benches={}", out_file);
515 CompilationResult::Ok
516}
517
518/// Likewise, but only link the resource to examples.
519///
520/// Only available since rustc 1.60.0, does nothing before.
521pub fn compile_for_examples<T: AsRef<Path>,
522 Ms: AsRef<OsStr>,
523 Mi: IntoIterator<Item = Ms>,
524 Is: AsRef<OsStr>,
525 Ii: IntoIterator<Item = Is>,
526 P: Into<ParameterBundle<Ms, Mi, Is, Ii>>>(
527 resource_file: T, parameters: P)
528 -> CompilationResult {
529 let (_, _, out_file) = try_compile_impl!(compile_impl(resource_file.as_ref(), parameters.into()));
530 println!("cargo:rustc-link-arg-examples={}", out_file);
531 CompilationResult::Ok
532}
533
534/// Likewise, but only link the resource to the cdylib.
535///
536/// Only available since rustc 1.61.0, does nothing before.
537pub fn compile_for_cdylib<T: AsRef<Path>,
538 Ms: AsRef<OsStr>,
539 Mi: IntoIterator<Item = Ms>,
540 Is: AsRef<OsStr>,
541 Ii: IntoIterator<Item = Is>,
542 P: Into<ParameterBundle<Ms, Mi, Is, Ii>>>(
543 resource_file: T, parameters: P)
544 -> CompilationResult {
545 let (_, _, out_file) = try_compile_impl!(compile_impl(resource_file.as_ref(), parameters.into()));
546 println!("cargo:rustc-link-arg-cdylib={}", out_file);
547 CompilationResult::Ok
548}
549
550/// Likewise, but link the resource into *every* artifact: binaries, cdylibs, examples, tests (`[[test]]`/`#[test]`/doctest),
551/// benchmarks, &c.
552///
553/// Only available since rustc 1.50.0, does nothing before.
554pub fn compile_for_everything<T: AsRef<Path>,
555 Ms: AsRef<OsStr>,
556 Mi: IntoIterator<Item = Ms>,
557 Is: AsRef<OsStr>,
558 Ii: IntoIterator<Item = Is>,
559 P: Into<ParameterBundle<Ms, Mi, Is, Ii>>>(
560 resource_file: T, parameters: P)
561 -> CompilationResult {
562 let (_, _, out_file) = try_compile_impl!(compile_impl(resource_file.as_ref(), parameters.into()));
563 println!("cargo:rustc-link-arg={}", out_file);
564 CompilationResult::Ok
565}
566
567fn compile_impl<Ms: AsRef<OsStr>, Mi: IntoIterator<Item = Ms>, Is: AsRef<OsStr>, Ii: IntoIterator<Item = Is>, P: Into<ParameterBundle<Ms, Mi, Is, Ii>>>(
568 resource_file: &Path, parameters: P)
569 -> Result<(&str, String, String), CompilationResult> {
570 let mut comp = ResourceCompiler::new();
571 if let Some(missing) = comp.is_supported() {
572 if missing.is_empty() {
573 Err(CompilationResult::NotWindows)
574 } else {
575 Err(CompilationResult::NotAttempted(missing))
576 }
577 } else {
578 let prefix = &resource_file.file_stem().expect("resource_file has no stem").to_str().expect("resource_file's stem not UTF-8");
579 let out_dir = env::var("OUT_DIR").expect("No OUT_DIR env var");
580
581 let out_file = comp.compile_resource(&out_dir, &prefix, resource_file.to_str().expect("resource_file not UTF-8"), parameters.into())
582 .map_err(CompilationResult::Failed)?;
583 Ok((prefix, out_dir, out_file))
584 }
585}
586
587fn apply_parameters<'t, Ms: AsRef<OsStr>, Mi: IntoIterator<Item = Ms>, Is: AsRef<OsStr>, Ii: IntoIterator<Item = Is>>(to: &'t mut Command, macro_pref: &str,
588 include_dir_pref: &str,
589 parameters: ParameterBundle<Ms,
590 Mi,
591 Is,
592 Ii>)
593 -> &'t mut Command {
594 for m in parameters.macros {
595 to.arg(macro_pref).arg(m);
596 }
597 for id in parameters.include_dirs {
598 to.arg(include_dir_pref).arg(id);
599 }
600 to
601}
602
603
604/// Find MSVC build tools other than the compiler and linker
605///
606/// On Windows + MSVC this can be used try to find tools such as `MIDL.EXE` in Windows Kits and/or SDK directories.
607///
608/// The compilers and linkers can be better found with the `cc` or `vswhom` crates.
609/// This always returns `None` on non-MSVC targets.
610///
611/// # Examples
612///
613/// In your build script, find `midl.exe` and use it to compile an IDL file:
614///
615/// ```rust,no_run
616/// # #[cfg(all(target_os = "windows", target_env = "msvc"))]
617/// # {
618/// extern crate embed_resource;
619/// extern crate vswhom;
620/// # use std::env;
621/// # use std::process::Command;
622///
623/// let midl = embed_resource::find_windows_sdk_tool("midl.exe").unwrap();
624///
625/// // midl.exe uses cl.exe as a preprocessor, so it needs to be in PATH
626/// let vs_locations = vswhom::VsFindResult::search().unwrap();
627/// let output = Command::new(midl)
628/// .env("PATH", vs_locations.vs_exe_path.unwrap())
629/// .arg("/out").arg(env::var("OUT_DIR").unwrap())
630/// .arg("haka.pfx.idl").output().unwrap();
631///
632/// assert!(output.status.success());
633/// # }
634/// ```
635pub fn find_windows_sdk_tool<T: AsRef<str>>(tool: T) -> Option<PathBuf> {
636 find_windows_sdk_tool_impl(tool.as_ref())
637}
638
639
640#[allow(unused)]
641fn env_target_and_rc() -> Result<(String, Option<OsString>), Cow<'static, str>> {
642 let target = env::var("TARGET").map_err(|_| Cow::from("no $TARGET"))?;
643 let rc = env::var_os(&format!("RC_{}", target)).or_else(|| env::var_os(&format!("RC_{}", target.replace('-', "_")))).or_else(|| env::var_os("RC"));
644 Ok((target, rc))
645}
646
647
648#[cfg(any(not(target_os = "windows"), all(target_os = "windows", not(target_env = "msvc"))))]
649mod windres {
650 use self::super::{ParameterBundle, apply_parameters};
651 use std::process::{Command, Stdio};
652 use std::ffi::{OsString, OsStr};
653 use std::borrow::Cow;
654 use std::path::Path;
655 use memchr::memmem;
656 use std::fs;
657
658 #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
659 pub enum CompilerType {
660 /// LLVM-RC
661 ///
662 /// Requires a separate C preprocessor step on the source RC file
663 LlvmRc { has_no_preprocess: bool, },
664 /// MinGW windres
665 WindRes,
666 }
667
668 #[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
669 pub struct Compiler {
670 pub tp: CompilerType,
671 pub executable: Cow<'static, OsStr>,
672 }
673
674
675 fn is_runnable(s: &str) -> bool {
676 Command::new(s).stdin(Stdio::null()).stdout(Stdio::null()).stderr(Stdio::null()).spawn().map(|mut c| c.kill()).is_ok()
677 }
678 fn if_runnable(executable: Cow<'static, str>, maketp: fn(&str) -> CompilerType) -> Result<Compiler, Cow<'static, str>> {
679 if is_runnable(&executable) {
680 Ok(Compiler {
681 tp: maketp(&executable),
682 executable: match executable {
683 Cow::Owned(e) => Cow::Owned(OsString::from(e)),
684 Cow::Borrowed(e) => Cow::Borrowed(OsStr::new(e)),
685 },
686 })
687 } else {
688 Err(executable)
689 }
690 }
691 impl Compiler {
692 pub fn llvm_rc(executable: Cow<'static, str>) -> Result<Compiler, Cow<'static, str>> {
693 if_runnable(executable, |executable| {
694 CompilerType::LlvmRc {
695 has_no_preprocess: Command::new(executable)
696 .arg("/?")
697 .output()
698 .ok()
699 .map(|out| memmem::find(&out.stdout, b"no-preprocess").is_some())
700 .unwrap_or(false),
701 }
702 })
703 }
704 pub fn windres(executable: Cow<'static, str>) -> Result<Compiler, Cow<'static, str>> {
705 if_runnable(executable, |_| CompilerType::WindRes)
706 }
707
708 pub fn compile<Ms: AsRef<OsStr>, Mi: IntoIterator<Item = Ms>, Is: AsRef<OsStr>, Ii: IntoIterator<Item = Is>, Wp: FnOnce(&mut Command) -> &mut Command>(
709 &self, out_dir: &str, prefix: &str, out_file: String, resource: &str, parameters: ParameterBundle<Ms, Mi, Is, Ii>, fo: &str, c: &str,
710 no_preprocess: &str, windres_params: Wp)
711 -> Result<String, Cow<'static, str>> {
712 match self.tp {
713 CompilerType::LlvmRc { has_no_preprocess } => {
714 let preprocessed_path = format!("{}/{}-preprocessed.rc", out_dir, prefix);
715 fs::write(&preprocessed_path,
716 cc_xc(apply_parameters_cc(cc::Build::new().define("RC_INVOKED", None), parameters))
717 .file(resource)
718 .cargo_metadata(false)
719 .include(out_dir)
720 .expand()).map_err(|e| e.to_string())?;
721
722 try_command(Command::new(&*self.executable)
723 .args(&[fo, &out_file])
724 .args(&[c, "65001"]) // UTF-8, cf. https://github.com/nabijaczleweli/rust-embed-resource/pull/73
725 .args(if has_no_preprocess {
726 // We already preprocessed using CC. llvm-rc preprocessing
727 // requires having clang in PATH, which more exotic toolchains
728 // may not necessarily have.
729 Some(no_preprocess)
730 } else {
731 None
732 })
733 .args(&["--", &preprocessed_path])
734 .stdin(Stdio::piped())
735 .current_dir(or_curdir(Path::new(resource).parent().expect("Resource parent nonexistent?"))),
736 Path::new(&self.executable),
737 "compile",
738 &preprocessed_path,
739 &out_file)?;
740 }
741 CompilerType::WindRes => {
742 try_command(apply_parameters(windres_params(Command::new(&*self.executable)
743 .args(&["--input", resource, "--output", &out_file, "--include-dir", out_dir, "--output-format=coff"])),
744 "-D",
745 "-I",
746 parameters),
747 Path::new(&self.executable),
748 "compile",
749 resource,
750 &out_file)?;
751 }
752 }
753 Ok(out_file)
754 }
755 }
756
757
758 fn apply_parameters_cc<'t, Ms: AsRef<OsStr>, Mi: IntoIterator<Item = Ms>, Is: AsRef<OsStr>, Ii: IntoIterator<Item = Is>>(to: &'t mut cc::Build,
759 parameters: ParameterBundle<Ms,
760 Mi,
761 Is,
762 Ii>)
763 -> &'t mut cc::Build {
764 for m in parameters.macros {
765 let mut m = m.as_ref().to_str().expect("macros must be UTF-8 in this configuration").splitn(2, '=');
766 to.define(m.next().unwrap(), m.next());
767 }
768 for id in parameters.include_dirs {
769 to.include(id.as_ref());
770 }
771 to
772 }
773
774 fn cc_xc(to: &mut cc::Build) -> &mut cc::Build {
775 if to.get_compiler().is_like_msvc() {
776 // clang-cl
777 to.flag("-Xclang");
778 }
779 to.flag("-xc");
780 to
781 }
782
783 fn try_command(cmd: &mut Command, exec: &Path, action: &str, whom: &str, whre: &str) -> Result<(), Cow<'static, str>> {
784 match cmd.status() {
785 Ok(stat) if stat.success() => Ok(()),
786 Ok(stat) => Err(format!("{} failed to {} \"{}\" into \"{}\" with {}", exec.display(), action, whom, whre, stat).into()),
787 Err(e) => Err(format!("Couldn't execute {} to {} \"{}\" into \"{}\": {}", exec.display(), action, whom, whre, e).into()),
788 }
789 }
790
791 fn or_curdir(directory: &Path) -> &Path {
792 if directory == Path::new("") {
793 Path::new(".")
794 } else {
795 directory
796 }
797 }
798
799}