|
1 |
| -use core::{panic, str}; |
2 |
| -use std::{env, path::PathBuf}; |
| 1 | +use std::{borrow::BorrowMut, env, path::PathBuf, process::Command, str}; |
3 | 2 |
|
4 |
| -fn main() { |
5 |
| - let out_dir = env::var("OUT_DIR").expect("Missing OUT_DIR"); |
| 3 | +use anyhow::{anyhow, bail, Context}; |
6 | 4 |
|
7 |
| - // Add DAITA as a conditional configuration |
| 5 | +fn main() -> anyhow::Result<()> { |
| 6 | + let target_os = env::var("CARGO_CFG_TARGET_OS").context("Missing 'CARGO_CFG_TARGET_OS")?; |
| 7 | + |
| 8 | + // Mark "daita" as a conditional configuration flag |
8 | 9 | println!("cargo::rustc-check-cfg=cfg(daita)");
|
9 | 10 |
|
10 |
| - let target_os = env::var("CARGO_CFG_TARGET_OS").expect("Missing 'CARGO_CFG_TARGET_OS"); |
11 |
| - let mut cmd = std::process::Command::new("bash"); |
12 |
| - cmd.arg("./build-wireguard-go.sh"); |
| 11 | + // Rerun build-script if libwg (or wireguard-go) is changed |
| 12 | + println!("cargo::rerun-if-changed=libwg"); |
13 | 13 |
|
14 | 14 | match target_os.as_str() {
|
15 |
| - "linux" | "macos" => { |
16 |
| - // Enable DAITA |
17 |
| - println!(r#"cargo::rustc-cfg=daita"#); |
18 |
| - // Tell the build script to build wireguard-go with DAITA support |
19 |
| - cmd.arg("--daita"); |
20 |
| - } |
21 |
| - "android" => { |
22 |
| - cmd.arg("--android"); |
23 |
| - } |
| 15 | + "linux" => build_static_lib(Os::Linux, true)?, |
| 16 | + "macos" => build_static_lib(Os::MacOs, true)?, |
| 17 | + "android" => build_android_dynamic_lib()?, |
24 | 18 | // building wireguard-go-rs for windows is not implemented
|
25 |
| - _ => return, |
| 19 | + _ => {} |
26 | 20 | }
|
27 | 21 |
|
28 |
| - let output = cmd.output().expect("build-wireguard-go.sh failed"); |
29 |
| - if !output.status.success() { |
30 |
| - let stdout = str::from_utf8(&output.stdout).unwrap(); |
31 |
| - let stderr = str::from_utf8(&output.stderr).unwrap(); |
32 |
| - eprintln!("build-wireguard-go.sh failed."); |
33 |
| - eprintln!("stdout:\n{stdout}"); |
34 |
| - eprintln!("stderr:\n{stderr}"); |
35 |
| - panic!(); |
| 22 | + Ok(()) |
| 23 | +} |
| 24 | + |
| 25 | +#[derive(PartialEq, Eq)] |
| 26 | +enum Os { |
| 27 | + MacOs, |
| 28 | + Linux, |
| 29 | +} |
| 30 | + |
| 31 | +#[derive(PartialEq, Eq)] |
| 32 | +enum Arch { |
| 33 | + Amd64, |
| 34 | + Arm64, |
| 35 | +} |
| 36 | + |
| 37 | +fn host_os() -> anyhow::Result<Os> { |
| 38 | + // this ugliness is a limitation of rust, where we can't directly |
| 39 | + // access the target triple of the build script. |
| 40 | + if cfg!(target_os = "linux") { |
| 41 | + Ok(Os::Linux) |
| 42 | + } else if cfg!(target_os = "macos") { |
| 43 | + Ok(Os::MacOs) |
| 44 | + } else { |
| 45 | + bail!("Unsupported host OS") |
36 | 46 | }
|
| 47 | +} |
37 | 48 |
|
38 |
| - if target_os.as_str() != "android" { |
39 |
| - println!("cargo::rustc-link-lib=static=wg"); |
| 49 | +fn host_arch() -> anyhow::Result<Arch> { |
| 50 | + if cfg!(target_arch = "x86_64") { |
| 51 | + Ok(Arch::Amd64) |
| 52 | + } else if cfg!(target_arch = "aarch64") { |
| 53 | + Ok(Arch::Arm64) |
40 | 54 | } else {
|
41 |
| - // NOTE: Link dynamically to libwg on Android, as go cannot produce archives |
42 |
| - println!("cargo::rustc-link-lib=dylib=wg"); |
| 55 | + bail!("Unsupported host architecture") |
43 | 56 | }
|
44 |
| - declare_libs_dir("../build/lib"); |
| 57 | +} |
45 | 58 |
|
46 |
| - println!("cargo::rerun-if-changed=libwg"); |
| 59 | +/// Compile libwg as a static library and place it in `OUT_DIR`. |
| 60 | +fn build_static_lib(target_os: Os, daita: bool) -> anyhow::Result<()> { |
| 61 | + let out_dir = env::var("OUT_DIR").context("Missing OUT_DIR")?; |
| 62 | + let target_arch = |
| 63 | + env::var("CARGO_CFG_TARGET_ARCH").context("Missing 'CARGO_CFG_TARGET_ARCH")?; |
| 64 | + |
| 65 | + let target_arch = match target_arch.as_str() { |
| 66 | + "x86_64" => Arch::Amd64, |
| 67 | + "aarch64" => Arch::Arm64, |
| 68 | + _ => bail!("Unsupported architecture: {target_arch}"), |
| 69 | + }; |
| 70 | + |
| 71 | + let out_file = format!("{out_dir}/libwg.a"); |
| 72 | + let mut go_build = Command::new("go"); |
| 73 | + go_build |
| 74 | + .args(["build", "-v", "-o", &out_file]) |
| 75 | + .args(["-buildmode", "c-archive"]) |
| 76 | + .args(if daita { &["--tags", "daita"][..] } else { &[] }) |
| 77 | + .env("CGO_ENABLED", "1") |
| 78 | + .current_dir("./libwg"); |
| 79 | + |
| 80 | + // are we cross compiling? |
| 81 | + let cross_compiling = host_os()? != target_os || host_arch()? != target_arch; |
| 82 | + |
| 83 | + match target_arch { |
| 84 | + Arch::Amd64 => go_build.env("GOARCH", "amd64"), |
| 85 | + Arch::Arm64 => go_build.env("GOARCH", "arm64"), |
| 86 | + }; |
| 87 | + |
| 88 | + match target_os { |
| 89 | + Os::Linux => { |
| 90 | + go_build.env("GOOS", "linux"); |
| 91 | + |
| 92 | + if cross_compiling { |
| 93 | + match target_arch { |
| 94 | + Arch::Arm64 => go_build.env("CC", "aarch64-linux-gnu-gcc"), |
| 95 | + Arch::Amd64 => bail!("cross-compiling to linux x86_64 is not implemented"), |
| 96 | + }; |
| 97 | + } |
| 98 | + } |
| 99 | + Os::MacOs => { |
| 100 | + go_build.env("GOOS", "darwin"); |
47 | 101 |
|
48 |
| - // Add `OUT_DIR` to the library search path to facilitate linking of libwg for debug artifacts, |
49 |
| - // such as test binaries. |
50 |
| - if cfg!(debug_assertions) { |
51 |
| - println!("cargo::rustc-link-search={out_dir}"); |
| 102 | + if cross_compiling { |
| 103 | + let sdkroot = env::var("SDKROOT").context("Missing 'SDKROOT'")?; |
| 104 | + |
| 105 | + let c_arch = match target_arch { |
| 106 | + Arch::Amd64 => "x86_64", |
| 107 | + Arch::Arm64 => "arm64", |
| 108 | + }; |
| 109 | + |
| 110 | + let xcrun_output = |
| 111 | + exec(Command::new("xcrun").args(["-sdk", &sdkroot, "--find", "clang"]))?; |
| 112 | + go_build.env("CC", xcrun_output); |
| 113 | + |
| 114 | + let cflags = format!("-isysroot {sdkroot} -arch {c_arch} -I{sdkroot}/usr/include"); |
| 115 | + go_build.env("CFLAGS", cflags); |
| 116 | + go_build.env("CGO_CFLAGS", format!("-isysroot {sdkroot} -arch {c_arch}")); |
| 117 | + go_build.env("CGO_LDFLAGS", format!("-isysroot {sdkroot} -arch {c_arch}")); |
| 118 | + go_build.env("LD_LIBRARY_PATH", format!("{sdkroot}/usr/lib")); |
| 119 | + } |
| 120 | + } |
52 | 121 | }
|
| 122 | + |
| 123 | + exec(go_build)?; |
| 124 | + |
| 125 | + // make sure to link to the resulting binary |
| 126 | + println!("cargo::rustc-link-search={out_dir}"); |
| 127 | + println!("cargo::rustc-link-lib=static=wg"); |
| 128 | + |
| 129 | + // if daita is enabled, also enable the corresponding rust feature flag |
| 130 | + if daita { |
| 131 | + println!(r#"cargo::rustc-cfg=daita"#); |
| 132 | + } |
| 133 | + |
| 134 | + Ok(()) |
53 | 135 | }
|
54 | 136 |
|
55 |
| -/// Tell linker to check `base`/$TARGET for shared libraries. |
56 |
| -fn declare_libs_dir(base: &str) { |
57 |
| - let target_triplet = env::var("TARGET").expect("TARGET is not set"); |
58 |
| - let lib_dir = manifest_dir().join(base).join(target_triplet); |
| 137 | +/// Compile libwg as a dynamic library for android and place it in `../build/lib/$TARGET`. |
| 138 | +// NOTE: We use dynamic linking as Go cannot produce static binaries specifically for Android. |
| 139 | +fn build_android_dynamic_lib() -> anyhow::Result<()> { |
| 140 | + let target_triplet = env::var("TARGET").context("TARGET is not set")?; |
| 141 | + |
| 142 | + exec(Command::new("./libwg/build-android.sh"))?; |
| 143 | + |
| 144 | + // Tell linker to check `base`/$TARGET for the dynamic library. |
| 145 | + let lib_dir = manifest_dir()?.join("../build/lib").join(target_triplet); |
59 | 146 | println!("cargo::rustc-link-search={}", lib_dir.display());
|
| 147 | + println!("cargo::rustc-link-lib=dylib=wg"); |
| 148 | + |
| 149 | + Ok(()) |
60 | 150 | }
|
61 | 151 |
|
62 | 152 | /// Get the directory containing `Cargo.toml`
|
63 |
| -fn manifest_dir() -> PathBuf { |
| 153 | +fn manifest_dir() -> anyhow::Result<PathBuf> { |
64 | 154 | env::var("CARGO_MANIFEST_DIR")
|
65 | 155 | .map(PathBuf::from)
|
66 |
| - .expect("CARGO_MANIFEST_DIR env var not set") |
| 156 | + .context("CARGO_MANIFEST_DIR env var not set") |
| 157 | +} |
| 158 | + |
| 159 | +/// Execute a command, assert that it succeeds, and return stdout as a string. |
| 160 | +fn exec(mut command: impl BorrowMut<Command>) -> anyhow::Result<String> { |
| 161 | + let command = command.borrow_mut(); |
| 162 | + |
| 163 | + let output = command |
| 164 | + .output() |
| 165 | + .with_context(|| anyhow!("Failed to execute command: {command:?}"))?; |
| 166 | + |
| 167 | + let stdout = str::from_utf8(&output.stdout).unwrap_or("Invalid UTF-8"); |
| 168 | + |
| 169 | + if !output.status.success() { |
| 170 | + let stderr = str::from_utf8(&output.stdout).unwrap_or("Invalid UTF-8"); |
| 171 | + |
| 172 | + eprintln!("Error from {command:?}"); |
| 173 | + eprintln!(); |
| 174 | + eprintln!("stdout:"); |
| 175 | + eprintln!(); |
| 176 | + eprintln!("{stdout}"); |
| 177 | + eprintln!(); |
| 178 | + eprintln!("-------"); |
| 179 | + eprintln!("stderr:"); |
| 180 | + eprintln!(); |
| 181 | + eprintln!("{stderr}"); |
| 182 | + eprintln!(); |
| 183 | + eprintln!("-------"); |
| 184 | + |
| 185 | + return Err(anyhow!("Failed to execute command: {command:?}")).with_context(|| { |
| 186 | + anyhow!( |
| 187 | + "Command exited with a non-zero exit code: {}", |
| 188 | + output.status |
| 189 | + ) |
| 190 | + }); |
| 191 | + } |
| 192 | + |
| 193 | + Ok(stdout.to_string()) |
67 | 194 | }
|
0 commit comments