|
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!(); |
36 |
| - } |
| 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 | +/// Compile libwg as a static library and place it in `OUT_DIR`. |
| 38 | +fn build_static_lib(target_os: Os, daita: bool) -> anyhow::Result<()> { |
| 39 | + let out_dir = env::var("OUT_DIR").context("Missing OUT_DIR")?; |
| 40 | + let target_arch = |
| 41 | + env::var("CARGO_CFG_TARGET_ARCH").context("Missing 'CARGO_CFG_TARGET_ARCH")?; |
| 42 | + |
| 43 | + let target_arch = match target_arch.as_str() { |
| 44 | + "x86_64" => Arch::Amd64, |
| 45 | + "aarch64" => Arch::Arm64, |
| 46 | + _ => bail!("Unsupported architecture: {target_arch}"), |
| 47 | + }; |
| 48 | + |
| 49 | + let out_file = format!("{out_dir}/libwg.a"); |
| 50 | + let mut go_build = Command::new("go"); |
| 51 | + go_build |
| 52 | + .args(["build", "-v", "-o", &out_file]) |
| 53 | + .args(["-buildmode", "c-archive"]) |
| 54 | + .args(if daita { &["--tags", "daita"][..] } else { &[] }) |
| 55 | + .env("CGO_ENABLED", "1") |
| 56 | + .current_dir("./libwg"); |
| 57 | + |
| 58 | + // compare target of build-script vs library target to figure out of this is cross compilation |
| 59 | + // this ugliness is a limitation of rust, where we can't directly access the target triple of |
| 60 | + // the build script. |
| 61 | + let cross_compiling = (target_arch == Arch::Amd64 && cfg!(not(target_arch = "x86_64"))) |
| 62 | + || (target_arch == Arch::Arm64 && cfg!(not(target_arch = "aarch64"))) |
| 63 | + || (target_os == Os::Linux && cfg!(not(target_os = "linux"))) |
| 64 | + || (target_os == Os::MacOs && cfg!(not(target_os = "macos"))); |
| 65 | + |
| 66 | + match target_arch { |
| 67 | + Arch::Amd64 => go_build.env("GOARCH", "amd64"), |
| 68 | + Arch::Arm64 => go_build.env("GOARCH", "arm64"), |
| 69 | + }; |
| 70 | + |
| 71 | + match target_os { |
| 72 | + Os::Linux => { |
| 73 | + go_build.env("GOOS", "linux"); |
| 74 | + |
| 75 | + if cross_compiling { |
| 76 | + match target_arch { |
| 77 | + Arch::Arm64 => go_build.env("CC", "aarch64-linux-gnu-gcc"), |
| 78 | + Arch::Amd64 => bail!("cross-compiling to linux x86_64 is not implemented"), |
| 79 | + }; |
| 80 | + } |
| 81 | + } |
| 82 | + Os::MacOs => { |
| 83 | + go_build.env("GOOS", "darwin"); |
| 84 | + |
| 85 | + if cross_compiling { |
| 86 | + let sdkroot = env::var("SDKROOT").context("Missing 'SDKROOT'")?; |
37 | 87 |
|
38 |
| - if target_os.as_str() != "android" { |
39 |
| - println!("cargo::rustc-link-lib=static=wg"); |
40 |
| - } else { |
41 |
| - // NOTE: Link dynamically to libwg on Android, as go cannot produce archives |
42 |
| - println!("cargo::rustc-link-lib=dylib=wg"); |
| 88 | + let c_arch = match target_arch { |
| 89 | + Arch::Amd64 => "x86_64", |
| 90 | + Arch::Arm64 => "arm64", |
| 91 | + }; |
| 92 | + |
| 93 | + let xcrun_output = |
| 94 | + exec(Command::new("xcrun").args(["-sdk", &sdkroot, "--find", "clang"]))?; |
| 95 | + go_build.env("CC", xcrun_output); |
| 96 | + |
| 97 | + let cflags = format!("-isysroot {sdkroot} -arch {c_arch} -I{sdkroot}/usr/include"); |
| 98 | + go_build.env("CFLAGS", cflags); |
| 99 | + go_build.env("CGO_CFLAGS", format!("-isysroot {sdkroot} -arch {c_arch}")); |
| 100 | + go_build.env("CGO_LDFLAGS", format!("-isysroot {sdkroot} -arch {c_arch}")); |
| 101 | + go_build.env("LD_LIBRARY_PATH", format!("{sdkroot}/usr/lib")); |
| 102 | + } |
| 103 | + } |
43 | 104 | }
|
44 |
| - declare_libs_dir("../build/lib"); |
45 | 105 |
|
46 |
| - println!("cargo::rerun-if-changed=libwg"); |
| 106 | + exec(go_build)?; |
| 107 | + |
| 108 | + // make sure to link to the resulting binary |
| 109 | + println!("cargo::rustc-link-search={out_dir}"); |
| 110 | + println!("cargo::rustc-link-lib=static=wg"); |
47 | 111 |
|
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}"); |
| 112 | + // if daita is enabled, also enable the corresponding rust feature flag |
| 113 | + if daita { |
| 114 | + println!(r#"cargo::rustc-cfg=daita"#); |
52 | 115 | }
|
| 116 | + |
| 117 | + Ok(()) |
53 | 118 | }
|
54 | 119 |
|
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); |
| 120 | +/// Compile libwg as a dynamic library for android and place it in `../build/lib/$TARGET`. |
| 121 | +// NOTE: We use dynamic linking as Go cannot produce static binaries specifically for Android. |
| 122 | +fn build_android_dynamic_lib() -> anyhow::Result<()> { |
| 123 | + let target_triplet = env::var("TARGET").context("TARGET is not set")?; |
| 124 | + |
| 125 | + exec(Command::new("./libwg/build-android.sh"))?; |
| 126 | + |
| 127 | + // Tell linker to check `base`/$TARGET for the dynamic library. |
| 128 | + let lib_dir = manifest_dir()?.join("../build/lib").join(target_triplet); |
59 | 129 | println!("cargo::rustc-link-search={}", lib_dir.display());
|
| 130 | + println!("cargo::rustc-link-lib=dylib=wg"); |
| 131 | + |
| 132 | + Ok(()) |
60 | 133 | }
|
61 | 134 |
|
62 | 135 | /// Get the directory containing `Cargo.toml`
|
63 |
| -fn manifest_dir() -> PathBuf { |
| 136 | +fn manifest_dir() -> anyhow::Result<PathBuf> { |
64 | 137 | env::var("CARGO_MANIFEST_DIR")
|
65 | 138 | .map(PathBuf::from)
|
66 |
| - .expect("CARGO_MANIFEST_DIR env var not set") |
| 139 | + .context("CARGO_MANIFEST_DIR env var not set") |
| 140 | +} |
| 141 | + |
| 142 | +/// Execute a command, assert that it succeeds, and return stdout as a string. |
| 143 | +fn exec(mut command: impl BorrowMut<Command>) -> anyhow::Result<String> { |
| 144 | + let command = command.borrow_mut(); |
| 145 | + |
| 146 | + let output = command |
| 147 | + .output() |
| 148 | + .with_context(|| anyhow!("Failed to execute command: {command:?}"))?; |
| 149 | + |
| 150 | + let stdout = str::from_utf8(&output.stdout).unwrap_or("Invalid UTF-8"); |
| 151 | + |
| 152 | + if !output.status.success() { |
| 153 | + let stderr = str::from_utf8(&output.stdout).unwrap_or("Invalid UTF-8"); |
| 154 | + |
| 155 | + eprintln!("Error from {command:?}"); |
| 156 | + eprintln!(); |
| 157 | + eprintln!("stdout:"); |
| 158 | + eprintln!(); |
| 159 | + eprintln!("{stdout}"); |
| 160 | + eprintln!(); |
| 161 | + eprintln!("-------"); |
| 162 | + eprintln!("stderr:"); |
| 163 | + eprintln!(); |
| 164 | + eprintln!("{stderr}"); |
| 165 | + eprintln!(); |
| 166 | + eprintln!("-------"); |
| 167 | + |
| 168 | + return Err(anyhow!("Failed to execute command: {command:?}")).with_context(|| { |
| 169 | + anyhow!( |
| 170 | + "Command exited with a non-zero exit code: {}", |
| 171 | + output.status |
| 172 | + ) |
| 173 | + }); |
| 174 | + } |
| 175 | + |
| 176 | + Ok(stdout.to_string()) |
67 | 177 | }
|
0 commit comments