diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4061f91..27fba85 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -56,17 +56,17 @@ jobs: - name: Lint with Clippy (Stable Only) if: matrix.toolchain == 'stable' - run: cargo clippy --all-targets --all-features + run: cargo clippy --all-targets --all-features --workspace - name: Check Code Formatting (Stable Only) if: matrix.toolchain == 'stable' run: cargo fmt -- --check - name: Build the Project - run: cargo build --all-features --verbose + run: cargo build --workspace --all-features --verbose - name: Run Tests - run: cargo test --all-features --verbose + run: cargo test --workspace --all-features --verbose - name: Generate Documentation (${{ matrix.DOCTYPE }}, ${{ matrix.toolchain }}) run: cargo doc --no-deps --all-features @@ -108,13 +108,16 @@ jobs: sudo apt-get update sudo apt-get install -y clang llvm mingw-w64 + - name: Ensure xtask compilable and runable on Linux + run: cargo xtask --version + - name: Set up environment for Windows cross-compilation shell: bash run: | - var_name=$(echo "${{ matrix.target.triple }}" | tr '-' '_' | tr '[:lower:]' '[:upper:]') + trple=$(echo "${{ matrix.target.triple }}" | tr '-' '_' | tr '[:lower:]' '[:upper:]') echo "Setting CARGO_TARGET_${var_name}_LINKER=${{ matrix.target.linker }}" - echo "CARGO_TARGET_${var_name}_LINKER=${{ matrix.target.linker }}" >> $GITHUB_ENV - echo "CARGO_TARGET_${var_name}_AR=${{ matrix.target.ar }}" >> $GITHUB_ENV + echo "CARGO_TARGET_${trple}_LINKER=${{ matrix.target.linker }}" >> $GITHUB_ENV + echo "CARGO_TARGET_${trple}_AR=${{ matrix.target.ar }}" >> $GITHUB_ENV - name: Build Project for target ${{ matrix.target.triple }} run: cargo build --target=${{ matrix.target.triple }} --all-features --release diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..d273e71 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,65 @@ +{ + // Utilisez IntelliSense pour en savoir plus sur les attributs possibles. + // Pointez pour afficher la description des attributs existants. + // Pour plus d'informations, visitez : https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'wslpluginapi_sys'", + "cargo": { + "args": [ + "test", + "--no-run", + "--lib", + "--package=wslpluginapi-sys" + ], + "filter": { + "name": "wslpluginapi_sys", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug executable 'xtask'", + "cargo": { + "args": [ + "build", + "--bin=xtask", + "--package=xtask" + ], + "filter": { + "name": "xtask", + "kind": "bin" + } + }, + "args": ["bindgen"], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in executable 'xtask'", + "cargo": { + "args": [ + "test", + "--no-run", + "--bin=xtask", + "--package=xtask" + ], + "filter": { + "name": "xtask", + "kind": "bin" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + } + ] +} \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index aca006e..16296fd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,11 +5,10 @@ default-members = ["wslpluginapi-sys"] [workspace.package] -name = "wslpluginapi-sys" -version = "0.1.0-beta.5.1+2.4.4" +version = "0.1.0-rc.1+2.4.4" edition = "2021" readme = "README.md" authors = ["Mickaël Véril "] description = "Rust bindings for the WSL Plugin API" -license = "MIT" +license = "Apache-2.0 OR MIT" repository = "https://github.com/mveril/wslpluginapi-sys" diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 0000000..c1215de --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2025 Mickaël Véril + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/LICENSE b/LICENSE-MIT similarity index 100% rename from LICENSE rename to LICENSE-MIT diff --git a/README.MD b/README.MD index 7d69d6d..2c52bbf 100644 --- a/README.MD +++ b/README.MD @@ -17,12 +17,8 @@ ## Prerequisites Before using `wslpluginapi-sys`, ensure you have the following installed: - - **Rust**: Latest stable version. -### Optional - -- **NuGet**: Use the NuGet CLI in the xtask nuget process to download NuGet (if not present, we use reqwest to do this process). ## Installation @@ -39,10 +35,21 @@ This crate provides unsafe bindings that closely follow the original C API. User ## NuGet package dependency -This project depends on a third-party dependency called [Microsoft.WSL.PluginApi](https://www.nuget.org/packages/Microsoft.WSL.PluginApi) from Microsoft, available on NuGet and providing bindings for it. To upgrade it, change the version build metadata of the project and run `cargo xtask nuget` (don't forget to commit changes generated from the xtask). This xtask extracts all the needed content from the NuGet package. For more info see `THIRD-PARTY-NOTICES.md` +This project depends on a third-party dependency called [Microsoft.WSL.PluginApi](https://www.nuget.org/packages/Microsoft.WSL.PluginApi) from Microsoft, available on NuGet and providing bindings for it. +## xtask bindgen +To upgrade it, change the version build metadata of the project to the target `Microsoft.WSL.PluginApi` version and run `cargo xtask bindgen` (don't forget to commit changes generated from the xtask). This xtask download the nuget package and run bindgen on the header to compute a rust file used as binding. This allow offline build with no need of the windows header at build time. All the output of this task is added to a `build` folder that need to be commit This folder contain a metadata file and a checksum file. +The information of this metadata file and checksum file are used at build time to detect the path of the binding and check the integrity (corect checksum and version correspondance). + +### Requirement + +To run this command you require the following dependencies +- llvm +- rustfmt (from rustup) +- Nuget CLI + ## License -This project is licensed under the MIT License. See the LICENSE file for details. +This project is Licensed under either MIT or Apache-2.0, at your option. See the LICENSE file for details. Note: This crate is part of the [WSLPlugins-rs](https://github.com/mveril/wslplugins-rs) project, which aims to create an idiomatic Rust framework for developing WSL plugins. diff --git a/THIRD-PARTY-NOTICES.md b/THIRD-PARTY-NOTICES.md deleted file mode 100644 index e236560..0000000 --- a/THIRD-PARTY-NOTICES.md +++ /dev/null @@ -1,22 +0,0 @@ -# Third Party Notices - -## wslpluginapi-sys - -### Microsoft.WSL.PluginApi - -**Version :** **2.4.4** - -**License :** MIT - -**Source:** -[Microsoft.WSL.PluginApi](https://www.nuget.org/packages/Microsoft.WSL.PluginApi/2.4.4) - -#### Included files from the package. - -- `wslpluginapi-sys/third_party/Microsoft.WSL.PluginApi/include/WslPluginApi.h` unmodified -- `wslpluginapi-sys/third_party/Microsoft.WSL.PluginApi/README.MD` unmodified -- `wslpluginapi-sys/third_party/Microsoft.WSL.PluginApi/LICENSE` generated from package metadata - -**copyright:** - -> © Microsoft Corporation. All rights reserved. diff --git a/wslpluginapi-sys/Cargo.toml b/wslpluginapi-sys/Cargo.toml index e0444fb..a28e6da 100644 --- a/wslpluginapi-sys/Cargo.toml +++ b/wslpluginapi-sys/Cargo.toml @@ -6,19 +6,20 @@ readme.workspace = true authors.workspace = true description.workspace = true license.workspace = true -publish-lockfile = false repository.workspace = true keywords = ["wsl", "plugin", "windows", "linux", "ffi"] categories = ["os::windows-apis", "external-ffi-bindings", "virtualization"] -build = "build/main.rs" [features] hooks-field-names = ["dep:struct-field-names-as-array"] [build-dependencies] -bindgen = "0.72" -cfg-if = "1.0" -constcat = "0.6" +serde_json = "1.0" +serde = { version = "1.0", features = ["derive"] } +semver = "1.0" +sha2 = "0.10.9" +anyhow = "1.0" +hex = "0.4" [target.'cfg(unix)'.build-dependencies] cow-utils = { version = "0.1" } diff --git a/wslpluginapi-sys/THIRD-PARTY-NOTICES.md b/wslpluginapi-sys/THIRD-PARTY-NOTICES.md deleted file mode 100644 index edb68a4..0000000 --- a/wslpluginapi-sys/THIRD-PARTY-NOTICES.md +++ /dev/null @@ -1,20 +0,0 @@ -# Third Party Notices - -## Microsoft.WSL.PluginApi - -**Version :** **2.4.4** - -**License :** MIT - -**Source:** -[Microsoft.WSL.PluginApi](https://www.nuget.org/packages/Microsoft.WSL.PluginApi/2.4.4) - -### Included files from the package. - -- `third_party/Microsoft.WSL.PluginApi/include/WslPluginApi.h` unmodified -- `third_party/Microsoft.WSL.PluginApi/README.MD` unmodified -- `third_party/Microsoft.WSL.PluginApi/LICENSE` generated from package metadata - -**copyright:** - -> © Microsoft Corporation. All rights reserved. diff --git a/wslpluginapi-sys/build.rs b/wslpluginapi-sys/build.rs new file mode 100644 index 0000000..67d5831 --- /dev/null +++ b/wslpluginapi-sys/build.rs @@ -0,0 +1,100 @@ +use sha2::{digest::Output, Digest, Sha256}; +use std::{ + collections::HashMap, + env, + fs::{self, File}, + io::Write, + io::{self, BufRead, BufReader}, +}; + +use std::path::{Path, PathBuf}; + +use semver::Version; +use serde::Deserialize; + +#[derive(Deserialize, Debug)] +pub struct Metadata { + pub header_version: String, + pub output_file_path: String, +} + +fn main() -> anyhow::Result<()> { + let root: PathBuf = env::var_os("CARGO_MANIFEST_DIR").unwrap().into(); + let build = root.join("build"); + let hashes: HashMap> = fs::read_dir(&build)? + .filter_map(Result::ok) + .map(|entry| entry.path()) + .filter(|path| path.is_file()) + .filter(|path| path.extension().is_none_or(|ext| ext != "sha256")) + .filter(|path| { + !path + .file_name() + .and_then(|name| name.to_str()) + .map(|name| name.starts_with(".git")) + .unwrap_or(false) + }) + .filter_map(|path| { + compute_hash::(&path) + .ok() + .map(|hash| (path.strip_prefix(&build).unwrap().to_owned(), hash)) + }) + .collect(); + let reader = BufReader::new(File::open(root.join(build.join("checksum.sha256")))?); + let mut hash_file = HashMap::, Box>::new(); + for line in reader.lines() { + let line = line?; + if line.is_empty() { + continue; + } + let mut splits = line.split_whitespace().take(2); + if let Some(val) = splits.next() { + if let Some(key) = splits.next() { + hash_file.insert( + key.to_owned().into_boxed_str(), + val.to_owned().into_boxed_str(), + ); + } + } + } + for (file, hash) in hashes { + let path_str = file.as_os_str().to_str().expect("Failed to convert path"); + + let mem_hash = hash_file + .get(path_str) + .unwrap_or_else(|| panic!("No hash in the checksum file for {}", &path_str)); + + if hex::encode(hash) != mem_hash.as_ref() { + panic!("Hash not ok for file {}", &path_str); + } + } + + let current_version: Version = env!("CARGO_PKG_VERSION") + .parse() + .expect("Failed to parse CARGO_PKG_VERSION"); + let expected_nuget_version = current_version.build.as_str(); + let json_metadata = { + let metadata_path = root.join("build/metadata.json"); + let file = fs::File::open(metadata_path).expect("Failed to open metadata.json"); + let json: Metadata = serde_json::from_reader(file).unwrap(); + json + }; + if json_metadata.header_version != expected_nuget_version { + panic!( + "Version mismatch: metadata.json version '{}' does not match package metadata '{}'", + &json_metadata.header_version, expected_nuget_version + ); + } + let output_full_path = build.join(json_metadata.output_file_path); + println!( + "cargo:rustc-env=RUST_BINDING={}", + output_full_path.to_str().unwrap() + ); + Ok(()) +} + +fn compute_hash>(path: P) -> io::Result> { + let mut file = File::open(path.as_ref())?; + let mut hasher = D::new(); + io::copy(&mut file, &mut hasher)?; + Ok(hasher.finalize()) +} diff --git a/wslpluginapi-sys/build/.gitattributes b/wslpluginapi-sys/build/.gitattributes new file mode 100644 index 0000000..5378fe0 --- /dev/null +++ b/wslpluginapi-sys/build/.gitattributes @@ -0,0 +1 @@ +* -text \ No newline at end of file diff --git a/wslpluginapi-sys/build/WslPluginApi.rs b/wslpluginapi-sys/build/WslPluginApi.rs new file mode 100644 index 0000000..cbd22b3 --- /dev/null +++ b/wslpluginapi-sys/build/WslPluginApi.rs @@ -0,0 +1,254 @@ +/* automatically generated by rust-bindgen 0.72.0 */ + +use windows::core::*; +use windows::Win32::Foundation::*; +use windows::Win32::Networking::WinSock::SOCKET; +use windows::Win32::Security::*; +#[allow(clippy::upper_case_acronyms)] +type LPCWSTR = PCWSTR; +#[allow(clippy::upper_case_acronyms)] +type LPCSTR = PCSTR; +#[allow(clippy::upper_case_acronyms)] +type DWORD = u32; +#[cfg(feature = "hooks-field-names")] +use struct_field_names_as_array::FieldNamesAsSlice; + +#[repr(C)] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub struct WSLVersion { + pub Major: u32, + pub Minor: u32, + pub Revision: u32, +} +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + ["Size of WSLVersion"][::std::mem::size_of::() - 12usize]; + ["Alignment of WSLVersion"][::std::mem::align_of::() - 4usize]; + ["Offset of field: WSLVersion::Major"][::std::mem::offset_of!(WSLVersion, Major) - 0usize]; + ["Offset of field: WSLVersion::Minor"][::std::mem::offset_of!(WSLVersion, Minor) - 4usize]; + ["Offset of field: WSLVersion::Revision"] + [::std::mem::offset_of!(WSLVersion, Revision) - 8usize]; +}; +pub const WSLUserConfiguration_None: WSLUserConfiguration = 0; +pub const WSLUserConfiguration_WSLUserConfigurationCustomKernel: WSLUserConfiguration = 1; +pub const WSLUserConfiguration_WSLUserConfigurationCustomKernelCommandLine: WSLUserConfiguration = + 2; +pub type WSLUserConfiguration = ::std::os::raw::c_int; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct WSLVmCreationSettings { + pub CustomConfigurationFlags: WSLUserConfiguration, +} +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + ["Size of WSLVmCreationSettings"][::std::mem::size_of::() - 4usize]; + ["Alignment of WSLVmCreationSettings"] + [::std::mem::align_of::() - 4usize]; + ["Offset of field: WSLVmCreationSettings::CustomConfigurationFlags"] + [::std::mem::offset_of!(WSLVmCreationSettings, CustomConfigurationFlags) - 0usize]; +}; +pub type WSLSessionId = DWORD; +#[repr(C)] +pub struct WSLSessionInformation { + pub SessionId: WSLSessionId, + pub UserToken: HANDLE, + pub UserSid: PSID, +} +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + ["Size of WSLSessionInformation"][::std::mem::size_of::() - 24usize]; + ["Alignment of WSLSessionInformation"] + [::std::mem::align_of::() - 8usize]; + ["Offset of field: WSLSessionInformation::SessionId"] + [::std::mem::offset_of!(WSLSessionInformation, SessionId) - 0usize]; + ["Offset of field: WSLSessionInformation::UserToken"] + [::std::mem::offset_of!(WSLSessionInformation, UserToken) - 8usize]; + ["Offset of field: WSLSessionInformation::UserSid"] + [::std::mem::offset_of!(WSLSessionInformation, UserSid) - 16usize]; +}; +#[repr(C)] +pub struct WSLDistributionInformation { + #[doc = " Distribution ID, guaranteed to be the same accross reboots"] + pub Id: GUID, + pub Name: LPCWSTR, + pub PidNamespace: u64, + #[doc = " Package family name, or NULL if none"] + pub PackageFamilyName: LPCWSTR, + #[doc = " Pid of the init process. Introduced in 2.0.5"] + pub InitPid: u32, + #[doc = " Type of distribution (ubuntu, debian, ...), introduced in TODO"] + pub Flavor: LPCWSTR, + #[doc = " Distribution version, introduced in TODO"] + pub Version: LPCWSTR, +} +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + ["Size of WSLDistributionInformation"] + [::std::mem::size_of::() - 64usize]; + ["Alignment of WSLDistributionInformation"] + [::std::mem::align_of::() - 8usize]; + ["Offset of field: WSLDistributionInformation::Id"] + [::std::mem::offset_of!(WSLDistributionInformation, Id) - 0usize]; + ["Offset of field: WSLDistributionInformation::Name"] + [::std::mem::offset_of!(WSLDistributionInformation, Name) - 16usize]; + ["Offset of field: WSLDistributionInformation::PidNamespace"] + [::std::mem::offset_of!(WSLDistributionInformation, PidNamespace) - 24usize]; + ["Offset of field: WSLDistributionInformation::PackageFamilyName"] + [::std::mem::offset_of!(WSLDistributionInformation, PackageFamilyName) - 32usize]; + ["Offset of field: WSLDistributionInformation::InitPid"] + [::std::mem::offset_of!(WSLDistributionInformation, InitPid) - 40usize]; + ["Offset of field: WSLDistributionInformation::Flavor"] + [::std::mem::offset_of!(WSLDistributionInformation, Flavor) - 48usize]; + ["Offset of field: WSLDistributionInformation::Version"] + [::std::mem::offset_of!(WSLDistributionInformation, Version) - 56usize]; +}; +#[repr(C)] +pub struct WslOfflineDistributionInformation { + #[doc = " Distribution ID, guaranteed to be the same accross reboots"] + pub Id: GUID, + pub Name: LPCWSTR, + #[doc = " Package family name, or NULL if none"] + pub PackageFamilyName: LPCWSTR, + #[doc = " Type of distribution (ubuntu, debian, ...), introduced in TODO"] + pub Flavor: LPCWSTR, + #[doc = " Distribution version, introduced in TODO"] + pub Version: LPCWSTR, +} +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + ["Size of WslOfflineDistributionInformation"] + [::std::mem::size_of::() - 48usize]; + ["Alignment of WslOfflineDistributionInformation"] + [::std::mem::align_of::() - 8usize]; + ["Offset of field: WslOfflineDistributionInformation::Id"] + [::std::mem::offset_of!(WslOfflineDistributionInformation, Id) - 0usize]; + ["Offset of field: WslOfflineDistributionInformation::Name"] + [::std::mem::offset_of!(WslOfflineDistributionInformation, Name) - 16usize]; + ["Offset of field: WslOfflineDistributionInformation::PackageFamilyName"] + [::std::mem::offset_of!(WslOfflineDistributionInformation, PackageFamilyName) - 24usize]; + ["Offset of field: WslOfflineDistributionInformation::Flavor"] + [::std::mem::offset_of!(WslOfflineDistributionInformation, Flavor) - 32usize]; + ["Offset of field: WslOfflineDistributionInformation::Version"] + [::std::mem::offset_of!(WslOfflineDistributionInformation, Version) - 40usize]; +}; +#[doc = " Create plan9 mount between Windows & Linux"] +pub type WSLPluginAPI_MountFolder = ::std::option::Option< + unsafe extern "C" fn( + Session: WSLSessionId, + WindowsPath: LPCWSTR, + LinuxPath: LPCWSTR, + ReadOnly: BOOL, + Name: LPCWSTR, + ) -> HRESULT, +>; +#[doc = " Execute a program in the root namespace.\n On success, 'Socket' is connected to stdin & stdout (stderr goes to dmesg) // 'Arguments' is expected to be NULL terminated"] +pub type WSLPluginAPI_ExecuteBinary = ::std::option::Option< + unsafe extern "C" fn( + Session: WSLSessionId, + Path: LPCSTR, + Arguments: *mut LPCSTR, + Socket: *mut SOCKET, + ) -> HRESULT, +>; +#[doc = " Execute a program in a user distribution\n On success, 'Socket' is connected to stdin & stdout (stderr goes to dmesg) // 'Arguments' is expected to be NULL terminated"] +pub type WSLPluginAPI_ExecuteBinaryInDistribution = ::std::option::Option< + unsafe extern "C" fn( + Session: WSLSessionId, + Distribution: *const GUID, + Path: LPCSTR, + Arguments: *mut LPCSTR, + Socket: *mut SOCKET, + ) -> HRESULT, +>; +#[doc = " Set the error message to display to the user if the VM or distribution creation fails.\n Must be called synchronously in either OnVMStarted() or OnDistributionStarted()."] +pub type WSLPluginAPI_PluginError = + ::std::option::Option HRESULT>; +#[doc = " Called when the VM has started.\n 'Session' and 'UserSettings' are only valid during while the call is in progress."] +pub type WSLPluginAPI_OnVMStarted = ::std::option::Option< + unsafe extern "C" fn( + Session: *const WSLSessionInformation, + UserSettings: *const WSLVmCreationSettings, + ) -> HRESULT, +>; +#[doc = " Called when the VM is about to stop.\n 'Session' is only valid during while the call is in progress."] +pub type WSLPluginAPI_OnVMStopping = + ::std::option::Option HRESULT>; +#[doc = " Called when a distribution has started.\n 'Session' and 'Distribution' is only valid during while the call is in progress."] +pub type WSLPluginAPI_OnDistributionStarted = ::std::option::Option< + unsafe extern "C" fn( + Session: *const WSLSessionInformation, + Distribution: *const WSLDistributionInformation, + ) -> HRESULT, +>; +#[doc = " Called when a distribution is about to stop.\n 'Session' and 'Distribution' is only valid during while the call is in progress.\n Note: It's possible that stopping a distribution fails (for instance if a file is in use).\n In this case, it's possible for this notification to be called multiple times for the same distribution."] +pub type WSLPluginAPI_OnDistributionStopping = ::std::option::Option< + unsafe extern "C" fn( + Session: *const WSLSessionInformation, + Distribution: *const WSLDistributionInformation, + ) -> HRESULT, +>; +#[doc = " Called when a distribution is registered or unregisteed.\n Returning failure will NOT cause the operation to fail."] +pub type WSLPluginAPI_OnDistributionRegistered = ::std::option::Option< + unsafe extern "C" fn( + Session: *const WSLSessionInformation, + Distribution: *const WslOfflineDistributionInformation, + ) -> HRESULT, +>; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +#[cfg_attr(feature = "hooks-field-names", derive(FieldNamesAsSlice))] +pub struct WSLPluginHooksV1 { + pub OnVMStarted: WSLPluginAPI_OnVMStarted, + pub OnVMStopping: WSLPluginAPI_OnVMStopping, + pub OnDistributionStarted: WSLPluginAPI_OnDistributionStarted, + pub OnDistributionStopping: WSLPluginAPI_OnDistributionStopping, + #[doc = " Introduced in 2.1.2"] + pub OnDistributionRegistered: WSLPluginAPI_OnDistributionRegistered, + #[doc = " Introduced in 2.1.2"] + pub OnDistributionUnregistered: WSLPluginAPI_OnDistributionRegistered, +} +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + ["Size of WSLPluginHooksV1"][::std::mem::size_of::() - 48usize]; + ["Alignment of WSLPluginHooksV1"][::std::mem::align_of::() - 8usize]; + ["Offset of field: WSLPluginHooksV1::OnVMStarted"] + [::std::mem::offset_of!(WSLPluginHooksV1, OnVMStarted) - 0usize]; + ["Offset of field: WSLPluginHooksV1::OnVMStopping"] + [::std::mem::offset_of!(WSLPluginHooksV1, OnVMStopping) - 8usize]; + ["Offset of field: WSLPluginHooksV1::OnDistributionStarted"] + [::std::mem::offset_of!(WSLPluginHooksV1, OnDistributionStarted) - 16usize]; + ["Offset of field: WSLPluginHooksV1::OnDistributionStopping"] + [::std::mem::offset_of!(WSLPluginHooksV1, OnDistributionStopping) - 24usize]; + ["Offset of field: WSLPluginHooksV1::OnDistributionRegistered"] + [::std::mem::offset_of!(WSLPluginHooksV1, OnDistributionRegistered) - 32usize]; + ["Offset of field: WSLPluginHooksV1::OnDistributionUnregistered"] + [::std::mem::offset_of!(WSLPluginHooksV1, OnDistributionUnregistered) - 40usize]; +}; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct WSLPluginAPIV1 { + pub Version: WSLVersion, + pub MountFolder: WSLPluginAPI_MountFolder, + pub ExecuteBinary: WSLPluginAPI_ExecuteBinary, + pub PluginError: WSLPluginAPI_PluginError, + #[doc = " Introduced in 2.1.2"] + pub ExecuteBinaryInDistribution: WSLPluginAPI_ExecuteBinaryInDistribution, +} +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + ["Size of WSLPluginAPIV1"][::std::mem::size_of::() - 48usize]; + ["Alignment of WSLPluginAPIV1"][::std::mem::align_of::() - 8usize]; + ["Offset of field: WSLPluginAPIV1::Version"] + [::std::mem::offset_of!(WSLPluginAPIV1, Version) - 0usize]; + ["Offset of field: WSLPluginAPIV1::MountFolder"] + [::std::mem::offset_of!(WSLPluginAPIV1, MountFolder) - 16usize]; + ["Offset of field: WSLPluginAPIV1::ExecuteBinary"] + [::std::mem::offset_of!(WSLPluginAPIV1, ExecuteBinary) - 24usize]; + ["Offset of field: WSLPluginAPIV1::PluginError"] + [::std::mem::offset_of!(WSLPluginAPIV1, PluginError) - 32usize]; + ["Offset of field: WSLPluginAPIV1::ExecuteBinaryInDistribution"] + [::std::mem::offset_of!(WSLPluginAPIV1, ExecuteBinaryInDistribution) - 40usize]; +}; +pub type WSLPluginAPI_EntryPointV1 = ::std::option::Option< + unsafe extern "C" fn(Api: *const WSLPluginAPIV1, Hooks: *mut WSLPluginHooksV1) -> HRESULT, +>; diff --git a/wslpluginapi-sys/build/checksum.sha256 b/wslpluginapi-sys/build/checksum.sha256 new file mode 100644 index 0000000..873a81c --- /dev/null +++ b/wslpluginapi-sys/build/checksum.sha256 @@ -0,0 +1,2 @@ +896ecb0b3b7e6467e6f4cda3b00201473ed7b7face1a31081671bc32b9e679d3 metadata.json +b8f7209005250c821184da6a11eefc40a84bfc9d44a34cb9bbd1fc302d62e4b4 WslPluginApi.rs diff --git a/wslpluginapi-sys/build/header_processing.rs b/wslpluginapi-sys/build/header_processing.rs deleted file mode 100644 index 0e964f7..0000000 --- a/wslpluginapi-sys/build/header_processing.rs +++ /dev/null @@ -1,118 +0,0 @@ -#[cfg(unix)] -use crate::WSL_PLUGIN_API_HEADER_FILE_NAME; -use bindgen::callbacks::{ParseCallbacks, TypeKind}; -use cfg_if::cfg_if; -#[cfg(unix)] -use cow_utils::CowUtils; -use std::{borrow::Cow, collections::HashMap, path::Path, vec}; -#[cfg(unix)] -use std::{env, fs, io::Write, path::PathBuf}; - -#[derive(Debug)] -struct BindgenCallback; - -impl BindgenCallback {} - -impl ParseCallbacks for BindgenCallback { - fn add_derives(&self, info: &bindgen::callbacks::DeriveInfo<'_>) -> Vec { - if info.kind == TypeKind::Struct && info.name == "WSLVersion" { - ["Eq", "PartialEq", "Ord", "PartialOrd", "Hash"] - .into_iter() - .map(|s| s.into()) - .collect() - } else { - Vec::default() - } - } - - fn add_attributes(&self, _info: &bindgen::callbacks::AttributeInfo<'_>) -> Vec { - if _info.kind == TypeKind::Struct && _info.name == "WSLPluginHooksV1" { - vec!["#[cfg_attr(feature=\"hooks-field-names\", derive(FieldNamesAsSlice))]".into()] - } else { - Vec::default() - } - } -} - -fn rust_to_llvm_target() -> HashMap<&'static str, &'static str> { - HashMap::from([ - ("x86_64-pc-windows-gnu", "x86_64-w64-mingw32"), - ("i686-pc-windows-gnu", "i686-w64-mingw32"), - ("aarch64-pc-windows-gnu", "aarch64-w64-mingw32"), - ("x86_64-pc-windows-gnullvm", "x86_64-w64-mingw32"), - ("i686-pc-windows-gnullvm", "i686-w64-mingw32"), - ("aarch64-pc-windows-gnullvm ", "aarch64-w64-mingw32"), - ("x86_64-pc-windows-msvc", "x86_64-windows-msvc"), - ("i686-pc-windows-msvc", "i686-windows-msvc"), - ("aarch64-pc-windows-msvc", "aarch64-windows-msvc"), - ]) -} - -/// If the host is not Windows, replace `Windows.h` with `windows.h` in a temporary file. -#[cfg(unix)] -fn preprocess_header<'a, P: 'a + AsRef>( - header_path: &'a P, -) -> Result, Box> { - let content = fs::read_to_string(&header_path)?; - let target_env = std::env::var("CARGO_CFG_TARGET_ENV").unwrap_or_default(); - let new_content = if target_env == "msvc" { - content.cow_replace("windows.h", "Windows.h") - } else { - content.cow_replace("Windows.h", "windows.h") - }; - - let result = match new_content { - Cow::Borrowed(_) => Cow::Borrowed(header_path.as_ref()), - Cow::Owned(ref modified_content) => { - let out_dir: PathBuf = env::var("OUT_DIR")?.into(); - let out_path = out_dir.join(format!("unix_{}", WSL_PLUGIN_API_HEADER_FILE_NAME)); - let mut file = fs::File::create(&out_path)?; - file.write_all(modified_content.as_bytes())?; - println!("Using modified header file at: {:?}", &out_path); - Cow::Owned(out_path) - } - }; - Ok(result) -} - -pub(crate) fn process, S: AsRef>( - header_file_path: P, - host: S, - target: S, -) -> Result> { - let host = host.as_ref(); - let target = target.as_ref(); - // Here we use cow to have the same type and avoiding clowning the PathBuff - let header_file_path: Cow<'_, Path> = { - cfg_if! { - if #[cfg(unix)] { - preprocess_header(&header_file_path)? - } else { - Cow::Borrowed(header_file_path.as_ref()) - } - } - }; - let mut builder = bindgen::Builder::default() - .header(header_file_path.to_str().unwrap()) - .raw_line("use windows::core::*;") - .raw_line("use windows::Win32::Foundation::*;") - .raw_line("use windows::Win32::Security::*;") - .raw_line("use windows::Win32::Networking::WinSock::SOCKET;") - .raw_line("#[allow(clippy::upper_case_acronyms)] type LPCWSTR = PCWSTR;") - .raw_line("#[allow(clippy::upper_case_acronyms)] type LPCSTR = PCSTR;") - .raw_line("#[allow(clippy::upper_case_acronyms)] type DWORD = u32;") - .raw_line(r#"#[cfg(feature = "hooks-field-names")]"#) - .raw_line("use struct_field_names_as_array::FieldNamesAsSlice;") - .allowlist_item("WSL.*") - .allowlist_item("Wsl.*") - .clang_arg("-fparse-all-comments") - .allowlist_recursively(false) - .parse_callbacks(Box::new(BindgenCallback)) - .generate_comments(true); - - if host != target { - builder = builder.clang_arg(format!("--target={}", rust_to_llvm_target()[target])) - } - let binding = builder.generate()?; - Ok(binding) -} diff --git a/wslpluginapi-sys/build/main.rs b/wslpluginapi-sys/build/main.rs deleted file mode 100644 index b4e51cf..0000000 --- a/wslpluginapi-sys/build/main.rs +++ /dev/null @@ -1,34 +0,0 @@ -extern crate bindgen; -mod header_processing; -use constcat::concat; -use std::env; -use std::path::PathBuf; -const WSL_PLUGIN_API_FILE_BASE_NAME: &str = "WslPluginApi"; -const WSL_PLUGIN_API_HEADER_FILE_NAME: &str = concat!(WSL_PLUGIN_API_FILE_BASE_NAME, ".h"); -const WSL_PLUGIN_API_OUTPUT_FILE_NAME: &str = concat!(WSL_PLUGIN_API_FILE_BASE_NAME, ".rs"); - -fn main() -> Result<(), Box> { - println!("cargo:rerun-if-changed=build.rs"); - let host = env::var("HOST")?; - let target = env::var("TARGET")?; - let out_path: PathBuf = env::var("OUT_DIR")?.into(); - - let manifest_dir = - PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR is not set")); - let header_file_path = manifest_dir - .join("third_party/Microsoft.WSL.PluginApi/include") - .join(WSL_PLUGIN_API_HEADER_FILE_NAME); - println!("cargo:rerun-if-changed={}", header_file_path.display()); - - if !header_file_path.exists() { - return Err(format!("Header file does not exist: {:?}", header_file_path).into()); - } - let out_file = out_path.join(WSL_PLUGIN_API_OUTPUT_FILE_NAME); - let api_header = header_processing::process(header_file_path, host, target)?; - api_header.write_to_file(&out_file)?; - println!( - "cargo:rustc-env=WSL_PLUGIN_API_BINDGEN_OUTPUT_FILE_PATH={}", - out_file.display() - ); - Ok(()) -} diff --git a/wslpluginapi-sys/build/metadata.json b/wslpluginapi-sys/build/metadata.json new file mode 100644 index 0000000..54884b1 --- /dev/null +++ b/wslpluginapi-sys/build/metadata.json @@ -0,0 +1,15 @@ +{ + "id": "Microsoft.WSL.PluginApi", + "xtask_version": "0.1.0-rc.1+2.4.4", + "header_version": "2.4.4", + "header_file_path": "build\\native\\include\\WslPluginApi.h", + "output_file_path": "WslPluginApi.rs", + "bindgen": { + "bindgen_version": "0.72.0", + "custom_llvm_target": null, + "build_host": { + "os": "windows", + "arch": "x86_64" + } + } +} \ No newline at end of file diff --git a/wslpluginapi-sys/src/bindgen.rs b/wslpluginapi-sys/src/bindgen.rs index ff6df33..908933f 100644 --- a/wslpluginapi-sys/src/bindgen.rs +++ b/wslpluginapi-sys/src/bindgen.rs @@ -1,4 +1,4 @@ #![allow(non_upper_case_globals)] #![allow(non_camel_case_types)] #![allow(non_snake_case)] -include!(env!("WSL_PLUGIN_API_BINDGEN_OUTPUT_FILE_PATH")); +include!(env!("RUST_BINDING")); diff --git a/wslpluginapi-sys/third_party/Microsoft.WSL.PluginApi/LICENSE b/wslpluginapi-sys/third_party/Microsoft.WSL.PluginApi/LICENSE deleted file mode 100644 index 415e372..0000000 --- a/wslpluginapi-sys/third_party/Microsoft.WSL.PluginApi/LICENSE +++ /dev/null @@ -1,18 +0,0 @@ -MIT License - -Copyright (c) Microsoft - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and -associated documentation files (the "Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the -following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial -portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT -LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO -EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE -USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/wslpluginapi-sys/third_party/Microsoft.WSL.PluginApi/README.MD b/wslpluginapi-sys/third_party/Microsoft.WSL.PluginApi/README.MD deleted file mode 100644 index 53cbe92..0000000 --- a/wslpluginapi-sys/third_party/Microsoft.WSL.PluginApi/README.MD +++ /dev/null @@ -1,5 +0,0 @@ -# WSL plugin api - -This package contains the `WslPluginApi.h` header which defines the WSL plugin interface. - -For more details, see: https://learn.microsoft.com/en-us/windows/wsl/ . \ No newline at end of file diff --git a/wslpluginapi-sys/third_party/Microsoft.WSL.PluginApi/include/WslPluginApi.h b/wslpluginapi-sys/third_party/Microsoft.WSL.PluginApi/include/WslPluginApi.h deleted file mode 100644 index 30aad7d..0000000 --- a/wslpluginapi-sys/third_party/Microsoft.WSL.PluginApi/include/WslPluginApi.h +++ /dev/null @@ -1,134 +0,0 @@ -#pragma once - -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -#define WSLPLUGINAPI_ENTRYPOINTV1 WSLPluginAPIV1_EntryPoint -#define WSL_E_PLUGIN_REQUIRES_UPDATE MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, 0x8004032A) - -#define WSL_PLUGIN_REQUIRE_VERSION(_Major, _Minor, _Revision, Api) \ - if (Api->Version.Major < (_Major) || (Api->Version.Major == (_Major) && Api->Version.Minor < (_Minor)) || \ - (Api->Version.Major == (_Major) && Api->Version.Minor == (_Minor) && Api->Version.Revision < (_Revision))) \ - { \ - return WSL_E_PLUGIN_REQUIRES_UPDATE; \ - } - -struct WSLVersion -{ - uint32_t Major; - uint32_t Minor; - uint32_t Revision; -}; - -enum WSLUserConfiguration -{ - None = 0, - WSLUserConfigurationCustomKernel = 1, - WSLUserConfigurationCustomKernelCommandLine = 2 -}; - -#ifdef __cplusplus -DEFINE_ENUM_FLAG_OPERATORS(WSLUserConfiguration); -#endif - -struct WSLVmCreationSettings -{ - enum WSLUserConfiguration CustomConfigurationFlags; -}; - -typedef DWORD WSLSessionId; - -struct WSLSessionInformation -{ - WSLSessionId SessionId; - HANDLE UserToken; - PSID UserSid; -}; - -struct WSLDistributionInformation -{ - GUID Id; // Distribution ID, guaranteed to be the same accross reboots - LPCWSTR Name; - uint64_t PidNamespace; - LPCWSTR PackageFamilyName; // Package family name, or NULL if none - uint32_t InitPid; // Pid of the init process. Introduced in 2.0.5 - LPCWSTR Flavor; // Type of distribution (ubuntu, debian, ...), introduced in TODO - LPCWSTR Version; // Distribution version, introduced in TODO -}; - -struct WslOfflineDistributionInformation -{ - GUID Id; // Distribution ID, guaranteed to be the same accross reboots - LPCWSTR Name; - LPCWSTR PackageFamilyName; // Package family name, or NULL if none - LPCWSTR Flavor; // Type of distribution (ubuntu, debian, ...), introduced in TODO - LPCWSTR Version; // Distribution version, introduced in TODO -}; - -// Create plan9 mount between Windows & Linux -typedef HRESULT (*WSLPluginAPI_MountFolder)(WSLSessionId Session, LPCWSTR WindowsPath, LPCWSTR LinuxPath, BOOL ReadOnly, LPCWSTR Name); - -// Execute a program in the root namespace. -// On success, 'Socket' is connected to stdin & stdout (stderr goes to dmesg) // 'Arguments' is expected to be NULL terminated -typedef HRESULT (*WSLPluginAPI_ExecuteBinary)(WSLSessionId Session, LPCSTR Path, LPCSTR* Arguments, SOCKET* Socket); - -// Execute a program in a user distribution -// On success, 'Socket' is connected to stdin & stdout (stderr goes to dmesg) // 'Arguments' is expected to be NULL terminated -typedef HRESULT (*WSLPluginAPI_ExecuteBinaryInDistribution)(WSLSessionId Session, const GUID* Distribution, LPCSTR Path, LPCSTR* Arguments, SOCKET* Socket); - -// Set the error message to display to the user if the VM or distribution creation fails. -// Must be called synchronously in either OnVMStarted() or OnDistributionStarted(). -typedef HRESULT (*WSLPluginAPI_PluginError)(LPCWSTR UserMessage); - -// Synchronous notifications sent to the plugin - -// Called when the VM has started. -// 'Session' and 'UserSettings' are only valid during while the call is in progress. -typedef HRESULT (*WSLPluginAPI_OnVMStarted)(const struct WSLSessionInformation* Session, const struct WSLVmCreationSettings* UserSettings); - -// Called when the VM is about to stop. -// 'Session' is only valid during while the call is in progress. -typedef HRESULT (*WSLPluginAPI_OnVMStopping)(const struct WSLSessionInformation* Session); - -// Called when a distribution has started. -// 'Session' and 'Distribution' is only valid during while the call is in progress. -typedef HRESULT (*WSLPluginAPI_OnDistributionStarted)(const struct WSLSessionInformation* Session, const struct WSLDistributionInformation* Distribution); - -// Called when a distribution is about to stop. -// 'Session' and 'Distribution' is only valid during while the call is in progress. -// Note: It's possible that stopping a distribution fails (for instance if a file is in use). -// In this case, it's possible for this notification to be called multiple times for the same distribution. -typedef HRESULT (*WSLPluginAPI_OnDistributionStopping)(const struct WSLSessionInformation* Session, const struct WSLDistributionInformation* Distribution); - -// Called when a distribution is registered or unregisteed. -// Returning failure will NOT cause the operation to fail. -typedef HRESULT (*WSLPluginAPI_OnDistributionRegistered)(const struct WSLSessionInformation* Session, const struct WslOfflineDistributionInformation* Distribution); - -struct WSLPluginHooksV1 -{ - WSLPluginAPI_OnVMStarted OnVMStarted; - WSLPluginAPI_OnVMStopping OnVMStopping; - WSLPluginAPI_OnDistributionStarted OnDistributionStarted; - WSLPluginAPI_OnDistributionStopping OnDistributionStopping; - WSLPluginAPI_OnDistributionRegistered OnDistributionRegistered; // Introduced in 2.1.2 - WSLPluginAPI_OnDistributionRegistered OnDistributionUnregistered; // Introduced in 2.1.2 -}; - -struct WSLPluginAPIV1 -{ - struct WSLVersion Version; - WSLPluginAPI_MountFolder MountFolder; - WSLPluginAPI_ExecuteBinary ExecuteBinary; - WSLPluginAPI_PluginError PluginError; - WSLPluginAPI_ExecuteBinaryInDistribution ExecuteBinaryInDistribution; // Introduced in 2.1.2 -}; - -typedef HRESULT (*WSLPluginAPI_EntryPointV1)(const struct WSLPluginAPIV1* Api, struct WSLPluginHooksV1* Hooks); - -#ifdef __cplusplus -} -#endif \ No newline at end of file diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml index dfaca86..57eaf0d 100644 --- a/xtask/Cargo.toml +++ b/xtask/Cargo.toml @@ -1,18 +1,18 @@ [package] +license.workspace = true +version.workspace = true name = "xtask" -version = "0.1.0" edition = "2024" publish = false [dependencies] -quick-xml = { version = "0.37", features = ["serialize"] } -spdx = { version = "0.10", features = ["text"] } +quick-xml = { version = "0.38", features = ["serialize"] } clap = { version = "4.0", features = ["derive"] } cargo_metadata = "0.20" -reqwest = { version = "0.12", features = ["blocking"] } -tempfile = "3.19" +ureq = "3.0" zip = "4.0" serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" log = "0.4" env_logger = "0.11" clap-verbosity-flag = "3.0" @@ -20,3 +20,14 @@ anyhow = "1.0" regex = { version = "1.11", features = ["use_std"] } markdown-builder = "1.0" walkdir = "2.5" +bindgen = "0.72" +cfg-if = "1.0" +constcat = "0.6" +sha2 = "0.10.9" +hex = "0.4" + +[build-dependencies] +cargo-lock = "10.1" + +[target.'cfg(unix)'.dependencies] +cow-utils = { version = "0.1" } diff --git a/xtask/build.rs b/xtask/build.rs new file mode 100644 index 0000000..6ee8bb4 --- /dev/null +++ b/xtask/build.rs @@ -0,0 +1,37 @@ +use cargo_lock::Lockfile; +use std::path::PathBuf; + +fn main() { + let mut root: PathBuf = std::env::var_os("CARGO_MANIFEST_DIR").unwrap().into(); + let lockfile = { + let mut lockfile = None; + for _ in 0..=1 { + let lock = root.join("Cargo.lock"); + if lock.exists() { + lockfile = Some(Lockfile::load(&lock).expect("Failed to load Cargo.lock")); + break; + } else { + root.pop(); + } + } + lockfile.expect("Cargo.lock not found") + }; + + if let Some(pkg) = lockfile + .packages + .iter() + .find(|pkg| pkg.name.as_str() == "xtask") + { + if let Some(dep) = pkg + .dependencies + .iter() + .find(|dep| dep.name.as_str() == "bindgen") + { + println!("cargo:rustc-env=BINDGEN_VERSION={}", dep.version); + } else { + println!("cargo:warning=No bindgen dependency found in xtask package"); + } + } else { + println!("cargo:warning=No xtask package found in Cargo.lock"); + } +} diff --git a/xtask/src/header_processing.rs b/xtask/src/header_processing.rs new file mode 100644 index 0000000..0df9e17 --- /dev/null +++ b/xtask/src/header_processing.rs @@ -0,0 +1,86 @@ +use bindgen::callbacks::{ParseCallbacks, TypeKind}; +use cfg_if::cfg_if; +#[cfg(unix)] +use cow_utils::CowUtils; +#[cfg(unix)] +use std::{borrow::Cow, fs, io}; +use std::{path::Path, vec}; + +#[derive(Debug)] +struct BindgenCallback; + +impl BindgenCallback {} + +impl ParseCallbacks for BindgenCallback { + fn add_derives(&self, info: &bindgen::callbacks::DeriveInfo<'_>) -> Vec { + if info.kind == TypeKind::Struct && info.name == "WSLVersion" { + ["Eq", "PartialEq", "Ord", "PartialOrd", "Hash"] + .into_iter() + .map(|s| s.into()) + .collect() + } else { + Vec::default() + } + } + + fn add_attributes(&self, _info: &bindgen::callbacks::AttributeInfo<'_>) -> Vec { + if _info.kind == TypeKind::Struct && _info.name == "WSLPluginHooksV1" { + vec!["#[cfg_attr(feature=\"hooks-field-names\", derive(FieldNamesAsSlice))]".into()] + } else { + Vec::default() + } + } +} +#[cfg(unix)] +fn preprocess_header<'a, P: 'a + AsRef>( + header_path: &'a P, + target: Option<&str>, +) -> io::Result { + let content = fs::read_to_string(&header_path)?; + let (old, new) = if target.unwrap_or_default().ends_with("-msvc") { + ("windows.h", "Windows.h") + } else { + ("Windows.h", "windows.h") + }; + let content = match content.cow_replace(old, new) { + Cow::Borrowed(_) => content, + Cow::Owned(owned) => owned, + }; + Ok(content) +} + +pub(crate) fn process>( + header_file_path: P, + target: Option<&str>, +) -> anyhow::Result { + let mut builder = bindgen::Builder::default() + .raw_line("use windows::core::*;") + .raw_line("use windows::Win32::Foundation::*;") + .raw_line("use windows::Win32::Security::*;") + .raw_line("use windows::Win32::Networking::WinSock::SOCKET;") + .raw_line("#[allow(clippy::upper_case_acronyms)] type LPCWSTR = PCWSTR;") + .raw_line("#[allow(clippy::upper_case_acronyms)] type LPCSTR = PCSTR;") + .raw_line("#[allow(clippy::upper_case_acronyms)] type DWORD = u32;") + .raw_line(r#"#[cfg(feature = "hooks-field-names")]"#) + .raw_line("use struct_field_names_as_array::FieldNamesAsSlice;") + .allowlist_item("WSL.*") + .formatter(bindgen::Formatter::Rustfmt) + .allowlist_item("Wsl.*") + .clang_arg("-fparse-all-comments") + .allowlist_recursively(false) + .parse_callbacks(Box::new(BindgenCallback)) + .generate_comments(true); + + if let Some(llvm_target) = target { + builder = builder.clang_arg(format!("--target={llvm_target}")); + } + cfg_if!( + if #[cfg(unix)] { + let content = preprocess_header(&header_file_path, target)?; + builder = builder.header_contents(crate::WSL_PLUGIN_API_HEADER_FILE_NAME, content.as_ref()) + } else { + builder = builder.header(header_file_path.as_ref().to_str().unwrap()); + } + ); + Ok(builder.generate()?) +} diff --git a/xtask/src/licence_definition.rs b/xtask/src/licence_definition.rs deleted file mode 100644 index 23eec95..0000000 --- a/xtask/src/licence_definition.rs +++ /dev/null @@ -1,44 +0,0 @@ -use regex::Regex; -use spdx::Expression; -use std::sync::LazyLock; - -static YEAR_REGEX: LazyLock = LazyLock::new(|| Regex::new(r"\s*").unwrap()); - -#[derive(Debug, Clone)] -pub struct LicenseDefinition { - pub license: Expression, - pub year: Option, - pub holders: String, -} - -impl LicenseDefinition { - /// Create a new license definition. - pub fn new(license: Expression, year: Option, holders: impl Into) -> Self { - LicenseDefinition { - license, - year, - holders: holders.into(), - } - } - - /// Generate the license body with placeholders replaced by year and holders. - pub fn generate_body(&self) -> Vec { - self.license - .requirements() - .flat_map(|req| req.req.license.id()) - .map(|id| { - let raw_text = id.text(); - let text_with_holders = - raw_text.replace("", self.holders.as_ref()); - if let Some(year) = self.year { - // Replace placeholder with the actual year - text_with_holders.replace("", &year.to_string()) - } else { - // Remove placeholders if no year is specified - let without_year = YEAR_REGEX.replace_all(&text_with_holders, ""); - without_year.into_owned() - } - }) - .collect() - } -} diff --git a/xtask/src/main.rs b/xtask/src/main.rs index f0da56f..42a7195 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -1,31 +1,30 @@ -mod licence_definition; +mod header_processing; +mod metadata; mod nuget; mod nuspec; -mod third_pary_management; - +mod utils; use anyhow::Result; -use cargo_metadata::Package; -use nuspec::LicenceContent; +use cargo_metadata::{Metadata, Package}; +use clap::{Parser, Subcommand}; +use clap_verbosity_flag::{InfoLevel, Verbosity}; +use constcat::concat; +use log::{debug, info, trace, warn}; +use nuget::{Mode, ensure_package_installed}; +use std::fs::File; use std::{ fs, - io::{self, BufReader, Write}, - iter::once, - path::Path, -}; -use third_pary_management::{ - DistributedFile, Status, - notice::{NoticeGeneration, ThirdPartyNotice, ThirdPartyNoticeItem, ThirdPartyNoticePackage}, + io::{BufReader, Write}, + path::{Path, PathBuf}, }; -use walkdir::WalkDir; - -use crate::nuget::{Mode, ensure_package_installed}; -use clap::{Parser, Subcommand, builder::OsStr}; -use clap_verbosity_flag::{InfoLevel, Verbosity}; -use env_logger; -use log::{debug, info, trace, warn}; use zip::ZipArchive; -/// Tâches de build et développement personnalisées pour le projet. +use crate::utils::format_with_rustfmt; +use crate::utils::writers::Sha256HashWriter; + +const WSL_PLUGIN_API_FILE_BASE_NAME: &str = "WslPluginApi"; +const WSL_PLUGIN_API_HEADER_FILE_NAME: &str = concat!(WSL_PLUGIN_API_FILE_BASE_NAME, ".h"); +const WSL_PLUGIN_API_OUTPUT_FILE_NAME: &str = concat!(WSL_PLUGIN_API_FILE_BASE_NAME, ".rs"); + #[derive(Parser)] #[command(author, version, about, long_about = None)] struct Cli { @@ -37,236 +36,130 @@ struct Cli { #[derive(Subcommand)] enum Commands { - Nuget, + Bindgen { + #[arg(long, help = "LLVM target triple to use for bindgen generation")] + target: Option, + }, } fn main() -> Result<()> { let cli = Cli::parse(); - - // Configure le logger selon le niveau de verbosité env_logger::Builder::new() .filter_level(cli.verbose.log_level_filter()) .init(); match &cli.command { - Commands::Nuget => { - info!("Running Nuget command..."); + Commands::Bindgen { target } => { + info!("Running bindgen command..."); let metadata = fetch_cargo_metadata()?; - cleanup(&metadata)?; - let workspace_root: &Path = &metadata.workspace_root.as_ref(); - let mut notice = ThirdPartyNotice::default(); - for package in metadata.workspace_packages() { + let workspace_root: &Path = metadata.workspace_root.as_ref(); + + for package in &metadata.workspace_packages() { if package .manifest_path .parent() - .map_or(true, |p| p.as_str() != env!("CARGO_MANIFEST_DIR")) + .is_none_or(|p| p != Path::new(env!("CARGO_MANIFEST_DIR"))) { - notice.push(process_package(package, workspace_root)?); + process_package(package, workspace_root, target.as_deref())?; } else { info!("Skipping package: {}", package.name); - continue; - }; + } } - notice.generate_notice(&workspace_root.join("THIRD-PARTY-NOTICES.md"))?; Ok(()) } } } -fn fetch_cargo_metadata() -> Result { +fn fetch_cargo_metadata() -> Result { debug!("Fetching cargo metadata..."); let metadata = cargo_metadata::MetadataCommand::new().exec()?; - trace!("Full cargo metadata: {:#?}", metadata); + trace!("Full cargo metadata: {metadata:#?}"); Ok(metadata) } fn process_package( - package: &cargo_metadata::Package, + package: &Package, workspace_root: &Path, -) -> Result { + llvm_target: Option<&str>, +) -> anyhow::Result<()> { debug!("Processing package: {}", package.name); let version = &package.version; - let nuget_package_version = &version.build; + let nuget_package_version: &str = version.build.as_ref(); debug!( - "Package '{}' build metadata version: {:?}", + "Package '{}' version: {}", package.name, nuget_package_version ); if nuget_package_version.is_empty() { warn!("No version found for package: {}", package.name); - return Ok(ThirdPartyNoticePackage::new(package.name.to_string())); + return Ok(()); } let nuget_package_name = "Microsoft.WSL.PluginApi"; - debug!( - "Ensuring NuGet package installed: {} @ {}", - nuget_package_name, nuget_package_version - ); + debug!("Ensuring NuGet package installed: {nuget_package_name} @ {nuget_package_version}"); let nuget_pkg_path = ensure_package_installed( nuget_package_name, - nuget_package_version.as_str(), + nuget_package_version, workspace_root, Mode::TryNuget, )?; debug!("NuGet package path: {}", nuget_pkg_path.display()); - - let third_party_dir = package.manifest_path.parent().unwrap().join("third_party"); - let third_party_wsl_nuget_dir = third_party_dir.join(nuget_package_name); - prepare_third_party_dirs( - &third_party_dir.as_std_path(), - &third_party_wsl_nuget_dir.as_std_path(), - )?; - let nuspec_data = get_nuspec_from_nupkg( - &nuget_pkg_path, - nuget_package_name, - nuget_package_version.as_str(), - )? - .unwrap(); - let licence: Option = nuspec_data.metadata.get_licence_content()?; - let mut notice_item = ThirdPartyNoticeItem::new( - nuget_package_name.into(), - nuspec_data.metadata.version.clone(), - format!( - "https://www.nuget.org/packages/{}/{}", - nuspec_data.metadata.id, nuspec_data.metadata.version, - ), - nuspec_data.metadata.copyright.clone(), - licence, - ); - let headers = copy_native_headers(&nuget_pkg_path, &third_party_wsl_nuget_dir.as_std_path())?; - notice_item.files_mut().extend(headers); - let readme: Option = handle_readme( - &nuspec_data, - nuget_pkg_path.as_ref(), - third_party_wsl_nuget_dir.as_ref(), - )?; - notice_item.files_mut().extend(readme.into_iter()); - let licenses = handle_license( - &nuspec_data, - nuget_pkg_path.as_ref(), - third_party_wsl_nuget_dir.as_ref(), - )?; - notice_item.files_mut().extend(licenses.into_iter()); - let mut notice = ThirdPartyNoticePackage::new(package.name.to_string()); - notice.push(notice_item); - notice.generate_notice( - &package - .manifest_path - .parent() - .unwrap() - .join("THIRD-PARTY-NOTICES.md"), - )?; - Ok(notice) -} - -fn prepare_third_party_dirs( - third_party_dir: &Path, - third_party_wsl_nuget_dir: &Path, -) -> Result<()> { - debug!( - "Creating third_party directory at: {}", - third_party_dir.display() - ); - if third_party_dir.exists() { - debug!( - "Removing existing third_party directory: {}", - third_party_dir.display() - ); - fs::remove_dir_all(third_party_dir)?; - } - fs::create_dir_all(third_party_dir)?; - debug!( - "Creating third_party directory for the package at: {}", - third_party_wsl_nuget_dir.display() - ); - fs::create_dir_all(third_party_wsl_nuget_dir)?; - Ok(()) -} - -fn cleanup(metadata: &cargo_metadata::Metadata) -> io::Result<()> { - remove_third_party_notice(&metadata)?; - remove_third_party_dir(metadata.workspace_packages().iter())?; - Ok(()) -} - -fn remove_third_party_dir<'a, I: IntoIterator>( - packages: I, -) -> io::Result<()> { - for package_path in packages - .into_iter() - .filter_map(|package| package.manifest_path.parent()) - { - if package_path.join("third_party").exists() { - debug!( - "Removing existing third_party directory at: {}", - package_path.join("third_party") - ); - fs::remove_dir_all(package_path.join("third_party")).unwrap_or_else(|err| { - warn!( - "Failed to remove third_party directory at {}: {}", - package_path.join("third_party"), - err - ); - }); - } else { - debug!( - "No third_party directory to remove at: {}", - package_path.join("third_party") + let nuspec = + get_nuspec_from_nupkg(&nuget_pkg_path, nuget_package_name, nuget_package_version)?.unwrap(); + let header_path = get_header_path(&nuget_pkg_path, WSL_PLUGIN_API_HEADER_FILE_NAME)?; + let binding = header_processing::process(&header_path, llvm_target)?; + if let Some(package_path) = package.manifest_path.parent() { + let build_path = &package_path.join("build"); + fs::create_dir_all(build_path)?; + File::create(build_path.join(".gitattributes"))?.write_all("* -text".as_bytes())?; + let mut checksum_file = File::create(build_path.join("checksum.sha256"))?; + let metadata_path = &build_path.join("metadata.json"); + let out_path = build_path.join(WSL_PLUGIN_API_OUTPUT_FILE_NAME); + { + let metadata = metadata::Metadata::new( + nuspec.metadata.id, + nuspec.metadata.version.to_string(), + header_path + .strip_prefix(nuget_pkg_path)? + .to_string_lossy() + .into_owned(), + out_path.strip_prefix(build_path)?.as_str(), + llvm_target, ); + let mut hash_writer = Sha256HashWriter::new(File::create(metadata_path)?); + metadata.write(&mut hash_writer)?; + writeln!( + checksum_file, + "{} {}", + hex::encode(hash_writer.finalise()?), + metadata_path.strip_prefix(build_path)? + )?; } - } - Ok(()) -} - -fn remove_third_party_notice(metadata: &cargo_metadata::Metadata) -> io::Result<()> { - for dir in once(metadata.workspace_root.as_ref()).chain( - metadata - .workspace_packages() - .iter() - .filter_map(|package| package.manifest_path.parent()), - ) { - let third_party_notice_path = dir.join("THIRD-PARTY-NOTICES.md"); - if third_party_notice_path.exists() { - debug!( - "Removing existing THIRD-PARTY-NOTICES.md file at: {}", - third_party_notice_path - ); - fs::remove_file(&third_party_notice_path)?; - } else { - debug!( - "No THIRD-PARTY-NOTICES.md file to remove at: {}", - third_party_notice_path - ); + { + let mut hash_writer = Sha256HashWriter::new(File::create(&out_path)?); + format_with_rustfmt(binding, &mut hash_writer, Some(build_path.as_std_path()))?; + writeln!( + checksum_file, + "{} {}", + hex::encode(hash_writer.finalise()?), + out_path.strip_prefix(build_path)? + )?; } + Ok(()) + } else { + warn!( + "Package manifest path does not have a parent directory: {}", + std::path::Path::new(package.manifest_path.as_str()).display() + ); + Ok(()) } - Ok(()) } -fn copy_native_headers( - nuget_pkg_path: &Path, - third_party_nuget_package_dir: &Path, -) -> Result> { - debug!("Copying native headers..."); - let nuget_native_path = nuget_pkg_path.join("build/native"); - let mut vec = Vec::with_capacity(1); - for entry in WalkDir::new(&nuget_native_path) { - let entry = entry?; - let path = entry.path(); - let result_path = - third_party_nuget_package_dir.join(path.strip_prefix(&nuget_native_path).unwrap()); - if path.is_dir() { - debug!("Copying directory: {}", path.display()); - fs::create_dir_all(&result_path)?; - } else { - debug!("Copying file: {}", path.display()); - fs::create_dir_all(&result_path.parent().unwrap())?; - fs::copy(&path, &result_path)?; - let distributed_file = DistributedFile::new(result_path, Status::Unmodified); - vec.push(distributed_file); - } - } - Ok(vec.into_boxed_slice()) +fn get_header_path(nuget_pkg_path: &Path, header_name: &str) -> Result { + let mut nuget_native_path = nuget_pkg_path.to_path_buf(); + nuget_native_path.extend(["build", "native", "include", header_name]); + Ok(nuget_native_path) } fn get_nuspec_from_nupkg( @@ -274,21 +167,21 @@ fn get_nuspec_from_nupkg( nuget_package_name: &str, nuget_package_version: &str, ) -> Result> { - let nuspec_name = format!("{}.nuspec", nuget_package_name); - debug!("Looking for nuspec file: {}", nuspec_name); + let nuspec_name = format!("{nuget_package_name}.nuspec"); + debug!("Looking for nuspec file: {nuspec_name}"); - let zip_file = fs::File::open(&nuget_pkg_path.join(format!( - "{}.{}.nupkg", - nuget_package_name, nuget_package_version - )))?; + let nupkg_file = nuget_pkg_path.join(format!( + "{nuget_package_name}.{nuget_package_version}.nupkg" + )); + let zip_file = File::open(&nupkg_file)?; let mut archive = ZipArchive::new(zip_file)?; trace!("ZIP archive opened with {} files", archive.len()); match archive.by_name(&nuspec_name) { Ok(nuspec_file) => { - debug!("Found .nuspec file: {}", nuspec_name); + debug!("Found .nuspec file: {nuspec_name}"); let nuspec_buffer = BufReader::new(nuspec_file); let package_data = nuspec::Package::from_reader(nuspec_buffer)?; - trace!("Parsed nuspec data: {:#?}", package_data); + trace!("Parsed nuspec data: {package_data:#?}"); Ok(Some(package_data)) } Err(_) => { @@ -301,70 +194,3 @@ fn get_nuspec_from_nupkg( } } } - -fn handle_readme( - package_data: &nuspec::Package, - nuget_pkg_path: &Path, - third_party_wsl_nuget_dir: &Path, -) -> Result> { - if let Some(readme_nuget_path) = package_data.metadata.readme.as_deref() { - let readme_path = third_party_wsl_nuget_dir.join( - &readme_nuget_path - .file_name() - .unwrap_or(&OsStr::from("README")), - ); - debug!("Copying README file to: {}", readme_path.display()); - fs::copy(nuget_pkg_path.join(readme_nuget_path), &readme_path)?; - return Ok(Some(DistributedFile::new(readme_path, Status::Unmodified))); - } else { - info!("No README file specified in nuspec."); - } - Ok(None) -} - -fn handle_license( - package_data: &nuspec::Package, - nuget_pkg_path: &Path, - third_party_wsl_nuget_dir: &Path, -) -> Result> { - let some_licence_content = package_data.metadata.get_licence_content()?; - if let Some(licence_content) = some_licence_content { - match licence_content { - LicenceContent::Body(body) => { - debug!("License file or expression found in nuspec."); - match body { - nuspec::LicenceBody::Generator(generator) => { - debug!("License generator found in nuspec."); - let license_body = generator.generate_body(); - let license_path = if license_body.len() == 1 { - third_party_wsl_nuget_dir.join("LICENSE") - } else { - third_party_wsl_nuget_dir.join("LICENSES") - }; - debug!("Writing license to: {}", &license_path.display()); - fs::File::create(&license_path)? - .write_all(license_body.join("\n\n").as_bytes())?; - Ok(Some(DistributedFile::new( - license_path, - Status::PackageMetadataGenerated, - ))) - } - nuspec::LicenceBody::File(file) => { - debug!("License file found in nuspec."); - let license_path = third_party_wsl_nuget_dir.join("LICENSE"); - debug!("Copy license to: {}", &license_path.display()); - fs::copy(nuget_pkg_path.join(file), &license_path)?; - Ok(Some(DistributedFile::new(license_path, Status::Unmodified))) - } - } - } - LicenceContent::URL(url) => { - debug!("License URL found in nuspec: {}", url); - Ok(None) - } - } - } else { - debug!("No license file or expression specified in nuspec."); - Ok(None) - } -} diff --git a/xtask/src/metadata.rs b/xtask/src/metadata.rs new file mode 100644 index 0000000..2391cb9 --- /dev/null +++ b/xtask/src/metadata.rs @@ -0,0 +1,74 @@ +use serde::Serialize; +use std::io::Write; + +#[derive(Serialize, Debug)] +pub struct Metadata { + pub id: String, + pub xtask_version: String, + pub header_version: String, + pub header_file_path: String, + pub output_file_path: String, + pub bindgen: BindgenMetadata, +} + +impl Metadata { + pub fn new( + id: Id, + header_version: HVer, + header_file_path: Header, + output_file_path: Output, + custom_llvm_target: Option, + ) -> Self + where + Id: Into, + HVer: Into, + Header: Into, + Output: Into, + Target: Into, + { + let custom_llvm_target = custom_llvm_target.map(|t| t.into()); + Self { + id: id.into(), + xtask_version: env!("CARGO_PKG_VERSION").into(), + header_version: header_version.into(), + header_file_path: header_file_path.into(), + output_file_path: output_file_path.into(), + bindgen: BindgenMetadata::new(custom_llvm_target), + } + } + + pub fn write(&self, writer: &mut W) -> serde_json::Result<()> { + serde_json::to_writer_pretty(writer, &self) + } +} + +#[derive(Serialize, Debug)] +pub struct BindgenMetadata { + pub bindgen_version: String, + pub custom_llvm_target: Option, + pub build_host: BuildMachine, +} +impl BindgenMetadata { + fn new(custom_llvm_target: Option) -> Self { + Self { + bindgen_version: env!("BINDGEN_VERSION").to_string(), + custom_llvm_target, + build_host: BuildMachine::default(), + } + } +} + +#[derive(Serialize, Debug)] +pub struct BuildMachine { + pub os: String, + pub arch: String, +} + +impl Default for BuildMachine { + fn default() -> Self { + BuildMachine { + os: std::env::consts::OS.to_string(), + arch: std::env::consts::ARCH.to_string(), + } + } +} diff --git a/xtask/src/nuget.rs b/xtask/src/nuget.rs index 04e921b..b44ec45 100644 --- a/xtask/src/nuget.rs +++ b/xtask/src/nuget.rs @@ -1,11 +1,12 @@ use anyhow::Result; -use reqwest::blocking::get; use std::fs; +use std::io; +use std::io::Seek; use std::path::Path; use std::path::PathBuf; use std::process::Command; use std::process::ExitStatus; -use tempfile::NamedTempFile; +use ureq::get; use zip::ZipArchive; #[allow(dead_code)] @@ -39,7 +40,7 @@ pub(crate) fn ensure_package_installed, S: AsRef>( let package_name = package_name.as_ref(); let package_version = package_version.as_ref(); let package_dir = out_dir.join(LOCAL_NUGET_FOLDER); - let package_output = package_dir.join(format!("{}.{}", package_name, package_version)); + let package_output = package_dir.join(format!("{package_name}.{package_version}")); fs::create_dir_all(&package_dir)?; @@ -52,16 +53,13 @@ pub(crate) fn ensure_package_installed, S: AsRef>( } Mode::TryNuget => { if let Err(e) = install_with_nuget_cli(package_name, package_version, &package_dir) { - println!( - "NuGet CLI failed: {}. Falling back to manual download...", - e - ); + println!("NuGet CLI failed: {e}. Falling back to manual download..."); download_and_extract(package_name, package_version, &package_output)?; } } } - println!("NuGet package installed successfully: {:?}", package_output); + println!("NuGet package installed successfully: {package_output:?}"); Ok(package_output) } @@ -90,21 +88,21 @@ fn download_and_extract( package_version: &str, package_output: &Path, ) -> Result<()> { - let package_url = format!( - "https://www.nuget.org/api/v2/package/{}/{}", - package_name, package_version - ); - println!("Downloading NuGet package from: {}", package_url); - - let mut response = get(&package_url)?.error_for_status()?; - - let mut temp_file = NamedTempFile::new()?; - response.copy_to(&mut temp_file)?; - - let zip_file = fs::File::open(temp_file.path())?; - let mut archive = ZipArchive::new(zip_file)?; - println!("Extracting NuGet package to: {:?}", package_output); + let package_url = + format!("https://www.nuget.org/api/v2/package/{package_name}/{package_version}"); + println!("Downloading NuGet package from: {package_url}"); + let response = get(&package_url).call()?; + fs::create_dir_all(package_output)?; + let mut nuget_pkg_file = fs::OpenOptions::new() + .create(true) + .write(true) + .read(true) + .truncate(true) + .open(package_output.join(format!("{package_name}.{package_version}.nupkg")))?; + io::copy(&mut response.into_body().into_reader(), &mut nuget_pkg_file)?; + nuget_pkg_file.seek(io::SeekFrom::Start(0))?; + let mut archive = ZipArchive::new(nuget_pkg_file)?; + println!("Extracting NuGet package to: {package_output:?}"); archive.extract(package_output)?; - Ok(()) } diff --git a/xtask/src/nuspec.rs b/xtask/src/nuspec.rs index ec21fff..020e939 100644 --- a/xtask/src/nuspec.rs +++ b/xtask/src/nuspec.rs @@ -1,12 +1,8 @@ -use crate::licence_definition::LicenseDefinition; use quick_xml::DeError; use quick_xml::de::from_reader; -use regex::Regex; use serde::{Deserialize, Serialize}; -use spdx::Expression; -use std::fs; use std::io::BufRead; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; #[derive(Debug, Serialize, Deserialize)] #[serde(rename = "package")] @@ -22,24 +18,6 @@ impl Package { } } -#[derive(Debug, Clone)] -pub enum LicenceContent { - Body(LicenceBody), - URL(String), -} - -impl From for LicenceContent { - fn from(body: LicenceBody) -> Self { - LicenceContent::Body(body) - } -} - -#[derive(Debug, Clone)] -pub enum LicenceBody { - Generator(LicenseDefinition), - File(String), -} - #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Metadata { @@ -69,36 +47,6 @@ pub struct Metadata { pub dependencies: Option, } -impl Metadata { - pub fn get_year(&self) -> Option { - let re = Regex::new(r"\d{4}").unwrap(); - self.copyright - .as_deref() - .map(|copyright| re.captures(©right).map(|year| year[0].parse().unwrap())) - .flatten() - } - - pub fn get_holders(&self) -> &str { - if let Some(owners) = &self.owners { - owners - } else { - &self.authors - } - } - - pub fn get_licence_content(&self) -> anyhow::Result> { - let year = self.get_year(); - let holders = self.get_holders(); - Ok(if let Some(license) = &self.license { - Some(LicenceContent::Body(license.get_body(year, &holders)?)) - } else if let Some(license_url) = &self.license_url { - Some(LicenceContent::URL(license_url.clone())) - } else { - None - }) - } -} - #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] #[serde(rename_all = "lowercase")] pub enum LicenseType { @@ -113,24 +61,6 @@ pub struct License { #[serde(rename = "$value")] value: String, } - -impl License { - pub fn get_body(&self, year: Option, holders: &str) -> anyhow::Result { - let result = match self.kind { - LicenseType::File => { - let path = Path::new(&self.value); - LicenceBody::File(fs::read_to_string(path)?) - } - LicenseType::Expression => LicenceBody::Generator(LicenseDefinition::new( - Expression::parse(&self.value)?, - year, - holders, - )), - }; - Ok(result) - } -} - #[derive(Debug, Serialize, Deserialize)] #[serde(rename = "dependencies")] pub struct Dependencies { diff --git a/xtask/src/third_pary_management/distributed_file.rs b/xtask/src/third_pary_management/distributed_file.rs deleted file mode 100644 index e500e27..0000000 --- a/xtask/src/third_pary_management/distributed_file.rs +++ /dev/null @@ -1,31 +0,0 @@ -use std::{fmt::Display, path::PathBuf}; - -#[derive(Debug, Clone)] -pub struct DistributedFile { - pub path: PathBuf, - pub status: Status, -} - -impl DistributedFile { - pub fn new(path: PathBuf, status: Status) -> Self { - Self { path, status } - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Status { - Modified, - Unmodified, - PackageMetadataGenerated, -} - -impl Display for Status { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let output = match self { - Status::Modified => "modified", - Status::Unmodified => "unmodified", - Status::PackageMetadataGenerated => "generated from package metadata", - }; - f.write_str(output) - } -} diff --git a/xtask/src/third_pary_management/mod.rs b/xtask/src/third_pary_management/mod.rs deleted file mode 100644 index d40855d..0000000 --- a/xtask/src/third_pary_management/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub(crate) mod distributed_file; -pub(crate) mod notice; -pub use distributed_file::{DistributedFile, Status}; diff --git a/xtask/src/third_pary_management/notice/mod.rs b/xtask/src/third_pary_management/notice/mod.rs deleted file mode 100644 index 77c5276..0000000 --- a/xtask/src/third_pary_management/notice/mod.rs +++ /dev/null @@ -1,8 +0,0 @@ -mod notice_generation; -mod third_party_notice; -mod third_party_notice_item; -mod third_party_notice_package; -pub use notice_generation::NoticeGeneration; -pub use third_party_notice::ThirdPartyNotice; -pub use third_party_notice_item::ThirdPartyNoticeItem; -pub use third_party_notice_package::ThirdPartyNoticePackage; diff --git a/xtask/src/third_pary_management/notice/notice_generation.rs b/xtask/src/third_pary_management/notice/notice_generation.rs deleted file mode 100644 index 250d112..0000000 --- a/xtask/src/third_pary_management/notice/notice_generation.rs +++ /dev/null @@ -1,28 +0,0 @@ -use markdown_builder::Markdown; -use std::{borrow::Cow, fs, path::Path}; -pub trait NoticeGeneration { - fn generate_notice>(&self, output_path: P) -> std::io::Result<()> { - let output_path = if output_path.as_ref().is_absolute() { - Cow::Borrowed(output_path.as_ref()) - } else { - Cow::Owned(output_path.as_ref().canonicalize()?) - }; - let mut md = Markdown::new(); - Self::header_generation(&mut md); - self.generate_content_in_place(&mut md, &output_path, 1)?; - fs::create_dir_all(output_path.parent().unwrap())?; - fs::write(output_path, md.render())?; - Ok(()) - } - - fn generate_content_in_place>( - &self, - md: &mut Markdown, - output_path: P, - header_level: usize, - ) -> std::io::Result<()>; - - fn header_generation(md: &mut Markdown) { - md.header1("Third Party Notices"); - } -} diff --git a/xtask/src/third_pary_management/notice/third_party_notice.rs b/xtask/src/third_pary_management/notice/third_party_notice.rs deleted file mode 100644 index cc04a16..0000000 --- a/xtask/src/third_pary_management/notice/third_party_notice.rs +++ /dev/null @@ -1,91 +0,0 @@ -use std::path::Path; - -use super::{ThirdPartyNoticePackage, notice_generation::NoticeGeneration}; - -#[derive(Debug, Clone, Default)] -pub struct ThirdPartyNotice { - packages: Vec, -} - -impl ThirdPartyNotice { - pub fn add_item(&mut self, item: ThirdPartyNoticePackage) { - self.packages.push(item); - } -} - -impl NoticeGeneration for ThirdPartyNotice { - fn generate_content_in_place>( - &self, - md: &mut markdown_builder::Markdown, - output_path: P, - header_level: usize, - ) -> std::io::Result<()> { - let add_header = self.len() > 0; - let package_level = if add_header { - header_level + 1 - } else { - header_level - }; - for package in self { - if add_header { - md.header(package.name(), header_level + 1); - } - package.generate_content_in_place(md, &output_path, package_level)?; - } - Ok(()) - } -} - -impl std::ops::Deref for ThirdPartyNotice { - type Target = Vec; - fn deref(&self) -> &Self::Target { - &self.packages - } -} - -impl std::ops::DerefMut for ThirdPartyNotice { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.packages - } -} - -impl std::iter::FromIterator for ThirdPartyNotice { - fn from_iter>(iter: I) -> Self { - Self { - packages: Vec::from_iter(iter), - } - } -} - -impl IntoIterator for ThirdPartyNotice { - type Item = ThirdPartyNoticePackage; - type IntoIter = std::vec::IntoIter; - - fn into_iter(self) -> Self::IntoIter { - self.packages.into_iter() - } -} - -impl<'a> IntoIterator for &'a ThirdPartyNotice { - type Item = &'a ThirdPartyNoticePackage; - type IntoIter = std::slice::Iter<'a, ThirdPartyNoticePackage>; - - fn into_iter(self) -> Self::IntoIter { - self.packages.iter() - } -} - -impl<'a> IntoIterator for &'a mut ThirdPartyNotice { - type Item = &'a mut ThirdPartyNoticePackage; - type IntoIter = std::slice::IterMut<'a, ThirdPartyNoticePackage>; - - fn into_iter(self) -> Self::IntoIter { - self.packages.iter_mut() - } -} - -impl Extend for ThirdPartyNotice { - fn extend>(&mut self, iter: T) { - self.packages.extend(iter); - } -} diff --git a/xtask/src/third_pary_management/notice/third_party_notice_item.rs b/xtask/src/third_pary_management/notice/third_party_notice_item.rs deleted file mode 100644 index 89d5c5b..0000000 --- a/xtask/src/third_pary_management/notice/third_party_notice_item.rs +++ /dev/null @@ -1,173 +0,0 @@ -use log::debug; -use markdown_builder::{BlockQuote, Bold, Inline, Link, List}; - -use crate::{nuspec::LicenceContent, third_pary_management::distributed_file::DistributedFile}; -use std::{borrow::Cow, path::Path}; - -use super::notice_generation::NoticeGeneration; - -#[derive(Debug, Clone)] -pub struct ThirdPartyNoticeItem { - name: String, - version: String, - link: String, - copyright: Option, - license: Option, - files: Vec, -} - -impl NoticeGeneration for ThirdPartyNoticeItem { - fn generate_content_in_place>( - &self, - md: &mut markdown_builder::Markdown, - output_path: P, - header_level: usize, - ) -> std::io::Result<()> { - md.header(self.name(), header_level); - md.paragraph(&format!( - "{} {}", - "Version :".to_bold(), - self.version().to_bold() - )); - let licence_txt = if let Some(license) = self.license() { - match license { - crate::nuspec::LicenceContent::Body(licence_body) => match licence_body { - crate::nuspec::LicenceBody::Generator(license_definition) => { - Cow::Borrowed(license_definition.license.as_ref()) - } - crate::nuspec::LicenceBody::File(_) => { - Cow::Borrowed("See the LICENSE file for details.") - } - }, - crate::nuspec::LicenceContent::URL(url) => Cow::Owned(format!( - "See the {} for details.", - Link::builder().text("license").url(url).inlined().build() - )), - } - } else { - Cow::Borrowed("Not specified") - }; - md.paragraph(format!("{} {}", "License :".to_bold(), licence_txt)); - md.paragraph(format!( - "{} {}", - "Source:".to_bold(), - Link::builder() - .text(self.link()) - .url(self.name()) - .footer(false) - .inlined() - .build() - )); - - if !self.files().is_empty() { - md.header("Included files from the package.", header_level + 1); - } - let md_files_list = self - .files() - .iter() - .fold(List::builder(), |builder, file| { - debug!("Adding file to list: {:?}", &file.path); - builder.append(format!( - "{} {}", - file.path - .strip_prefix(output_path.as_ref().parent().unwrap()) - .unwrap() - .to_string_lossy() - .replace("\\", "/") - .to_inline(), - file.status - )) - }) - .unordered(); - md.list(md_files_list); - if let Some(copyright) = self.copyright() { - md.paragraph("copyright:".to_bold()) - .paragraph(copyright.to_block_quote()); - } - Ok(()) - } -} - -impl ThirdPartyNoticeItem { - pub fn new( - name: String, - version: String, - link: String, - copyright: Option, - license: Option, - ) -> Self { - Self { - name, - version, - link, - copyright, - license, - files: Vec::default(), - } - } - - pub fn name(&self) -> &str { - &self.name - } - - pub fn version(&self) -> &str { - &self.version - } - - pub fn link(&self) -> &str { - &self.link - } - - pub fn copyright(&self) -> Option<&str> { - self.copyright.as_deref() - } - - pub fn license(&self) -> Option<&LicenceContent> { - self.license.as_ref() - } - - pub fn files(&self) -> &Vec { - &self.files - } - - pub fn files_mut(&mut self) -> &mut Vec { - &mut self.files - } - - pub fn add_file(&mut self, file: DistributedFile) { - self.files.push(file); - } - - // Méthodes utiles de collection - pub fn len(&self) -> usize { - self.files.len() - } - - pub fn is_empty(&self) -> bool { - self.files.is_empty() - } - - pub fn iter(&self) -> std::slice::Iter<'_, DistributedFile> { - self.files.iter() - } - - pub fn iter_mut(&mut self) -> std::slice::IterMut<'_, DistributedFile> { - self.files.iter_mut() - } - - pub fn clear(&mut self) { - self.files.clear(); - } - - pub fn remove(&mut self, index: usize) -> DistributedFile { - self.files.remove(index) - } - - pub fn get(&self, index: usize) -> Option<&DistributedFile> { - self.files.get(index) - } - - pub fn get_mut(&mut self, index: usize) -> Option<&mut DistributedFile> { - self.files.get_mut(index) - } -} diff --git a/xtask/src/third_pary_management/notice/third_party_notice_package.rs b/xtask/src/third_pary_management/notice/third_party_notice_package.rs deleted file mode 100644 index ee11336..0000000 --- a/xtask/src/third_pary_management/notice/third_party_notice_package.rs +++ /dev/null @@ -1,82 +0,0 @@ -use std::path::Path; - -use super::{ThirdPartyNoticeItem, notice_generation::NoticeGeneration}; - -#[derive(Debug, Clone)] -pub struct ThirdPartyNoticePackage { - name: String, - items: Vec, -} - -impl ThirdPartyNoticePackage { - pub fn new(name: String) -> Self { - Self { - name, - items: Vec::new(), - } - } - - pub fn name(&self) -> &str { - &self.name - } -} - -impl NoticeGeneration for ThirdPartyNoticePackage { - fn generate_content_in_place>( - &self, - md: &mut markdown_builder::Markdown, - output_path: P, - header_level: usize, - ) -> std::io::Result<()> { - for item in self { - item.generate_content_in_place(md, &output_path, header_level + 1)?; - } - Ok(()) - } -} - -impl std::ops::Deref for ThirdPartyNoticePackage { - type Target = Vec; - fn deref(&self) -> &Self::Target { - &self.items - } -} - -impl std::ops::DerefMut for ThirdPartyNoticePackage { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.items - } -} - -impl IntoIterator for ThirdPartyNoticePackage { - type Item = ThirdPartyNoticeItem; - type IntoIter = std::vec::IntoIter; - - fn into_iter(self) -> Self::IntoIter { - self.items.into_iter() - } -} - -impl<'a> IntoIterator for &'a ThirdPartyNoticePackage { - type Item = &'a ThirdPartyNoticeItem; - type IntoIter = std::slice::Iter<'a, ThirdPartyNoticeItem>; - - fn into_iter(self) -> Self::IntoIter { - self.items.iter() - } -} - -impl<'a> IntoIterator for &'a mut ThirdPartyNoticePackage { - type Item = &'a mut ThirdPartyNoticeItem; - type IntoIter = std::slice::IterMut<'a, ThirdPartyNoticeItem>; - - fn into_iter(self) -> Self::IntoIter { - self.items.iter_mut() - } -} - -impl Extend for ThirdPartyNoticePackage { - fn extend>(&mut self, iter: T) { - self.items.extend(iter); - } -} diff --git a/xtask/src/utils/mod.rs b/xtask/src/utils/mod.rs new file mode 100644 index 0000000..a9cdb68 --- /dev/null +++ b/xtask/src/utils/mod.rs @@ -0,0 +1,44 @@ +pub mod writers; + +use std::io::{self, Write}; +use std::path::Path; +use std::process::{Command, Stdio}; + +use bindgen::Bindings; + +pub fn format_with_rustfmt( + binding: Bindings, + mut output: W, + workdir: Option<&Path>, +) -> io::Result<()> { + let mut cmd = Command::new("rustfmt"); + cmd.arg("--emit") + .arg("stdout") + .stdin(Stdio::piped()) + .stdout(Stdio::piped()); + if let Some(dir) = workdir { + cmd.current_dir(dir); + } + let mut child = cmd.spawn()?; + + { + let stdin = child + .stdin + .take() + .ok_or_else(|| io::Error::other("Failed to open rustfmt stdin"))?; + binding.write(Box::new(stdin))?; + } + + if let Some(mut stdout) = child.stdout.take() { + io::copy(&mut stdout, &mut output)?; + } + + let status = child.wait()?; + if !status.success() { + Err(io::Error::other(format!( + "rustfmt exited with status {status}" + ))) + } else { + Ok(()) + } +} diff --git a/xtask/src/utils/writers/hash_writer.rs b/xtask/src/utils/writers/hash_writer.rs new file mode 100644 index 0000000..0958205 --- /dev/null +++ b/xtask/src/utils/writers/hash_writer.rs @@ -0,0 +1,37 @@ +use sha2::{Sha256, digest::Output}; +use std::io::{Result, Write}; + +use sha2::Digest; + +pub type Sha256HashWriter = HashWriter; + +pub struct HashWriter { + writer: W, + hash: D, +} + +impl Write for HashWriter { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + let value = self.writer.write(buf)?; + self.hash.update(&buf[..value]); + Ok(value) + } + + fn flush(&mut self) -> std::io::Result<()> { + self.writer.flush() + } +} + +impl HashWriter { + pub fn new(writer: W) -> Self { + Self { + writer, + hash: D::new(), + } + } + + pub fn finalise(mut self) -> Result> { + self.writer.flush()?; + Ok(self.hash.finalize()) + } +} diff --git a/xtask/src/utils/writers/mod.rs b/xtask/src/utils/writers/mod.rs new file mode 100644 index 0000000..4241122 --- /dev/null +++ b/xtask/src/utils/writers/mod.rs @@ -0,0 +1,2 @@ +mod hash_writer; +pub use hash_writer::Sha256HashWriter;