1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
extern crate bindgen;
use std::env;
use std::path::PathBuf;
use std::process::Command;
/// Converts Rust's architecture name to Go's architecture name.
fn rust_arch_to_go_arch(rust_arch: &str) -> &str {
match rust_arch {
"x86_64" => "amd64",
"aarch64" => "arm64",
"riscv64" => "riscv64",
"loongarch64" => "loong64",
_ => panic!("Unsupported architecture: {rust_arch}"),
}
}
fn rust_os_to_go_os<'a>(rust_arch: &str, rust_os: &'a str) -> &'a str {
if rust_arch.starts_with("wasm") {
return "js";
}
match rust_os {
"macos" => "darwin",
_ => rust_os,
}
}
fn main() {
// Set the environment variables for `go` compilation, so that cross-compilation works.
let host_triple = env::var("HOST").expect("HOST not defined");
let target_triple = env::var("TARGET").expect("TARGET not defined");
let target_os = env::var("CARGO_CFG_TARGET_OS").expect("CARGO_CFG_TARGET_OS not defined");
let target_arch = env::var("CARGO_CFG_TARGET_ARCH").expect("CARGO_CFG_TARGET_ARCH not defined");
let go_arch = rust_arch_to_go_arch(&target_arch);
let go_os = rust_os_to_go_os(&target_arch, &target_os);
// If cross-compiling, suggest to the user that they will need to set their sysroot properly.
if target_triple != host_triple
&& !env::var("BINDGEN_EXTRA_CLANG_ARGS")
.unwrap_or_default()
.contains("sysroot")
{
panic!("Environment variable `BINDGEN_EXTRA_CLANG_ARGS` should contain `--sysroot=/path/to/target/sysroot` when cross-compiling");
}
let out_dir = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR not defined"));
// Directory containing uplink-c project source
let uplink_c_src = PathBuf::from("uplink-c");
// Don't compile the uplink-c libraries when building the docs for not requiring Go to be
// installed in the Docker image for building them used by docs.rs
if env::var("DOCS_RS").is_err() {
// Build uplink-c generates precompiled lib and header files in .build directory.
// We execute the command in its directory because go build, from v1.18, embeds version control
// information and the command fails if `-bildvcs=false` isn't set. We don't want to pass the
// command-line flag because then it would fail when using a previous Go version.
// Copying and building from a copy it doesn't work because it's a git submodule, hence it uses
// a relative path to the superproject unless that the destination path is under the same
// parent tree directory and with the same depth.
Command::new("make")
.arg("build")
.env("GOOS", go_os)
.env("GOARCH", go_arch)
.current_dir(&uplink_c_src)
.status()
.expect("Failed to run make command from build.rs.");
}
// Directory containing uplink-c project for building
let uplink_c_dir = out_dir.join("uplink-c");
// Copy project to OUT_DIR for building
Command::new("cp")
.args([
"-Rf",
&uplink_c_src.to_string_lossy(),
&uplink_c_dir.to_string_lossy(),
])
.status()
.expect("Failed to copy uplink-c directory.");
if env::var("DOCS_RS").is_ok() {
// Use the precompiled uplink-c libraries for building the docs by docs.rs.
Command::new("cp")
.args([
"-R",
&PathBuf::from(".docs-rs").to_string_lossy(),
&uplink_c_dir.join(".build").to_string_lossy(),
])
.status()
.expect("Failed to copy docs-rs precompiled uplink-c lib binaries");
} else {
// Delete the generated build files for avoiding `cargo publish` to complain about modifying
// things outside of the OUT_DIR.
Command::new("rm")
.args(["-r", &uplink_c_src.join(".build").to_string_lossy()])
.status()
.expect("Failed to delete uplink-c/.build directory.");
}
// Directory containing uplink-c build
let uplink_c_build = uplink_c_dir.join(".build");
// Header file with complete API interface
let uplink_c_header = uplink_c_build.join("uplink/uplink.h");
// Link (statically) to uplink-c library during build
println!("cargo:rustc-link-lib=static=uplink");
// Add uplink-c build directory to library search path
println!(
"cargo:rustc-link-search={}",
uplink_c_build.to_string_lossy()
);
// Make uplink-c interface header a dependency of the build
println!(
"cargo:rerun-if-changed={}",
uplink_c_header.to_string_lossy()
);
// Manually link to core and security libs on MacOS
//
// N.B.: `CARGO_CFG_TARGET_OS` should be read instead of `cfg(target_os = "macos")`. The latter
// detects the host OS that is building the `build.rs` script, not the target OS.
if target_os == "macos" {
println!("cargo:rustc-flags=-l framework=CoreFoundation -l framework=Security");
}
bindgen::Builder::default()
// Use 'allow lists' to avoid generating bindings for system header includes
// a lot of which isn't required and can't be handled safely anyway.
// uplink-c uses consistent naming so an allow list is much easier than a block list.
// All uplink types start with Uplink
.allowlist_type("Uplink.*")
// All edge services types start with Edge
.allowlist_type("Edge.*")
// except for uplink_const_char
.allowlist_type("uplink_const_char")
// All uplink functions start with uplink_
.allowlist_function("uplink_.*")
// All edge services functions start with edge_
.allowlist_function("edge_.*")
// Uplink error code #define's start with UPLINK_ERROR_
.allowlist_var("UPLINK_ERROR_.*")
// Edge services error code #define's start with EDGE_ERROR_
.allowlist_var("EDGE_ERROR_.*")
// This header file is the main API interface and includes all other header files that are required
// (bindgen runs c preprocessor so we don't need to include nested headers)
.header(
uplink_c_dir
.join(".build/uplink/uplink.h")
.to_string_lossy(),
)
// Also make headers included by main header dependencies of the build
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
// Generate bindings
.generate()
.expect("Error generating bindings.")
// Write bindings to file to be referenced by main build
.write_to_file(out_dir.join("bindings.rs"))
.expect("Error writing bindings to file.");
}