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