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
//! Build script for blazediff-shared
//! Compiles libspng with SIMD optimizations and libjpeg-turbo via cmake
use std::env;
use std::path::PathBuf;
fn main() {
// Skip vendored C compilation when the `codecs` feature is off (e.g. wasm
// builds). Only the PNG/JPEG decoders need C; `Image`, `ImageError` and
// `ImageFormat` are pure Rust.
if env::var("CARGO_FEATURE_CODECS").is_err() {
return;
}
let libspng_dir = "vendor/libspng/spng";
let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap();
let target_env = env::var("CARGO_CFG_TARGET_ENV").unwrap_or_default();
let host = env::var("HOST").unwrap_or_default();
// The msvc link line carries the *static* CRT (libcmt.lib + libucrt.lib),
// so the vendored C has to be compiled against it too. cc's default is
// `-MD`, which emits `__declspec(dllimport)` references to malloc/realloc
// that only `ucrt.lib` satisfies; while these objects lived in blazediff's
// own rlib that mismatch happened to resolve, but from a dependency rlib
// lld-link loads them lazily and the link dies on
// `undefined symbol: __declspec(dllimport) realloc`. Forcing `/MT` here
// matches what is actually on the line and keeps spng's allocations on the
// same heap as the `libc::free` in png_io.rs.
let static_crt = target_env == "msvc";
// Use the vendored miniz instead of system zlib whenever zlib headers
// are not reliably available:
// - Windows targets (no system zlib)
// - Cross-compiling to Linux from a non-Linux host (e.g. maturin --zig
// from macOS - zig ships glibc but not zlib headers)
// - Explicit override via BLAZEDIFF_FORCE_MINIZ=1
let cross_to_linux = target_os == "linux" && !host.contains("linux");
let force_miniz = env::var("BLAZEDIFF_FORCE_MINIZ").is_ok();
let use_miniz = target_os == "windows" || cross_to_linux || force_miniz;
let miniz_include = if use_miniz {
Some(build_miniz(static_crt))
} else {
println!("cargo:rustc-link-lib=z");
None
};
// Build libspng
build_libspng(
libspng_dir,
&target_os,
miniz_include.as_ref(),
use_miniz,
static_crt,
);
// Build libjpeg-turbo from vendored source
build_libjpeg_turbo(static_crt);
}
fn build_miniz(static_crt: bool) -> PathBuf {
let src_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap())
.join("vendor")
.join("miniz");
let mut config = cmake::Config::new(&src_dir);
config
.define("BUILD_SHARED_LIBS", "OFF")
.define("CMAKE_POSITION_INDEPENDENT_CODE", "ON");
if static_crt {
config.define("CMAKE_MSVC_RUNTIME_LIBRARY", "MultiThreaded");
}
let dst = config.build();
println!("cargo:rustc-link-search=native={}/lib", dst.display());
// NOTE: Don't link miniz here - it must be linked AFTER spng (which depends on it)
// The link directive is in build_libspng() for correct ordering
println!("cargo:rerun-if-changed=vendor/miniz/CMakeLists.txt");
// Return include directory for spng (miniz installs to include/miniz/)
dst.join("include").join("miniz")
}
fn build_libspng(
libspng_dir: &str,
_target_os: &str,
miniz_include: Option<&PathBuf>,
use_miniz: bool,
static_crt: bool,
) {
let mut build = cc::Build::new();
build
.file(format!("{}/spng.c", libspng_dir))
.include(libspng_dir)
.define("SPNG_STATIC", None)
.opt_level(3);
if static_crt {
build.static_crt(true);
}
if use_miniz {
if let Some(include_dir) = miniz_include {
build.include(include_dir);
}
build.define("SPNG_USE_MINIZ", None);
}
// MSVC vs GCC/Clang flags
if env::var("CARGO_CFG_TARGET_ENV").unwrap_or_default() == "msvc" {
// MSVC doesn't support -std=c99
} else {
build.flag("-std=c99");
}
// libspng auto-detects SIMD (ARM NEON, x86 SSE) based on target architecture
build.compile("spng");
// Link miniz AFTER spng (spng depends on miniz, linker needs correct order)
if use_miniz {
println!("cargo:rustc-link-lib=static=miniz");
}
println!("cargo:rerun-if-changed=vendor/libspng/spng/spng.c");
println!("cargo:rerun-if-changed=vendor/libspng/spng/spng.h");
}
fn build_libjpeg_turbo(static_crt: bool) {
let src_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap())
.join("vendor")
.join("libjpeg-turbo");
// Use cmake crate to build libjpeg-turbo
let mut config = cmake::Config::new(&src_dir);
config
.define("ENABLE_SHARED", "OFF")
.define("ENABLE_STATIC", "ON")
.define("WITH_TURBOJPEG", "ON")
.define("WITH_JAVA", "OFF")
.define("WITH_JPEG7", "OFF")
.define("WITH_JPEG8", "OFF")
// Defaults to ON, and `cmake --build` builds every target, so we were
// compiling libjpeg-turbo's regression suite into every release build.
// Its Catch2 runner is also the only C++ in this tree — which made an
// MSVC-STL/clang version mismatch fail the Windows cross-build on a
// test binary we never run.
.define("WITH_TESTS", "OFF")
.define("CMAKE_POSITION_INDEPENDENT_CODE", "ON");
if static_crt {
config.define("CMAKE_MSVC_RUNTIME_LIBRARY", "MultiThreaded");
}
let dst = config.build();
// Link the static library
println!("cargo:rustc-link-search=native={}/lib", dst.display());
// Library naming differs by platform:
// - Unix: libturbojpeg.a, libjpeg.a
// - Windows: turbojpeg-static.lib, jpeg-static.lib
let lib_dir = dst.join("lib");
// TurboJPEG library
if lib_dir.join("libturbojpeg.a").exists() {
println!("cargo:rustc-link-lib=static=turbojpeg");
} else if lib_dir.join("turbojpeg-static.lib").exists() {
println!("cargo:rustc-link-lib=static=turbojpeg-static");
} else {
// Fallback
println!("cargo:rustc-link-lib=static=turbojpeg");
}
// JPEG library
if lib_dir.join("libjpeg.a").exists() {
println!("cargo:rustc-link-lib=static=jpeg");
} else if lib_dir.join("jpeg-static.lib").exists() {
println!("cargo:rustc-link-lib=static=jpeg-static");
} else {
// Fallback
println!("cargo:rustc-link-lib=static=jpeg");
}
println!("cargo:rerun-if-changed=vendor/libjpeg-turbo/src/turbojpeg.c");
println!("cargo:rerun-if-changed=vendor/libjpeg-turbo/CMakeLists.txt");
}