Cross-compile build works on Windows
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "xtask"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1-alpha1"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
|
||||
@@ -63,7 +63,13 @@ struct Target {
|
||||
archive_ext: &'static str,
|
||||
}
|
||||
|
||||
const TARGETS: &[Target] = &[
|
||||
const CROSS_TARGETS: &[Target] = &[
|
||||
Target {
|
||||
name: "FreeBSD x86_64",
|
||||
triple: "x86_64-unknown-freebsd",
|
||||
binary_name: "anvil",
|
||||
archive_ext: "tar.gz",
|
||||
},
|
||||
Target {
|
||||
name: "Linux x86_64",
|
||||
triple: "x86_64-unknown-linux-gnu",
|
||||
@@ -78,6 +84,18 @@ const TARGETS: &[Target] = &[
|
||||
},
|
||||
];
|
||||
|
||||
/// Returns cross-compile targets for the current host, skipping the native target.
|
||||
fn cross_targets_for_host(host: &str) -> Vec<&'static Target> {
|
||||
CROSS_TARGETS.iter().filter(|t| {
|
||||
match host {
|
||||
"freebsd" => t.triple != "x86_64-unknown-freebsd",
|
||||
"linux" => t.triple != "x86_64-unknown-linux-gnu",
|
||||
"windows" => t.triple != "x86_64-pc-windows-gnu",
|
||||
_ => true,
|
||||
}
|
||||
}).collect()
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -179,16 +197,12 @@ fn read_version(workspace: &Path) -> Result<String> {
|
||||
|
||||
/// Detect host OS
|
||||
fn host_os() -> &'static str {
|
||||
if cfg!(target_os = "freebsd") {
|
||||
"freebsd"
|
||||
} else if cfg!(target_os = "linux") {
|
||||
"linux"
|
||||
} else if cfg!(target_os = "macos") {
|
||||
"macos"
|
||||
} else if cfg!(target_os = "windows") {
|
||||
"windows"
|
||||
} else {
|
||||
"unknown"
|
||||
match std::env::consts::OS {
|
||||
"freebsd" => "freebsd",
|
||||
"linux" => "linux",
|
||||
"macos" => "macos",
|
||||
"windows" => "windows",
|
||||
_ => "unknown",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -242,12 +256,20 @@ fn package_binary(
|
||||
}
|
||||
"zip" => {
|
||||
let archive = artifacts.join(format!("{}.zip", archive_name));
|
||||
// -j = junk paths (store just the filename, not the full path)
|
||||
run(
|
||||
"zip",
|
||||
&["-q", "-j", archive.to_str().unwrap(), binary_path.to_str().unwrap()],
|
||||
None,
|
||||
)?;
|
||||
if cfg!(windows) {
|
||||
let ps_cmd = format!(
|
||||
"Compress-Archive -Path '{}' -DestinationPath '{}' -Force",
|
||||
binary_path.display(),
|
||||
archive.display()
|
||||
);
|
||||
run("powershell", &["-NoProfile", "-Command", &ps_cmd], None)?;
|
||||
} else {
|
||||
run(
|
||||
"zip",
|
||||
&["-q", "-j", archive.to_str().unwrap(), binary_path.to_str().unwrap()],
|
||||
None,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
_ => bail!("Unknown archive format: {}", archive_ext),
|
||||
}
|
||||
@@ -342,17 +364,30 @@ fn run_check(_workspace: &Path) -> Result<bool> {
|
||||
ok(&format!("zig {}", ver));
|
||||
} else {
|
||||
warn("zig -- not found");
|
||||
println!(" {}", "sudo pkg install zig (FreeBSD) or apt install zig (Linux)");
|
||||
println!(" {}", match host_os() {
|
||||
"freebsd" => "sudo pkg install zig",
|
||||
"linux" => "sudo apt install zig (or: sudo dnf install zig)",
|
||||
"windows" => "winget install zig.zig (or: choco install zig)",
|
||||
_ => "see https://ziglang.org/download/",
|
||||
});
|
||||
missing += 1;
|
||||
}
|
||||
|
||||
// zip (for Windows archive)
|
||||
if cmd_exists("zip") {
|
||||
ok("zip");
|
||||
// zip -- not needed on Windows (PowerShell Compress-Archive is built in)
|
||||
if !cfg!(windows) {
|
||||
if cmd_exists("zip") {
|
||||
ok("zip");
|
||||
} else {
|
||||
warn("zip -- not found");
|
||||
println!(" {}", match host_os() {
|
||||
"freebsd" => "sudo pkg install zip",
|
||||
"linux" => "sudo apt install zip",
|
||||
_ => "install zip",
|
||||
});
|
||||
missing += 1;
|
||||
}
|
||||
} else {
|
||||
warn("zip -- not found");
|
||||
println!(" {}", "sudo pkg install zip (FreeBSD) or apt install zip (Linux)");
|
||||
missing += 1;
|
||||
ok("zip (PowerShell Compress-Archive -- built in)");
|
||||
}
|
||||
|
||||
// cargo-zigbuild
|
||||
@@ -370,7 +405,7 @@ fn run_check(_workspace: &Path) -> Result<bool> {
|
||||
if cmd_exists("rustup") {
|
||||
let targets = run_captured("rustup", &["target", "list", "--installed"])
|
||||
.unwrap_or_default();
|
||||
for t in TARGETS {
|
||||
for t in cross_targets_for_host(host_os()) {
|
||||
if targets.contains(t.triple) {
|
||||
ok(&format!("rustup target: {}", t.triple));
|
||||
} else {
|
||||
@@ -387,13 +422,13 @@ fn run_check(_workspace: &Path) -> Result<bool> {
|
||||
|
||||
// Host platform note
|
||||
let host = host_os();
|
||||
if host == "linux" {
|
||||
if host == "windows" {
|
||||
println!();
|
||||
warn("Running on Linux -- FreeBSD binary cannot be produced.");
|
||||
println!(" Boot into FreeBSD and run 'cargo xtask' for the FreeBSD binary.");
|
||||
} else if host != "freebsd" {
|
||||
ok("Windows host -- cargo-zigbuild produces Linux, FreeBSD, and Windows binaries");
|
||||
} else if host == "linux" {
|
||||
println!();
|
||||
warn(&format!("Unsupported host OS: {}", host));
|
||||
ok("Linux host -- cargo-zigbuild produces FreeBSD and Windows binaries");
|
||||
println!(" (native Linux binary built with plain cargo)");
|
||||
}
|
||||
|
||||
println!();
|
||||
@@ -410,6 +445,38 @@ fn run_check(_workspace: &Path) -> Result<bool> {
|
||||
// Fix
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// On Windows, reads the current Machine + User PATH from the registry and
|
||||
/// returns a merged PATH string so the current process can find newly
|
||||
/// installed tools without requiring a new terminal session.
|
||||
#[cfg(windows)]
|
||||
fn refresh_windows_path() -> Result<String> {
|
||||
use std::process::Command;
|
||||
// Read system PATH
|
||||
let sys = Command::new("powershell")
|
||||
.args(&[
|
||||
"-NoProfile", "-Command",
|
||||
"[System.Environment]::GetEnvironmentVariable('PATH','Machine')"
|
||||
])
|
||||
.output()
|
||||
.map(|o| String::from_utf8_lossy(&o.stdout).trim().to_string())
|
||||
.unwrap_or_default();
|
||||
// Read user PATH
|
||||
let usr = Command::new("powershell")
|
||||
.args(&[
|
||||
"-NoProfile", "-Command",
|
||||
"[System.Environment]::GetEnvironmentVariable('PATH','User')"
|
||||
])
|
||||
.output()
|
||||
.map(|o| String::from_utf8_lossy(&o.stdout).trim().to_string())
|
||||
.unwrap_or_default();
|
||||
Ok(format!("{};{}", sys, usr))
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
fn refresh_windows_path() -> Result<String> {
|
||||
bail!("refresh_windows_path called on non-Windows platform")
|
||||
}
|
||||
|
||||
fn run_fix() -> Result<()> {
|
||||
header("Installing dependencies");
|
||||
|
||||
@@ -432,30 +499,57 @@ fn run_fix() -> Result<()> {
|
||||
bail!("Could not install zig automatically. Install manually: https://ziglang.org/download/");
|
||||
}
|
||||
}
|
||||
"windows" => {
|
||||
if cmd_exists("winget") {
|
||||
run("winget", &["install", "--id", "zig.zig", "-e", "--silent"], None)?;
|
||||
// Refresh PATH in the current process so zig is findable immediately.
|
||||
// winget updates the registry but the current session won't see it
|
||||
// without this -- otherwise the user needs to open a new terminal.
|
||||
if let Ok(new_path) = refresh_windows_path() {
|
||||
unsafe {
|
||||
std::env::set_var("PATH", &new_path);
|
||||
}
|
||||
}
|
||||
if !cmd_exists("zig") {
|
||||
println!();
|
||||
warn("zig was installed but is not yet on PATH.");
|
||||
println!(" Open a new terminal and run: cargo xtask");
|
||||
println!(" (Windows PATH updates require a new shell session)");
|
||||
}
|
||||
} else if cmd_exists("choco") {
|
||||
run("choco", &["install", "zig", "-y"], None)?;
|
||||
} else {
|
||||
bail!("Install zig via winget: winget install zig.zig\n Or download from: https://ziglang.org/download/");
|
||||
}
|
||||
}
|
||||
_ => bail!("Cannot auto-install zig on {}. Install manually: https://ziglang.org/download/", host),
|
||||
}
|
||||
ok("zig installed");
|
||||
}
|
||||
|
||||
// zip (for Windows archive packaging)
|
||||
if cmd_exists("zip") {
|
||||
ok("zip already installed");
|
||||
} else {
|
||||
info("Installing zip...");
|
||||
match host {
|
||||
"freebsd" => run("sudo", &["pkg", "install", "-y", "zip"], None)?,
|
||||
"linux" => {
|
||||
if cmd_exists("apt-get") {
|
||||
run("sudo", &["apt-get", "install", "-y", "zip"], None)?;
|
||||
} else if cmd_exists("dnf") {
|
||||
run("sudo", &["dnf", "install", "-y", "zip"], None)?;
|
||||
} else {
|
||||
bail!("Could not install zip automatically. Install it manually.");
|
||||
// zip (not needed on Windows -- PowerShell Compress-Archive is built in)
|
||||
if host != "windows" {
|
||||
if cmd_exists("zip") {
|
||||
ok("zip already installed");
|
||||
} else {
|
||||
info("Installing zip...");
|
||||
match host {
|
||||
"freebsd" => run("sudo", &["pkg", "install", "-y", "zip"], None)?,
|
||||
"linux" => {
|
||||
if cmd_exists("apt-get") {
|
||||
run("sudo", &["apt-get", "install", "-y", "zip"], None)?;
|
||||
} else if cmd_exists("dnf") {
|
||||
run("sudo", &["dnf", "install", "-y", "zip"], None)?;
|
||||
} else {
|
||||
bail!("Could not install zip automatically. Install it manually.");
|
||||
}
|
||||
}
|
||||
_ => bail!("Cannot auto-install zip on {}. Install it manually.", host),
|
||||
}
|
||||
_ => bail!("Cannot auto-install zip on {}. Install it manually.", host),
|
||||
ok("zip installed");
|
||||
}
|
||||
ok("zip installed");
|
||||
} else {
|
||||
ok("zip not needed on Windows (using PowerShell Compress-Archive)");
|
||||
}
|
||||
|
||||
// cargo-zigbuild
|
||||
@@ -471,7 +565,7 @@ fn run_fix() -> Result<()> {
|
||||
if cmd_exists("rustup") {
|
||||
let installed = run_captured("rustup", &["target", "list", "--installed"])
|
||||
.unwrap_or_default();
|
||||
for t in TARGETS {
|
||||
for t in cross_targets_for_host(host) {
|
||||
if installed.contains(t.triple) {
|
||||
ok(&format!("rustup target {} already installed", t.triple));
|
||||
} else {
|
||||
@@ -496,7 +590,7 @@ fn run_fix() -> Result<()> {
|
||||
fn run_clean(workspace: &Path) -> Result<()> {
|
||||
header("Cleaning cross-compile artifacts");
|
||||
|
||||
for t in TARGETS {
|
||||
for t in CROSS_TARGETS {
|
||||
let target_dir = workspace.join("target").join(t.triple);
|
||||
if target_dir.exists() {
|
||||
info(&format!("Removing target/{}/...", t.triple));
|
||||
@@ -538,56 +632,53 @@ fn run_build(workspace: &Path, version: &str) -> Result<()> {
|
||||
}
|
||||
fs::create_dir_all(&artifacts)?;
|
||||
|
||||
// -- FreeBSD native ----------------------------------------------------
|
||||
if host == "freebsd" {
|
||||
header("FreeBSD x86_64");
|
||||
info("Building...");
|
||||
run("cargo", &["build", "--release"], Some(workspace))?;
|
||||
let archive_name = format!("anvil-{}-freebsd-x86_64", version);
|
||||
// -- Native binary (current host) -------------------------------------
|
||||
let (native_triple, native_name, native_ext) = match host {
|
||||
"freebsd" => ("freebsd-x86_64", "anvil", "tar.gz"),
|
||||
"linux" => ("linux-x86_64", "anvil", "tar.gz"),
|
||||
"windows" => ("windows-x86_64", "anvil.exe", "zip"),
|
||||
_ => ("unknown", "anvil", "tar.gz"),
|
||||
};
|
||||
header(&format!("{} (native)", native_name.to_uppercase().replace("ANVIL", &format!("{} x86_64", host.to_uppercase().replace("FREEBSD", "FreeBSD").replace("LINUX", "Linux").replace("WINDOWS", "Windows")))));
|
||||
info("Building...");
|
||||
run("cargo", &["build", "--release"], Some(workspace))?;
|
||||
let archive_name = format!("anvil-{}-{}", version, native_triple);
|
||||
if native_ext == "zip" {
|
||||
// Windows native -- package exe directly
|
||||
let binary_path = workspace.join("target").join("release").join(native_name);
|
||||
package_binary(workspace, "", native_name, &archive_name, "zip")?;
|
||||
let _ = binary_path; // used inside package_binary
|
||||
} else {
|
||||
package_native(workspace, &archive_name)?;
|
||||
ok(&format!("{}.tar.gz", archive_name));
|
||||
} else if host == "linux" {
|
||||
println!();
|
||||
warn("Running on Linux -- FreeBSD binary cannot be produced.");
|
||||
println!(" Boot into FreeBSD and run 'cargo xtask' for the FreeBSD binary.");
|
||||
println!();
|
||||
}
|
||||
ok(&format!("{}.{}", archive_name, native_ext));
|
||||
|
||||
// -- Linux x86_64 via cargo-zigbuild -----------------------------------
|
||||
header("Linux x86_64 (via cargo-zigbuild)");
|
||||
info("Building...");
|
||||
run(
|
||||
"cargo",
|
||||
&["zigbuild", "--release", "--target", "x86_64-unknown-linux-gnu"],
|
||||
Some(workspace),
|
||||
)?;
|
||||
let archive_name = format!("anvil-{}-linux-x86_64", version);
|
||||
package_binary(
|
||||
workspace,
|
||||
"x86_64-unknown-linux-gnu",
|
||||
"anvil",
|
||||
&archive_name,
|
||||
"tar.gz",
|
||||
)?;
|
||||
ok(&format!("{}.tar.gz", archive_name));
|
||||
|
||||
// -- Windows x86_64 via cargo-zigbuild ---------------------------------
|
||||
header("Windows x86_64 (via cargo-zigbuild)");
|
||||
info("Building...");
|
||||
run(
|
||||
"cargo",
|
||||
&["zigbuild", "--release", "--target", "x86_64-pc-windows-gnu"],
|
||||
Some(workspace),
|
||||
)?;
|
||||
let archive_name = format!("anvil-{}-windows-x86_64", version);
|
||||
package_binary(
|
||||
workspace,
|
||||
"x86_64-pc-windows-gnu",
|
||||
"anvil.exe",
|
||||
&archive_name,
|
||||
"zip",
|
||||
)?;
|
||||
ok(&format!("{}.zip", archive_name));
|
||||
// -- Cross-compile targets via cargo-zigbuild --------------------------
|
||||
for t in cross_targets_for_host(host) {
|
||||
let label = match t.triple {
|
||||
"x86_64-unknown-freebsd" => "FreeBSD x86_64",
|
||||
"x86_64-unknown-linux-gnu" => "Linux x86_64",
|
||||
"x86_64-pc-windows-gnu" => "Windows x86_64",
|
||||
other => other,
|
||||
};
|
||||
header(&format!("{} (via cargo-zigbuild)", label));
|
||||
info("Building...");
|
||||
run(
|
||||
"cargo",
|
||||
&["zigbuild", "--release", "--target", t.triple],
|
||||
Some(workspace),
|
||||
)?;
|
||||
let suffix = t.triple
|
||||
.replace("x86_64-unknown-", "")
|
||||
.replace("x86_64-pc-", "")
|
||||
.replace("-gnu", "")
|
||||
.replace("freebsd", "freebsd-x86_64")
|
||||
.replace("linux", "linux-x86_64")
|
||||
.replace("windows", "windows-x86_64");
|
||||
let archive_name = format!("anvil-{}-{}", version, suffix);
|
||||
package_binary(workspace, t.triple, t.binary_name, &archive_name, t.archive_ext)?;
|
||||
ok(&format!("{}.{}", archive_name, t.archive_ext));
|
||||
}
|
||||
|
||||
// -- Checksums ---------------------------------------------------------
|
||||
header("Checksums");
|
||||
|
||||
Reference in New Issue
Block a user