diff --git a/.github/workflows/msvc/build.rs b/.github/workflows/msvc/build.rs index 206a339..d7d663e 100644 --- a/.github/workflows/msvc/build.rs +++ b/.github/workflows/msvc/build.rs @@ -1,3 +1,3 @@ fn main() { - embed_resource::compile("version.rc", embed_resource::NONE); + embed_resource::compile("version.rc", embed_resource::NONE).manifest_required().unwrap(); } diff --git a/README.md b/README.md index cebc2e8..1d4f959 100644 --- a/README.md +++ b/README.md @@ -13,14 +13,17 @@ extern crate embed_resource; fn main() { // Compile and link checksums.rc - embed_resource::compile("checksums.rc", embed_resource::NONE); + embed_resource::compile("checksums.rc", embed_resource::NONE).manifest_optional().unwrap(); // Or, to select a resource file for each binary separately - embed_resource::compile_for("assets/poke-a-mango.rc", &["poke-a-mango", "poke-a-mango-installer"], &["VERSION=\"0.5.0\""]); - embed_resource::compile_for("assets/uninstaller.rc", &["unins001"], embed_resource::NONE); + embed_resource::compile_for("assets/poke-a-mango.rc", &["poke-a-mango", "poke-a-mango-installer"], &["VERSION=\"0.5.0\""]).manifest_required().unwrap(); + embed_resource::compile_for("assets/uninstaller.rc", &["unins001"], embed_resource::NONE).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.). + ## Example: Embedding a Windows Manifest Courtesy of [@jpoles1](https://github.com/jpoles1). @@ -36,7 +39,7 @@ embed-resource = "2.5" ```rust extern crate embed_resource; fn main() { - embed_resource::compile("app-name-manifest.rc", embed_resource::NONE); + embed_resource::compile("app-name-manifest.rc", embed_resource::NONE).manifest_optional().unwrap(); } ``` @@ -70,7 +73,7 @@ Because the first step in building a manifest is an unspecified C preprocessor s 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: ```rust println!("cargo:rerun-if-changed=app-name-manifest.rc"); -embed_resource::compile("app-name-manifest.rc", embed_resource::NONE); +embed_resource::compile("app-name-manifest.rc", embed_resource::NONE).manifest_optional().unwrap(); ``` for the above example (cf. [#41](https://github.com/nabijaczleweli/rust-embed-resource/issues/41)). @@ -79,6 +82,13 @@ for the above example (cf. [#41](https://github.com/nabijaczleweli/rust-embed-re 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 `embed_resource::compile()` and `embed_resource::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: diff --git a/src/lib.rs b/src/lib.rs index b7e0a44..ade0053 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -45,11 +45,15 @@ //! extern crate embed_resource; //! //! fn main() { -//! embed_resource::compile("checksums.rc", embed_resource::NONE); // or -//! embed_resource::compile("checksums.rc", &["VERSION=000901"]); +//! 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. @@ -80,6 +84,13 @@ //! //! 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: @@ -137,19 +148,89 @@ use self::windows_not_msvc::*; use std::{env, fs}; use std::ffi::OsStr; -use std::fmt::Display; +use std::borrow::Cow; use std::process::Command; use toml::Value as TomlValue; +use std::fmt::{self, Display}; use toml::map::Map as TomlMap; use std::path::{Path, PathBuf}; -/// Empty slice, properly-typed for `compile()` and `compile_for()`'s macro list. +/// Empty slice, properly-typed for [`compile()`] and `compile_for*()`'s macro list. /// /// Rust helpfully forbids default type parameters on functions, so just passing `[]` doesn't work :) pub const NONE: &[&OsStr] = &[]; +/// Result of [`compile()`] and `compile_for*()` +/// +/// Turn this into a `Result` with `manifest_optional()` if the manifest is nice, but isn't required, like when embedding an +/// icon or some other cosmetic. +/// +/// Turn this into a `Result` with `manifest_required()` if the manifest is mandatory, like when configuring entry points or +/// security. +#[must_use] +#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] +pub enum CompilationResult { + /// not building for windows + NotWindows, + /// built, linked + Ok, + /// building for windows, but the environment can't compile a resource (most likely due to a missing compiler) + NotAttempted(Cow<'static, str>), + /// environment can compile a resource, but has failed to do so + Failed(Cow<'static, str>), +} +impl CompilationResult { + /// `Ok(())` if `NotWindows`, `Ok`, or `NotAttempted`; `Err(self)` if `Failed` + pub fn manifest_optional(self) -> Result<(), CompilationResult> { + match self { + CompilationResult::NotWindows | + CompilationResult::Ok | + CompilationResult::NotAttempted(..) => Ok(()), + err @ CompilationResult::Failed(..) => Err(err), + } + } + + /// `Ok(())` if `NotWindows`, `Ok`; `Err(self)` if `NotAttempted` or `Failed` + pub fn manifest_required(self) -> Result<(), CompilationResult> { + match self { + CompilationResult::NotWindows | + CompilationResult::Ok => Ok(()), + err @ CompilationResult::NotAttempted(..) | + err @ CompilationResult::Failed(..) => Err(err), + } + } +} +impl Display for CompilationResult { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + f.write_str("embed-resource: ")?; + match self { + CompilationResult::NotWindows => f.write_str("not building for windows"), + CompilationResult::Ok => f.write_str("OK"), + CompilationResult::NotAttempted(why) => { + f.write_str("compilation not attempted: ")?; + if !why.contains(' ') { + f.write_str("missing compiler: ")?; + } + f.write_str(why) + } + CompilationResult::Failed(err) => f.write_str(err), + } + } +} +impl std::error::Error for CompilationResult {} + +macro_rules! try_compile_impl { + ($expr:expr) => { + match $expr { + Result::Ok(val) => val, + Result::Err(err) => return err, + } + }; +} + + /// Compile the Windows resource file and update the cargo search path if building for Windows. /// /// On non-Windows non-Windows-cross-compile-target this does nothing, on non-MSVC Windows and Windows cross-compile targets, @@ -180,32 +261,32 @@ pub const NONE: &[&OsStr] = &[]; /// embed_resource::compile("checksums.rc", embed_resource::NONE); /// } /// ``` -pub fn compile, Ms: AsRef, Mi: IntoIterator>(resource_file: T, macros: Mi) { - if let Some((prefix, out_dir, out_file)) = compile_impl(resource_file.as_ref(), macros) { - let hasbins = fs::read_to_string("Cargo.toml") - .unwrap_or_else(|err| { - eprintln!("Couldn't read Cargo.toml: {}; assuming src/main.rs or S_ISDIR(src/bin/)", err); - String::new() - }) - .parse::() - .unwrap_or_else(|err| { - eprintln!("Couldn't parse Cargo.toml: {}; assuming src/main.rs or S_ISDIR(src/bin/)", err); - TomlValue::Table(TomlMap::new()) - }) - .as_table() - .map(|t| t.contains_key("bin")) - .unwrap_or(false) || (Path::new("src/main.rs").exists() || Path::new("src/bin").is_dir()); - eprintln!("Final verdict: crate has binaries: {}", hasbins); +pub fn compile, Ms: AsRef, Mi: IntoIterator>(resource_file: T, macros: Mi) -> CompilationResult { + let (prefix, out_dir, out_file) = try_compile_impl!(compile_impl(resource_file.as_ref(), macros)); + let hasbins = fs::read_to_string("Cargo.toml") + .unwrap_or_else(|err| { + eprintln!("Couldn't read Cargo.toml: {}; assuming src/main.rs or S_ISDIR(src/bin/)", err); + String::new() + }) + .parse::() + .unwrap_or_else(|err| { + eprintln!("Couldn't parse Cargo.toml: {}; assuming src/main.rs or S_ISDIR(src/bin/)", err); + TomlValue::Table(TomlMap::new()) + }) + .as_table() + .map(|t| t.contains_key("bin")) + .unwrap_or(false) || (Path::new("src/main.rs").exists() || Path::new("src/bin").is_dir()); + eprintln!("Final verdict: crate has binaries: {}", hasbins); - if hasbins && rustc_version::version().expect("couldn't get rustc version") >= rustc_version::Version::new(1, 50, 0) { - println!("cargo:rustc-link-arg-bins={}", out_file); - } else { - // Cargo pre-0.51.0 (rustc pre-1.50.0) compat - // Only links to the calling crate's library - println!("cargo:rustc-link-search=native={}", out_dir); - println!("cargo:rustc-link-lib=dylib={}", prefix); - } + if hasbins && rustc_version::version().expect("couldn't get rustc version") >= rustc_version::Version::new(1, 50, 0) { + println!("cargo:rustc-link-arg-bins={}", out_file); + } else { + // Cargo pre-0.51.0 (rustc pre-1.50.0) compat + // Only links to the calling crate's library + println!("cargo:rustc-link-search=native={}", out_dir); + println!("cargo:rustc-link-lib=dylib={}", prefix); } + CompilationResult::Ok } /// Likewise, but only for select binaries. @@ -224,60 +305,68 @@ pub fn compile, Ms: AsRef, Mi: IntoIterator>(re /// } /// ``` pub fn compile_for, J: Display, I: IntoIterator, Ms: AsRef, Mi: IntoIterator>(resource_file: T, for_bins: I, - macros: Mi) { - if let Some((_, _, out_file)) = compile_impl(resource_file.as_ref(), macros) { - for bin in for_bins { - println!("cargo:rustc-link-arg-bin={}={}", bin, out_file); - } + macros: Mi) + -> CompilationResult { + let (_, _, out_file) = try_compile_impl!(compile_impl(resource_file.as_ref(), macros)); + for bin in for_bins { + println!("cargo:rustc-link-arg-bin={}={}", bin, out_file); } + CompilationResult::Ok } -/// Likewise, but only link the resource to test binaries (select types only. unclear which (and likely to change). you may prefer [`compile_for_everything()`]). +/// Likewise, but only link the resource to test binaries (select types only. unclear which (and likely to change). you may +/// prefer [`compile_for_everything()`]). /// /// Only available since rustc 1.60.0, does nothing before. -pub fn compile_for_tests, Ms: AsRef, Mi: IntoIterator>(resource_file: T, macros: Mi) { - if let Some((_, _, out_file)) = compile_impl(resource_file.as_ref(), macros) { - println!("cargo:rustc-link-arg-tests={}", out_file); - } +pub fn compile_for_tests, Ms: AsRef, Mi: IntoIterator>(resource_file: T, macros: Mi) -> CompilationResult { + let (_, _, out_file) = try_compile_impl!(compile_impl(resource_file.as_ref(), macros)); + println!("cargo:rustc-link-arg-tests={}", out_file); + CompilationResult::Ok } /// Likewise, but only link the resource to benchmarks. /// /// Only available since rustc 1.60.0, does nothing before. -pub fn compile_for_benchmarks, Ms: AsRef, Mi: IntoIterator>(resource_file: T, macros: Mi) { - if let Some((_, _, out_file)) = compile_impl(resource_file.as_ref(), macros) { - println!("cargo:rustc-link-arg-benches={}", out_file); - } +pub fn compile_for_benchmarks, Ms: AsRef, Mi: IntoIterator>(resource_file: T, macros: Mi) -> CompilationResult { + let (_, _, out_file) = try_compile_impl!(compile_impl(resource_file.as_ref(), macros)); + println!("cargo:rustc-link-arg-benches={}", out_file); + CompilationResult::Ok } /// Likewise, but only link the resource to examples. /// /// Only available since rustc 1.60.0, does nothing before. -pub fn compile_for_examples, Ms: AsRef, Mi: IntoIterator>(resource_file: T, macros: Mi) { - if let Some((_, _, out_file)) = compile_impl(resource_file.as_ref(), macros) { - println!("cargo:rustc-link-arg-examples={}", out_file); - } +pub fn compile_for_examples, Ms: AsRef, Mi: IntoIterator>(resource_file: T, macros: Mi) -> CompilationResult { + let (_, _, out_file) = try_compile_impl!(compile_impl(resource_file.as_ref(), macros)); + println!("cargo:rustc-link-arg-examples={}", out_file); + CompilationResult::Ok } -/// Likewise, but link the resource into *every* artifact: binaries, cdylibs, examples, tests (`[[test]]`/`#[test]`/doctest), benchmarks, &c. +/// Likewise, but link the resource into *every* artifact: binaries, cdylibs, examples, tests (`[[test]]`/`#[test]`/doctest), +/// benchmarks, &c. /// /// Only available since rustc 1.50.0, does nothing before. -pub fn compile_for_everything, Ms: AsRef, Mi: IntoIterator>(resource_file: T, macros: Mi) { - if let Some((_, _, out_file)) = compile_impl(resource_file.as_ref(), macros) { - println!("cargo:rustc-link-arg={}", out_file); - } +pub fn compile_for_everything, Ms: AsRef, Mi: IntoIterator>(resource_file: T, macros: Mi) -> CompilationResult { + let (_, _, out_file) = try_compile_impl!(compile_impl(resource_file.as_ref(), macros)); + println!("cargo:rustc-link-arg={}", out_file); + CompilationResult::Ok } -fn compile_impl, Mi: IntoIterator>(resource_file: &Path, macros: Mi) -> Option<(&str, String, String)> { +fn compile_impl, Mi: IntoIterator>(resource_file: &Path, macros: Mi) -> Result<(&str, String, String), CompilationResult> { let comp = ResourceCompiler::new(); - if comp.is_supported() { + if let Some(missing) = comp.is_supported() { + if missing.is_empty() { + Err(CompilationResult::NotWindows) + } else { + Err(CompilationResult::NotAttempted(missing)) + } + } else { let prefix = &resource_file.file_stem().expect("resource_file has no stem").to_str().expect("resource_file's stem not UTF-8"); let out_dir = env::var("OUT_DIR").expect("No OUT_DIR env var"); - let out_file = comp.compile_resource(&out_dir, &prefix, resource_file.to_str().expect("resource_file not UTF-8"), macros); - Some((prefix, out_dir, out_file)) - } else { - None + let out_file = comp.compile_resource(&out_dir, &prefix, resource_file.to_str().expect("resource_file not UTF-8"), macros) + .map_err(CompilationResult::Failed)?; + Ok((prefix, out_dir, out_file)) } } diff --git a/src/main.rs b/src/main.rs index e0d4892..0742478 100644 --- a/src/main.rs +++ b/src/main.rs @@ -23,6 +23,6 @@ fn main() { env::set_var("OUT_DIR", "."); let resource = env::args().nth(1).expect("Specify the resource file to be compiled as the first argument."); - embed_resource::compile(&resource, &["VERSION=\"0.5.0\""]); - embed_resource::compile_for(&resource, &["embed_resource", "embed_resource-installer"], embed_resource::NONE); + embed_resource::compile(&resource, &["VERSION=\"0.5.0\""]).manifest_required().unwrap(); + embed_resource::compile_for(&resource, &["embed_resource", "embed_resource-installer"], embed_resource::NONE).manifest_required().unwrap(); } diff --git a/src/non_windows.rs b/src/non_windows.rs index 7944f04..38b0573 100644 --- a/src/non_windows.rs +++ b/src/non_windows.rs @@ -9,7 +9,7 @@ use std::{env, fs}; #[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] pub struct ResourceCompiler { - compiler: Option, + compiler: Result>, } impl ResourceCompiler { @@ -18,11 +18,12 @@ impl ResourceCompiler { } #[inline] - pub fn is_supported(&self) -> bool { - self.compiler.is_some() + pub fn is_supported(&self) -> Option> { + self.compiler.err() } - pub fn compile_resource, Mi: IntoIterator>(&self, out_dir: &str, prefix: &str, resource: &str, macros: Mi) -> String { + pub fn compile_resource, Mi: IntoIterator>(&self, out_dir: &str, prefix: &str, resource: &str, macros: Mi) + -> Result> { self.compiler.as_ref().expect("Not supported but we got to compile_resource()?").compile(out_dir, prefix, resource, macros) } } @@ -45,23 +46,25 @@ struct Compiler { } impl Compiler { - pub fn probe() -> Option { - let target = env::var("TARGET").ok()?; + pub fn probe() -> Result> { + let target = env::var("TARGET").map_err(|_| "no $TARGET".into())?; if let Some(rc) = env::var(&format!("RC_{}", target)) .or_else(|_| env::var(&format!("RC_{}", target.replace('-', "_")))) .or_else(|_| env::var("RC")) .ok() { - return Some(guess_compiler_variant(&rc)); + return guess_compiler_variant(&rc); } if target.ends_with("-pc-windows-gnu") || target.ends_with("-pc-windows-gnullvm") { let executable = format!("{}-w64-mingw32-windres", &target[0..target.find('-').unwrap_or_default()]); if is_runnable(&executable) { - return Some(Compiler { + return Ok(Compiler { tp: CompilerType::WindRes, executable: executable.into(), }); + } else { + return Err(executable.into()); } } else if target.ends_with("-pc-windows-msvc") { if is_runnable("llvm-rc") { @@ -76,10 +79,12 @@ impl Compiler { }, executable: "llvm-rc".into(), }); + } else { + return Err("llvm-rc".into()); } } - None + Err("".into()) } pub fn compile, Mi: IntoIterator>(&self, out_dir: &str, prefix: &str, resource: &str, macros: Mi) -> String { @@ -137,7 +142,8 @@ fn apply_macros_cc<'t, Ms: AsRef, Mi: IntoIterator>(to: &'t mu } fn cc_xc(to: &mut cc::Build) -> &mut cc::Build { - if to.get_compiler().is_like_msvc() { // clang-cl + if to.get_compiler().is_like_msvc() { + // clang-cl to.flag("-Xclang"); } to.flag("-xc"); @@ -163,24 +169,24 @@ fn or_curdir(directory: &Path) -> &Path { /// -V will print the version in windres. /// /? will print the help in LLVM-RC and Microsoft RC.EXE. /// If combined, /? takes precedence over -V. -fn guess_compiler_variant(s: &str) -> Compiler { +fn guess_compiler_variant(s: &str) -> Result> { match Command::new(s).args(&["-V", "/?"]).output() { Ok(out) => { if out.stdout.starts_with(b"GNU windres") { - Compiler { + Ok(Compiler { executable: s.to_string().into(), tp: CompilerType::WindRes, - } + }) } else if out.stdout.starts_with(b"OVERVIEW: Resource Converter") || out.stdout.starts_with(b"OVERVIEW: LLVM Resource Converter") { - Compiler { + Ok(Compiler { executable: s.to_string().into(), tp: CompilerType::LlvmRc { has_no_preprocess: memmem::find(&out.stdout, b"no-preprocess").is_some() }, - } + }) } else { - panic!("Unknown RC compiler variant: {}", s) + Err(format!("Unknown RC compiler variant: {}", s).into()) } } - Err(err) => panic!("Couldn't execute {}: {}", s, err), + Err(err) => Err(format!("Couldn't execute {}: {}", s, err).into()), } } diff --git a/src/windows_msvc.rs b/src/windows_msvc.rs index 669f1c8..231cfa9 100644 --- a/src/windows_msvc.rs +++ b/src/windows_msvc.rs @@ -21,11 +21,12 @@ impl ResourceCompiler { } #[inline(always)] - pub fn is_supported(&self) -> bool { - true + pub fn is_supported(&self) -> Option> { + None } - pub fn compile_resource, Mi: IntoIterator>(&self, out_dir: &str, prefix: &str, resource: &str, macros: Mi) -> String { + pub fn compile_resource, Mi: IntoIterator>(&self, out_dir: &str, prefix: &str, resource: &str, macros: Mi) + -> Result> { let out_file = format!("{}/{}.lib", out_dir, prefix); // `.res`es are linkable under MSVC as well as normal libraries. if !apply_macros(Command::new(find_windows_sdk_tool_impl("rc.exe").as_ref().map_or(Path::new("rc.exe"), Path::new)) @@ -34,11 +35,11 @@ impl ResourceCompiler { macros) .arg(resource) .status() - .expect("Are you sure you have RC.EXE in your $PATH?") + .map_err(|_| "Are you sure you have RC.EXE in your $PATH?".into())? .success() { - panic!("RC.EXE failed to compile specified resource file"); + return Err("RC.EXE failed to compile specified resource file".into()); } - out_file + Ok(out_file) } } @@ -52,8 +53,8 @@ enum Arch { pub fn find_windows_sdk_tool_impl(tool: &str) -> Option { let arch = match env::var("HOST").expect("No HOST env var").as_bytes() { - [b'x', b'8', b'6', b'_', b'6', b'4', ..] => Arch::X64, // "x86_64" - [b'a', b'a', b'r', b'c', b'h', b'6', b'4', ..] => Arch::AArch64, // "aarch64" + [b'x', b'8', b'6', b'_', b'6', b'4', _..] => Arch::X64, // "x86_64" + [b'a', b'a', b'r', b'c', b'h', b'6', b'4', _..] => Arch::AArch64, // "aarch64" _ => Arch::X86, }; diff --git a/src/windows_not_msvc.rs b/src/windows_not_msvc.rs index 6fb713b..7891fab 100644 --- a/src/windows_not_msvc.rs +++ b/src/windows_not_msvc.rs @@ -17,11 +17,12 @@ impl ResourceCompiler { } #[inline(always)] - pub fn is_supported(&self) -> bool { - true + pub fn is_supported(&self) -> Option> { + None } - pub fn compile_resource, Mi: IntoIterator>(&self, out_dir: &str, prefix: &str, resource: &str, macros: Mi) -> String { + pub fn compile_resource, Mi: IntoIterator>(&self, out_dir: &str, prefix: &str, resource: &str, macros: Mi) + -> Result> { let out_file = format!("{}/lib{}.a", out_dir, prefix); // Under some msys2 environments, $MINGW_CHOST has the correct target for @@ -44,11 +45,10 @@ impl ResourceCompiler { "-D", macros) .status() { - Ok(stat) if stat.success() => {} - Ok(stat) => panic!("windres failed to compile \"{}\" into \"{}\" with {}", resource, out_file, stat), - Err(e) => panic!("Couldn't to execute windres to compile \"{}\" into \"{}\": {}", resource, out_file, e), + Ok(stat) if stat.success() => Ok(out_file), + Ok(stat) => Err(format!("windres failed to compile \"{}\" into \"{}\" with {}", resource, out_file, stat).into()), + Err(e) => Err(format!("Couldn't to execute windres to compile \"{}\" into \"{}\": {}", resource, out_file, e).into()), } - out_file } }