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
use std::path::{Path, PathBuf};
fn main() {
let manifest_dir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap());
let verovio_src = manifest_dir.join("vendor/verovio");
if !verovio_src.join("include/vrv/toolkit.h").exists() {
panic!(
"Verovio submodule not initialized at {}.\n\
Run: git submodule update --init --recursive",
verovio_src.display()
);
}
// Verovio's src/vrv.cpp does `#include "git_commit.h"` on non-Windows non-
// CocoaPods non-SwiftPackage builds. Upstream's `tools/get_git_commit.sh`
// writes a header that appends `-<short-sha>` to `Toolkit::GetVersion()`;
// since we pin Verovio by tag, the hash adds no information for our
// users — leaving GIT_COMMIT empty makes `tk.version()` return a clean
// `"6.2.1"` instead of `"6.2.1[verovio-rs]"` or `"6.2.1-8d42439dc"`.
//
// On Windows, vrv.cpp unconditionally `#define GIT_COMMIT "[undefined]"`
// without an `#ifndef` guard, so the header alone isn't enough. We define
// GIT_COMMIT before the source is compiled and guard the header's own
// define so it doesn't conflict.
let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap());
std::fs::write(
out_dir.join("git_commit.h"),
"#ifndef GIT_COMMIT\n#define GIT_COMMIT \"\"\n#endif\n",
)
.expect("write git_commit.h");
// The include layout mirrors Verovio's own cmake/CMakeLists.txt — every
// vendored dep sits in its own subdir under include/ and is referenced
// unqualified by Verovio's source.
let mut include_dirs = vec![
out_dir.clone(), // first, so our generated git_commit.h wins
verovio_src.join("include"),
verovio_src.join("include/crc"),
verovio_src.join("include/midi"),
verovio_src.join("include/hum"),
verovio_src.join("include/json"),
verovio_src.join("include/pugi"),
verovio_src.join("include/tuning-library"),
verovio_src.join("include/zip"),
verovio_src.join("include/vrv"),
verovio_src.join("libmei/dist"),
verovio_src.join("libmei/addons"),
manifest_dir.join("include"),
];
// Windows: Verovio ships POSIX shims (dirent.h, gettimeofday) under
// include/win32 — mirrors upstream cmake's `include_directories(../include/win32)`.
if cfg!(target_os = "windows") {
include_dirs.push(verovio_src.join("include/win32"));
}
// Sanitizer feature gates. These are mutually exclusive — ASan and TSan
// share runtime state in libsanitizer and can't be linked together.
let asan = std::env::var_os("CARGO_FEATURE_SANITIZE").is_some();
let tsan = std::env::var_os("CARGO_FEATURE_SANITIZE_THREAD").is_some();
assert!(
!(asan && tsan),
"verovio-sys: `sanitize` and `sanitize-thread` features are mutually exclusive"
);
let sanitizer_flags: Vec<&str> = if asan {
vec![
"-fsanitize=address",
"-fsanitize=undefined",
"-fno-omit-frame-pointer",
]
} else if tsan {
vec!["-fsanitize=thread", "-fno-omit-frame-pointer"]
} else {
vec![]
};
// Compile Verovio + libmei into a single static archive. We skip
// tools/c_wrapper.cpp deliberately: we bridge to the C++ Toolkit directly
// via cxx, and the C wrapper isn't part of our linkage path.
let mut verovio_build = cc::Build::new();
verovio_build
.cpp(true)
.std("c++20")
.warnings(false) // upstream Verovio has unused-parameter warnings; not ours to fix
.flag_if_supported("-fvisibility=hidden")
.flag_if_supported("-fvisibility-inlines-hidden");
// MSVC: enable C++ exception handling (Verovio uses try/catch).
// On Windows, vrv.cpp skips `#include "git_commit.h"` and unconditionally
// `#define GIT_COMMIT "[undefined]"`. We patch a copy to add a #ifndef
// guard, and pass /DGIT_COMMIT="" so the guard sees it already defined.
if cfg!(target_env = "msvc") {
verovio_build.flag("/EHsc");
verovio_build.define("GIT_COMMIT", r#""""#);
}
if cfg!(target_os = "windows") {
let original = verovio_src.join("src/vrv.cpp");
let patched = out_dir.join("vrv_patched.cpp");
let src = std::fs::read_to_string(&original).expect("read vrv.cpp");
let src = src.replace(
"#define GIT_COMMIT \"[undefined]\"",
"#ifndef GIT_COMMIT\n#define GIT_COMMIT \"[undefined]\"\n#endif",
);
std::fs::write(&patched, src).expect("write patched vrv.cpp");
verovio_build.file(&patched);
println!("cargo:rerun-if-changed={}", original.display());
}
for f in &sanitizer_flags {
verovio_build.flag(f);
}
for dir in &include_dirs {
verovio_build.include(dir);
}
for sub in [
"src",
"src/crc",
"src/hum",
"src/midi",
"src/pugi",
"src/json",
"libmei/dist",
"libmei/addons",
] {
add_cpp_sources(&mut verovio_build, &verovio_src.join(sub));
}
verovio_build.compile("verovio"); // produces libverovio.a, emits rustc-link-lib=static=verovio
// Compile the cxx bridge + our shim against the same include layout.
let mut bridge_build = cxx_build::bridge("src/lib.rs");
bridge_build
.file("src/vrv_bridge.cpp")
.std("c++20")
.warnings(false);
for dir in &include_dirs {
bridge_build.include(dir);
}
for f in &sanitizer_flags {
bridge_build.flag(f);
}
bridge_build.compile("verovio_bridge");
// Emit sanitizer link args for verovio-sys's own benchmarks/binaries
// /tests. The verovio crate's build.rs re-emits the same flags for the
// downstream targets (`cargo:rustc-link-arg` is per-package).
for f in &sanitizer_flags {
// -fno-omit-frame-pointer is a compile-only flag; skip at link.
if f.starts_with("-fsanitize=") {
println!("cargo:rustc-link-arg={f}");
}
}
// Publish the active sanitizer mode to downstream build scripts via
// the `links=verovio` metadata channel (DEP_VEROVIO_SANITIZER).
let sanitizer_mode = if asan {
"address"
} else if tsan {
"thread"
} else {
"none"
};
println!("cargo:sanitizer={sanitizer_mode}");
// C++ runtime. MSVC links the CRT automatically; mingw uses stdc++.
if cfg!(target_os = "macos") {
println!("cargo:rustc-link-lib=dylib=c++");
} else if cfg!(target_os = "windows") {
if cfg!(target_env = "gnu") {
println!("cargo:rustc-link-lib=dylib=stdc++");
}
// MSVC: runtime linked automatically by the compiler driver.
} else {
println!("cargo:rustc-link-lib=dylib=stdc++");
emit_libstdcxx_rpath_for_own_tests();
}
println!("cargo:rerun-if-changed=src/lib.rs");
println!("cargo:rerun-if-changed=src/vrv_bridge.cpp");
println!("cargo:rerun-if-changed=include/vrv_bridge.h");
println!("cargo:rerun-if-changed=build.rs");
}
/// On NixOS the C++ runtime sits under `/nix/store/<hash>-gcc-*-lib/lib` with
/// no FHS path. Discover it via the host compiler and emit an rpath that
/// applies to this crate's own benchmarks/binaries/examples/tests.
///
/// `cargo:rustc-link-arg` propagates only to targets in the same package as
/// the emitting build.rs — the `verovio` safe-wrapper crate emits the same
/// rpath for its own targets in its own build.rs.
fn emit_libstdcxx_rpath_for_own_tests() {
let Ok(out) = std::process::Command::new("c++")
.arg("-print-file-name=libstdc++.so.6")
.output()
else {
return;
};
let Ok(path) = std::str::from_utf8(&out.stdout) else {
return;
};
let path = Path::new(path.trim());
if !path.is_absolute() {
return;
}
if let Some(libdir) = path.parent() {
println!("cargo:rustc-link-arg=-Wl,-rpath,{}", libdir.display());
}
}
fn add_cpp_sources(build: &mut cc::Build, dir: &Path) {
for entry in std::fs::read_dir(dir)
.unwrap_or_else(|e| panic!("read_dir({}) failed: {}", dir.display(), e))
{
let path = entry.expect("dir entry").path();
let ext = path.extension().and_then(|s| s.to_str());
// jsonxx upstream uses .cc; everything else is .cpp.
if matches!(ext, Some("cpp") | Some("cc")) {
// On Windows, vrv.cpp is compiled from a patched copy in OUT_DIR.
if cfg!(target_os = "windows") && path.file_name().is_some_and(|n| n == "vrv.cpp") {
continue;
}
build.file(&path);
println!("cargo:rerun-if-changed={}", path.display());
}
}
}