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
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
use std::env;
use std::path::PathBuf;
#[cfg(feature = "download-lib")]
#[path = "build-utils/downloader.rs"]
mod downloader;
#[path = "build-utils/runtime_linking.rs"]
mod runtime_linking;
fn main() {
// Rerun the build script if the header file changes
println!("cargo:rerun-if-changed=include/aic.h");
// Rerun the build script if the AIC_LIB_PATH environment variable changes
println!("cargo:rerun-if-env-changed=AIC_LIB_PATH");
// Bindings need to be generated before early return on docs.rs
generate_bindings();
if env::var("DOCS_RS").is_ok() {
// On docs.rs we don't need to link and we don't have network,
// so we couldn't download anything if we wanted to
return;
}
let runtime_linking = env::var("CARGO_FEATURE_RUNTIME_LINKING").is_ok();
let dynamic_linking = env::var("CARGO_FEATURE_DYNAMIC_LINKING").is_ok();
// `dynamic-linking` and `runtime-linking` select alternative linking strategies. Cargo
// features are additive, so enabling both (e.g. via `--all-features`) is possible; in that
// case runtime linking wins. Warn so the choice is not silently surprising.
if runtime_linking && dynamic_linking {
println!(
"cargo:warning=Both `dynamic-linking` and `runtime-linking` are enabled; using \
runtime linking. These features select alternative linking strategies and are not \
meant to be combined."
);
}
if runtime_linking {
// Runtime linking resolves symbols from a user-provided dynamic library path,
// so there is intentionally no build-time link step. If `dynamic-linking`
// is also enabled through additive Cargo features, runtime linking wins.
return;
}
let lib_path = if let Ok(path) = env::var("AIC_LIB_PATH") {
PathBuf::from(path)
} else {
#[cfg(feature = "download-lib")]
{
let downloaded_path = download_lib();
downloaded_path.join("lib")
}
#[cfg(not(feature = "download-lib"))]
{
panic!(
"Enable feature `download-lib` or use a local library by setting the environment variable `AIC_LIB_PATH`"
);
}
};
let target_env = env::var("CARGO_CFG_TARGET_ENV").unwrap_or_default();
// Link with the curated library
println!("cargo:rustc-link-search=native={}", lib_path.display());
if dynamic_linking && target_env == "msvc" {
// The MSVC SDK package ships the DLL import library as `aic.dll.lib` next to the static
// `aic.lib`. A plain `dylib=aic` resolves to `aic.lib` (the static archive), which would
// be linked statically and miss its system dependencies. Name the import library
// verbatim so the linker binds against `aic.dll` instead.
println!("cargo:rustc-link-lib=dylib:+verbatim=aic.dll.lib");
} else {
// Every other target uses the standard `-laic` naming, so no verbatim trick is needed:
// * static: `libaic.a` (Unix and the Windows `gnullvm` target).
// * dynamic on Unix: `libaic.so` / `libaic.dylib`.
// * dynamic on `gnullvm` (target_env == "gnu"): the MinGW/LLD driver searches the GNU
// import library `libaic.dll.a` before the static `libaic.a`, so `dylib=aic` binds
// against `aic.dll` correctly. This is the gnu equivalent of the MSVC case above,
// which only needs special handling because its import library (`aic.dll.lib`) and
// static archive (`aic.lib`) collide under the plain `dylib=aic` name.
let link_kind = if dynamic_linking { "dylib" } else { "static" };
println!("cargo:rustc-link-lib={link_kind}=aic");
}
// The platform system libraries below are transitive dependencies of the *static* AIC
// library and must be linked into the final binary. A shared `libaic` already records its
// own dependencies, so when linking dynamically we leave them out.
if !dynamic_linking {
add_platform_specific_libs();
}
}
fn add_platform_specific_libs() {
// Select libraries by the *target* OS via `CARGO_CFG_TARGET_OS`, not `cfg!(target_os = ...)`.
// A build script is compiled for the host, so `cfg!` reports the host platform and would link
// the wrong system libraries when cross-compiling (e.g. host Linux -> target windows-gnullvm
// would pull in `dl`/`rt` and miss the Windows libs). For a native build the two agree.
let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap_or_default();
match target_os.as_str() {
"macos" => {
// macOS requires CoreFoundation framework for time zone operations
// This is needed by chrono and other crates that interact with system time
println!("cargo:rustc-link-lib=framework=CoreFoundation");
// Security framework might also be needed for some operations
println!("cargo:rustc-link-lib=framework=Security");
}
"windows" => {
// Windows system libraries that might be needed
println!("cargo:rustc-link-lib=advapi32");
println!("cargo:rustc-link-lib=bcrypt");
println!("cargo:rustc-link-lib=kernel32");
println!("cargo:rustc-link-lib=ws2_32");
println!("cargo:rustc-link-lib=oleaut32");
println!("cargo:rustc-link-lib=crypt32");
}
"linux" => {
// Linux system libraries
println!("cargo:rustc-link-lib=pthread");
println!("cargo:rustc-link-lib=dl");
println!("cargo:rustc-link-lib=rt");
}
_ => {}
}
}
#[cfg(feature = "download-lib")]
fn download_lib() -> PathBuf {
use downloader::Downloader;
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
let downloader = Downloader::new(&out_dir);
downloader.download()
}
/// Maps a Cargo target triple to the triple clang should use when parsing the C header.
///
/// Returns `Some` only for the Rust-only `*-pc-windows-gnullvm` targets, which clang rejects
/// (it parses the `gnullvm` environment as an invalid OS version). Their ABI-compatible clang
/// triple is `*-pc-windows-gnu`. All other targets are valid clang triples, so this returns
/// `None` and bindgen keeps using the `TARGET` value unchanged.
fn clang_target_for(target: &str) -> Option<String> {
target
.strip_suffix("-pc-windows-gnullvm")
.map(|arch| format!("{arch}-pc-windows-gnu"))
}
fn generate_bindings() {
let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
let header_path = manifest_dir.join("include").join("aic.h");
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
// Generate bindings using bindgen
let mut builder = bindgen::Builder::default()
// The input header we would like to generate bindings for.
.header(header_path.to_str().unwrap())
// Tell cargo to invalidate the built crate whenever any of the
// included header files changed.
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
// Generate constified enums to avoid name repetition
.constified_enum_module("AicErrorCode")
.constified_enum_module("AicProcessorParameter")
.constified_enum_module("AicVadParameter");
// bindgen feeds the Cargo `TARGET` triple to libclang, but clang does not understand the
// Rust-only `gnullvm` environment: it reads `windows-gnullvm` as the `windows-gnu` triple
// with an invalid OS version (`llvm`) and aborts before it can parse the header. Map such
// triples to their clang equivalent (`*-pc-windows-gnu`, ABI-compatible) and pass it
// explicitly. bindgen detects the user-provided `--target=` and does not add its own.
let target = env::var("TARGET").unwrap_or_default();
if let Some(clang_target) = clang_target_for(&target) {
builder = builder.clang_arg(format!("--target={clang_target}"));
}
if env::var("CARGO_FEATURE_RUNTIME_LINKING").is_ok() {
let runtime_bindings = builder
.clone()
.generate()
.expect("Unable to generate runtime-linking symbols");
runtime_linking::generate(&runtime_bindings, &out_path.join("runtime_symbols.rs"));
// The runtime-linking module provides Rust functions with these names
// that dispatch through libloading. Keep types/constants from bindgen,
// but omit build-linked extern function declarations to avoid conflicts.
builder = builder.blocklist_function("aic_.*");
}
let bindings = builder
// Finish the builder and generate the bindings.
.generate()
// Unwrap the Result and panic on failure.
.expect("Unable to generate bindings");
// Write the bindings to the $OUT_DIR/bindings.rs file.
bindings
.write_to_file(out_path.join("bindings.rs"))
.expect("Couldn't write bindings!");
// Tell cargo to rerun the build script if the library or header changes.
println!("cargo:rerun-if-changed={}", header_path.display());
}