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
fn main() {
#[cfg(feature = "coolprop-static")]
coolprop_static::build();
#[cfg(feature = "coolprop-dylib")]
coolprop_dylib::link();
}
#[cfg(feature = "coolprop-static")]
mod coolprop_static {
use std::{
env,
path::{Path, PathBuf},
process::Command,
};
pub fn build() {
let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
// Allow COOLPROP_SOURCE_DIR to override the default vendor location.
// This lets crates.io consumers point at a local CoolProp checkout
// instead of requiring a full git clone with submodules.
println!("cargo:rerun-if-env-changed=COOLPROP_SOURCE_DIR");
let source_dir = env::var("COOLPROP_SOURCE_DIR")
.map(PathBuf::from)
.unwrap_or_else(|_| manifest_dir.join("vendor/CoolProp"));
let target_arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap();
// Rebuild if the build script or the CoolProp header changes.
// The submodule is pinned to a specific commit, so source changes
// only happen through a deliberate submodule update.
// There is no need to watch individual source files.
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-changed=vendor/CoolProp/include/CoolPropLib.h");
if target_arch == "wasm32" {
build_wasm(&source_dir, &out_dir);
} else {
build_native(&source_dir, &out_dir);
}
}
/// Build `CoolProp` as a static library for native targets using the `cmake` crate.
fn build_native(source_dir: &Path, out_dir: &Path) {
// CoolProp's CMakeLists.txt forces CMAKE_INSTALL_PREFIX to its own
// default, so we cannot rely on cmake's install step to put the
// library where cargo expects it.
// Set CMAKE_ARCHIVE_OUTPUT_DIRECTORY to a fixed location instead.
let lib_dir = out_dir.join("lib");
// -DCOOLPROP_LIB tells CoolPropLib.h to mark the API functions with
// `extern "C"`, giving us C linkage in the static library.
// The shared-library cmake target normally adds this flag;
// the static-library target does not, so we supply it manually.
cmake::Config::new(source_dir)
.define("COOLPROP_STATIC_LIBRARY", "ON")
.define("COOLPROP_SHARED_LIBRARY", "OFF")
.define("BUILD_TESTING", "OFF")
.define("CMAKE_BUILD_TYPE", "Release")
.cxxflag("-DCOOLPROP_LIB")
.define(
"CMAKE_ARCHIVE_OUTPUT_DIRECTORY",
lib_dir.to_str().expect("lib_dir path is valid UTF-8"),
)
// Redirect cmake's install step into OUT_DIR so it doesn't write
// install_root/ into the CoolProp source tree (see issue #56).
.define(
"CMAKE_INSTALL_PREFIX",
out_dir
.join("coolprop-install")
.to_str()
.expect("out_dir path is valid UTF-8"),
)
.build();
println!("cargo:rustc-link-search=native={}", lib_dir.display());
println!("cargo:rustc-link-lib=static=CoolProp");
// Link the C++ standard library — CoolProp's internals are in C++.
let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap();
match target_os.as_str() {
"macos" => println!("cargo:rustc-link-lib=c++"),
_ => println!("cargo:rustc-link-lib=stdc++"),
}
}
/// Build `CoolProp` as a static library for WASM via Emscripten.
///
/// The `cmake` crate cannot be used here — it injects
/// `--target=wasm32-unknown-unknown` and `-fno-exceptions`, both
/// incompatible with Emscripten.
/// We invoke cmake directly via `std::process::Command`.
fn build_wasm(source_dir: &Path, out_dir: &Path) {
let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap();
assert!(
target_os == "emscripten",
"CoolProp requires Emscripten for WASM builds. \
Use target `wasm32-unknown-emscripten`, \
not `wasm32-unknown-unknown`."
);
let emscripten_root = discover_emscripten_root();
let toolchain_file = emscripten_root.join("cmake/Modules/Platform/Emscripten.cmake");
assert!(
toolchain_file.exists(),
"Emscripten toolchain file not found at {}",
toolchain_file.display()
);
let build_dir = out_dir.join("coolprop-wasm-build");
let lib_dir = out_dir.join("lib");
std::fs::create_dir_all(&build_dir).expect("failed to create WASM build directory");
// CoolProp CXX flags:
// - `-fwasm-exceptions`: Align with Rust's emscripten runtime which
// uses the WASM EH proposal.
// Without this, CoolProp uses legacy sjlj EH and exceptions escape
// past `catch(...)`.
// - `-DCOOLPROP_NO_INCBIN`: Preprocessor define that disables the
// `incbin` assembly directive — WASM assembler rejects GAS syntax.
// - `-DCOOLPROP_LIB`: C linkage for the API functions (same as native).
let cxx_flags = "-fwasm-exceptions -DCOOLPROP_NO_INCBIN -DCOOLPROP_LIB";
let c_flags = "-DCOOLPROP_NO_INCBIN";
// Configure.
let configure_status = Command::new("cmake")
.current_dir(&build_dir)
.args([
"-S",
source_dir.to_str().expect("source_dir path is valid UTF-8"),
"-B",
".",
&format!(
"-DCMAKE_TOOLCHAIN_FILE={}",
toolchain_file
.to_str()
.expect("toolchain path is valid UTF-8")
),
"-DCOOLPROP_STATIC_LIBRARY=ON",
"-DCOOLPROP_SHARED_LIBRARY=OFF",
"-DBUILD_TESTING=OFF",
"-DCMAKE_BUILD_TYPE=Release",
&format!("-DCMAKE_CXX_FLAGS={cxx_flags}"),
&format!("-DCMAKE_C_FLAGS={c_flags}"),
&format!(
"-DCMAKE_ARCHIVE_OUTPUT_DIRECTORY={}",
lib_dir.to_str().expect("lib_dir path is valid UTF-8")
),
// Redirect cmake's install step into OUT_DIR so it doesn't write
// install_root/ into the CoolProp source tree (see issue #56).
&format!(
"-DCMAKE_INSTALL_PREFIX={}",
out_dir
.join("coolprop-install")
.to_str()
.expect("out_dir path is valid UTF-8")
),
])
.status()
.expect("failed to run cmake configure for WASM");
assert!(
configure_status.success(),
"cmake configure failed for WASM CoolProp build"
);
// Build.
let build_status = Command::new("cmake")
.current_dir(&build_dir)
.args(["--build", ".", "--config", "Release"])
.status()
.expect("failed to run cmake build for WASM");
assert!(
build_status.success(),
"cmake build failed for WASM CoolProp build"
);
println!("cargo:rustc-link-search=native={}", lib_dir.display());
println!("cargo:rustc-link-lib=static=CoolProp");
// No explicit C++ stdlib link needed — Emscripten's toolchain links
// its own C++ runtime automatically during final linking.
// CoolProp decompresses ~25 MB of fluid data at init, exceeding the
// default 16 MB WASM heap.
println!("cargo:rustc-link-arg=-sALLOW_MEMORY_GROWTH=1");
}
/// Discover the Emscripten root directory via `em-config`.
fn discover_emscripten_root() -> PathBuf {
let output = Command::new("em-config")
.arg("EMSCRIPTEN_ROOT")
.output()
.expect(
"failed to run `em-config`. \
Is the Emscripten SDK installed and `em-config` on PATH?",
);
assert!(
output.status.success(),
"`em-config EMSCRIPTEN_ROOT` failed: {}",
String::from_utf8_lossy(&output.stderr)
);
let root = String::from_utf8(output.stdout).expect("`em-config` output is not valid UTF-8");
PathBuf::from(root.trim())
}
}
#[cfg(feature = "coolprop-dylib")]
mod coolprop_dylib {
/// Tell the linker to link against the prebuilt `CoolProp` shared library.
///
/// The library search path is already provided by the platform-specific
/// `coolprop-sys-*` dependency crate. We only need to emit the link
/// directive so the symbols from our `extern "C"` declarations resolve.
pub fn link() {
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rustc-link-lib=dylib=CoolProp");
}
}