hanko_xml 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +44 -0
- data/Cargo.lock +401 -0
- data/Cargo.toml +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +222 -0
- data/THIRD_PARTY_LICENSES.md +392 -0
- data/dependencies.yml +87 -0
- data/ext/hanko_xml/Cargo.toml +20 -0
- data/ext/hanko_xml/build/vendor.rs +817 -0
- data/ext/hanko_xml/build.rs +298 -0
- data/ext/hanko_xml/extconf.rb +6 -0
- data/ext/hanko_xml/src/dsig.rs +1851 -0
- data/ext/hanko_xml/src/errors.rs +151 -0
- data/ext/hanko_xml/src/lib.rs +490 -0
- data/ext/hanko_xml/src/sys.rs +43 -0
- data/lib/hanko_xml/algorithms.rb +228 -0
- data/lib/hanko_xml/dom.rb +137 -0
- data/lib/hanko_xml/errors.rb +132 -0
- data/lib/hanko_xml/exc-c14n.xsd +26 -0
- data/lib/hanko_xml/input.rb +50 -0
- data/lib/hanko_xml/key.rb +213 -0
- data/lib/hanko_xml/key_info.rb +67 -0
- data/lib/hanko_xml/native_version.rb +75 -0
- data/lib/hanko_xml/nokogiri_ext.rb +34 -0
- data/lib/hanko_xml/placement.rb +127 -0
- data/lib/hanko_xml/policy.rb +502 -0
- data/lib/hanko_xml/reference.rb +49 -0
- data/lib/hanko_xml/signature_info.rb +134 -0
- data/lib/hanko_xml/signed_document.rb +155 -0
- data/lib/hanko_xml/signer.rb +572 -0
- data/lib/hanko_xml/template.rb +150 -0
- data/lib/hanko_xml/transform.rb +87 -0
- data/lib/hanko_xml/verification.rb +377 -0
- data/lib/hanko_xml/verifier.rb +694 -0
- data/lib/hanko_xml/version.rb +5 -0
- data/lib/hanko_xml/xades/verifier.rb +319 -0
- data/lib/hanko_xml/xades.rb +287 -0
- data/lib/hanko_xml/xml.rb +93 -0
- data/lib/hanko_xml/xmldsig-core-schema.xsd +332 -0
- data/lib/hanko_xml.rb +122 -0
- data/sig/hanko_xml/key.rbs +48 -0
- data/sig/hanko_xml/native.rbs +12 -0
- data/sig/hanko_xml/native_version.rbs +13 -0
- data/sig/hanko_xml/nokogiri_ext.rbs +23 -0
- data/sig/hanko_xml/placement.rbs +27 -0
- data/sig/hanko_xml/policy.rbs +83 -0
- data/sig/hanko_xml/reference.rbs +36 -0
- data/sig/hanko_xml/signed_document.rbs +66 -0
- data/sig/hanko_xml/signer.rbs +50 -0
- data/sig/hanko_xml/template.rbs +41 -0
- data/sig/hanko_xml/verification.rbs +100 -0
- data/sig/hanko_xml/verifier.rbs +45 -0
- data/sig/hanko_xml/xades.rbs +70 -0
- data/sig/hanko_xml.rbs +62 -0
- metadata +105 -0
|
@@ -0,0 +1,817 @@
|
|
|
1
|
+
//! Builds libxml2 and xmlsec1 from pinned upstream source and links them
|
|
2
|
+
//! statically into this extension.
|
|
3
|
+
//!
|
|
4
|
+
//! Only runs when `HANKO_XML_VENDOR=1`. The default build still probes the
|
|
5
|
+
//! system libraries via pkg-config (see the parent `build.rs`), so this path
|
|
6
|
+
//! never executes on an ordinary user's machine -- CI sets the variable when
|
|
7
|
+
//! producing precompiled platform gems.
|
|
8
|
+
//!
|
|
9
|
+
//! Why vendor libxml2 but not OpenSSL: `xmlDoc`/`xmlNode`/`xmlAttr` are public
|
|
10
|
+
//! structs this crate dereferences by field offset, and the same `xmlDocPtr` is
|
|
11
|
+
//! shared with xmlsec, so all three parties must agree on one layout. OpenSSL
|
|
12
|
+
//! 3.x structs are opaque behind accessors and its pointers never enter this
|
|
13
|
+
//! crate at all. See dependencies.yml.
|
|
14
|
+
|
|
15
|
+
use std::collections::BTreeMap;
|
|
16
|
+
use std::env;
|
|
17
|
+
use std::fs;
|
|
18
|
+
use std::path::{Path, PathBuf};
|
|
19
|
+
use std::process::Command;
|
|
20
|
+
|
|
21
|
+
use sha2::{Digest, Sha256};
|
|
22
|
+
|
|
23
|
+
/// One pinned upstream library, as declared in dependencies.yml.
|
|
24
|
+
struct Dependency {
|
|
25
|
+
version: String,
|
|
26
|
+
url: String,
|
|
27
|
+
/// An alternate host serving the identical tarball. Upstream sites go down,
|
|
28
|
+
/// rate-limit, or (in xmlsec's case) sit behind a CDN that blocks CI
|
|
29
|
+
/// runners; a failed download should not fail the build when the same bytes
|
|
30
|
+
/// are one hop away. Trust does not depend on which host answers -- the
|
|
31
|
+
/// SHA256 is verified either way.
|
|
32
|
+
mirror: Option<String>,
|
|
33
|
+
sha256: String,
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
pub fn build() -> crate::Headers {
|
|
37
|
+
let deps = read_dependencies();
|
|
38
|
+
let out_dir = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR not set"));
|
|
39
|
+
let prefix = shared_prefix(&deps);
|
|
40
|
+
let cache = cache_dir();
|
|
41
|
+
fs::create_dir_all(&cache).expect("could not create the vendor tarball cache");
|
|
42
|
+
|
|
43
|
+
let dep = |name: &str| {
|
|
44
|
+
deps.get(name)
|
|
45
|
+
.unwrap_or_else(|| panic!("dependencies.yml has no {name} entry"))
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// The stamp is written only after all three installs succeed, so an
|
|
49
|
+
// interrupted build is never mistaken for a complete one. It also skips
|
|
50
|
+
// checksum verification entirely, so the prefix is a trusted input: see
|
|
51
|
+
// "Trusted build inputs" in RELEASING.md (S-18).
|
|
52
|
+
let stamp = prefix.join(".stamp-complete");
|
|
53
|
+
if stamp.exists() {
|
|
54
|
+
eprintln!("hanko_xml: reusing vendored build at {}", prefix.display());
|
|
55
|
+
} else {
|
|
56
|
+
// OpenSSL first: xmlsec1 configures against it.
|
|
57
|
+
let openssl_src = fetch_and_extract(dep("openssl"), "openssl", &cache, &out_dir);
|
|
58
|
+
build_openssl(&openssl_src, &prefix);
|
|
59
|
+
|
|
60
|
+
let libxml2_src = fetch_and_extract(dep("libxml2"), "libxml2", &cache, &out_dir);
|
|
61
|
+
build_libxml2(&libxml2_src, &prefix);
|
|
62
|
+
|
|
63
|
+
let xmlsec1_src = fetch_and_extract(dep("xmlsec1"), "xmlsec1", &cache, &out_dir);
|
|
64
|
+
build_xmlsec1(&xmlsec1_src, &prefix);
|
|
65
|
+
|
|
66
|
+
fs::write(&stamp, "").expect("could not write the vendored build stamp");
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
emit_link_flags(&prefix);
|
|
70
|
+
probe_vendored_headers(&prefix)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/// Where the three C libraries get installed.
|
|
74
|
+
///
|
|
75
|
+
/// Deliberately outside `OUT_DIR`. A platform gem is compiled once per Ruby
|
|
76
|
+
/// ABI, and `OUT_DIR` differs for each, so keeping the prefix there rebuilt
|
|
77
|
+
/// OpenSSL, libxml2 and xmlsec1 four times per platform. None of them depend
|
|
78
|
+
/// on the Ruby ABI in any way, so the build is keyed by target triple and the
|
|
79
|
+
/// pinned versions instead and shared across ABIs. Bumping any version in
|
|
80
|
+
/// dependencies.yml changes the key and forces a clean rebuild.
|
|
81
|
+
fn shared_prefix(deps: &BTreeMap<String, Dependency>) -> PathBuf {
|
|
82
|
+
let key = deps
|
|
83
|
+
.iter()
|
|
84
|
+
.map(|(name, dep)| format!("{name}-{}", dep.version))
|
|
85
|
+
.collect::<Vec<_>>()
|
|
86
|
+
.join("_");
|
|
87
|
+
manifest_root()
|
|
88
|
+
.join("tmp/vendor-build")
|
|
89
|
+
.join(target_triple())
|
|
90
|
+
.join(key)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/// Harvests the include paths and `-D` defines from the `.pc` files the two
|
|
94
|
+
/// builds just installed into our own prefix.
|
|
95
|
+
///
|
|
96
|
+
/// Deliberately pkg-config rather than a hardcoded list: the define set records
|
|
97
|
+
/// which algorithms the library was actually compiled with (`XMLSEC_NO_MD5`,
|
|
98
|
+
/// `XMLSEC_NO_GOST`, and so on) plus the crypto backend selector that xmlsec's
|
|
99
|
+
/// headers refuse to compile without. Hardcoding it would silently drift the
|
|
100
|
+
/// moment a configure flag above changes. This is the same mechanism the system
|
|
101
|
+
/// path uses, pointed at our prefix instead of the OS's.
|
|
102
|
+
fn probe_vendored_headers(prefix: &Path) -> crate::Headers {
|
|
103
|
+
let pkgconfig_dir = prefix.join("lib/pkgconfig");
|
|
104
|
+
env::set_var("PKG_CONFIG_PATH", &pkgconfig_dir);
|
|
105
|
+
// We already emitted our own link flags, and these are the archives we just
|
|
106
|
+
// built for the target, so pkg-config must not second-guess a cross build.
|
|
107
|
+
env::set_var("PKG_CONFIG_ALLOW_CROSS", "1");
|
|
108
|
+
|
|
109
|
+
let mut cfg = pkg_config::Config::new();
|
|
110
|
+
cfg.cargo_metadata(false).statik(true);
|
|
111
|
+
let xmlsec = cfg.probe("xmlsec1-openssl").unwrap_or_else(|e| {
|
|
112
|
+
panic!(
|
|
113
|
+
"the vendored xmlsec1 did not install a usable xmlsec1-openssl.pc in {}: {e}",
|
|
114
|
+
pkgconfig_dir.display()
|
|
115
|
+
)
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
let mut include_paths = vec![prefix.join("include"), prefix.join("include/libxml2")];
|
|
119
|
+
include_paths.extend(xmlsec.include_paths.iter().cloned());
|
|
120
|
+
if let Some(multiarch) = multiarch_include_dir() {
|
|
121
|
+
eprintln!("hanko_xml: multiarch include dir {}", multiarch.display());
|
|
122
|
+
include_paths.push(multiarch);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
let mut defines: Vec<(String, Option<String>)> = xmlsec
|
|
126
|
+
.defines
|
|
127
|
+
.iter()
|
|
128
|
+
.map(|(name, value)| (name.clone(), value.clone()))
|
|
129
|
+
.collect();
|
|
130
|
+
if target_is_windows() {
|
|
131
|
+
// See build_cflags(): the headers themselves branch on these, so
|
|
132
|
+
// bindgen has to see the same static-linkage defines the actual
|
|
133
|
+
// build used, or it generates dllimport-shaped declarations for
|
|
134
|
+
// symbols that are in fact statically linked.
|
|
135
|
+
defines.push(("LIBXML_STATIC".to_string(), None));
|
|
136
|
+
defines.push(("XMLSEC_STATIC".to_string(), None));
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
crate::Headers {
|
|
140
|
+
include_paths,
|
|
141
|
+
defines,
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// --- dependencies.yml ---------------------------------------------------
|
|
146
|
+
|
|
147
|
+
/// A deliberately small parser for the exact shape dependencies.yml has:
|
|
148
|
+
/// top-level keys, two-space-indented `key: "value"` pairs, `#` comments.
|
|
149
|
+
/// A real YAML crate would be a build dependency (and the maintained ones are
|
|
150
|
+
/// large) for a file we own and that spec/packaging_spec.rb already validates.
|
|
151
|
+
fn read_dependencies() -> BTreeMap<String, Dependency> {
|
|
152
|
+
let path = manifest_root().join("dependencies.yml");
|
|
153
|
+
let text = fs::read_to_string(&path)
|
|
154
|
+
.unwrap_or_else(|e| panic!("could not read {}: {e}", path.display()));
|
|
155
|
+
|
|
156
|
+
let mut deps: BTreeMap<String, BTreeMap<String, String>> = BTreeMap::new();
|
|
157
|
+
let mut current: Option<String> = None;
|
|
158
|
+
|
|
159
|
+
for line in text.lines() {
|
|
160
|
+
let trimmed = line.trim_end();
|
|
161
|
+
if trimmed.trim_start().starts_with('#') || trimmed.trim().is_empty() {
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
if !trimmed.starts_with(' ') {
|
|
165
|
+
let name = trimmed.trim_end_matches(':').trim().to_string();
|
|
166
|
+
deps.entry(name.clone()).or_default();
|
|
167
|
+
current = Some(name);
|
|
168
|
+
} else if let Some(name) = ¤t {
|
|
169
|
+
let Some((key, value)) = trimmed.split_once(':') else {
|
|
170
|
+
continue;
|
|
171
|
+
};
|
|
172
|
+
let value = value.trim().trim_matches('"').to_string();
|
|
173
|
+
deps.get_mut(name)
|
|
174
|
+
.expect("current section is present")
|
|
175
|
+
.insert(key.trim().to_string(), value);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
deps.into_iter()
|
|
180
|
+
.filter_map(|(name, fields)| {
|
|
181
|
+
let version = fields.get("version")?.clone();
|
|
182
|
+
let url = fields.get("url")?.clone();
|
|
183
|
+
let sha256 = fields.get("sha256")?.clone();
|
|
184
|
+
let mirror = fields.get("mirror").cloned();
|
|
185
|
+
Some((
|
|
186
|
+
name,
|
|
187
|
+
Dependency {
|
|
188
|
+
version,
|
|
189
|
+
url,
|
|
190
|
+
mirror,
|
|
191
|
+
sha256,
|
|
192
|
+
},
|
|
193
|
+
))
|
|
194
|
+
})
|
|
195
|
+
.collect()
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
fn manifest_root() -> PathBuf {
|
|
199
|
+
// CARGO_MANIFEST_DIR is ext/hanko_xml; dependencies.yml lives at the repo
|
|
200
|
+
// root, which is also what ships in the gem.
|
|
201
|
+
PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not set")).join("../..")
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/// Tarball cache. Not trusted: every file in it is SHA-256 verified before
|
|
205
|
+
/// extraction (see `fetch_and_extract`), so it may be shared or mirrored.
|
|
206
|
+
fn cache_dir() -> PathBuf {
|
|
207
|
+
match env::var_os("HANKO_XML_VENDOR_CACHE") {
|
|
208
|
+
Some(dir) => PathBuf::from(dir),
|
|
209
|
+
None => manifest_root().join("tmp/vendor-cache"),
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// --- fetch + verify -----------------------------------------------------
|
|
214
|
+
|
|
215
|
+
fn fetch_and_extract(dep: &Dependency, name: &str, cache: &Path, out_dir: &Path) -> PathBuf {
|
|
216
|
+
let filename = dep
|
|
217
|
+
.url
|
|
218
|
+
.rsplit('/')
|
|
219
|
+
.next()
|
|
220
|
+
.unwrap_or_else(|| panic!("{name}: could not derive a filename from {}", dep.url));
|
|
221
|
+
let tarball = cache.join(filename);
|
|
222
|
+
|
|
223
|
+
if !tarball.exists() {
|
|
224
|
+
download_with_fallback(dep, &tarball, name);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Verified before extraction, every time -- including for a cached file,
|
|
228
|
+
// which may have been truncated by an interrupted earlier run. We are
|
|
229
|
+
// redistributing cryptographic code; a mismatch is fatal, never a warning.
|
|
230
|
+
let actual = sha256_file(&tarball);
|
|
231
|
+
if actual != dep.sha256 {
|
|
232
|
+
let _ = fs::remove_file(&tarball);
|
|
233
|
+
panic!(
|
|
234
|
+
"{name} {} checksum mismatch\n expected {}\n actual {}\n\
|
|
235
|
+
The cached file has been removed. If this was not a corrupt download, \
|
|
236
|
+
do not update the checksum in dependencies.yml without verifying the \
|
|
237
|
+
release upstream.",
|
|
238
|
+
dep.version, dep.sha256, actual
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
let src = out_dir.join(format!("{name}-{}", dep.version));
|
|
243
|
+
if src.exists() {
|
|
244
|
+
fs::remove_dir_all(&src).ok();
|
|
245
|
+
}
|
|
246
|
+
run(
|
|
247
|
+
Command::new("tar")
|
|
248
|
+
.arg("-xf")
|
|
249
|
+
.arg(&tarball)
|
|
250
|
+
.arg("-C")
|
|
251
|
+
.arg(out_dir),
|
|
252
|
+
&format!("extracting {filename}"),
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
if !src.exists() {
|
|
256
|
+
panic!(
|
|
257
|
+
"{name}: expected {} after extracting {filename}",
|
|
258
|
+
src.display()
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
src
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
fn download_with_fallback(dep: &Dependency, dest: &Path, name: &str) {
|
|
265
|
+
let mut sources = vec![dep.url.as_str()];
|
|
266
|
+
sources.extend(dep.mirror.as_deref());
|
|
267
|
+
|
|
268
|
+
let mut failures = Vec::new();
|
|
269
|
+
for url in &sources {
|
|
270
|
+
match download(url, dest) {
|
|
271
|
+
Ok(()) => return,
|
|
272
|
+
Err(err) => {
|
|
273
|
+
eprintln!("hanko_xml: {name}: {url} failed ({err})");
|
|
274
|
+
failures.push(format!(" {url}: {err}"));
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
panic!(
|
|
279
|
+
"could not download {name} {} from any known source:\n{}",
|
|
280
|
+
dep.version,
|
|
281
|
+
failures.join("\n")
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
fn download(url: &str, dest: &Path) -> Result<(), String> {
|
|
286
|
+
let partial = dest.with_extension("partial");
|
|
287
|
+
let _ = fs::remove_file(&partial);
|
|
288
|
+
|
|
289
|
+
// An explicit User-Agent because some upstream hosts sit behind a CDN that
|
|
290
|
+
// rejects curl's default one outright.
|
|
291
|
+
let status = Command::new("curl")
|
|
292
|
+
.args([
|
|
293
|
+
"-sSfL",
|
|
294
|
+
"--retry",
|
|
295
|
+
"3",
|
|
296
|
+
"--max-time",
|
|
297
|
+
"300",
|
|
298
|
+
"-A",
|
|
299
|
+
"hanko_xml-build/1.0",
|
|
300
|
+
"-o",
|
|
301
|
+
])
|
|
302
|
+
.arg(&partial)
|
|
303
|
+
.arg(url)
|
|
304
|
+
.status()
|
|
305
|
+
.map_err(|e| format!("could not start curl: {e}"))?;
|
|
306
|
+
|
|
307
|
+
if !status.success() {
|
|
308
|
+
let _ = fs::remove_file(&partial);
|
|
309
|
+
return Err(format!("curl exited with {status}"));
|
|
310
|
+
}
|
|
311
|
+
fs::rename(&partial, dest).map_err(|e| format!("could not move into the cache: {e}"))
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
fn sha256_file(path: &Path) -> String {
|
|
315
|
+
let bytes = fs::read(path).unwrap_or_else(|e| panic!("could not read {}: {e}", path.display()));
|
|
316
|
+
let mut hasher = Sha256::new();
|
|
317
|
+
hasher.update(&bytes);
|
|
318
|
+
// Formatted byte-by-byte rather than with `{:x}`: sha2 0.11 returns
|
|
319
|
+
// hybrid-array's `Array` instead of `GenericArray`, and that type does not
|
|
320
|
+
// implement LowerHex. Doing it here keeps the build working across both.
|
|
321
|
+
hasher
|
|
322
|
+
.finalize()
|
|
323
|
+
.iter()
|
|
324
|
+
.map(|byte| format!("{byte:02x}"))
|
|
325
|
+
.collect()
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// --- the builds ---------------------------------------------------------
|
|
329
|
+
|
|
330
|
+
/// Hidden visibility is not an optimisation here -- it is what keeps our
|
|
331
|
+
/// statically-linked libxml2 from being interposed by Nokogiri's copy (or vice
|
|
332
|
+
/// versa) when both are loaded into one Ruby process. On Linux the dynamic
|
|
333
|
+
/// linker resolves a duplicated symbol to whichever definition was loaded
|
|
334
|
+
/// first, process-wide, so an exported `xmlReadMemory` from this extension
|
|
335
|
+
/// could silently bind to a different libxml2's implementation. Upstream's
|
|
336
|
+
/// XMLPUBFUN/XMLSEC_EXPORT macros expand to nothing on non-Windows, so this
|
|
337
|
+
/// flag is not overridden.
|
|
338
|
+
const HIDDEN_VISIBILITY_CFLAGS: &str = "-fvisibility=hidden -fPIC -O2";
|
|
339
|
+
|
|
340
|
+
/// CFLAGS for building libxml2 and xmlsec1 (not OpenSSL, which has its own
|
|
341
|
+
/// static/shared switch and never dllimports anything of ours).
|
|
342
|
+
///
|
|
343
|
+
/// On Windows, XMLPUBFUN/XMLSEC_EXPORT expand to `__declspec(dllimport)` for
|
|
344
|
+
/// any translation unit that doesn't define LIBXML_STATIC/XMLSEC_STATIC --
|
|
345
|
+
/// including a library's *own* other translation units, since they all
|
|
346
|
+
/// `#include` the same public headers. Leaving these undefined is what
|
|
347
|
+
/// produced `undefined reference to __imp_xmlSecError` linking
|
|
348
|
+
/// libxmlsec1-openssl.a against libxmlsec1.a: both were built assuming the
|
|
349
|
+
/// symbols would come from a DLL's import library, and there is no DLL --
|
|
350
|
+
/// only static archives, both here and in the final `cargo:rustc-link-lib`.
|
|
351
|
+
/// Non-Windows never defines these dllimport/dllexport forms in the first
|
|
352
|
+
/// place (see the module doc comment), so this is a no-op there.
|
|
353
|
+
fn build_cflags() -> String {
|
|
354
|
+
if target_is_windows() {
|
|
355
|
+
format!("{HIDDEN_VISIBILITY_CFLAGS} -DLIBXML_STATIC -DXMLSEC_STATIC")
|
|
356
|
+
} else {
|
|
357
|
+
HIDDEN_VISIBILITY_CFLAGS.to_string()
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/// OpenSSL's Configure wants its target as a positional argument and cannot
|
|
362
|
+
/// auto-detect one when cross-compiling. Map the Rust triple onto OpenSSL's
|
|
363
|
+
/// own naming; returning None falls back to `./config`, which auto-detects and
|
|
364
|
+
/// is correct for a native build.
|
|
365
|
+
fn openssl_target() -> Option<&'static str> {
|
|
366
|
+
match target_triple().as_str() {
|
|
367
|
+
"x86_64-unknown-linux-gnu" => Some("linux-x86_64"),
|
|
368
|
+
"aarch64-unknown-linux-gnu" => Some("linux-aarch64"),
|
|
369
|
+
"aarch64-apple-darwin" => Some("darwin64-arm64-cc"),
|
|
370
|
+
"x86_64-apple-darwin" => Some("darwin64-x86_64-cc"),
|
|
371
|
+
// rb_sys' x64-mingw-ucrt maps to this Rust triple, built with mingw-w64
|
|
372
|
+
// (not MSVC), so OpenSSL's own mingw64 target is the correct one, not
|
|
373
|
+
// one of the VC-WIN64* targets.
|
|
374
|
+
"x86_64-pc-windows-gnu" => Some("mingw64"),
|
|
375
|
+
_ => None,
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/// autoconf's config.sub does not understand Rust's `pc-windows-gnu` vendor
|
|
380
|
+
/// field the way it understands the Linux and Darwin triples above -- mingw-w64
|
|
381
|
+
/// cross toolchains identify themselves as `x86_64-w64-mingw32`. Only libxml2
|
|
382
|
+
/// and xmlsec1 need this (they go through `./configure --host=`); OpenSSL's
|
|
383
|
+
/// `./Configure` takes its own `mingw64` name from `openssl_target()` above and
|
|
384
|
+
/// never sees this value.
|
|
385
|
+
fn autotools_host() -> String {
|
|
386
|
+
match target_triple().as_str() {
|
|
387
|
+
"x86_64-pc-windows-gnu" => "x86_64-w64-mingw32".to_string(),
|
|
388
|
+
other => other.to_string(),
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
fn build_openssl(src: &Path, prefix: &Path) {
|
|
393
|
+
let (cross_args, cross_env) = cross_config();
|
|
394
|
+
let target = openssl_target();
|
|
395
|
+
|
|
396
|
+
// `./Configure` takes an explicit target; `./config` guesses. Use the
|
|
397
|
+
// explicit one wherever we know the mapping.
|
|
398
|
+
let script = if target.is_some() {
|
|
399
|
+
"./Configure"
|
|
400
|
+
} else {
|
|
401
|
+
"./config"
|
|
402
|
+
};
|
|
403
|
+
|
|
404
|
+
let mut args: Vec<String> = Vec::new();
|
|
405
|
+
if let Some(t) = target {
|
|
406
|
+
args.push(t.to_string());
|
|
407
|
+
}
|
|
408
|
+
args.push(format!("--prefix={}", prefix.display()));
|
|
409
|
+
args.push(format!("--openssldir={}", prefix.join("ssl").display()));
|
|
410
|
+
// Some platforms default to lib64/, which our single link-search path
|
|
411
|
+
// would then miss entirely.
|
|
412
|
+
args.push("--libdir=lib".into());
|
|
413
|
+
args.push("no-shared".into());
|
|
414
|
+
args.push("no-tests".into());
|
|
415
|
+
args.push("no-docs".into());
|
|
416
|
+
// no-shared disables shared *libraries*; providers are *modules*, which is
|
|
417
|
+
// a separate switch. Without this, OpenSSL still tries to link
|
|
418
|
+
// providers/legacy.dylib and fails on Darwin -- the legacy provider's
|
|
419
|
+
// symbols are not available to a module when nothing is being built
|
|
420
|
+
// shared. Modules are useless to us regardless: xmlsec is configured
|
|
421
|
+
// --disable-crypto-dl precisely so nothing is loaded at runtime.
|
|
422
|
+
args.push("no-module".into());
|
|
423
|
+
// We link libcrypto; the `openssl` command-line tool is dead weight, and
|
|
424
|
+
// cross-linking it for Darwin fails on s_client/s_server symbols that have
|
|
425
|
+
// nothing to do with what we use.
|
|
426
|
+
args.push("no-apps".into());
|
|
427
|
+
// Deliberately not no-legacy. The algorithms stay compiled into libcrypto;
|
|
428
|
+
// this only stops them being packaged as a separately loadable file.
|
|
429
|
+
|
|
430
|
+
let mut configure = Command::new(script);
|
|
431
|
+
configure.current_dir(src).args(&args);
|
|
432
|
+
// OpenSSL's own build system handles --host-style flags itself, so only
|
|
433
|
+
// the toolchain environment carries over from cross_config().
|
|
434
|
+
let _ = &cross_args;
|
|
435
|
+
configure.env("CFLAGS", HIDDEN_VISIBILITY_CFLAGS);
|
|
436
|
+
for (key, value) in &cross_env {
|
|
437
|
+
configure.env(key, value);
|
|
438
|
+
}
|
|
439
|
+
run(
|
|
440
|
+
&mut configure,
|
|
441
|
+
&format!("configuring openssl for {}", target_triple()),
|
|
442
|
+
);
|
|
443
|
+
|
|
444
|
+
let jobs = env::var("NUM_JOBS").unwrap_or_else(|_| "4".into());
|
|
445
|
+
let mut build = Command::new("make");
|
|
446
|
+
build.current_dir(src).arg(format!("-j{jobs}"));
|
|
447
|
+
for (key, value) in &cross_env {
|
|
448
|
+
build.env(key, value);
|
|
449
|
+
}
|
|
450
|
+
run(&mut build, "building openssl");
|
|
451
|
+
|
|
452
|
+
// install_sw is the libraries and headers only; plain `install` also
|
|
453
|
+
// builds and installs man pages we do not ship.
|
|
454
|
+
let mut install = Command::new("make");
|
|
455
|
+
install.current_dir(src).arg("install_sw");
|
|
456
|
+
for (key, value) in &cross_env {
|
|
457
|
+
install.env(key, value);
|
|
458
|
+
}
|
|
459
|
+
run(&mut install, "installing openssl");
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
fn build_libxml2(src: &Path, prefix: &Path) {
|
|
463
|
+
// Feature flags are load-bearing beyond linking: build.rs allowlists
|
|
464
|
+
// `LIBXML_.*` for bindgen, which sweeps in the LIBXML_*_ENABLED macros, and
|
|
465
|
+
// those gate declarations in headers bindgen parses. Disabling a feature
|
|
466
|
+
// can therefore change which items exist in the generated bindings -- so
|
|
467
|
+
// this list stays conservative, and any change to it needs a full rebuild
|
|
468
|
+
// plus the whole spec suite.
|
|
469
|
+
let mut args = vec![
|
|
470
|
+
format!("--prefix={}", prefix.display()),
|
|
471
|
+
"--enable-static".into(),
|
|
472
|
+
"--disable-shared".into(),
|
|
473
|
+
"--with-pic".into(),
|
|
474
|
+
// Required: xmlsec needs xpath/xptr/c14n/schemas to build DSig at all,
|
|
475
|
+
// and this crate needs tree + output for xmlReadMemory/xmlDocDumpMemory.
|
|
476
|
+
"--with-xpath".into(),
|
|
477
|
+
"--with-xptr".into(),
|
|
478
|
+
"--with-c14n".into(),
|
|
479
|
+
"--with-schemas".into(),
|
|
480
|
+
"--with-output".into(),
|
|
481
|
+
"--with-regexps".into(),
|
|
482
|
+
"--with-pattern".into(),
|
|
483
|
+
"--with-valid".into(),
|
|
484
|
+
"--with-tree".into(),
|
|
485
|
+
"--with-threads".into(),
|
|
486
|
+
// Not used by this crate, and dropping http/ftp also closes off
|
|
487
|
+
// network fetches from within XML processing.
|
|
488
|
+
"--without-python".into(),
|
|
489
|
+
"--without-lzma".into(),
|
|
490
|
+
"--without-zlib".into(),
|
|
491
|
+
"--without-http".into(),
|
|
492
|
+
"--without-html".into(),
|
|
493
|
+
"--without-icu".into(),
|
|
494
|
+
"--without-modules".into(),
|
|
495
|
+
"--without-readline".into(),
|
|
496
|
+
"--without-history".into(),
|
|
497
|
+
"--without-debug".into(),
|
|
498
|
+
];
|
|
499
|
+
|
|
500
|
+
// "--with-iconv" (no argument) means required, not "use it if present" --
|
|
501
|
+
// configure hard-errors when it is requested but not found, which is
|
|
502
|
+
// exactly the mingw dock's situation (no libiconv there, and we do not
|
|
503
|
+
// vendor one). Falls back to libxml2's own built-in UTF-8/UTF-16/ASCII/
|
|
504
|
+
// ISO-8859-1 converters, which is what every fixture in this repo needs;
|
|
505
|
+
// revisit if a real document shows up needing another legacy encoding.
|
|
506
|
+
args.push(if target_is_windows() {
|
|
507
|
+
"--without-iconv".into()
|
|
508
|
+
} else {
|
|
509
|
+
"--with-iconv".into()
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
configure_make_install(src, &args, "libxml2", &[]);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
fn build_xmlsec1(src: &Path, prefix: &Path) {
|
|
516
|
+
// OpenSSL is vendored into the same prefix, so xmlsec configures against
|
|
517
|
+
// the exact build we just made rather than whatever the machine happens to
|
|
518
|
+
// have. That is what stops the gem recording a NEEDED entry for a
|
|
519
|
+
// libcrypto soname the user does not have.
|
|
520
|
+
eprintln!(
|
|
521
|
+
"hanko_xml: target={} cross={} openssl=vendored at {}",
|
|
522
|
+
target_triple(),
|
|
523
|
+
is_cross_compiling(),
|
|
524
|
+
prefix.display()
|
|
525
|
+
);
|
|
526
|
+
|
|
527
|
+
let mut args = vec![
|
|
528
|
+
format!("--prefix={}", prefix.display()),
|
|
529
|
+
format!("--with-libxml={}", prefix.display()),
|
|
530
|
+
"--enable-static".into(),
|
|
531
|
+
"--disable-shared".into(),
|
|
532
|
+
"--with-pic".into(),
|
|
533
|
+
"--disable-apps".into(),
|
|
534
|
+
"--disable-docs".into(),
|
|
535
|
+
// Essential: without this xmlsec dlopen()s its crypto backend at
|
|
536
|
+
// runtime, which defeats static linking entirely.
|
|
537
|
+
"--disable-crypto-dl".into(),
|
|
538
|
+
"--disable-apps-crypto-dl".into(),
|
|
539
|
+
"--with-default-crypto=openssl".into(),
|
|
540
|
+
"--without-nss".into(),
|
|
541
|
+
"--without-nspr".into(),
|
|
542
|
+
"--without-gnutls".into(),
|
|
543
|
+
"--without-gcrypt".into(),
|
|
544
|
+
"--without-libxslt".into(),
|
|
545
|
+
];
|
|
546
|
+
if target_is_windows() {
|
|
547
|
+
// configure auto-detects the Windows-native CNG/CryptoAPI backends
|
|
548
|
+
// and builds them alongside OpenSSL unless told not to. Unlike
|
|
549
|
+
// nss/gnutls/gcrypt above, these two are AC_ARG_ENABLE options, not
|
|
550
|
+
// AC_ARG_WITH -- "--without-mscng" is silently accepted and ignored
|
|
551
|
+
// (same as the harmless "--with-tree" warning elsewhere in this
|
|
552
|
+
// build), which is how the mscng module still got compiled the first
|
|
553
|
+
// time this was tried. We want exactly one crypto backend, chosen by
|
|
554
|
+
// --with-default-crypto=openssl above; building CNG's code needs
|
|
555
|
+
// mingw headers this dock's older w32api does not declare
|
|
556
|
+
// (CryptExportPublicKeyInfoFromBCryptKeyHandle and friends).
|
|
557
|
+
args.push("--disable-mscng".into());
|
|
558
|
+
args.push("--disable-mscrypto".into());
|
|
559
|
+
}
|
|
560
|
+
args.push(format!("--with-openssl={}", prefix.display()));
|
|
561
|
+
|
|
562
|
+
// OpenSSL and libxml2 are both already installed into this prefix, so
|
|
563
|
+
// xmlsec's configure test-compiles resolve against exactly what we built.
|
|
564
|
+
let extra_env = vec![
|
|
565
|
+
(
|
|
566
|
+
"PKG_CONFIG_PATH".to_string(),
|
|
567
|
+
prefix.join("lib/pkgconfig").display().to_string(),
|
|
568
|
+
),
|
|
569
|
+
(
|
|
570
|
+
"LDFLAGS".to_string(),
|
|
571
|
+
format!("-L{}", prefix.join("lib").display()),
|
|
572
|
+
),
|
|
573
|
+
(
|
|
574
|
+
"CPPFLAGS".to_string(),
|
|
575
|
+
format!("-I{}", prefix.join("include").display()),
|
|
576
|
+
),
|
|
577
|
+
];
|
|
578
|
+
|
|
579
|
+
configure_make_install(src, &args, "xmlsec1", &extra_env);
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
/// The Rust target triple this extension is being built for, e.g.
|
|
583
|
+
/// `aarch64-unknown-linux-gnu`. Cargo always sets it, including for host builds.
|
|
584
|
+
fn target_triple() -> String {
|
|
585
|
+
env::var("TARGET").expect("TARGET not set")
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
/// True when cargo is cross-compiling. `x86_64-linux` platform gems are built
|
|
589
|
+
/// natively inside the x86_64 dock container and take the cheap path; only
|
|
590
|
+
/// `aarch64-linux` actually cross-compiles.
|
|
591
|
+
fn is_cross_compiling() -> bool {
|
|
592
|
+
match env::var("HOST") {
|
|
593
|
+
Ok(host) => host != target_triple(),
|
|
594
|
+
Err(_) => false,
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
/// Whether the *target* is macOS. `cfg!(target_os = ...)` is wrong here: a
|
|
599
|
+
/// build script is compiled for the host, so it would describe the build
|
|
600
|
+
/// machine rather than what we are producing.
|
|
601
|
+
fn target_is_macos() -> bool {
|
|
602
|
+
env::var("CARGO_CFG_TARGET_OS").as_deref() == Ok("macos")
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
fn target_is_windows() -> bool {
|
|
606
|
+
env::var("CARGO_CFG_TARGET_OS").as_deref() == Ok("windows")
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
/// The cross toolchain to hand autotools, resolved from the environment the
|
|
610
|
+
/// way the `cc` crate does: `CC_<triple>` (underscored), then `TARGET_CC`, then
|
|
611
|
+
/// the conventional `<triple>-gcc` prefix that rake-compiler-dock provides.
|
|
612
|
+
fn cross_tool(var: &str, suffixes: &[&str]) -> Option<String> {
|
|
613
|
+
let triple = target_triple();
|
|
614
|
+
let underscored = triple.replace('-', "_");
|
|
615
|
+
if let Ok(tool) =
|
|
616
|
+
env::var(format!("{var}_{underscored}")).or_else(|_| env::var(format!("TARGET_{var}")))
|
|
617
|
+
{
|
|
618
|
+
return Some(tool);
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
// Two prefixes and several suffixes, because the toolchains disagree on
|
|
622
|
+
// both halves of the name. rake-compiler-dock installs
|
|
623
|
+
// `aarch64-linux-gnu-gcc` for Linux but `aarch64-apple-darwin-clang` for
|
|
624
|
+
// macOS, while cargo's TARGET carries the vendor field for Linux
|
|
625
|
+
// (`aarch64-unknown-linux-gnu`) and not for Darwin (`aarch64-apple-darwin`).
|
|
626
|
+
// Probing one spelling finds nothing on the other platform, which would
|
|
627
|
+
// leave CC unset and build the C libraries with the *host* compiler while
|
|
628
|
+
// configured for the target.
|
|
629
|
+
let gnu_triple = triple.replacen("-unknown-", "-", 1);
|
|
630
|
+
// rake-compiler-dock's mingw toolchain drops the vendor field too
|
|
631
|
+
// (`x86_64-windows-gnu-gcc`, not `x86_64-pc-windows-gnu-gcc`), and a plain
|
|
632
|
+
// mingw-w64 cross package as installed on Debian uses the standard GNU
|
|
633
|
+
// triple instead (`x86_64-w64-mingw32-gcc`). Probe all the spellings this
|
|
634
|
+
// dock or a distro package might have used; only one needs to exist.
|
|
635
|
+
let dock_windows_triple = triple.replacen("-pc-windows-", "-windows-", 1);
|
|
636
|
+
let mingw_host_triple = autotools_host();
|
|
637
|
+
let prefixes = [
|
|
638
|
+
triple.as_str(),
|
|
639
|
+
gnu_triple.as_str(),
|
|
640
|
+
dock_windows_triple.as_str(),
|
|
641
|
+
mingw_host_triple.as_str(),
|
|
642
|
+
];
|
|
643
|
+
|
|
644
|
+
prefixes
|
|
645
|
+
.iter()
|
|
646
|
+
.flat_map(|prefix| suffixes.iter().map(move |suffix| format!("{prefix}-{suffix}")))
|
|
647
|
+
.find(|candidate| {
|
|
648
|
+
// Existence, not exit status. The osxcross ar and ranlib wrappers
|
|
649
|
+
// reject --version outright ("error: ...", or a usage dump), so a
|
|
650
|
+
// success check silently rejects tools that are present and
|
|
651
|
+
// correct. That is how RANLIB ended up unset for Darwin, leaving
|
|
652
|
+
// OpenSSL to call the host GNU ranlib with Apple's -c flag.
|
|
653
|
+
// Command::output errors only when the binary cannot be spawned.
|
|
654
|
+
Command::new(candidate).arg("--version").output().is_ok()
|
|
655
|
+
})
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
/// Extra configure arguments and environment needed when cross-compiling.
|
|
659
|
+
/// Empty for a native build, so the host path is unchanged.
|
|
660
|
+
fn cross_config() -> (Vec<String>, Vec<(String, String)>) {
|
|
661
|
+
if !is_cross_compiling() {
|
|
662
|
+
return (Vec::new(), Vec::new());
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
// autotools' config.sub understands Rust's cpu-vendor-os triples directly,
|
|
666
|
+
// except mingw's -- see autotools_host().
|
|
667
|
+
let mut args = vec![format!("--host={}", autotools_host())];
|
|
668
|
+
// libxml2 and xmlsec both run link-and-execute feature probes that cannot
|
|
669
|
+
// work when the binary is for another architecture; --host already puts
|
|
670
|
+
// autotools in cross mode, this just makes the intent explicit.
|
|
671
|
+
args.push("--disable-dependency-tracking".into());
|
|
672
|
+
|
|
673
|
+
let mut env_vars = Vec::new();
|
|
674
|
+
// clang before gcc: the Darwin cross toolchain is clang-based, and the
|
|
675
|
+
// Linux one has no `-clang` binary to shadow it.
|
|
676
|
+
for (var, suffixes) in [
|
|
677
|
+
("CC", &["clang", "gcc"][..]),
|
|
678
|
+
("AR", &["ar"][..]),
|
|
679
|
+
("RANLIB", &["ranlib"][..]),
|
|
680
|
+
] {
|
|
681
|
+
if let Some(tool) = cross_tool(var, suffixes) {
|
|
682
|
+
env_vars.push((var.to_string(), tool));
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
(args, env_vars)
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
fn configure_make_install(src: &Path, args: &[String], name: &str, extra_env: &[(String, String)]) {
|
|
690
|
+
let (cross_args, cross_env) = cross_config();
|
|
691
|
+
|
|
692
|
+
let mut configure = Command::new("./configure");
|
|
693
|
+
configure.current_dir(src).args(args).args(&cross_args);
|
|
694
|
+
configure.env("CFLAGS", build_cflags());
|
|
695
|
+
for (key, value) in cross_env.iter().chain(extra_env) {
|
|
696
|
+
configure.env(key, value);
|
|
697
|
+
}
|
|
698
|
+
run(&mut configure, &format!("configuring {name} for {}", target_triple()));
|
|
699
|
+
|
|
700
|
+
let jobs = env::var("NUM_JOBS").unwrap_or_else(|_| "4".into());
|
|
701
|
+
let mut build = Command::new("make");
|
|
702
|
+
build.current_dir(src).arg(format!("-j{jobs}"));
|
|
703
|
+
for (key, value) in cross_env.iter().chain(extra_env) {
|
|
704
|
+
build.env(key, value);
|
|
705
|
+
}
|
|
706
|
+
run(&mut build, &format!("building {name}"));
|
|
707
|
+
|
|
708
|
+
let mut install = Command::new("make");
|
|
709
|
+
install.current_dir(src).arg("install");
|
|
710
|
+
for (key, value) in cross_env.iter().chain(extra_env) {
|
|
711
|
+
install.env(key, value);
|
|
712
|
+
}
|
|
713
|
+
run(&mut install, &format!("installing {name}"));
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
/// Debian and Ubuntu put architecture-specific headers under
|
|
717
|
+
/// `/usr/include/<multiarch triple>`, and OpenSSL's `opensslconf.h` is one of
|
|
718
|
+
/// them: `e_os2.h` does `#include <openssl/opensslconf.h>` and relies on that
|
|
719
|
+
/// directory being on the include path.
|
|
720
|
+
///
|
|
721
|
+
/// GCC adds it automatically, which is why libxml2 and xmlsec1 compile fine.
|
|
722
|
+
/// The clang instance bindgen runs does not, so without this the C libraries
|
|
723
|
+
/// build and then binding generation fails on a header GCC found without
|
|
724
|
+
/// being asked. Returns nothing on systems that do not use multiarch.
|
|
725
|
+
fn multiarch_include_dir() -> Option<PathBuf> {
|
|
726
|
+
fn ask(cc: &str) -> Option<String> {
|
|
727
|
+
Command::new(cc)
|
|
728
|
+
.arg("-print-multiarch")
|
|
729
|
+
.output()
|
|
730
|
+
.ok()
|
|
731
|
+
.filter(|out| out.status.success())
|
|
732
|
+
.and_then(|out| String::from_utf8(out.stdout).ok())
|
|
733
|
+
.map(|s| s.trim().to_string())
|
|
734
|
+
.filter(|s| !s.is_empty())
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
// Only ask a compiler that speaks for the *target*. Falling back to the
|
|
738
|
+
// host `cc` while cross-compiling would confidently return the build
|
|
739
|
+
// machine's triple — x86_64-linux-gnu while building for aarch64 — and
|
|
740
|
+
// point bindgen at headers for the wrong architecture. When there is no
|
|
741
|
+
// target compiler to ask, derive it instead.
|
|
742
|
+
let probed = match (cross_tool("CC", &["clang", "gcc"]), is_cross_compiling()) {
|
|
743
|
+
(Some(target_cc), _) => ask(&target_cc),
|
|
744
|
+
(None, true) => None,
|
|
745
|
+
(None, false) => ask("cc"),
|
|
746
|
+
};
|
|
747
|
+
|
|
748
|
+
// Fall back to deriving it from the Rust target, whose triples map onto
|
|
749
|
+
// Debian's by dropping the vendor field: x86_64-unknown-linux-gnu becomes
|
|
750
|
+
// x86_64-linux-gnu.
|
|
751
|
+
let triple = probed.unwrap_or_else(|| target_triple().replacen("-unknown-", "-", 1));
|
|
752
|
+
|
|
753
|
+
let dir = PathBuf::from("/usr/include").join(triple);
|
|
754
|
+
dir.is_dir().then_some(dir)
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
// --- linking ------------------------------------------------------------
|
|
758
|
+
|
|
759
|
+
fn emit_link_flags(prefix: &Path) {
|
|
760
|
+
println!(
|
|
761
|
+
"cargo:rustc-link-search=native={}",
|
|
762
|
+
prefix.join("lib").display()
|
|
763
|
+
);
|
|
764
|
+
// Order matters for static archives: xmlsec's OpenSSL backend depends on
|
|
765
|
+
// xmlsec core, which depends on libxml2.
|
|
766
|
+
println!("cargo:rustc-link-lib=static=xmlsec1-openssl");
|
|
767
|
+
println!("cargo:rustc-link-lib=static=xmlsec1");
|
|
768
|
+
println!("cargo:rustc-link-lib=static=xml2");
|
|
769
|
+
// Static, not dylib: linking the system libcrypto is what recorded a
|
|
770
|
+
// NEEDED entry for the build container's soname (libcrypto.so.1.1) and
|
|
771
|
+
// made the gem unloadable on any current distro.
|
|
772
|
+
println!("cargo:rustc-link-lib=static=crypto");
|
|
773
|
+
|
|
774
|
+
if target_is_macos() {
|
|
775
|
+
println!("cargo:rustc-link-lib=dylib=iconv");
|
|
776
|
+
println!("cargo:rustc-link-lib=dylib=m");
|
|
777
|
+
} else if target_is_windows() {
|
|
778
|
+
// PE has no dlopen()/pthread/libm to link against -- mingw-w64's
|
|
779
|
+
// runtime and rustc's own windows-gnu target spec already pull in
|
|
780
|
+
// whatever those three cover on this platform (winpthread, ucrtbase).
|
|
781
|
+
// --exclude-libs is a GNU ld option for ELF/XCOFF and is not passed
|
|
782
|
+
// here. A PE DLL does not export archive members by default anyway,
|
|
783
|
+
// and cross-gem.yml confirms it on every build: the DLL exports only
|
|
784
|
+
// Init_hanko_xml and ruby_abi_version, and imports no libxml2, xmlsec1
|
|
785
|
+
// or OpenSSL DLL (.github/scripts/check_pe_exports.rb, #20).
|
|
786
|
+
//
|
|
787
|
+
// What Windows *does* need: system import libraries for the Win32
|
|
788
|
+
// APIs our static libcrypto/libxml2 call directly instead of through
|
|
789
|
+
// a libc shim -- advapi32 (registry/event log, legacy CryptXxx),
|
|
790
|
+
// crypt32 (certificate store), bcrypt (BCryptGenRandom, used by
|
|
791
|
+
// libxml2's dict.c), user32/gdi32 (pulled in transitively by
|
|
792
|
+
// OpenSSL's Windows build). None of these are optional the way the
|
|
793
|
+
// -lm/-ldl/-lpthread they replace are -- they were undefined
|
|
794
|
+
// references at link time before this was added.
|
|
795
|
+
for lib in ["advapi32", "crypt32", "bcrypt", "user32", "gdi32"] {
|
|
796
|
+
println!("cargo:rustc-link-lib=dylib={lib}");
|
|
797
|
+
}
|
|
798
|
+
} else {
|
|
799
|
+
// Keep the vendored archives' symbols out of this cdylib's dynamic
|
|
800
|
+
// symbol table so they cannot interpose with Nokogiri's libxml2 -- or,
|
|
801
|
+
// now, with the libcrypto Ruby's own openssl extension already loaded.
|
|
802
|
+
println!("cargo:rustc-link-arg=-Wl,--exclude-libs,ALL");
|
|
803
|
+
// A static libcrypto still needs these from the platform.
|
|
804
|
+
println!("cargo:rustc-link-lib=dylib=dl");
|
|
805
|
+
println!("cargo:rustc-link-lib=dylib=pthread");
|
|
806
|
+
println!("cargo:rustc-link-lib=dylib=m");
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
fn run(command: &mut Command, what: &str) {
|
|
811
|
+
let status = command
|
|
812
|
+
.status()
|
|
813
|
+
.unwrap_or_else(|e| panic!("failed to start `{what}`: {e}"));
|
|
814
|
+
if !status.success() {
|
|
815
|
+
panic!("`{what}` failed with {status}");
|
|
816
|
+
}
|
|
817
|
+
}
|