зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1759175 pt1 - Repository integration r=glandium,supply-chain-reviewers
Differential Revision: https://phabricator.services.mozilla.com/D174916
This commit is contained in:
Родитель
187916a0ea
Коммит
5390c33a4e
|
@ -798,6 +798,16 @@ checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961"
|
|||
name = "cmake"
|
||||
version = "0.1.999"
|
||||
|
||||
[[package]]
|
||||
name = "cocoabind"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bindgen 0.69.4",
|
||||
"block",
|
||||
"mozbuild",
|
||||
"objc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codespan-reporting"
|
||||
version = "0.11.1"
|
||||
|
@ -962,6 +972,41 @@ dependencies = [
|
|||
"mach2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crashreporter"
|
||||
version = "1.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"block",
|
||||
"bytes",
|
||||
"cfg-if 1.0.0",
|
||||
"cocoabind",
|
||||
"embed-manifest",
|
||||
"env_logger",
|
||||
"fluent",
|
||||
"gtkbind",
|
||||
"intl-memoizer",
|
||||
"lazy_static",
|
||||
"libloading",
|
||||
"log",
|
||||
"mozbuild",
|
||||
"mozilla-central-workspace-hack",
|
||||
"objc",
|
||||
"phf",
|
||||
"phf_codegen",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"time 0.3.23",
|
||||
"tokio",
|
||||
"unic-langid",
|
||||
"uuid",
|
||||
"warp",
|
||||
"windows-sys 0.52.0",
|
||||
"yaml-rust",
|
||||
"zip",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.3.2"
|
||||
|
@ -1484,6 +1529,12 @@ version = "1.8.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"
|
||||
|
||||
[[package]]
|
||||
name = "embed-manifest"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41cd446c890d6bed1d8b53acef5f240069ebef91d6fae7c5f52efe61fe8b5eae"
|
||||
|
||||
[[package]]
|
||||
name = "encoding_c"
|
||||
version = "0.9.8"
|
||||
|
@ -2467,6 +2518,14 @@ dependencies = [
|
|||
"bitflags 1.999.999",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gtkbind"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bindgen 0.69.4",
|
||||
"mozbuild",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "guid_win"
|
||||
version = "0.2.0"
|
||||
|
|
|
@ -13,6 +13,7 @@ members = [
|
|||
"security/manager/ssl/osclientcerts",
|
||||
"testing/geckodriver",
|
||||
"toolkit/components/uniffi-bindgen-gecko-js",
|
||||
"toolkit/crashreporter/client-rust/app",
|
||||
"toolkit/crashreporter/mozwer-rust",
|
||||
"toolkit/crashreporter/rust_minidump_writer_linux",
|
||||
"toolkit/library/gtest/rust",
|
||||
|
|
|
@ -13,6 +13,20 @@ def generate_bool(name):
|
|||
return f"pub const {name}: bool = {'true' if value else 'false'};\n"
|
||||
|
||||
|
||||
def generate_string_array(name):
|
||||
value = buildconfig.substs.get(name) or []
|
||||
return (
|
||||
f"pub const {name}: [&str; {len(value)}] = ["
|
||||
+ ",".join(map(escape_rust_string, value))
|
||||
+ "];\n"
|
||||
)
|
||||
|
||||
|
||||
def generate_string(name):
|
||||
value = buildconfig.substs.get(name) or ""
|
||||
return f"pub const {name}: &str = {escape_rust_string(value)};\n"
|
||||
|
||||
|
||||
def escape_rust_string(value):
|
||||
"""escape the string into a Rust literal"""
|
||||
# This could be more generous, but we're only escaping paths with it.
|
||||
|
@ -85,7 +99,7 @@ def generate(output):
|
|||
)
|
||||
)
|
||||
|
||||
# Finally, write out some useful booleans from the buildconfig.
|
||||
# Finally, write out some useful values from the buildconfig.
|
||||
output.write(generate_bool("MOZ_FOLD_LIBS"))
|
||||
output.write(generate_bool("NIGHTLY_BUILD"))
|
||||
output.write(generate_bool("RELEASE_OR_BETA"))
|
||||
|
@ -93,3 +107,10 @@ def generate(output):
|
|||
output.write(generate_bool("MOZ_DEV_EDITION"))
|
||||
output.write(generate_bool("MOZ_ESR"))
|
||||
output.write(generate_bool("MOZ_DIAGNOSTIC_ASSERT_ENABLED"))
|
||||
|
||||
# Used by toolkit/crashreporter/client
|
||||
output.write(generate_bool("MOZ_CRASHREPORTER_MOCK"))
|
||||
output.write(generate_string_array("CC_BASE_FLAGS"))
|
||||
output.write(generate_string_array("MOZ_GTK3_CFLAGS"))
|
||||
output.write(generate_string_array("MOZ_GTK3_LIBS"))
|
||||
output.write(generate_string("MOZ_APP_NAME"))
|
||||
|
|
|
@ -162,6 +162,8 @@ optional = true
|
|||
features = [
|
||||
"Wdk_System_Threading",
|
||||
"Win32_Foundation",
|
||||
"Win32_Globalization",
|
||||
"Win32_Graphics_Gdi",
|
||||
"Win32_Security",
|
||||
"Win32_Storage_FileSystem",
|
||||
"Win32_System_Com",
|
||||
|
@ -175,6 +177,8 @@ features = [
|
|||
"Win32_System_SystemInformation",
|
||||
"Win32_System_SystemServices",
|
||||
"Win32_System_Threading",
|
||||
"Win32_UI_Controls",
|
||||
"Win32_UI_Input_KeyboardAndMouse",
|
||||
"Win32_UI_Shell",
|
||||
"Win32_UI_WindowsAndMessaging",
|
||||
]
|
||||
|
@ -182,6 +186,7 @@ features = [
|
|||
|
||||
[features]
|
||||
builtins-static = ["dep:bindgen", "dep:bitflags", "dep:memchr", "dep:nom", "dep:regex", "dep:smallvec"]
|
||||
crashreporter = ["dep:env_logger", "dep:hyper", "dep:log", "dep:serde_json", "dep:time", "dep:uuid", "dep:windows-sys"]
|
||||
geckodriver = ["dep:bitflags", "dep:bytes", "dep:cc", "dep:chrono", "dep:flate2", "dep:futures-channel", "dep:futures-core", "dep:futures-sink", "dep:futures-util", "dep:getrandom", "dep:hashbrown", "dep:hyper", "dep:indexmap", "dep:log", "dep:memchr", "dep:mio", "dep:num-integer", "dep:num-traits", "dep:once_cell", "dep:regex", "dep:semver", "dep:serde_json", "dep:smallvec", "dep:time", "dep:tokio", "dep:tokio-util", "dep:tracing", "dep:url", "dep:uuid", "dep:windows-sys"]
|
||||
gkrust = ["dep:arrayvec", "dep:bindgen", "dep:bitflags", "dep:bytes", "dep:cc", "dep:chrono", "dep:env_logger", "dep:flate2", "dep:futures-channel", "dep:futures-core", "dep:futures-sink", "dep:futures-util", "dep:getrandom", "dep:hashbrown", "dep:indexmap", "dep:log", "dep:memchr", "dep:nom", "dep:num-integer", "dep:num-traits", "dep:once_cell", "dep:regex", "dep:scopeguard", "dep:semver", "dep:serde_json", "dep:smallvec", "dep:time", "dep:url", "dep:uuid", "dep:windows-sys"]
|
||||
gkrust-gtest = ["gkrust"]
|
||||
|
|
|
@ -1533,6 +1533,12 @@ who = "Mike Hommey <mh+mozilla@glandium.org>"
|
|||
criteria = "safe-to-deploy"
|
||||
delta = "1.8.0 -> 1.8.1"
|
||||
|
||||
[[audits.embed-manifest]]
|
||||
who = "Alex Franchuk <afranchuk@mozilla.com>"
|
||||
criteria = "safe-to-deploy"
|
||||
version = "1.4.0"
|
||||
notes = "Necessary dependencies, all environment variable access is for build script vars set by cargo."
|
||||
|
||||
[[audits.encoding_c]]
|
||||
who = "Henri Sivonen <hsivonen@hsivonen.fi>"
|
||||
criteria = "safe-to-deploy"
|
||||
|
@ -2386,6 +2392,12 @@ criteria = "safe-to-deploy"
|
|||
version = "0.5.4"
|
||||
notes = "I own this crate (I am contain-rs) and 0.5.4 passes miri. This code is very old and used by lots of people, so I'm pretty confident in it, even though it's in maintenance-mode and missing some nice-to-have APIs."
|
||||
|
||||
[[audits.linked-hash-map]]
|
||||
who = "Alex Franchuk <afranchuk@mozilla.com>"
|
||||
criteria = "safe-to-deploy"
|
||||
delta = "0.5.4 -> 0.5.6"
|
||||
notes = "New unsafe code has debug assertions and meets invariants. All other changes are formatting-related."
|
||||
|
||||
[[audits.linked-hash-map]]
|
||||
who = "Mike Hommey <mh+mozilla@glandium.org>"
|
||||
criteria = "safe-to-run"
|
||||
|
@ -4708,6 +4720,15 @@ who = "Makoto Kato <m_kato@ga2.so-net.ne.jp>"
|
|||
criteria = "safe-to-deploy"
|
||||
version = "0.10.1"
|
||||
|
||||
[[audits.zip]]
|
||||
who = "Alex Franchuk <afranchuk@mozilla.com>"
|
||||
criteria = "safe-to-deploy"
|
||||
version = "0.6.4"
|
||||
notes = """
|
||||
No unsafe code nor unwarranted dependencies. Side-effectful std usage is only
|
||||
present where expected (zip archive reading/writing and unpacking)
|
||||
"""
|
||||
|
||||
[[audits.zip]]
|
||||
who = "Mike Hommey <mh+mozilla@glandium.org>"
|
||||
criteria = "safe-to-run"
|
||||
|
|
|
@ -817,7 +817,3 @@ criteria = "safe-to-deploy"
|
|||
[[exemptions.xml-rs]]
|
||||
version = "0.8.4"
|
||||
criteria = "safe-to-deploy"
|
||||
|
||||
[[exemptions.zip]]
|
||||
version = "0.6.2"
|
||||
criteria = "safe-to-run"
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
{"files":{"CHANGELOG.md":"a1cbcc712504ce6658dc209cab886c724654e6e0ef7b26315fd86d27e04a6e09","Cargo.toml":"4f091eabd120a0c1beefc14b92728e312f0c4f57ec8d85e2b3c5008e1ab01fd0","LICENSE":"d041a5a86caaf9cc7720f3637d689252083a664d9ccd946a36d1c7c708d204cb","README.md":"8cf8a7b20cfb7fa43c4ee9585bf92ea6c5a5c4ca3ef700974edb02025d5146c8","rustfmt.toml":"84f2508ad6e506e71f2005a75ef20ba32eedb3bd6547af9ea04596517be883bb","src/embed/coff.rs":"55ba01eea1a5e16ef7ae4438f5cc51ec60b6eca45f5cabd706ee5b82da4f010b","src/embed/error.rs":"aecb4928e70b02b784557352608f6d4fb9b88b44ae3297a33969a0f2219c416c","src/embed/mod.rs":"a3f02a410c7b681f87ecfed415c97c4252569dee231b864c2b12e5d80ed4065e","src/embed/test.rs":"969dc4e2faf9ef9e9164b0e1ad01082a0a5e6ef5eb963526cb207a0ec380d809","src/lib.rs":"2786336ef4e787b0e3ccec9ab02043ae064f50df73a63ffe8c374217adfb353a","src/manifest/mod.rs":"c82936859d2ef795836972e08323c9698cc0513ba125cfed43414cd05d27e0b1","src/manifest/test.rs":"f4ab58ed4fc99b087ba30ab595026e980573aad980b8fa6f59a5b748404012ff","src/manifest/xml.rs":"1bce12120e17a49da43eabbd1b04f712b3f6ece7fcbca9186319e301890e20c5","testdata/sample.exe.manifest":"01e80ef76de2b628d452c7e80022654b9e0c8aee72ec64ee522c7083d835f4df"},"package":"41cd446c890d6bed1d8b53acef5f240069ebef91d6fae7c5f52efe61fe8b5eae"}
|
|
@ -0,0 +1,59 @@
|
|||
# Changelog
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [1.4.0] - 2023-06-22
|
||||
### Added
|
||||
- Use `empty_manifest()` to start from a manifest with no default values.
|
||||
Fixes [issue #6](https://gitlab.com/careyevans/embed-manifest/-/issues/6).
|
||||
### Fixed
|
||||
- Generate an object file with a single `.rsrc` section on GNU targets.
|
||||
This lets it replace the default manifest from MinGW build tools.
|
||||
Fixes [issue #5](https://gitlab.com/careyevans/embed-manifest/-/issues/5).
|
||||
|
||||
## [1.3.1] - 2022-08-07
|
||||
### Added
|
||||
- Format the code with Rustfmt.
|
||||
- Assume `gnullvm` target environment should work like `gnu`.
|
||||
- Add Windows 11 22H2 SDK version for maxversiontested.
|
||||
### Changed
|
||||
- Update `object` dependency and simplify unit tests.
|
||||
|
||||
## [1.3.0] - 2022-05-01
|
||||
### Changed
|
||||
- Use our own code again to generate COFF object files for GNU targets, but with
|
||||
better knowledge of how such files are structured, reducing dependencies and
|
||||
compile time.
|
||||
- Link directly to the COFF object file instead of an archive file with one member.
|
||||
### Fixed
|
||||
- Make the custom `Error` type public.
|
||||
|
||||
## [1.2.1] - 2022-04-18
|
||||
### Added
|
||||
- Add checks for Windows builds to the documentation, for programs that
|
||||
should still build for non-Windows targets.
|
||||
|
||||
## [1.2.0] - 2022-04-17
|
||||
### Added
|
||||
- Generate the manifest XML from Rust code rather than requiring the developer
|
||||
to supply a correct manifest file.
|
||||
|
||||
## [1.1.0] - 2022-03-24
|
||||
### Changed
|
||||
- Use [Gimli Object](https://crates.io/crates/object) crate to build COFF
|
||||
objects containing resources for GNU targets, removing a lot of magic numbers
|
||||
and generating output more like LLVM `windres`.
|
||||
|
||||
## [1.0.0] - 2021-12-18
|
||||
### Added
|
||||
- Initial version.
|
||||
|
||||
[1.0.0]: https://gitlab.com/careyevans/embed-manifest/-/releases/v1.0.0
|
||||
[1.1.0]: https://gitlab.com/careyevans/embed-manifest/-/releases/v1.1.0
|
||||
[1.2.0]: https://gitlab.com/careyevans/embed-manifest/-/releases/v1.2.0
|
||||
[1.2.1]: https://gitlab.com/careyevans/embed-manifest/-/releases/v1.2.1
|
||||
[1.3.0]: https://gitlab.com/careyevans/embed-manifest/-/releases/v1.3.0
|
||||
[1.3.1]: https://gitlab.com/careyevans/embed-manifest/-/releases/v1.3.1
|
||||
[1.4.0]: https://gitlab.com/careyevans/embed-manifest/-/releases/v1.4.0
|
|
@ -0,0 +1,38 @@
|
|||
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
|
||||
#
|
||||
# When uploading crates to the registry Cargo will automatically
|
||||
# "normalize" Cargo.toml files for maximal compatibility
|
||||
# with all versions of Cargo and also rewrite `path` dependencies
|
||||
# to registry (e.g., crates.io) dependencies.
|
||||
#
|
||||
# If you are reading this file be aware that the original Cargo.toml
|
||||
# will likely look very different (and much more reasonable).
|
||||
# See Cargo.toml.orig for the original contents.
|
||||
|
||||
[package]
|
||||
edition = "2021"
|
||||
rust-version = "1.56"
|
||||
name = "embed-manifest"
|
||||
version = "1.4.0"
|
||||
authors = ["Carey Evans <carey@carey.geek.nz>"]
|
||||
description = "Build script library to easily embed a Windows manifest."
|
||||
readme = "README.md"
|
||||
keywords = [
|
||||
"build",
|
||||
"manifest",
|
||||
"windows",
|
||||
]
|
||||
categories = ["development-tools::build-utils"]
|
||||
license = "MIT"
|
||||
repository = "https://gitlab.com/careyevans/embed-manifest"
|
||||
|
||||
[dev-dependencies.object]
|
||||
version = "0.31.1"
|
||||
features = [
|
||||
"read_core",
|
||||
"coff",
|
||||
]
|
||||
default-features = false
|
||||
|
||||
[dev-dependencies.tempfile]
|
||||
version = "3.5.0"
|
|
@ -0,0 +1,23 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2021, 2022 Carey Evans
|
||||
|
||||
All rights reserved.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,62 @@
|
|||
Windows Manifest Embedding for Rust
|
||||
===================================
|
||||
|
||||
The `embed-manifest` crate provides a straightforward way to embed
|
||||
a Windows manifest in an executable, whatever the build environment,
|
||||
without dependencies on external tools from LLVM or MinGW.
|
||||
|
||||
If you need to embed more resources than just a manifest, you may
|
||||
find the [winres](https://crates.io/crates/winres) or
|
||||
[embed-resource](https://crates.io/crates/embed-resource)
|
||||
crates more suitable. They have additional dependencies and setup
|
||||
requirements that may make them a little more difficult to use, though.
|
||||
|
||||
|
||||
Why use it?
|
||||
-----------
|
||||
|
||||
The Rust compiler doesn’t add a manifest to a Windows executable, which
|
||||
means that it runs with a few compatibility options and settings that
|
||||
make it look like the program is running on an older version of Windows.
|
||||
By adding an application manifest using this crate, even when cross-compiling:
|
||||
|
||||
- 32-bit programs with names that look like installers don’t try to run
|
||||
as an administrator.
|
||||
- 32-bit programs aren’t shown a virtualised view of the filesystem and
|
||||
registry.
|
||||
- Many non-Unicode APIs accept UTF-8 strings, the same as Rust uses.
|
||||
- The program sees the real Windows version, not Windows Vista.
|
||||
- Built-in message boxes and other UI elements can display without blurring
|
||||
on high-DPI monitors.
|
||||
- Other features like long path names in APIs can be enabled.
|
||||
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
To embed a default manifest, include this code in a `build.rs` build
|
||||
script:
|
||||
|
||||
```rust
|
||||
use embed_manifest::{embed_manifest, new_manifest};
|
||||
|
||||
fn main() {
|
||||
if std::env::var_os("CARGO_CFG_WINDOWS").is_some() {
|
||||
embed_manifest(new_manifest("Contoso.Sample"))
|
||||
.expect("unable to embed manifest file");
|
||||
}
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
}
|
||||
```
|
||||
|
||||
See the [crate documentation](https://docs.rs/embed-manifest) for
|
||||
information about how to customise the embedded manifest.
|
||||
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
For the avoidance of doubt, while this crate itself is licensed to
|
||||
you under the MIT License, this does not affect the copyright status
|
||||
and licensing of your own code when this is used from a Cargo build
|
||||
script.
|
|
@ -0,0 +1,2 @@
|
|||
max_width = 132
|
||||
newline_style = "Unix"
|
|
@ -0,0 +1,192 @@
|
|||
//! Just as much COFF object file support as is needed to write a resource data segment
|
||||
//! for GNU Windows targets. Inspired by the `write::Object` code from the `object` crate.
|
||||
//!
|
||||
//! Integers are converted from u64 to u32 and added without checking because the manifest
|
||||
//! data cannot get anywhere close to overflowing unless the supplied application name or
|
||||
//! number of dependencies was extremely long. If this code was used more generally or if
|
||||
//! the input was less trustworthy then more checked conversions and checked arithmetic
|
||||
//! would be needed.
|
||||
|
||||
use std::io::{self, Seek, SeekFrom, Write};
|
||||
use std::time::SystemTime;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum MachineType {
|
||||
I386,
|
||||
X86_64,
|
||||
Aarch64,
|
||||
}
|
||||
|
||||
impl MachineType {
|
||||
pub fn machine(&self) -> u16 {
|
||||
match self {
|
||||
Self::I386 => 0x014c,
|
||||
Self::X86_64 => 0x8664,
|
||||
Self::Aarch64 => 0xaa64,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn relocation_type(&self) -> u16 {
|
||||
match self {
|
||||
Self::I386 => 7,
|
||||
Self::X86_64 => 3,
|
||||
Self::Aarch64 => 2,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CoffWriter<W> {
|
||||
/// wrapped writer or buffer
|
||||
writer: W,
|
||||
/// machine type for file header
|
||||
machine_type: MachineType,
|
||||
// size in bytes of resource section data
|
||||
size_of_raw_data: u32,
|
||||
// number of relocations at the end of the section
|
||||
number_of_relocations: u16,
|
||||
}
|
||||
|
||||
impl<W: Write + Seek> CoffWriter<W> {
|
||||
/// Create a new instance wrapping a writer.
|
||||
pub fn new(mut writer: W, machine_type: MachineType) -> io::Result<Self> {
|
||||
// Add space for file header and section table.
|
||||
writer.write_all(&[0; 60])?;
|
||||
Ok(Self {
|
||||
writer,
|
||||
machine_type,
|
||||
size_of_raw_data: 0,
|
||||
number_of_relocations: 0,
|
||||
})
|
||||
}
|
||||
|
||||
/// Add data to a section and return its offset within the section.
|
||||
pub fn add_data(&mut self, data: &[u8]) -> io::Result<u32> {
|
||||
let start = self.size_of_raw_data;
|
||||
self.writer.write_all(data)?;
|
||||
self.size_of_raw_data = start + data.len() as u32;
|
||||
Ok(start)
|
||||
}
|
||||
|
||||
// Pad the resource data to a multiple of `n` bytes.
|
||||
pub fn align_to(&mut self, n: u32) -> io::Result<()> {
|
||||
let offset = self.size_of_raw_data % n;
|
||||
if offset != 0 {
|
||||
let padding = n - offset;
|
||||
for _ in 0..padding {
|
||||
self.writer.write_all(&[0])?;
|
||||
}
|
||||
self.size_of_raw_data += padding;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Write a relocation for a symbol at the end of the section.
|
||||
pub fn add_relocation(&mut self, address: u32) -> io::Result<()> {
|
||||
self.number_of_relocations += 1;
|
||||
self.writer.write_all(&address.to_le_bytes())?;
|
||||
self.writer.write_all(&[0, 0, 0, 0])?;
|
||||
self.writer.write_all(&self.machine_type.relocation_type().to_le_bytes())
|
||||
}
|
||||
|
||||
/// Write the object and section headers and write the symbol table.
|
||||
pub fn finish(mut self) -> io::Result<W> {
|
||||
// Get the timestamp for the header. `as` is correct here, as the low 32 bits
|
||||
// should be used.
|
||||
let timestamp = SystemTime::now()
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.map_or(0, |d| d.as_secs() as u32);
|
||||
|
||||
// Copy file location of the symbol table.
|
||||
let pointer_to_symbol_table = self.writer.stream_position()? as u32;
|
||||
|
||||
// Write the symbols and auxiliary data for the section.
|
||||
self.writer.write_all(b".rsrc\0\0\0")?; // name
|
||||
self.writer.write_all(&[0, 0, 0, 0])?; // address
|
||||
self.writer.write_all(&[1, 0])?; // section number (1-based)
|
||||
self.writer.write_all(&[0, 0, 3, 1])?; // type = 0, class = static, aux symbols = 1
|
||||
self.writer.write_all(&self.size_of_raw_data.to_le_bytes())?;
|
||||
self.writer.write_all(&self.number_of_relocations.to_le_bytes())?;
|
||||
self.writer.write_all(&[0; 12])?;
|
||||
|
||||
// Write the empty string table.
|
||||
self.writer.write_all(&[0; 4])?;
|
||||
|
||||
// Write the object header.
|
||||
let end_of_file = self.writer.seek(SeekFrom::Start(0))?;
|
||||
self.writer.write_all(&self.machine_type.machine().to_le_bytes())?;
|
||||
self.writer.write_all(&[1, 0])?; // number of sections
|
||||
self.writer.write_all(×tamp.to_le_bytes())?;
|
||||
self.writer.write_all(&pointer_to_symbol_table.to_le_bytes())?;
|
||||
self.writer.write_all(&[2, 0, 0, 0])?; // number of symbol table entries
|
||||
self.writer.write_all(&[0; 4])?; // optional header size = 0, characteristics = 0
|
||||
|
||||
// Write the section header.
|
||||
self.writer.write_all(b".rsrc\0\0\0")?;
|
||||
self.writer.write_all(&[0; 8])?; // virtual size = 0 and virtual address = 0
|
||||
self.writer.write_all(&self.size_of_raw_data.to_le_bytes())?;
|
||||
self.writer.write_all(&[60, 0, 0, 0])?; // pointer to raw data
|
||||
self.writer.write_all(&(self.size_of_raw_data + 60).to_le_bytes())?; // pointer to relocations
|
||||
self.writer.write_all(&[0; 4])?; // pointer to line numbers
|
||||
self.writer.write_all(&self.number_of_relocations.to_le_bytes())?;
|
||||
self.writer.write_all(&[0; 2])?; // number of line numbers
|
||||
self.writer.write_all(&[0x40, 0, 0x30, 0xc0])?; // characteristics
|
||||
|
||||
// Return the inner writer and dispose of this object.
|
||||
self.writer.seek(SeekFrom::Start(end_of_file))?;
|
||||
Ok(self.writer)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the bytes for a resource directory table.
|
||||
///
|
||||
/// Most of the fields are set to zero, including the timestamp, to aid
|
||||
/// with making builds reproducible.
|
||||
///
|
||||
/// ```c
|
||||
/// typedef struct {
|
||||
/// DWORD Characteristics,
|
||||
/// DWORD TimeDateStamp,
|
||||
/// WORD MajorVersion,
|
||||
/// WORD MinorVersion,
|
||||
/// WORD NumberOfNamedEntries,
|
||||
/// WORD NumberOfIdEntries
|
||||
/// } IMAGE_RESOURCE_DIRECTORY;
|
||||
/// ```
|
||||
pub fn resource_directory_table(number_of_id_entries: u16) -> [u8; 16] {
|
||||
let mut table = [0; 16];
|
||||
table[14..16].copy_from_slice(&number_of_id_entries.to_le_bytes());
|
||||
table
|
||||
}
|
||||
|
||||
/// Returns the bytes for a resource directory entry for an ID.
|
||||
///
|
||||
/// ```c
|
||||
/// typedef struct {
|
||||
/// DWORD Name,
|
||||
/// DWORD OffsetToData
|
||||
/// } IMAGE_RESOURCE_DIRECTORY_ENTRY;
|
||||
/// ```
|
||||
pub fn resource_directory_id_entry(id: u32, offset: u32, subdirectory: bool) -> [u8; 8] {
|
||||
let mut entry = [0; 8];
|
||||
entry[0..4].copy_from_slice(&id.to_le_bytes());
|
||||
let flag: u32 = if subdirectory { 0x80000000 } else { 0 };
|
||||
entry[4..8].copy_from_slice(&((offset & 0x7fffffff) | flag).to_le_bytes());
|
||||
entry
|
||||
}
|
||||
|
||||
/// Returns the bytes for a resource data entry.
|
||||
///
|
||||
/// ```c
|
||||
/// typedef struct {
|
||||
/// DWORD OffsetToData,
|
||||
/// DWORD Size,
|
||||
/// DWORD CodePage,
|
||||
/// DWORD Reserved
|
||||
/// } IMAGE_RESOURCE_DATA_ENTRY;
|
||||
/// ```
|
||||
pub fn resource_data_entry(rva: u32, size: u32) -> [u8; 16] {
|
||||
let mut entry = [0; 16];
|
||||
entry[0..4].copy_from_slice(&rva.to_le_bytes());
|
||||
entry[4..8].copy_from_slice(&size.to_le_bytes());
|
||||
entry
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
//! Error handling for application manifest embedding.
|
||||
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
use std::io::{self, ErrorKind};
|
||||
|
||||
/// The error type which is returned when application manifest embedding fails.
|
||||
#[derive(Debug)]
|
||||
pub struct Error {
|
||||
repr: Repr,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Repr {
|
||||
IoError(io::Error),
|
||||
UnknownTarget,
|
||||
}
|
||||
|
||||
impl Error {
|
||||
pub(crate) fn unknown_target() -> Error {
|
||||
Error {
|
||||
repr: Repr::UnknownTarget,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Error {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
match self.repr {
|
||||
Repr::IoError(ref e) => write!(f, "I/O error: {}", e),
|
||||
Repr::UnknownTarget => f.write_str("unknown target"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for Error {
|
||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||
match self.repr {
|
||||
Repr::IoError(ref e) => Some(e),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<io::Error> for Error {
|
||||
fn from(e: io::Error) -> Self {
|
||||
Error { repr: Repr::IoError(e) }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Error> for io::Error {
|
||||
fn from(e: Error) -> Self {
|
||||
match e.repr {
|
||||
Repr::IoError(ioe) => ioe,
|
||||
_ => io::Error::new(ErrorKind::Other, e),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,139 @@
|
|||
use std::env;
|
||||
use std::fs::{self, File};
|
||||
use std::io::{self, stdout, BufWriter, Cursor, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use crate::manifest::ManifestBuilder;
|
||||
|
||||
use self::coff::{resource_data_entry, resource_directory_id_entry, resource_directory_table, CoffWriter, MachineType};
|
||||
use self::error::Error;
|
||||
|
||||
mod coff;
|
||||
pub mod error;
|
||||
|
||||
#[cfg(test)]
|
||||
mod test;
|
||||
|
||||
/// Embeds the manifest described by `manifest` by converting it to XML,
|
||||
/// then saving it to a file and passing the correct options to the linker
|
||||
/// on MSVC targets, or by building a static library and instructing Cargo
|
||||
/// to link the executable against it on GNU targets.
|
||||
pub fn embed_manifest(manifest: ManifestBuilder) -> Result<(), Error> {
|
||||
let out_dir = get_out_dir()?;
|
||||
let target = get_target()?;
|
||||
if matches!(target.os, TargetOs::WindowsMsvc) {
|
||||
let manifest_file = out_dir.join("manifest.xml");
|
||||
write!(BufWriter::new(File::create(&manifest_file)?), "{}", manifest)?;
|
||||
link_manifest_msvc(&manifest_file, &mut stdout().lock())
|
||||
} else {
|
||||
let manifest_data = manifest.to_string();
|
||||
link_manifest_gnu(manifest_data.as_bytes(), &out_dir, target.arch, &mut stdout().lock())
|
||||
}
|
||||
}
|
||||
|
||||
/// Directly embeds the manifest in the provided `file` by passing the correct
|
||||
/// options to the linker on MSVC targets, or by building a static library
|
||||
/// and instructing Cargo to link the executable against it on GNU targets.
|
||||
pub fn embed_manifest_file<P: AsRef<Path>>(file: P) -> Result<(), io::Error> {
|
||||
let out_dir = get_out_dir()?;
|
||||
let target = get_target()?;
|
||||
if matches!(target.os, TargetOs::WindowsMsvc) {
|
||||
Ok(link_manifest_msvc(file.as_ref(), &mut stdout().lock())?)
|
||||
} else {
|
||||
let manifest = fs::read(file.as_ref())?;
|
||||
Ok(link_manifest_gnu(&manifest, &out_dir, target.arch, &mut stdout().lock())?)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_out_dir() -> Result<PathBuf, io::Error> {
|
||||
match env::var_os("OUT_DIR") {
|
||||
Some(dir) => Ok(PathBuf::from(dir)),
|
||||
None => env::current_dir(),
|
||||
}
|
||||
}
|
||||
|
||||
enum TargetOs {
|
||||
WindowsGnu,
|
||||
WindowsMsvc,
|
||||
}
|
||||
|
||||
struct Target {
|
||||
arch: MachineType,
|
||||
os: TargetOs,
|
||||
}
|
||||
|
||||
fn get_target() -> Result<Target, Error> {
|
||||
match env::var("TARGET") {
|
||||
Ok(target) => parse_target(&target),
|
||||
_ => Err(Error::unknown_target()),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_target(target: &str) -> Result<Target, Error> {
|
||||
let mut iter = target.splitn(3, '-');
|
||||
let arch = match iter.next() {
|
||||
Some("i686") => MachineType::I386,
|
||||
Some("aarch64") => MachineType::Aarch64,
|
||||
Some("x86_64") => MachineType::X86_64,
|
||||
_ => return Err(Error::unknown_target()),
|
||||
};
|
||||
if iter.next() != Some("pc") {
|
||||
return Err(Error::unknown_target());
|
||||
}
|
||||
let os = match iter.next() {
|
||||
Some("windows-gnu") => TargetOs::WindowsGnu,
|
||||
Some("windows-gnullvm") => TargetOs::WindowsGnu,
|
||||
Some("windows-msvc") => TargetOs::WindowsMsvc,
|
||||
_ => return Err(Error::unknown_target()),
|
||||
};
|
||||
Ok(Target { arch, os })
|
||||
}
|
||||
|
||||
fn link_manifest_msvc<W: Write>(manifest_path: &Path, out: &mut W) -> Result<(), Error> {
|
||||
writeln!(out, "cargo:rustc-link-arg-bins=/MANIFEST:EMBED")?;
|
||||
writeln!(
|
||||
out,
|
||||
"cargo:rustc-link-arg-bins=/MANIFESTINPUT:{}",
|
||||
manifest_path.canonicalize()?.display()
|
||||
)?;
|
||||
writeln!(out, "cargo:rustc-link-arg-bins=/MANIFESTUAC:NO")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn link_manifest_gnu<W: Write>(manifest: &[u8], out_dir: &Path, arch: MachineType, out: &mut W) -> Result<(), Error> {
|
||||
// Generate a COFF object file containing the manifest in a .rsrc section.
|
||||
let object_data = create_object_file(manifest, arch)?;
|
||||
let path = out_dir.join("embed-manifest.o");
|
||||
fs::create_dir_all(out_dir)?;
|
||||
fs::write(&path, object_data)?;
|
||||
|
||||
// Link the manifest with the executable.
|
||||
writeln!(out, "cargo:rustc-link-arg-bins={}", path.display())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_object_file(manifest: &[u8], arch: MachineType) -> Result<Vec<u8>, io::Error> {
|
||||
// Start object file with .rsrc section.
|
||||
let mut obj = CoffWriter::new(Cursor::new(Vec::with_capacity(4096)), arch)?;
|
||||
|
||||
// Create resource directories for type ID 24, name ID 1, language ID 1033.
|
||||
obj.add_data(&resource_directory_table(1))?;
|
||||
obj.add_data(&resource_directory_id_entry(24, 24, true))?;
|
||||
obj.add_data(&resource_directory_table(1))?;
|
||||
obj.add_data(&resource_directory_id_entry(1, 48, true))?;
|
||||
obj.add_data(&resource_directory_table(1))?;
|
||||
obj.add_data(&resource_directory_id_entry(1033, 72, false))?;
|
||||
|
||||
// Add resource data entry with relocated address.
|
||||
let address = obj.add_data(&resource_data_entry(88, manifest.len() as u32))?;
|
||||
|
||||
// Add the manifest data.
|
||||
obj.add_data(manifest)?;
|
||||
obj.align_to(8)?;
|
||||
|
||||
// Write manifest data relocation at the end of the section.
|
||||
obj.add_relocation(address)?;
|
||||
|
||||
// Finish writing file and return the populated object data.
|
||||
Ok(obj.finish()?.into_inner())
|
||||
}
|
|
@ -0,0 +1,173 @@
|
|||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use object::coff::CoffFile;
|
||||
use object::pe::{ImageResourceDataEntry, ImageResourceDirectory, ImageResourceDirectoryEntry, IMAGE_RESOURCE_DATA_IS_DIRECTORY};
|
||||
use object::{
|
||||
pod, Architecture, LittleEndian, Object, ObjectSection, ObjectSymbol, RelocationEncoding, RelocationKind, SectionKind,
|
||||
};
|
||||
use tempfile::{tempdir, TempDir};
|
||||
|
||||
use crate::embed::coff::MachineType;
|
||||
use crate::embed::{create_object_file, link_manifest_gnu, link_manifest_msvc, TargetOs};
|
||||
use crate::new_manifest;
|
||||
|
||||
#[test]
|
||||
fn create_obj() {
|
||||
let res = do_embed_file(MachineType::X86_64, TargetOs::WindowsGnu);
|
||||
let data = fs::read(&res.object_file()).unwrap();
|
||||
let obj = CoffFile::parse(&data[..]).unwrap();
|
||||
assert_eq!(obj.architecture(), Architecture::X86_64);
|
||||
let expected_manifest = fs::read(&sample_manifest_path()).unwrap();
|
||||
check_object_file(obj, &expected_manifest);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn link_lib_gnu() {
|
||||
let res = do_embed_file(MachineType::X86_64, TargetOs::WindowsGnu);
|
||||
assert!(res.object_file().exists());
|
||||
let object_option = format!("cargo:rustc-link-arg-bins={}", res.object_file().display());
|
||||
assert_eq!(res.lines(), &[object_option.as_str()]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn link_file_msvc() {
|
||||
let res = do_embed_file(MachineType::X86_64, TargetOs::WindowsMsvc);
|
||||
assert!(!res.object_file().exists());
|
||||
let mut input_option = String::from("cargo:rustc-link-arg-bins=/MANIFESTINPUT:");
|
||||
input_option.push_str(res.manifest_path.canonicalize().unwrap().to_str().unwrap());
|
||||
assert_eq!(
|
||||
res.lines(),
|
||||
&[
|
||||
"cargo:rustc-link-arg-bins=/MANIFEST:EMBED",
|
||||
input_option.as_str(),
|
||||
"cargo:rustc-link-arg-bins=/MANIFESTUAC:NO"
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
struct EmbedResult {
|
||||
manifest_path: PathBuf,
|
||||
out_dir: TempDir,
|
||||
output: String,
|
||||
}
|
||||
|
||||
impl EmbedResult {
|
||||
fn object_file(&self) -> PathBuf {
|
||||
self.out_dir.path().join("embed-manifest.o")
|
||||
}
|
||||
|
||||
fn lines(&self) -> Vec<&str> {
|
||||
self.output.lines().collect()
|
||||
}
|
||||
}
|
||||
|
||||
fn sample_manifest_path() -> PathBuf {
|
||||
Path::new(env!("CARGO_MANIFEST_DIR")).join("testdata/sample.exe.manifest")
|
||||
}
|
||||
|
||||
fn do_embed_file(arch: MachineType, os: TargetOs) -> EmbedResult {
|
||||
let manifest_path = sample_manifest_path();
|
||||
let out_dir = tempdir().unwrap();
|
||||
let mut buf: Vec<u8> = Vec::new();
|
||||
if matches!(os, TargetOs::WindowsMsvc) {
|
||||
link_manifest_msvc(&manifest_path, &mut buf).unwrap();
|
||||
} else {
|
||||
link_manifest_gnu(&fs::read(&manifest_path).unwrap(), out_dir.path(), arch, &mut buf).unwrap();
|
||||
}
|
||||
EmbedResult {
|
||||
manifest_path,
|
||||
out_dir,
|
||||
output: String::from_utf8(buf).unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn object_file_x86() {
|
||||
let manifest = new_manifest("Test.X86").to_string().into_bytes();
|
||||
let file = create_object_file(&manifest, MachineType::I386).unwrap();
|
||||
let obj = CoffFile::parse(&file[..]).unwrap();
|
||||
assert_eq!(obj.architecture(), Architecture::I386);
|
||||
check_object_file(obj, &manifest);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn object_file_x86_64() {
|
||||
let manifest = new_manifest("Test.X86_64").to_string().into_bytes();
|
||||
let file = create_object_file(&manifest, MachineType::X86_64).unwrap();
|
||||
let obj = CoffFile::parse(&file[..]).unwrap();
|
||||
assert_eq!(obj.architecture(), Architecture::X86_64);
|
||||
check_object_file(obj, &manifest);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn object_file_aarch64() {
|
||||
let manifest = new_manifest("Test.AARCH64").to_string().into_bytes();
|
||||
let file = create_object_file(&manifest, MachineType::Aarch64).unwrap();
|
||||
let obj = CoffFile::parse(&file[..]).unwrap();
|
||||
assert_eq!(obj.architecture(), Architecture::Aarch64);
|
||||
check_object_file(obj, &manifest);
|
||||
}
|
||||
|
||||
fn check_object_file(obj: CoffFile, expected_manifest: &[u8]) {
|
||||
// There should be one sections `.rsrc`.
|
||||
assert_eq!(
|
||||
obj.sections().map(|s| s.name().unwrap().to_string()).collect::<Vec<_>>(),
|
||||
&[".rsrc"]
|
||||
);
|
||||
|
||||
// There should be one section symbol.
|
||||
assert_eq!(
|
||||
obj.symbols().map(|s| s.name().unwrap().to_string()).collect::<Vec<_>>(),
|
||||
&[".rsrc"]
|
||||
);
|
||||
|
||||
// The resource section must be a data section.
|
||||
let rsrc = obj.section_by_name(".rsrc").unwrap();
|
||||
assert_eq!(rsrc.address(), 0);
|
||||
assert_eq!(rsrc.kind(), SectionKind::Data);
|
||||
|
||||
// The data RVA in the resource data entry must be relocatable.
|
||||
let (addr, reloc) = rsrc.relocations().next().unwrap();
|
||||
assert_eq!(reloc.kind(), RelocationKind::ImageOffset);
|
||||
assert_eq!(reloc.encoding(), RelocationEncoding::Generic);
|
||||
assert_eq!(addr, 0x48); // size of the directory table, three directories, and no strings
|
||||
assert_eq!(reloc.addend(), 0);
|
||||
|
||||
// The resource directory contains one manifest resource type subdirectory.
|
||||
let data = rsrc.data().unwrap();
|
||||
let (dir, rest) = pod::from_bytes::<ImageResourceDirectory>(data).unwrap();
|
||||
assert_eq!(0, dir.number_of_named_entries.get(LittleEndian));
|
||||
assert_eq!(1, dir.number_of_id_entries.get(LittleEndian));
|
||||
let (entries, _) = pod::slice_from_bytes::<ImageResourceDirectoryEntry>(rest, 1).unwrap();
|
||||
assert_eq!(24, entries[0].name_or_id.get(LittleEndian));
|
||||
let offset = entries[0].offset_to_data_or_directory.get(LittleEndian);
|
||||
assert_eq!(IMAGE_RESOURCE_DATA_IS_DIRECTORY, offset & IMAGE_RESOURCE_DATA_IS_DIRECTORY);
|
||||
let offset = (offset & !IMAGE_RESOURCE_DATA_IS_DIRECTORY) as usize;
|
||||
|
||||
// The manifest subdirectory contains one image (not DLL) manifest subdirectory.
|
||||
let (dir, rest) = pod::from_bytes::<ImageResourceDirectory>(&data[offset..]).unwrap();
|
||||
assert_eq!(0, dir.number_of_named_entries.get(LittleEndian));
|
||||
assert_eq!(1, dir.number_of_id_entries.get(LittleEndian));
|
||||
let (entries, _) = pod::slice_from_bytes::<ImageResourceDirectoryEntry>(rest, 1).unwrap();
|
||||
assert_eq!(1, entries[0].name_or_id.get(LittleEndian));
|
||||
let offset = entries[0].offset_to_data_or_directory.get(LittleEndian);
|
||||
assert_eq!(IMAGE_RESOURCE_DATA_IS_DIRECTORY, offset & IMAGE_RESOURCE_DATA_IS_DIRECTORY);
|
||||
let offset = (offset & !IMAGE_RESOURCE_DATA_IS_DIRECTORY) as usize;
|
||||
|
||||
// The image manifest subdirectory contains one US English manifest data entry.
|
||||
let (dir, rest) = pod::from_bytes::<ImageResourceDirectory>(&data[offset..]).unwrap();
|
||||
assert_eq!(0, dir.number_of_named_entries.get(LittleEndian));
|
||||
assert_eq!(1, dir.number_of_id_entries.get(LittleEndian));
|
||||
let (entries, _) = pod::slice_from_bytes::<ImageResourceDirectoryEntry>(rest, 1).unwrap();
|
||||
assert_eq!(0x0409, entries[0].name_or_id.get(LittleEndian));
|
||||
let offset = entries[0].offset_to_data_or_directory.get(LittleEndian);
|
||||
assert_eq!(0, offset & IMAGE_RESOURCE_DATA_IS_DIRECTORY);
|
||||
let offset = offset as usize;
|
||||
|
||||
// The manifest data matches what was added.
|
||||
let (entry, resource_data) = pod::from_bytes::<ImageResourceDataEntry>(&data[offset..]).unwrap();
|
||||
let end = entry.size.get(LittleEndian) as usize;
|
||||
let manifest = &resource_data[..end];
|
||||
assert_eq!(manifest, expected_manifest);
|
||||
}
|
|
@ -0,0 +1,134 @@
|
|||
//! The `embed-manifest` crate provides a straightforward way to embed
|
||||
//! a Windows manifest in an executable, whatever the build environment
|
||||
//! and even when cross-compiling, without dependencies on external
|
||||
//! tools from LLVM or MinGW.
|
||||
//!
|
||||
//! This should be called from a [build script][1], as shown below.
|
||||
//!
|
||||
//! [1]: https://doc.rust-lang.org/cargo/reference/build-scripts.html
|
||||
//!
|
||||
//! On MSVC targets, the manifest file is embedded in the executable by
|
||||
//! instructing Cargo to pass `/MANIFEST` options to `LINK.EXE`. This
|
||||
//! requires Cargo from Rust 1.56.
|
||||
//!
|
||||
//! On GNU targets, the manifest file is added as a resource in a COFF
|
||||
//! object file, and Cargo is instructed to link this file into the
|
||||
//! executable, also using functionality from Rust 1.56.
|
||||
//!
|
||||
//! # Usage
|
||||
//!
|
||||
//! This crate should be added to the `[build-dependencies]` section in
|
||||
//! your executable’s `Cargo.toml`:
|
||||
//!
|
||||
//! ```toml
|
||||
//! [build-dependencies]
|
||||
//! embed-manifest = "1.3.1"
|
||||
//! ```
|
||||
//!
|
||||
//! In the same directory, create a `build.rs` file to call this crate’s
|
||||
//! code when building for Windows, and to only run once:
|
||||
//!
|
||||
//! ```
|
||||
//! use embed_manifest::{embed_manifest, new_manifest};
|
||||
//!
|
||||
//! fn main() {
|
||||
//! # let tempdir = tempfile::tempdir().unwrap();
|
||||
//! # std::env::set_var("OUT_DIR", tempdir.path());
|
||||
//! # std::env::set_var("TARGET", "x86_64-pc-windows-gnu");
|
||||
//! # std::env::set_var("CARGO_CFG_WINDOWS", "");
|
||||
//! if std::env::var_os("CARGO_CFG_WINDOWS").is_some() {
|
||||
//! embed_manifest(new_manifest("Contoso.Sample")).expect("unable to embed manifest file");
|
||||
//! }
|
||||
//! println!("cargo:rerun-if-changed=build.rs");
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! To customise the application manifest, use the methods on it to change things like
|
||||
//! enabling the segment heap:
|
||||
//!
|
||||
//! ```
|
||||
//! use embed_manifest::{embed_manifest, new_manifest, manifest::HeapType};
|
||||
//!
|
||||
//! fn main() {
|
||||
//! # let tempdir = tempfile::tempdir().unwrap();
|
||||
//! # std::env::set_var("OUT_DIR", tempdir.path());
|
||||
//! # std::env::set_var("TARGET", "x86_64-pc-windows-gnu");
|
||||
//! # std::env::set_var("CARGO_CFG_WINDOWS", "");
|
||||
//! if std::env::var_os("CARGO_CFG_WINDOWS").is_some() {
|
||||
//! embed_manifest(new_manifest("Contoso.Sample").heap_type(HeapType::SegmentHeap))
|
||||
//! .expect("unable to embed manifest file");
|
||||
//! }
|
||||
//! println!("cargo:rerun-if-changed=build.rs");
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! or making it always use legacy single-byte API encoding and only declaring compatibility
|
||||
//! up to Windows 8.1, without checking whether this is a Windows build:
|
||||
//!
|
||||
//! ```
|
||||
//! use embed_manifest::{embed_manifest, new_manifest};
|
||||
//! use embed_manifest::manifest::{ActiveCodePage::Legacy, SupportedOS::*};
|
||||
//!
|
||||
//! fn main() {
|
||||
//! # let tempdir = tempfile::tempdir().unwrap();
|
||||
//! # std::env::set_var("OUT_DIR", tempdir.path());
|
||||
//! # std::env::set_var("TARGET", "x86_64-pc-windows-gnu");
|
||||
//! let manifest = new_manifest("Contoso.Sample")
|
||||
//! .active_code_page(Legacy)
|
||||
//! .supported_os(Windows7..=Windows81);
|
||||
//! embed_manifest(manifest).expect("unable to embed manifest file");
|
||||
//! println!("cargo:rerun-if-changed=build.rs");
|
||||
//! }
|
||||
//! ```
|
||||
|
||||
#![allow(clippy::needless_doctest_main)]
|
||||
|
||||
pub use embed::error::Error;
|
||||
pub use embed::{embed_manifest, embed_manifest_file};
|
||||
|
||||
use crate::manifest::ManifestBuilder;
|
||||
|
||||
mod embed;
|
||||
pub mod manifest;
|
||||
|
||||
/// Creates a new [`ManifestBuilder`] with sensible defaults, allowing customisation
|
||||
/// before the Windows application manifest XML is generated.
|
||||
///
|
||||
/// The initial values used by the manifest are:
|
||||
/// - Version number from the `CARGO_PKG_VERSION_MAJOR`, `CARGO_PKG_VERSION_MINOR` and
|
||||
/// `CARGO_PKG_VERSION_PATCH` environment variables. This can then be changed with
|
||||
/// [`version()`][ManifestBuilder::version].
|
||||
/// - A dependency on version 6 of the Common Controls so that message boxes and dialogs
|
||||
/// will use the latest design, and have the best available support for high DPI displays.
|
||||
/// This can be removed with
|
||||
/// [`remove_dependency`][ManifestBuilder::remove_dependency].
|
||||
/// - [Compatible with Windows from 7 to 11][ManifestBuilder::supported_os],
|
||||
/// matching the Rust compiler [tier 1 targets][tier1].
|
||||
/// - A “[maximum version tested][ManifestBuilder::max_version_tested]” of Windows 10
|
||||
/// version 1903.
|
||||
/// - An [active code page][ManifestBuilder::active_code_page] of UTF-8, so that
|
||||
/// single-byte Windows APIs will generally interpret Rust strings correctly, starting
|
||||
/// from Windows 10 version 1903.
|
||||
/// - [Version 2 of per-monitor high DPI awareness][manifest::DpiAwareness::PerMonitorV2Only],
|
||||
/// so that user interface elements can be scaled correctly by the application and
|
||||
/// Common Controls from Windows 10 version 1703.
|
||||
/// - [Long path awareness][ManifestBuilder::long_path_aware] from Windows 10 version
|
||||
/// 1607 [when separately enabled][longpaths].
|
||||
/// - [Printer driver isolation][ManifestBuilder::printer_driver_isolation] enabled
|
||||
/// to improve reliability and security.
|
||||
/// - An [execution level][ManifestBuilder::requested_execution_level] of “as invoker”
|
||||
/// so that the UAC elevation dialog will never be displayed, regardless of the name
|
||||
/// of the program, and [UAC Virtualisation][uac] is disabled in 32-bit programs.
|
||||
///
|
||||
/// [tier1]: https://doc.rust-lang.org/nightly/rustc/platform-support.html
|
||||
/// [longpaths]: https://docs.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation?tabs=cmd#enable-long-paths-in-windows-10-version-1607-and-later
|
||||
/// [uac]: https://docs.microsoft.com/en-us/windows/security/identity-protection/user-account-control/how-user-account-control-works#virtualization
|
||||
pub fn new_manifest(name: &str) -> ManifestBuilder {
|
||||
ManifestBuilder::new(name)
|
||||
}
|
||||
|
||||
/// Creates a new [`ManifestBuilder`] without any settings, allowing creation of
|
||||
/// a manifest with only desired content.
|
||||
pub fn empty_manifest() -> ManifestBuilder {
|
||||
ManifestBuilder::empty()
|
||||
}
|
|
@ -0,0 +1,882 @@
|
|||
//! A builder for Windows application manifest XML files.
|
||||
//!
|
||||
//! This module allows the construction of application manifests from code with the
|
||||
//! [`ManifestBuilder`], for use from a Cargo build script. Once configured, the builder
|
||||
//! should be passed to [`embed_manifest()`][crate::embed_manifest] to generate the
|
||||
//! correct instructions for Cargo. For any other use, the builder will output the XML
|
||||
//! when formatted for [`Display`], or with [`to_string()`][ToString]. For more
|
||||
//! information about the different elements of an application manifest, see
|
||||
//! [Application Manifests][1] in the Microsoft Windows App Development documentation.
|
||||
//!
|
||||
//! [1]: https://docs.microsoft.com/en-us/windows/win32/sbscs/application-manifests
|
||||
//!
|
||||
//! To generate the manifest XML separately, the XML can be output with `write!` or
|
||||
//! copied to a string with [`to_string()`][ToString]. To produce the manifest XML with
|
||||
//! extra whitespace for formatting, format it with the ‘alternate’ flag:
|
||||
//!
|
||||
//! ```
|
||||
//! # use embed_manifest::new_manifest;
|
||||
//! let builder = new_manifest("Company.OrgUnit.Program");
|
||||
//! assert_eq!(format!("{:#}", builder), r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
//! <assembly xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3" manifestVersion="1.0">
|
||||
//! <assemblyIdentity name="Company.OrgUnit.Program" type="win32" version="1.4.0.0"/>
|
||||
//! <dependency>
|
||||
//! <dependentAssembly>
|
||||
//! <assemblyIdentity language="*" name="Microsoft.Windows.Common-Controls" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" type="win32" version="6.0.0.0"/>
|
||||
//! </dependentAssembly>
|
||||
//! </dependency>
|
||||
//! <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
//! <application>
|
||||
//! <maxversiontested Id="10.0.18362.1"/>
|
||||
//! <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
|
||||
//! <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
|
||||
//! <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
|
||||
//! <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
|
||||
//! </application>
|
||||
//! </compatibility>
|
||||
//! <asmv3:application>
|
||||
//! <asmv3:windowsSettings>
|
||||
//! <activeCodePage xmlns="http://schemas.microsoft.com/SMI/2019/WindowsSettings">UTF-8</activeCodePage>
|
||||
//! <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">permonitorv2</dpiAwareness>
|
||||
//! <longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
|
||||
//! <printerDriverIsolation xmlns="http://schemas.microsoft.com/SMI/2011/WindowsSettings">true</printerDriverIsolation>
|
||||
//! </asmv3:windowsSettings>
|
||||
//! </asmv3:application>
|
||||
//! <asmv3:trustInfo>
|
||||
//! <asmv3:security>
|
||||
//! <asmv3:requestedPrivileges>
|
||||
//! <asmv3:requestedExecutionLevel level="asInvoker" uiAccess="false"/>
|
||||
//! </asmv3:requestedPrivileges>
|
||||
//! </asmv3:security>
|
||||
//! </asmv3:trustInfo>
|
||||
//! </assembly>"#.replace("\n", "\r\n"))
|
||||
//! ```
|
||||
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::ops::RangeBounds;
|
||||
use std::{env, fmt};
|
||||
|
||||
use crate::manifest::xml::XmlFormatter;
|
||||
|
||||
mod xml;
|
||||
|
||||
#[cfg(test)]
|
||||
mod test;
|
||||
|
||||
/// An opaque container to describe the Windows application manifest for the
|
||||
/// executable. A new instance with reasonable defaults is created with
|
||||
/// [`new_manifest()`][crate::new_manifest].
|
||||
#[derive(Debug)]
|
||||
pub struct ManifestBuilder {
|
||||
identity: Option<AssemblyIdentity>,
|
||||
dependent_assemblies: Vec<AssemblyIdentity>,
|
||||
compatibility: ApplicationCompatibility,
|
||||
windows_settings: WindowsSettings,
|
||||
requested_execution_level: Option<RequestedExecutionLevel>,
|
||||
}
|
||||
|
||||
impl ManifestBuilder {
|
||||
pub(crate) fn new(name: &str) -> Self {
|
||||
ManifestBuilder {
|
||||
identity: Some(AssemblyIdentity::application(name)),
|
||||
dependent_assemblies: vec![AssemblyIdentity::new(
|
||||
"Microsoft.Windows.Common-Controls",
|
||||
[6, 0, 0, 0],
|
||||
0x6595b64144ccf1df,
|
||||
)],
|
||||
compatibility: ApplicationCompatibility {
|
||||
max_version_tested: Some(MaxVersionTested::Windows10Version1903),
|
||||
supported_os: vec![
|
||||
SupportedOS::Windows7,
|
||||
SupportedOS::Windows8,
|
||||
SupportedOS::Windows81,
|
||||
SupportedOS::Windows10,
|
||||
],
|
||||
},
|
||||
windows_settings: WindowsSettings::new(),
|
||||
requested_execution_level: Some(RequestedExecutionLevel {
|
||||
level: ExecutionLevel::AsInvoker,
|
||||
ui_access: false,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn empty() -> Self {
|
||||
ManifestBuilder {
|
||||
identity: None,
|
||||
dependent_assemblies: Vec::new(),
|
||||
compatibility: ApplicationCompatibility {
|
||||
max_version_tested: None,
|
||||
supported_os: Vec::new(),
|
||||
},
|
||||
windows_settings: WindowsSettings::empty(),
|
||||
requested_execution_level: None,
|
||||
}
|
||||
}
|
||||
|
||||
// Set the dot-separated [application name][identity] in the manifest.
|
||||
//
|
||||
// [identity]: https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests#assemblyIdentity
|
||||
pub fn name(mut self, name: &str) -> Self {
|
||||
match self.identity {
|
||||
Some(ref mut identity) => identity.name = name.to_string(),
|
||||
None => self.identity = Some(AssemblyIdentity::application_version(name, 0, 0, 0, 0)),
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the four-part application version number in the manifest.
|
||||
pub fn version(mut self, major: u16, minor: u16, build: u16, revision: u16) -> Self {
|
||||
match self.identity {
|
||||
Some(ref mut identity) => identity.version = Version(major, minor, build, revision),
|
||||
None => {
|
||||
self.identity = Some(AssemblyIdentity::application_version("", major, minor, build, revision));
|
||||
}
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Add a dependency on a specific version of a side-by-side assembly
|
||||
/// to the application manifest. For more information on side-by-side
|
||||
/// assemblies, see [Using Side-by-side Assemblies][sxs].
|
||||
///
|
||||
/// [sxs]: https://docs.microsoft.com/en-us/windows/win32/sbscs/using-side-by-side-assemblies
|
||||
pub fn dependency(mut self, identity: AssemblyIdentity) -> Self {
|
||||
self.dependent_assemblies.push(identity);
|
||||
self
|
||||
}
|
||||
|
||||
/// Remove a dependency on a side-by-side assembly. This can be used to
|
||||
/// remove the default dependency on Common Controls version 6:
|
||||
///
|
||||
/// ```
|
||||
/// # use embed_manifest::new_manifest;
|
||||
/// new_manifest("Company.OrgUnit.Program")
|
||||
/// .remove_dependency("Microsoft.Windows.Common-Controls")
|
||||
/// # ;
|
||||
/// ```
|
||||
pub fn remove_dependency(mut self, name: &str) -> Self {
|
||||
self.dependent_assemblies.retain(|d| d.name != name);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the “maximum version tested” based on a Windows SDK version.
|
||||
/// This compatibility setting enables the use of XAML Islands, as described in
|
||||
/// [Host a standard WinRT XAML control in a C++ desktop (Win32) app][xaml].
|
||||
///
|
||||
/// [xaml]: https://docs.microsoft.com/en-us/windows/apps/desktop/modernize/host-standard-control-with-xaml-islands-cpp
|
||||
pub fn max_version_tested(mut self, version: MaxVersionTested) -> Self {
|
||||
self.compatibility.max_version_tested = Some(version);
|
||||
self
|
||||
}
|
||||
|
||||
/// Remove the “maximum version tested” from the application compatibility.
|
||||
pub fn remove_max_version_tested(mut self) -> Self {
|
||||
self.compatibility.max_version_tested = None;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the range of supported versions of Windows for application compatibility.
|
||||
/// The default value declares compatibility with every version from
|
||||
/// [Windows 7][SupportedOS::Windows7] to [Windows 10 and 11][SupportedOS::Windows10].
|
||||
pub fn supported_os<R: RangeBounds<SupportedOS>>(mut self, os_range: R) -> Self {
|
||||
use SupportedOS::*;
|
||||
|
||||
self.compatibility.supported_os.clear();
|
||||
for os in [WindowsVista, Windows7, Windows8, Windows81, Windows10] {
|
||||
if os_range.contains(&os) {
|
||||
self.compatibility.supported_os.push(os);
|
||||
}
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the code page used for single-byte Windows API, starting from Windows 10
|
||||
/// version 1903. The default setting of [UTF-8][`ActiveCodePage::Utf8`] makes Rust
|
||||
/// strings work directly with APIs like `MessageBoxA`.
|
||||
pub fn active_code_page(mut self, code_page: ActiveCodePage) -> Self {
|
||||
self.windows_settings.active_code_page = code_page;
|
||||
self
|
||||
}
|
||||
|
||||
/// Configures how Windows should display this program on monitors where the
|
||||
/// graphics need scaling, whether by the application drawing its user
|
||||
/// interface at different sizes or by the Windows system rendering the graphics
|
||||
/// to a bitmap then resizing that for display.
|
||||
pub fn dpi_awareness(mut self, setting: DpiAwareness) -> Self {
|
||||
self.windows_settings.dpi_awareness = setting;
|
||||
self
|
||||
}
|
||||
|
||||
/// Attempts to scale GDI primitives by the per-monitor scaling values,
|
||||
/// from Windows 10 version 1703. It seems to be best to leave this disabled.
|
||||
pub fn gdi_scaling(mut self, setting: Setting) -> Self {
|
||||
self.windows_settings.gdi_scaling = setting.enabled();
|
||||
self
|
||||
}
|
||||
|
||||
/// Select the memory allocator use by the standard heap allocation APIs,
|
||||
/// including the default Rust allocator. Selecting a different algorithm
|
||||
/// may make performance and memory use better or worse, so any changes
|
||||
/// should be carefully tested.
|
||||
pub fn heap_type(mut self, setting: HeapType) -> Self {
|
||||
self.windows_settings.heap_type = setting;
|
||||
self
|
||||
}
|
||||
|
||||
/// Enable paths longer than 260 characters with some wide-character Win32 APIs,
|
||||
/// when also enabled in the Windows registry. For more details, see
|
||||
/// [Maximum Path Length Limitation][1] in the Windows App Development
|
||||
/// documentation.
|
||||
///
|
||||
/// As of Rust 1.58, the [Rust standard library bypasses this limitation][2] itself
|
||||
/// by using Unicode paths beginning with `\\?\`.
|
||||
///
|
||||
/// [1]: https://docs.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation
|
||||
/// [2]: https://github.com/rust-lang/rust/pull/89174
|
||||
pub fn long_path_aware(mut self, setting: Setting) -> Self {
|
||||
self.windows_settings.long_path_aware = setting.enabled();
|
||||
self
|
||||
}
|
||||
|
||||
/// Enable printer driver isolation for the application, improving security and
|
||||
/// stability when printing by loading the printer driver in a separate
|
||||
/// application. This is poorly documented, but is described in a blog post,
|
||||
/// “[Application-level Printer Driver Isolation][post]”.
|
||||
///
|
||||
/// [post]: https://peteronprogramming.wordpress.com/2018/01/22/application-level-printer-driver-isolation/
|
||||
pub fn printer_driver_isolation(mut self, setting: Setting) -> Self {
|
||||
self.windows_settings.printer_driver_isolation = setting.enabled();
|
||||
self
|
||||
}
|
||||
|
||||
/// Configure whether the application should receive mouse wheel scroll events
|
||||
/// with a minimum delta of 1, 40 or 120, as described in
|
||||
/// [Windows precision touchpad devices][touchpad].
|
||||
///
|
||||
/// [touchpad]: https://docs.microsoft.com/en-us/windows/win32/w8cookbook/windows-precision-touchpad-devices
|
||||
pub fn scrolling_awareness(mut self, setting: ScrollingAwareness) -> Self {
|
||||
self.windows_settings.scrolling_awareness = setting;
|
||||
self
|
||||
}
|
||||
|
||||
/// Allows the application to disable the filtering that normally
|
||||
/// removes UWP windows from the results of the `EnumWindows` API.
|
||||
pub fn window_filtering(mut self, setting: Setting) -> Self {
|
||||
self.windows_settings.disable_window_filtering = setting.disabled();
|
||||
self
|
||||
}
|
||||
|
||||
/// Selects the authorities to execute the program with, rather than
|
||||
/// [guessing based on a filename][installer] like `setup.exe`,
|
||||
/// `update.exe` or `patch.exe`.
|
||||
///
|
||||
/// [installer]: https://docs.microsoft.com/en-us/windows/security/identity-protection/user-account-control/how-user-account-control-works#installer-detection-technology
|
||||
pub fn requested_execution_level(mut self, level: ExecutionLevel) -> Self {
|
||||
match self.requested_execution_level {
|
||||
Some(ref mut requested_execution_level) => requested_execution_level.level = level,
|
||||
None => self.requested_execution_level = Some(RequestedExecutionLevel { level, ui_access: false }),
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Allows the application to access the user interface of applications
|
||||
/// running with elevated permissions when this program does not, typically
|
||||
/// for accessibility. The program must additionally be correctly signed
|
||||
/// and installed in a trusted location like the Program Files directory.
|
||||
pub fn ui_access(mut self, access: bool) -> Self {
|
||||
match self.requested_execution_level {
|
||||
Some(ref mut requested_execution_level) => requested_execution_level.ui_access = access,
|
||||
None => {
|
||||
self.requested_execution_level = Some(RequestedExecutionLevel {
|
||||
level: ExecutionLevel::AsInvoker,
|
||||
ui_access: access,
|
||||
})
|
||||
}
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ManifestBuilder {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
let mut w = XmlFormatter::new(f);
|
||||
w.start_document()?;
|
||||
let mut attrs = vec![("xmlns", "urn:schemas-microsoft-com:asm.v1")];
|
||||
if !self.windows_settings.is_empty() || self.requested_execution_level.is_some() {
|
||||
attrs.push(("xmlns:asmv3", "urn:schemas-microsoft-com:asm.v3"));
|
||||
}
|
||||
attrs.push(("manifestVersion", "1.0"));
|
||||
w.start_element("assembly", &attrs)?;
|
||||
if let Some(ref identity) = self.identity {
|
||||
identity.xml_to(&mut w)?;
|
||||
}
|
||||
if !self.dependent_assemblies.is_empty() {
|
||||
w.element("dependency", &[], |w| {
|
||||
for d in self.dependent_assemblies.as_slice() {
|
||||
w.element("dependentAssembly", &[], |w| d.xml_to(w))?;
|
||||
}
|
||||
Ok(())
|
||||
})?;
|
||||
}
|
||||
if !self.compatibility.is_empty() {
|
||||
self.compatibility.xml_to(&mut w)?;
|
||||
}
|
||||
if !self.windows_settings.is_empty() {
|
||||
self.windows_settings.xml_to(&mut w)?;
|
||||
}
|
||||
if let Some(ref requested_execution_level) = self.requested_execution_level {
|
||||
requested_execution_level.xml_to(&mut w)?;
|
||||
}
|
||||
w.end_element("assembly")
|
||||
}
|
||||
}
|
||||
|
||||
/// Identity of a side-by-side assembly dependency for the application.
|
||||
#[derive(Debug)]
|
||||
pub struct AssemblyIdentity {
|
||||
r#type: AssemblyType,
|
||||
name: String,
|
||||
language: Option<String>,
|
||||
processor_architecture: Option<AssemblyProcessorArchitecture>,
|
||||
version: Version,
|
||||
public_key_token: Option<PublicKeyToken>,
|
||||
}
|
||||
|
||||
impl AssemblyIdentity {
|
||||
fn application(name: &str) -> AssemblyIdentity {
|
||||
let major = env::var("CARGO_PKG_VERSION_MAJOR").map_or(0, |s| s.parse().unwrap_or(0));
|
||||
let minor = env::var("CARGO_PKG_VERSION_MINOR").map_or(0, |s| s.parse().unwrap_or(0));
|
||||
let patch = env::var("CARGO_PKG_VERSION_PATCH").map_or(0, |s| s.parse().unwrap_or(0));
|
||||
AssemblyIdentity {
|
||||
r#type: AssemblyType::Win32,
|
||||
name: name.to_string(),
|
||||
language: None,
|
||||
processor_architecture: None,
|
||||
version: Version(major, minor, patch, 0),
|
||||
public_key_token: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn application_version(name: &str, major: u16, minor: u16, build: u16, revision: u16) -> AssemblyIdentity {
|
||||
AssemblyIdentity {
|
||||
r#type: AssemblyType::Win32,
|
||||
name: name.to_string(),
|
||||
language: None,
|
||||
processor_architecture: None,
|
||||
version: Version(major, minor, build, revision),
|
||||
public_key_token: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new value for a [manifest dependency][ManifestBuilder::dependency],
|
||||
/// with the `version` as an array of numbers like `[1, 0, 0, 0]` for 1.0.0.0,
|
||||
/// and the public key token as a 64-bit number like `0x6595b64144ccf1df`.
|
||||
pub fn new(name: &str, version: [u16; 4], public_key_token: u64) -> AssemblyIdentity {
|
||||
AssemblyIdentity {
|
||||
r#type: AssemblyType::Win32,
|
||||
name: name.to_string(),
|
||||
language: Some("*".to_string()),
|
||||
processor_architecture: Some(AssemblyProcessorArchitecture::All),
|
||||
version: Version(version[0], version[1], version[2], version[3]),
|
||||
public_key_token: Some(PublicKeyToken(public_key_token)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Change the language from `"*"` to the language code in `language`.
|
||||
pub fn language(mut self, language: &str) -> Self {
|
||||
self.language = Some(language.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
/// Change the processor architecture from `"*"` to the architecture in `arch`.
|
||||
pub fn processor_architecture(mut self, arch: AssemblyProcessorArchitecture) -> Self {
|
||||
self.processor_architecture = Some(arch);
|
||||
self
|
||||
}
|
||||
|
||||
fn xml_to(&self, w: &mut XmlFormatter) -> fmt::Result {
|
||||
let version = self.version.to_string();
|
||||
let public_key_token = self.public_key_token.as_ref().map(|token| token.to_string());
|
||||
|
||||
let mut attrs: Vec<(&str, &str)> = Vec::with_capacity(6);
|
||||
if let Some(ref language) = self.language {
|
||||
attrs.push(("language", language));
|
||||
}
|
||||
attrs.push(("name", &self.name));
|
||||
if let Some(ref arch) = self.processor_architecture {
|
||||
attrs.push(("processorArchitecture", arch.as_str()))
|
||||
}
|
||||
if let Some(ref token) = public_key_token {
|
||||
attrs.push(("publicKeyToken", token));
|
||||
}
|
||||
attrs.push(("type", self.r#type.as_str()));
|
||||
attrs.push(("version", &version));
|
||||
w.empty_element("assemblyIdentity", &attrs)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Version(u16, u16, u16, u16);
|
||||
|
||||
impl fmt::Display for Version {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
write!(f, "{}.{}.{}.{}", self.0, self.1, self.2, self.3)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct PublicKeyToken(u64);
|
||||
|
||||
impl fmt::Display for PublicKeyToken {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{:016x}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Processor architecture for an assembly identity.
|
||||
#[derive(Debug)]
|
||||
#[non_exhaustive]
|
||||
pub enum AssemblyProcessorArchitecture {
|
||||
/// Any processor architecture, as `"*"`.
|
||||
All,
|
||||
/// 32-bit x86 processors, as `"x86"`.
|
||||
X86,
|
||||
/// 64-bit x64 processors, as `"x64"`.
|
||||
Amd64,
|
||||
/// 32-bit ARM processors, as `"arm"`.
|
||||
Arm,
|
||||
/// 64-bit ARM processors, as `"arm64"`.
|
||||
Arm64,
|
||||
}
|
||||
|
||||
impl AssemblyProcessorArchitecture {
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
Self::All => "*",
|
||||
Self::X86 => "x86",
|
||||
Self::Amd64 => "amd64",
|
||||
Self::Arm => "arm",
|
||||
Self::Arm64 => "arm64",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for AssemblyProcessorArchitecture {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
f.pad(self.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[non_exhaustive]
|
||||
enum AssemblyType {
|
||||
Win32,
|
||||
}
|
||||
|
||||
impl AssemblyType {
|
||||
fn as_str(&self) -> &'static str {
|
||||
"win32"
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct ApplicationCompatibility {
|
||||
max_version_tested: Option<MaxVersionTested>,
|
||||
supported_os: Vec<SupportedOS>,
|
||||
}
|
||||
|
||||
impl ApplicationCompatibility {
|
||||
fn is_empty(&self) -> bool {
|
||||
self.supported_os.is_empty()
|
||||
}
|
||||
|
||||
fn xml_to(&self, w: &mut XmlFormatter) -> fmt::Result {
|
||||
w.element(
|
||||
"compatibility",
|
||||
&[("xmlns", "urn:schemas-microsoft-com:compatibility.v1")],
|
||||
|w| {
|
||||
w.element("application", &[], |w| {
|
||||
if self.supported_os.contains(&SupportedOS::Windows10) {
|
||||
if let Some(ref version) = self.max_version_tested {
|
||||
w.empty_element("maxversiontested", &[("Id", version.as_str())])?;
|
||||
}
|
||||
}
|
||||
for os in self.supported_os.iter() {
|
||||
w.empty_element("supportedOS", &[("Id", os.as_str())])?
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Windows build versions for [`max_version_tested()`][ManifestBuilder::max_version_tested]
|
||||
/// from the [Windows SDK archive](https://developer.microsoft.com/en-us/windows/downloads/sdk-archive/).
|
||||
#[derive(Debug)]
|
||||
#[non_exhaustive]
|
||||
pub enum MaxVersionTested {
|
||||
/// Windows 10 version 1903, with build version 10.0.18362.0.
|
||||
Windows10Version1903,
|
||||
/// Windows 10 version 2004, with build version 10.0.19041.0.
|
||||
Windows10Version2004,
|
||||
/// Windows 10 version 2104, with build version 10.0.20348.0.
|
||||
Windows10Version2104,
|
||||
/// Windows 11, with build version 10.0.22000.194.
|
||||
Windows11,
|
||||
/// Windows 11 version 22H2, with build version 10.0.22621.1.
|
||||
Windows11Version22H2,
|
||||
}
|
||||
|
||||
impl MaxVersionTested {
|
||||
/// Return the Windows version as a string.
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Windows10Version1903 => "10.0.18362.1",
|
||||
Self::Windows10Version2004 => "10.0.19041.0",
|
||||
Self::Windows10Version2104 => "10.0.20348.0",
|
||||
Self::Windows11 => "10.0.22000.194",
|
||||
Self::Windows11Version22H2 => "10.0.22621.1",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for MaxVersionTested {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
f.pad(self.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
/// Operating system versions for Windows compatibility.
|
||||
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq)]
|
||||
#[non_exhaustive]
|
||||
pub enum SupportedOS {
|
||||
/// Windows Vista and Windows Server 2008.
|
||||
WindowsVista,
|
||||
/// Windows 7 and Windows Server 2008 R2.
|
||||
Windows7,
|
||||
/// Windows 8 and Windows Server 2012.
|
||||
Windows8,
|
||||
/// Windows 8.1 and Windows Server 2012 R2.
|
||||
Windows81,
|
||||
/// Windows 10 and 11, and Windows Server 2016, 2019 and 2022.
|
||||
Windows10,
|
||||
}
|
||||
|
||||
impl SupportedOS {
|
||||
/// Returns the GUID string for the Windows version.
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
Self::WindowsVista => "{e2011457-1546-43c5-a5fe-008deee3d3f0}",
|
||||
Self::Windows7 => "{35138b9a-5d96-4fbd-8e2d-a2440225f93a}",
|
||||
Self::Windows8 => "{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}",
|
||||
Self::Windows81 => "{1f676c76-80e1-4239-95bb-83d0f6d0da78}",
|
||||
Self::Windows10 => "{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for SupportedOS {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
f.pad(self.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
static WS2005: (&str, &str) = ("xmlns", "http://schemas.microsoft.com/SMI/2005/WindowsSettings");
|
||||
static WS2011: (&str, &str) = ("xmlns", "http://schemas.microsoft.com/SMI/2011/WindowsSettings");
|
||||
static WS2013: (&str, &str) = ("xmlns", "http://schemas.microsoft.com/SMI/2013/WindowsSettings");
|
||||
static WS2016: (&str, &str) = ("xmlns", "http://schemas.microsoft.com/SMI/2016/WindowsSettings");
|
||||
static WS2017: (&str, &str) = ("xmlns", "http://schemas.microsoft.com/SMI/2017/WindowsSettings");
|
||||
static WS2019: (&str, &str) = ("xmlns", "http://schemas.microsoft.com/SMI/2019/WindowsSettings");
|
||||
static WS2020: (&str, &str) = ("xmlns", "http://schemas.microsoft.com/SMI/2020/WindowsSettings");
|
||||
|
||||
#[derive(Debug)]
|
||||
struct WindowsSettings {
|
||||
active_code_page: ActiveCodePage,
|
||||
disable_window_filtering: bool,
|
||||
dpi_awareness: DpiAwareness,
|
||||
gdi_scaling: bool,
|
||||
heap_type: HeapType,
|
||||
long_path_aware: bool,
|
||||
printer_driver_isolation: bool,
|
||||
scrolling_awareness: ScrollingAwareness,
|
||||
}
|
||||
|
||||
impl WindowsSettings {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
active_code_page: ActiveCodePage::Utf8,
|
||||
disable_window_filtering: false,
|
||||
dpi_awareness: DpiAwareness::PerMonitorV2Only,
|
||||
gdi_scaling: false,
|
||||
heap_type: HeapType::LowFragmentationHeap,
|
||||
long_path_aware: true,
|
||||
printer_driver_isolation: true,
|
||||
scrolling_awareness: ScrollingAwareness::UltraHighResolution,
|
||||
}
|
||||
}
|
||||
|
||||
fn empty() -> Self {
|
||||
Self {
|
||||
active_code_page: ActiveCodePage::System,
|
||||
disable_window_filtering: false,
|
||||
dpi_awareness: DpiAwareness::UnawareByDefault,
|
||||
gdi_scaling: false,
|
||||
heap_type: HeapType::LowFragmentationHeap,
|
||||
long_path_aware: false,
|
||||
printer_driver_isolation: false,
|
||||
scrolling_awareness: ScrollingAwareness::UltraHighResolution,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_empty(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
Self {
|
||||
active_code_page: ActiveCodePage::System,
|
||||
disable_window_filtering: false,
|
||||
dpi_awareness: DpiAwareness::UnawareByDefault,
|
||||
gdi_scaling: false,
|
||||
heap_type: HeapType::LowFragmentationHeap,
|
||||
long_path_aware: false,
|
||||
printer_driver_isolation: false,
|
||||
scrolling_awareness: ScrollingAwareness::UltraHighResolution,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fn xml_to(&self, w: &mut XmlFormatter) -> fmt::Result {
|
||||
w.element("asmv3:application", &[], |w| {
|
||||
w.element("asmv3:windowsSettings", &[], |w| {
|
||||
self.active_code_page.xml_to(w)?;
|
||||
if self.disable_window_filtering {
|
||||
w.element("disableWindowFiltering", &[WS2011], |w| w.text("true"))?;
|
||||
}
|
||||
self.dpi_awareness.xml_to(w)?;
|
||||
if self.gdi_scaling {
|
||||
w.element("gdiScaling", &[WS2017], |w| w.text("true"))?;
|
||||
}
|
||||
if matches!(self.heap_type, HeapType::SegmentHeap) {
|
||||
w.element("heapType", &[WS2020], |w| w.text("SegmentHeap"))?;
|
||||
}
|
||||
if self.long_path_aware {
|
||||
w.element("longPathAware", &[WS2016], |w| w.text("true"))?;
|
||||
}
|
||||
if self.printer_driver_isolation {
|
||||
w.element("printerDriverIsolation", &[WS2011], |w| w.text("true"))?;
|
||||
}
|
||||
self.scrolling_awareness.xml_to(w)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Configure whether a Windows setting is enabled or disabled, avoiding confusion
|
||||
/// over which of these options `true` and `false` represent.
|
||||
#[derive(Debug)]
|
||||
pub enum Setting {
|
||||
Disabled = 0,
|
||||
Enabled = 1,
|
||||
}
|
||||
|
||||
impl Setting {
|
||||
/// Returns `true` if the setting should be disabled.
|
||||
fn disabled(&self) -> bool {
|
||||
matches!(self, Setting::Disabled)
|
||||
}
|
||||
|
||||
/// Returns `true` if the setting should be enabled.
|
||||
fn enabled(&self) -> bool {
|
||||
matches!(self, Setting::Enabled)
|
||||
}
|
||||
}
|
||||
|
||||
/// The code page used by single-byte APIs in the program.
|
||||
#[derive(Debug)]
|
||||
#[non_exhaustive]
|
||||
pub enum ActiveCodePage {
|
||||
/// Use the code page from the configured system locale, or UTF-8 if “Use Unicode UTF-8
|
||||
/// for worldwide language support” is configured.
|
||||
System,
|
||||
/// Use UTF-8 from Windows 10 version 1903 and on Windows 11.
|
||||
Utf8,
|
||||
/// Use the code page from the configured system locale, even when “Use Unicode UTF-8
|
||||
/// for worldwide language support” is configured.
|
||||
Legacy,
|
||||
/// Use the code page from the configured system locale on Windows 10, or from this
|
||||
/// locale on Windows 11.
|
||||
Locale(String),
|
||||
}
|
||||
|
||||
impl ActiveCodePage {
|
||||
pub fn as_str(&self) -> &str {
|
||||
match self {
|
||||
Self::System => "",
|
||||
Self::Utf8 => "UTF-8",
|
||||
Self::Legacy => "Legacy",
|
||||
Self::Locale(s) => s,
|
||||
}
|
||||
}
|
||||
|
||||
fn xml_to(&self, w: &mut XmlFormatter) -> fmt::Result {
|
||||
match self {
|
||||
Self::System => Ok(()),
|
||||
_ => w.element("activeCodePage", &[WS2019], |w| w.text(self.as_str())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ActiveCodePage {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
f.pad(self.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
/// Options for how Windows will handle drawing on monitors when the graphics
|
||||
/// need scaling to display at the correct size.
|
||||
///
|
||||
/// See [High DPI Desktop Application Development on Windows][dpi] for more details
|
||||
/// about the impact of these options.
|
||||
///
|
||||
/// [dpi]: https://docs.microsoft.com/en-us/windows/win32/hidpi/high-dpi-desktop-application-development-on-windows
|
||||
#[derive(Debug)]
|
||||
#[non_exhaustive]
|
||||
pub enum DpiAwareness {
|
||||
/// DPI awareness is not configured, so Windows will scale the application unless
|
||||
/// changed via the `SetProcessDpiAware` or `SetProcessDpiAwareness` API.
|
||||
UnawareByDefault,
|
||||
/// DPI awareness is not configured, with Windows 8.1, 10 and 11 not able
|
||||
/// to change the setting via API.
|
||||
Unaware,
|
||||
/// The program uses the system DPI, or the DPI of the monitor they start on if
|
||||
/// “Fix scaling for apps” is enabled. If the DPI does not match the current
|
||||
/// monitor then Windows will scale the application.
|
||||
System,
|
||||
/// The program uses the system DPI on Windows Vista, 7 and 8, and version 1 of
|
||||
/// per-monitor DPI awareness on Windows 8.1, 10 and 11. Using version 1 of the
|
||||
/// API is not recommended.
|
||||
PerMonitor,
|
||||
/// The program uses the system DPI on Windows Vista, 7 and 8, version 1 of
|
||||
/// per-monitor DPI awareness on Windows 8.1 and Windows 10 version 1507 and 1511,
|
||||
/// and version 2 of per-monitor DPI awareness from Windows 10 version 1607.
|
||||
PerMonitorV2,
|
||||
/// The program uses the system DPI on Windows Vista, 7, 8, 8.1 and Windows 10
|
||||
/// version 1507 and 1511, and version 2 of per-monitor DPI awareness from
|
||||
/// Windows 10 version 1607.
|
||||
PerMonitorV2Only,
|
||||
}
|
||||
|
||||
impl DpiAwareness {
|
||||
fn xml_to(&self, w: &mut XmlFormatter) -> fmt::Result {
|
||||
let settings = match self {
|
||||
Self::UnawareByDefault => (None, None),
|
||||
Self::Unaware => (Some("false"), None),
|
||||
Self::System => (Some("true"), None),
|
||||
Self::PerMonitor => (Some("true/pm"), None),
|
||||
Self::PerMonitorV2 => (Some("true/pm"), Some("permonitorv2,permonitor")),
|
||||
Self::PerMonitorV2Only => (None, Some("permonitorv2")),
|
||||
};
|
||||
if let Some(dpi_aware) = settings.0 {
|
||||
w.element("dpiAware", &[WS2005], |w| w.text(dpi_aware))?;
|
||||
}
|
||||
if let Some(dpi_awareness) = settings.1 {
|
||||
w.element("dpiAwareness", &[WS2016], |w| w.text(dpi_awareness))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// The heap type for the default memory allocator.
|
||||
#[derive(Debug)]
|
||||
#[non_exhaustive]
|
||||
pub enum HeapType {
|
||||
/// The default heap type since Windows Vista.
|
||||
LowFragmentationHeap,
|
||||
/// The modern segment heap, which may reduce total memory allocation in some programs.
|
||||
/// This is supported since Windows 10 version 2004. See
|
||||
/// [Improving Memory Usage in Microsoft Edge][edge].
|
||||
///
|
||||
/// [edge]: https://blogs.windows.com/msedgedev/2020/06/17/improving-memory-usage/
|
||||
SegmentHeap,
|
||||
}
|
||||
|
||||
/// Whether the application supports scroll wheel events with a minimum delta
|
||||
/// of 1, 40 or 120.
|
||||
#[derive(Debug)]
|
||||
#[non_exhaustive]
|
||||
pub enum ScrollingAwareness {
|
||||
/// The application can only handle scroll wheel events with the original delta of 120.
|
||||
LowResolution,
|
||||
/// The application can handle high precision scroll wheel events with a delta of 40.
|
||||
HighResolution,
|
||||
/// The application can handle ultra high precision scroll wheel events with a delta as low as 1.
|
||||
UltraHighResolution,
|
||||
}
|
||||
|
||||
impl ScrollingAwareness {
|
||||
fn xml_to(&self, w: &mut XmlFormatter) -> fmt::Result {
|
||||
match self {
|
||||
Self::LowResolution => w.element("ultraHighResolutionScrollingAware", &[WS2013], |w| w.text("false")),
|
||||
Self::HighResolution => w.element("highResolutionScrollingAware", &[WS2013], |w| w.text("true")),
|
||||
Self::UltraHighResolution => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct RequestedExecutionLevel {
|
||||
level: ExecutionLevel,
|
||||
ui_access: bool,
|
||||
}
|
||||
|
||||
impl RequestedExecutionLevel {
|
||||
fn xml_to(&self, w: &mut XmlFormatter) -> fmt::Result {
|
||||
w.element("asmv3:trustInfo", &[], |w| {
|
||||
w.element("asmv3:security", &[], |w| {
|
||||
w.element("asmv3:requestedPrivileges", &[], |w| {
|
||||
w.empty_element(
|
||||
"asmv3:requestedExecutionLevel",
|
||||
&[
|
||||
("level", self.level.as_str()),
|
||||
("uiAccess", if self.ui_access { "true" } else { "false" }),
|
||||
],
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// The requested execution level for the application when started.
|
||||
///
|
||||
/// The behaviour of each option is described in
|
||||
/// [Designing UAC Applications for Windows Vista Step 6: Create and Embed an Application Manifest][step6].
|
||||
///
|
||||
/// [step6]: https://msdn.microsoft.com/en-us/library/bb756929.aspx
|
||||
#[derive(Debug)]
|
||||
pub enum ExecutionLevel {
|
||||
/// The application will always run with the same authorities as the program invoking it.
|
||||
AsInvoker,
|
||||
/// The program will run without special authorities for a standard user, but will try to
|
||||
/// run with administrator authority if the user is an administrator. This is rarely used.
|
||||
HighestAvailable,
|
||||
/// The application will run as an administrator, prompting for elevation if necessary.
|
||||
RequireAdministrator,
|
||||
}
|
||||
|
||||
impl ExecutionLevel {
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
Self::AsInvoker => "asInvoker",
|
||||
Self::HighestAvailable => "highestAvailable",
|
||||
Self::RequireAdministrator => "requireAdministrator",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ExecutionLevel {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
f.pad(self.as_str())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,117 @@
|
|||
use super::{ExecutionLevel, SupportedOS};
|
||||
use crate::{empty_manifest, new_manifest};
|
||||
|
||||
fn enc(s: &str) -> String {
|
||||
let mut buf = String::with_capacity(1024);
|
||||
buf.push('\u{feff}');
|
||||
for l in s.lines() {
|
||||
buf.push_str(l);
|
||||
buf.push_str("\r\n");
|
||||
}
|
||||
buf.truncate(buf.len() - 2);
|
||||
buf
|
||||
}
|
||||
|
||||
fn encp(s: &str) -> String {
|
||||
s.replace("\n", "\r\n")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_manifest_canonical() {
|
||||
let builder = empty_manifest();
|
||||
assert_eq!(
|
||||
format!("{}", builder),
|
||||
enc(r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"></assembly>"#)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_manifest_pretty() {
|
||||
let builder = empty_manifest();
|
||||
assert_eq!(
|
||||
format!("{:#}", builder),
|
||||
encp(
|
||||
r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"/>"#
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn only_execution_level() {
|
||||
let builder = empty_manifest().requested_execution_level(ExecutionLevel::AsInvoker);
|
||||
assert_eq!(
|
||||
format!("{:#}", builder),
|
||||
encp(
|
||||
r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3" manifestVersion="1.0">
|
||||
<asmv3:trustInfo>
|
||||
<asmv3:security>
|
||||
<asmv3:requestedPrivileges>
|
||||
<asmv3:requestedExecutionLevel level="asInvoker" uiAccess="false"/>
|
||||
</asmv3:requestedPrivileges>
|
||||
</asmv3:security>
|
||||
</asmv3:trustInfo>
|
||||
</assembly>"#
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn only_windows10() {
|
||||
let builder = empty_manifest().supported_os(SupportedOS::Windows10..);
|
||||
assert_eq!(
|
||||
format!("{:#}", builder),
|
||||
encp(
|
||||
r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
|
||||
</application>
|
||||
</compatibility>
|
||||
</assembly>"#
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_comctl32_6() {
|
||||
let builder = new_manifest("Company.OrgUnit.Program")
|
||||
.version(1, 0, 0, 2)
|
||||
.remove_dependency("Microsoft.Windows.Common-Controls");
|
||||
assert_eq!(
|
||||
format!("{:#}", builder),
|
||||
encp(
|
||||
r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3" manifestVersion="1.0">
|
||||
<assemblyIdentity name="Company.OrgUnit.Program" type="win32" version="1.0.0.2"/>
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||
<maxversiontested Id="10.0.18362.1"/>
|
||||
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
|
||||
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
|
||||
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
|
||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
|
||||
</application>
|
||||
</compatibility>
|
||||
<asmv3:application>
|
||||
<asmv3:windowsSettings>
|
||||
<activeCodePage xmlns="http://schemas.microsoft.com/SMI/2019/WindowsSettings">UTF-8</activeCodePage>
|
||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">permonitorv2</dpiAwareness>
|
||||
<longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
|
||||
<printerDriverIsolation xmlns="http://schemas.microsoft.com/SMI/2011/WindowsSettings">true</printerDriverIsolation>
|
||||
</asmv3:windowsSettings>
|
||||
</asmv3:application>
|
||||
<asmv3:trustInfo>
|
||||
<asmv3:security>
|
||||
<asmv3:requestedPrivileges>
|
||||
<asmv3:requestedExecutionLevel level="asInvoker" uiAccess="false"/>
|
||||
</asmv3:requestedPrivileges>
|
||||
</asmv3:security>
|
||||
</asmv3:trustInfo>
|
||||
</assembly>"#
|
||||
)
|
||||
);
|
||||
}
|
|
@ -0,0 +1,140 @@
|
|||
use std::fmt::{self, Display, Formatter, Write};
|
||||
|
||||
/// Simple but still over-engineered XML generator for a [`Formatter`], for generating
|
||||
/// Windows Manifest XML. This can easily generate invalid XML.
|
||||
///
|
||||
/// When used correctly, this should generate the same output as MT’s `-canonicalize`
|
||||
/// option.
|
||||
pub struct XmlFormatter<'a, 'f> {
|
||||
f: &'f mut Formatter<'a>,
|
||||
state: State,
|
||||
depth: usize,
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq)]
|
||||
enum State {
|
||||
Init,
|
||||
StartTag,
|
||||
Text,
|
||||
}
|
||||
|
||||
impl<'a, 'f> XmlFormatter<'a, 'f> {
|
||||
pub fn new(f: &'f mut Formatter<'a>) -> Self {
|
||||
Self {
|
||||
f,
|
||||
state: State::Init,
|
||||
depth: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn pretty(&mut self) -> fmt::Result {
|
||||
if self.f.alternate() {
|
||||
self.f.write_str("\r\n")?;
|
||||
for _ in 0..self.depth {
|
||||
self.f.write_str(" ")?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn start_document(&mut self) -> fmt::Result {
|
||||
if !self.f.alternate() {
|
||||
self.f.write_char('\u{FEFF}')?;
|
||||
}
|
||||
self.f
|
||||
.write_str("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\r\n")
|
||||
}
|
||||
|
||||
pub fn element<F: FnOnce(&mut Self) -> fmt::Result>(&mut self, name: &str, attrs: &[(&str, &str)], f: F) -> fmt::Result {
|
||||
self.start_element(name, attrs)?;
|
||||
f(self)?;
|
||||
self.end_element(name)
|
||||
}
|
||||
|
||||
pub fn empty_element(&mut self, name: &str, attrs: &[(&str, &str)]) -> fmt::Result {
|
||||
self.start_element(name, attrs)?;
|
||||
self.end_element(name)
|
||||
}
|
||||
|
||||
pub fn start_element(&mut self, name: &str, attrs: &[(&str, &str)]) -> fmt::Result {
|
||||
if self.state == State::StartTag {
|
||||
self.f.write_char('>')?;
|
||||
}
|
||||
if self.depth != 0 {
|
||||
self.pretty()?;
|
||||
}
|
||||
write!(self.f, "<{}", name)?;
|
||||
for (name, value) in attrs {
|
||||
write!(self.f, " {}=\"{}\"", name, Xml(value))?;
|
||||
}
|
||||
self.depth += 1;
|
||||
self.state = State::StartTag;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn end_element(&mut self, name: &str) -> fmt::Result {
|
||||
self.depth -= 1;
|
||||
match self.state {
|
||||
State::Init => {
|
||||
self.pretty()?;
|
||||
write!(self.f, "</{}>", name)
|
||||
}
|
||||
State::Text => {
|
||||
self.state = State::Init;
|
||||
write!(self.f, "</{}>", name)
|
||||
}
|
||||
State::StartTag => {
|
||||
self.state = State::Init;
|
||||
if self.f.alternate() {
|
||||
self.f.write_str("/>")
|
||||
} else {
|
||||
write!(self.f, "></{}>", name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn text(&mut self, s: &str) -> fmt::Result {
|
||||
if self.state == State::StartTag {
|
||||
self.state = State::Text;
|
||||
self.f.write_char('>')?;
|
||||
}
|
||||
Xml(s).fmt(self.f)
|
||||
}
|
||||
}
|
||||
|
||||
/// Temporary wrapper for outputting a string with XML attribute encoding.
|
||||
/// This does not do anything with the control characters which are not
|
||||
/// valid in XML, encoded or not.
|
||||
struct Xml<'a>(&'a str);
|
||||
|
||||
impl<'a> Display for Xml<'a> {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
// Process the string in blocks separated by special characters, so that the parts that
|
||||
// don't need encoding can be written all at once, not character by character, and with
|
||||
// no checks for whether string slices are aligned on character boundaries.
|
||||
for s in self.0.split_inclusive(&['<', '&', '>', '"', '\r'][..]) {
|
||||
// Check whether the last character in the substring needs encoding. This will be
|
||||
// `None` at the end of the input string.
|
||||
let mut iter = s.chars();
|
||||
let ch = match iter.next_back() {
|
||||
Some('<') => Some("<"),
|
||||
Some('&') => Some("&"),
|
||||
Some('>') => Some(">"),
|
||||
Some('"') => Some("""),
|
||||
Some('\r') => Some(" "),
|
||||
_ => None,
|
||||
};
|
||||
// Write the substring except the last character, then the encoded character;
|
||||
// or the entire substring if it is not terminated by a special character.
|
||||
match ch {
|
||||
Some(enc) => {
|
||||
f.write_str(iter.as_str())?;
|
||||
f.write_str(enc)?;
|
||||
}
|
||||
None => f.write_str(s)?,
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3" manifestVersion="1.0">
|
||||
<assemblyIdentity name="Sample.Test" type="win32" version="1.0.0.0"/>
|
||||
<asmv3:trustInfo>
|
||||
<asmv3:security>
|
||||
<asmv3:requestedPrivileges>
|
||||
<asmv3:requestedExecutionLevel level="asInvoker" uiAccess="false"/>
|
||||
</asmv3:requestedPrivileges>
|
||||
</asmv3:security>
|
||||
</asmv3:trustInfo>
|
||||
</assembly>
|
|
@ -0,0 +1,66 @@
|
|||
[package]
|
||||
name = "crashreporter"
|
||||
version = "1.0.0"
|
||||
edition = "2021"
|
||||
|
||||
[[bin]]
|
||||
name = "crashreporter"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
cfg-if = "1.0"
|
||||
env_logger = { version = "0.10", default-features = false }
|
||||
fluent = "0.16.0"
|
||||
intl-memoizer = "0.5"
|
||||
lazy_static = "1.4"
|
||||
libloading = "0.7"
|
||||
log = "0.4.17"
|
||||
mozbuild = "0.1"
|
||||
mozilla-central-workspace-hack = { version = "0.1", features = ["crashreporter"], optional = true }
|
||||
phf = "0.11"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
sha2 = "0.10"
|
||||
time = { version = "0.3", features = ["formatting", "serde"] }
|
||||
unic-langid = { version = "0.9.1" }
|
||||
uuid = { version = "1", features = ["v4", "serde"] }
|
||||
zip = { version = "0.6", default-features = false }
|
||||
|
||||
[target."cfg(target_os = \"macos\")".dependencies]
|
||||
block = "0.1"
|
||||
cocoa = { package = "cocoabind", path = "../cocoabind" }
|
||||
objc = "0.2"
|
||||
|
||||
[target."cfg(target_os = \"linux\")".dependencies]
|
||||
gtk = { package = "gtkbind", path = "../gtkbind" }
|
||||
|
||||
[target."cfg(target_os = \"windows\")".dependencies.windows-sys]
|
||||
version = "0.52"
|
||||
features = [
|
||||
"Win32_Foundation",
|
||||
"Win32_Globalization",
|
||||
"Win32_Graphics_Gdi",
|
||||
"Win32_System_Com",
|
||||
"Win32_System_LibraryLoader",
|
||||
"Win32_System_SystemServices",
|
||||
"Win32_System_Threading",
|
||||
"Win32_UI_Controls",
|
||||
"Win32_UI_Input_KeyboardAndMouse",
|
||||
"Win32_UI_Shell",
|
||||
"Win32_UI_WindowsAndMessaging"
|
||||
]
|
||||
|
||||
[features]
|
||||
# Required for tests
|
||||
mock = []
|
||||
|
||||
[build-dependencies]
|
||||
embed-manifest = "1.4"
|
||||
mozbuild = "0.1"
|
||||
phf_codegen = "0.11"
|
||||
yaml-rust = "0.4"
|
||||
|
||||
[dev-dependencies]
|
||||
bytes = "1.4"
|
||||
tokio = { version = "1.29", features = ["rt", "net", "time", "sync"] }
|
||||
warp = { version = "0.3", default-features = false }
|
|
@ -0,0 +1,15 @@
|
|||
# vim:set ts=8 sw=8 sts=8 noet:
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
include $(topsrcdir)/config/rules.mk
|
||||
|
||||
ifeq ($(OS_ARCH),Darwin)
|
||||
libs::
|
||||
$(NSINSTALL) -D $(DIST)/bin/crashreporter.app
|
||||
rsync --archive --cvs-exclude --exclude '*.in' $(srcdir)/macos_app_bundle/ $(DIST)/bin/crashreporter.app/Contents/
|
||||
$(call py_action,preprocessor crashreporter.app/Contents/Resources/English.lproj/InfoPlist.strings,-Fsubstitution --output-encoding utf-16 -DAPP_NAME='$(MOZ_APP_DISPLAYNAME)' $(srcdir)/macos_app_bundle/Resources/English.lproj/InfoPlist.strings.in -o $(DIST)/bin/crashreporter.app/Contents/Resources/English.lproj/InfoPlist.strings)
|
||||
$(NSINSTALL) -D $(DIST)/bin/crashreporter.app/Contents/MacOS
|
||||
$(NSINSTALL) $(DIST)/bin/crashreporter $(DIST)/bin/crashreporter.app/Contents/MacOS
|
||||
endif
|
|
@ -0,0 +1,89 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use std::{env, path::Path};
|
||||
|
||||
fn main() {
|
||||
windows_manifest();
|
||||
crash_ping_annotations();
|
||||
set_mock_cfg();
|
||||
}
|
||||
|
||||
fn windows_manifest() {
|
||||
use embed_manifest::{embed_manifest, manifest, new_manifest};
|
||||
|
||||
if std::env::var_os("CARGO_CFG_WINDOWS").is_none() {
|
||||
return;
|
||||
}
|
||||
|
||||
// See https://docs.rs/embed-manifest/1.4.0/embed_manifest/fn.new_manifest.html for what the
|
||||
// default manifest includes. The defaults include almost all of the settings used in the old
|
||||
// crash reporter.
|
||||
let manifest = new_manifest("CrashReporter")
|
||||
// Use legacy active code page because GDI doesn't support per-process UTF8 (and older
|
||||
// win10 may not support this setting anyway).
|
||||
.active_code_page(manifest::ActiveCodePage::Legacy)
|
||||
// GDI scaling is not enabled by default but we need it to make the GDI-drawn text look
|
||||
// nice on high-DPI displays.
|
||||
.gdi_scaling(manifest::Setting::Enabled);
|
||||
|
||||
embed_manifest(manifest).expect("unable to embed windows manifest file");
|
||||
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
}
|
||||
|
||||
/// Generate crash ping annotation information from the yaml definition file.
|
||||
fn crash_ping_annotations() {
|
||||
use std::fs::File;
|
||||
use std::io::{BufWriter, Write};
|
||||
use yaml_rust::{Yaml, YamlLoader};
|
||||
|
||||
let crash_annotations = Path::new("../../CrashAnnotations.yaml")
|
||||
.canonicalize()
|
||||
.unwrap();
|
||||
println!("cargo:rerun-if-changed={}", crash_annotations.display());
|
||||
|
||||
let crash_ping_file = Path::new(&env::var("OUT_DIR").unwrap()).join("ping_annotations.rs");
|
||||
|
||||
let yaml = std::fs::read_to_string(crash_annotations).unwrap();
|
||||
let Yaml::Hash(entries) = YamlLoader::load_from_str(&yaml)
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.next()
|
||||
.unwrap()
|
||||
else {
|
||||
panic!("unexpected crash annotations root type");
|
||||
};
|
||||
|
||||
let ping_annotations = entries.into_iter().filter_map(|(k, v)| {
|
||||
v["ping"]
|
||||
.as_bool()
|
||||
.unwrap_or(false)
|
||||
.then(|| k.into_string().unwrap())
|
||||
});
|
||||
|
||||
let mut phf_set = phf_codegen::Set::new();
|
||||
for annotation in ping_annotations {
|
||||
phf_set.entry(annotation);
|
||||
}
|
||||
|
||||
let mut file = BufWriter::new(File::create(&crash_ping_file).unwrap());
|
||||
writeln!(
|
||||
&mut file,
|
||||
"static PING_ANNOTATIONS: phf::Set<&'static str> = {};",
|
||||
phf_set.build()
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
/// Set the mock configuration option when tests are enabled or when the mock feature is enabled.
|
||||
fn set_mock_cfg() {
|
||||
// Very inconveniently, there's no way to detect `cfg(test)` from build scripts. See
|
||||
// https://github.com/rust-lang/cargo/issues/4789. This seems like an arbitrary and pointless
|
||||
// limitation, and only complicates the evaluation of mock behavior. Because of this, we have a
|
||||
// `mock` feature which is activated by `toolkit/library/rust/moz.build`.
|
||||
if env::var_os("CARGO_FEATURE_MOCK").is_some() || mozbuild::config::MOZ_CRASHREPORTER_MOCK {
|
||||
println!("cargo:rustc-cfg=mock");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>English</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>crashreporter</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>crashreporter</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>crashreporter.icns</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>org.mozilla.crashreporter</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>crashreporter</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.0</string>
|
||||
<key>LSHasLocalizedDisplayName</key>
|
||||
<true/>
|
||||
<key>NSRequiresAquaSystemAppearance</key>
|
||||
<false/>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string>NSApplication</string>
|
||||
<key>LSUIElement</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
|
@ -0,0 +1,2 @@
|
|||
APPL????
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/* Localized versions of Info.plist keys */
|
||||
|
||||
CFBundleName = "Crash Reporter";
|
||||
CFBundleDisplayName = "@APP_NAME@ Crash Reporter";
|
Двоичные данные
toolkit/crashreporter/client-rust/app/macos_app_bundle/Resources/crashreporter.icns
Normal file
Двоичные данные
toolkit/crashreporter/client-rust/app/macos_app_bundle/Resources/crashreporter.icns
Normal file
Двоичный файл не отображается.
|
@ -0,0 +1,7 @@
|
|||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
RUST_PROGRAMS = ["crashreporter"]
|
|
@ -0,0 +1,10 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
// Use the WINDOWS windows subsystem. This prevents a console window from opening with the
|
||||
// application.
|
||||
#![cfg_attr(windows, windows_subsystem = "windows")]
|
||||
fn main() {
|
||||
todo!()
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
[package]
|
||||
name = "cocoabind"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
block = "0.1"
|
||||
objc = "0.2"
|
||||
|
||||
[build-dependencies]
|
||||
bindgen = { version = "0.69", default-features = false, features = ["runtime"] }
|
||||
mozbuild = "0.1.0"
|
|
@ -0,0 +1,74 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use mozbuild::config::CC_BASE_FLAGS as CFLAGS;
|
||||
|
||||
const TYPES: &[&str] = &[
|
||||
"ActionCell",
|
||||
"Application",
|
||||
"Array",
|
||||
"AttributedString",
|
||||
"Box",
|
||||
"Button",
|
||||
"ButtonCell",
|
||||
"Cell",
|
||||
"ClassDescription",
|
||||
"Control",
|
||||
"DefaultRunLoopMode",
|
||||
"Dictionary",
|
||||
"ForegroundColorAttributeName",
|
||||
"LayoutDimension",
|
||||
"LayoutGuide",
|
||||
"LayoutXAxisAnchor",
|
||||
"LayoutYAxisAnchor",
|
||||
"MutableAttributedString",
|
||||
"MutableParagraphStyle",
|
||||
"MutableString",
|
||||
"ModalPanelRunLoopMode",
|
||||
"Panel",
|
||||
"ProcessInfo",
|
||||
"ProgressIndicator",
|
||||
"Proxy",
|
||||
"RunLoop",
|
||||
"ScrollView",
|
||||
"StackView",
|
||||
"String",
|
||||
"TextField",
|
||||
"TextView",
|
||||
"Value",
|
||||
"View",
|
||||
"Window",
|
||||
];
|
||||
|
||||
fn main() {
|
||||
let mut builder = bindgen::Builder::default()
|
||||
.header_contents(
|
||||
"cocoa_bindings.h",
|
||||
"#define self self_
|
||||
#import <Cocoa/Cocoa.h>
|
||||
",
|
||||
)
|
||||
.generate_block(true)
|
||||
.prepend_enum_name(false)
|
||||
.clang_args(CFLAGS)
|
||||
.clang_args(["-x", "objective-c"])
|
||||
.clang_arg("-fblocks")
|
||||
.derive_default(true)
|
||||
.allowlist_item("TransformProcessType");
|
||||
for name in TYPES {
|
||||
// (I|P) covers generated traits (interfaces and protocols). `(_.*)?` covers categories
|
||||
// (which are generated as `CLASS_CATEGORY`).
|
||||
builder = builder.allowlist_item(format!("(I|P)?NS{name}(_.*)?"));
|
||||
}
|
||||
let bindings = builder
|
||||
.generate()
|
||||
.expect("unable to generate cocoa bindings");
|
||||
let out_path = std::path::PathBuf::from(std::env::var("OUT_DIR").unwrap());
|
||||
bindings
|
||||
.write_to_file(out_path.join("cocoa_bindings.rs"))
|
||||
.expect("failed to write cocoa bindings");
|
||||
println!("cargo:rustc-link-lib=framework=AppKit");
|
||||
println!("cargo:rustc-link-lib=framework=Cocoa");
|
||||
println!("cargo:rustc-link-lib=framework=Foundation");
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#![allow(non_upper_case_globals)]
|
||||
#![allow(non_camel_case_types)]
|
||||
#![allow(non_snake_case)]
|
||||
#![allow(unused_imports)]
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/cocoa_bindings.rs"));
|
|
@ -0,0 +1,8 @@
|
|||
[package]
|
||||
name = "gtkbind"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[build-dependencies]
|
||||
bindgen = { version = "0.69.0", default-features = false, features = ["runtime"] }
|
||||
mozbuild = "0.1.0"
|
|
@ -0,0 +1,30 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use mozbuild::config::{
|
||||
CC_BASE_FLAGS as CFLAGS, MOZ_GTK3_CFLAGS as GTK_CFLAGS, MOZ_GTK3_LIBS as GTK_LIBS,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
let bindings = bindgen::Builder::default()
|
||||
.header_contents("gtk_bindings.h", r#"#include "gtk/gtk.h""#)
|
||||
.clang_args(CFLAGS)
|
||||
.clang_args(GTK_CFLAGS)
|
||||
.allowlist_function("gtk_.*")
|
||||
.allowlist_function("g_(application|main_context|object|signal|timeout)_.*")
|
||||
.derive_default(true)
|
||||
.generate()
|
||||
.expect("unable to generate gtk bindings");
|
||||
for flag in GTK_LIBS {
|
||||
if let Some(lib) = flag.strip_prefix("-l") {
|
||||
println!("cargo:rustc-link-lib={lib}");
|
||||
} else if let Some(path) = flag.strip_prefix("-L") {
|
||||
println!("cargo:rustc-link-search={path}");
|
||||
}
|
||||
}
|
||||
let out_path = std::path::PathBuf::from(std::env::var("OUT_DIR").unwrap());
|
||||
bindings
|
||||
.write_to_file(out_path.join("gtk_bindings.rs"))
|
||||
.expect("failed to write gtk bindings");
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#![allow(non_upper_case_globals)]
|
||||
#![allow(non_camel_case_types)]
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/gtk_bindings.rs"));
|
|
@ -67,10 +67,12 @@ if CONFIG["MOZ_CRASHREPORTER"]:
|
|||
DIRS += ["rust_minidump_writer_linux"]
|
||||
|
||||
if CONFIG["OS_TARGET"] != "Android":
|
||||
DIRS += ["minidump-analyzer"]
|
||||
DIRS += [
|
||||
"client-rust/app",
|
||||
"minidump-analyzer",
|
||||
]
|
||||
|
||||
DIRS += [
|
||||
"client",
|
||||
"mozannotation_client",
|
||||
"mozannotation_server",
|
||||
]
|
||||
|
|
|
@ -21,6 +21,7 @@ for feature in gkrust_features:
|
|||
# Target directory doesn't matter a lot here, since we can't share panic=abort
|
||||
# compilation artifacts with gkrust.
|
||||
RUST_TESTS = [
|
||||
"crashreporter",
|
||||
"firefox-on-glean",
|
||||
"l10nregistry",
|
||||
"selectors",
|
||||
|
@ -31,6 +32,8 @@ RUST_TESTS = [
|
|||
"gkrust",
|
||||
]
|
||||
|
||||
RUST_TEST_FEATURES.append("crashreporter/mock")
|
||||
|
||||
# Code coverage builds link a bunch of Gecko bindings code from the style
|
||||
# crate, which is not used by our tests but would cause link errors.
|
||||
#
|
||||
|
|
|
@ -3054,6 +3054,10 @@ def oxidized_breakpad(target):
|
|||
set_config("MOZ_OXIDIZED_BREAKPAD", True, when=oxidized_breakpad)
|
||||
set_define("MOZ_OXIDIZED_BREAKPAD", True, when=oxidized_breakpad)
|
||||
|
||||
# Environment variable to mock the crashreporter for testing
|
||||
option(env="MOZ_CRASHREPORTER_MOCK", help="Mock the crashreporter to test native GUIs")
|
||||
set_config("MOZ_CRASHREPORTER_MOCK", True, when="MOZ_CRASHREPORTER_MOCK")
|
||||
|
||||
|
||||
# Wine
|
||||
# ==============================================================
|
||||
|
|
Загрузка…
Ссылка в новой задаче