mysql_handler_build/lib.rs
1// Copyright (C) 2026 ren-yamanashi
2//
3// This program is free software; you can redistribute it and/or modify
4// it under the terms of the GNU General Public License, version 2.0,
5// as published by the Free Software Foundation.
6//
7// This program is designed to work with certain software (including
8// but not limited to OpenSSL) that is licensed under separate terms,
9// as designated in a particular file or component or in included license
10// documentation. The authors of this program hereby grant you an additional
11// permission to link the program and your derivative works with the
12// separately licensed software that they have either included with
13// the program or referenced in the documentation.
14//
15// This program is distributed in the hope that it will be useful,
16// but WITHOUT ANY WARRANTY; without even the implied warranty of
17// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18// GNU General Public License for more details.
19//
20// You should have received a copy of the GNU General Public License
21// along with this program; if not, see <https://www.gnu.org/licenses/>.
22
23//! Build-script helper for `mysql-handler` downstream engine cdylibs.
24//!
25//! A downstream engine crate's `build.rs` reduces to a single call to
26//! [`configure`]. The helper wires the C++ shim staticlib and the
27//! platform's C++ runtime into the link line so the resulting cdylib
28//! is loadable by mysqld.
29
30const MISSING_SHIM_WARNING: &str = "cargo:warning=mysql-handler shim staticlib not produced — the resulting cdylib will be missing C++ shim symbols and cannot be loaded by mysqld. Set MYSQL_HANDLER_FROM_SOURCE=1 or MYSQL_HANDLER_ARCHIVE=<path> to enable shim linking.";
31
32/// Configure the link line for a `mysql-handler` engine cdylib.
33///
34/// Call this from `build.rs`. The helper performs three steps:
35///
36/// 1. **Shim staticlib.** When the `mysql-handler` crate's build script
37/// produced `libha_rusty_shim.a`, it exports the output directory via
38/// `DEP_HA_RUSTY_SHIM_STATICLIB_DIR` (Cargo's `links` metadata
39/// bridge). The helper emits `rustc-link-search` and
40/// `rustc-link-lib=static=ha_rusty_shim` so the cdylib picks the
41/// staticlib up.
42/// 2. **C++ runtime.** On Apple targets the helper links `c++`; on
43/// Linux, `stdc++`. Other targets are skipped — engines targeting
44/// them must wire their own runtime.
45/// 3. **macOS dynamic lookup.** On Apple targets, the helper adds
46/// `-Wl,-undefined,dynamic_lookup` so the C++ symbols defined inside
47/// mysqld (the loading host) are resolved at plugin-load time rather
48/// than at link time.
49///
50/// When `DEP_HA_RUSTY_SHIM_STATICLIB_DIR` is unset — that is, the user
51/// has not opted into `MYSQL_HANDLER_FROM_SOURCE=1` or
52/// `MYSQL_HANDLER_ARCHIVE=<path>` for the `mysql-handler` dependency —
53/// the helper emits a `cargo:warning=` describing the env vars to set.
54/// The build is not failed; `cargo check` and IDE workflows still work,
55/// but the produced cdylib will not load into mysqld.
56///
57/// # Panics
58///
59/// Panics if Cargo did not export `TARGET`. Cargo always exports it
60/// before invoking a build script, so this branch is unreachable in
61/// practice.
62pub fn configure() {
63 let target = std::env::var("TARGET").expect("TARGET is always set by cargo");
64 let shim_dir = std::env::var("DEP_HA_RUSTY_SHIM_STATICLIB_DIR").ok();
65
66 for line in directives(&target, shim_dir.as_deref()) {
67 println!("{line}");
68 }
69}
70
71fn directives(target: &str, shim_dir: Option<&str>) -> Vec<String> {
72 let mut out = Vec::new();
73 match shim_dir {
74 Some(dir) => {
75 out.push(format!("cargo:rustc-link-search=native={dir}"));
76 out.push("cargo:rustc-link-lib=static=ha_rusty_shim".into());
77 }
78 None => out.push(MISSING_SHIM_WARNING.into()),
79 }
80
81 if target.contains("apple") {
82 out.push("cargo:rustc-link-lib=dylib=c++".into());
83 out.push("cargo:rustc-link-arg=-Wl,-undefined,dynamic_lookup".into());
84 } else if target.contains("linux") {
85 out.push("cargo:rustc-link-lib=dylib=stdc++".into());
86 }
87
88 out
89}
90
91#[cfg(test)]
92mod tests {
93 use super::{MISSING_SHIM_WARNING, directives};
94
95 #[test]
96 fn apple_with_shim_dir_emits_static_cpp_and_dynamic_lookup() {
97 let lines = directives("aarch64-apple-darwin", Some("/tmp/shim"));
98 assert!(
99 lines
100 .iter()
101 .any(|l| l == "cargo:rustc-link-search=native=/tmp/shim")
102 );
103 assert!(
104 lines
105 .iter()
106 .any(|l| l == "cargo:rustc-link-lib=static=ha_rusty_shim")
107 );
108 assert!(lines.iter().any(|l| l == "cargo:rustc-link-lib=dylib=c++"));
109 assert!(
110 lines
111 .iter()
112 .any(|l| l == "cargo:rustc-link-arg=-Wl,-undefined,dynamic_lookup")
113 );
114 assert!(!lines.iter().any(|l| l.starts_with("cargo:warning=")));
115 }
116
117 #[test]
118 fn linux_with_shim_dir_emits_static_and_stdcpp() {
119 let lines = directives("x86_64-unknown-linux-gnu", Some("/var/cache/shim"));
120 assert!(
121 lines
122 .iter()
123 .any(|l| l == "cargo:rustc-link-search=native=/var/cache/shim")
124 );
125 assert!(
126 lines
127 .iter()
128 .any(|l| l == "cargo:rustc-link-lib=static=ha_rusty_shim")
129 );
130 assert!(
131 lines
132 .iter()
133 .any(|l| l == "cargo:rustc-link-lib=dylib=stdc++")
134 );
135 assert!(!lines.iter().any(|l| l.contains("dynamic_lookup")));
136 assert!(!lines.iter().any(|l| l.starts_with("cargo:warning=")));
137 }
138
139 #[test]
140 fn missing_shim_dir_emits_warning_and_no_static_link() {
141 let lines = directives("aarch64-apple-darwin", None);
142 assert!(lines.iter().any(|l| l == MISSING_SHIM_WARNING));
143 assert!(
144 !lines
145 .iter()
146 .any(|l| l.starts_with("cargo:rustc-link-search"))
147 );
148 assert!(!lines.iter().any(|l| l.contains("static=ha_rusty_shim")));
149 // C++ runtime still wired so cargo check produces sensible output.
150 assert!(lines.iter().any(|l| l == "cargo:rustc-link-lib=dylib=c++"));
151 }
152
153 #[test]
154 fn unknown_target_skips_cpp_runtime() {
155 let lines = directives("wasm32-unknown-unknown", Some("/x"));
156 assert!(!lines.iter().any(|l| l.contains("c++")));
157 assert!(!lines.iter().any(|l| l.contains("stdc++")));
158 assert!(!lines.iter().any(|l| l.contains("dynamic_lookup")));
159 }
160}