Repository: rust-cross/cargo-xwin (language: python)

Issue:
Test and Run support
Would it be possible to add support for `cargo test` and `cargo run`? I'm trying to test blake3 using `CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_RUNNER=wine` but building the tests with xwinbuild and then trying `cargo test --target x86_64-pc-windows-msvc` does a rebuild that fails with `error: linker link.exe not found`.


Discussion / hints:
I think it's possible but it will take some time to implement. IMO `cargo run` isn't a big issue since most of the time you know the binary name and path, you can just invoke `wine` with that.

Fix patch:
diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml
index 05601e4..e2a0a96 100644
--- a/.github/workflows/CI.yml
+++ b/.github/workflows/CI.yml
@@ -33,11 +33,17 @@ jobs:
           path: tests/crfsuite-rs
           submodules: true
       - uses: seanmiddleditch/gha-setup-ninja@master
-      - name: Install GNU tar
+      - name: Install dependencies
         if: matrix.os == 'macos-latest'
         run: |
           brew install gnu-tar
           echo "/usr/local/opt/gnu-tar/libexec/gnubin" >> $GITHUB_PATH
+      - name: Install dependencies
+        if: matrix.os == 'ubuntu-latest'
+        run: |
+          sudo apt update
+          sudo apt-get install --quiet -y wine-stable winetricks
+          winetricks nocrashdialog
       - name: Install LLVM and Clang
         if: matrix.os == 'ubuntu-latest'
         uses: KyleMayes/install-llvm-action@v1
@@ -66,7 +72,7 @@ jobs:
         with:
           command: build
           args: --release
-      - name: Test - x86_64
+      - name: xwinbuild - x86_64
         run: |
           rustup target add x86_64-pc-windows-msvc
 
@@ -76,7 +82,11 @@ jobs:
           cargo run --release xwinbuild --target x86_64-pc-windows-msvc --manifest-path tests/hello-rustls/Cargo.toml
           # Test CMake support
           cargo run --release xwinbuild --target x86_64-pc-windows-msvc --manifest-path tests/crfsuite-rs/Cargo.toml
-      - name: Test - aarch64
+      - name: xwintest - x86_64
+        if: matrix.os == 'ubuntu-latest'
+        run: |
+          cargo run --release --bin cargo-xwintest xwintest --target x86_64-pc-windows-msvc --manifest-path tests/hello-windows/Cargo.toml
+      - name: xwinbuild - aarch64
         run: |
           rustup target add aarch64-pc-windows-msvc
 
diff --git a/.github/workflows/Release.yml b/.github/workflows/Release.yml
index ecdc659..20bf22e 100644
--- a/.github/workflows/Release.yml
+++ b/.github/workflows/Release.yml
@@ -23,7 +23,7 @@ jobs:
       - name: Build
         uses: messense/maturin-action@v1
         with:
-          args: --no-sdist --release --out dist --strip --universal2
+          args: --no-sdist --release --out dist --universal2
       - name: Upload wheels
         uses: actions/upload-artifact@v2
         with:
@@ -81,7 +81,7 @@ jobs:
         uses: messense/maturin-action@v1
         with:
           target: ${{ matrix.platform.target }}
-          args: --release --out dist --no-sdist --strip
+          args: --release --out dist --no-sdist
       - name: Upload wheels
         uses: actions/upload-artifact@v2
         with:
@@ -141,7 +141,7 @@ jobs:
           target: ${{ matrix.platform.target }}
           manylinux: auto
           container: off
-          args: --no-sdist --release -o dist --strip
+          args: --no-sdist --release -o dist
       - name: Upload wheels
         uses: actions/upload-artifact@v2
         with:
diff --git a/Cargo.toml b/Cargo.toml
index d132956..819f5f4 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -2,6 +2,7 @@
 name = "cargo-xwinbuild"
 version = "0.3.3"
 edition = "2018"
+default-run = "cargo-xwinbuild"
 description = "Cross compile Cargo project to Windows msvc target with ease"
 license = "MIT"
 keywords = ["windows", "cargo", "msvc"]
diff --git a/src/build.rs b/src/build.rs
index ad579fc..6d53139 100644
--- a/src/build.rs
+++ b/src/build.rs
@@ -343,28 +343,31 @@ impl Build {
         if let Some(target) = self.target.as_ref() {
             if target.contains("msvc") {
                 self.setup_msvc_crt(xwin_cache_dir.clone())?;
-                let env_target = target.to_uppercase().replace('-', "_");
+                let env_target = target.to_lowercase().replace('-', "_");
                 build.env("TARGET_CC", format!("clang-cl --target={}", target));
                 build.env("TARGET_CXX", format!("clang-cl --target={}", target));
                 build.env(
-                    format!("CC_{}", env_target.to_lowercase()),
+                    format!("CC_{}", env_target),
                     format!("clang-cl --target={}", target),
                 );
                 build.env(
-                    format!("CXX_{}", env_target.to_lowercase()),
+                    format!("CXX_{}", env_target),
                     format!("clang-cl --target={}", target),
                 );
                 build.env("TARGET_AR", "llvm-lib");
                 build.env(format!("AR_{}", env_target), "llvm-lib");
-                build.env(format!("CARGO_TARGET_{}_LINKER", env_target), "lld-link");
+                build.env(
+                    format!("CARGO_TARGET_{}_LINKER", env_target.to_uppercase()),
+                    "lld-link",
+                );
 
                 let cl_flags = format!(
                     "-Wno-unused-command-line-argument -fuse-ld=lld-link /imsvc{dir}/crt/include /imsvc{dir}/sdk/include/ucrt /imsvc{dir}/sdk/include/um /imsvc{dir}/sdk/include/shared",
                     dir = xwin_cache_dir.display()
                 );
                 build.env("CL_FLAGS", &cl_flags);
-                build.env(format!("CFLAGS_{}", env_target.to_lowercase()), &cl_flags);
-                build.env(format!("CXXFLAGS_{}", env_target.to_lowercase()), &cl_flags);
+                build.env(format!("CFLAGS_{}", env_target), &cl_flags);
+                build.env(format!("CXXFLAGS_{}", env_target), &cl_flags);
 
                 let target_arch = target
                     .split_once('-')
@@ -402,7 +405,7 @@ impl Build {
                     .env("CMAKE_GENERATOR", "Ninja")
                     .env("CMAKE_SYSTEM_NAME", "Windows")
                     .env(
-                        format!("CMAKE_TOOLCHAIN_FILE_{}", env_target.to_lowercase()),
+                        format!("CMAKE_TOOLCHAIN_FILE_{}", env_target),
                         cmake_toolchain,
                     );
             }
diff --git a/src/lib.rs b/src/lib.rs
index 86095a4..2a5e5f2 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,3 +1,5 @@
 mod build;
+mod test;
 
 pub use build::Build;
+pub use test::Test;


Test patch:
diff --git a/src/bin/cargo-xwintest.rs b/src/bin/cargo-xwintest.rs
new file mode 100644
index 0000000..bf577a4
--- /dev/null
+++ b/src/bin/cargo-xwintest.rs
@@ -0,0 +1,22 @@
+use cargo_xwinbuild::Test;
+use clap::Parser;
+
+#[allow(clippy::large_enum_variant)]
+#[derive(Debug, Parser)]
+#[clap(
+    version,
+    name = "cargo",
+    global_setting(clap::AppSettings::DeriveDisplayOrder)
+)]
+pub enum Opt {
+    #[clap(name = "xwintest")]
+    Test(Test),
+}
+
+fn main() -> anyhow::Result<()> {
+    let opt = Opt::parse();
+    match opt {
+        Opt::Test(test) => test.execute()?,
+    }
+    Ok(())
+}
diff --git a/src/test.rs b/src/test.rs
new file mode 100644
index 0000000..4cb43ed
--- /dev/null
+++ b/src/test.rs
@@ -0,0 +1,298 @@
+use std::env;
+use std::path::PathBuf;
+use std::process;
+
+use anyhow::{Context, Result};
+use clap::Parser;
+
+use crate::Build;
+
+/// Execute all unit and integration tests and build examples of a local package
+#[derive(Clone, Debug, Default, Parser)]
+#[clap(
+    setting = clap::AppSettings::DeriveDisplayOrder,
+    trailing_var_arg = true,
+    after_help = "Run `cargo help test` for more detailed information.\nRun `cargo test -- --help` for test binary options.")
+]
+pub struct Test {
+    /// Do not print cargo log messages
+    #[clap(short = 'q', long)]
+    pub quiet: bool,
+
+    /// Package to build (see `cargo help pkgid`)
+    #[clap(
+        short = 'p',
+        long = "package",
+        value_name = "SPEC",
+        multiple_values = true
+    )]
+    pub packages: Vec<String>,
+
+    /// Test all packages in the workspace
+    #[clap(long)]
+    pub workspace: bool,
+
+    /// Exclude packages from the build
+    #[clap(long, value_name = "SPEC", multiple_values = true)]
+    pub exclude: Vec<String>,
+
+    /// Alias for workspace (deprecated)
+    #[clap(long)]
+    pub all: bool,
+
+    /// Number of parallel jobs, defaults to # of CPUs
+    #[clap(short = 'j', long, value_name = "N")]
+    pub jobs: Option<usize>,
+
+    /// Test only this package's library
+    #[clap(long)]
+    pub lib: bool,
+
+    /// Test only the specified binary
+    #[clap(long, value_name = "NAME", multiple_values = true)]
+    pub bin: Vec<String>,
+
+    /// Test all binaries
+    #[clap(long)]
+    pub bins: bool,
+
+    /// Test only the specified example
+    #[clap(long, value_name = "NAME", multiple_values = true)]
+    pub example: Vec<String>,
+
+    /// Test all examples
+    #[clap(long)]
+    pub examples: bool,
+
+    /// Test only the specified test target
+    #[clap(long, value_name = "NAME", multiple_values = true)]
+    pub test: Vec<String>,
+
+    /// Test all tests
+    #[clap(long)]
+    pub tests: bool,
+
+    /// Test only the specified bench target
+    #[clap(long, value_name = "NAME", multiple_values = true)]
+    pub bench: Vec<String>,
+
+    /// Test all benches
+    #[clap(long)]
+    pub benches: bool,
+
+    /// Test all targets
+    #[clap(long)]
+    pub all_targets: bool,
+
+    /// Test only this library's documentation
+    #[clap(long)]
+    pub doc: bool,
+
+    /// Compile, but don't run tests
+    #[clap(long)]
+    pub no_run: bool,
+
+    /// Run all tests regardless of failure
+    #[clap(long)]
+    pub no_fail_fast: bool,
+
+    /// Build artifacts in release mode, with optimizations
+    #[clap(short = 'r', long)]
+    pub release: bool,
+
+    /// Build artifacts with the specified Cargo profile
+    #[clap(long, value_name = "PROFILE-NAME")]
+    pub profile: Option<String>,
+
+    /// Space or comma separated list of features to activate
+    #[clap(long, multiple_values = true)]
+    pub features: Vec<String>,
+
+    /// Activate all available features
+    #[clap(long)]
+    pub all_features: bool,
+
+    /// Do not activate the `default` feature
+    #[clap(long)]
+    pub no_default_features: bool,
+
+    /// Build for the target triple
+    #[clap(long, value_name = "TRIPLE", env = "CARGO_BUILD_TARGET")]
+    pub target: Option<String>,
+
+    /// Directory for all generated artifacts
+    #[clap(long, value_name = "DIRECTORY", parse(from_os_str))]
+    pub target_dir: Option<PathBuf>,
+
+    /// Path to Cargo.toml
+    #[clap(long, value_name = "PATH", parse(from_os_str))]
+    pub manifest_path: Option<PathBuf>,
+
+    /// Ignore `rust-version` specification in packages
+    #[clap(long)]
+    pub ignore_rust_version: bool,
+
+    /// Error format
+    #[clap(long, value_name = "FMT", multiple_values = true)]
+    pub message_format: Vec<String>,
+
+    /// Output build graph in JSON (unstable)
+    #[clap(long)]
+    pub unit_graph: bool,
+
+    /// Outputs a future incompatibility report at the end of the build (unstable)
+    #[clap(long)]
+    pub future_incompat_report: bool,
+
+    /// Use verbose output (-vv very verbose/build.rs output)
+    #[clap(short = 'v', long, parse(from_occurrences), max_occurrences = 2)]
+    pub verbose: usize,
+
+    /// Coloring: auto, always, never
+    #[clap(long, value_name = "WHEN")]
+    pub color: Option<String>,
+
+    /// Require Cargo.lock and cache are up to date
+    #[clap(long)]
+    pub frozen: bool,
+
+    /// Require Cargo.lock is up to date
+    #[clap(long)]
+    pub locked: bool,
+
+    /// Run without accessing the network
+    #[clap(long)]
+    pub offline: bool,
+
+    /// Override a configuration value (unstable)
+    #[clap(long, value_name = "KEY=VALUE", multiple_values = true)]
+    pub config: Vec<String>,
+
+    /// Unstable (nightly-only) flags to Cargo, see 'cargo -Z help' for details
+    #[clap(short = 'Z', value_name = "FLAG", multiple_values = true)]
+    pub unstable_flags: Vec<String>,
+
+    /// xwin cache directory
+    #[clap(long, parse(from_os_str), env = "XWIN_CACHE_DIR", hide = true)]
+    pub xwin_cache_dir: Option<PathBuf>,
+
+    /// The architectures to include in CRT/SDK
+    #[clap(
+        long,
+        env = "XWIN_ARCH",
+        possible_values(&["x86", "x86_64", "aarch", "aarch64"]),
+        use_value_delimiter = true,
+        default_value = "x86_64,aarch64",
+        hide = true,
+    )]
+    pub xwin_arch: Vec<xwin::Arch>,
+
+    /// The variants to include
+    #[clap(
+        long,
+        env = "XWIN_VARIANT",
+        possible_values(&["desktop", "onecore", /*"store",*/ "spectre"]),
+        use_value_delimiter = true,
+        default_value = "desktop",
+        hide = true,
+    )]
+    pub xwin_variant: Vec<xwin::Variant>,
+
+    /// The version to retrieve, can either be a major version of 15 or 16, or
+    /// a "<major>.<minor>" version.
+    #[clap(long, env = "XWIN_VERSION", default_value = "16", hide = true)]
+    pub xwin_version: String,
+
+    /// If specified, only run tests containing this string in their names
+    #[clap(value_name = "TESTNAME", takes_value = true)]
+    pub test_name: Option<String>,
+
+    /// Arguments for the test binary
+    #[clap(takes_value = true, multiple_values = true)]
+    pub args: Vec<String>,
+}
+
+impl Test {
+    /// Execute `cargo test` command
+    pub fn execute(&self) -> Result<()> {
+        let build = Build {
+            quiet: self.quiet,
+            packages: self.packages.clone(),
+            workspace: self.workspace,
+            exclude: self.exclude.clone(),
+            all: self.all,
+            jobs: self.jobs,
+            lib: self.lib,
+            bin: self.bin.clone(),
+            bins: self.bins,
+            example: self.example.clone(),
+            examples: self.examples,
+            test: self.test.clone(),
+            tests: self.tests,
+            bench: self.bench.clone(),
+            benches: self.benches,
+            all_targets: self.all_targets,
+            release: self.release,
+            profile: self.profile.clone(),
+            features: self.features.clone(),
+            all_features: self.all_features,
+            no_default_features: self.no_default_features,
+            target: self.target.clone(),
+            target_dir: self.target_dir.clone(),
+            manifest_path: self.manifest_path.clone(),
+            ignore_rust_version: self.ignore_rust_version,
+            message_format: self.message_format.clone(),
+            unit_graph: self.unit_graph,
+            future_incompat_report: self.future_incompat_report,
+            verbose: self.verbose,
+            color: self.color.clone(),
+            frozen: self.frozen,
+            locked: self.locked,
+            offline: self.offline,
+            config: self.config.clone(),
+            unstable_flags: self.unstable_flags.clone(),
+            xwin_cache_dir: self.xwin_cache_dir.clone(),
+            xwin_arch: self.xwin_arch.clone(),
+            xwin_variant: self.xwin_variant.clone(),
+            xwin_version: self.xwin_version.clone(),
+            ..Default::default()
+        };
+        let mut test = build.build_command("test")?;
+        if self.doc {
+            test.arg("--doc");
+        }
+        if self.no_run {
+            test.arg("--no-run");
+        }
+        if self.no_fail_fast {
+            test.arg("--no-fail-fast");
+        }
+        if let Some(test_name) = self.test_name.as_ref() {
+            test.arg(test_name);
+        }
+        if !self.args.is_empty() {
+            test.arg("--");
+            test.args(&self.args);
+        }
+
+        if let Some(target) = self.target.as_ref() {
+            if target.contains("msvc") {
+                if env::var_os("WINEDEBUG").is_none() {
+                    test.env("WINEDEBUG", "-all");
+                }
+                let env_target = target.to_uppercase().replace('-', "_");
+                let runner_env = format!("CARGO_TARGET_{}_RUNNER", env_target);
+                if env::var_os(&runner_env).is_none() {
+                    test.env(runner_env, "wine");
+                }
+            }
+        }
+
+        let mut child = test.spawn().context("Failed to run cargo test")?;
+        let status = child.wait().expect("Failed to wait on cargo test process");
+        if !status.success() {
+            process::exit(status.code().unwrap_or(1));
+        }
+        Ok(())
+    }
+}
diff --git a/tests/hello-windows/src/main.rs b/tests/hello-windows/src/main.rs
index 84da37d..3e63aba 100644
--- a/tests/hello-windows/src/main.rs
+++ b/tests/hello-windows/src/main.rs
@@ -10,3 +10,21 @@ fn main() {
 
     assert_eq!(v1, v2);
 }
+
+#[cfg(test)]
+mod test {
+    use winapi::um::sysinfoapi::GetVersion as WinApiGetVersion;
+    use windows_sys::Win32::System::SystemInformation::GetVersion as WindowsSysGetVersion;
+
+    #[test]
+    fn test_winapi_get_version() {
+        let v1 = unsafe { WinApiGetVersion() };
+        println!("version from winapi: {}", v1);
+    }
+
+    #[test]
+    fn test_windows_sys_get_version() {
+        let v2 = unsafe { WindowsSysGetVersion() };
+        println!("version from windows-sys: {}", v2);
+    }
+}