├── .github ├── CODEOWNERS └── workflows │ └── rust-ci.yml ├── .earthlyignore ├── .gitignore ├── tests ├── xwin-test │ ├── src │ │ └── main.rs │ └── Cargo.toml ├── snapshots │ ├── xwin-unpack.snap │ ├── xwin-download.snap │ ├── xwin-list.snap │ ├── xwin-splat.snap │ ├── xwin-minimize.snap │ ├── xwin.snap │ └── compiles__verify_compiles_minimized.snap ├── deterministic.rs └── compiles.rs ├── Earthfile ├── release.toml ├── LICENSE-MIT ├── deny.toml ├── Cargo.toml ├── .cargo └── config.toml ├── CODE_OF_CONDUCT.md ├── src ├── util.rs ├── download.rs ├── manifest.rs ├── ctx.rs ├── minimize.rs └── unpack.rs ├── CONTRIBUTING.md ├── xwin.dockerfile ├── docs └── example-map.toml ├── LICENSE-APACHE ├── README.md └── CHANGELOG.md /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @Jake-Shadle -------------------------------------------------------------------------------- /.earthlyignore: -------------------------------------------------------------------------------- 1 | **/* 2 | !xwin.dockerfile 3 | !Earthfile 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | .xwin-cache 4 | tests/xwin-test/target 5 | tests/xwin-test/Cargo.lock 6 | *.snapshot.new 7 | -------------------------------------------------------------------------------- /tests/xwin-test/src/main.rs: -------------------------------------------------------------------------------- 1 | #[link(name = "KERNEL32")] 2 | extern "system" { 3 | #[link_name = "CloseHandle"] 4 | pub fn close_handle(handle: u32) -> i32; 5 | } 6 | 7 | fn main() {} 8 | -------------------------------------------------------------------------------- /Earthfile: -------------------------------------------------------------------------------- 1 | VERSION 0.8 2 | 3 | ctx: 4 | FROM busybox 5 | RUN mkdir -p /tmp/docker-build 6 | SAVE ARTIFACT /tmp/docker-build 7 | 8 | xwin: 9 | FROM DOCKERFILE -f xwin.dockerfile +ctx/docker-build 10 | SAVE IMAGE xwin:latest 11 | -------------------------------------------------------------------------------- /tests/snapshots/xwin-unpack.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/main.rs 3 | expression: help_text 4 | --- 5 | Unpacks all of the downloaded packages to disk 6 | 7 | Usage: unpack 8 | 9 | Options: 10 | -h, --help 11 | Print help 12 | 13 | -V, --version 14 | Print version 15 | 16 | -------------------------------------------------------------------------------- /tests/snapshots/xwin-download.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/main.rs 3 | expression: help_text 4 | --- 5 | Downloads all the selected packages that aren't already present in the download 6 | cache 7 | 8 | Usage: download 9 | 10 | Options: 11 | -h, --help 12 | Print help 13 | 14 | -V, --version 15 | Print version 16 | 17 | -------------------------------------------------------------------------------- /tests/snapshots/xwin-list.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/main.rs 3 | expression: help_text 4 | --- 5 | Displays a summary of the packages that would be downloaded. 6 | 7 | Note that this is not a full list as the SDK uses MSI files for many packages, 8 | so they would need to be downloaded and inspected to determine which CAB files 9 | must also be downloaded to get the content needed. 10 | 11 | Usage: list 12 | 13 | Options: 14 | -h, --help 15 | Print help (see a summary with '-h') 16 | 17 | -V, --version 18 | Print version 19 | 20 | -------------------------------------------------------------------------------- /release.toml: -------------------------------------------------------------------------------- 1 | pre-release-commit-message = "Release {{crate_name}} {{version}}" 2 | tag-message = "Release {{crate_name}} {{version}}" 3 | tag-name = "{{version}}" 4 | pre-release-replacements = [ 5 | { file = "CHANGELOG.md", search = "Unreleased", replace = "{{version}}" }, 6 | { file = "CHANGELOG.md", search = "\\.\\.\\.HEAD", replace = "...{{tag_name}}" }, 7 | { file = "CHANGELOG.md", search = "ReleaseDate", replace = "{{date}}" }, 8 | { file = "CHANGELOG.md", search = "", replace = "\n## [Unreleased] - ReleaseDate" }, 9 | { file = "CHANGELOG.md", search = "", replace = "\n[Unreleased]: https://github.com/Jake-Shadle/xwin/compare/{{tag_name}}...HEAD" }, 10 | { file = "xwin.dockerfile", search = "xwin_version=\"\\d+\\.\\d+\\.\\d+\";", replace = "xwin_version=\"{{version}}\";" }, 11 | ] 12 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021 Jake Shadle 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /deny.toml: -------------------------------------------------------------------------------- 1 | [graph] 2 | targets = [ 3 | "x86_64-unknown-linux-musl", 4 | "x86_64-unknown-linux-gnu", 5 | "x86_64-apple-darwin", 6 | "aarch64-apple-darwin", 7 | ] 8 | all-features = true 9 | 10 | [advisories] 11 | ignore = [] 12 | 13 | [licenses] 14 | allow = ["MIT", "Apache-2.0", "BSD-3-Clause", "ISC"] 15 | exceptions = [ 16 | # ring uses code from multiple libraries but all with permissive licenses 17 | # https://tldrlegal.com/license/openssl-license-(openssl) 18 | { allow = ["Unicode-3.0"], name = "unicode-ident" }, 19 | { allow = ["CDLA-Permissive-2.0"], name = "webpki-roots" }, 20 | { allow = ["CDLA-Permissive-2.0"], name = "webpki-root-certs" }, 21 | ] 22 | 23 | [bans] 24 | multiple-versions = "deny" 25 | deny = [] 26 | skip = [ 27 | { crate = "regex-syntax@0.6.29", reason = "tracing-subscriber depends on 2 different versions :(" }, 28 | { crate = "regex-automata@0.1.10", reason = "tracing-subscriber depends on 2 different versions :(" }, 29 | { crate = "getrandom@0.2.16", reason = "ring use this old version, lol" }, 30 | ] 31 | skip-tree = [] 32 | 33 | [sources] 34 | unknown-registry = "deny" 35 | # Lint level for what to happen when a crate from a git repository that is not 36 | # in the allow list is encountered 37 | unknown-git = "deny" 38 | allow-git = [] 39 | -------------------------------------------------------------------------------- /tests/snapshots/xwin-splat.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/main.rs 3 | expression: help_text 4 | --- 5 | Fixes the packages to prune unneeded files and adds symlinks to address file 6 | casing issues and then spalts the final artifacts into directories 7 | 8 | Usage: splat [OPTIONS] 9 | 10 | Options: 11 | --include-debug-libs 12 | The MSVCRT includes (non-redistributable) debug versions of the 13 | various libs that are generally uninteresting to keep for most usage 14 | 15 | --include-debug-symbols 16 | The MSVCRT includes PDB (debug symbols) files for several of the 17 | libraries that are generally uninteresting to keep for most usage 18 | 19 | --disable-symlinks 20 | By default, symlinks are added to both the CRT and `WindowsSDK` to 21 | address casing issues in general usage. 22 | 23 | For example, if you are compiling C/C++ code that does `#include 24 | `, it will break on a case-sensitive file system, as the 25 | actual path in the `WindowsSDK` is `Windows.h`. This also applies even 26 | if the C/C++ you are compiling uses correct casing for all CRT/SDK 27 | includes, as the internal headers also use incorrect casing in most 28 | cases. 29 | 30 | --preserve-ms-arch-notation 31 | By default, we convert the MS specific `x64`, `arm`, and `arm64` 32 | target architectures to the more canonical `x86_64`, `aarch`, and 33 | `aarch64` of LLVM etc when creating directories/names. 34 | 35 | Passing this flag will preserve the MS names for those targets. 36 | 37 | --use-winsysroot-style 38 | Use the /winsysroot layout, so that clang-cl's /winsysroot flag can be 39 | used with the output, rather than needing both -vctoolsdir and 40 | -winsdkdir. You will likely also want to use 41 | --preserve-ms-arch-notation and --disable-symlinks for use with 42 | clang-cl on Windows 43 | 44 | --output 45 | The root output directory. Defaults to `./.xwin-cache/splat` if not 46 | specified 47 | 48 | --map 49 | If specified, a toml file that can be used to create additional 50 | symlinks or skip files entirely 51 | 52 | --copy 53 | Copies files from the unpack directory to the splat directory instead 54 | of moving them, which preserves the original unpack directories but 55 | increases overall time and disk usage 56 | 57 | -h, --help 58 | Print help (see a summary with '-h') 59 | 60 | -V, --version 61 | Print version 62 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "xwin" 3 | version = "0.6.7" 4 | description = "Allows downloading and repacking the MSVC CRT and Windows SDK for cross compilation" 5 | authors = ["Jake Shadle "] 6 | edition = "2024" 7 | license = "Apache-2.0 OR MIT" 8 | repository = "https://github.com/Jake-Shadle/xwin" 9 | homepage = "https://github.com/Jake-Shadle/xwin" 10 | categories = ["development-tools", "command-line-utilities"] 11 | keywords = ["windows", "cross-compilation"] 12 | exclude = [ 13 | ".cargo", 14 | ".github", 15 | "docs", 16 | "tests/**", 17 | "deny.toml", 18 | "release.toml", 19 | "xwin.dockerfile", 20 | ] 21 | 22 | [features] 23 | # By default we use rustls for TLS 24 | default = ["rustls-tls"] 25 | rustls-tls = ["ureq/rustls"] 26 | # If this feature is enabled we instead use the native TLS implementation for the 27 | # target platform 28 | native-tls = ["ureq/native-tls"] 29 | 30 | [dependencies] 31 | # Easy errors 32 | anyhow = "1.0" 33 | # Network/file buffers 34 | bytes = "1.10" 35 | # CAB files are used in conjunction with MSI files for SDK packages 36 | cab = "0.6" 37 | # Nicer to use utf-8 paths 38 | camino = "1.0" 39 | # Easy CLI tables 40 | cli-table = { version = "0.5", default-features = false } 41 | crossbeam-channel = "0.5" 42 | # Pretty progress bars 43 | indicatif = "0.17" 44 | # Decoding of MSI installer packages 45 | msi = "0.8" 46 | parking_lot = "0.12" 47 | # brrr 48 | rayon = "1.5" 49 | # Include scanning 50 | regex = "1.11" 51 | # HTTP requests 52 | ureq = { version = "3.0.0", default-features = false, features = ["gzip", "socks-proxy"] } 53 | memchr = "2.6" 54 | native-tls-crate = { package = "native-tls", version = "0.2", optional = true } 55 | # SHA-256 verification 56 | sha2 = "0.10" 57 | # Deserialization 58 | serde = { version = "1.0", features = ["derive"] } 59 | # JSON deserialization 60 | serde_json = "1.0" 61 | # Argument parsing 62 | clap = { version = "4.5", features = ["derive", "env", "wrap_help"] } 63 | # Easy management of temp files 64 | tempfile = "3.19" 65 | toml = "0.9" 66 | # Tracing logs 67 | tracing = { version = "0.1", default-features = false, features = [ 68 | "attributes", 69 | "std", 70 | ] } 71 | # Emission of logs to stdout 72 | tracing-subscriber = { version = "0.3", default-features = false, features = [ 73 | "env-filter", 74 | "fmt", 75 | "json", 76 | ] } 77 | # Hashing 78 | twox-hash = "2.1" 79 | # Determine the latest CRT and SDK versions 80 | versions = "7.0" 81 | walkdir = "2.3" 82 | # Unpacking of VSIX "packages" 83 | zip = { version = "4.1", default-features = false, features = ["deflate-flate2"] } 84 | 85 | [target.'cfg(all(target_env = "musl", target_arch = "x86_64"))'.dependencies] 86 | # Faster allocator for musl builds 87 | mimalloc = { version = "0.1", default-features = false } 88 | 89 | [dev-dependencies] 90 | insta = "1.42" 91 | 92 | [profile.dev.package.insta] 93 | opt-level = 3 94 | 95 | [profile.dev.package.similar] 96 | opt-level = 3 97 | 98 | [profile.dev.package.sha2] 99 | opt-level = 3 100 | -------------------------------------------------------------------------------- /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.'cfg(all())'] 2 | rustflags = [ 3 | # BEGIN - Embark standard lints v5 for Rust 1.55+ 4 | # do not change or add/remove here, but one can add exceptions after this section 5 | # for more info see: 6 | "-Wclippy::all", 7 | "-Wclippy::await_holding_lock", 8 | "-Wclippy::char_lit_as_u8", 9 | "-Wclippy::checked_conversions", 10 | "-Wclippy::clear_with_drain", 11 | "-Wclippy::dbg_macro", 12 | "-Wclippy::debug_assert_with_mut_call", 13 | "-Wclippy::disallowed_methods", 14 | "-Wclippy::disallowed_types", 15 | "-Wclippy::doc_markdown", 16 | "-Wclippy::empty_enum", 17 | "-Wclippy::enum_glob_use", 18 | "-Wclippy::exit", 19 | "-Wclippy::expl_impl_clone_on_copy", 20 | "-Wclippy::explicit_deref_methods", 21 | "-Wclippy::explicit_into_iter_loop", 22 | "-Wclippy::fallible_impl_from", 23 | "-Wclippy::filter_map_next", 24 | "-Wclippy::flat_map_option", 25 | "-Wclippy::float_cmp_const", 26 | "-Wclippy::fn_params_excessive_bools", 27 | "-Wclippy::from_iter_instead_of_collect", 28 | "-Wclippy::if_let_mutex", 29 | "-Wclippy::implicit_clone", 30 | "-Wclippy::imprecise_flops", 31 | "-Wclippy::inefficient_to_string", 32 | "-Wclippy::invalid_upcast_comparisons", 33 | "-Wclippy::large_digit_groups", 34 | "-Wclippy::large_stack_arrays", 35 | "-Wclippy::large_types_passed_by_value", 36 | "-Wclippy::let_unit_value", 37 | "-Wclippy::linkedlist", 38 | "-Wclippy::lossy_float_literal", 39 | "-Wclippy::macro_use_imports", 40 | "-Wclippy::manual_ok_or", 41 | "-Wclippy::map_err_ignore", 42 | "-Wclippy::map_flatten", 43 | "-Wclippy::map_unwrap_or", 44 | "-Wclippy::match_same_arms", 45 | "-Wclippy::match_wild_err_arm", 46 | "-Wclippy::match_wildcard_for_single_variants", 47 | "-Wclippy::mem_forget", 48 | "-Wclippy::missing_enforced_import_renames", 49 | "-Wclippy::mut_mut", 50 | "-Wclippy::mutex_integer", 51 | "-Wclippy::needless_borrow", 52 | "-Wclippy::needless_continue", 53 | "-Wclippy::needless_for_each", 54 | "-Wclippy::option_option", 55 | "-Wclippy::path_buf_push_overwrite", 56 | "-Wclippy::ptr_as_ptr", 57 | "-Wclippy::ptr_cast_constness", 58 | "-Wclippy::rc_mutex", 59 | "-Wclippy::ref_option_ref", 60 | "-Wclippy::rest_pat_in_fully_bound_structs", 61 | "-Wclippy::same_functions_in_if_condition", 62 | "-Wclippy::semicolon_if_nothing_returned", 63 | "-Wclippy::single_match_else", 64 | "-Wclippy::string_add_assign", 65 | "-Wclippy::string_add", 66 | "-Wclippy::string_lit_as_bytes", 67 | "-Wclippy::string_to_string", 68 | "-Wclippy::todo", 69 | "-Wclippy::trait_duplication_in_bounds", 70 | "-Wclippy::unimplemented", 71 | "-Wclippy::unnecessary_box_returns", 72 | "-Wclippy::unnested_or_patterns", 73 | "-Wclippy::unused_self", 74 | "-Wclippy::useless_transmute", 75 | "-Wclippy::verbose_file_reads", 76 | "-Wclippy::zero_sized_map_values", 77 | "-Wfuture_incompatible", 78 | "-Wnonstandard_style", 79 | "-Wrust_2018_idioms", 80 | "-Wunexpected_cfgs", 81 | # END - Embark standard lints v5 for Rust 1.55+ 82 | ] 83 | -------------------------------------------------------------------------------- /tests/snapshots/xwin-minimize.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/main.rs 3 | expression: help_text 4 | --- 5 | Runs the specified build command, detecting all of the headers and libraries 6 | used by the build, and generating a file that can be used to filter future splat 7 | operations, and optionally move only the user files to a new directory 8 | 9 | This command is only intended to work with cargo builds 10 | 11 | This command requires that `strace`, `clang-cl` and `lld-link` are installed and 12 | _probably_ only works on Linux. 13 | 14 | Usage: minimize [OPTIONS] 15 | 16 | Options: 17 | --include-debug-libs 18 | The MSVCRT includes (non-redistributable) debug versions of the 19 | various libs that are generally uninteresting to keep for most usage 20 | 21 | --include-debug-symbols 22 | The MSVCRT includes PDB (debug symbols) files for several of the 23 | libraries that are generally uninteresting to keep for most usage 24 | 25 | --disable-symlinks 26 | By default, symlinks are added to both the CRT and `WindowsSDK` to 27 | address casing issues in general usage. 28 | 29 | For example, if you are compiling C/C++ code that does `#include 30 | `, it will break on a case-sensitive file system, as the 31 | actual path in the `WindowsSDK` is `Windows.h`. This also applies even 32 | if the C/C++ you are compiling uses correct casing for all CRT/SDK 33 | includes, as the internal headers also use incorrect casing in most 34 | cases. 35 | 36 | --preserve-ms-arch-notation 37 | By default, we convert the MS specific `x64`, `arm`, and `arm64` 38 | target architectures to the more canonical `x86_64`, `aarch`, and 39 | `aarch64` of LLVM etc when creating directories/names. 40 | 41 | Passing this flag will preserve the MS names for those targets. 42 | 43 | --use-winsysroot-style 44 | Use the /winsysroot layout, so that clang-cl's /winsysroot flag can be 45 | used with the output, rather than needing both -vctoolsdir and 46 | -winsdkdir. You will likely also want to use 47 | --preserve-ms-arch-notation and --disable-symlinks for use with 48 | clang-cl on Windows 49 | 50 | --map 51 | The path of the filter file that is generated. Defaults to 52 | ./.xwin-cache/xwin-map.toml 53 | 54 | --output 55 | The root splat output directory. Defaults to `./.xwin-cache/splat` if 56 | not specified 57 | 58 | --minimize-output 59 | The root output directory for the minimized set of files discovered 60 | during the build. If not specified only the map file is written in 61 | addition to the splat 62 | 63 | --copy 64 | Copies files from the splat directory rather than moving them. Only 65 | used if --output is specified 66 | 67 | --target 68 | The cargo build triple to compile for. Defaults to 69 | `x86_64-pc-windows-msvc` if not specified 70 | 71 | --manifest-path 72 | The path of the manifest to compile. Defaults to Cargo.toml if not 73 | specified 74 | 75 | --preserve-strace 76 | If supplied, the strace output is persisted to disk rather than being 77 | deleted once the compilation has finished 78 | 79 | -h, --help 80 | Print help (see a summary with '-h') 81 | 82 | -V, --version 83 | Print version 84 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at opensource@embark-studios.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /tests/snapshots/xwin.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/main.rs 3 | expression: help_text 4 | --- 5 | Allows downloading and repacking the MSVC CRT and Windows SDK for cross 6 | compilation 7 | 8 | Usage: xwin [OPTIONS] 9 | 10 | Commands: 11 | list Displays a summary of the packages that would be downloaded 12 | download Downloads all the selected packages that aren't already present in 13 | the download cache 14 | unpack Unpacks all of the downloaded packages to disk 15 | splat Fixes the packages to prune unneeded files and adds symlinks to 16 | address file casing issues and then spalts the final artifacts into 17 | directories 18 | minimize Runs the specified build command, detecting all of the headers and 19 | libraries used by the build, and generating a file that can be used 20 | to filter future splat operations, and optionally move only the user 21 | files to a new directory 22 | help Print this message or the help of the given subcommand(s) 23 | 24 | Options: 25 | --accept-license 26 | Doesn't display the prompt to accept the license 27 | 28 | [env: XWIN_ACCEPT_LICENSE] 29 | 30 | -L, --log-level 31 | The log level for messages, only log messages at or above the level 32 | will be emitted 33 | 34 | [default: info] 35 | [possible values: off, error, warn, info, debug, trace] 36 | 37 | --json 38 | Output log messages as json 39 | 40 | --temp 41 | If set, will use a temporary directory for all files used for creating 42 | the archive and deleted upon exit, otherwise, all downloaded files are 43 | kept in the `--cache-dir` and won't be retrieved again 44 | 45 | --cache-dir 46 | Specifies the cache directory used to persist downloaded items to 47 | disk. Defaults to `./.xwin-cache` if not specified 48 | 49 | --manifest 50 | Specifies a VS manifest to use from a file, rather than downloading it 51 | from the Microsoft site 52 | 53 | --manifest-version 54 | The manifest version to retrieve 55 | 56 | [default: 17] 57 | 58 | --channel 59 | The product channel to use 60 | 61 | [default: release] 62 | 63 | --sdk-version 64 | If specified, this is the version of the SDK that the user wishes to 65 | use instead of defaulting to the latest SDK available in the the 66 | manifest 67 | 68 | --crt-version 69 | If specified, this is the version of the MSVCRT that the user wishes 70 | to use instead of defaulting to the latest MSVCRT available in the the 71 | manifest 72 | 73 | --include-atl 74 | Whether to include the Active Template Library (ATL) in the 75 | installation 76 | 77 | --include-debug-runtime 78 | Whether to include VCR debug libraries 79 | 80 | -t, --timeout 81 | Specifies a timeout for how long a single download is allowed to take 82 | 83 | [default: 60s] 84 | 85 | --https-proxy 86 | An HTTPS proxy to use 87 | 88 | [env: HTTPS_PROXY] 89 | 90 | --http-retry 91 | The number of times an HTTP get will be retried if it fails due to I/O 92 | failures 93 | 94 | [env: XWIN_HTTP_RETRY] 95 | [default: 0] 96 | 97 | --arch 98 | The architectures to include 99 | 100 | [default: x86_64] 101 | [possible values: x86, x86_64, aarch, aarch64] 102 | 103 | --variant 104 | The variants to include 105 | 106 | [default: desktop] 107 | [possible values: desktop, onecore, spectre] 108 | 109 | -h, --help 110 | Print help 111 | 112 | -V, --version 113 | Print version 114 | -------------------------------------------------------------------------------- /src/util.rs: -------------------------------------------------------------------------------- 1 | use crate::{Path, PathBuf}; 2 | use anyhow::{Context as _, Error}; 3 | use std::fmt; 4 | 5 | #[inline] 6 | pub fn canonicalize(path: &Path) -> anyhow::Result { 7 | PathBuf::from_path_buf( 8 | path.canonicalize() 9 | .with_context(|| format!("unable to canonicalize path '{path}'"))?, 10 | ) 11 | .map_err(|pb| anyhow::anyhow!("canonicalized path {} is not utf-8", pb.display())) 12 | } 13 | 14 | #[derive(Copy, Clone)] 15 | pub enum ProgressTarget { 16 | Stdout, 17 | Stderr, 18 | Hidden, 19 | } 20 | 21 | impl From for indicatif::ProgressDrawTarget { 22 | fn from(pt: ProgressTarget) -> Self { 23 | match pt { 24 | ProgressTarget::Stdout => Self::stdout(), 25 | ProgressTarget::Stderr => Self::stderr(), 26 | ProgressTarget::Hidden => Self::hidden(), 27 | } 28 | } 29 | } 30 | 31 | #[derive(Clone, PartialEq, Eq)] 32 | pub struct Sha256(pub [u8; 32]); 33 | 34 | impl fmt::Debug for Sha256 { 35 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 36 | write!(f, "{self}") 37 | } 38 | } 39 | 40 | impl fmt::Display for Sha256 { 41 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 42 | for x in self.0 { 43 | write!(f, "{x:02x}")?; 44 | } 45 | 46 | Ok(()) 47 | } 48 | } 49 | 50 | impl<'slice> PartialEq<&'slice [u8]> for Sha256 { 51 | fn eq(&self, o: &&'slice [u8]) -> bool { 52 | self.0 == *o 53 | } 54 | } 55 | 56 | impl std::str::FromStr for Sha256 { 57 | type Err = Error; 58 | 59 | fn from_str(hex_str: &str) -> Result { 60 | anyhow::ensure!( 61 | hex_str.len() == 64, 62 | "sha256 string length is {} instead of 64", 63 | hex_str.len() 64 | ); 65 | 66 | let mut digest = [0u8; 32]; 67 | 68 | for (ind, chars) in hex_str.as_bytes().chunks(2).enumerate() { 69 | let mut cur = match chars[0] { 70 | b'A'..=b'F' => chars[0] - b'A' + 10, 71 | b'a'..=b'f' => chars[0] - b'a' + 10, 72 | b'0'..=b'9' => chars[0] - b'0', 73 | c => anyhow::bail!("invalid byte in hex string {}", c), 74 | }; 75 | 76 | cur <<= 4; 77 | 78 | cur |= match chars[1] { 79 | b'A'..=b'F' => chars[1] - b'A' + 10, 80 | b'a'..=b'f' => chars[1] - b'a' + 10, 81 | b'0'..=b'9' => chars[1] - b'0', 82 | c => anyhow::bail!("invalid byte in hex checksum string {}", c), 83 | }; 84 | 85 | digest[ind] = cur; 86 | } 87 | 88 | Ok(Self(digest)) 89 | } 90 | } 91 | 92 | impl<'de> serde::Deserialize<'de> for Sha256 { 93 | fn deserialize(deserializer: D) -> Result 94 | where 95 | D: serde::de::Deserializer<'de>, 96 | { 97 | struct Visitor; 98 | 99 | impl serde::de::Visitor<'_> for Visitor { 100 | type Value = Sha256; 101 | 102 | fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 103 | formatter.write_str("sha256 string") 104 | } 105 | 106 | fn visit_str(self, value: &str) -> Result 107 | where 108 | E: serde::de::Error, 109 | { 110 | value.parse().map_err(serde::de::Error::custom) 111 | } 112 | } 113 | 114 | deserializer.deserialize_str(Visitor) 115 | } 116 | } 117 | 118 | pub(crate) fn serialize_sha256(hash: &Sha256, serializer: S) -> Result 119 | where 120 | S: serde::Serializer, 121 | { 122 | serializer.serialize_str(&hash.to_string()) 123 | } 124 | 125 | impl Sha256 { 126 | pub fn digest(buffer: &[u8]) -> Self { 127 | use sha2::Digest; 128 | 129 | let mut hasher = sha2::Sha256::new(); 130 | hasher.update(buffer); 131 | let digest = hasher.finalize(); 132 | 133 | Self(digest.into()) 134 | } 135 | } 136 | 137 | #[cfg(test)] 138 | mod test { 139 | use super::*; 140 | 141 | #[test] 142 | fn sha256() { 143 | let buffer = [3u8; 11]; 144 | let digest = Sha256::digest(&buffer); 145 | 146 | let hex = digest.to_string(); 147 | 148 | assert_eq!(digest, hex.parse::().unwrap()); 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Embark Contributor Guidelines 2 | 3 | Welcome! This project is created by the team at [Embark Studios](https://embark.games). We're glad you're interested in contributing! We welcome contributions from people of all backgrounds who are interested in making great software with us. 4 | 5 | At Embark, we aspire to empower everyone to create interactive experiences. To do this, we're exploring and pushing the boundaries of new technologies, and sharing our learnings with the open source community. 6 | 7 | If you have ideas for collaboration, email us at opensource@embark-studios.com. 8 | 9 | We're also hiring full-time engineers to work with us in Stockholm! Check out our current job postings [here](https://www.embark-studios.com/jobs). 10 | 11 | ## Issues 12 | 13 | ### Feature Requests 14 | 15 | If you have ideas or how to improve our projects, you can suggest features by opening a GitHub issue. Make sure to include details about the feature or change, and describe any uses cases it would enable. 16 | 17 | Feature requests will be tagged as `enhancement` and their status will be updated in the comments of the issue. 18 | 19 | ### Bugs 20 | 21 | When reporting a bug or unexpected behaviour in a project, make sure your issue describes steps to reproduce the behaviour, including the platform you were using, what steps you took, and any error messages. 22 | 23 | Reproducible bugs will be tagged as `bug` and their status will be updated in the comments of the issue. 24 | 25 | ### Wontfix 26 | 27 | Issues will be closed and tagged as `wontfix` if we decide that we do not wish to implement it, usually due to being misaligned with the project vision or out of scope. We will comment on the issue with more detailed reasoning. 28 | 29 | ## Contribution Workflow 30 | 31 | ### Open Issues 32 | 33 | If you're ready to contribute, start by looking at our open issues tagged as [`help wanted`](../../issues?q=is%3Aopen+is%3Aissue+label%3A"help+wanted") or [`good first issue`](../../issues?q=is%3Aopen+is%3Aissue+label%3A"good+first+issue"). 34 | 35 | You can comment on the issue to let others know you're interested in working on it or to ask questions. 36 | 37 | ### Making Changes 38 | 39 | 1. Fork the repository. 40 | 41 | 2. Create a new feature branch. 42 | 43 | 3. Make your changes. Ensure that there are no build errors by running the project with your changes locally. 44 | 45 | 4. Open a pull request with a name and description of what you did. You can read more about working with pull requests on GitHub [here](https://help.github.com/en/articles/creating-a-pull-request-from-a-fork). 46 | 47 | 5. A maintainer will review your pull request and may ask you to make changes. 48 | 49 | ## Code Guidelines 50 | 51 | ### Rust 52 | 53 | You can read about our standards and recommendations for working with Rust [here](https://github.com/EmbarkStudios/rust-ecosystem/blob/main/guidelines.md). 54 | 55 | ### Python 56 | 57 | We recommend following [PEP8 conventions](https://www.python.org/dev/peps/pep-0008/) when working with Python modules. 58 | 59 | ### JavaScript & TypeScript 60 | 61 | We use [Prettier](https://prettier.io/) with the default settings to auto-format our JavaScript and TypeScript code. 62 | 63 | ## Licensing 64 | 65 | Unless otherwise specified, all Embark open source projects shall comply with the Rust standard licensing model (MIT + Apache 2.0) and are thereby licensed under a dual license, allowing licensees to choose either MIT OR Apache-2.0 at their option. 66 | 67 | ## Contributor Terms 68 | 69 | Thank you for your interest in Embark Studios’ open source project. By providing a contribution (new or modified code, other input, feedback or suggestions etc.) you agree to these Contributor Terms. 70 | 71 | You confirm that each of your contributions has been created by you and that you are the copyright owner. You also confirm that you have the right to provide the contribution to us and that you do it under the Rust dual licence model (MIT + Apache 2.0). 72 | 73 | If you want to contribute something that is not your original creation, you may submit it to Embark Studios separately from any contribution, including details of its source and of any license or other restriction (such as related patents, trademarks, agreements etc.) 74 | 75 | Please also note that our projects are released with a [Contributor Code of Conduct](CODE_OF_CONDUCT.md) to ensure that they are welcoming places for everyone to contribute. By participating in any Embark Studios open source project, you agree to keep to the Contributor Code of Conduct. 76 | -------------------------------------------------------------------------------- /xwin.dockerfile: -------------------------------------------------------------------------------- 1 | # We'll just use the official Rust image rather than build our own from scratch 2 | FROM docker.io/library/rust:1.81.0-slim-bookworm 3 | 4 | ENV KEYRINGS /usr/local/share/keyrings 5 | 6 | RUN set -eux; \ 7 | mkdir -p $KEYRINGS; \ 8 | apt-get update && apt-get install -y gpg curl; \ 9 | # clang/lld/llvm 10 | curl --fail https://apt.llvm.org/llvm-snapshot.gpg.key | gpg --dearmor > $KEYRINGS/llvm.gpg; \ 11 | # wine 12 | curl --fail https://dl.winehq.org/wine-builds/winehq.key | gpg --dearmor > $KEYRINGS/winehq.gpg; \ 13 | echo "deb [signed-by=$KEYRINGS/llvm.gpg] http://apt.llvm.org/bookworm/ llvm-toolchain-bookworm-19 main" > /etc/apt/sources.list.d/llvm.list; \ 14 | echo "deb [signed-by=$KEYRINGS/winehq.gpg] https://dl.winehq.org/wine-builds/debian/ bookworm main" > /etc/apt/sources.list.d/winehq.list; 15 | 16 | RUN set -eux; \ 17 | dpkg --add-architecture i386; \ 18 | # Skipping all of the "recommended" cruft reduces total images size by ~300MiB 19 | apt-get update && apt-get install --no-install-recommends -y \ 20 | clang-19 \ 21 | # llvm-ar 22 | llvm-19 \ 23 | lld-19 \ 24 | # get a recent wine so we can run tests 25 | winehq-staging \ 26 | # Unpack xwin 27 | tar; \ 28 | # ensure that clang/clang++ are callable directly 29 | ln -s clang-19 /usr/bin/clang && ln -s clang /usr/bin/clang++ && ln -s lld-19 /usr/bin/ld.lld; \ 30 | # We also need to setup symlinks ourselves for the MSVC shims because they aren't in the debian packages 31 | ln -s clang-19 /usr/bin/clang-cl && ln -s llvm-ar-19 /usr/bin/llvm-lib && ln -s lld-link-19 /usr/bin/lld-link && ln -s llvm-rc-19 /usr/bin/llvm-rc; \ 32 | # Verify the symlinks are correct 33 | clang++ -v; \ 34 | ld.lld -v; \ 35 | # Doesn't have an actual -v/--version flag, but it still exits with 0 36 | llvm-lib -v; \ 37 | clang-cl -v; \ 38 | lld-link --version; \ 39 | # Use clang instead of gcc when compiling and linking binaries targeting the host (eg proc macros, build files) 40 | update-alternatives --install /usr/bin/cc cc /usr/bin/clang 100; \ 41 | update-alternatives --install /usr/bin/c++ c++ /usr/bin/clang++ 100; \ 42 | update-alternatives --install /usr/bin/ld ld /usr/bin/ld.lld 100; \ 43 | apt-get remove -y --auto-remove; \ 44 | rm -rf /var/lib/apt/lists/*; 45 | 46 | # Retrieve the std lib for the target 47 | RUN rustup target add x86_64-pc-windows-msvc 48 | 49 | RUN set -eux; \ 50 | xwin_version="0.6.7"; \ 51 | xwin_prefix="xwin-$xwin_version-x86_64-unknown-linux-musl"; \ 52 | # Install xwin to cargo/bin via github release. Note you could also just use `cargo install xwin`. 53 | curl --fail -L https://github.com/Jake-Shadle/xwin/releases/download/$xwin_version/$xwin_prefix.tar.gz | tar -xzv -C /usr/local/cargo/bin --strip-components=1 $xwin_prefix/xwin; \ 54 | # Splat the CRT and SDK files to /xwin/crt and /xwin/sdk respectively 55 | xwin --accept-license splat --output /xwin; \ 56 | # Remove unneeded files to reduce image size 57 | rm -rf .xwin-cache /usr/local/cargo/bin/xwin; 58 | 59 | # Note that we're using the full target triple for each variable instead of the 60 | # simple CC/CXX/AR shorthands to avoid issues when compiling any C/C++ code for 61 | # build dependencies that need to compile and execute in the host environment 62 | ENV CC_x86_64_pc_windows_msvc="clang-cl" \ 63 | CXX_x86_64_pc_windows_msvc="clang-cl" \ 64 | AR_x86_64_pc_windows_msvc="llvm-lib" \ 65 | # wine can be quite spammy with log messages and they're generally uninteresting 66 | WINEDEBUG="-all" \ 67 | # Use wine to run test executables 68 | CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_RUNNER="wine" \ 69 | # Note that we only disable unused-command-line-argument here since clang-cl 70 | # doesn't implement all of the options supported by cl, but the ones it doesn't 71 | # are _generally_ not interesting. 72 | CL_FLAGS="-Wno-unused-command-line-argument -fuse-ld=lld-link /vctoolsdir /xwin/crt /winsdkdir /xwin/sdk" \ 73 | # Let cargo know what linker to invoke if you haven't already specified it 74 | # in a .cargo/config.toml file 75 | CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_LINKER="lld-link" \ 76 | CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_RUSTFLAGS="-Lnative=/xwin/crt/lib/x86_64 -Lnative=/xwin/sdk/lib/um/x86_64 -Lnative=/xwin/sdk/lib/ucrt/x86_64" 77 | 78 | # These are separate since docker/podman won't transform environment variables defined in the same ENV block 79 | ENV CFLAGS_x86_64_pc_windows_msvc="$CL_FLAGS" \ 80 | CXXFLAGS_x86_64_pc_windows_msvc="$CL_FLAGS" 81 | 82 | # Run wineboot just to setup the default WINEPREFIX so we don't do it every 83 | # container run 84 | RUN wine wineboot --init 85 | -------------------------------------------------------------------------------- /tests/deterministic.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Context as _; 2 | use xwin::PathBuf; 3 | 4 | #[test] 5 | fn verify_deterministic() { 6 | let ctx = xwin::Ctx::with_dir( 7 | PathBuf::from(".xwin-cache/deterministic"), 8 | xwin::util::ProgressTarget::Hidden, 9 | ureq::agent(), 10 | 0, 11 | ) 12 | .unwrap(); 13 | 14 | let ctx = std::sync::Arc::new(ctx); 15 | 16 | let hidden = indicatif::ProgressBar::hidden(); 17 | 18 | let manifest_contents = std::fs::read_to_string("tests/deterministic_manifest.json").unwrap(); 19 | let manifest: xwin::manifest::Manifest = serde_json::from_str(&manifest_contents).unwrap(); 20 | let pkg_manifest = 21 | xwin::manifest::get_package_manifest(&ctx, &manifest, hidden.clone()).unwrap(); 22 | 23 | let pruned = xwin::prune_pkg_list( 24 | &pkg_manifest, 25 | xwin::Arch::X86_64 as u32, 26 | xwin::Variant::Desktop as u32, 27 | true, 28 | true, 29 | None, 30 | None, 31 | ) 32 | .unwrap(); 33 | 34 | let output_dir = ctx.work_dir.join("splat"); 35 | if !output_dir.exists() { 36 | std::fs::create_dir_all(&output_dir).unwrap(); 37 | } 38 | 39 | let op = xwin::Ops::Splat(xwin::SplatConfig { 40 | include_debug_libs: false, 41 | include_debug_symbols: false, 42 | enable_symlinks: true, 43 | preserve_ms_arch_notation: false, 44 | use_winsysroot_style: false, 45 | map: None, 46 | copy: true, 47 | output: output_dir.clone(), 48 | }); 49 | 50 | ctx.execute( 51 | pkg_manifest.packages, 52 | pruned 53 | .payloads 54 | .into_iter() 55 | .map(|payload| xwin::WorkItem { 56 | progress: hidden.clone(), 57 | payload: std::sync::Arc::new(payload), 58 | }) 59 | .collect(), 60 | pruned.crt_version, 61 | pruned.sdk_version, 62 | pruned.vcr_version, 63 | xwin::Arch::X86_64 as u32, 64 | xwin::Variant::Desktop as u32, 65 | op, 66 | ) 67 | .unwrap(); 68 | 69 | #[inline] 70 | fn calc_hash(file: &std::fs::File) -> Result { 71 | use std::{hash::Hasher, io::BufRead}; 72 | 73 | let mut hasher = twox_hash::XxHash64::with_seed(0); 74 | 75 | let mut reader = std::io::BufReader::new(file); 76 | loop { 77 | let len = { 78 | let buf = reader.fill_buf()?; 79 | if buf.is_empty() { 80 | break; 81 | } 82 | 83 | hasher.write(buf); 84 | buf.len() 85 | }; 86 | reader.consume(len); 87 | } 88 | 89 | Ok(hasher.finish()) 90 | } 91 | 92 | struct Splatted { 93 | path: PathBuf, 94 | link: Option, 95 | hash: u64, 96 | } 97 | 98 | let output_dir = xwin::util::canonicalize(&output_dir).unwrap(); 99 | let mut files: Vec<_> = walkdir::WalkDir::new(&output_dir) 100 | .sort_by_file_name() 101 | .into_iter() 102 | .filter_map(|entry| { 103 | let entry = entry.unwrap(); 104 | 105 | if entry.file_type().is_dir() { 106 | return None; 107 | } 108 | 109 | let path = PathBuf::from_path_buf(entry.path().to_owned()).unwrap(); 110 | 111 | let link = if entry.path_is_symlink() { 112 | Some(PathBuf::from_path_buf(std::fs::read_link(&path).unwrap()).unwrap()) 113 | } else { 114 | None 115 | }; 116 | 117 | Some(Splatted { 118 | path, 119 | link, 120 | hash: 0, 121 | }) 122 | }) 123 | .collect(); 124 | 125 | use rayon::prelude::*; 126 | files.par_iter_mut().for_each(|splat| { 127 | if splat.link.is_none() { 128 | let file = std::fs::File::open(&splat.path) 129 | .with_context(|| format!("failed to open {}", splat.path)) 130 | .unwrap(); 131 | splat.hash = calc_hash(&file) 132 | .with_context(|| format!("failed to read {}", splat.path)) 133 | .unwrap(); 134 | } 135 | }); 136 | 137 | let mut actual = String::with_capacity(4 * 1024); 138 | 139 | use std::fmt::Write; 140 | for file in files { 141 | actual 142 | .write_str(file.path.strip_prefix(&output_dir).unwrap().as_str()) 143 | .unwrap(); 144 | 145 | match file.link { 146 | Some(link) => { 147 | actual.write_str(" => ").unwrap(); 148 | actual.write_str(link.as_str()).unwrap(); 149 | } 150 | None => write!(&mut actual, " @ {:x}", file.hash).unwrap(), 151 | } 152 | 153 | actual.push('\n'); 154 | } 155 | 156 | insta::assert_snapshot!(actual); 157 | } 158 | -------------------------------------------------------------------------------- /src/download.rs: -------------------------------------------------------------------------------- 1 | use crate::{Ctx, Error, manifest, util::Sha256}; 2 | use anyhow::Context as _; 3 | use camino::Utf8PathBuf as PathBuf; 4 | use std::sync::Arc; 5 | 6 | #[derive(Debug)] 7 | struct Cab { 8 | filename: PathBuf, 9 | sha256: Sha256, 10 | url: String, 11 | #[allow(dead_code)] 12 | size: u64, 13 | } 14 | 15 | pub(crate) struct CabContents { 16 | pub(crate) path: PathBuf, 17 | pub(crate) content: bytes::Bytes, 18 | pub(crate) sequence: u32, 19 | } 20 | 21 | pub(crate) enum PayloadContents { 22 | Vsix(bytes::Bytes), 23 | Msi { 24 | msi: bytes::Bytes, 25 | cabs: Vec, 26 | }, 27 | } 28 | 29 | pub(crate) fn download( 30 | ctx: Arc, 31 | pkgs: Arc>, 32 | item: &crate::WorkItem, 33 | ) -> Result, Error> { 34 | item.progress.set_message("📥 downloading.."); 35 | 36 | let contents = ctx.get_and_validate( 37 | &item.payload.url, 38 | &item.payload.filename, 39 | Some(item.payload.sha256.clone()), 40 | item.progress.clone(), 41 | )?; 42 | 43 | let pc = match item.payload.filename.extension() { 44 | Some("msi") => { 45 | let cabs: Vec<_> = match pkgs.values().find(|mi| { 46 | mi.payloads 47 | .iter() 48 | .any(|mi_payload| mi_payload.sha256 == item.payload.sha256) 49 | }) { 50 | Some(mi) => mi 51 | .payloads 52 | .iter() 53 | .filter(|pay| pay.file_name.ends_with(".cab")) 54 | .map(|pay| Cab { 55 | filename: pay 56 | .file_name 57 | .strip_prefix("Installers\\") 58 | .unwrap_or(&pay.file_name) 59 | .into(), 60 | sha256: pay.sha256.clone(), 61 | url: pay.url.clone(), 62 | size: pay.size, 63 | }) 64 | .collect(), 65 | None => anyhow::bail!( 66 | "unable to find manifest parent for {}", 67 | item.payload.filename 68 | ), 69 | }; 70 | 71 | download_cabs(ctx, &cabs, item, contents) 72 | } 73 | Some("vsix") => Ok(Some(PayloadContents::Vsix(contents))), 74 | ext => anyhow::bail!("unknown extension {ext:?}"), 75 | }; 76 | 77 | item.progress.finish_with_message("downloaded"); 78 | 79 | pc 80 | } 81 | 82 | /// Each SDK MSI has 1 or more cab files associated with it containing the actual 83 | /// data we need that must be downloaded separately and indexed from the MSI 84 | fn download_cabs( 85 | ctx: Arc, 86 | cabs: &[Cab], 87 | msi: &crate::WorkItem, 88 | msi_content: bytes::Bytes, 89 | ) -> Result, Error> { 90 | use rayon::prelude::*; 91 | 92 | let msi_filename = &msi.payload.filename; 93 | 94 | let mut msi_pkg = msi::Package::open(std::io::Cursor::new(msi_content.clone())) 95 | .with_context(|| format!("invalid MSI for {msi_filename}"))?; 96 | 97 | // The `Media` table contains the list of cabs by name, which we then need 98 | // to lookup in the list of payloads. 99 | // Columns: [DiskId, LastSequence, DiskPrompt, Cabinet, VolumeLabel, Source] 100 | let cab_files: Vec<_> = msi_pkg 101 | .select_rows(msi::Select::table("Media")) 102 | .with_context(|| format!("{msi_filename} does not contain a list of CAB files"))? 103 | .filter_map(|row| { 104 | // Columns: 105 | // 0 - DiskId 106 | // 1 - LastSequence 107 | // 2 - DiskPrompt 108 | // 3 - Cabinet name 109 | // ... 110 | if row.len() >= 3 { 111 | // For some reason most/all of the msi files contain a NULL cabinet 112 | // in the first position which is useless 113 | row[3] 114 | .as_str() 115 | .and_then(|s| row[1].as_int().map(|seq| (s, seq as u32))) 116 | .and_then(|(name, seq)| { 117 | let cab_name = name.trim_matches('"'); 118 | 119 | cabs.iter().find_map(|payload| { 120 | (payload.filename == cab_name).then(|| { 121 | ( 122 | PathBuf::from(format!( 123 | "{}/{cab_name}", 124 | msi_filename.file_stem().unwrap(), 125 | )), 126 | payload.sha256.clone(), 127 | payload.url.clone(), 128 | seq, 129 | ) 130 | }) 131 | }) 132 | }) 133 | } else { 134 | None 135 | } 136 | }) 137 | .collect(); 138 | 139 | if cab_files.is_empty() { 140 | return Ok(None); 141 | } 142 | 143 | let cabs = cab_files 144 | .into_par_iter() 145 | .map( 146 | |(cab_name, chksum, url, sequence)| -> Result { 147 | let cab_contents = 148 | ctx.get_and_validate(url, &cab_name, Some(chksum), msi.progress.clone())?; 149 | Ok(CabContents { 150 | path: cab_name, 151 | content: cab_contents, 152 | sequence, 153 | }) 154 | }, 155 | ) 156 | .collect::, _>>()?; 157 | 158 | Ok(Some(PayloadContents::Msi { 159 | msi: msi_content, 160 | cabs, 161 | })) 162 | } 163 | -------------------------------------------------------------------------------- /src/manifest.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Context as _, ensure}; 2 | use serde::Deserialize; 3 | use std::{cmp, collections::BTreeMap}; 4 | 5 | use crate::Ctx; 6 | 7 | #[derive(Deserialize, Debug, Clone)] 8 | pub struct Payload { 9 | #[serde(rename = "fileName")] 10 | pub file_name: String, 11 | pub sha256: crate::util::Sha256, 12 | pub size: u64, 13 | pub url: String, 14 | } 15 | 16 | #[derive(Copy, Clone, Deserialize, PartialEq, Eq, Debug)] 17 | #[serde(rename_all = "snake_case")] 18 | pub enum Chip { 19 | X86, 20 | X64, 21 | Arm, 22 | Arm64, 23 | Neutral, 24 | } 25 | 26 | impl Chip { 27 | #[inline] 28 | pub fn as_str(&self) -> &'static str { 29 | match self { 30 | Self::X86 => "x86", 31 | Self::X64 => "x64", 32 | Self::Arm => "arm", 33 | Self::Arm64 => "arm64", 34 | Self::Neutral => "neutral", 35 | } 36 | } 37 | } 38 | 39 | #[derive(Copy, Clone, Deserialize, PartialEq, Eq, Debug)] 40 | pub enum ItemKind { 41 | /// Unused. 42 | Bootstrapper, 43 | /// Unused. 44 | Channel, 45 | /// Unused. 46 | ChannelProduct, 47 | /// A composite package, no contents itself. Unused. 48 | Component, 49 | /// A single executable. Unused. 50 | Exe, 51 | /// Another kind of composite package without contents, and no localization. Unused. 52 | Group, 53 | /// Top level manifest 54 | Manifest, 55 | /// MSI installer 56 | Msi, 57 | /// Unused. 58 | Msu, 59 | /// Nuget package. Unused. 60 | Nupkg, 61 | /// Unused 62 | Product, 63 | /// A glorified zip file 64 | Vsix, 65 | /// Windows feature install/toggle. Unused. 66 | WindowsFeature, 67 | /// Unused. 68 | Workload, 69 | /// Plain zip file (ie not vsix). Unused. 70 | Zip, 71 | } 72 | 73 | #[derive(Deserialize, Debug, Clone, Copy)] 74 | #[serde(rename_all = "camelCase")] 75 | pub struct InstallSizes { 76 | pub target_drive: Option, 77 | } 78 | 79 | #[derive(Deserialize, Debug, Clone)] 80 | #[serde(rename_all = "camelCase")] 81 | pub struct ManifestItem { 82 | pub id: String, 83 | pub version: String, 84 | #[serde(rename = "type")] 85 | pub kind: ItemKind, 86 | pub chip: Option, 87 | #[serde(default)] 88 | pub payloads: Vec, 89 | #[serde(default)] 90 | pub dependencies: BTreeMap, 91 | pub install_sizes: Option, 92 | } 93 | 94 | impl PartialEq for ManifestItem { 95 | #[inline] 96 | fn eq(&self, o: &Self) -> bool { 97 | self.cmp(o) == cmp::Ordering::Equal 98 | } 99 | } 100 | 101 | impl Eq for ManifestItem {} 102 | 103 | impl cmp::Ord for ManifestItem { 104 | #[inline] 105 | fn cmp(&self, o: &Self) -> cmp::Ordering { 106 | self.id.cmp(&o.id) 107 | } 108 | } 109 | 110 | impl cmp::PartialOrd for ManifestItem { 111 | #[inline] 112 | fn partial_cmp(&self, o: &Self) -> Option { 113 | Some(self.cmp(o)) 114 | } 115 | } 116 | 117 | #[derive(Deserialize, Debug)] 118 | pub struct Manifest { 119 | #[serde(rename = "channelItems")] 120 | channel_items: Vec, 121 | } 122 | 123 | /// Retrieves the top-level manifest which contains license links as well as the 124 | /// link to the actual package manifest which describes all of the contents 125 | pub fn get_manifest( 126 | ctx: &Ctx, 127 | version: &str, 128 | channel: &str, 129 | progress: indicatif::ProgressBar, 130 | ) -> Result { 131 | let manifest_bytes = ctx.get_and_validate( 132 | format!("https://aka.ms/vs/{version}/{channel}/channel"), 133 | &format!("manifest_{version}.json"), 134 | None, 135 | progress, 136 | )?; 137 | 138 | let manifest: Manifest = serde_json::from_slice(&manifest_bytes)?; 139 | 140 | Ok(manifest) 141 | } 142 | 143 | /// Retrieves the package manifest specified in the input manifest 144 | pub fn get_package_manifest( 145 | ctx: &Ctx, 146 | manifest: &Manifest, 147 | progress: indicatif::ProgressBar, 148 | ) -> Result { 149 | let pkg_manifest = manifest 150 | .channel_items 151 | .iter() 152 | .find(|ci| ci.kind == ItemKind::Manifest && !ci.payloads.is_empty()) 153 | .context("Unable to locate package manifest")?; 154 | 155 | // This always just a single payload, but ensure it stays that way in the future 156 | ensure!( 157 | pkg_manifest.payloads.len() == 1, 158 | "VS package manifest should have exactly 1 payload" 159 | ); 160 | 161 | // While the payload includes a sha256 checksum for the payload it is actually 162 | // never correct (even though it is part of the url!) so we have to just download 163 | // it without checking, which is terrible but...¯\_(ツ)_/¯ 164 | let payload = &pkg_manifest.payloads[0]; 165 | 166 | let manifest_bytes = ctx.get_and_validate( 167 | payload.url.clone(), 168 | &format!("pkg_manifest_{}.vsman", payload.sha256), 169 | None, 170 | progress, 171 | )?; 172 | 173 | #[derive(Deserialize, Debug)] 174 | struct PkgManifest { 175 | packages: Vec, 176 | } 177 | 178 | let manifest: PkgManifest = 179 | serde_json::from_slice(&manifest_bytes).context("unable to parse manifest")?; 180 | 181 | let mut packages = BTreeMap::new(); 182 | let mut package_counts = BTreeMap::new(); 183 | 184 | for pkg in manifest.packages { 185 | // built a unqiue key for each duplicate id to prevent overriding distinct packages 186 | let pkg_id = if packages.contains_key(&pkg.id) { 187 | let count = package_counts.get(&pkg.id).unwrap_or(&0) + 1; 188 | package_counts.insert(pkg.id.clone(), count); 189 | format!("{}#{}", pkg.id, count) 190 | } else { 191 | pkg.id.clone() 192 | }; 193 | 194 | packages.insert(pkg_id, pkg); 195 | } 196 | 197 | Ok(PackageManifest { packages }) 198 | } 199 | 200 | pub struct PackageManifest { 201 | pub packages: BTreeMap, 202 | } 203 | -------------------------------------------------------------------------------- /.github/workflows/rust-ci.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - main 5 | tags: 6 | - "*" 7 | pull_request: 8 | 9 | concurrency: 10 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 11 | cancel-in-progress: true 12 | 13 | name: CI 14 | jobs: 15 | lint: 16 | name: Lint 17 | strategy: 18 | matrix: 19 | include: 20 | - os: ubuntu-22.04 21 | runs-on: ${{ matrix.os }} 22 | steps: 23 | - uses: actions/checkout@v4 24 | - uses: dtolnay/rust-toolchain@stable 25 | 26 | # make sure all code has been formatted with rustfmt 27 | - name: check rustfmt 28 | run: | 29 | rustup component add rustfmt 30 | cargo fmt -- --check --color always 31 | 32 | # run clippy to verify we have no warnings 33 | - run: cargo fetch 34 | - name: cargo clippy 35 | run: | 36 | rustup component add clippy 37 | cargo clippy --all-targets --all-features -- -D warnings 38 | 39 | check_compiles: 40 | name: Verify compiles 41 | runs-on: ubuntu-22.04 42 | steps: 43 | - uses: actions/checkout@v4 44 | - uses: dtolnay/rust-toolchain@stable 45 | # Add the actual target we compile for in the test 46 | - run: rustup target add x86_64-pc-windows-msvc aarch64-pc-windows-msvc 47 | - name: symlinks 48 | run: | 49 | set -eux 50 | sudo ln -s clang-14 /usr/bin/clang-cl 51 | sudo ln -s llvm-ar-14 /usr/bin/llvm-lib 52 | sudo ln -s lld-link-14 /usr/bin/lld-link 53 | sudo ln -s lld-14 /usr/bin/ld.lld 54 | clang++ -v 55 | ld.lld -v 56 | llvm-lib -v 57 | clang-cl -v 58 | lld-link --version 59 | - run: cargo fetch 60 | - name: cargo test build 61 | run: cargo build --tests --release 62 | - name: cargo test 63 | run: cargo test --release verify_compiles 64 | 65 | check_deterministic: 66 | name: Verify deterministic 67 | runs-on: ubuntu-22.04 68 | steps: 69 | - uses: actions/checkout@v4 70 | - uses: dtolnay/rust-toolchain@stable 71 | - run: cargo fetch 72 | - name: cargo test build 73 | run: cargo build --tests --release 74 | - name: cargo test 75 | run: cargo test --release verify_deterministic 76 | 77 | check_cli: 78 | name: Verify CLI 79 | runs-on: ubuntu-22.04 80 | steps: 81 | - uses: actions/checkout@v4 82 | - uses: dtolnay/rust-toolchain@stable 83 | - run: cargo fetch 84 | - name: cargo test build 85 | run: cargo build --tests 86 | - name: cargo test 87 | run: cargo test cli_help 88 | 89 | deny-check: 90 | name: cargo-deny 91 | runs-on: ubuntu-22.04 92 | steps: 93 | - uses: actions/checkout@v4 94 | - uses: EmbarkStudios/cargo-deny-action@v2 95 | 96 | publish-check: 97 | name: Publish Check 98 | strategy: 99 | matrix: 100 | include: 101 | - target: x86_64-unknown-linux-musl 102 | - target: aarch64-unknown-linux-musl 103 | runs-on: ubuntu-22.04 104 | env: 105 | CC_aarch64_unknown_linux_musl: clang 106 | AR_aarch64_unknown_linux_musl: llvm-ar 107 | CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_RUSTFLAGS: "-Clink-self-contained=yes -Clinker=rust-lld" 108 | steps: 109 | - uses: actions/checkout@v4 110 | - uses: dtolnay/rust-toolchain@stable 111 | with: 112 | target: ${{ matrix.target }} 113 | - run: cargo fetch --target ${{ matrix.target }} 114 | - name: Install musl tools 115 | if: endsWith(matrix.target, '-linux-musl') 116 | run: sudo apt-get install -y musl-tools llvm 117 | - name: cargo publish check 118 | run: cargo publish --dry-run --target ${{ matrix.target }} 119 | 120 | release: 121 | name: Release 122 | needs: [deny-check] 123 | if: startsWith(github.ref, 'refs/tags/') 124 | strategy: 125 | matrix: 126 | include: 127 | - os: ubuntu-22.04 128 | target: x86_64-unknown-linux-musl 129 | strip: llvm-strip 130 | - os: ubuntu-22.04 131 | target: aarch64-unknown-linux-musl 132 | strip: llvm-strip 133 | - os: macos-12 134 | target: x86_64-apple-darwin 135 | strip: strip 136 | - os: macos-12 137 | target: aarch64-apple-darwin 138 | strip: strip 139 | - os: windows-2022 140 | target: x86_64-pc-windows-msvc 141 | strip: "" 142 | runs-on: ${{ matrix.os }} 143 | env: 144 | CC_aarch64_unknown_linux_musl: clang 145 | AR_aarch64_unknown_linux_musl: llvm-ar 146 | CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_RUSTFLAGS: "-Clink-self-contained=yes -Clinker=rust-lld" 147 | steps: 148 | - uses: dtolnay/rust-toolchain@stable 149 | with: 150 | targets: ${{ matrix.target }} 151 | - name: Install musl tools 152 | if: endsWith(matrix.target, '-linux-musl') 153 | run: sudo apt-get install -y musl-tools llvm 154 | - name: Checkout 155 | uses: actions/checkout@v4 156 | - run: cargo fetch --target ${{ matrix.target }} 157 | - name: Release build 158 | shell: bash 159 | run: | 160 | cargo build --release --target ${{ matrix.target }} 161 | - name: Package 162 | shell: bash 163 | run: | 164 | name=xwin 165 | tag=$(git describe --tags --abbrev=0) 166 | target="${{ matrix.target }}" 167 | release_name="$name-$tag-$target" 168 | release_tar="${release_name}.tar.gz" 169 | mkdir "$release_name" 170 | 171 | if [ ! -z "${{ matrix.strip }}" ]; then 172 | ${{ matrix.strip }} "target/$target/release/$name" 173 | fi 174 | 175 | cp "target/$target/release/$name" "$release_name/" 176 | cp README.md LICENSE-APACHE LICENSE-MIT "$release_name/" 177 | tar czvf "$release_tar" "$release_name" 178 | 179 | rm -r "$release_name" 180 | 181 | if [ "${{ matrix.target }}" == "x86_64-pc-windows-msvc" ]; then 182 | echo "(Get-FileHash \"${release_tar}\" -Algorithm SHA256).Hash | Out-File -Encoding ASCII -NoNewline \"${release_tar}.sha256\"" | pwsh -c - 183 | else 184 | echo -n "$(shasum -ba 256 "${release_tar}" | cut -d " " -f 1)" > "${release_tar}.sha256" 185 | fi 186 | - name: Publish 187 | uses: softprops/action-gh-release@v1 188 | with: 189 | draft: true 190 | files: "xwin*" 191 | env: 192 | GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }} 193 | -------------------------------------------------------------------------------- /docs/example-map.toml: -------------------------------------------------------------------------------- 1 | [crt.headers] 2 | filter = [ 3 | "__msvc_iter_core.hpp", 4 | "__msvc_sanitizer_annotate_container.hpp", 5 | "algorithm", 6 | "cassert", 7 | "cctype", 8 | "cfloat", 9 | "climits", 10 | "concurrencysal.h", 11 | "crtdefs.h", 12 | "cstddef", 13 | "cstdint", 14 | "cstdio", 15 | "cstdlib", 16 | "cstring", 17 | "cwchar", 18 | "eh.h", 19 | "exception", 20 | "excpt.h", 21 | "initializer_list", 22 | "iosfwd", 23 | "isa_availability.h", 24 | "limits", 25 | "limits.h", 26 | "list", 27 | "new", 28 | "sal.h", 29 | "setjmp.h", 30 | "stdint.h", 31 | "string", 32 | "type_traits", 33 | "use_ansi.h", 34 | "utility", 35 | "vadefs.h", 36 | "vcruntime.h", 37 | "vcruntime_exception.h", 38 | "vcruntime_new.h", 39 | "vcruntime_new_debug.h", 40 | "vcruntime_string.h", 41 | "vector", 42 | "xatomic.h", 43 | "xkeycheck.h", 44 | "xmemory", 45 | "xstring", 46 | "xtr1common", 47 | "xutility", 48 | "yvals.h", 49 | "yvals_core.h", 50 | ] 51 | 52 | [crt.libs] 53 | filter = ["x86_64/msvcrt.lib", "x86_64/vcruntime.lib"] 54 | 55 | [crt.libs.symlinks] 56 | "x86_64/msvcrt.lib" = ["MSVCRT.lib"] 57 | 58 | [sdk.headers] 59 | filter = [ 60 | "shared/WTypesbase.h", 61 | "shared/apiset.h", 62 | "shared/apisetcconv.h", 63 | "shared/basetsd.h", 64 | "shared/bcrypt.h", 65 | "shared/cderr.h", 66 | "shared/driverspecs.h", 67 | "shared/guiddef.h", 68 | "shared/inaddr.h", 69 | "shared/kernelspecs.h", 70 | "shared/ktmtypes.h", 71 | "shared/minwindef.h", 72 | "shared/poppack.h", 73 | "shared/pshpack1.h", 74 | "shared/pshpack2.h", 75 | "shared/pshpack4.h", 76 | "shared/pshpack8.h", 77 | "shared/rpc.h", 78 | "shared/rpcasync.h", 79 | "shared/rpcdce.h", 80 | "shared/rpcdcep.h", 81 | "shared/rpcndr.h", 82 | "shared/rpcnterr.h", 83 | "shared/rpcsal.h", 84 | "shared/sdkddkver.h", 85 | "shared/sdv_driverspecs.h", 86 | "shared/specstrings.h", 87 | "shared/specstrings_strict.h", 88 | "shared/specstrings_undef.h", 89 | "shared/stralign.h", 90 | "shared/tvout.h", 91 | "shared/winapifamily.h", 92 | "shared/windef.h", 93 | "shared/winerror.h", 94 | "shared/winpackagefamily.h", 95 | "shared/winsmcrd.h", 96 | "shared/wnnc.h", 97 | "shared/wtypes.h", 98 | "ucrt/assert.h", 99 | "ucrt/corecrt.h", 100 | "ucrt/corecrt_malloc.h", 101 | "ucrt/corecrt_memcpy_s.h", 102 | "ucrt/corecrt_memory.h", 103 | "ucrt/corecrt_search.h", 104 | "ucrt/corecrt_share.h", 105 | "ucrt/corecrt_stdio_config.h", 106 | "ucrt/corecrt_terminate.h", 107 | "ucrt/corecrt_wconio.h", 108 | "ucrt/corecrt_wctype.h", 109 | "ucrt/corecrt_wdirect.h", 110 | "ucrt/corecrt_wio.h", 111 | "ucrt/corecrt_wprocess.h", 112 | "ucrt/corecrt_wstdio.h", 113 | "ucrt/corecrt_wstdlib.h", 114 | "ucrt/corecrt_wstring.h", 115 | "ucrt/corecrt_wtime.h", 116 | "ucrt/crtdbg.h", 117 | "ucrt/ctype.h", 118 | "ucrt/errno.h", 119 | "ucrt/float.h", 120 | "ucrt/inttypes.h", 121 | "ucrt/malloc.h", 122 | "ucrt/math.h", 123 | "ucrt/stdio.h", 124 | "ucrt/stdlib.h", 125 | "ucrt/string.h", 126 | "ucrt/sys/stat.h", 127 | "ucrt/sys/types.h", 128 | "ucrt/wchar.h", 129 | "um/DbgHelp.h", 130 | "um/PropIdl.h", 131 | "um/PropIdlBase.h", 132 | "um/Unknwn.h", 133 | "um/Unknwnbase.h", 134 | "um/WinBase.h", 135 | "um/WinNls.h", 136 | "um/WinUser.h", 137 | "um/Windows.h", 138 | "um/apiquery2.h", 139 | "um/cguid.h", 140 | "um/combaseapi.h", 141 | "um/coml2api.h", 142 | "um/commdlg.h", 143 | "um/consoleapi.h", 144 | "um/consoleapi2.h", 145 | "um/consoleapi3.h", 146 | "um/datetimeapi.h", 147 | "um/dde.h", 148 | "um/ddeml.h", 149 | "um/debugapi.h", 150 | "um/dlgs.h", 151 | "um/dpapi.h", 152 | "um/enclaveapi.h", 153 | "um/errhandlingapi.h", 154 | "um/fibersapi.h", 155 | "um/fileapi.h", 156 | "um/fileapifromapp.h", 157 | "um/handleapi.h", 158 | "um/heapapi.h", 159 | "um/ime_cmodes.h", 160 | "um/imm.h", 161 | "um/interlockedapi.h", 162 | "um/ioapiset.h", 163 | "um/jobapi.h", 164 | "um/jobapi2.h", 165 | "um/joystickapi.h", 166 | "um/libloaderapi.h", 167 | "um/lzexpand.h", 168 | "um/mciapi.h", 169 | "um/mcx.h", 170 | "um/memoryapi.h", 171 | "um/minidumpapiset.h", 172 | "um/minwinbase.h", 173 | "um/mmeapi.h", 174 | "um/mmiscapi.h", 175 | "um/mmiscapi2.h", 176 | "um/mmsyscom.h", 177 | "um/mmsystem.h", 178 | "um/msxml.h", 179 | "um/namedpipeapi.h", 180 | "um/namespaceapi.h", 181 | "um/nb30.h", 182 | "um/ncrypt.h", 183 | "um/oaidl.h", 184 | "um/objbase.h", 185 | "um/objidl.h", 186 | "um/objidlbase.h", 187 | "um/ole2.h", 188 | "um/oleauto.h", 189 | "um/oleidl.h", 190 | "um/playsoundapi.h", 191 | "um/processenv.h", 192 | "um/processthreadsapi.h", 193 | "um/processtopologyapi.h", 194 | "um/profileapi.h", 195 | "um/prsht.h", 196 | "um/realtimeapiset.h", 197 | "um/reason.h", 198 | "um/rpcnsi.h", 199 | "um/rpcnsip.h", 200 | "um/securityappcontainer.h", 201 | "um/securitybaseapi.h", 202 | "um/servprov.h", 203 | "um/shellapi.h", 204 | "um/stringapiset.h", 205 | "um/synchapi.h", 206 | "um/sysinfoapi.h", 207 | "um/systemtopologyapi.h", 208 | "um/threadpoolapiset.h", 209 | "um/threadpoollegacyapiset.h", 210 | "um/timeapi.h", 211 | "um/timezoneapi.h", 212 | "um/urlmon.h", 213 | "um/utilapiset.h", 214 | "um/verrsrc.h", 215 | "um/wincon.h", 216 | "um/wincontypes.h", 217 | "um/wincrypt.h", 218 | "um/winefs.h", 219 | "um/wingdi.h", 220 | "um/winioctl.h", 221 | "um/winnetwk.h", 222 | "um/winnt.h", 223 | "um/winperf.h", 224 | "um/winreg.h", 225 | "um/winscard.h", 226 | "um/winsock.h", 227 | "um/winspool.h", 228 | "um/winsvc.h", 229 | "um/winver.h", 230 | "um/wow64apiset.h", 231 | ] 232 | 233 | [sdk.headers.symlinks] 234 | "shared/WTypesbase.h" = ["wtypesbase.h"] 235 | "shared/driverspecs.h" = ["DriverSpecs.h"] 236 | "shared/specstrings.h" = ["SpecStrings.h"] 237 | "um/DbgHelp.h" = ["dbghelp.h"] 238 | "um/PropIdl.h" = ["propidl.h"] 239 | "um/PropIdlBase.h" = ["propidlbase.h"] 240 | "um/Unknwn.h" = ["unknwn.h"] 241 | "um/Unknwnbase.h" = ["unknwnbase.h"] 242 | "um/WinBase.h" = ["winbase.h"] 243 | "um/WinNls.h" = ["winnls.h"] 244 | "um/WinUser.h" = ["winuser.h"] 245 | "um/Windows.h" = ["windows.h"] 246 | 247 | [sdk.libs] 248 | filter = [ 249 | "ucrt/x86_64/ucrt.lib", 250 | "um/x86_64/AdvAPI32.Lib", 251 | "um/x86_64/UserEnv.Lib", 252 | "um/x86_64/WS2_32.Lib", 253 | "um/x86_64/bcrypt.lib", 254 | "um/x86_64/kernel32.Lib", 255 | "um/x86_64/ntdll.lib", 256 | ] 257 | 258 | [sdk.libs.symlinks] 259 | "um/x86_64/AdvAPI32.Lib" = ["ADVAPI32.lib", "advapi32.lib"] 260 | "um/x86_64/UserEnv.Lib" = ["USERENV.lib", "userenv.lib"] 261 | "um/x86_64/WS2_32.Lib" = ["WS2_32.lib", "ws2_32.lib"] 262 | "um/x86_64/bcrypt.lib" = ["BCRYPT.lib"] 263 | "um/x86_64/kernel32.Lib" = ["KERNEL32.lib", "Kernel32.lib", "kernel32.lib"] 264 | "um/x86_64/ntdll.lib" = ["NTDLL.lib"] 265 | -------------------------------------------------------------------------------- /tests/snapshots/compiles__verify_compiles_minimized.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: tests/compiles.rs 3 | expression: "std::fs::read_to_string(map_path).unwrap()" 4 | --- 5 | [crt.headers] 6 | filter = [ 7 | "__msvc_iter_core.hpp", 8 | "__msvc_sanitizer_annotate_container.hpp", 9 | "algorithm", 10 | "cassert", 11 | "cctype", 12 | "cfloat", 13 | "climits", 14 | "concurrencysal.h", 15 | "crtdefs.h", 16 | "cstddef", 17 | "cstdint", 18 | "cstdio", 19 | "cstdlib", 20 | "cstring", 21 | "cwchar", 22 | "eh.h", 23 | "exception", 24 | "excpt.h", 25 | "initializer_list", 26 | "iosfwd", 27 | "isa_availability.h", 28 | "limits", 29 | "limits.h", 30 | "list", 31 | "new", 32 | "sal.h", 33 | "setjmp.h", 34 | "stdint.h", 35 | "string", 36 | "type_traits", 37 | "use_ansi.h", 38 | "utility", 39 | "vadefs.h", 40 | "vcruntime.h", 41 | "vcruntime_exception.h", 42 | "vcruntime_new.h", 43 | "vcruntime_new_debug.h", 44 | "vcruntime_string.h", 45 | "vector", 46 | "xatomic.h", 47 | "xkeycheck.h", 48 | "xmemory", 49 | "xstring", 50 | "xtr1common", 51 | "xutility", 52 | "yvals.h", 53 | "yvals_core.h", 54 | ] 55 | 56 | [crt.libs] 57 | filter = [ 58 | "x86_64/msvcrt.lib", 59 | "x86_64/vcruntime.lib", 60 | ] 61 | 62 | [crt.libs.symlinks] 63 | "x86_64/msvcrt.lib" = ["MSVCRT.lib"] 64 | 65 | [sdk.headers] 66 | filter = [ 67 | "shared/WTypesbase.h", 68 | "shared/apiset.h", 69 | "shared/apisetcconv.h", 70 | "shared/basetsd.h", 71 | "shared/bcrypt.h", 72 | "shared/cderr.h", 73 | "shared/driverspecs.h", 74 | "shared/guiddef.h", 75 | "shared/inaddr.h", 76 | "shared/kernelspecs.h", 77 | "shared/ktmtypes.h", 78 | "shared/minwindef.h", 79 | "shared/poppack.h", 80 | "shared/pshpack1.h", 81 | "shared/pshpack2.h", 82 | "shared/pshpack4.h", 83 | "shared/pshpack8.h", 84 | "shared/rpc.h", 85 | "shared/rpcasync.h", 86 | "shared/rpcdce.h", 87 | "shared/rpcdcep.h", 88 | "shared/rpcndr.h", 89 | "shared/rpcnterr.h", 90 | "shared/rpcsal.h", 91 | "shared/sdkddkver.h", 92 | "shared/sdv_driverspecs.h", 93 | "shared/specstrings.h", 94 | "shared/specstrings_strict.h", 95 | "shared/specstrings_undef.h", 96 | "shared/stralign.h", 97 | "shared/tvout.h", 98 | "shared/winapifamily.h", 99 | "shared/windef.h", 100 | "shared/winerror.h", 101 | "shared/winpackagefamily.h", 102 | "shared/winsmcrd.h", 103 | "shared/wnnc.h", 104 | "shared/wtypes.h", 105 | "ucrt/assert.h", 106 | "ucrt/corecrt.h", 107 | "ucrt/corecrt_malloc.h", 108 | "ucrt/corecrt_memcpy_s.h", 109 | "ucrt/corecrt_memory.h", 110 | "ucrt/corecrt_search.h", 111 | "ucrt/corecrt_share.h", 112 | "ucrt/corecrt_stdio_config.h", 113 | "ucrt/corecrt_terminate.h", 114 | "ucrt/corecrt_wconio.h", 115 | "ucrt/corecrt_wctype.h", 116 | "ucrt/corecrt_wdirect.h", 117 | "ucrt/corecrt_wio.h", 118 | "ucrt/corecrt_wprocess.h", 119 | "ucrt/corecrt_wstdio.h", 120 | "ucrt/corecrt_wstdlib.h", 121 | "ucrt/corecrt_wstring.h", 122 | "ucrt/corecrt_wtime.h", 123 | "ucrt/crtdbg.h", 124 | "ucrt/ctype.h", 125 | "ucrt/errno.h", 126 | "ucrt/float.h", 127 | "ucrt/inttypes.h", 128 | "ucrt/malloc.h", 129 | "ucrt/math.h", 130 | "ucrt/stdio.h", 131 | "ucrt/stdlib.h", 132 | "ucrt/string.h", 133 | "ucrt/sys/stat.h", 134 | "ucrt/sys/types.h", 135 | "ucrt/wchar.h", 136 | "um/DbgHelp.h", 137 | "um/PropIdl.h", 138 | "um/PropIdlBase.h", 139 | "um/Unknwn.h", 140 | "um/Unknwnbase.h", 141 | "um/WinBase.h", 142 | "um/WinNls.h", 143 | "um/WinUser.h", 144 | "um/Windows.h", 145 | "um/apiquery2.h", 146 | "um/cguid.h", 147 | "um/combaseapi.h", 148 | "um/coml2api.h", 149 | "um/commdlg.h", 150 | "um/consoleapi.h", 151 | "um/consoleapi2.h", 152 | "um/consoleapi3.h", 153 | "um/datetimeapi.h", 154 | "um/dde.h", 155 | "um/ddeml.h", 156 | "um/debugapi.h", 157 | "um/dlgs.h", 158 | "um/dpapi.h", 159 | "um/enclaveapi.h", 160 | "um/errhandlingapi.h", 161 | "um/fibersapi.h", 162 | "um/fileapi.h", 163 | "um/fileapifromapp.h", 164 | "um/handleapi.h", 165 | "um/heapapi.h", 166 | "um/ime_cmodes.h", 167 | "um/imm.h", 168 | "um/interlockedapi.h", 169 | "um/ioapiset.h", 170 | "um/jobapi.h", 171 | "um/jobapi2.h", 172 | "um/joystickapi.h", 173 | "um/libloaderapi.h", 174 | "um/lzexpand.h", 175 | "um/mciapi.h", 176 | "um/mcx.h", 177 | "um/memoryapi.h", 178 | "um/minidumpapiset.h", 179 | "um/minwinbase.h", 180 | "um/mmeapi.h", 181 | "um/mmiscapi.h", 182 | "um/mmiscapi2.h", 183 | "um/mmsyscom.h", 184 | "um/mmsystem.h", 185 | "um/msxml.h", 186 | "um/namedpipeapi.h", 187 | "um/namespaceapi.h", 188 | "um/nb30.h", 189 | "um/ncrypt.h", 190 | "um/oaidl.h", 191 | "um/objbase.h", 192 | "um/objidl.h", 193 | "um/objidlbase.h", 194 | "um/ole2.h", 195 | "um/oleauto.h", 196 | "um/oleidl.h", 197 | "um/playsoundapi.h", 198 | "um/processenv.h", 199 | "um/processthreadsapi.h", 200 | "um/processtopologyapi.h", 201 | "um/profileapi.h", 202 | "um/prsht.h", 203 | "um/realtimeapiset.h", 204 | "um/reason.h", 205 | "um/rpcnsi.h", 206 | "um/rpcnsip.h", 207 | "um/securityappcontainer.h", 208 | "um/securitybaseapi.h", 209 | "um/servprov.h", 210 | "um/shellapi.h", 211 | "um/stringapiset.h", 212 | "um/synchapi.h", 213 | "um/sysinfoapi.h", 214 | "um/systemtopologyapi.h", 215 | "um/threadpoolapiset.h", 216 | "um/threadpoollegacyapiset.h", 217 | "um/timeapi.h", 218 | "um/timezoneapi.h", 219 | "um/urlmon.h", 220 | "um/utilapiset.h", 221 | "um/verrsrc.h", 222 | "um/wincon.h", 223 | "um/wincontypes.h", 224 | "um/wincrypt.h", 225 | "um/winefs.h", 226 | "um/wingdi.h", 227 | "um/winioctl.h", 228 | "um/winnetwk.h", 229 | "um/winnt.h", 230 | "um/winperf.h", 231 | "um/winreg.h", 232 | "um/winscard.h", 233 | "um/winsock.h", 234 | "um/winspool.h", 235 | "um/winsvc.h", 236 | "um/winver.h", 237 | "um/wow64apiset.h", 238 | ] 239 | 240 | [sdk.headers.symlinks] 241 | "shared/WTypesbase.h" = ["wtypesbase.h"] 242 | "shared/driverspecs.h" = ["DriverSpecs.h"] 243 | "shared/specstrings.h" = ["SpecStrings.h"] 244 | "um/DbgHelp.h" = ["dbghelp.h"] 245 | "um/PropIdl.h" = ["propidl.h"] 246 | "um/PropIdlBase.h" = ["propidlbase.h"] 247 | "um/Unknwn.h" = ["unknwn.h"] 248 | "um/Unknwnbase.h" = ["unknwnbase.h"] 249 | "um/WinBase.h" = ["winbase.h"] 250 | "um/WinNls.h" = ["winnls.h"] 251 | "um/WinUser.h" = ["winuser.h"] 252 | "um/Windows.h" = ["windows.h"] 253 | 254 | [sdk.libs] 255 | filter = [ 256 | "ucrt/x86_64/ucrt.lib", 257 | "um/x86_64/AdvAPI32.Lib", 258 | "um/x86_64/UserEnv.Lib", 259 | "um/x86_64/WS2_32.Lib", 260 | "um/x86_64/bcrypt.lib", 261 | "um/x86_64/kernel32.Lib", 262 | "um/x86_64/ntdll.lib", 263 | ] 264 | 265 | [sdk.libs.symlinks] 266 | "um/x86_64/AdvAPI32.Lib" = [ 267 | "ADVAPI32.lib", 268 | "advapi32.lib", 269 | ] 270 | "um/x86_64/UserEnv.Lib" = [ 271 | "USERENV.lib", 272 | "userenv.lib", 273 | ] 274 | "um/x86_64/WS2_32.Lib" = [ 275 | "WS2_32.lib", 276 | "ws2_32.lib", 277 | ] 278 | "um/x86_64/bcrypt.lib" = ["BCRYPT.lib"] 279 | "um/x86_64/kernel32.Lib" = [ 280 | "KERNEL32.lib", 281 | "Kernel32.lib", 282 | "kernel32.lib", 283 | ] 284 | "um/x86_64/ntdll.lib" = ["NTDLL.lib"] 285 | 286 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | # `xwin` 6 | 7 | **A utility for downloading and packaging the [Microsoft CRT](https://docs.microsoft.com/en-us/cpp/c-runtime-library/crt-library-features?redirectedfrom=MSDN&view=msvc-160) headers and libraries, and [Windows SDK](https://en.wikipedia.org/wiki/Microsoft_Windows_SDK) headers and libraries needed for compiling and linking programs targeting Windows.** 8 | 9 | [![Crates.io](https://img.shields.io/crates/v/xwin.svg)](https://crates.io/crates/xwin) 10 | [![Docs](https://docs.rs/xwin/badge.svg)](https://docs.rs/xwin) 11 | [![dependency status](https://deps.rs/repo/github/Jake-Shadle/xwin/status.svg)](https://deps.rs/repo/github/Jake-Shadle/xwin) 12 | [![Build status](https://github.com/Jake-Shadle/xwin/workflows/CI/badge.svg)](https://github.com/Jake-Shadle/xwin/actions) 13 | 14 |
15 | 16 | ## Introduction 17 | 18 | The goal of this project is to create a root directory for both the CRT and Windows SDK that each contain all of the necessary includes and libraries needed for an application to compile and link from a non-Windows platform, using a native cross compiling toolchain like clang/LLVM. This includes adding symlinks to correct numerous casing issues in the Windows SDK so that the files generated by this program can function on a case-sensitive file system. 19 | 20 | See this [blog post](https://jake-shadle.github.io/xwin/) for an in depth walk-through of how xwin can be used. 21 | 22 | ## Installation 23 | 24 | ### From source 25 | 26 | `cargo install xwin --locked` 27 | 28 | #### Features 29 | 30 | `xwin` provides two feature toggles used to decide which TLS implementation to use 31 | 32 | * `rustls` (default) - Uses [`rustls`](https://github.com/rustls/rustls) for TLS 33 | * `native-tls` - Uses [`native-tls`](https://github.com/sfackler/rust-native-tls) for TLS. Note that on platforms where OpenSSL is used it is always built from source. 34 | 35 | ### From tarball 36 | 37 | You can download a prebuilt binary from the [Releases](https://github.com/Jake-Shadle/xwin/releases). 38 | 39 | * `x86_64-unknown-linux-musl` 40 | * `x86_64-apple-darwin` 41 | * `aarch64-apple-darwin` 42 | 43 | ## Usage 44 | 45 | ### Common 46 | 47 | * `--accept-license` - Doesn't display the prompt to accept the license. You can also set the `XWIN_ACCEPT_LICENSE=1` environment variable 48 | * `--arch ` - The architectures to include [default: `x86_64`] [possible values: `x86`, `x86_64`, `aarch`, `aarch64`]. Note that I haven't fully tested aarch/64 nor x86 so there _might_ be issues with them, please file an issue if you encounter problems with them. 49 | * `--cache-dir ` - Specifies the cache directory used to persist downloaded items to disk. Defaults to `./.xwin-cache` if not specified. 50 | * `-L, --log-level ` - The log level for messages, only log messages at or above the level will be emitted [default: info] [possible values: off, error, warn, info, debug, trace]. 51 | * `--variant ...` - The variants to include [default: desktop] [possible values: desktop, onecore, spectre]. Note that I haven't fully tested any variant except `desktop`, please file an issue if you try to use one of the others and run into issues. Note that there is another `store` variant that hasn't even been implemented due to it being weird and me not having a real project targeting it. 52 | * `--channel ` - The product channel to use [default: release] 53 | * `--manifest-version ` - The manifest version to retrieve [default: 17]. 54 | * `--manifest` - Specifies a top level manifest to use, rather than downloading it from Microsoft. This can be used to ensure the output is reproducible. 55 | * `--sdk-version` - The specific SDK version to use. If not specified the latest SDK version in the manifest is used. 56 | * `--crt-version` - The specific CRT version to use. If not specified the latest CRT version in the manifest is used. 57 | * `--timeout` - Specifies a timeout for long a single HTTP get request is allowed to take. The default is 60s. 58 | 59 | ### Env vars 60 | 61 | * `https_proxy` - Environment variable that specifies the HTTPS proxy to use. 62 | 63 | ### `xwin download` 64 | 65 | This downloads the top level manifest and any vsix, msi, or cab files that are needed that aren't already in the download cache. 66 | 67 | ### `xwin unpack` 68 | 69 | Decompresses all of the downloaded package contents to disk. `download` is run automatically. 70 | 71 | ### `xwin splat` 72 | 73 | Fixes the packages to prune unneeded files and adds symlinks to address file casing issues and then splats the final artifacts into directories. This is the main command you will want to run as it also `download`s and `unpack`s automatically, providing the desired headers at the path specified to `--output` (`./.xwin-cache/splat`). 74 | 75 | #### Splat options 76 | 77 | * `--copy` - Copies files from the unpack directory to the splat directory instead of moving them, which preserves the original unpack directories but increases overall execution time and disk usage. 78 | * `--disable-symlinks` - By default, symlinks are added to both the CRT and `WindowsSDK` to address casing issues in general usage. For example, if you are compiling C/C++ code that does `#include `, it will break on a case-sensitive file system, as the actual path in the `WindowsSDK` is `Windows.h`. This also applies even if the C/C++ you are compiling uses correct casing for all CRT/SDK includes, as the internal headers also use incorrect casing in most cases 79 | * `--include-debug-libs` - The MSVCRT includes (non-redistributable) debug versions of the various libs that are generally uninteresting to keep for most usage 80 | * `--include-debug-symbols` - The MSVCRT includes PDB (debug symbols) files for several of the libraries that are generally uninteresting to keep for most usage 81 | * `--preserve-ms-arch-notation` - By default, we convert the MS specific `x64`, `arm`, and `arm64` target architectures to the more canonical `x86_64`, `aarch`, and `aarch64` of LLVM etc when creating directories/names. Passing this flag will preserve the MS names for those targets 82 | * `--use-winsysroot-style` - Use the /winsysroot layout, so that clang-cl's /winsysroot flag can be used with the output, rather than needing both -vctoolsdir and -winsdkdir. You will likely also want to use --preserve-ms-arch-notation and --disable-symlinks for use with clang-cl on Windows. 83 | * `--output` - The root output directory. Defaults to `./.xwin-cache/splat` if not specified 84 | * `--map` - An optional [map](#map-file) file used to configure what files are splatted, and any additional symlinks to create. 85 | 86 | This moves all of the unpacked files which aren't pruned to their canonical locations under a root directory, for example here is what an `x86_64` `Desktop` splat looks like. `unpack` is run automatically as needed. 87 | 88 | ```txt 89 | .xwin-cache/splat 90 | ├── crt 91 | │ ├── include 92 | │ │ ├── cliext 93 | │ │ ├── CodeAnalysis 94 | │ │ ├── cvt 95 | │ │ ├── experimental 96 | │ │ ├── Manifest 97 | │ │ └── msclr 98 | │ │ └── com 99 | │ └── lib 100 | │ └── x86_64 101 | └── sdk 102 | ├── include 103 | │ ├── cppwinrt 104 | │ │ └── winrt 105 | │ │ └── impl 106 | │ ├── shared 107 | │ │ ├── ndis 108 | │ │ └── netcx 109 | │ │ └── shared 110 | │ │ └── net 111 | │ │ └── wifi 112 | │ ├── ucrt 113 | │ │ └── sys 114 | │ ├── um 115 | │ │ ├── alljoyn_c 116 | │ │ ├── gl 117 | │ │ ├── qcc 118 | │ │ │ └── windows 119 | │ │ └── winsqlite 120 | │ └── winrt 121 | │ └── wrl 122 | │ └── wrappers 123 | └── lib 124 | ├── ucrt 125 | │ └── x86_64 126 | └── um 127 | └── x86_64 128 | ``` 129 | 130 | ### `xwin minimize` 131 | 132 | This is an advanced command that performs a `splat` before performing a build on a cargo manifest using strace to capture all of the headers and libraries that are used throughout the build and dumping them to a [map](#map-file). This command can also output the final splat to disk, or the map file can be used with `splat` to only splat the files and symlinks described in it. 133 | 134 | Note that currently the build is always done with the `/vctoolsdir` and `/winsdkdir` options, so it is expected these are the same options used when compiling C/C++ code in your normal environment. If that is not the case please open an issue. 135 | 136 | At the end of the command, a printout of the amount and size of the original versus minimized files is done, eg. 137 | 138 | ```txt 139 | crt headers: 73(2.6MiB) / 384(18.4MiB) => 14.00% 140 | crt libs: 5(28.0MiB) / 26(81.1MiB) => 34.58% 141 | sdk headers: 180(9.6MiB) / 4435(304.7MiB) => 3.15% 142 | sdk libs: 29(69.8MiB) / 456(169.9MiB) => 41.06% 143 | ``` 144 | 145 | #### Requirements 146 | 147 | * Linux host - This _might_ work on other platforms, but it's not guaranteed, nor tested 148 | * `cargo` - This is the singular supported build tool. 149 | * `-pc-windows-msvc` - The target you are building for needs to be installed (eg. via `rustup target add`) 150 | * `clang-cl` - This is used as the C/C++ compiler 151 | * `llvm-lib` - This is used as the archiver 152 | * `lld-link` - This is used as the linker 153 | * `strace` - This is used to capture the syscalls made by the lld and clang compiler 154 | 155 | #### Minimize options 156 | 157 | Note all of the [splat options](#splat-options) also apply to minimize. 158 | 159 | * `--map` - The path to the [map](#map-file) to output the minimized results to. Default to `./.xwin-cache/xwin-map.toml` if not specified. 160 | * `--minimize-output` - The root directory where only the minimized files are splatted to. If not specified only the `--map` file is written in addition to the normal splat 161 | * `--preserve-strace` - By default the `strace` output is written to disk in a temporary location that is deleted once the build is finished, passing this option allows it to be persisted. The path is written out before the build starts. 162 | 163 | ## Map file 164 | 165 | As noted in [minimize](#xwin-minimize), there are many restrictions on it to make my life easier, but that make it unsuitable for those who don't use cargo/rust. It's possible for others to come up with their own versions of minimize that can output the same format that `splat` understands to still get the benefits of `xwin` without cargo/rust. 166 | 167 | The format is extremely simple 168 | 169 | ```txt 170 | ├── crt 171 | │ ├── headers 172 | │ │ ├── filter - Array of relative paths to keep 173 | │ │ └── symlinks 174 | │ │ └── - The same path as one of the filters 175 | │ │ └── - Array of symlinks to create in the same directory as the parent path 176 | │ ├── libs * 177 | └── sdk * 178 | ``` 179 | 180 | ### Example 181 | 182 | See [docs/example-map.toml](docs/example-map.toml) for a real world example. 183 | 184 | ## Container 185 | 186 | [xwin.dockerfile](xwin.dockerfile) is an example Dockerfile that can be used a container image capable of building and testing Rust crates targeting `x86_64-pc-windows-msvc`. 187 | 188 | ### Thanks 189 | 190 | Special thanks to for the inspiration and [@mdsteele](https://github.com/mdsteele) for publishing several Rust crates around msi/cab files that were needed in this project 191 | 192 | ### License 193 | 194 | This contribution is dual licensed under EITHER OF 195 | 196 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or ) 197 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or ) 198 | 199 | at your option. 200 | -------------------------------------------------------------------------------- /tests/compiles.rs: -------------------------------------------------------------------------------- 1 | #[test] 2 | fn verify_compiles() { 3 | let ctx = xwin::Ctx::with_dir( 4 | xwin::PathBuf::from(".xwin-cache/compile-test"), 5 | xwin::util::ProgressTarget::Hidden, 6 | ureq::agent(), 7 | 0, 8 | ) 9 | .unwrap(); 10 | 11 | let ctx = std::sync::Arc::new(ctx); 12 | 13 | let hidden = indicatif::ProgressBar::hidden(); 14 | 15 | // TODO: Bump to CI to 17 once github actions isn't using an ancient version, 16 | // we could install in the action run, but not really worth it since I can 17 | // test locally 18 | let manifest_version = if std::env::var_os("CI").is_some() { 19 | "16" 20 | } else { 21 | "17" 22 | }; 23 | 24 | let manifest = 25 | xwin::manifest::get_manifest(&ctx, manifest_version, "release", hidden.clone()).unwrap(); 26 | let pkg_manifest = 27 | xwin::manifest::get_package_manifest(&ctx, &manifest, hidden.clone()).unwrap(); 28 | 29 | let pruned = xwin::prune_pkg_list( 30 | &pkg_manifest, 31 | xwin::Arch::X86_64 as u32, 32 | xwin::Variant::Desktop as u32, 33 | false, 34 | false, 35 | None, 36 | None, 37 | ) 38 | .unwrap(); 39 | 40 | #[derive(Debug)] 41 | enum Style { 42 | Default, 43 | WinSysRoot, 44 | } 45 | 46 | for style in [Style::Default, Style::WinSysRoot] { 47 | let output_dir = ctx.work_dir.join(format!("{style:?}")); 48 | if !output_dir.exists() { 49 | std::fs::create_dir_all(&output_dir).unwrap(); 50 | } 51 | 52 | if !cfg!(target_os = "windows") && matches!(style, Style::WinSysRoot) { 53 | continue; 54 | } 55 | 56 | let op = xwin::Ops::Splat(xwin::SplatConfig { 57 | include_debug_libs: false, 58 | include_debug_symbols: false, 59 | enable_symlinks: matches!(style, Style::Default), 60 | preserve_ms_arch_notation: matches!(style, Style::WinSysRoot), 61 | use_winsysroot_style: matches!(style, Style::WinSysRoot), 62 | map: None, 63 | copy: true, 64 | output: output_dir.clone(), 65 | }); 66 | 67 | ctx.clone() 68 | .execute( 69 | pkg_manifest.packages.clone(), 70 | pruned 71 | .payloads 72 | .clone() 73 | .into_iter() 74 | .map(|payload| xwin::WorkItem { 75 | progress: hidden.clone(), 76 | payload: std::sync::Arc::new(payload), 77 | }) 78 | .collect(), 79 | pruned.crt_version.clone(), 80 | pruned.sdk_version.clone(), 81 | pruned.vcr_version.clone(), 82 | xwin::Arch::X86_64 as u32, 83 | xwin::Variant::Desktop as u32, 84 | op, 85 | ) 86 | .unwrap(); 87 | 88 | if xwin::Path::new("tests/xwin-test/target").exists() { 89 | std::fs::remove_dir_all("tests/xwin-test/target").expect("failed to remove target dir"); 90 | } 91 | 92 | let mut cmd = std::process::Command::new("cargo"); 93 | cmd.args([ 94 | "build", 95 | "--target", 96 | "x86_64-pc-windows-msvc", 97 | "--manifest-path", 98 | "tests/xwin-test/Cargo.toml", 99 | ]); 100 | 101 | let od = xwin::util::canonicalize(&output_dir).unwrap(); 102 | 103 | let includes = match style { 104 | Style::Default => { 105 | cmd.env("RUSTFLAGS", format!("-C linker=lld-link -Lnative={od}/crt/lib/x86_64 -Lnative={od}/sdk/lib/um/x86_64 -Lnative={od}/sdk/lib/ucrt/x86_64")); 106 | format!( 107 | "-Wno-unused-command-line-argument -fuse-ld=lld-link /imsvc{od}/crt/include /imsvc{od}/sdk/include/ucrt /imsvc{od}/sdk/include/um /imsvc{od}/sdk/include/shared" 108 | ) 109 | } 110 | Style::WinSysRoot => { 111 | const SEP: char = '\x1F'; 112 | cmd.env("CARGO_ENCODED_RUSTFLAGS", format!("-C{SEP}linker=lld-link{SEP}-Lnative={od}/VC/Tools/MSVC/{crt_version}/Lib/x64{SEP}-Lnative={od}/Windows Kits/10/Lib/{sdk_version}/um/x64{SEP}-Lnative={od}/Windows Kits/10/Lib/{sdk_version}/ucrt/x64", crt_version = &pruned.crt_version, sdk_version = &pruned.sdk_version)); 113 | 114 | format!("-Wno-unused-command-line-argument -fuse-ld=lld-link /winsysroot {od}") 115 | } 116 | }; 117 | 118 | let cc_env = [ 119 | ("CC_x86_64_pc_windows_msvc", "clang-cl"), 120 | ("CXX_x86_64_pc_windows_msvc", "clang-cl"), 121 | ("AR_x86_64_pc_windows_msvc", "llvm-lib"), 122 | ("CFLAGS_x86_64_pc_windows_msvc", &includes), 123 | ("CXXFLAGS_x86_64_pc_windows_msvc", &includes), 124 | ]; 125 | 126 | cmd.envs(cc_env); 127 | 128 | assert!(cmd.status().unwrap().success()); 129 | 130 | // Ignore the /vctoolsdir /winsdkdir test below on CI since it fails, I'm assuming 131 | // due to the clang version in GHA being outdated, but don't have the will to 132 | // look into it now 133 | if !matches!(style, Style::Default) || std::env::var("CI").is_ok() { 134 | return; 135 | } 136 | 137 | std::fs::remove_dir_all("tests/xwin-test/target").expect("failed to remove target dir"); 138 | 139 | let mut cmd = std::process::Command::new("cargo"); 140 | cmd.args([ 141 | "build", 142 | "--target", 143 | "x86_64-pc-windows-msvc", 144 | "--manifest-path", 145 | "tests/xwin-test/Cargo.toml", 146 | ]); 147 | 148 | let includes = format!( 149 | "-Wno-unused-command-line-argument -fuse-ld=lld-link /vctoolsdir {od}/crt /winsdkdir {od}/sdk" 150 | ); 151 | let libs = format!( 152 | "-C linker=lld-link -Lnative={od}/crt/lib/x86_64 -Lnative={od}/sdk/lib/um/x86_64 -Lnative={od}/sdk/lib/ucrt/x86_64" 153 | ); 154 | 155 | let cc_env = [ 156 | ("CC_x86_64_pc_windows_msvc", "clang-cl"), 157 | ("CXX_x86_64_pc_windows_msvc", "clang-cl"), 158 | ("AR_x86_64_pc_windows_msvc", "llvm-lib"), 159 | ("CFLAGS_x86_64_pc_windows_msvc", &includes), 160 | ("CXXFLAGS_x86_64_pc_windows_msvc", &includes), 161 | ("RUSTFLAGS", &libs), 162 | ]; 163 | 164 | cmd.envs(cc_env); 165 | 166 | assert!(cmd.status().unwrap().success()); 167 | } 168 | } 169 | 170 | #[test] 171 | #[ignore = "very expensive, and conflicts with the test above, only run this in isolation"] 172 | fn verify_compiles_minimized() { 173 | let ctx = xwin::Ctx::with_dir( 174 | xwin::PathBuf::from(".xwin-cache/compile-test-minimized"), 175 | xwin::util::ProgressTarget::Hidden, 176 | ureq::agent(), 177 | 0, 178 | ) 179 | .unwrap(); 180 | 181 | let ctx = std::sync::Arc::new(ctx); 182 | 183 | let hidden = indicatif::ProgressBar::hidden(); 184 | 185 | // TODO: Bump to CI to 17 once github actions isn't using an ancient version, 186 | // we could install in the action run, but not really worth it since I can 187 | // test locally 188 | let manifest_version = if std::env::var_os("CI").is_some() { 189 | "16" 190 | } else { 191 | "17" 192 | }; 193 | 194 | let manifest = 195 | xwin::manifest::get_manifest(&ctx, manifest_version, "release", hidden.clone()).unwrap(); 196 | let pkg_manifest = 197 | xwin::manifest::get_package_manifest(&ctx, &manifest, hidden.clone()).unwrap(); 198 | 199 | let pruned = xwin::prune_pkg_list( 200 | &pkg_manifest, 201 | xwin::Arch::X86_64 as u32, 202 | xwin::Variant::Desktop as u32, 203 | false, 204 | false, 205 | None, 206 | None, 207 | ) 208 | .unwrap(); 209 | 210 | let output_dir = ctx.work_dir.join("splat"); 211 | if !output_dir.exists() { 212 | std::fs::create_dir_all(&output_dir).unwrap(); 213 | } 214 | 215 | let filtered = ctx.work_dir.join("filtered"); 216 | if !filtered.exists() { 217 | std::fs::create_dir_all(&filtered).unwrap(); 218 | } 219 | 220 | let map_path = ctx.work_dir.join("map.toml"); 221 | 222 | let op = xwin::Ops::Minimize(xwin::MinimizeConfig { 223 | include_debug_libs: false, 224 | include_debug_symbols: false, 225 | enable_symlinks: true, 226 | preserve_ms_arch_notation: false, 227 | use_winsysroot_style: false, 228 | map: map_path.clone(), 229 | copy: true, 230 | splat_output: output_dir.clone(), 231 | manifest_path: "tests/xwin-test/Cargo.toml".into(), 232 | target: "x86_64-pc-windows-msvc".into(), 233 | minimize_output: Some(filtered.clone()), 234 | preserve_strace: false, 235 | }); 236 | 237 | ctx.execute( 238 | pkg_manifest.packages, 239 | pruned 240 | .payloads 241 | .into_iter() 242 | .map(|payload| xwin::WorkItem { 243 | progress: hidden.clone(), 244 | payload: std::sync::Arc::new(payload), 245 | }) 246 | .collect(), 247 | pruned.crt_version, 248 | pruned.sdk_version, 249 | pruned.vcr_version, 250 | xwin::Arch::X86_64 as u32, 251 | xwin::Variant::Desktop as u32, 252 | op, 253 | ) 254 | .unwrap(); 255 | 256 | insta::assert_snapshot!(std::fs::read_to_string(map_path).unwrap()); 257 | 258 | if xwin::Path::new("tests/xwin-test/target").exists() { 259 | std::fs::remove_dir_all("tests/xwin-test/target").expect("failed to remove target dir"); 260 | } 261 | 262 | let mut cmd = std::process::Command::new("cargo"); 263 | cmd.args([ 264 | "build", 265 | "--target", 266 | "x86_64-pc-windows-msvc", 267 | "--manifest-path", 268 | "tests/xwin-test/Cargo.toml", 269 | ]); 270 | 271 | let od = xwin::util::canonicalize(&filtered).unwrap(); 272 | 273 | let includes = format!( 274 | "-Wno-unused-command-line-argument -fuse-ld=lld-link /vctoolsdir {od}/crt /winsdkdir {od}/sdk" 275 | ); 276 | let libs = format!( 277 | "-C linker=lld-link -Lnative={od}/crt/lib/x86_64 -Lnative={od}/sdk/lib/um/x86_64 -Lnative={od}/sdk/lib/ucrt/x86_64" 278 | ); 279 | 280 | let cc_env = [ 281 | ("CC_x86_64_pc_windows_msvc", "clang-cl"), 282 | ("CXX_x86_64_pc_windows_msvc", "clang-cl"), 283 | ("AR_x86_64_pc_windows_msvc", "llvm-lib"), 284 | ("CFLAGS_x86_64_pc_windows_msvc", &includes), 285 | ("CXXFLAGS_x86_64_pc_windows_msvc", &includes), 286 | ("RUSTFLAGS", &libs), 287 | ]; 288 | 289 | cmd.envs(cc_env); 290 | 291 | assert!(cmd.status().unwrap().success()); 292 | } 293 | 294 | #[test] 295 | fn verify_compiles_aarch64() { 296 | let ctx = xwin::Ctx::with_dir( 297 | xwin::PathBuf::from(".xwin-cache/compiles-aarch64"), 298 | xwin::util::ProgressTarget::Hidden, 299 | ureq::agent(), 300 | 0, 301 | ) 302 | .unwrap(); 303 | 304 | let ctx = std::sync::Arc::new(ctx); 305 | 306 | let hidden = indicatif::ProgressBar::hidden(); 307 | 308 | // TODO: Bump to CI to 17 once github actions isn't using an ancient version, 309 | // we could install in the action run, but not really worth it since I can 310 | // test locally 311 | let manifest_version = if std::env::var_os("CI").is_some() { 312 | "16" 313 | } else { 314 | "17" 315 | }; 316 | 317 | let manifest = 318 | xwin::manifest::get_manifest(&ctx, manifest_version, "release", hidden.clone()).unwrap(); 319 | let pkg_manifest = 320 | xwin::manifest::get_package_manifest(&ctx, &manifest, hidden.clone()).unwrap(); 321 | 322 | let pruned = xwin::prune_pkg_list( 323 | &pkg_manifest, 324 | xwin::Arch::Aarch64 as u32, 325 | xwin::Variant::Desktop as u32, 326 | false, 327 | false, 328 | None, 329 | None, 330 | ) 331 | .unwrap(); 332 | 333 | #[derive(Debug)] 334 | enum Style { 335 | Default, 336 | WinSysRoot, 337 | } 338 | 339 | for style in [Style::Default, Style::WinSysRoot] { 340 | let output_dir = ctx.work_dir.join(format!("{style:?}")); 341 | if !output_dir.exists() { 342 | std::fs::create_dir_all(&output_dir).unwrap(); 343 | } 344 | 345 | if !cfg!(target_os = "windows") && matches!(style, Style::WinSysRoot) { 346 | continue; 347 | } 348 | 349 | let op = xwin::Ops::Splat(xwin::SplatConfig { 350 | include_debug_libs: false, 351 | include_debug_symbols: false, 352 | enable_symlinks: matches!(style, Style::Default), 353 | preserve_ms_arch_notation: matches!(style, Style::WinSysRoot), 354 | use_winsysroot_style: matches!(style, Style::WinSysRoot), 355 | map: None, 356 | copy: true, 357 | output: output_dir.clone(), 358 | }); 359 | 360 | ctx.clone() 361 | .execute( 362 | pkg_manifest.packages.clone(), 363 | pruned 364 | .payloads 365 | .clone() 366 | .into_iter() 367 | .map(|payload| xwin::WorkItem { 368 | progress: hidden.clone(), 369 | payload: std::sync::Arc::new(payload), 370 | }) 371 | .collect(), 372 | pruned.crt_version.clone(), 373 | pruned.sdk_version.clone(), 374 | pruned.vcr_version.clone(), 375 | xwin::Arch::Aarch64 as u32, 376 | xwin::Variant::Desktop as u32, 377 | op, 378 | ) 379 | .unwrap(); 380 | 381 | if xwin::Path::new("tests/xwin-test/target").exists() { 382 | std::fs::remove_dir_all("tests/xwin-test/target").expect("failed to remove target dir"); 383 | } 384 | 385 | let mut cmd = std::process::Command::new("cargo"); 386 | cmd.args([ 387 | "build", 388 | "--target", 389 | "aarch64-pc-windows-msvc", 390 | "--manifest-path", 391 | "tests/xwin-test/Cargo.toml", 392 | ]); 393 | 394 | let od = xwin::util::canonicalize(&output_dir).unwrap(); 395 | 396 | let includes = match style { 397 | Style::Default => { 398 | cmd.env("RUSTFLAGS", format!("-C linker=lld-link -Lnative={od}/crt/lib/aarch64 -Lnative={od}/sdk/lib/um/aarch64 -Lnative={od}/sdk/lib/ucrt/aarch64")); 399 | format!( 400 | "-Wno-unused-command-line-argument -fuse-ld=lld-link /imsvc{od}/crt/include /imsvc{od}/sdk/include/ucrt /imsvc{od}/sdk/include/um /imsvc{od}/sdk/include/shared" 401 | ) 402 | } 403 | Style::WinSysRoot => { 404 | const SEP: char = '\x1F'; 405 | cmd.env("CARGO_ENCODED_RUSTFLAGS", format!("-C{SEP}linker=lld-link{SEP}-Lnative={od}/VC/Tools/MSVC/{crt_version}/Lib/x64{SEP}-Lnative={od}/Windows Kits/10/Lib/{sdk_version}/um/x64{SEP}-Lnative={od}/Windows Kits/10/Lib/{sdk_version}/ucrt/x64", crt_version = &pruned.crt_version, sdk_version = &pruned.sdk_version)); 406 | 407 | format!("-Wno-unused-command-line-argument -fuse-ld=lld-link /winsysroot {od}") 408 | } 409 | }; 410 | 411 | let cc_env = [ 412 | ("CC_aarch64_pc_windows_msvc", "clang-cl"), 413 | ("CXX_aarch64_pc_windows_msvc", "clang-cl"), 414 | ("AR_aarch64_pc_windows_msvc", "llvm-lib"), 415 | ("CFLAGS_aarch64_pc_windows_msvc", &includes), 416 | ("CXXFLAGS_aarch64_pc_windows_msvc", &includes), 417 | ]; 418 | 419 | cmd.envs(cc_env); 420 | 421 | assert!(cmd.status().unwrap().success()); 422 | 423 | // Ignore the /vctoolsdir /winsdkdir test below on CI since it fails, I'm assuming 424 | // due to the clang version in GHA being outdated, but don't have the will to 425 | // look into it now 426 | if !matches!(style, Style::Default) || std::env::var("CI").is_ok() { 427 | return; 428 | } 429 | 430 | std::fs::remove_dir_all("tests/xwin-test/target").expect("failed to remove target dir"); 431 | 432 | let mut cmd = std::process::Command::new("cargo"); 433 | cmd.args([ 434 | "build", 435 | "--target", 436 | "aarch64-pc-windows-msvc", 437 | "--manifest-path", 438 | "tests/xwin-test/Cargo.toml", 439 | ]); 440 | 441 | let includes = format!( 442 | "-Wno-unused-command-line-argument -fuse-ld=lld-link /vctoolsdir {od}/crt /winsdkdir {od}/sdk" 443 | ); 444 | let libs = format!( 445 | "-C linker=lld-link -Lnative={od}/crt/lib/aarch64 -Lnative={od}/sdk/lib/um/aarch64 -Lnative={od}/sdk/lib/ucrt/aarch64" 446 | ); 447 | 448 | let cc_env = [ 449 | ("CC_aarch4_pc_windows_msvc", "clang-cl"), 450 | ("CXX_aarch64_pc_windows_msvc", "clang-cl"), 451 | ("AR_aarch64_pc_windows_msvc", "llvm-lib"), 452 | ("CFLAGS_aarch64_pc_windows_msvc", &includes), 453 | ("CXXFLAGS_aarch64_pc_windows_msvc", &includes), 454 | ("RUSTFLAGS", &libs), 455 | ]; 456 | 457 | cmd.envs(cc_env); 458 | 459 | assert!(cmd.status().unwrap().success()); 460 | } 461 | } 462 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Changelog 4 | All notable changes to this project will be documented in this file. 5 | 6 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 7 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 8 | 9 | 10 | ## [Unreleased] - ReleaseDate 11 | ## [0.6.7] - 2025-08-15 12 | ### Fixed 13 | - [PR#160](https://github.com/Jake-Shadle/xwin/pull/160) resolved [#126](https://github.com/Jake-Shadle/xwin/issues/126) by ignoring MSI installers that don't reference any cabinet files. Why do such utterly fucking useless installers exist? Because fuck me I guess. 14 | - [PR#159](https://github.com/Jake-Shadle/xwin/pull/159) fixed an issue where enabling native TLS was not actually doing anything. No one actually reported this not working which makes me think no one is using this feature. :) 15 | 16 | ## [0.6.6] - 2025-06-19 17 | ### Fixed 18 | - [PR#143](https://github.com/Jake-Shadle/xwin/pull/142) is a second attempt to resolve [#141](https://github.com/Jake-Shadle/xwin/issues/141) by switching to a new `3.0.0-rc1` version of ureq that might not have the same issue, as well as adding support for retries for EOF I/O errors seen by users which can be configured via `--http-retry` or `XWIN_HTTP_RETRY`. 19 | 20 | ### Added 21 | - [PR#148](https://github.com/Jake-Shadle/xwin/pull/148) resolved [#81](https://github.com/Jake-Shadle/xwin/issues/81) by adding `--include-debug-runtime` to download the debug runtime binaries that are not provided by wine. Thanks [@superewald](https://github.com/superewald)! 22 | 23 | ## [0.6.5] - 2024-08-21 24 | ### Fixed 25 | - [PR#137](https://github.com/Jake-Shadle/xwin/pull/137) fixes the fix introduced in [PR#136](https://github.com/Jake-Shadle/xwin/pull/136). 26 | 27 | ## [0.6.4] - 2024-08-21 28 | ### Fixed 29 | - [PR#136](https://github.com/Jake-Shadle/xwin/pull/136) fixed an issue introduced in [PR#131](https://github.com/Jake-Shadle/xwin/pull/131) where symlink disabling when a case-insensitive file system was detected was...not being respected. At all. 30 | 31 | ## [0.6.3] - 2024-08-09 32 | ### Fixed 33 | - [PR#134](https://github.com/Jake-Shadle/xwin/pull/134) added back onecoreuap headers that were moved from the main SDK header package in recent versions of the SDK. Thanks [@tomager](https://github.com/tomager)! 34 | 35 | ## [0.6.2] - 2024-07-02 36 | ### Fixed 37 | - [PR#131](https://github.com/Jake-Shadle/xwin/pull/131) resolved [#130](https://github.com/Jake-Shadle/xwin/issues/130) by adding detection of case-insensitive file systems, which then disables symlink creation since it is not needed, and breaks. 38 | 39 | ## [0.6.1] - 2024-06-30 40 | ### Fixed 41 | - [PR#129](https://github.com/Jake-Shadle/xwin/pull/129) fixed [#128](https://github.com/Jake-Shadle/xwin/issues/128) by adding the additional `onecoreuap` MSI package that contains headers that were previously (before SDK 10.0.26100) part of other MSI packages. Thanks [@bigfoodK](https://github.com/bigfoodK)! 42 | 43 | ## [0.6.0] - 2024-06-03 44 | ### Added 45 | - [PR#123](https://github.com/Jake-Shadle/xwin/pull/123) (a rework of [#119](https://github.com/Jake-Shadle/xwin/pull/119)) adds the ability to splat in the format understood by clang-cl `/winsysroot` option. 46 | 47 | ## [0.5.2] - 2024-05-06 48 | ### Changed 49 | - [PR#117](https://github.com/Jake-Shadle/xwin/pull/117) updated a few crates, notably `zip`. 50 | 51 | ## [0.5.1] - 2024-04-02 52 | ### Changed 53 | - [PR#116](https://github.com/Jake-Shadle/xwin/pull/116) (a rework of [#115](https://github.com/Jake-Shadle/xwin/pull/115)) improves the speed of the `x86_64-unknown-linux-musl` binary by using `mimalloc`. 54 | 55 | ## [0.5.0] - 2023-11-13 56 | ### Changed 57 | - [PR#110](https://github.com/Jake-Shadle/xwin/pull/110) changed how `Ctx` is built. It was getting too complicated to support niche use cases, some of which didn't belong in a library (like reading environment variables), so this functionality has been completely removed. Instead, one must pass in a `ureq::Agent` that is fully configured how the user wants it. 58 | - [PR#110](https://github.com/Jake-Shadle/xwin/pull/110) changed the environment variable read to the `xwin` binary instead of the library, as well as its name `https_proxy` -> `HTTPS_PROXY`, and added it to an an option on the command line. 59 | 60 | ## [0.4.1] - 2023-11-09 61 | ### Fixed 62 | - [PR#108](https://github.com/Jake-Shadle/xwin/pull/108) resolved [#107](https://github.com/Jake-Shadle/xwin/issues/107) by fixing the Window symlink code added in [PR#105](https://github.com/Jake-Shadle/xwin/pull/105) and only using it in the two cases it was needed. 63 | 64 | ## [0.4.0] - 2023-11-07 65 | ### Added 66 | - [PR#101](https://github.com/Jake-Shadle/xwin/pull/101) resolved [#28](https://github.com/Jake-Shadle/xwin/issues/28), [#84](https://github.com/Jake-Shadle/xwin/issues/84), and [#85](https://github.com/Jake-Shadle/xwin/issues/85) by adding a `minimize` command that straces a cargo build to write a `map` file that can be used by a `splat` command to only splat the headers and libraries actually needed to build, drastically reducing the splat output (eg. 1.3GiB -> 101MiB). This `map` file also allows the creation of symlinks on a per-file basis, allowing users to create their own symlinks if needed. 67 | - [PR#104](https://github.com/Jake-Shadle/xwin/pull/104) resolved [#103](https://github.com/Jake-Shadle/xwin/issues/103) by allowing custom certificates to be specified via the `SSL_CERT_FILE`, `CURL_CA_BUNDLE`, or `REQUESTS_CA_BUNDLE` environment variables. `xwin` must be compiled with the `native-tls` feature for this to function. Thanks [@Owen-CH-Leung](https://github.com/Owen-CH-Leung)! 68 | - [PR#105](https://github.com/Jake-Shadle/xwin/pull/105) supplanted [#100](https://github.com/Jake-Shadle/xwin/pull/100), allowing creation of symlinks on a Windows host. Thanks [@sykhro](https://github.com/sykhro)! 69 | 70 | ## [0.3.1] - 2023-09-12 71 | ### Changed 72 | - [PR#99](https://github.com/Jake-Shadle/xwin/pull/99) changed the default VS manifest version from 16 -> 17. You can preserve the old behavior by passing `--manifest-version 16` on the cmd line. 73 | 74 | ### Fixed 75 | - [PR#99](https://github.com/Jake-Shadle/xwin/pull/99) resolved [#92](https://github.com/Jake-Shadle/xwin/issues/92) by only failing if matching relative paths didn't have the same contents. This currently only applies to one file, `appnotify.h`, which is present in the SDK headers and Store headers. 76 | 77 | ## [0.3.0] - 2023-09-12 78 | ### Changed 79 | - [PR#93](https://github.com/Jake-Shadle/xwin/pull/93) added the ability to specify a download timeout for each individual download, and changed the default from infinite to 60 seconds, so that xwin will error if the remote HTTP server is slow/unresponsive. Thanks [@dragonmux](https://github.com/dragonmux)! 80 | 81 | ## [0.2.15] - 2023-09-11 82 | ### Changed 83 | - [PR#93](https://github.com/Jake-Shadle/xwin/pull/93) added the ability to specify a download timeout for each individual download, and changed the default from infinite to 60 seconds, so that xwin will error if the remote HTTP server is slow/unresponsive. Thanks [@dragonmux](https://github.com/dragonmux)! 84 | 85 | ## [0.2.14] - 2023-06-20 86 | ### Fixed 87 | - [PR#90](https://github.com/Jake-Shadle/xwin/pull/90) fixed a problem caused by [PR#87](https://github.com/Jake-Shadle/xwin/pull/87). 88 | 89 | ## [0.2.13] - 2023-06-15 90 | ### Changed 91 | - [PR#88](https://github.com/Jake-Shadle/xwin/pull/88) updated dependencies. 92 | 93 | ### Added 94 | - [PR#87](https://github.com/Jake-Shadle/xwin/pull/87) added binaries for `aarch64-unknown-linux-musl` 95 | 96 | ## [0.2.12] - 2023-03-31 97 | ### Fixed 98 | - [PR#77](https://github.com/Jake-Shadle/xwin/pull/77) resolved [#76](https://github.com/Jake-Shadle/xwin/issues/76) by correctly handling the retrieval of the latest SDK version, regardless of whether it is for the Windows 10 or 11 SDK. 99 | 100 | ## [0.2.11] - 2023-03-06 101 | ### Fixed 102 | - [PR#74](https://github.com/Jake-Shadle/xwin/pull/74) resolved [#70](https://github.com/Jake-Shadle/xwin/issues/70) by creating symlinks for SDK headers that are included by the CRT and ATL headers. 103 | - [PR#74](https://github.com/Jake-Shadle/xwin/pull/74) fixed an issue where debug symbols were splatted to disk even when not requested. 104 | 105 | ## [0.2.10] - 2022-11-30 106 | ### Fixed 107 | - [PR#67](https://github.com/Jake-Shadle/xwin/pull/67) fixed an issue where incorrect packages could be selected due to using string ordering on strings that could both be version strings and regular non-version strings. Thanks [@mite-user](https://github.com/mite-user)! 108 | 109 | ## [0.2.9] - 2022-10-14 110 | ### Added 111 | - [PR#62](https://github.com/Jake-Shadle/xwin/pull/62) added release builds for Windows, closing [#58](https://github.com/Jake-Shadle/xwin/issues/58). 112 | 113 | ### Changed 114 | - [PR#61](https://github.com/Jake-Shadle/xwin/pull/61) updated clap to 4.0. Thanks [@messense](https://github.com/messense)! 115 | 116 | ## [0.2.8] - 2022-09-07 117 | ### Added 118 | - [PR#59](https://github.com/Jake-Shadle/xwin/pull/59) added support for installing the Active Template Library (ATL). Thanks [@pascalkuthe](https://github.com/pascalkuthe)! 119 | 120 | ## [0.2.7] - 2022-08-29 121 | ### Added 122 | - No changes in xwin itself, but now prebuilt binaries for `apple-darwin` are supplied. 123 | 124 | ## [0.2.6] - 2022-08-26 125 | ### Changed 126 | - Updated dependencies, notably `indicatif` and `insta`. 127 | 128 | ## [0.2.5] - 2022-06-21 129 | ### Changed 130 | - [PR#52](https://github.com/Jake-Shadle/xwin/pull/52) updated dependencies, including openssl-src to fix various issues raised by Github security advisories. 131 | 132 | ## [0.2.4] - 2022-05-23 133 | ### Added 134 | - [PR#50](https://github.com/Jake-Shadle/xwin/pull/50) added the ability to specify an HTTPS proxy via the `https_proxy` environment variable. Thanks [@j-raccoon](https://github.com/j-raccoon)! 135 | 136 | ## [0.2.3] - 2022-05-16 137 | ### Fixed 138 | - [PR#48](https://github.com/Jake-Shadle/xwin/pull/48) fixed an issue introduced in [PR#47](https://github.com/Jake-Shadle/xwin/pull/47) when using multiple architectures. Thanks [@messense](https://github.com/messense)! 139 | 140 | ## [0.2.2] - 2022-05-16 141 | ### Changed 142 | - [PR#45](https://github.com/Jake-Shadle/xwin/pull/45) replaced `reqwest` with `ureq` which significantly reduced dependencies. It also made `rustls` an optional (but default) TLS implementation in addition to supporting native TLS for arcane platforms that are not supported by `ring`. Thanks [@messense](https://github.com/messense)! 143 | - [PR#46](https://github.com/Jake-Shadle/xwin/pull/46) updated MSI to 0.5. Thanks [@messense](https://github.com/messense)! 144 | 145 | ### Added 146 | - [PR#47](https://github.com/Jake-Shadle/xwin/pull/47) added symlinks to support the usage of the `/vctoolsdir` and `/winsdkdir` options in `clang-cl`, which allow for a more concise compiler invocation. I would point you to official docs for this but apparently there are none. Thanks [@Qyriad](https://github.com/Qyriad)! 147 | 148 | ## [0.2.1] - 2022-05-04 149 | ### Changed 150 | - [PR#41](https://github.com/Jake-Shadle/xwin/pull/41) added a symlink for `BaseTsd.h`. Thanks [@jemc](https://github.com/jemc)! 151 | - [PR#42](https://github.com/Jake-Shadle/xwin/pull/42) updated dependencies, fixing a [CVE](https://rustsec.org/advisories/RUSTSEC-2022-0013). 152 | 153 | ## [0.2.0] - 2022-03-01 154 | ### Changed 155 | - [PR#37](https://github.com/Jake-Shadle/xwin/pull/37) changed from structopt to clap v3 for arguments parsing. Thanks [@messense](https://github.com/messense)! 156 | - [PR#38](https://github.com/Jake-Shadle/xwin/pull/38) fixed up the clap arguments to include metadata to be closer to the original structopt output with eg. `xwin -V`, however this exposed a problem that clap couldn't handle the old `--version ` flag since it clashed with `-V, --version`, so the flag has been renamed to `--manifest-version`. This is unfortunately a breaking change for the CLI. 157 | 158 | ## [0.1.10] - 2022-02-28 159 | ### Fixed 160 | - [PR#34](https://github.com/Jake-Shadle/xwin/pull/34) changed some code so that it is possible to compile and run for `x86_64-pc-windows-msvc`, though this target is not explicitly support. Thanks [@messense](https://github.com/messense)! 161 | - [PR#36](https://github.com/Jake-Shadle/xwin/pull/36) updated indicatif to `0.17.0-rc.6` and pinned it to fix [#35](https://github.com/Jake-Shadle/xwin/issues/35). 162 | 163 | ## [0.1.9] - 2022-02-28 164 | ### Fixed 165 | - [PR#32](https://github.com/Jake-Shadle/xwin/pull/32) fixed the `--disable-symlinks` flag to _actually_ not emit symlinks, which is needed if the target filesystem is case-insensitive. 166 | 167 | ## [0.1.8] - 2022-02-28 168 | ### Fixed 169 | - [PR#30](https://github.com/Jake-Shadle/xwin/pull/30) updated the indicatif pre-release as a workaround for `cargo install`'s [broken behavior](https://github.com/rust-lang/cargo/issues/7169). Thanks [@messense](https://github.com/messense)! 170 | 171 | ## [0.1.7] - 2022-02-24 172 | ### Fixed 173 | - [PR#27](https://github.com/Jake-Shadle/xwin/pull/27) added a fixup for `Iphlpapi.lib => iphlpapi.lib`. Thanks [@jelmansouri](https://github.com/jelmansouri)! 174 | 175 | ## [0.1.6] - 2022-02-07 176 | ### Fixed 177 | - [PR#22](https://github.com/Jake-Shadle/xwin/pull/22) added a fix for zeromq using a [mixed case include](https://github.com/zeromq/libzmq/blob/3070a4b2461ec64129062907d915ed665d2ac126/src/precompiled.hpp#L73). Thanks [@Jasper-Bekkers](https://github.com/Jasper-Bekkers)! 178 | - [PR#23](https://github.com/Jake-Shadle/xwin/pull/23) updated dependencies, which included bumping `thread_local` to fix a [security advisory](https://rustsec.org/advisories/RUSTSEC-2022-0006). 179 | 180 | ## [0.1.5] - 2021-11-25 181 | ### Fixed 182 | - [PR#19](https://github.com/Jake-Shadle/xwin/pull/19) resolved [#18](https://github.com/Jake-Shadle/xwin/issues/18) by removing a source of non-determinism in the output. It also made it so that some `Store` headers are no longer splatted to disk when targeting the `Desktop` variant alone. 183 | 184 | ## [0.1.4] - 2021-11-22 185 | ### Added 186 | - [PR#17](https://github.com/Jake-Shadle/xwin/pull/17) resolved [#6](https://github.com/Jake-Shadle/xwin/issues/6) by adding the `--manifest` option so that users can specify an exact manifest to use rather than downloading the mutable one from the Microsoft CDN. 187 | 188 | ## [0.1.3] - 2021-11-17 189 | ### Fixed 190 | - [PR#15](https://github.com/Jake-Shadle/xwin/pull/15) resolved [#14](https://github.com/Jake-Shadle/xwin/issues/14) by removing the unnecessary use of `tokio::main`. Thanks [@mite-user](https://github.com/mite-user)! 191 | - [PR#13](https://github.com/Jake-Shadle/xwin/pull/13) resolved [#12](https://github.com/Jake-Shadle/xwin/issues/12) by using the actual output directory rather than a hardcoded default. Thanks [@mite-user](https://github.com/mite-user)! 192 | 193 | ## [0.1.2] - 2021-11-11 194 | ### Fixed 195 | - [PR#11](https://github.com/Jake-Shadle/xwin/pull/11) added a workaround symlink for `Kernel32.lib` to fix the prevalent `time` crate in older versions. Thanks [@twistedfall](https://github.com/twistedfall)! 196 | 197 | ## [0.1.1] - 2021-08-24 198 | ### Fixed 199 | - [PR#9](https://github.com/Jake-Shadle/xwin/pull/9) resolved [#8](https://github.com/Jake-Shadle/xwin/pull/9) by adding support for additional symlinks for each `.lib` in `SCREAMING` case, since [some crates](https://github.com/microsoft/windows-rs/blob/a27a74784ccf304ab362bf2416f5f44e98e5eecd/src/bindings.rs) link them that way. 200 | 201 | ## [0.1.0] - 2021-08-22 202 | ### Added 203 | - Initial implementation if downloading, unpacking, and splatting of the CRT and Windows SDK. This first pass focused on targeting x86_64 Desktop, so targeting the Windows Store or other architectures is not guaranteed to work. 204 | 205 | 206 | [Unreleased]: https://github.com/Jake-Shadle/xwin/compare/0.6.7...HEAD 207 | [0.6.7]: https://github.com/Jake-Shadle/xwin/compare/0.6.6...0.6.7 208 | [0.6.6]: https://github.com/Jake-Shadle/xwin/compare/0.6.5...0.6.6 209 | [0.6.5]: https://github.com/Jake-Shadle/xwin/compare/0.6.4...0.6.5 210 | [0.6.4]: https://github.com/Jake-Shadle/xwin/compare/0.6.3...0.6.4 211 | [0.6.3]: https://github.com/Jake-Shadle/xwin/compare/0.6.2...0.6.3 212 | [0.6.2]: https://github.com/Jake-Shadle/xwin/compare/0.6.1...0.6.2 213 | [0.6.1]: https://github.com/Jake-Shadle/xwin/compare/0.6.0...0.6.1 214 | [0.6.0]: https://github.com/Jake-Shadle/xwin/compare/0.5.2...0.6.0 215 | [0.5.2]: https://github.com/Jake-Shadle/xwin/compare/0.5.1...0.5.2 216 | [0.5.1]: https://github.com/Jake-Shadle/xwin/compare/0.5.0...0.5.1 217 | [0.5.0]: https://github.com/Jake-Shadle/xwin/compare/0.4.1...0.5.0 218 | [0.4.1]: https://github.com/Jake-Shadle/xwin/compare/0.4.0...0.4.1 219 | [0.4.0]: https://github.com/Jake-Shadle/xwin/compare/0.3.1...0.4.0 220 | [0.3.1]: https://github.com/Jake-Shadle/xwin/compare/0.3.0...0.3.1 221 | [0.3.0]: https://github.com/Jake-Shadle/xwin/compare/0.2.15...0.3.0 222 | [0.2.15]: https://github.com/Jake-Shadle/xwin/compare/0.2.14...0.2.15 223 | [0.2.14]: https://github.com/Jake-Shadle/xwin/compare/0.2.13...0.2.14 224 | [0.2.13]: https://github.com/Jake-Shadle/xwin/compare/0.2.12...0.2.13 225 | [0.2.12]: https://github.com/Jake-Shadle/xwin/compare/0.2.11...0.2.12 226 | [0.2.11]: https://github.com/Jake-Shadle/xwin/compare/0.2.10...0.2.11 227 | [0.2.10]: https://github.com/Jake-Shadle/xwin/compare/0.2.9...0.2.10 228 | [0.2.9]: https://github.com/Jake-Shadle/xwin/compare/0.2.8...0.2.9 229 | [0.2.8]: https://github.com/Jake-Shadle/xwin/compare/0.2.7...0.2.8 230 | [0.2.7]: https://github.com/Jake-Shadle/xwin/compare/0.2.6...0.2.7 231 | [0.2.6]: https://github.com/Jake-Shadle/xwin/compare/0.2.5...0.2.6 232 | [0.2.5]: https://github.com/Jake-Shadle/xwin/compare/0.2.4...0.2.5 233 | [0.2.4]: https://github.com/Jake-Shadle/xwin/compare/0.2.3...0.2.4 234 | [0.2.3]: https://github.com/Jake-Shadle/xwin/compare/0.2.2...0.2.3 235 | [0.2.2]: https://github.com/Jake-Shadle/xwin/compare/0.2.1...0.2.2 236 | [0.2.1]: https://github.com/Jake-Shadle/xwin/compare/0.2.0...0.2.1 237 | [0.2.0]: https://github.com/Jake-Shadle/xwin/compare/0.1.10...0.2.0 238 | [0.1.10]: https://github.com/Jake-Shadle/xwin/compare/0.1.9...0.1.10 239 | [0.1.9]: https://github.com/Jake-Shadle/xwin/compare/0.1.8...0.1.9 240 | [0.1.8]: https://github.com/Jake-Shadle/xwin/compare/0.1.7...0.1.8 241 | [0.1.7]: https://github.com/Jake-Shadle/xwin/compare/0.1.6...0.1.7 242 | [0.1.6]: https://github.com/Jake-Shadle/xwin/compare/0.1.5...0.1.6 243 | [0.1.5]: https://github.com/Jake-Shadle/xwin/compare/0.1.4...0.1.5 244 | [0.1.4]: https://github.com/Jake-Shadle/xwin/compare/xwin-0.1.3...0.1.4 245 | [0.1.3]: https://github.com/Jake-Shadle/xwin/compare/xwin-0.1.2...xwin-0.1.3 246 | [0.1.2]: https://github.com/Jake-Shadle/xwin/compare/xwin-0.1.1...xwin-0.1.2 247 | [0.1.1]: https://github.com/Jake-Shadle/xwin/compare/0.1.0...xwin-0.1.1 248 | [0.1.0]: https://github.com/Jake-Shadle/xwin/releases/tag/0.1.0 249 | -------------------------------------------------------------------------------- /tests/xwin-test/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "xwin-test" 3 | version = "0.1.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [dependencies] 8 | # Compiles and links C/C++ code 9 | breakpad-sys = "0.1" 10 | windows-sys = { version = "0.28", features = [ 11 | "AI_MachineLearning", 12 | "ApplicationModel_Activation", 13 | "ApplicationModel_AppExtensions", 14 | "ApplicationModel_AppService", 15 | "ApplicationModel_Appointments", 16 | "ApplicationModel_Appointments_AppointmentsProvider", 17 | "ApplicationModel_Appointments_DataProvider", 18 | "ApplicationModel_Background", 19 | "ApplicationModel_Calls_Background", 20 | "ApplicationModel_Calls_Provider", 21 | "ApplicationModel_Chat", 22 | "ApplicationModel_CommunicationBlocking", 23 | "ApplicationModel_Contacts_DataProvider", 24 | "ApplicationModel_Contacts_Provider", 25 | "ApplicationModel_ConversationalAgent", 26 | "ApplicationModel_Core", 27 | "ApplicationModel_DataTransfer_DragDrop_Core", 28 | "ApplicationModel_DataTransfer", 29 | "ApplicationModel_Email_DataProvider", 30 | "ApplicationModel_ExtendedExecution_Foreground", 31 | "ApplicationModel_Holographic", 32 | "ApplicationModel_LockScreen", 33 | "ApplicationModel_Payments_Provider", 34 | "ApplicationModel_Resources_Core", 35 | "ApplicationModel_Resources_Management", 36 | "ApplicationModel_Search_Core", 37 | "ApplicationModel_SocialInfo_Provider", 38 | "ApplicationModel_Store_LicenseManagement", 39 | "ApplicationModel_UserActivities_Core", 40 | "ApplicationModel_UserDataAccounts_Provider", 41 | "ApplicationModel_UserDataAccounts_SystemAccess", 42 | "ApplicationModel_UserDataTasks_DataProvider", 43 | "ApplicationModel_VoiceCommands", 44 | "ApplicationModel_Wallet_System", 45 | "Data_Html", 46 | "Data_Json", 47 | "Data_Pdf", 48 | "Data_Text", 49 | "Data_Xml_Dom", 50 | "Data_Xml_Xsl", 51 | "Devices_Adc_Provider", 52 | "Devices_AllJoyn", 53 | "Devices_Background", 54 | "Devices_Bluetooth_Advertisement", 55 | "Devices_Bluetooth_Background", 56 | "Devices_Bluetooth_GenericAttributeProfile", 57 | "Devices_Bluetooth_Rfcomm", 58 | "Devices_Custom", 59 | "Devices_Display_Core", 60 | "Devices_Enumeration_Pnp", 61 | "Devices_Geolocation_Geofencing", 62 | "Devices_Gpio_Provider", 63 | "Devices_Haptics", 64 | "Devices_HumanInterfaceDevice", 65 | "Devices_I2c_Provider", 66 | "Devices_Input_Preview", 67 | "Devices_Lights_Effects", 68 | "Devices_Midi", 69 | "Devices_Perception_Provider", 70 | "Devices_PointOfService_Provider", 71 | "Devices_Portable", 72 | "Devices_Power", 73 | "Devices_Printers_Extensions", 74 | "Devices_Pwm_Provider", 75 | "Devices_Radios", 76 | "Devices_Scanners", 77 | "Devices_Sensors_Custom", 78 | "Devices_SerialCommunication", 79 | "Devices_SmartCards", 80 | "Devices_Sms", 81 | "Devices_Spi_Provider", 82 | "Devices_Usb", 83 | "Devices_WiFi", 84 | "Devices_WiFiDirect_Services", 85 | "Embedded_DeviceLockdown", 86 | "Foundation_Collections", 87 | "Foundation_Diagnostics", 88 | "Foundation_Metadata", 89 | "Foundation_Numerics", 90 | "Gaming_Input_Custom", 91 | "Gaming_Input_ForceFeedback", 92 | "Gaming_UI", 93 | "Gaming_XboxLive_Storage", 94 | "Globalization_Collation", 95 | "Globalization_DateTimeFormatting", 96 | "Globalization_Fonts", 97 | "Globalization_NumberFormatting", 98 | "Globalization_PhoneNumberFormatting", 99 | "Graphics_Capture", 100 | "Graphics_DirectX", 101 | "Graphics_DirectX_Direct3D11", 102 | "Graphics_Display_Core", 103 | "Graphics_Effects", 104 | "Graphics_Holographic", 105 | "Graphics_Imaging", 106 | "Graphics_Printing_OptionDetails", 107 | "Graphics_Printing_PrintSupport", 108 | "Graphics_Printing_PrintTicket", 109 | "Graphics_Printing_Workflow", 110 | "Graphics_Printing3D", 111 | "Management_Core", 112 | "Management_Deployment", 113 | "Management_Policies", 114 | "Management_Update", 115 | "Management_Workplace", 116 | "Media_AppBroadcasting", 117 | "Media_AppRecording", 118 | "Media_Audio", 119 | "Media_Capture_Core", 120 | "Media_Capture_Frames", 121 | "Media_Casting", 122 | "Media_ClosedCaptioning", 123 | "Media_ContentRestrictions", 124 | "Media_Control", 125 | "Media_Core", 126 | "Media_Devices_Core", 127 | "Media_DialProtocol", 128 | "Media_Editing", 129 | "Media_Effects", 130 | "Media_FaceAnalysis", 131 | "Media_Import", 132 | "Media_MediaProperties", 133 | "Media_Miracast", 134 | "Media_Ocr", 135 | "Media_PlayTo", 136 | "Media_Playback", 137 | "Media_Playlists", 138 | "Media_Protection_PlayReady", 139 | "Media_Render", 140 | "Media_SpeechRecognition", 141 | "Media_SpeechSynthesis", 142 | "Media_Streaming_Adaptive", 143 | "Media_Transcoding", 144 | "Networking_BackgroundTransfer", 145 | "Networking_Connectivity", 146 | "Networking_NetworkOperators", 147 | "Networking_Proximity", 148 | "Networking_PushNotifications", 149 | "Networking_ServiceDiscovery_Dnssd", 150 | "Networking_Sockets", 151 | "Networking_Vpn", 152 | "Networking_XboxLive", 153 | "Perception_Automation_Core", 154 | "Perception_People", 155 | "Perception_Spatial_Surfaces", 156 | "Phone_ApplicationModel", 157 | "Phone_Devices_Notification", 158 | "Phone_Devices_Power", 159 | "Phone_Management_Deployment", 160 | "Phone_Media_Devices", 161 | "Phone_Notification_Management", 162 | "Phone_PersonalInformation_Provisioning", 163 | "Phone_Speech_Recognition", 164 | "Phone_StartScreen", 165 | "Phone_System_Power", 166 | "Phone_System_Profile", 167 | "Phone_UI_Input", 168 | "Security_Authentication_Identity_Core", 169 | "Security_Authentication_Identity_Provider", 170 | "Security_Authentication_OnlineId", 171 | "Security_Authentication_Web_Core", 172 | "Security_Authentication_Web_Provider", 173 | "Security_Authorization_AppCapabilityAccess", 174 | "Security_Credentials_UI", 175 | "Security_Cryptography_Certificates", 176 | "Security_Cryptography_Core", 177 | "Security_Cryptography_DataProtection", 178 | "Security_DataProtection", 179 | "Security_EnterpriseData", 180 | "Security_ExchangeActiveSyncProvisioning", 181 | "Security_Isolation", 182 | "Services_Cortana", 183 | "Services_Maps_Guidance", 184 | "Services_Maps_LocalSearch", 185 | "Services_Maps_OfflineMaps", 186 | "Services_Store", 187 | "Services_TargetedContent", 188 | "Storage_AccessCache", 189 | "Storage_BulkAccess", 190 | "Storage_Compression", 191 | "Storage_FileProperties", 192 | "Storage_Pickers_Provider", 193 | "Storage_Provider", 194 | "Storage_Search", 195 | "Storage_Streams", 196 | "System_Diagnostics_DevicePortal", 197 | "System_Diagnostics_Telemetry", 198 | "System_Diagnostics_TraceReporting", 199 | "System_Display", 200 | "System_Implementation_FileExplorer", 201 | "System_Inventory", 202 | "System_Power_Diagnostics", 203 | "System_Profile_SystemManufacturers", 204 | "System_RemoteDesktop_Input", 205 | "System_RemoteSystems", 206 | "System_Threading_Core", 207 | "System_Update", 208 | "System_UserProfile", 209 | "UI_Accessibility", 210 | "UI_ApplicationSettings", 211 | "UI_Composition_Core", 212 | "UI_Composition_Desktop", 213 | "UI_Composition_Diagnostics", 214 | "UI_Composition_Effects", 215 | "UI_Composition_Interactions", 216 | "UI_Composition_Scenes", 217 | "UI_Core_AnimationMetrics", 218 | "UI_Input_Core", 219 | "UI_Input_Inking_Analysis", 220 | "UI_Input_Inking_Core", 221 | "UI_Input_Inking_Preview", 222 | "UI_Input_Spatial", 223 | "UI_Notifications_Management", 224 | "UI_Popups", 225 | "UI_Shell", 226 | "UI_StartScreen", 227 | "UI_Text_Core", 228 | "UI_UIAutomation_Core", 229 | "UI_ViewManagement_Core", 230 | "UI_WebUI_Core", 231 | "UI_WindowManagement", 232 | "UI_Xaml_Automation_Peers", 233 | "UI_Xaml_Automation_Provider", 234 | "UI_Xaml_Automation_Text", 235 | "UI_Xaml_Controls_Maps", 236 | "UI_Xaml_Controls_Primitives", 237 | "UI_Xaml_Core_Direct", 238 | "UI_Xaml_Data", 239 | "UI_Xaml_Documents", 240 | "UI_Xaml_Hosting", 241 | "UI_Xaml_Input", 242 | "UI_Xaml_Interop", 243 | "UI_Xaml_Markup", 244 | "UI_Xaml_Media_Animation", 245 | "UI_Xaml_Media_Imaging", 246 | "UI_Xaml_Media_Media3D", 247 | "UI_Xaml_Navigation", 248 | "UI_Xaml_Printing", 249 | "UI_Xaml_Resources", 250 | "UI_Xaml_Shapes", 251 | "Web_AtomPub", 252 | "Web_Http_Diagnostics", 253 | "Web_Http_Filters", 254 | "Web_Http_Headers", 255 | "Web_Syndication", 256 | "Web_UI_Interop", 257 | "Win32_AI_MachineLearning_DirectML", 258 | "Win32_AI_MachineLearning_WinML", 259 | "Win32_Data_HtmlHelp", 260 | "Win32_Data_RightsManagement", 261 | "Win32_Data_Xml_MsXml", 262 | "Win32_Data_Xml_XmlLite", 263 | "Win32_Devices_AllJoyn", 264 | "Win32_Devices_BiometricFramework", 265 | "Win32_Devices_Bluetooth", 266 | "Win32_Devices_Communication", 267 | "Win32_Devices_DeviceAccess", 268 | "Win32_Devices_DeviceAndDriverInstallation", 269 | "Win32_Devices_DeviceQuery", 270 | "Win32_Devices_Display", 271 | "Win32_Devices_Enumeration_Pnp", 272 | "Win32_Devices_Fax", 273 | "Win32_Devices_FunctionDiscovery", 274 | "Win32_Devices_Geolocation", 275 | "Win32_Devices_HumanInterfaceDevice", 276 | "Win32_Devices_ImageAcquisition", 277 | "Win32_Devices_PortableDevices", 278 | "Win32_Devices_Properties", 279 | "Win32_Devices_Pwm", 280 | "Win32_Devices_Sensors", 281 | "Win32_Devices_SerialCommunication", 282 | "Win32_Devices_Tapi", 283 | "Win32_Devices_Usb", 284 | "Win32_Devices_WebServicesOnDevices", 285 | "Win32_Foundation", 286 | "Win32_Gaming", 287 | "Win32_Globalization", 288 | "Win32_Graphics_CompositionSwapchain", 289 | "Win32_Graphics_DXCore", 290 | "Win32_Graphics_Direct2D_Common", 291 | "Win32_Graphics_Direct3D_Dxc", 292 | "Win32_Graphics_Direct3D_Fxc", 293 | "Win32_Graphics_Direct3D10", 294 | "Win32_Graphics_Direct3D11", 295 | "Win32_Graphics_Direct3D11on12", 296 | "Win32_Graphics_Direct3D12", 297 | "Win32_Graphics_Direct3D9", 298 | "Win32_Graphics_Direct3D9on12", 299 | "Win32_Graphics_DirectComposition", 300 | "Win32_Graphics_DirectDraw", 301 | "Win32_Graphics_DirectManipulation", 302 | "Win32_Graphics_DirectWrite", 303 | "Win32_Graphics_Dwm", 304 | "Win32_Graphics_Dxgi_Common", 305 | "Win32_Graphics_Gdi", 306 | "Win32_Graphics_Hlsl", 307 | "Win32_Graphics_Imaging_D2D", 308 | "Win32_Graphics_OpenGL", 309 | "Win32_Graphics_Printing_PrintTicket", 310 | "Win32_Management_MobileDeviceManagementRegistration", 311 | "Win32_Media_Audio_Apo", 312 | "Win32_Media_Audio_DirectMusic", 313 | "Win32_Media_Audio_DirectSound", 314 | "Win32_Media_Audio_Endpoints", 315 | "Win32_Media_Audio_XAudio2", 316 | "Win32_Media_DeviceManager", 317 | "Win32_Media_DirectShow_Xml", 318 | "Win32_Media_DxMediaObjects", 319 | "Win32_Media_KernelStreaming", 320 | "Win32_Media_LibrarySharingServices", 321 | "Win32_Media_MediaFoundation", 322 | "Win32_Media_MediaPlayer", 323 | "Win32_Media_Multimedia", 324 | "Win32_Media_PictureAcquisition", 325 | "Win32_Media_Speech", 326 | "Win32_Media_Streaming", 327 | "Win32_Media_WindowsMediaFormat", 328 | "Win32_NetworkManagement_Dhcp", 329 | "Win32_NetworkManagement_Dns", 330 | "Win32_NetworkManagement_InternetConnectionWizard", 331 | "Win32_NetworkManagement_IpHelper", 332 | "Win32_NetworkManagement_MobileBroadband", 333 | "Win32_NetworkManagement_Multicast", 334 | "Win32_NetworkManagement_Ndis", 335 | "Win32_NetworkManagement_NetBios", 336 | "Win32_NetworkManagement_NetManagement", 337 | "Win32_NetworkManagement_NetShell", 338 | "Win32_NetworkManagement_NetworkPolicyServer", 339 | "Win32_NetworkManagement_P2P", 340 | "Win32_NetworkManagement_QoS", 341 | "Win32_NetworkManagement_Rras", 342 | "Win32_NetworkManagement_Snmp", 343 | "Win32_NetworkManagement_WNet", 344 | "Win32_NetworkManagement_WebDav", 345 | "Win32_NetworkManagement_WiFi", 346 | "Win32_NetworkManagement_WindowsConnectNow", 347 | "Win32_NetworkManagement_WindowsConnectionManager", 348 | "Win32_NetworkManagement_WindowsFilteringPlatform", 349 | "Win32_NetworkManagement_WindowsFirewall", 350 | "Win32_Networking_ActiveDirectory", 351 | "Win32_Networking_BackgroundIntelligentTransferService", 352 | "Win32_Networking_Clustering", 353 | "Win32_Networking_HttpServer", 354 | "Win32_Networking_Ldap", 355 | "Win32_Networking_NetworkListManager", 356 | "Win32_Networking_RemoteDifferentialCompression", 357 | "Win32_Networking_WebSocket", 358 | "Win32_Networking_WinHttp", 359 | "Win32_Networking_WinInet", 360 | "Win32_Networking_WinSock", 361 | "Win32_Networking_WindowsWebServices", 362 | "Win32_Security_AppLocker", 363 | "Win32_Security_Authorization_UI", 364 | "Win32_Security_ConfigurationSnapin", 365 | "Win32_Security_Credentials", 366 | "Win32_Security_Cryptography_Catalog", 367 | "Win32_Security_Cryptography_Certificates", 368 | "Win32_Security_Cryptography_Sip", 369 | "Win32_Security_Cryptography_UI", 370 | "Win32_Security_DiagnosticDataQuery", 371 | "Win32_Security_DirectoryServices", 372 | "Win32_Security_EnterpriseData", 373 | "Win32_Security_ExtensibleAuthenticationProtocol", 374 | "Win32_Security_Isolation", 375 | "Win32_Security_LicenseProtection", 376 | "Win32_Security_NetworkAccessProtection", 377 | "Win32_Security_Tpm", 378 | "Win32_Security_WinTrust", 379 | "Win32_Security_WinWlx", 380 | "Win32_Storage_Cabinets", 381 | "Win32_Storage_CloudFilters", 382 | "Win32_Storage_Compression", 383 | "Win32_Storage_DataDeduplication", 384 | "Win32_Storage_DistributedFileSystem", 385 | "Win32_Storage_EnhancedStorage", 386 | "Win32_Storage_FileHistory", 387 | "Win32_Storage_FileServerResourceManager", 388 | "Win32_Storage_FileSystem", 389 | "Win32_Storage_Imapi", 390 | "Win32_Storage_IndexServer", 391 | "Win32_Storage_InstallableFileSystems", 392 | "Win32_Storage_IscsiDisc", 393 | "Win32_Storage_Jet", 394 | "Win32_Storage_OfflineFiles", 395 | "Win32_Storage_OperationRecorder", 396 | "Win32_Storage_Packaging_Appx", 397 | "Win32_Storage_Packaging_Opc", 398 | "Win32_Storage_ProjectedFileSystem", 399 | "Win32_Storage_StructuredStorage", 400 | "Win32_Storage_Vhd", 401 | "Win32_Storage_VirtualDiskService", 402 | "Win32_Storage_Vss", 403 | "Win32_Storage_Xps_Printing", 404 | "Win32_System_AddressBook", 405 | "Win32_System_Antimalware", 406 | "Win32_System_ApplicationInstallationAndServicing", 407 | "Win32_System_ApplicationVerifier", 408 | "Win32_System_AssessmentTool", 409 | "Win32_System_Com_CallObj", 410 | "Win32_System_Com_ChannelCredentials", 411 | "Win32_System_Com_Events", 412 | "Win32_System_Com_Marshal", 413 | "Win32_System_Com_StructuredStorage", 414 | "Win32_System_Com_UI", 415 | "Win32_System_Com_Urlmon", 416 | "Win32_System_ComponentServices", 417 | "Win32_System_Console", 418 | "Win32_System_Contacts", 419 | "Win32_System_CorrelationVector", 420 | "Win32_System_DataExchange", 421 | "Win32_System_DeploymentServices", 422 | "Win32_System_DesktopSharing", 423 | "Win32_System_DeveloperLicensing", 424 | "Win32_System_Diagnostics_Ceip", 425 | "Win32_System_Diagnostics_Debug_WebApp", 426 | "Win32_System_Diagnostics_Etw", 427 | "Win32_System_Diagnostics_ProcessSnapshotting", 428 | "Win32_System_Diagnostics_ToolHelp", 429 | "Win32_System_DistributedTransactionCoordinator", 430 | "Win32_System_Environment", 431 | "Win32_System_ErrorReporting", 432 | "Win32_System_EventCollector", 433 | "Win32_System_EventLog", 434 | "Win32_System_EventNotificationService", 435 | "Win32_System_GroupPolicy", 436 | "Win32_System_HostCompute", 437 | "Win32_System_HostComputeNetwork", 438 | "Win32_System_HostComputeSystem", 439 | "Win32_System_Hypervisor", 440 | "Win32_System_IO", 441 | "Win32_System_Iis", 442 | "Win32_System_Ioctl", 443 | "Win32_System_JobObjects", 444 | "Win32_System_Js", 445 | "Win32_System_Kernel", 446 | "Win32_System_LibraryLoader", 447 | "Win32_System_Mailslots", 448 | "Win32_System_Mapi", 449 | "Win32_System_Memory_NonVolatile", 450 | "Win32_System_MessageQueuing", 451 | "Win32_System_MixedReality", 452 | "Win32_System_Mmc", 453 | "Win32_System_Ole", 454 | "Win32_System_ParentalControls", 455 | "Win32_System_PasswordManagement", 456 | "Win32_System_Performance_HardwareCounterProfiling", 457 | "Win32_System_Pipes", 458 | "Win32_System_Power", 459 | "Win32_System_ProcessStatus", 460 | "Win32_System_RealTimeCommunications", 461 | "Win32_System_Recovery", 462 | "Win32_System_Registry", 463 | "Win32_System_RemoteAssistance", 464 | "Win32_System_RemoteDesktop", 465 | "Win32_System_RemoteManagement", 466 | "Win32_System_RestartManager", 467 | "Win32_System_Restore", 468 | "Win32_System_Rpc", 469 | "Win32_System_Search_Common", 470 | "Win32_System_SecurityCenter", 471 | "Win32_System_ServerBackup", 472 | "Win32_System_Services", 473 | "Win32_System_SettingsManagementInfrastructure", 474 | "Win32_System_SetupAndMigration", 475 | "Win32_System_Shutdown", 476 | "Win32_System_SideShow", 477 | "Win32_System_SqlLite", 478 | "Win32_System_StationsAndDesktops", 479 | "Win32_System_SubsystemForLinux", 480 | "Win32_System_StationsAndDesktops", 481 | "Win32_System_SystemInformation", 482 | "Win32_System_SystemServices", 483 | "Win32_System_TaskScheduler", 484 | "Win32_System_Threading", 485 | "Win32_System_Time", 486 | "Win32_System_TpmBaseServices", 487 | "Win32_System_TransactionServer", 488 | "Win32_System_UpdateAgent", 489 | "Win32_System_UpdateAssessment", 490 | "Win32_System_UserAccessLogging", 491 | "Win32_System_VirtualDosMachines", 492 | "Win32_System_WinRT_AllJoyn", 493 | "Win32_System_WinRT_Composition", 494 | "Win32_System_WinRT_CoreInputView", 495 | "Win32_System_WinRT_Direct3D11", 496 | "Win32_System_WinRT_Display", 497 | "Win32_System_WinRT_Graphics_Capture", 498 | "Win32_System_WinRT_Graphics_Direct2D", 499 | "Win32_System_WinRT_Graphics_Imaging", 500 | "Win32_System_WinRT_Holographic", 501 | "Win32_System_WinRT_Isolation", 502 | "Win32_System_WinRT_ML", 503 | "Win32_System_WinRT_Media", 504 | "Win32_System_WinRT_Pdf", 505 | "Win32_System_WinRT_Printing", 506 | "Win32_System_WinRT_Shell", 507 | "Win32_System_WinRT_Storage", 508 | "Win32_System_WinRT_Xaml", 509 | "Win32_System_WindowsProgramming", 510 | "Win32_System_WindowsSync", 511 | "Win32_System_Wmi", 512 | "Win32_UI_Accessibility", 513 | "Win32_UI_Animation", 514 | "Win32_UI_ColorSystem", 515 | "Win32_UI_Controls_Dialogs", 516 | "Win32_UI_Controls_RichEdit", 517 | "Win32_UI_HiDpi", 518 | "Win32_UI_Input_Ime", 519 | "Win32_UI_Input_Ink", 520 | "Win32_UI_Input_KeyboardAndMouse", 521 | "Win32_UI_Input_Pointer", 522 | "Win32_UI_Input_Radial", 523 | "Win32_UI_Input_Touch", 524 | "Win32_UI_Input_XboxController", 525 | "Win32_UI_InteractionContext", 526 | "Win32_UI_LegacyWindowsEnvironmentFeatures", 527 | "Win32_UI_Magnification", 528 | "Win32_UI_Notifications", 529 | "Win32_UI_Ribbon", 530 | "Win32_UI_Shell_Common", 531 | "Win32_UI_Shell_PropertiesSystem", 532 | "Win32_UI_TabletPC", 533 | "Win32_UI_TextServices", 534 | "Win32_UI_WindowsAndMessaging", 535 | "Win32_UI_Wpf", 536 | "Win32_UI_Xaml_Diagnostics", 537 | "Win32_Web_MsHtml", 538 | 539 | ] } 540 | -------------------------------------------------------------------------------- /src/ctx.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | Path, PathBuf, WorkItem, 3 | splat::SdkHeaders, 4 | util::{ProgressTarget, Sha256}, 5 | }; 6 | use anyhow::{Context as _, Error}; 7 | 8 | #[allow(dead_code)] 9 | pub enum Unpack { 10 | Present { 11 | output_dir: PathBuf, 12 | compressed: u64, 13 | decompressed: u64, 14 | num_files: u32, 15 | }, 16 | Needed(PathBuf), 17 | } 18 | 19 | pub struct Ctx { 20 | pub work_dir: PathBuf, 21 | pub tempdir: Option, 22 | pub client: ureq::Agent, 23 | pub draw_target: ProgressTarget, 24 | pub http_retry: u8, 25 | } 26 | 27 | impl Ctx { 28 | pub fn with_temp( 29 | dt: ProgressTarget, 30 | client: ureq::Agent, 31 | http_retry: u8, 32 | ) -> Result { 33 | let td = tempfile::TempDir::new()?; 34 | 35 | Ok(Self { 36 | work_dir: PathBuf::from_path_buf(td.path().to_owned()).map_err(|pb| { 37 | anyhow::anyhow!("tempdir {} is not a valid utf-8 path", pb.display()) 38 | })?, 39 | tempdir: Some(td), 40 | client, 41 | draw_target: dt, 42 | http_retry, 43 | }) 44 | } 45 | 46 | pub fn with_dir( 47 | mut work_dir: PathBuf, 48 | dt: ProgressTarget, 49 | client: ureq::Agent, 50 | http_retry: u8, 51 | ) -> Result { 52 | work_dir.push("dl"); 53 | std::fs::create_dir_all(&work_dir)?; 54 | work_dir.pop(); 55 | work_dir.push("unpack"); 56 | std::fs::create_dir_all(&work_dir)?; 57 | work_dir.pop(); 58 | 59 | Ok(Self { 60 | work_dir, 61 | tempdir: None, 62 | client, 63 | draw_target: dt, 64 | http_retry, 65 | }) 66 | } 67 | 68 | pub fn get_and_validate

( 69 | &self, 70 | url: impl AsRef, 71 | path: &P, 72 | checksum: Option, 73 | mut progress: indicatif::ProgressBar, 74 | ) -> Result 75 | where 76 | P: AsRef + std::fmt::Debug, 77 | { 78 | let short_path = path.as_ref(); 79 | let cache_path = { 80 | let mut cp = self.work_dir.clone(); 81 | cp.push("dl"); 82 | cp.push(short_path); 83 | cp 84 | }; 85 | 86 | if cache_path.exists() { 87 | tracing::debug!("verifying existing cached dl file"); 88 | 89 | match std::fs::read(&cache_path) { 90 | Ok(contents) => { 91 | if let Some(expected) = &checksum { 92 | let chksum = Sha256::digest(&contents); 93 | 94 | if chksum != *expected { 95 | tracing::warn!( 96 | "checksum mismatch, expected {expected} != actual {chksum}", 97 | ); 98 | } else { 99 | progress.inc_length(contents.len() as u64); 100 | progress.inc(contents.len() as u64); 101 | return Ok(contents.into()); 102 | } 103 | } else { 104 | progress.inc_length(contents.len() as u64); 105 | progress.inc(contents.len() as u64); 106 | return Ok(contents.into()); 107 | } 108 | } 109 | Err(e) => { 110 | tracing::warn!(error = %e, "failed to read cached file"); 111 | } 112 | } 113 | } 114 | 115 | use bytes::BufMut; 116 | 117 | struct ProgressCopy { 118 | progress: indicatif::ProgressBar, 119 | inner: bytes::buf::Writer, 120 | failed: usize, 121 | written: usize, 122 | } 123 | 124 | impl std::io::Write for ProgressCopy { 125 | fn write(&mut self, buf: &[u8]) -> std::io::Result { 126 | self.written += buf.len(); 127 | if self.failed == 0 { 128 | self.progress.inc(buf.len() as u64); 129 | } else if self.written > self.failed { 130 | self.progress.inc((self.written - self.failed) as u64); 131 | self.failed = 0; 132 | } 133 | 134 | self.inner.write(buf) 135 | } 136 | 137 | fn flush(&mut self) -> std::io::Result<()> { 138 | self.inner.flush() 139 | } 140 | } 141 | 142 | enum DownloadError { 143 | Ureq(ureq::Error), 144 | Io(std::io::Error), 145 | Retry((bytes::BytesMut, indicatif::ProgressBar)), 146 | } 147 | 148 | let try_download = |mut body: bytes::BytesMut, 149 | progress: indicatif::ProgressBar| 150 | -> Result { 151 | let res = self 152 | .client 153 | .get(url.as_ref()) 154 | .call() 155 | .map_err(DownloadError::Ureq)?; 156 | 157 | let content_length = res 158 | .headers() 159 | .get("content-length") 160 | .and_then(|header| header.to_str().ok()?.parse().ok()) 161 | .unwrap_or_default(); 162 | 163 | if body.capacity() > 0 { 164 | if body.capacity() as u64 != content_length { 165 | tracing::warn!( 166 | url = url.as_ref(), 167 | "a previous HTTP GET had a content-length of {}, but we now received a content-length of {content_length}", 168 | body.capacity() 169 | ); 170 | 171 | if body.capacity() as u64 > content_length { 172 | progress.inc_length(body.capacity() as u64 - content_length); 173 | } else { 174 | body.reserve(content_length as usize - body.capacity()); 175 | } 176 | } 177 | } else { 178 | body.reserve(content_length as usize); 179 | progress.inc_length(content_length); 180 | } 181 | 182 | let failed = body.len(); 183 | body.clear(); 184 | 185 | let mut pc = ProgressCopy { 186 | progress, 187 | inner: body.writer(), 188 | failed, 189 | written: 0, 190 | }; 191 | 192 | let res = std::io::copy(&mut res.into_body().as_reader(), &mut pc); 193 | let body = pc.inner.into_inner(); 194 | 195 | match res { 196 | Ok(_) => Ok(body), 197 | Err(ref err) if err.kind() == std::io::ErrorKind::UnexpectedEof => { 198 | Err(DownloadError::Retry((body, pc.progress))) 199 | } 200 | Err(err) => Err(DownloadError::Io(err)), 201 | } 202 | }; 203 | 204 | let mut tries = self.http_retry + 1; 205 | let total = tries; 206 | let mut body = bytes::BytesMut::new(); 207 | 208 | while tries > 0 { 209 | match try_download(body, progress) { 210 | Ok(body) => { 211 | let body = body.freeze(); 212 | 213 | if let Some(expected) = checksum { 214 | let chksum = Sha256::digest(&body); 215 | 216 | anyhow::ensure!( 217 | chksum == expected, 218 | "checksum mismatch, expected {expected} != actual {chksum}" 219 | ); 220 | } 221 | 222 | if let Some(parent) = cache_path.parent() { 223 | std::fs::create_dir_all(parent)?; 224 | } 225 | 226 | std::fs::write(cache_path, &body)?; 227 | return Ok(body); 228 | } 229 | Err(DownloadError::Retry((b, prog))) => { 230 | tries -= 1; 231 | body = b; 232 | progress = prog; 233 | 234 | if tries > 0 { 235 | tracing::warn!( 236 | url = url.as_ref(), 237 | "HTTP GET failed to retrieve entire body, retrying" 238 | ); 239 | } 240 | } 241 | Err(DownloadError::Ureq(err)) => { 242 | return Err(err) 243 | .with_context(|| format!("HTTP GET request for {} failed", url.as_ref())); 244 | } 245 | Err(DownloadError::Io(err)) => { 246 | return Err(err) 247 | .with_context(|| format!("failed to retrieve body for {}", url.as_ref())); 248 | } 249 | } 250 | } 251 | 252 | anyhow::bail!( 253 | "failed to retrieve {} after {total} tries due to I/O failures reading the response body, try using --http-retries to increase the retry count", 254 | url.as_ref() 255 | ); 256 | } 257 | 258 | #[allow(clippy::too_many_arguments)] 259 | pub fn execute( 260 | self: std::sync::Arc, 261 | packages: std::collections::BTreeMap, 262 | payloads: Vec, 263 | crt_version: String, 264 | sdk_version: String, 265 | vcrd_version: Option, 266 | arches: u32, 267 | variants: u32, 268 | ops: crate::Ops, 269 | ) -> Result<(), Error> { 270 | use rayon::prelude::*; 271 | 272 | let packages = std::sync::Arc::new(packages); 273 | 274 | let mut results = Vec::new(); 275 | let crt_ft = parking_lot::Mutex::new(None); 276 | let atl_ft = parking_lot::Mutex::new(None); 277 | 278 | let mut splat_config = match &ops { 279 | crate::Ops::Splat(config) => { 280 | let splat_roots = crate::splat::prep_splat( 281 | self.clone(), 282 | &config.output, 283 | config.use_winsysroot_style.then_some(&crt_version), 284 | )?; 285 | let mut config = config.clone(); 286 | config.output = splat_roots.root.clone(); 287 | 288 | Some((splat_roots, config)) 289 | } 290 | crate::Ops::Minimize(config) => { 291 | let splat_roots = crate::splat::prep_splat( 292 | self.clone(), 293 | &config.splat_output, 294 | config.use_winsysroot_style.then_some(&crt_version), 295 | )?; 296 | 297 | let config = crate::SplatConfig { 298 | preserve_ms_arch_notation: config.preserve_ms_arch_notation, 299 | include_debug_libs: config.include_debug_libs, 300 | include_debug_symbols: config.include_debug_symbols, 301 | enable_symlinks: config.enable_symlinks, 302 | use_winsysroot_style: config.use_winsysroot_style, 303 | output: splat_roots.root.clone(), 304 | map: Some(config.map.clone()), 305 | copy: config.copy, 306 | }; 307 | 308 | Some((splat_roots, config)) 309 | } 310 | _ => None, 311 | }; 312 | 313 | // Detect if the output root directory is case sensitive or not, 314 | // if it's not, disable symlinks as they won't work 315 | let enable_symlinks = if let Some((root, sc_enable_symlinks)) = 316 | splat_config.as_mut().and_then(|(sr, c)| { 317 | c.enable_symlinks 318 | .then_some((&sr.root, &mut c.enable_symlinks)) 319 | }) { 320 | let test_path = root.join("BIG.xwin"); 321 | std::fs::write(&test_path, "").with_context(|| { 322 | format!("failed to write case-sensitivity test file {test_path}") 323 | })?; 324 | 325 | let enable_symlinks = if std::fs::read(root.join("big.xwin")).is_ok() { 326 | tracing::warn!( 327 | "detected splat root '{root}' is on a case-insensitive file system, disabling symlinks" 328 | ); 329 | false 330 | } else { 331 | true 332 | }; 333 | 334 | // Will be ugly but won't harm anything if file is left 335 | let _ = std::fs::remove_file(test_path); 336 | *sc_enable_symlinks = enable_symlinks; 337 | enable_symlinks 338 | } else { 339 | false 340 | }; 341 | 342 | let map = if let Some(map) = splat_config.as_ref().and_then(|(_, sp)| sp.map.as_ref()) { 343 | match std::fs::read_to_string(map) { 344 | Ok(m) => Some( 345 | toml::from_str::(&m) 346 | .with_context(|| format!("failed to deserialize '{map}'"))?, 347 | ), 348 | Err(err) => { 349 | if !matches!(err.kind(), std::io::ErrorKind::NotFound) { 350 | tracing::error!("unable to read mapping from '{map}': {err}"); 351 | } 352 | None 353 | } 354 | } 355 | } else { 356 | None 357 | }; 358 | 359 | payloads 360 | .into_par_iter() 361 | .map(|wi| -> Result, Error> { 362 | let payload_contents = 363 | crate::download::download(self.clone(), packages.clone(), &wi)?; 364 | 365 | if let crate::Ops::Download = ops { 366 | return Ok(None); 367 | } 368 | 369 | let Some(payload_contents) = payload_contents else { 370 | wi.progress.abandon_with_message("MSI with no cabs"); 371 | return Ok(None); 372 | }; 373 | 374 | let ft = crate::unpack::unpack(self.clone(), &wi, payload_contents)?; 375 | 376 | if let crate::Ops::Unpack = ops { 377 | return Ok(None); 378 | } 379 | 380 | let sdk_headers = if let Some((splat_roots, config)) = &splat_config { 381 | crate::splat::splat( 382 | config, 383 | splat_roots, 384 | &wi, 385 | &ft, 386 | map.as_ref() 387 | .filter(|_m| !matches!(ops, crate::Ops::Minimize(_))), 388 | &sdk_version, 389 | vcrd_version.clone(), 390 | arches, 391 | variants, 392 | ) 393 | .with_context(|| format!("failed to splat {}", wi.payload.filename))? 394 | } else { 395 | None 396 | }; 397 | 398 | match wi.payload.kind { 399 | crate::PayloadKind::CrtHeaders => *crt_ft.lock() = Some(ft), 400 | crate::PayloadKind::AtlHeaders => *atl_ft.lock() = Some(ft), 401 | _ => {} 402 | } 403 | 404 | Ok(sdk_headers) 405 | }) 406 | .collect_into_vec(&mut results); 407 | 408 | let sdk_headers = results.into_iter().collect::, _>>()?; 409 | let sdk_headers = sdk_headers.into_iter().flatten().collect(); 410 | 411 | let Some((roots, sc)) = splat_config else { 412 | return Ok(()); 413 | }; 414 | 415 | let splat_links = || -> anyhow::Result<()> { 416 | if enable_symlinks { 417 | let crt_ft = crt_ft.lock().take(); 418 | let atl_ft = atl_ft.lock().take(); 419 | 420 | crate::splat::finalize_splat( 421 | &self, 422 | sc.use_winsysroot_style.then_some(&sdk_version), 423 | &roots, 424 | sdk_headers, 425 | crt_ft, 426 | atl_ft, 427 | )?; 428 | } 429 | 430 | Ok(()) 431 | }; 432 | 433 | match ops { 434 | crate::Ops::Minimize(config) => { 435 | splat_links()?; 436 | let results = crate::minimize::minimize(self, config, roots, &sdk_version)?; 437 | 438 | fn emit(name: &str, num: crate::minimize::FileNumbers) { 439 | fn hb(bytes: u64) -> String { 440 | let mut bytes = bytes as f64; 441 | 442 | for unit in ["B", "KiB", "MiB", "GiB"] { 443 | if bytes > 1024.0 { 444 | bytes /= 1024.0; 445 | } else { 446 | return format!("{bytes:.1}{unit}"); 447 | } 448 | } 449 | 450 | "this seems bad".to_owned() 451 | } 452 | 453 | let ratio = (num.used.bytes as f64 / num.total.bytes as f64) * 100.0; 454 | 455 | println!( 456 | " {name}: {}({}) / {}({}) => {ratio:.02}%", 457 | num.used.count, 458 | hb(num.used.bytes), 459 | num.total.count, 460 | hb(num.total.bytes), 461 | ); 462 | } 463 | 464 | emit("crt headers", results.crt_headers); 465 | emit("crt libs", results.crt_libs); 466 | emit("sdk headers", results.sdk_headers); 467 | emit("sdk libs", results.sdk_libs); 468 | } 469 | crate::Ops::Splat(_config) => { 470 | if map.is_none() { 471 | splat_links()?; 472 | } 473 | } 474 | _ => {} 475 | } 476 | 477 | Ok(()) 478 | } 479 | 480 | pub(crate) fn prep_unpack(&self, payload: &crate::Payload) -> Result { 481 | let mut unpack_dir = { 482 | let mut pb = self.work_dir.clone(); 483 | pb.push("unpack"); 484 | pb.push(&payload.filename); 485 | pb 486 | }; 487 | 488 | unpack_dir.push(".unpack"); 489 | 490 | if let Ok(unpack) = std::fs::read(&unpack_dir) 491 | && let Ok(um) = serde_json::from_slice::(&unpack) 492 | && payload.sha256 == um.sha256 493 | { 494 | tracing::debug!("already unpacked"); 495 | unpack_dir.pop(); 496 | return Ok(Unpack::Present { 497 | output_dir: unpack_dir, 498 | compressed: um.compressed, 499 | decompressed: um.decompressed, 500 | num_files: um.num_files, 501 | }); 502 | } 503 | 504 | unpack_dir.pop(); 505 | 506 | // If we didn't validate the .unpack file, ensure that we clean up anything 507 | // that might be leftover from a failed unpack 508 | if unpack_dir.exists() { 509 | std::fs::remove_dir_all(&unpack_dir) 510 | .with_context(|| format!("unable to remove invalid unpack dir '{unpack_dir}'"))?; 511 | } 512 | 513 | std::fs::create_dir_all(&unpack_dir) 514 | .with_context(|| format!("unable to create unpack dir '{unpack_dir}'"))?; 515 | 516 | Ok(Unpack::Needed(unpack_dir)) 517 | } 518 | 519 | #[allow(clippy::unused_self)] 520 | pub(crate) fn finish_unpack( 521 | &self, 522 | mut unpack_dir: PathBuf, 523 | um: crate::unpack::UnpackMeta, 524 | ) -> Result<(), Error> { 525 | unpack_dir.push(".unpack"); 526 | let um = serde_json::to_vec(&um)?; 527 | 528 | std::fs::write(&unpack_dir, um).with_context(|| format!("unable to write {unpack_dir}"))?; 529 | Ok(()) 530 | } 531 | } 532 | 533 | impl Drop for Ctx { 534 | fn drop(&mut self) { 535 | if let Some(td) = self.tempdir.take() { 536 | let path = td.path().to_owned(); 537 | if let Err(e) = td.close() { 538 | tracing::warn!( 539 | path = ?path, 540 | error = %e, 541 | "unable to delete temporary directory", 542 | ); 543 | } 544 | } 545 | } 546 | } 547 | -------------------------------------------------------------------------------- /src/minimize.rs: -------------------------------------------------------------------------------- 1 | use crate::{Ctx, Path, PathBuf, SectionKind, util::canonicalize}; 2 | use anyhow::Context as _; 3 | 4 | pub struct MinimizeConfig { 5 | pub include_debug_libs: bool, 6 | pub include_debug_symbols: bool, 7 | pub enable_symlinks: bool, 8 | pub use_winsysroot_style: bool, 9 | pub preserve_ms_arch_notation: bool, 10 | pub splat_output: PathBuf, 11 | pub copy: bool, 12 | pub minimize_output: Option, 13 | pub map: PathBuf, 14 | pub target: String, 15 | pub manifest_path: PathBuf, 16 | pub preserve_strace: bool, 17 | } 18 | 19 | #[derive(Default)] 20 | pub struct FileCounts { 21 | pub bytes: u64, 22 | pub count: u32, 23 | } 24 | 25 | pub struct FileNumbers { 26 | /// The counts for the total set of files 27 | pub total: FileCounts, 28 | /// The counts for the used set of files 29 | pub used: FileCounts, 30 | } 31 | 32 | pub struct MinimizeResults { 33 | pub crt_headers: FileNumbers, 34 | pub crt_libs: FileNumbers, 35 | pub sdk_headers: FileNumbers, 36 | pub sdk_libs: FileNumbers, 37 | } 38 | 39 | pub(crate) fn minimize( 40 | _ctx: std::sync::Arc, 41 | config: MinimizeConfig, 42 | roots: crate::splat::SplatRoots, 43 | sdk_version: &str, 44 | ) -> anyhow::Result { 45 | let mut used_paths: std::collections::BTreeMap< 46 | PathBuf, 47 | (SectionKind, std::collections::BTreeSet), 48 | > = std::collections::BTreeMap::new(); 49 | 50 | let (used, total) = rayon::join( 51 | || -> anyhow::Result<_> { 52 | // Clean the output for the package, otherwise we'll miss headers if 53 | // C/C++ code has already been built 54 | let mut clean = std::process::Command::new("cargo"); 55 | 56 | clean.args([ 57 | "clean", 58 | "--target", 59 | &config.target, 60 | "--manifest-path", 61 | config.manifest_path.as_str(), 62 | ]); 63 | if !clean.status().is_ok_and(|s| s.success()) { 64 | tracing::error!("failed to clean cargo target directory"); 65 | } 66 | 67 | // Use a temporary (hopefully ramdisk) file to store the actual output 68 | // from strace, and just let the output from the build itself go 69 | // to stderr as normal 70 | let td = tempfile::tempdir().context("failed to create strace output file")?; 71 | let strace_output_path = td.path().join("strace_output.txt"); 72 | 73 | if config.preserve_strace { 74 | let path = td.keep(); 75 | tracing::info!("strace output {}", path.display()); 76 | } 77 | 78 | let mut strace = std::process::Command::new("strace"); 79 | strace.args([ 80 | // Follow forks, cargo spawns clang/lld 81 | "-f", 82 | // We only care about opens 83 | "-e", 84 | "trace=openat", 85 | "-o", 86 | ]); 87 | strace.arg(&strace_output_path); 88 | strace.args([ 89 | "cargo", 90 | "build", 91 | "--target", 92 | &config.target, 93 | "--manifest-path", 94 | config.manifest_path.as_str(), 95 | ]); 96 | 97 | let splat_root = canonicalize(&config.splat_output)?; 98 | 99 | let includes = format!( 100 | "-Wno-unused-command-line-argument -fuse-ld=lld-link /vctoolsdir {splat_root}/crt /winsdkdir {splat_root}/sdk" 101 | ); 102 | 103 | let mut libs = format!( 104 | "-C linker=lld-link -Lnative={splat_root}/crt/lib/x86_64 -Lnative={splat_root}/sdk/lib/um/x86_64 -Lnative={splat_root}/sdk/lib/ucrt/x86_64" 105 | ); 106 | 107 | let rust_flags_env = format!( 108 | "CARGO_TARGET_{}_RUSTFLAGS", 109 | config.target.replace('-', "_").to_uppercase() 110 | ); 111 | 112 | // Sigh, some people use RUSTFLAGS to enable hidden library features, incredibly annoying 113 | if let Ok(rf) = std::env::var(&rust_flags_env) { 114 | libs.push(' '); 115 | libs.push_str(&rf); 116 | } else if let Ok(rf) = std::env::var("RUSTFLAGS") { 117 | libs.push(' '); 118 | libs.push_str(&rf); 119 | } 120 | 121 | let triple = config.target.replace('-', "_"); 122 | 123 | let cc_env = [ 124 | (format!("CC_{triple}"), "clang-cl"), 125 | (format!("CXX_{triple}"), "clang-cl"), 126 | (format!("AR_{triple}"), "llvm-lib"), 127 | (format!("CFLAGS_{triple}"), &includes), 128 | (format!("CXXFLAGS_{triple}"), &includes), 129 | (rust_flags_env, &libs), 130 | ]; 131 | 132 | strace.envs(cc_env); 133 | 134 | tracing::info!("compiling {}", config.manifest_path); 135 | 136 | let mut child = strace.spawn().context("unable to start strace")?; 137 | 138 | let (tx, rx) = crossbeam_channel::unbounded(); 139 | 140 | // This should happen quickly 141 | let strace_output = { 142 | let start = std::time::Instant::now(); 143 | let max = std::time::Duration::from_secs(10); 144 | loop { 145 | match std::fs::File::open(&strace_output_path) { 146 | Ok(f) => break f, 147 | Err(err) => { 148 | if start.elapsed() > max { 149 | anyhow::bail!( 150 | "failed to open strace output '{}' after waiting for {max:?}: {err}", 151 | strace_output_path.display() 152 | ); 153 | } 154 | 155 | std::thread::sleep(std::time::Duration::from_millis(10)); 156 | } 157 | } 158 | } 159 | }; 160 | 161 | let mut output = std::io::BufReader::new(strace_output); 162 | 163 | let (_, counts) = rayon::join( 164 | move || -> anyhow::Result<()> { 165 | use std::io::BufRead; 166 | let mut line = String::new(); 167 | 168 | // We cannot use read_line/read_until here as Rust's BufRead 169 | // will end a line on either the delimiter OR EOF, and since 170 | // the file is being written to while we are reading, it is 171 | // almost guaranteed we will hit EOF 1 or more times before 172 | // an actual line is completed, given a large enough trace, 173 | // so we roll our own 174 | let mut read_line = |line: &mut String| -> anyhow::Result { 175 | let buf = unsafe { line.as_mut_vec() }; 176 | loop { 177 | let (done, used) = { 178 | let available = match output.fill_buf() { 179 | Ok(n) => n, 180 | Err(ref e) if e.kind() == std::io::ErrorKind::Interrupted => { 181 | continue; 182 | } 183 | Err(e) => anyhow::bail!(e), 184 | }; 185 | if let Some(i) = memchr::memchr(b'\n', available) { 186 | buf.extend_from_slice(&available[..=i]); 187 | (true, i + 1) 188 | } else { 189 | buf.extend_from_slice(available); 190 | (false, available.len()) 191 | } 192 | }; 193 | output.consume(used); 194 | if done { 195 | return Ok(true); 196 | } else if used == 0 197 | && child.try_wait().context("compile child failed")?.is_some() 198 | { 199 | return Ok(false); 200 | } 201 | } 202 | }; 203 | 204 | loop { 205 | line.clear(); 206 | if !read_line(&mut line)? { 207 | break; 208 | } 209 | 210 | let Some(i) = line.find("openat(AT_FDCWD, \"") else { 211 | continue; 212 | }; 213 | let Some(open) = line[i + 18..].split_once('"') else { 214 | continue; 215 | }; 216 | 217 | // We can immediately skip files that were unable to be opened, 218 | // but many file opens will be asynchronous so this won't 219 | // catch all of them, but that's fine since we check for 220 | // existence in the other thread 221 | if open.1.contains("-1 NOENT (") { 222 | continue; 223 | } 224 | 225 | let _ = tx.send(open.0.to_owned()); 226 | } 227 | 228 | drop(tx); 229 | let status = child.wait()?; 230 | anyhow::ensure!(status.success(), "compilation failed"); 231 | 232 | Ok(()) 233 | }, 234 | || { 235 | let mut crt_headers = FileCounts::default(); 236 | let mut crt_libs = FileCounts::default(); 237 | let mut sdk_headers = FileCounts::default(); 238 | let mut sdk_libs = FileCounts::default(); 239 | 240 | let sdk_root = canonicalize(&roots.sdk).unwrap(); 241 | let crt_root = canonicalize(&roots.crt).unwrap(); 242 | 243 | while let Ok(path) = rx.recv() { 244 | let path = PathBuf::from(path); 245 | let (hdrs, libs, is_sdk) = if path.starts_with(&sdk_root) { 246 | (&mut sdk_headers, &mut sdk_libs, true) 247 | } else if path.starts_with(&crt_root) { 248 | (&mut crt_headers, &mut crt_libs, false) 249 | } else { 250 | continue; 251 | }; 252 | 253 | let (counts, which) = match path.extension() { 254 | // Some headers don't have extensions, eg ciso646 255 | Some("h" | "hpp") | None => ( 256 | hdrs, 257 | if is_sdk { 258 | SectionKind::SdkHeader 259 | } else { 260 | SectionKind::CrtHeader 261 | }, 262 | ), 263 | Some("lib" | "Lib") => ( 264 | libs, 265 | if is_sdk { 266 | SectionKind::SdkLib 267 | } else { 268 | SectionKind::CrtLib 269 | }, 270 | ), 271 | _ => continue, 272 | }; 273 | 274 | let mut insert = |path: PathBuf, symlink: Option| { 275 | if let Some((_, sls)) = used_paths.get_mut(&path) { 276 | sls.extend(symlink); 277 | return; 278 | } 279 | 280 | let Ok(md) = std::fs::metadata(&path) else { 281 | // clang will probe paths according to the include directory ordering, 282 | // and while we filter on NOENT in the strace output, in many 283 | // cases the opens are async and thus will be split over multiple lines, 284 | // and while we _could_ keep a small buffer to pair the open results 285 | // with the original thread that queued it, it's simpler to just ignore 286 | // paths that managed to get here that don't actually exist 287 | return; 288 | }; 289 | 290 | if !md.is_file() { 291 | return; 292 | } 293 | 294 | counts.bytes += md.len(); 295 | counts.count += 1; 296 | 297 | used_paths 298 | .entry(path) 299 | .or_insert_with(|| (which, Default::default())) 300 | .1 301 | .extend(symlink); 302 | }; 303 | 304 | if path.is_symlink() { 305 | // We're the ones creating symlinks and they are always utf-8 306 | let sl = std::fs::read_link(&path).expect("failed to read symlink"); 307 | let sl = PathBuf::from_path_buf(sl).expect("symlink path was non-utf8"); 308 | 309 | let resolved = path.parent().unwrap().join(sl); 310 | insert(resolved, Some(path.file_name().unwrap().to_owned())); 311 | } else { 312 | insert(path, None); 313 | } 314 | } 315 | 316 | (crt_headers, crt_libs, sdk_headers, sdk_libs) 317 | }, 318 | ); 319 | 320 | Ok(counts) 321 | }, 322 | || { 323 | let walk = |root: &Path| { 324 | let mut hdrs = FileCounts::default(); 325 | let mut libs = FileCounts::default(); 326 | let mut symlinks = 327 | std::collections::BTreeMap::>::new( 328 | ); 329 | 330 | let root = canonicalize(root).unwrap(); 331 | 332 | for entry in walkdir::WalkDir::new(root) 333 | .into_iter() 334 | .filter_map(|e| e.ok()) 335 | { 336 | let Some(path) = Path::from_path(entry.path()) else { 337 | continue; 338 | }; 339 | 340 | if entry.file_type().is_dir() { 341 | continue; 342 | } 343 | 344 | if entry.path_is_symlink() { 345 | let Ok(rp) = std::fs::read_link(path) else { 346 | continue; 347 | }; 348 | 349 | if !path.is_file() { 350 | continue; 351 | } 352 | 353 | let Ok(real_path) = PathBuf::from_path_buf(rp) else { 354 | continue; 355 | }; 356 | 357 | symlinks 358 | .entry(path.parent().unwrap().join(real_path)) 359 | .or_default() 360 | .insert(path.file_name().unwrap().to_owned()); 361 | } else { 362 | let Ok(md) = entry.metadata() else { 363 | continue; 364 | }; 365 | 366 | let which = match path.extension() { 367 | Some("h" | "idl" | "hpp") | None => &mut hdrs, 368 | Some("lib" | "Lib") => &mut libs, 369 | _ => continue, 370 | }; 371 | 372 | which.bytes += md.len(); 373 | which.count += 1; 374 | } 375 | } 376 | 377 | (hdrs, libs, symlinks) 378 | }; 379 | 380 | let (sdk, crt) = rayon::join(|| walk(&roots.sdk), || walk(&roots.crt)); 381 | 382 | let mut symlinks = sdk.2; 383 | let mut crt_symlinks = crt.2; 384 | symlinks.append(&mut crt_symlinks); 385 | 386 | (crt.0, crt.1, sdk.0, sdk.1, symlinks) 387 | }, 388 | ); 389 | 390 | let used = used.context("unable to determine used file set")?; 391 | let symlinks = total.4; 392 | let root = canonicalize(&roots.root).unwrap(); 393 | 394 | let mut additional_symlinks = 0; 395 | 396 | // For libraries, there are cases where strace doesn't actually detect 397 | // all the symlinks from which they are referenced, so just be conservative 398 | // and add all of them 399 | for (p, sls) in symlinks { 400 | if let Some((_, symlinks)) = used_paths.get_mut(&p) { 401 | let before = symlinks.len(); 402 | symlinks.extend(sls); 403 | additional_symlinks += symlinks.len() - before; 404 | } 405 | } 406 | 407 | tracing::info!("added {additional_symlinks} additional symlinks"); 408 | 409 | let (serialize, mv) = rayon::join( 410 | || -> anyhow::Result<()> { 411 | let cur_map = if config.map.exists() { 412 | match std::fs::read_to_string(&config.map) { 413 | Ok(contents) => match toml::from_str::(&contents) { 414 | Ok(t) => Some(t), 415 | Err(err) => { 416 | tracing::error!( 417 | path = config.map.as_str(), 418 | error = ?err, 419 | "failed to deserialize map file" 420 | ); 421 | None 422 | } 423 | }, 424 | Err(err) => { 425 | tracing::error!( 426 | path = config.map.as_str(), 427 | error = ?err, 428 | "failed to read map file" 429 | ); 430 | None 431 | } 432 | } 433 | } else { 434 | None 435 | }; 436 | 437 | let mut map = cur_map.unwrap_or_default(); 438 | 439 | // We _could_ keep the original filters, but that would mean that the 440 | // user could just accumulate things over time that they aren't 441 | // actually using any longer, if this file is in source control then 442 | // they can just revert the changes if a file that was previously in 443 | // the list was removed 444 | map.clear(); 445 | 446 | let crt_hdr_prefix = roots.crt.join("include"); 447 | let crt_lib_prefix = roots.crt.join("lib"); 448 | let sdk_hdr_prefix = { 449 | let mut sp = roots.sdk.clone(); 450 | sp.push("Include"); 451 | sp.push(sdk_version); 452 | sp 453 | }; 454 | let sdk_lib_prefix = roots.sdk.join("lib"); 455 | 456 | for (p, (which, sls)) in &used_paths { 457 | let (prefix, section) = match which { 458 | SectionKind::SdkHeader => (&sdk_hdr_prefix, &mut map.sdk.headers), 459 | SectionKind::SdkLib => (&sdk_lib_prefix, &mut map.sdk.libs), 460 | SectionKind::CrtHeader => (&crt_hdr_prefix, &mut map.crt.headers), 461 | SectionKind::CrtLib => (&crt_lib_prefix, &mut map.crt.libs), 462 | SectionKind::VcrDebug => (&roots.vcrd, &mut map.vcrd.libs), 463 | }; 464 | 465 | let path = p 466 | .strip_prefix(prefix) 467 | .with_context(|| { 468 | format!("path {p} did not begin with expected prefix {prefix}") 469 | }) 470 | .unwrap() 471 | .as_str() 472 | .to_owned(); 473 | 474 | if sls.is_empty() { 475 | section.filter.insert(path); 476 | continue; 477 | } 478 | 479 | section.filter.insert(path.clone()); 480 | section.symlinks.insert(path, sls.iter().cloned().collect()); 481 | } 482 | 483 | let serialized = toml::to_string_pretty(&map).unwrap(); 484 | 485 | if let Err(err) = std::fs::write(&config.map, serialized) { 486 | tracing::error!( 487 | path = config.map.as_str(), 488 | error = ?err, 489 | "failed to write map file" 490 | ); 491 | } 492 | 493 | Ok(()) 494 | }, 495 | || -> anyhow::Result<()> { 496 | let Some(od) = config.minimize_output else { 497 | return Ok(()); 498 | }; 499 | 500 | if od.exists() { 501 | std::fs::remove_dir_all(&od).context("failed to clean output directory")?; 502 | } 503 | 504 | let mv = |up: &Path| -> anyhow::Result { 505 | let np = od.join(up.strip_prefix(&root).unwrap()); 506 | 507 | std::fs::create_dir_all(np.parent().unwrap()) 508 | .context("failed to create directories")?; 509 | 510 | if config.copy { 511 | std::fs::copy(up, &np) 512 | .with_context(|| format!("failed to copy {up} => {np}"))?; 513 | } else { 514 | std::fs::rename(up, &np) 515 | .with_context(|| format!("failed to move {up} => {np}"))?; 516 | } 517 | 518 | Ok(np) 519 | }; 520 | 521 | for (up, (_, sls)) in &used_paths { 522 | let np = mv(up)?; 523 | 524 | for sl in sls { 525 | let sl = np.parent().unwrap().join(sl); 526 | crate::symlink(np.file_name().unwrap(), &sl) 527 | .context("failed to create link")?; 528 | } 529 | } 530 | 531 | Ok(()) 532 | }, 533 | ); 534 | 535 | serialize?; 536 | mv?; 537 | 538 | Ok(MinimizeResults { 539 | crt_headers: FileNumbers { 540 | total: total.0, 541 | used: used.0, 542 | }, 543 | crt_libs: FileNumbers { 544 | total: total.1, 545 | used: used.1, 546 | }, 547 | sdk_headers: FileNumbers { 548 | total: total.2, 549 | used: used.2, 550 | }, 551 | sdk_libs: FileNumbers { 552 | total: total.3, 553 | used: used.3, 554 | }, 555 | }) 556 | } 557 | -------------------------------------------------------------------------------- /src/unpack.rs: -------------------------------------------------------------------------------- 1 | use crate::{Ctx, Error, Path, PathBuf, download::PayloadContents}; 2 | use anyhow::Context as _; 3 | 4 | #[derive(serde::Serialize, serde::Deserialize)] 5 | pub(crate) struct UnpackMeta { 6 | #[serde(serialize_with = "crate::util::serialize_sha256")] 7 | pub(crate) sha256: crate::util::Sha256, 8 | pub(crate) compressed: u64, 9 | pub(crate) decompressed: u64, 10 | pub(crate) num_files: u32, 11 | } 12 | 13 | #[derive(Debug)] 14 | pub(crate) struct FileTree { 15 | pub(crate) files: Vec<(PathBuf, u64)>, 16 | pub(crate) dirs: Vec<(PathBuf, FileTree)>, 17 | } 18 | 19 | impl FileTree { 20 | fn new() -> Self { 21 | Self { 22 | files: Vec::new(), 23 | dirs: Vec::new(), 24 | } 25 | } 26 | 27 | fn push(&mut self, path: &Path, size: u64) { 28 | let fname = path.file_name().unwrap(); 29 | let mut tree = self; 30 | 31 | for comp in path.iter() { 32 | if comp != fname { 33 | #[allow(clippy::single_match_else)] 34 | match tree.dirs.iter().position(|(dir, _tree)| dir == comp) { 35 | Some(t) => tree = &mut tree.dirs[t].1, 36 | None => { 37 | tree.dirs.push((comp.into(), FileTree::new())); 38 | tree = &mut tree.dirs.last_mut().unwrap().1; 39 | } 40 | } 41 | } else { 42 | tree.files.push((fname.into(), size)); 43 | } 44 | } 45 | } 46 | 47 | pub(crate) fn stats(&self) -> (u32, u64) { 48 | self.dirs.iter().fold( 49 | ( 50 | self.files.len() as u32, 51 | self.files.iter().map(|(_, size)| *size).sum(), 52 | ), 53 | |(num_files, size), tree| { 54 | let stats = tree.1.stats(); 55 | (num_files + stats.0, size + stats.1) 56 | }, 57 | ) 58 | } 59 | 60 | pub(crate) fn subtree(&self, path: &Path) -> Option<&FileTree> { 61 | let mut tree = self; 62 | 63 | for comp in path.iter() { 64 | match tree.dirs.iter().find(|dir| dir.0 == comp) { 65 | Some(t) => tree = &t.1, 66 | None => return None, 67 | } 68 | } 69 | 70 | Some(tree) 71 | } 72 | } 73 | 74 | fn read_unpack_dir(root: PathBuf) -> Result { 75 | let mut root_tree = FileTree::new(); 76 | 77 | fn read(src: PathBuf, tree: &mut FileTree) -> Result<(), Error> { 78 | for entry in std::fs::read_dir(&src).with_context(|| format!("unable to read {src}"))? { 79 | let entry = entry.with_context(|| format!("unable to read entry from {src}"))?; 80 | 81 | let src_name = PathBuf::from_path_buf(entry.file_name().into()).map_err(|_pb| { 82 | anyhow::anyhow!( 83 | "src path {} is not a valid utf-8 path", 84 | entry.path().display() 85 | ) 86 | })?; 87 | 88 | if src_name == ".unpack" { 89 | continue; 90 | } 91 | 92 | let metadata = entry.metadata().with_context(|| { 93 | format!("unable to get metadata for {}", entry.path().display()) 94 | })?; 95 | 96 | let ft = metadata.file_type(); 97 | 98 | if ft.is_dir() { 99 | let mut dir_tree = FileTree::new(); 100 | read(src.join(&src_name), &mut dir_tree)?; 101 | 102 | tree.dirs.push((src_name, dir_tree)); 103 | } else if ft.is_file() { 104 | tree.files.push((src_name, metadata.len())); 105 | } else if ft.is_symlink() { 106 | anyhow::bail!( 107 | "detected symlink {} in source directory which should be impossible", 108 | entry.path().display() 109 | ); 110 | } 111 | } 112 | 113 | Ok(()) 114 | } 115 | 116 | read(root, &mut root_tree)?; 117 | 118 | Ok(root_tree) 119 | } 120 | 121 | pub(crate) fn unpack( 122 | ctx: std::sync::Arc, 123 | item: &crate::WorkItem, 124 | contents: PayloadContents, 125 | ) -> Result { 126 | item.progress.reset(); 127 | item.progress.set_message("📂 unpacking..."); 128 | 129 | let output_dir = match ctx.prep_unpack(&item.payload)? { 130 | crate::ctx::Unpack::Present { output_dir, .. } => { 131 | return read_unpack_dir(output_dir); 132 | } 133 | crate::ctx::Unpack::Needed(od) => od, 134 | }; 135 | 136 | let pkg = &item.payload.filename; 137 | 138 | let (tree, compressed) = match contents { 139 | PayloadContents::Vsix(vsix) => { 140 | let mut tree = FileTree::new(); 141 | 142 | let mut zip = zip::ZipArchive::new(std::io::Cursor::new(vsix)) 143 | .with_context(|| format!("invalid zip {pkg}"))?; 144 | 145 | // VSIX files are just a "specially" formatted zip file, all 146 | // of the actual files we want are under "Contents" 147 | let mut to_extract = Vec::new(); 148 | let mut total_uncompressed = 0; 149 | 150 | for findex in 0..zip.len() { 151 | let file = zip.by_index_raw(findex)?; 152 | 153 | let fname = file.name(); 154 | 155 | if fname.starts_with("Contents/") 156 | && (fname.contains("lib") || fname.contains("include")) 157 | { 158 | to_extract.push(findex); 159 | total_uncompressed += file.size(); 160 | } 161 | } 162 | 163 | item.progress.set_length(total_uncompressed); 164 | 165 | let mut total_compressed = 0; 166 | 167 | for findex in to_extract { 168 | let mut file = zip.by_index(findex).unwrap(); 169 | let zip_path = Path::new(file.name()); 170 | let mut fs_path = output_dir.clone(); 171 | 172 | for comp in zip_path 173 | .components() 174 | .skip_while(|comp| comp.as_str() != "lib" && comp.as_str() != "include") 175 | { 176 | fs_path.push(comp); 177 | } 178 | 179 | if let Some(parent) = fs_path.parent() 180 | && !parent.exists() 181 | { 182 | std::fs::create_dir_all(parent) 183 | .with_context(|| format!("unable to create unpack dir '{parent}'"))?; 184 | } 185 | 186 | let mut dest = std::fs::File::create(&fs_path).with_context(|| { 187 | format!( 188 | "unable to create {fs_path} to decompress {} from {pkg}", 189 | file.name(), 190 | ) 191 | })?; 192 | 193 | let decompressed = std::io::copy(&mut file, &mut dest).with_context(|| { 194 | format!( 195 | "unable to decompress {} from {pkg} to {fs_path}", 196 | file.name(), 197 | ) 198 | })?; 199 | 200 | item.progress.inc(decompressed); 201 | 202 | let tree_path = fs_path.strip_prefix(&output_dir).unwrap(); 203 | tree.push(tree_path, decompressed); 204 | 205 | total_compressed += file.compressed_size(); 206 | } 207 | 208 | (tree, total_compressed) 209 | } 210 | PayloadContents::Msi { msi, cabs } => { 211 | let mut msi = msi::Package::open(std::io::Cursor::new(msi)) 212 | .with_context(|| format!("unable to read MSI from {pkg}"))?; 213 | 214 | // Open source ftw https://gitlab.gnome.org/GNOME/msitools/-/blob/master/tools/msiextract.vala 215 | 216 | // For some reason many filenames in the table(s) have a weird 217 | // checksum(?) filename with an extension separated from the 218 | // _actual_ filename with a `|` so we need to detect that and 219 | // strip off just the real name we want 220 | #[inline] 221 | fn fix_name(name: &msi::Value) -> Result<&str, Error> { 222 | let name = name.as_str().context("filename is not a string")?; 223 | 224 | Ok(match name.find('|') { 225 | Some(ind) => &name[ind + 1..], 226 | None => name, 227 | }) 228 | } 229 | 230 | let components = { 231 | #[derive(Debug)] 232 | struct Dir { 233 | id: String, 234 | parent: Option, 235 | path: PathBuf, 236 | } 237 | 238 | // Collect the directories that can be referenced by a component 239 | // that are reference by files. Ugh. 240 | let mut directories: Vec<_> = msi 241 | .select_rows(msi::Select::table("Directory")) 242 | .with_context(|| format!("MSI {pkg} has no 'Directory' table"))? 243 | .map(|row| -> Result<_, _> { 244 | // Columns: 245 | // 0 - Directory (name) 246 | // 1 - Directory_Parent (name of parent) 247 | // 2 - DefaultDir (location of directory on disk) 248 | // ... 249 | anyhow::ensure!(row.len() >= 3, "invalid row in 'Directory'"); 250 | 251 | Ok(Dir { 252 | id: row[0] 253 | .as_str() 254 | .context("directory name is not a string")? 255 | .to_owned(), 256 | // This can be `null` 257 | parent: row[1].as_str().map(String::from), 258 | path: fix_name(&row[2])?.into(), 259 | }) 260 | }) 261 | .collect::>() 262 | .with_context(|| format!("unable to read directories for {pkg}"))?; 263 | 264 | directories.sort_by(|a, b| a.id.cmp(&b.id)); 265 | 266 | let components: std::collections::BTreeMap<_, _> = msi 267 | .select_rows(msi::Select::table("Component")) 268 | .with_context(|| format!("MSI {pkg} has no 'Directory' table"))? 269 | .map(|row| -> Result<_, _> { 270 | // Columns: 271 | // 0 - Component (name, really, id) 272 | // 1 - ComponentId 273 | // 2 - Directory_ (directory id) 274 | anyhow::ensure!(row.len() >= 3, "invalid row in 'Component'"); 275 | 276 | // The recursion depth for directory lookup is quite shallow 277 | // typically, the full path to a file would be something like 278 | // `Program Files/Windows Kits/10/Lib/10.0.19041.0/um/x64` 279 | // but this a terrible path, so we massage it to instead be 280 | // `lib/um/x64` 281 | fn build_dir(dirs: &[Dir], id: &str, dir: &mut PathBuf) { 282 | #[allow(clippy::single_match_else)] 283 | let cur_dir = match dirs.binary_search_by(|d| d.id.as_str().cmp(id)) { 284 | Ok(i) => &dirs[i], 285 | Err(_) => { 286 | tracing::warn!("unable to find directory {id}"); 287 | return; 288 | } 289 | }; 290 | 291 | match cur_dir.path.file_name() { 292 | Some("Lib") => { 293 | dir.push("lib"); 294 | } 295 | Some("Include") => { 296 | dir.push("include"); 297 | } 298 | other => { 299 | if let Some(parent) = &cur_dir.parent { 300 | build_dir(dirs, parent, dir); 301 | } 302 | 303 | if let Some(other) = other { 304 | // Ignore the SDK version directory between 305 | // Lib/Include and the actual subdirs we care about 306 | if !other.starts_with(|c: char| c.is_ascii_digit()) { 307 | dir.push(other); 308 | } 309 | } 310 | } 311 | } 312 | } 313 | 314 | let component_id = row[0] 315 | .as_str() 316 | .context("component id is not a string")? 317 | .to_owned(); 318 | 319 | let mut dir = PathBuf::new(); 320 | build_dir( 321 | &directories, 322 | row[2] 323 | .as_str() 324 | .context("component directory is not a string")?, 325 | &mut dir, 326 | ); 327 | 328 | Ok((component_id, dir)) 329 | }) 330 | .collect::>() 331 | .with_context(|| format!("unable to read components for {pkg}"))?; 332 | 333 | components 334 | }; 335 | 336 | struct Cab { 337 | /// The max sequence number, each `File` in an MSI has a 338 | /// sequence number that maps to exactly one CAB file 339 | sequence: u32, 340 | path: PathBuf, 341 | cab: bytes::Bytes, 342 | } 343 | 344 | let cabs = { 345 | let mut cab_contents = Vec::with_capacity(cabs.len()); 346 | 347 | for cab in cabs { 348 | // Validate the cab file 349 | cab::Cabinet::new(std::io::Cursor::new(cab.content.clone())) 350 | .with_context(|| format!("CAB {} is invalid", cab.path))?; 351 | 352 | cab_contents.push(Cab { 353 | sequence: cab.sequence, 354 | path: cab.path, 355 | cab: cab.content, 356 | }); 357 | } 358 | 359 | // They are usually always sorted correctly, but you never know 360 | cab_contents.sort_by(|a, b| a.sequence.cmp(&b.sequence)); 361 | cab_contents 362 | }; 363 | 364 | anyhow::ensure!( 365 | !cabs.is_empty(), 366 | "no cab files were referenced by the MSI {}", 367 | pkg.as_str() 368 | ); 369 | 370 | struct CabFile { 371 | id: String, 372 | name: PathBuf, 373 | size: u64, 374 | sequence: u32, 375 | } 376 | 377 | let (files, uncompressed) = { 378 | let mut uncompressed = 0u64; 379 | let mut files: Vec<_> = msi 380 | .select_rows(msi::Select::table("File")) 381 | .with_context(|| format!("MSI {pkg} has no 'File' table"))? 382 | .filter_map(|row| -> Option> { 383 | // Columns: 384 | // 0 - File Id (lookup in CAB) 385 | // 1 - Component_ (target directory) 386 | // 2 - FileName 387 | // 3 - FileSize 388 | // 4 - Version 389 | // 5 - Language 390 | // 6 - Attributes 391 | // 7 - Sequence (determines which CAB file) 392 | if row.len() < 8 { 393 | return Some(Err(anyhow::anyhow!("invalid row in 'File'"))); 394 | } 395 | 396 | #[allow(clippy::blocks_in_conditions)] 397 | let (dir, fname, id, seq, size) = match || -> Result<_, Error> { 398 | let fname = fix_name(&row[2])?; 399 | let dir = components 400 | .get(row[1].as_str().context("component id was not a string")?) 401 | .with_context(|| { 402 | format!("file {} referenced an unknown component", row[2]) 403 | })?; 404 | 405 | let size = row[3].as_int().context("size is not an integer")? as u64; 406 | let id = row[0].as_str().context("File (id) is not a string")?; 407 | let seq = row[7].as_int().context("sequence is not an integer")? as u32; 408 | 409 | Ok((dir, fname, id, seq, size)) 410 | }() { 411 | Ok(items) => items, 412 | Err(e) => return Err(e).transpose(), 413 | }; 414 | 415 | if let Some(camino::Utf8Component::Normal( 416 | "Catalogs" | "bin" | "Source" | "SourceDir", 417 | )) = dir 418 | .strip_prefix(&output_dir) 419 | .ok() 420 | .and_then(|rel| rel.components().next()) 421 | { 422 | return None; 423 | } 424 | 425 | uncompressed += size; 426 | 427 | let cf = CabFile { 428 | id: id.to_owned(), 429 | name: dir.join(fname), 430 | sequence: seq, 431 | size, 432 | }; 433 | 434 | Some(Ok(cf)) 435 | }) 436 | .collect::, Error>>() 437 | .with_context(|| format!("unable to read 'File' metadata for {pkg}"))?; 438 | 439 | files.sort_by(|a, b| a.sequence.cmp(&b.sequence)); 440 | 441 | (files, uncompressed) 442 | }; 443 | 444 | item.progress.set_length(uncompressed); 445 | 446 | // Some MSIs have a lot of cabs and take an _extremely_ long time to 447 | // decompress, so we just split the files into roughly equal sized 448 | // chunks and decompress in parallel to reduce wall time 449 | let mut chunks = Vec::new(); 450 | 451 | struct Chunk { 452 | cab: bytes::Bytes, 453 | cab_index: usize, 454 | files: Vec, 455 | chunk_size: u64, 456 | } 457 | 458 | chunks.push(Chunk { 459 | cab: cabs[0].cab.clone(), 460 | cab_index: 0, 461 | files: Vec::new(), 462 | chunk_size: 0, 463 | }); 464 | 465 | let mut cur_chunk = 0; 466 | let mut cur_cab = 0; 467 | const CHUNK_SIZE: u64 = 1024 * 1024; 468 | 469 | for file in files { 470 | let chunk = &mut chunks[cur_chunk]; 471 | 472 | if chunk.chunk_size + file.size < CHUNK_SIZE 473 | && file.sequence <= cabs[cur_cab].sequence 474 | { 475 | chunk.chunk_size += file.size; 476 | chunk.files.push(file); 477 | } else { 478 | let cab = if file.sequence <= cabs[cur_cab].sequence { 479 | chunk.cab.clone() 480 | } else { 481 | match cabs[cur_cab + 1..] 482 | .iter() 483 | .position(|cab| file.sequence <= cab.sequence) 484 | { 485 | Some(i) => cur_cab += i + 1, 486 | None => anyhow::bail!( 487 | "unable to find cab file containing {} {}", 488 | file.name, 489 | file.sequence 490 | ), 491 | } 492 | 493 | cabs[cur_cab].cab.clone() 494 | }; 495 | 496 | cur_chunk += 1; 497 | chunks.push(Chunk { 498 | cab, 499 | cab_index: cur_cab, 500 | chunk_size: file.size, 501 | files: vec![file], 502 | }); 503 | } 504 | } 505 | 506 | let mut results = Vec::new(); 507 | 508 | use rayon::prelude::*; 509 | 510 | let tree = parking_lot::Mutex::new(FileTree::new()); 511 | 512 | chunks 513 | .into_par_iter() 514 | .map(|chunk| -> Result<(), Error> { 515 | let mut cab = cab::Cabinet::new(std::io::Cursor::new(chunk.cab)).unwrap(); 516 | 517 | let cab_path = &cabs[chunk.cab_index].path; 518 | 519 | for file in chunk.files { 520 | let mut cab_file = match cab.read_file(file.id.as_str()) { 521 | Ok(cf) => cf, 522 | Err(e) => Err(e).with_context(|| { 523 | format!("unable to read '{}' from {cab_path}", file.name) 524 | })?, 525 | }; 526 | 527 | let unpack_path = output_dir.join(&file.name); 528 | 529 | if let Some(parent) = unpack_path.parent() 530 | && !parent.exists() 531 | { 532 | std::fs::create_dir_all(parent)?; 533 | } 534 | 535 | let unpacked_file = std::fs::File::create(&unpack_path)?; 536 | 537 | struct Wrapper<'pb> { 538 | pb: &'pb indicatif::ProgressBar, 539 | uf: std::fs::File, 540 | } 541 | 542 | impl std::io::Write for Wrapper<'_> { 543 | fn write(&mut self, buf: &[u8]) -> std::io::Result { 544 | self.pb.inc(buf.len() as u64); 545 | self.uf.write(buf) 546 | } 547 | 548 | fn flush(&mut self) -> std::io::Result<()> { 549 | self.uf.flush() 550 | } 551 | } 552 | 553 | let size = std::io::copy( 554 | &mut cab_file, 555 | &mut Wrapper { 556 | pb: &item.progress, 557 | uf: unpacked_file, 558 | }, 559 | )?; 560 | 561 | tree.lock().push(&file.name, size); 562 | } 563 | 564 | Ok(()) 565 | }) 566 | .collect_into_vec(&mut results); 567 | 568 | (tree.into_inner(), uncompressed) 569 | } 570 | }; 571 | 572 | let tree_path = format!("{output_dir}/tree.txt"); 573 | 574 | std::fs::write(&tree_path, format!("{tree:#?}").as_bytes()) 575 | .with_context(|| format!("failed to write {tree_path}"))?; 576 | 577 | item.progress.finish_with_message("unpacked"); 578 | 579 | let (num_files, decompressed) = tree.stats(); 580 | 581 | ctx.finish_unpack( 582 | output_dir, 583 | UnpackMeta { 584 | sha256: item.payload.sha256.clone(), 585 | compressed, 586 | decompressed, 587 | num_files, 588 | }, 589 | )?; 590 | 591 | Ok(tree) 592 | } 593 | --------------------------------------------------------------------------------