Skip to content

Commit 6d3e3ff

Browse files
committed
Delete build-wireguard-go.sh and put libwg.a in OUT_DIR
1 parent faf1461 commit 6d3e3ff

File tree

4 files changed

+159
-173
lines changed

4 files changed

+159
-173
lines changed

Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

wireguard-go-rs/Cargo.toml

+4-1
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,16 @@ description = "Rust bindings to wireguard-go with DAITA support"
44
edition = "2021"
55
license.workspace = true
66

7+
[build-dependencies]
8+
anyhow = "1.0"
9+
710
[target.'cfg(unix)'.dependencies]
811
thiserror.workspace = true
912
log.workspace = true
1013
zeroize = "1.8.1"
1114

1215
[target.'cfg(any(target_os = "linux", target_os = "macos"))'.dependencies]
13-
# The app does not depend on maybenot-ffi itself, but adds it as a dependency to expose FFI symbols to wireguard-go.
16+
# The app does not depend on maybenot-ffi itself, but adds it as a dependency to expose FFI symbols to wireguard-go.
1417
# This is done, instead of using the makefile in wireguard-go to build maybenot-ffi into its archive, to prevent
1518
# name clashes induced by link-time optimization.
1619
# NOTE: the version of maybenot-ffi below must be the same as the version checked into the wireguard-go submodule

wireguard-go-rs/build-wireguard-go.sh

-128
This file was deleted.

wireguard-go-rs/build.rs

+154-44
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,177 @@
1-
use core::{panic, str};
2-
use std::{env, path::PathBuf};
1+
use std::{borrow::BorrowMut, env, path::PathBuf, process::Command, str};
32

4-
fn main() {
5-
let out_dir = env::var("OUT_DIR").expect("Missing OUT_DIR");
3+
use anyhow::{anyhow, bail, Context};
64

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
89
println!("cargo::rustc-check-cfg=cfg(daita)");
910

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");
1313

1414
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()?,
2418
// building wireguard-go-rs for windows is not implemented
25-
_ => return,
19+
_ => {}
2620
}
2721

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'")?;
3787

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+
}
43104
}
44-
declare_libs_dir("../build/lib");
45105

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");
47111

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"#);
52115
}
116+
117+
Ok(())
53118
}
54119

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);
59129
println!("cargo::rustc-link-search={}", lib_dir.display());
130+
println!("cargo::rustc-link-lib=dylib=wg");
131+
132+
Ok(())
60133
}
61134

62135
/// Get the directory containing `Cargo.toml`
63-
fn manifest_dir() -> PathBuf {
136+
fn manifest_dir() -> anyhow::Result<PathBuf> {
64137
env::var("CARGO_MANIFEST_DIR")
65138
.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())
67177
}

0 commit comments

Comments
 (0)