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
use std::env;
use std::path::PathBuf;
#[cfg(feature = "download-lib")]
#[path = "build-utils/downloader.rs"]
mod downloader;
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 {
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() {
if cfg!(target_os = "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");
} else if cfg!(target_os = "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");
} else if cfg!(target_os = "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()
}
fn generate_bindings() {
let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
let header_path = manifest_dir.join("include").join("aic.h");
// 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");
if env::var("CARGO_FEATURE_RUNTIME_LINKING").is_ok() {
// 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.
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
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());
}