Skip to main content

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}