Crate embed_resource

Source
Expand description

A Cargo build script library to handle compilation and inclusion of Windows resources in the most resilient fashion imaginable

§Background

Including Windows resources seems very easy at first, despite the build scripts’ abhorrent documentation: compile with windres, then make linkable with ar.

I was very happy with that solution until it was brought to my attention, that MSVC uses something different, and now either windres-ar combo or RC.EXE would be used, which was OK.

Later it transpired, that MSVC is even more incompatible with everything else by way of not having RC.EXE in $PATH (because it would only be reasonable to do so), so another MSVC artisan made the script find the most likely places for RC.EXE to be, and the script grew yet again, now standing at 100 lines and 3.2 kB.

After copying the build script in its entirety and realising how error-prone that was, then being nudged by Shepmaster to extract it to a crate, here we are.

§Usage

For the purposes of the demonstration we will assume that the resource file’s name is “checksums.rc”, but it can be any name relative to the crate root.

In Cargo.toml:

# The general section with crate name, license, etc.
build = "build.rs"

[build-dependencies]
embed-resource = "3.0"

In build.rs:

extern crate embed_resource;

fn main() {
    embed_resource::compile("checksums.rc", embed_resource::NONE).manifest_optional().unwrap();
    // or
    embed_resource::compile("checksums.rc", &["VERSION=000901"]).manifest_required().unwrap();
}

Use .manifest_optional().unwrap() if the manifest is cosmetic (like an icon).
Use .manifest_required().unwrap() if the manifest is required (security, entry point, &c.).

§Errata

If no cargo:rerun-if-changed annotations are generated, Cargo scans the entire build root by default. Because the first step in building a manifest is an unspecified C preprocessor step with-out the ability to generate the equivalent of cc -MD, we do not output said annotation.

If scanning is prohibitively expensive, or you have something else that generates the annotations, you may want to spec the full non-system dependency list for your manifest manually, so:

println!("cargo:rerun-if-changed=app-name-manifest.rc");
embed_resource::compile("app-name-manifest.rc", embed_resource::NONE);

for the above example (cf, #41).

§Cross-compilation

It is possible to embed resources in Windows executables built on non-Windows hosts. There are two ways to do this:

When targetting *-pc-windows-gnu, *-w64-mingw32-windres is attempted by default, for *-pc-windows-msvc it’s llvm-rc, this can be overriden by setting RC_$TARGET, RC_${TARGET//-/_}, or RC environment variables.

When compiling with LLVM-RC, an external C compiler is used to preprocess the resource, preloaded with configuration from cc.

§Migration

§2.x

Add embed_resource::NONE as the last argument to embed_resource::compile() and embed_resource::compile_for().

§3.x

Add .manifest_optional().unwrap() or .manifest_required().unwrap() to all compile() and compile_for*() calls. CompilationResult is #[must_use] so should be highlighted automatically.

Embed-resource <3.x always behaves like .manifest_optional().unwrap().

§Credit

In chronological order:

@liigo – persistency in pestering me and investigating problems where I have failed

@mzji – MSVC lab rat

@TheCatPlusPlus – knowledge and providing first iteration of manifest-embedding code

@azyobuzin – providing code for finding places where RC.EXE could hide

@retep998 – fixing MSVC support

@SonnyX – Windows cross-compilation support and testing

@MSxDOS – finding and supplying RC.EXE its esoteric header include paths

@roblabla – cross-compilation to Windows MSVC via LLVM-RC

§Special thanks

To all who support further development on Patreon, in particular:

  • ThePhD
  • Embark Studios
  • Lars Strojny
  • EvModder

Enums§

CompilationResult
Result of compile() and compile_for*()

Constants§

NONE
Empty slice, properly-typed for compile() and compile_for*()’s macro list.

Functions§

compile
Compile the Windows resource file and update the cargo search path if building for Windows.
compile_for
Likewise, but only for select binaries.
compile_for_benchmarks
Likewise, but only link the resource to benchmarks.
compile_for_everything
Likewise, but link the resource into every artifact: binaries, cdylibs, examples, tests ([[test]]/#[test]/doctest), benchmarks, &c.
compile_for_examples
Likewise, but only link the resource to examples.
compile_for_tests
Likewise, but only link the resource to test binaries (select types only. unclear which (and likely to change). you may prefer compile_for_everything()).
find_windows_sdk_tool
Find MSVC build tools other than the compiler and linker