-
Notifications
You must be signed in to change notification settings - Fork 352
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'wgrs-constant-rebuild'
- Loading branch information
Showing
4 changed files
with
174 additions
and
172 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,68 +1,194 @@ | ||
use core::{panic, str}; | ||
use std::{env, path::PathBuf}; | ||
use std::{borrow::BorrowMut, env, path::PathBuf, process::Command, str}; | ||
|
||
fn main() { | ||
let out_dir = env::var("OUT_DIR").expect("Missing OUT_DIR"); | ||
use anyhow::{anyhow, bail, Context}; | ||
|
||
// Add DAITA as a conditional configuration | ||
fn main() -> anyhow::Result<()> { | ||
let target_os = env::var("CARGO_CFG_TARGET_OS").context("Missing 'CARGO_CFG_TARGET_OS")?; | ||
|
||
// Mark "daita" as a conditional configuration flag | ||
println!("cargo::rustc-check-cfg=cfg(daita)"); | ||
|
||
let target_os = env::var("CARGO_CFG_TARGET_OS").expect("Missing 'CARGO_CFG_TARGET_OS"); | ||
let mut cmd = std::process::Command::new("bash"); | ||
cmd.arg("./build-wireguard-go.sh"); | ||
// Rerun build-script if libwg (or wireguard-go) is changed | ||
println!("cargo::rerun-if-changed=libwg"); | ||
|
||
match target_os.as_str() { | ||
"linux" | "macos" => { | ||
// Enable DAITA | ||
println!(r#"cargo::rustc-cfg=daita"#); | ||
// Tell the build script to build wireguard-go with DAITA support | ||
cmd.arg("--daita"); | ||
} | ||
"android" => { | ||
cmd.arg("--android"); | ||
} | ||
"linux" => build_static_lib(Os::Linux, true)?, | ||
"macos" => build_static_lib(Os::MacOs, true)?, | ||
"android" => build_android_dynamic_lib()?, | ||
// building wireguard-go-rs for windows is not implemented | ||
_ => return, | ||
_ => {} | ||
} | ||
|
||
let output = cmd.output().expect("build-wireguard-go.sh failed"); | ||
if !output.status.success() { | ||
let stdout = str::from_utf8(&output.stdout).unwrap(); | ||
let stderr = str::from_utf8(&output.stderr).unwrap(); | ||
eprintln!("build-wireguard-go.sh failed."); | ||
eprintln!("stdout:\n{stdout}"); | ||
eprintln!("stderr:\n{stderr}"); | ||
panic!(); | ||
Ok(()) | ||
} | ||
|
||
#[derive(PartialEq, Eq)] | ||
enum Os { | ||
MacOs, | ||
Linux, | ||
} | ||
|
||
#[derive(PartialEq, Eq)] | ||
enum Arch { | ||
Amd64, | ||
Arm64, | ||
} | ||
|
||
fn host_os() -> anyhow::Result<Os> { | ||
// this ugliness is a limitation of rust, where we can't directly | ||
// access the target triple of the build script. | ||
if cfg!(target_os = "linux") { | ||
Ok(Os::Linux) | ||
} else if cfg!(target_os = "macos") { | ||
Ok(Os::MacOs) | ||
} else { | ||
bail!("Unsupported host OS") | ||
} | ||
} | ||
|
||
if target_os.as_str() != "android" { | ||
println!("cargo::rustc-link-lib=static=wg"); | ||
fn host_arch() -> anyhow::Result<Arch> { | ||
if cfg!(target_arch = "x86_64") { | ||
Ok(Arch::Amd64) | ||
} else if cfg!(target_arch = "aarch64") { | ||
Ok(Arch::Arm64) | ||
} else { | ||
// NOTE: Link dynamically to libwg on Android, as go cannot produce archives | ||
println!("cargo::rustc-link-lib=dylib=wg"); | ||
bail!("Unsupported host architecture") | ||
} | ||
declare_libs_dir("../build/lib"); | ||
} | ||
|
||
println!("cargo::rerun-if-changed=libwg"); | ||
/// Compile libwg as a static library and place it in `OUT_DIR`. | ||
fn build_static_lib(target_os: Os, daita: bool) -> anyhow::Result<()> { | ||
let out_dir = env::var("OUT_DIR").context("Missing OUT_DIR")?; | ||
let target_arch = | ||
env::var("CARGO_CFG_TARGET_ARCH").context("Missing 'CARGO_CFG_TARGET_ARCH")?; | ||
|
||
let target_arch = match target_arch.as_str() { | ||
"x86_64" => Arch::Amd64, | ||
"aarch64" => Arch::Arm64, | ||
_ => bail!("Unsupported architecture: {target_arch}"), | ||
}; | ||
|
||
let out_file = format!("{out_dir}/libwg.a"); | ||
let mut go_build = Command::new("go"); | ||
go_build | ||
.args(["build", "-v", "-o", &out_file]) | ||
.args(["-buildmode", "c-archive"]) | ||
.args(if daita { &["--tags", "daita"][..] } else { &[] }) | ||
.env("CGO_ENABLED", "1") | ||
.current_dir("./libwg"); | ||
|
||
// are we cross compiling? | ||
let cross_compiling = host_os()? != target_os || host_arch()? != target_arch; | ||
|
||
match target_arch { | ||
Arch::Amd64 => go_build.env("GOARCH", "amd64"), | ||
Arch::Arm64 => go_build.env("GOARCH", "arm64"), | ||
}; | ||
|
||
match target_os { | ||
Os::Linux => { | ||
go_build.env("GOOS", "linux"); | ||
|
||
if cross_compiling { | ||
match target_arch { | ||
Arch::Arm64 => go_build.env("CC", "aarch64-linux-gnu-gcc"), | ||
Arch::Amd64 => bail!("cross-compiling to linux x86_64 is not implemented"), | ||
}; | ||
} | ||
} | ||
Os::MacOs => { | ||
go_build.env("GOOS", "darwin"); | ||
|
||
// Add `OUT_DIR` to the library search path to facilitate linking of libwg for debug artifacts, | ||
// such as test binaries. | ||
if cfg!(debug_assertions) { | ||
println!("cargo::rustc-link-search={out_dir}"); | ||
if cross_compiling { | ||
let sdkroot = env::var("SDKROOT").context("Missing 'SDKROOT'")?; | ||
|
||
let c_arch = match target_arch { | ||
Arch::Amd64 => "x86_64", | ||
Arch::Arm64 => "arm64", | ||
}; | ||
|
||
let xcrun_output = | ||
exec(Command::new("xcrun").args(["-sdk", &sdkroot, "--find", "clang"]))?; | ||
go_build.env("CC", xcrun_output); | ||
|
||
let cflags = format!("-isysroot {sdkroot} -arch {c_arch} -I{sdkroot}/usr/include"); | ||
go_build.env("CFLAGS", cflags); | ||
go_build.env("CGO_CFLAGS", format!("-isysroot {sdkroot} -arch {c_arch}")); | ||
go_build.env("CGO_LDFLAGS", format!("-isysroot {sdkroot} -arch {c_arch}")); | ||
go_build.env("LD_LIBRARY_PATH", format!("{sdkroot}/usr/lib")); | ||
} | ||
} | ||
} | ||
|
||
exec(go_build)?; | ||
|
||
// make sure to link to the resulting binary | ||
println!("cargo::rustc-link-search={out_dir}"); | ||
println!("cargo::rustc-link-lib=static=wg"); | ||
|
||
// if daita is enabled, also enable the corresponding rust feature flag | ||
if daita { | ||
println!(r#"cargo::rustc-cfg=daita"#); | ||
} | ||
|
||
Ok(()) | ||
} | ||
|
||
/// Tell linker to check `base`/$TARGET for shared libraries. | ||
fn declare_libs_dir(base: &str) { | ||
let target_triplet = env::var("TARGET").expect("TARGET is not set"); | ||
let lib_dir = manifest_dir().join(base).join(target_triplet); | ||
println!("cargo::rerun-if-changed={}", lib_dir.display()); | ||
/// Compile libwg as a dynamic library for android and place it in `../build/lib/$TARGET`. | ||
// NOTE: We use dynamic linking as Go cannot produce static binaries specifically for Android. | ||
fn build_android_dynamic_lib() -> anyhow::Result<()> { | ||
let target_triplet = env::var("TARGET").context("TARGET is not set")?; | ||
|
||
exec(Command::new("./libwg/build-android.sh"))?; | ||
|
||
// Tell linker to check `base`/$TARGET for the dynamic library. | ||
let lib_dir = manifest_dir()?.join("../build/lib").join(target_triplet); | ||
println!("cargo::rustc-link-search={}", lib_dir.display()); | ||
println!("cargo::rustc-link-lib=dylib=wg"); | ||
|
||
Ok(()) | ||
} | ||
|
||
/// Get the directory containing `Cargo.toml` | ||
fn manifest_dir() -> PathBuf { | ||
fn manifest_dir() -> anyhow::Result<PathBuf> { | ||
env::var("CARGO_MANIFEST_DIR") | ||
.map(PathBuf::from) | ||
.expect("CARGO_MANIFEST_DIR env var not set") | ||
.context("CARGO_MANIFEST_DIR env var not set") | ||
} | ||
|
||
/// Execute a command, assert that it succeeds, and return stdout as a string. | ||
fn exec(mut command: impl BorrowMut<Command>) -> anyhow::Result<String> { | ||
let command = command.borrow_mut(); | ||
|
||
let output = command | ||
.output() | ||
.with_context(|| anyhow!("Failed to execute command: {command:?}"))?; | ||
|
||
let stdout = str::from_utf8(&output.stdout).unwrap_or("Invalid UTF-8"); | ||
|
||
if !output.status.success() { | ||
let stderr = str::from_utf8(&output.stdout).unwrap_or("Invalid UTF-8"); | ||
|
||
eprintln!("Error from {command:?}"); | ||
eprintln!(); | ||
eprintln!("stdout:"); | ||
eprintln!(); | ||
eprintln!("{stdout}"); | ||
eprintln!(); | ||
eprintln!("-------"); | ||
eprintln!("stderr:"); | ||
eprintln!(); | ||
eprintln!("{stderr}"); | ||
eprintln!(); | ||
eprintln!("-------"); | ||
|
||
return Err(anyhow!("Failed to execute command: {command:?}")).with_context(|| { | ||
anyhow!( | ||
"Command exited with a non-zero exit code: {}", | ||
output.status | ||
) | ||
}); | ||
} | ||
|
||
Ok(stdout.to_string()) | ||
} |