-
Notifications
You must be signed in to change notification settings - Fork 98
/
build.rs
258 lines (226 loc) · 9.07 KB
/
build.rs
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
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
use std::env;
use std::path::Path;
/// Tells whether we're building for Windows. This is more suitable than a plain
/// `cfg!(windows)`, since the latter does not properly handle cross-compilation
///
/// Note that there is no way to know at compile-time which system we'll be
/// targetting, and this test must be made at run-time (of the build script) See
/// https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts
#[allow(dead_code)]
fn win_target() -> bool {
std::env::var("CARGO_CFG_WINDOWS").is_ok()
}
/// Tells whether a given compiler will be used `compiler_name` is compared to
/// the content of `CARGO_CFG_TARGET_ENV` (and is always lowercase)
///
/// See [`win_target`]
#[allow(dead_code)]
fn is_compiler(compiler_name: &str) -> bool {
std::env::var("CARGO_CFG_TARGET_ENV").map_or(false, |v| v == compiler_name)
}
fn main() {
let out_dir = env::var("OUT_DIR").unwrap();
let out_path = Path::new(&out_dir).join("bindgen.rs");
#[cfg(feature = "bundled")]
{
build_bundled::main(&out_dir, &out_path);
}
#[cfg(not(feature = "bundled"))]
{
build_linked::main(&out_dir, &out_path)
}
}
#[cfg(feature = "bundled")]
mod build_bundled {
use std::path::Path;
use crate::win_target;
pub fn main(out_dir: &str, out_path: &Path) {
let lib_name = super::lib_name();
if !cfg!(feature = "bundled") {
// This is just a sanity check, the top level `main` should ensure this.
panic!("This module should not be used: bundled feature has not been enabled");
}
#[cfg(feature = "buildtime_bindgen")]
{
use super::{bindings, HeaderLocation};
let header = HeaderLocation::FromPath(format!("{}/duckdb.h", lib_name));
bindings::write_to_out_dir(header, out_path);
}
#[cfg(not(feature = "buildtime_bindgen"))]
{
use std::fs;
fs::copy(format!("{}/bindgen_bundled_version.rs", lib_name), out_path)
.expect("Could not copy bindings to output directory");
}
println!("cargo:rerun-if-changed={}/duckdb.hpp", lib_name);
println!("cargo:rerun-if-changed={}/duckdb.cpp", lib_name);
let mut cfg = cc::Build::new();
cfg.file(format!("{}/duckdb.cpp", lib_name))
.cpp(true)
.flag_if_supported("-std=c++11")
.flag_if_supported("-stdlib=libc++")
.flag_if_supported("-stdlib=libstdc++")
.flag_if_supported("/bigobj")
.warnings(false);
if win_target() {
cfg.define("DUCKDB_BUILD_LIBRARY", None);
}
cfg.compile(lib_name);
println!("cargo:lib_dir={}", out_dir);
}
}
fn env_prefix() -> &'static str {
"DUCKDB"
}
fn lib_name() -> &'static str {
"duckdb"
}
pub enum HeaderLocation {
FromEnvironment,
Wrapper,
FromPath(String),
}
impl From<HeaderLocation> for String {
fn from(header: HeaderLocation) -> String {
match header {
HeaderLocation::FromEnvironment => {
let prefix = env_prefix();
let mut header = env::var(format!("{}_INCLUDE_DIR", prefix))
.unwrap_or_else(|_| env::var(format!("{}_LIB_DIR", env_prefix())).unwrap());
header.push_str("/duckdb.h");
header
}
HeaderLocation::Wrapper => "wrapper.h".into(),
HeaderLocation::FromPath(path) => path,
}
}
}
#[cfg(not(feature = "bundled"))]
mod build_linked {
#[cfg(feature = "vcpkg")]
extern crate vcpkg;
#[cfg(feature = "buildtime_bindgen")]
use super::bindings;
use super::{env_prefix, is_compiler, lib_name, win_target, HeaderLocation};
use std::env;
use std::path::Path;
pub fn main(_out_dir: &str, out_path: &Path) {
// We need this to config the LD_LIBRARY_PATH
#[allow(unused_variables)]
let header = find_duckdb();
if !cfg!(feature = "buildtime_bindgen") {
std::fs::copy(format!("{}/bindgen_bundled_version.rs", lib_name()), out_path)
.expect("Could not copy bindings to output directory");
} else {
#[cfg(feature = "buildtime_bindgen")]
{
bindings::write_to_out_dir(header, out_path);
}
}
}
fn find_link_mode() -> &'static str {
// If the user specifies DUCKDB_STATIC, do static
// linking, unless it's explicitly set to 0.
match &env::var(format!("{}_STATIC", env_prefix())) {
Ok(v) if v != "0" => "static",
_ => "dylib",
}
}
// Prints the necessary cargo link commands and returns the path to the header.
fn find_duckdb() -> HeaderLocation {
let link_lib = lib_name();
println!("cargo:rerun-if-env-changed={}_INCLUDE_DIR", env_prefix());
println!("cargo:rerun-if-env-changed={}_LIB_DIR", env_prefix());
println!("cargo:rerun-if-env-changed={}_STATIC", env_prefix());
if cfg!(feature = "vcpkg") && is_compiler("msvc") {
println!("cargo:rerun-if-env-changed=VCPKGRS_DYNAMIC");
}
// dependents can access `DEP_DUCKDB_LINK_TARGET` (`duckdb` being the
// `links=` value in our Cargo.toml) to get this value. This might be
// useful if you need to ensure whatever crypto library sqlcipher relies
// on is available, for example.
println!("cargo:link-target={}", link_lib);
if win_target() && cfg!(feature = "winduckdb") {
println!("cargo:rustc-link-lib=dylib={}", link_lib);
return HeaderLocation::Wrapper;
}
// Allow users to specify where to find DuckDB.
if let Ok(dir) = env::var(format!("{}_LIB_DIR", env_prefix())) {
println!("cargo:rustc-env=LD_LIBRARY_PATH={}", dir);
// Try to use pkg-config to determine link commands
let pkgconfig_path = Path::new(&dir).join("pkgconfig");
env::set_var("PKG_CONFIG_PATH", pkgconfig_path);
if pkg_config::Config::new().probe(link_lib).is_err() {
// Otherwise just emit the bare minimum link commands.
println!("cargo:rustc-link-lib={}={}", find_link_mode(), link_lib);
println!("cargo:rustc-link-search={}", dir);
}
return HeaderLocation::FromEnvironment;
}
if let Some(header) = try_vcpkg() {
return header;
}
// See if pkg-config can do everything for us.
match pkg_config::Config::new().print_system_libs(false).probe(link_lib) {
Ok(mut lib) => {
if let Some(mut header) = lib.include_paths.pop() {
header.push("duckdb.h");
HeaderLocation::FromPath(header.to_string_lossy().into())
} else {
HeaderLocation::Wrapper
}
}
Err(_) => {
// No env var set and pkg-config couldn't help; just output the link-lib
// request and hope that the library exists on the system paths. We used to
// output /usr/lib explicitly, but that can introduce other linking problems;
// see https://github.com/rusqlite/rusqlite/issues/207.
println!("cargo:rustc-link-lib={}={}", find_link_mode(), link_lib);
HeaderLocation::Wrapper
}
}
}
fn try_vcpkg() -> Option<HeaderLocation> {
if cfg!(feature = "vcpkg") && is_compiler("msvc") {
// See if vcpkg can find it.
if let Ok(mut lib) = vcpkg::Config::new().probe(lib_name()) {
if let Some(mut header) = lib.include_paths.pop() {
header.push("duckdb.h");
return Some(HeaderLocation::FromPath(header.to_string_lossy().into()));
}
}
None
} else {
None
}
}
}
#[cfg(feature = "buildtime_bindgen")]
mod bindings {
use super::HeaderLocation;
use std::fs::OpenOptions;
use std::io::Write;
use std::path::Path;
pub fn write_to_out_dir(header: HeaderLocation, out_path: &Path) {
let header: String = header.into();
let mut output = Vec::new();
bindgen::builder()
.trust_clang_mangling(false)
.header(header.clone())
.parse_callbacks(Box::new(bindgen::CargoCallbacks))
.rustfmt_bindings(true)
.generate()
.unwrap_or_else(|_| panic!("could not run bindgen on header {}", header))
.write(Box::new(&mut output))
.expect("could not write output of bindgen");
let output = String::from_utf8(output).expect("bindgen output was not UTF-8?!");
let mut file = OpenOptions::new()
.write(true)
.truncate(true)
.create(true)
.open(out_path)
.unwrap_or_else(|_| panic!("Could not write to {:?}", out_path));
file.write_all(output.as_bytes())
.unwrap_or_else(|_| panic!("Could not write to {:?}", out_path));
}
}