1use std::{
2 collections::{hash_map::Keys, HashMap},
3 env,
4 path::PathBuf,
5 process::Command,
6};
7
8use regex::Regex;
9mod flags;
10mod library;
11mod search_path;
12
13use library::*;
14use search_path::*;
15use std::ffi::OsString;
16
17use crate::{
18 debug_log, memoize,
19 utils::{is_msvc, shellsplit},
20};
21
22use self::flags::Flags;
23
24#[derive(Debug, PartialEq, Eq)]
27pub struct RbConfig {
28 pub search_paths: Vec<SearchPath>,
29 pub libs: Vec<Library>,
30 pub link_args: Vec<String>,
31 pub cflags: Vec<String>,
32 pub blocklist_lib: Vec<String>,
33 pub blocklist_link_arg: Vec<String>,
34 use_rpath: bool,
35 value_map: HashMap<String, String>,
36}
37
38impl Default for RbConfig {
39 fn default() -> Self {
40 Self::new()
41 }
42}
43
44impl RbConfig {
45 pub(crate) fn new() -> RbConfig {
47 RbConfig {
48 blocklist_lib: vec![],
49 blocklist_link_arg: vec![],
50 search_paths: Vec::new(),
51 libs: Vec::new(),
52 link_args: Vec::new(),
53 cflags: Vec::new(),
54 value_map: HashMap::new(),
55 use_rpath: false,
56 }
57 }
58
59 pub fn all_keys(&self) -> Keys<'_, String, String> {
61 self.value_map.keys()
62 }
63
64 pub fn current() -> RbConfig {
66 println!("cargo:rerun-if-env-changed=RUBY");
67
68 let mut rbconfig = RbConfig::new();
69
70 let parsed = if rbconfig.is_cross_compiling() {
73 HashMap::new()
74 } else {
75 let output = memoize!(String: {
76 let ruby = env::var_os("RUBY").unwrap_or_else(|| OsString::from("ruby"));
77
78 let config = Command::new(ruby)
79 .arg("--disable-gems")
80 .arg("-rrbconfig")
81 .arg("-e")
82 .arg("print RbConfig::CONFIG.map {|kv| kv.join(\"\x1F\")}.join(\"\x1E\")")
83 .output()
84 .unwrap_or_else(|e| panic!("ruby not found: {}", e));
85 if !config.status.success() {
86 panic!("non-zero exit status while dumping RbConfig: {:?}", config);
87 }
88 String::from_utf8(config.stdout).expect("RbConfig value not UTF-8!")
89 });
90
91 let mut parsed = HashMap::new();
92 for line in output.split('\x1E') {
93 let mut parts = line.splitn(2, '\x1F');
94 if let (Some(key), Some(val)) = (parts.next(), parts.next()) {
95 parsed.insert(key.to_owned(), val.to_owned());
96 }
97 }
98 parsed
99 };
100
101 parsed.get("cflags").map(|f| rbconfig.push_cflags(f));
102 parsed.get("DLDFLAGS").map(|f| rbconfig.push_dldflags(f));
103
104 rbconfig.value_map = parsed;
105
106 rbconfig
107 }
108
109 pub fn link_ruby(&mut self, is_static: bool) -> &mut Self {
111 let Some(libdir) = self.get("libdir") else {
112 return self;
113 };
114
115 self.push_search_path(libdir.as_str());
116 self.push_dldflags(&format!("-L{}", libdir));
117
118 let librubyarg = if is_static {
119 self.get("LIBRUBYARG_STATIC")
120 } else {
121 self.get("LIBRUBYARG_SHARED")
122 };
123
124 let librubyarg = match librubyarg {
125 Some(lib) => lib,
126 None => {
127 debug_log!("WARN: LIBRUBYARG not found in RbConfig, skipping linking Ruby");
128 return self;
129 }
130 };
131
132 if is_msvc() {
133 for lib in librubyarg.split_whitespace() {
134 self.push_library(lib);
135 }
136
137 let mut to_link: Vec<String> = vec![];
138
139 if let Some(libs) = self.get("LIBS") {
140 to_link.extend(libs.split_whitespace().map(|s| s.to_string()));
141 }
142
143 if let Some(libs) = self.get("LOCAL_LIBS") {
144 to_link.extend(libs.split_whitespace().map(|s| s.to_string()));
145 }
146
147 for lib in to_link {
148 self.push_library(lib);
149 }
150 } else {
151 self.push_dldflags(&librubyarg);
152
153 if cfg!(unix) {
154 self.use_rpath();
155 }
156 }
157
158 self
159 }
160
161 pub fn libruby_static_name(&self) -> String {
163 let Some(lib) = self.get("LIBRUBY_A") else {
164 return format!("{}-static", self.libruby_so_name());
165 };
166
167 lib.trim_start_matches("lib")
168 .trim_end_matches(".a")
169 .to_string()
170 }
171
172 pub fn libruby_so_name(&self) -> String {
174 self.get("RUBY_SO_NAME")
175 .unwrap_or_else(|| "ruby".to_string())
176 }
177
178 pub fn platform(&self) -> String {
180 self.get("platform")
181 .unwrap_or_else(|| self.get("arch").expect("arch not found"))
182 }
183
184 pub fn blocklist_lib(&mut self, name: &str) -> &mut RbConfig {
186 self.blocklist_lib.push(name.to_string());
187 self
188 }
189
190 pub fn blocklist_link_arg(&mut self, name: &str) -> &mut RbConfig {
192 self.blocklist_link_arg.push(name.to_string());
193 self
194 }
195
196 pub fn ruby_version_slug(&self) -> String {
198 let ver = if let Some(progv) = self.get("RUBY_PROGRAM_VERSION") {
199 progv
200 } else if let Some(major_minor) = self.major_minor() {
201 format!(
202 "{}.{}.{}",
203 major_minor.0,
204 major_minor.1,
205 self.get("TEENY").unwrap_or_else(|| "0".to_string())
206 )
207 } else if let Some(fallback) = self.get("ruby_version") {
208 fallback
209 } else {
210 panic!("RUBY_PROGRAM_VERSION not found")
211 };
212
213 format!("{}-{}-{}", self.ruby_engine(), self.platform(), ver)
214 }
215
216 pub fn cppflags(&self) -> Vec<String> {
218 if let Some(cppflags) = self.get("CPPFLAGS") {
219 let flags = self.subst_shell_variables(&cppflags);
220 shellsplit(flags)
221 } else {
222 vec![]
223 }
224 }
225
226 pub fn is_cross_compiling(&self) -> bool {
228 if let Some(cross) = self.get("CROSS_COMPILING") {
229 cross == "yes" || cross == "1"
230 } else {
231 false
232 }
233 }
234
235 pub fn get(&self, key: &str) -> Option<String> {
238 self.try_rbconfig_env(key)
239 .or_else(|| self.try_value_map(key))
240 }
241
242 pub fn use_rpath(&mut self) -> &mut RbConfig {
244 self.use_rpath = true;
245 self
246 }
247
248 pub fn push_cflags(&mut self, cflags: &str) -> &mut Self {
250 for flag in shellsplit(cflags) {
251 if !self.cflags.contains(&flag) {
252 self.cflags.push(flag.to_string());
253 }
254 }
255
256 self
257 }
258
259 pub fn major_minor(&self) -> Option<(u32, u32)> {
261 let major = self.get("MAJOR").map(|v| v.parse::<u32>())?.ok()?;
262 let minor = self.get("MINOR").map(|v| v.parse::<u32>())?.ok()?;
263 Some((major, minor))
264 }
265
266 pub fn cargo_args(&self) -> Vec<String> {
268 let mut result = vec![];
269
270 let mut search_paths = vec![];
271
272 for search_path in &self.search_paths {
273 result.push(format!("cargo:rustc-link-search={}", search_path));
274 search_paths.push(search_path.name.as_str());
275 }
276
277 for lib in &self.libs {
278 if !self.blocklist_lib.iter().any(|b| lib.name.contains(b)) {
279 result.push(format!("cargo:rustc-link-lib={}", lib));
280 }
281
282 if self.use_rpath && !lib.is_static() {
283 result.push(format!("cargo:rustc-link-arg=-Wl,-rpath,{}", lib));
284 }
285 }
286
287 for link_arg in &self.link_args {
288 if !self.blocklist_link_arg.iter().any(|b| link_arg == b) {
289 result.push(format!("cargo:rustc-link-arg={}", link_arg));
290 }
291 }
292
293 result
294 }
295
296 pub fn print_cargo_args(&self) {
298 let cargo_args = self.cargo_args();
299
300 for arg in &cargo_args {
301 println!("{}", arg);
302 }
303
304 debug_log!("INFO: printing cargo args ({:?})", cargo_args);
305
306 let encoded_cargo_args = cargo_args.join("\x1E");
307 let encoded_cargo_args = encoded_cargo_args.replace('\n', "\x1F");
308
309 println!("cargo:encoded_cargo_args={}", encoded_cargo_args);
310 }
311
312 pub fn push_dldflags(&mut self, input: &str) -> &mut Self {
314 let input = self.subst_shell_variables(input);
315 let split_args = Flags::new(input.as_str());
316
317 let search_path_regex = Regex::new(r"^-L\s*(?P<name>.*)$").unwrap();
318 let lib_regex_short = Regex::new(r"^-l\s*(?P<name>\w+\S+)$").unwrap();
319 let lib_regex_long = Regex::new(r"^--library=(?P<name>\w+\S+)$").unwrap();
320 let dynamic_lib_regex = Regex::new(r"^-l\s*:lib(?P<name>\S+).(so|dylib|dll)$").unwrap();
321 let framework_regex_short = Regex::new(r"^-F\s*(?P<name>.*)$").unwrap();
322 let framework_regex_long = Regex::new(r"^-framework\s*(?P<name>.*)$").unwrap();
323
324 for arg in split_args {
325 let arg = arg.trim().to_owned();
326
327 if let Some(name) = capture_name(&search_path_regex, &arg) {
328 self.push_search_path(name.as_str());
329 } else if let Some(name) = capture_name(&lib_regex_long, &arg) {
330 self.push_library(name);
331 } else if let Some(name) = capture_name(&lib_regex_short, &arg) {
332 if name.contains("ruby") && name.contains("-static") {
333 self.push_library((LibraryKind::Static, name));
334 } else {
335 self.push_library(name);
336 }
337 } else if let Some(name) = capture_name(&dynamic_lib_regex, &arg) {
338 self.push_library((LibraryKind::Dylib, name));
339 } else if let Some(name) = capture_name(&framework_regex_short, &arg) {
340 self.push_search_path((SearchPathKind::Framework, name));
341 } else if let Some(name) = capture_name(&framework_regex_long, &arg) {
342 self.push_library((LibraryKind::Framework, name));
343 } else {
344 self.push_link_arg(arg);
345 }
346 }
347
348 self
349 }
350
351 pub fn set_value_for_key(&mut self, key: &str, value: String) {
353 self.value_map.insert(key.to_owned(), value);
354 }
355
356 pub fn has_ruby_dln_check_abi(&self) -> bool {
358 let Some((major, minor)) = self.major_minor() else {
359 return false;
360 };
361
362 let patchlevel = self
363 .get("PATCHLEVEL")
364 .and_then(|v| v.parse::<i32>().ok())
365 .unwrap_or(-1);
366
367 (major > 3 || (major == 3 && minor >= 2))
370 && patchlevel == -1
371 && !cfg!(target_family = "windows")
372 }
373
374 pub fn ruby_engine(&self) -> RubyEngine {
376 if let Some(engine) = self.get("ruby_install_name") {
377 match engine.as_str() {
378 "ruby" => RubyEngine::Mri,
379 "jruby" => RubyEngine::JRuby,
380 "truffleruby" => RubyEngine::TruffleRuby,
381 _ => RubyEngine::Mri, }
383 } else {
384 RubyEngine::Mri
385 }
386 }
387
388 fn subst_shell_variables(&self, input: &str) -> String {
390 let mut result = String::new();
391 let mut chars = input.chars().enumerate();
392
393 while let Some((_, c)) = chars.next() {
394 if c == '$' {
395 if let Some((i, c)) = chars.next() {
396 if c == '(' {
397 let start = i + 1;
398 let mut end = start;
399
400 for (i, c) in chars.by_ref() {
401 if c == ')' {
402 end = i;
403 break;
404 }
405 }
406
407 let key = &input[start..end];
408
409 if let Some(val) = self.get(key) {
410 result.push_str(&val);
411 } else if let Some(val) = env::var_os(key) {
412 result.push_str(&val.to_string_lossy());
413 } else {
414 chars.next();
416 }
417 } else {
418 result.push(c);
419 }
420 }
421 } else {
422 result.push(c);
423 }
424 }
425
426 result
427 }
428
429 pub fn have_ruby_header<T: AsRef<str>>(&self, header: T) -> bool {
430 let Some(ruby_include_dir) = self.get("rubyhdrdir") else {
431 return false;
432 };
433 PathBuf::from(ruby_include_dir)
434 .join(header.as_ref())
435 .exists()
436 }
437
438 fn push_search_path<T: Into<SearchPath>>(&mut self, path: T) -> &mut Self {
439 let path = path.into();
440
441 if !self.search_paths.contains(&path) {
442 self.search_paths.push(path);
443 }
444
445 self
446 }
447
448 fn push_library<T: Into<Library>>(&mut self, lib: T) -> &mut Self {
449 let lib = lib.into();
450
451 if !self.libs.contains(&lib) {
452 self.libs.push(lib);
453 }
454
455 self
456 }
457
458 fn push_link_arg<T: Into<String>>(&mut self, arg: T) -> &mut Self {
459 let arg = arg.into();
460
461 if !self.link_args.contains(&arg) {
462 self.link_args.push(arg);
463 }
464
465 self
466 }
467
468 fn try_value_map(&self, key: &str) -> Option<String> {
469 self.value_map
470 .get(key)
471 .map(|val| val.trim_matches('\n').to_owned())
472 }
473
474 fn try_rbconfig_env(&self, key: &str) -> Option<String> {
475 let key = format!("RBCONFIG_{}", key);
476 println!("cargo:rerun-if-env-changed={}", key);
477 env::var(key).map(|v| v.trim_matches('\n').to_owned()).ok()
478 }
479}
480
481#[derive(Debug, PartialEq, Eq, Clone, Copy)]
482pub enum RubyEngine {
483 Mri,
484 TruffleRuby,
485 JRuby,
486}
487
488impl std::fmt::Display for RubyEngine {
489 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
490 match self {
491 RubyEngine::Mri => write!(f, "mri"),
492 RubyEngine::TruffleRuby => write!(f, "truffleruby"),
493 RubyEngine::JRuby => write!(f, "jruby"),
494 }
495 }
496}
497
498fn capture_name(regex: &Regex, arg: &str) -> Option<String> {
499 regex
500 .captures(arg)
501 .map(|cap| cap.name("name").unwrap().as_str().trim().to_owned())
502}
503
504#[cfg(test)]
505mod tests {
506 use super::*;
507 use std::{sync::Mutex, vec};
508
509 lazy_static::lazy_static! {
510 static ref ENV_LOCK: Mutex<()> = Mutex::new(());
511 }
512
513 fn with_locked_env<F, T>(f: F) -> T
514 where
515 F: FnOnce() -> T,
516 {
517 let _guard = ENV_LOCK.lock().unwrap();
518 f()
519 }
520
521 #[test]
522 fn test_extract_lib_search_paths() {
523 let mut rb_config = RbConfig::new();
524 rb_config.push_dldflags("-L/usr/local/lib -L/usr/lib");
525 assert_eq!(
526 rb_config.search_paths,
527 vec!["/usr/local/lib".into(), "/usr/lib".into()]
528 );
529 }
530
531 #[test]
532 fn test_search_path_basic() {
533 let mut rb_config = RbConfig::new();
534 rb_config.push_dldflags("-L/usr/local/lib");
535
536 assert_eq!(rb_config.search_paths, vec!["native=/usr/local/lib".into()]);
537 }
538
539 #[test]
540 fn test_search_path_space() {
541 let mut rb_config = RbConfig::new();
542 rb_config.push_dldflags("-L /usr/local/lib");
543
544 assert_eq!(rb_config.search_paths, vec!["/usr/local/lib".into()]);
545 }
546
547 #[test]
548 fn test_search_path_space_in_path() {
549 let mut rb_config = RbConfig::new();
550 rb_config.push_dldflags("-L/usr/local/my lib");
551
552 assert_eq!(
553 rb_config.search_paths,
554 vec!["native=/usr/local/my lib".into()]
555 );
556 }
557
558 #[test]
559 fn test_simple_lib() {
560 let mut rb_config = RbConfig::new();
561 rb_config.push_dldflags("-lfoo");
562
563 assert_eq!(rb_config.libs, ["foo".into()]);
564 }
565
566 #[test]
567 fn test_lib_with_nonascii() {
568 let mut rb_config = RbConfig::new();
569 rb_config.push_dldflags("-lws2_32");
570
571 assert_eq!(rb_config.libs, ["ws2_32".into()]);
572 }
573
574 #[test]
575 fn test_simple_lib_space() {
576 let mut rb_config = RbConfig::new();
577 rb_config.push_dldflags("-l foo");
578
579 assert_eq!(rb_config.libs, ["foo".into()]);
580 }
581
582 #[test]
583 fn test_verbose_lib_space() {
584 let mut rb_config = RbConfig::new();
585 rb_config.push_dldflags("--library=foo");
586
587 assert_eq!(rb_config.libs, ["foo".into()]);
588 }
589
590 #[test]
591 fn test_dylib_with_colon_space() {
592 let mut rb_config = RbConfig::new();
593 rb_config.push_dldflags("-l :libssp.dylib");
594
595 assert_eq!(rb_config.libs, ["dylib=ssp".into()]);
596 }
597
598 #[test]
599 fn test_so_with_colon_space() {
600 let mut rb_config = RbConfig::new();
601 rb_config.push_dldflags("-l :libssp.so");
602
603 assert_eq!(rb_config.libs, ["dylib=ssp".into()]);
604 }
605
606 #[test]
607 fn test_dll_with_colon_space() {
608 let mut rb_config = RbConfig::new();
609 rb_config.push_dldflags("-l :libssp.dll");
610
611 assert_eq!(rb_config.libs, ["dylib=ssp".into()]);
612 }
613
614 #[test]
615 fn test_framework() {
616 let mut rb_config = RbConfig::new();
617 rb_config.push_dldflags("-F/some/path");
618
619 assert_eq!(rb_config.search_paths, ["framework=/some/path".into()]);
620 }
621
622 #[test]
623 fn test_framework_space() {
624 let mut rb_config = RbConfig::new();
625 rb_config.push_dldflags("-F /some/path");
626
627 assert_eq!(
628 rb_config.search_paths,
629 [SearchPath {
630 kind: SearchPathKind::Framework,
631 name: "/some/path".into(),
632 }]
633 );
634 }
635
636 #[test]
637 fn test_framework_arg_real() {
638 let mut rb_config = RbConfig::new();
639 rb_config.push_dldflags("-framework CoreFoundation");
640
641 assert_eq!(
642 rb_config.libs,
643 [Library {
644 kind: LibraryKind::Framework,
645 name: "CoreFoundation".into(),
646 }]
647 );
648 }
649
650 #[test]
651 fn test_libruby_static() {
652 let mut rb_config = RbConfig::new();
653 rb_config.push_dldflags("-lruby.3.1-static");
654
655 assert_eq!(
656 rb_config.cargo_args(),
657 ["cargo:rustc-link-lib=static=ruby.3.1-static"]
658 );
659 }
660
661 #[test]
662 fn test_libruby_dynamic() {
663 let mut rb_config = RbConfig::new();
664 rb_config.push_dldflags("-lruby.3.1");
665
666 assert_eq!(rb_config.cargo_args(), ["cargo:rustc-link-lib=ruby.3.1"]);
667 }
668
669 #[test]
670 fn test_non_lib_dash_l() {
671 let mut rb_config = RbConfig::new();
672 rb_config.push_dldflags("test_rubygems_20220413-976-lemgf9/prefix");
673
674 assert_eq!(
675 rb_config.link_args,
676 vec!["test_rubygems_20220413-976-lemgf9/prefix"]
677 );
678 }
679
680 #[test]
681 fn test_real_dldflags() {
682 let mut rb_config = RbConfig::new();
683 rb_config.push_dldflags("-L/Users/ianks/.asdf/installs/ruby/3.1.1/lib -L/opt/homebrew/opt/openssl@1.1/lib -Wl,-undefined,dynamic_lookup -Wl,-multiply_defined,suppress");
684
685 assert_eq!(
686 rb_config.link_args,
687 vec![
688 "-Wl,-undefined,dynamic_lookup",
689 "-Wl,-multiply_defined,suppress"
690 ]
691 );
692 assert_eq!(
693 rb_config.search_paths,
694 vec![
695 SearchPath {
696 kind: SearchPathKind::Native,
697 name: "/Users/ianks/.asdf/installs/ruby/3.1.1/lib".to_string()
698 },
699 SearchPath {
700 kind: SearchPathKind::Native,
701 name: "/opt/homebrew/opt/openssl@1.1/lib".to_string()
702 },
703 ]
704 );
705 }
706
707 #[test]
708 fn test_crazy_cases() {
709 let mut rb_config = RbConfig::new();
710 rb_config.push_dldflags("-F /something -l:libssp.a -static-libgcc ");
711
712 assert_eq!(rb_config.link_args, vec!["-l:libssp.a", "-static-libgcc"]);
713 assert_eq!(
714 rb_config.search_paths,
715 vec![SearchPath {
716 kind: SearchPathKind::Framework,
717 name: "/something".to_string()
718 },]
719 );
720 }
721
722 #[test]
723 fn test_printing_cargo_args() {
724 let mut rb_config = RbConfig::new();
725 rb_config.push_dldflags("-L/Users/ianks/.asdf/installs/ruby/3.1.1/lib");
726 rb_config.push_dldflags("-lfoo");
727 rb_config.push_dldflags("-static-libgcc");
728 let result = rb_config.cargo_args();
729
730 assert_eq!(
731 vec![
732 "cargo:rustc-link-search=native=/Users/ianks/.asdf/installs/ruby/3.1.1/lib",
733 "cargo:rustc-link-lib=foo",
734 "cargo:rustc-link-arg=-static-libgcc"
735 ],
736 result
737 );
738 }
739
740 #[test]
741 fn test_use_rpath() {
742 let mut rb_config = RbConfig::new();
743 rb_config.push_dldflags("-lfoo");
744
745 assert_eq!(vec!["cargo:rustc-link-lib=foo"], rb_config.cargo_args());
746
747 rb_config.use_rpath();
748
749 assert_eq!(
750 vec![
751 "cargo:rustc-link-lib=foo",
752 "cargo:rustc-link-arg=-Wl,-rpath,foo"
753 ],
754 rb_config.cargo_args()
755 );
756 }
757
758 #[test]
759 fn test_link_mswin() {
760 with_locked_env(|| {
761 let old_var = env::var("TARGET").ok();
762 env::set_var("TARGET", "x86_64-pc-windows-msvc");
763
764 let mut rb_config = RbConfig::new();
765 rb_config.set_value_for_key("LIBRUBYARG_SHARED", "x64-vcruntime140-ruby320.lib".into());
766 rb_config.set_value_for_key("libdir", "D:/ruby-mswin/lib".into());
767 rb_config.set_value_for_key("LIBS", "user32.lib".into());
768 rb_config.link_ruby(false);
769
770 assert_eq!(
771 vec![
772 "cargo:rustc-link-search=native=D:/ruby-mswin/lib",
773 "cargo:rustc-link-lib=x64-vcruntime140-ruby320",
774 "cargo:rustc-link-lib=user32",
775 ],
776 rb_config.cargo_args()
777 );
778
779 if let Some(old_var) = old_var {
780 env::set_var("TARGET", old_var);
781 } else {
782 env::remove_var("TARGET");
783 }
784 })
785 }
786
787 #[test]
788 fn test_link_static() {
789 with_locked_env(|| {
790 let mut rb_config = RbConfig::new();
791 rb_config.set_value_for_key("LIBRUBYARG_STATIC", "-lruby-static".into());
792 rb_config.set_value_for_key("libdir", "/opt/ruby".into());
793
794 rb_config.link_ruby(true);
795
796 assert_eq!(
797 vec![
798 "cargo:rustc-link-search=native=/opt/ruby",
799 "cargo:rustc-link-lib=static=ruby-static",
800 ],
801 rb_config.cargo_args()
802 );
803 });
804 }
805
806 #[test]
807 fn test_prioritizes_rbconfig_env() {
808 with_locked_env(|| {
809 env::set_var("RBCONFIG_libdir", "/foo");
810 let rb_config = RbConfig::new();
811
812 assert_eq!(rb_config.get("libdir"), Some("/foo".into()));
813
814 env::remove_var("RBCONFIG_libdir");
815 });
816 }
817
818 #[test]
819 fn test_never_loads_shell_rbconfig_if_cross_compiling() {
820 with_locked_env(|| {
821 env::set_var("RBCONFIG_CROSS_COMPILING", "yes");
822
823 let rb_config = RbConfig::current();
824
825 assert!(rb_config.value_map.is_empty());
826 });
827 }
828
829 #[test]
830 fn test_loads_shell_rbconfig_if_not_cross_compiling() {
831 with_locked_env(|| {
832 env::set_var("RBCONFIG_CROSS_COMPILING", "no");
833
834 let rb_config = RbConfig::current();
835
836 assert!(!rb_config.value_map.is_empty());
837 });
838 }
839
840 #[test]
841 fn test_libstatic() {
842 let mut rb_config = RbConfig::new();
843 rb_config.push_dldflags("-l:libssp.a");
844
845 assert_eq!(rb_config.link_args, ["-l:libssp.a".to_string()]);
846 }
847
848 #[test]
849 fn test_link_arg_blocklist() {
850 let mut rb_config = RbConfig::new();
851 rb_config.blocklist_link_arg("-Wl,--compress-debug-sections=zlib");
852 rb_config.blocklist_link_arg("-s");
853 rb_config.push_dldflags(
854 "-lfoo -Wl,--compress-debug-sections=zlib -s -somethingthatshouldnotbeblocked",
855 );
856
857 assert_eq!(
858 vec![
859 "cargo:rustc-link-lib=foo",
860 "cargo:rustc-link-arg=-somethingthatshouldnotbeblocked"
861 ],
862 rb_config.cargo_args()
863 );
864 }
865
866 #[test]
867 fn test_has_ruby_dln_check_abi() {
868 fn make_config(major: &str, minor: &str, patchlevel: &str) -> RbConfig {
870 let mut rb_config = RbConfig::new();
871 rb_config.set_value_for_key("MAJOR", major.into());
872 rb_config.set_value_for_key("MINOR", minor.into());
873 rb_config.set_value_for_key("PATCHLEVEL", patchlevel.into());
874 rb_config
875 }
876
877 assert!(!make_config("3", "1", "-1").has_ruby_dln_check_abi());
879 assert!(!make_config("3", "1", "0").has_ruby_dln_check_abi());
880
881 #[cfg(not(target_family = "windows"))]
883 assert!(make_config("3", "2", "-1").has_ruby_dln_check_abi());
884
885 assert!(!make_config("3", "2", "0").has_ruby_dln_check_abi());
887
888 #[cfg(not(target_family = "windows"))]
890 assert!(make_config("3", "3", "-1").has_ruby_dln_check_abi());
891
892 #[cfg(not(target_family = "windows"))]
894 assert!(make_config("4", "0", "-1").has_ruby_dln_check_abi());
895
896 assert!(!make_config("4", "0", "0").has_ruby_dln_check_abi());
898
899 #[cfg(not(target_family = "windows"))]
901 assert!(make_config("4", "1", "-1").has_ruby_dln_check_abi());
902
903 assert!(!make_config("2", "7", "-1").has_ruby_dln_check_abi());
905 }
906}