rb-sys-build-0.9.97/.cargo_vcs_info.json0000644000000001610000000000100134540ustar { "git": { "sha1": "71a214816e4bd7dfc5620ae3dd07b4944d531dfb" }, "path_in_vcs": "crates/rb-sys-build" }rb-sys-build-0.9.97/Cargo.toml0000644000000026000000000000100114520ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2018" rust-version = "1.63" name = "rb-sys-build" version = "0.9.97" description = "Build system for rb-sys" homepage = "https://github.com/oxidize-rb/rb-sys" license = "MIT OR Apache-2.0" repository = "https://github.com/oxidize-rb/rb-sys" [lib] doctest = false bench = false [dependencies.bindgen] version = "0.69" features = ["runtime"] default-features = false [dependencies.lazy_static] version = "1.4.0" [dependencies.proc-macro2] version = "1.0" [dependencies.quote] version = "1.0" [dependencies.regex] version = "1" [dependencies.shell-words] version = "1.1" [dependencies.syn] version = "2.0" features = [ "parsing", "full", "extra-traits", ] [features] bindgen-deprecated-types = [] bindgen-enable-function-attribute-detection = [] bindgen-impl-debug = [] bindgen-layout-tests = [] bindgen-rbimpls = [] bindgen-return-const-encoding-pointers = [] bindgen-sizet-is-usize = [] default = [] rb-sys-build-0.9.97/Cargo.toml.orig000064400000000000000000000014451046102023000151410ustar 00000000000000[package] name = "rb-sys-build" version = "0.9.97" edition = "2018" description = "Build system for rb-sys" homepage = "https://github.com/oxidize-rb/rb-sys" license = "MIT OR Apache-2.0" repository = "https://github.com/oxidize-rb/rb-sys" rust-version = "1.63" [lib] bench = false doctest = false [dependencies] regex = "1" shell-words = "1.1" bindgen = { version = "0.69", default-features = false, features = ["runtime"] } syn = { version = "2.0", features = ["parsing", "full", "extra-traits"] } quote = "1.0" lazy_static = "1.4.0" proc-macro2 = "1.0" [features] default = [] bindgen-rbimpls = [] bindgen-deprecated-types = [] bindgen-layout-tests = [] bindgen-impl-debug = [] bindgen-sizet-is-usize = [] bindgen-return-const-encoding-pointers = [] bindgen-enable-function-attribute-detection = [] rb-sys-build-0.9.97/LICENSE-APACHE000064400000000000000000000250141046102023000141740ustar 00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS Copyright 2021-2022 Ian Ker-Seymer Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. rb-sys-build-0.9.97/LICENSE-MIT000064400000000000000000000020761046102023000137070ustar 00000000000000The MIT License (MIT) Copyright (c) 2021-2022 Ian Ker-Seymer Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. rb-sys-build-0.9.97/src/bindings/sanitizer.rs000064400000000000000000000143721046102023000172170ustar 00000000000000use regex::Regex; use std::{ borrow::{Borrow, Cow}, error::Error, }; use syn::{Attribute, Expr, Item, Lit, LitStr, Meta}; lazy_static::lazy_static! { static ref URL_REGEX: Regex = Regex::new(r#"https?://[^\s'"]+"#).unwrap(); static ref DOC_SECTION_REGEX: Regex = Regex::new(r"^@(warning|internal|private|note)\s*").unwrap(); static ref PARAM_DIRECTIVE_REGEX: Regex = Regex::new(r"^(@\w+)\[(\S+)\]\s+(.*)$").unwrap(); static ref OTHER_DIRECTIVE_REGEX: Regex = Regex::new(r"^(@\w+)\s+(.*)$").unwrap(); static ref BARE_CODE_REF_REGEX: Regex = Regex::new(r"(\b)(rb_(\w|_)+)(\(|\))*").unwrap(); } /// Append a link directive to each foreign module to the given syntax tree. pub fn add_link_ruby_directives( syntax: &mut syn::File, link_name: &str, kind: &str, ) -> Result<(), Box> { for item in syntax.items.iter_mut() { if let Item::ForeignMod(fmod) = item { fmod.attrs.push(syn::parse_quote! { #[link(name = #link_name, kind = #kind)] }); } } Ok(()) } /// Converts all `*const rb_encoding` and `*const OnigEncodingTypeST` to *mut /// _` to keep backwards compatibility with bindgen < 0.62. pub fn ensure_backwards_compatible_encoding_pointers(syntax: &mut syn::File) { for item in syntax.items.iter_mut() { if let Item::ForeignMod(fmod) = item { for item in fmod.items.iter_mut() { if let syn::ForeignItem::Fn(f) = item { if let syn::ReturnType::Type(_, ty) = &mut f.sig.output { if let syn::Type::Ptr(ptr) = &mut **ty { if let syn::Type::Path(path) = &*ptr.elem { if path.path.segments.len() == 1 && path.path.segments[0].ident == "OnigEncodingTypeST" || path.path.segments[0].ident == "rb_encoding" { ptr.mutability = Some(syn::token::Mut::default()); } } } } } } } } } /// Turn the cruby comments into rustdoc comments. pub fn cleanup_docs(syntax: &mut syn::File, ruby_version: &str) -> Result<(), Box> { let footer = doc_footer(ruby_version); for item in syntax.items.iter_mut() { match item { Item::ForeignMod(fmod) => { for item in fmod.items.iter_mut() { match item { syn::ForeignItem::Fn(f) => clean_doc_attrs(&mut f.attrs, &footer), syn::ForeignItem::Static(s) => clean_doc_attrs(&mut s.attrs, &footer), syn::ForeignItem::Type(s) => clean_doc_attrs(&mut s.attrs, &footer), _ => {} } } } Item::Type(t) => clean_doc_attrs(&mut t.attrs, &footer), Item::Struct(s) => { clean_doc_attrs(&mut s.attrs, &footer); for f in s.fields.iter_mut() { clean_doc_attrs(&mut f.attrs, &footer); } } Item::Enum(e) => { clean_doc_attrs(&mut e.attrs, &footer); for v in e.variants.iter_mut() { clean_doc_attrs(&mut v.attrs, &footer); } } Item::Union(u) => { clean_doc_attrs(&mut u.attrs, &footer); for u in u.fields.named.iter_mut() { clean_doc_attrs(&mut u.attrs, &footer); } } _ => {} } } Ok(()) } fn clean_doc_line(attr: &mut Attribute) -> bool { if !attr.path().is_ident("doc") { return false; } let mut deprecated: bool = false; if let Meta::NameValue(name_value) = &mut attr.meta { if let Expr::Lit(expr_lit) = &mut name_value.value { if let Lit::Str(lit_str) = &mut expr_lit.lit { let cleaned = lit_str.value(); let cleaned = cleaned.trim_matches('"').trim(); let cleaned = URL_REGEX.replace_all(cleaned, "<${0}>"); let cleaned = DOC_SECTION_REGEX.replace_all(&cleaned, |captures: ®ex::Captures| { if let Some(header) = captures.get(1) { format!("---\n ### {}\n", capitalize(header.as_str())).into() } else { Cow::Borrowed("") } }); let cleaned = PARAM_DIRECTIVE_REGEX.replace(&cleaned, "- **$1** `$2` $3"); let cleaned = OTHER_DIRECTIVE_REGEX.replace(&cleaned, "- **$1** $2"); let mut cleaned = BARE_CODE_REF_REGEX.replace_all(&cleaned, "${1}[`${2}`]"); if cleaned.is_empty() { cleaned = "\n".into(); } if cleaned.contains("@deprecated") { deprecated = true; } *lit_str = LitStr::new(cleaned.borrow(), lit_str.span()); } } } deprecated } fn clean_doc_attrs(attrs: &mut Vec, footer: &str) { let mut deprecated: bool = false; for attr in attrs.iter_mut() { if clean_doc_line(attr) { deprecated = true; }; } attrs.push(syn::parse_quote! { #[doc = #footer] }); if deprecated { attrs.push(syn::parse_quote! { #[deprecated] }) } } fn doc_footer(ruby_version: &str) -> String { format!( "\n---\n\nGenerated by [rb-sys]({}) for Ruby {}", env!("CARGO_PKG_REPOSITORY"), ruby_version ) } fn capitalize(input: &str) -> Cow<'_, str> { if let Some(first) = input.chars().next() { if first.is_ascii_uppercase() && input[1..].chars().all(|c| c.is_ascii_lowercase()) { Cow::Borrowed(input) } else { let mut result = String::with_capacity(input.len()); result.push(first.to_ascii_uppercase()); result.push_str(&input[1..].to_ascii_lowercase()); Cow::Owned(result) } } else { Cow::Borrowed(input) } } rb-sys-build-0.9.97/src/bindings/stable_api.rs000064400000000000000000000173541046102023000173150ustar 00000000000000use std::vec; use quote::ToTokens; use crate::RbConfig; const OPAQUE_STRUCTS: [&str; 2] = ["RString", "RArray"]; const OPAQUE_STRUCTS_RUBY_3_3: [&str; 3] = [ "rb_matchext_struct", "rb_internal_thread_event_data", "rb_io_internal_buffer", ]; /// Generate opaque structs for the given bindings. pub fn opaqueify_bindings( rbconfig: &RbConfig, bindings: bindgen::Builder, wrapper_h: &mut String, ) -> bindgen::Builder { let version_specific_opaque_structs = get_version_specific_opaque_structs(rbconfig.major_minor()); let structs_to_opaque = OPAQUE_STRUCTS .iter() .chain(&version_specific_opaque_structs); structs_to_opaque.fold(bindings, |bindings, name| { gen_opaque_struct(bindings, name, wrapper_h) }) } /// Categorize all bindings into stable, unstable, and internal. pub fn categorize_bindings(syntax: &mut syn::File) { let mut normal_items = Vec::new(); let mut unstable_items = Vec::new(); let mut internal_items = Vec::new(); let mut excluded_items = Vec::new(); let mut opaque_items = Vec::new(); let mut opaque_idents_to_swap = Vec::new(); for item in syntax.items.iter_mut() { if let syn::Item::Struct(s) = item { if s.ident.to_string().contains("rb_sys__Opaque__") { let new_name = s.ident.to_string().replace("rb_sys__Opaque__", ""); s.ident = syn::Ident::new(&new_name, s.ident.span()); opaque_idents_to_swap.push(new_name); opaque_items.push(item.clone()); } else { normal_items.push(item.clone()); } } else if let syn::Item::Type(t) = item { if t.ident.to_string().contains("rb_sys__Opaque__") { let new_name = t.ident.to_string().replace("rb_sys__Opaque__", ""); t.ident = syn::Ident::new(&new_name, t.ident.span()); opaque_idents_to_swap.push(new_name); opaque_items.push(item.clone()); } else { normal_items.push(item.clone()); } } else { if let syn::Item::Fn(ref mut f) = item { if f.sig.ident.to_string().contains("bindgen_test_") { let body = &mut f.block; let code = body.clone().to_token_stream().to_string(); let new_code = code.replace("rb_sys__Opaque__", "super::stable::"); let new_code = syn::parse_str::(&new_code).unwrap(); *body = syn::parse_quote! { { #[allow(unused)] use super::internal::*; #new_code; } }; } } normal_items.push(item.clone()); } } for item in normal_items.iter_mut() { if let syn::Item::Type(ref mut t) = item { if let Ok(syn::Type::Path(ref mut type_path)) = syn::parse2::(t.ty.to_token_stream()) { if opaque_idents_to_swap.contains(&type_path.path.segments[0].ident.to_string()) { let new_ident = syn::Ident::new( &type_path.path.segments[0].ident.to_string(), type_path.path.segments[0].ident.span(), ); t.ty = syn::parse_quote! { crate::internal::#new_ident }; } } } } for mut item in normal_items { if let syn::Item::Struct(s) = &mut item { if opaque_idents_to_swap.contains(&s.ident.to_string()) { internal_items.push(syn::Item::Struct(s.clone())); s.attrs.push(syn::parse_quote! { #[deprecated(note = "To improve API stability with ruby-head, direct usage of Ruby internal structs has been deprecated. To migrate, please replace the usage of this internal struct with its counterpart in the `rb_sys::stable` module. For example, instead of `use rb_sys::rb_sys__Opaque__ExampleStruct;`, use `use rb_sys::stable::ExampleStruct;`. If you need to access the internals of these items, you can use the provided `rb-sys::macros` instead.")] }); unstable_items.push(item); } else { excluded_items.push(item); } } else if let syn::Item::Type(t) = &mut item { if opaque_idents_to_swap.contains(&t.ident.to_string()) { internal_items.push(syn::Item::Type(t.clone())); t.attrs.push(syn::parse_quote! { #[deprecated(note = "To improve API stability with ruby-head, direct usage of Ruby internal structs has been deprecated. To migrate, please replace the usage of this internal struct with its counterpart in the `rb_sys::stable` module. For example, instead of `use rb_sys::rb_sys__Opaque__ExampleStruct;`, use `use rb_sys::stable::ExampleStruct;`. If you need to access the internals of these items, you can use the provided `rb-sys::macros` instead.")] }); unstable_items.push(item); } else { excluded_items.push(item); } } else { excluded_items.push(item); } } // Perform a pass on all std::fmt::Debug implementations to fully qualify opaque structs for item in excluded_items.iter_mut() { if let syn::Item::Impl(ref mut impl_item) = item { if let Some((_, syn::Path { segments, .. }, _)) = impl_item.trait_.as_ref() { if segments.iter().any(|segment| segment.ident == "Debug") { if let syn::Type::Path(ref mut path) = *impl_item.self_ty { if opaque_idents_to_swap.contains(&path.to_token_stream().to_string()) { *impl_item.self_ty = syn::parse_quote! { crate::internal::#path }; } } } } } } *syntax = syn::parse_quote! { /// Contains all items that are not yet categorized by ABI stability. /// These items are candidates for promotion to `stable` or `unstable` /// in the future. pub mod uncategorized { #(#excluded_items)* } /// Contains all items that are considered unstable ABI and should be /// avoided. Any items in this list offer a stable alternative for most /// use cases. pub mod unstable { use super::uncategorized::*; #(#unstable_items)* } /// Contains all items that are considered stable ABI and are safe to /// use. These items are intentionally opaque to prevent accidental /// compatibility issues. /// /// If you need to access the internals of these items, please open an /// issue. pub mod stable { #(#opaque_items)* } /// Unstable items for usage internally in rb_sys to avoid deprecated warnings. pub (crate) mod internal { use super::uncategorized::*; #(#internal_items)* } }; } fn gen_opaque_struct( bindings: bindgen::Builder, name: &str, wrapper_h: &mut String, ) -> bindgen::Builder { let struct_name = format!("rb_sys__Opaque__{}", name); wrapper_h.push_str(&format!( "struct {} {{ struct {} dummy; }};\n", struct_name, name )); bindings .opaque_type(&struct_name) .allowlist_type(struct_name) } fn get_version_specific_opaque_structs(major_minor: (u32, u32)) -> Vec<&'static str> { let mut result = vec![]; let (major, minor) = major_minor; if major == 3 && minor >= 3 { result.extend(OPAQUE_STRUCTS_RUBY_3_3) } result } rb-sys-build-0.9.97/src/bindings/wrapper.h000064400000000000000000000025331046102023000164660ustar 00000000000000#include "ruby.h" #ifdef HAVE_RUBY_DEBUG_H #include "ruby/debug.h" #endif #ifdef HAVE_RUBY_DEFINES_H #include "ruby/defines.h" #endif #ifdef HAVE_RUBY_ENCODING_H #include "ruby/encoding.h" #endif #ifdef HAVE_RUBY_FIBER_SCHEDULER_H #include "ruby/fiber/scheduler.h" #endif #ifdef HAVE_RUBY_INTERN_H #include "ruby/intern.h" #endif #ifdef HAVE_RUBY_IO_H #include "ruby/io.h" #endif #ifdef HAVE_RUBY_MEMORY_VIEW_H #include "ruby/memory_view.h" #endif #ifdef HAVE_RUBY_MISSING_H #include "ruby/missing.h" #endif #ifdef HAVE_RUBY_ONIGMO_H #include "ruby/onigmo.h" #endif #ifdef HAVE_RUBY_ONIGURUMA_H #include "ruby/oniguruma.h" #endif #ifdef HAVE_RUBY_RACTOR_H #include "ruby/ractor.h" #endif #ifdef HAVE_RUBY_RANDOM_H #include "ruby/random.h" #endif #ifdef HAVE_RUBY_RE_H #include "ruby/re.h" #endif #ifdef HAVE_RUBY_REGEX_H #include "ruby/regex.h" #endif #ifdef HAVE_RUBY_RUBY_H #include "ruby/ruby.h" #endif #ifdef HAVE_RUBY_ST_H #include "ruby/st.h" #endif #ifdef HAVE_RUBY_THREAD_H #include "ruby/thread.h" #endif #ifdef HAVE_RUBY_THREAD_NATIVE_H #include "ruby/thread_native.h" #endif #ifdef HAVE_RUBY_UTIL_H #include "ruby/util.h" #endif #ifdef HAVE_RUBY_VERSION_H #include "ruby/version.h" #endif #ifdef HAVE_RUBY_VM_H #include "ruby/vm.h" #endif #ifdef HAVE_RUBY_WIN32_H #include "ruby/win32.h" #endif #ifdef HAVE_RUBY_IO_BUFFER_H #include "ruby/io/buffer.h" #endif rb-sys-build-0.9.97/src/bindings.rs000064400000000000000000000171511046102023000152050ustar 00000000000000mod sanitizer; mod stable_api; use crate::cc::Build; use crate::utils::is_msvc; use crate::{debug_log, RbConfig}; use quote::ToTokens; use std::fs::File; use std::io::Write; use std::path::{Path, PathBuf}; use std::{env, error::Error}; use syn::{Expr, ExprLit, ItemConst, Lit}; const WRAPPER_H_CONTENT: &str = include_str!("bindings/wrapper.h"); /// Generate bindings for the Ruby using bindgen. pub fn generate( rbconfig: &RbConfig, static_ruby: bool, cfg_out: &mut File, ) -> Result> { let out_dir = PathBuf::from(env::var("OUT_DIR")?); let mut clang_args = vec![ format!("-I{}", rbconfig.get("rubyhdrdir")), format!("-I{}", rbconfig.get("rubyarchhdrdir")), ]; clang_args.extend(Build::default_cflags()); clang_args.extend(rbconfig.cflags.clone()); clang_args.extend(rbconfig.cppflags()); debug_log!("INFO: using bindgen with clang args: {:?}", clang_args); let mut wrapper_h = WRAPPER_H_CONTENT.to_string(); if !is_msvc() { wrapper_h.push_str("#ifdef HAVE_RUBY_ATOMIC_H\n"); wrapper_h.push_str("#include \"ruby/atomic.h\"\n"); wrapper_h.push_str("#endif\n"); } if rbconfig.have_ruby_header("ruby/io/buffer.h") { clang_args.push("-DHAVE_RUBY_IO_BUFFER_H".to_string()); } let bindings = default_bindgen(clang_args) .allowlist_file(".*ruby.*") .blocklist_item("ruby_abi_version") .blocklist_function("^__.*") .blocklist_item("RData") .parse_callbacks(Box::new(bindgen::CargoCallbacks::new())); let bindings = if cfg!(feature = "bindgen-rbimpls") { bindings } else { bindings .blocklist_item("^rbimpl_.*") .blocklist_item("^RBIMPL_.*") }; let bindings = if cfg!(feature = "bindgen-deprecated-types") { bindings } else { bindings .blocklist_item("^ruby_fl_type.*") .blocklist_item("^_bindgen_ty_9.*") }; let bindings = stable_api::opaqueify_bindings(rbconfig, bindings, &mut wrapper_h); let mut tokens = { write!(std::io::stderr(), "{}", wrapper_h)?; let bindings = bindings.header_contents("wrapper.h", &wrapper_h); let code_string = bindings.generate()?.to_string(); syn::parse_file(&code_string)? }; let ruby_version = rbconfig.ruby_program_version(); let ruby_platform = rbconfig.platform(); let crate_version = env!("CARGO_PKG_VERSION"); let out_path = out_dir.join(format!( "bindings-{}-{}-{}.rs", crate_version, ruby_platform, ruby_version )); let code = { sanitizer::ensure_backwards_compatible_encoding_pointers(&mut tokens); clean_docs(rbconfig, &mut tokens); if is_msvc() { qualify_symbols_for_msvc(&mut tokens, static_ruby, rbconfig); } push_cargo_cfg_from_bindings(&tokens, cfg_out)?; stable_api::categorize_bindings(&mut tokens); tokens.into_token_stream().to_string() }; let mut out_file = File::create(&out_path)?; std::io::Write::write_all(&mut out_file, code.as_bytes())?; run_rustfmt(&out_path); Ok(out_path) } fn run_rustfmt(path: &Path) { let mut cmd = std::process::Command::new("rustfmt"); cmd.stderr(std::process::Stdio::inherit()); cmd.stdout(std::process::Stdio::inherit()); cmd.arg(path); if let Err(e) = cmd.status() { debug_log!("WARN: failed to run rustfmt: {}", e); } } fn clean_docs(rbconfig: &RbConfig, syntax: &mut syn::File) { if rbconfig.is_cross_compiling() { return; } let ver = rbconfig.ruby_program_version(); sanitizer::cleanup_docs(syntax, &ver).unwrap_or_else(|e| { debug_log!("WARN: failed to clean up docs, skipping: {}", e); }) } fn default_bindgen(clang_args: Vec) -> bindgen::Builder { let bindings = bindgen::Builder::default() .rustified_enum(".*") .no_copy("rb_data_type_struct") .derive_eq(true) .derive_debug(true) .clang_args(clang_args) .layout_tests(cfg!(feature = "bindgen-layout-tests")) .blocklist_item("^__darwin_pthread.*") .blocklist_item("^_opaque_pthread.*") .blocklist_item("^pthread_.*") .blocklist_item("^rb_native.*") .opaque_type("^__sFILE$") .merge_extern_blocks(true) .generate_comments(true) .size_t_is_usize(env::var("CARGO_FEATURE_BINDGEN_SIZE_T_IS_USIZE").is_ok()) .impl_debug(cfg!(feature = "bindgen-impl-debug")) .parse_callbacks(Box::new(bindgen::CargoCallbacks::new())); if env::var("CARGO_FEATURE_BINDGEN_ENABLE_FUNCTION_ATTRIBUTE_DETECTION").is_ok() { bindings.enable_function_attribute_detection() } else { bindings } } // This is needed because bindgen doesn't support the `__declspec(dllimport)` on // global variables. Without it, symbols are not found. // See https://stackoverflow.com/a/66182704/2057700 fn qualify_symbols_for_msvc(tokens: &mut syn::File, is_static: bool, rbconfig: &RbConfig) { let kind = if is_static { "static" } else { "dylib" }; let name = if is_static { rbconfig.libruby_static_name() } else { rbconfig.libruby_so_name() }; sanitizer::add_link_ruby_directives(tokens, &name, kind).unwrap_or_else(|e| { debug_log!("WARN: failed to add link directives: {}", e); }); } // Add things like `#[cfg(ruby_use_transient_heap = "true")]` to the bindings config fn push_cargo_cfg_from_bindings( syntax: &syn::File, cfg_out: &mut File, ) -> Result<(), Box> { fn is_defines(line: &str) -> bool { line.starts_with("HAVE_RUBY") || line.starts_with("HAVE_RB") || line.starts_with("USE") || line.starts_with("RUBY_DEBUG") || line.starts_with("RUBY_NDEBUG") } for item in syntax.items.iter() { if let syn::Item::Const(item) = item { let conf = ConfValue::new(item); let conf_name = conf.name(); if is_defines(&conf_name) { let name = conf_name.to_lowercase(); let val = conf.value_bool().to_string(); println!("cargo:rustc-cfg=ruby_{}=\"{}\"", name, val); println!("cargo:defines_{}={}", name, val); writeln!(cfg_out, "cargo:defines_{}={}", name, val)?; } if conf_name.starts_with("RUBY_ABI_VERSION") { println!("cargo:ruby_abi_version={}", conf.value_string()); writeln!(cfg_out, "cargo:ruby_abi_version={}", conf.value_string())?; } } } Ok(()) } /// An autoconf constant in the bindings struct ConfValue<'a> { item: &'a syn::ItemConst, } impl<'a> ConfValue<'a> { pub fn new(item: &'a ItemConst) -> Self { Self { item } } pub fn name(&self) -> String { self.item.ident.to_string() } pub fn value_string(&self) -> String { match &*self.item.expr { Expr::Lit(ExprLit { lit, .. }) => lit.to_token_stream().to_string(), _ => panic!( "Could not convert HAVE_* constant to string: {:#?}", self.item ), } } pub fn value_bool(&self) -> bool { match &*self.item.expr { Expr::Lit(ExprLit { lit: Lit::Int(ref lit), .. }) => lit.base10_parse::().unwrap() != 0, Expr::Lit(ExprLit { lit: Lit::Bool(ref lit), .. }) => lit.value, _ => panic!( "Could not convert HAVE_* constant to bool: {:#?}", self.item ), } } } rb-sys-build-0.9.97/src/cc.rs000064400000000000000000000242611046102023000137750ustar 00000000000000use crate::{ debug_log, rb_config, utils::{is_msvc, shellsplit}, }; use std::{ collections::{hash_map::DefaultHasher, HashSet}, env, ffi::{OsStr, OsString}, fs, hash::Hasher, path::{Path, PathBuf}, process::{Command, ExitStatus, Stdio}, }; type Result = std::result::Result>; const WELL_KNOWN_WRAPPERS: &[&str] = &["sccache", "cachepot"]; #[derive(Default, Debug)] pub struct Build { files: Vec, flags: Vec, } impl Build { pub fn new() -> Self { Self::default() } pub fn default_cflags() -> Vec { let mut cflags = vec![]; if cfg!(target_os = "openbsd") { cflags.push("-fdeclspec".into()); } else { cflags.push("-fms-extensions".into()); }; cflags } pub fn file(&mut self, file: PathBuf) { println!("cargo:rerun-if-changed={}", file.display()); self.files.push(file); } pub fn try_compile(self, name: &str) -> Result<()> { let compiler = get_compiler(); let archiver = get_archiver(); let out_dir = PathBuf::from(env::var("OUT_DIR")?).join("cc"); fs::create_dir_all(&out_dir)?; let rb = rb_config(); let object_files = self.compile_each_file(compiler, &rb, &out_dir)?; let (lib_path, lib_name) = self.archive_object_files(archiver.copied(), name, &out_dir, object_files)?; self.strip_archived_objects(archiver, &lib_path)?; println!("cargo:rustc-link-search=native={}", out_dir.display()); println!("cargo:rustc-link-lib=static={}", lib_name); Ok(()) } fn compile_each_file( &self, compiler: Command, rb: &rb_config::RbConfig, out_dir: &Path, ) -> Result> { self.files .iter() .map(|f| self.compile_file(f, compiler.copied(), rb, out_dir)) .collect() } fn compile_file( &self, f: &Path, compiler: Command, rb: &rb_config::RbConfig, out_dir: &Path, ) -> Result { let mut hasher = DefaultHasher::new(); hasher.write(fs::read(f)?.as_slice()); let object_file = out_dir .join(hasher.finish().to_string()) .with_extension("o"); let mut cmd = compiler; cmd.args(&get_include_args(rb)) .arg("-c") .arg(f) .args(&rb.cflags) .args(get_common_args()) .args(&self.flags) .args(get_output_file_flag(&object_file)); run_command(cmd)?; Ok(object_file) } fn archive_object_files( &self, archiver: Command, name: &str, out_dir: &Path, object_files: HashSet, ) -> Result<(PathBuf, String)> { let mut cmd = archiver; let mut hasher = DefaultHasher::new(); object_files .iter() .for_each(|f| hasher.write(f.to_str().expect("non-utf8 filename").as_bytes())); let lib_name = format!("{}-{}", name, hasher.finish()); let lib_filename = format!("lib{}.a", lib_name); let dst = out_dir.join(lib_filename); // The argument structure differs for MSVC and GCC. if is_msvc() { cmd.arg(format!("/OUT:{}", dst.display())); cmd.args(&object_files); } else { cmd.env("ZERO_AR_DATE", "1").arg("crs").arg(&dst); cmd.args(&object_files); } run_command(cmd)?; // The Rust compiler will look for libfoo.a and foo.lib, but the // MSVC linker will also be passed foo.lib, so be sure that both // exist for now. if is_msvc() { let lib_dst = dst.with_file_name(format!("{}.lib", lib_name)); let _ = fs::remove_file(&lib_dst); match fs::hard_link(&dst, &lib_dst).or_else(|_| { // if hard-link fails, just copy (ignoring the number of bytes written) fs::copy(&dst, &lib_dst).map(|_| ()) }) { Ok(_) => (), Err(_) => { return Err( "Could not copy or create a hard-link to the generated lib file.".into(), ); } }; } Ok((dst, lib_name)) } fn strip_archived_objects(&self, archiver: Command, libpath: &Path) -> Result<()> { let mut cmd = archiver; if is_msvc() { cmd.arg("/LTCG").arg(libpath); } else { cmd.arg("s").arg(libpath); } run_command(cmd)?; Ok(()) } } fn get_include_args(rb: &rb_config::RbConfig) -> Vec { vec![ format!("-I{}", rb.get("rubyhdrdir")), format!("-I{}", rb.get("rubyarchhdrdir")), format!("-I{}/include/internal", rb.get("rubyhdrdir")), format!("-I{}/include/impl", rb.get("rubyhdrdir")), ] } fn get_common_args() -> Vec { fn add_debug_flags(flags: &mut Vec) { match env::var("DEBUG") { Ok(val) if val == "true" => { if is_msvc() { flags.push("-Z7".into()); } else if cfg!(target_os = "linux") { flags.push("-gdwarf-4".into()); } else { flags.push("-gdwarf-2".into()); } } _ => {} } } fn add_opt_level(flags: &mut Vec) { if let Ok(val) = env::var("OPT_LEVEL") { match val.as_str() { // Msvc uses /O1 to enable all optimizations that minimize code size. "z" | "s" | "1" if is_msvc() => flags.push("-O1".into()), // -O3 is a valid value for gcc and clang compilers, but not msvc. Cap to /O2. "2" | "3" if is_msvc() => flags.push("-O2".into()), lvl => flags.push(format!("-O{}", lvl)), } } } fn add_compiler_flags(flags: &mut Vec) { if !is_msvc() { flags.push("-ffunction-sections".into()); flags.push("-fdata-sections".into()); flags.push("-fPIC".into()); flags.push("-fno-omit-frame-pointer".into()); } flags.extend(Build::default_cflags()); } let mut items = vec![]; add_debug_flags(&mut items); add_compiler_flags(&mut items); add_opt_level(&mut items); items } fn get_compiler() -> Command { let cmd = get_tool("CC", "cc"); let cmd_program = cmd.get_program().to_str().unwrap_or_default(); let already_wrapped = WELL_KNOWN_WRAPPERS.iter().any(|w| cmd_program.contains(w)); match get_tool_from_rb_config_or_env("CC_WRAPPER") { Some(wrapper) if !wrapper.is_empty() && !already_wrapped => { debug_log!("INFO: using CC_WRAPPER ({:?})", wrapper); cmd.wrapped(wrapper) } _ => match rustc_wrapper_fallback() { Some(wrapper) if !already_wrapped => cmd.wrapped(wrapper), _ => cmd, }, } } fn rustc_wrapper_fallback() -> Option { let rustc_wrapper = std::env::var_os("RUSTC_WRAPPER")?; let wrapper_path = Path::new(&rustc_wrapper); let wrapper_stem = wrapper_path.file_stem()?; if WELL_KNOWN_WRAPPERS.contains(&wrapper_stem.to_str()?) { debug_log!("INFO: using RUSTC_WRAPPER ({:?})", rustc_wrapper); Some(rustc_wrapper.to_str()?.to_owned()) } else { None } } fn get_archiver() -> Command { let cmd = get_tool("AR", "ar"); if cmd.get_program() == "libtool" { new_command("ar") } else { cmd } } fn get_tool(env_var: &str, default: &str) -> Command { let tool_args = get_tool_from_rb_config_or_env(env_var) .unwrap_or_else(|| panic!("no {} tool found", env_var)); let mut tool_args = shellsplit(tool_args).into_iter(); let tool = tool_args.next().unwrap_or_else(|| default.to_string()); let mut cmd = new_command(&tool); cmd.args(tool_args.clone()); debug_log!("INFO: found {:?} tool ({:?})", env_var, &cmd); cmd } fn get_tool_from_rb_config_or_env(env_var: &str) -> Option { let rb = rb_config(); get_tool_from_env(env_var) .filter(|s| !s.is_empty()) .or_else(|| rb.get_optional(env_var)) } fn get_tool_from_env(env_var: &str) -> Option { let target_slug = env::var("TARGET").ok()?.replace('-', "_"); let env_var_with_target = format!("{}_{}", env_var, target_slug); println!("cargo:rerun-if-env-changed={}", env_var); println!("cargo:rerun-if-env-changed={}", env_var_with_target); env::var(env_var) .or_else(|_| env::var(env_var_with_target)) .ok() } fn run_command(mut cmd: Command) -> Result { debug_log!("INFO: running command ({:?})", cmd); let status = cmd.status()?; if !status.success() { Err(format!("Command '{:?}' failed with status: {}", cmd, status).into()) } else { Ok(status) } } fn new_command(name: &str) -> Command { let mut cmd = Command::new(name); cmd.stderr(Stdio::inherit()).stdout(Stdio::inherit()); cmd } fn get_output_file_flag(file: &Path) -> Vec { if is_msvc() { vec![format!("-Fo{}", file.display()).into()] } else { vec!["-o".into(), file.into()] } } pub trait CommandExt { fn copied(&self) -> Command; fn wrapped>(&self, wrapper: W) -> Command; } impl CommandExt for Command { fn copied(&self) -> Command { let mut cmd = Command::new(self.get_program()); cmd.args(self.get_args()); for (k, v) in self.get_envs() { if let Some(v) = v { cmd.env(k, v); } else { cmd.env_remove(k); } } cmd } fn wrapped>(&self, wrapper: W) -> Command { let mut new_cmd = Command::new(wrapper); new_cmd.arg(self.get_program()); for arg in self.get_args() { new_cmd.arg(arg); } for (k, v) in self.get_envs() { if let Some(v) = v { new_cmd.env(k, v); } else { new_cmd.env_remove(k); } } new_cmd } } rb-sys-build-0.9.97/src/lib.rs000064400000000000000000000002521046102023000141500ustar 00000000000000pub mod bindings; pub mod cc; pub mod utils; mod rb_config; pub use rb_config::*; /// The current RbConfig. pub fn rb_config() -> RbConfig { RbConfig::current() } rb-sys-build-0.9.97/src/rb_config/flags.rs000064400000000000000000000052101046102023000164250ustar 00000000000000/// Some helper functionality around shell flags pub struct Flags<'a> { inner: &'a str, } impl<'a> Flags<'a> { /// Creates a new `Flags` instance pub fn new(inner: &'a str) -> Self { Self { inner } } } /// Iterates over a string of flags impl<'a> Iterator for Flags<'a> { type Item = &'a str; fn next(&mut self) -> Option { let mut last_was_space = false; let last_idx = self .inner .chars() .by_ref() .take_while(|c| match c { '-' => { if last_was_space { false } else { last_was_space = false; true } } ' ' => { last_was_space = true; true } _ => { last_was_space = false; true } }) .count(); let buf = &self.inner[..last_idx].trim(); if buf.is_empty() { None } else { self.inner = self.inner[last_idx..].trim(); Some(buf) } } } #[cfg(test)] mod tests { use super::*; #[test] fn test_basic_flags() { let mut flags = Flags::new("--foo --bar -baz"); assert_eq!(flags.next(), Some("--foo")); assert_eq!(flags.next(), Some("--bar")); assert_eq!(flags.next(), Some("-baz")); assert_eq!(flags.next(), None); } #[test] fn test_flag_variations() { let mut flags = Flags::new("-ltest --library test"); assert_eq!(flags.next(), Some("-ltest")); assert_eq!(flags.next(), Some("--library test")); assert_eq!(flags.next(), None); } #[test] fn test_real_ldflags() { let mut flags = Flags::new("-L. -L/Users/ianks/.asdf/installs/ruby/3.1.1/lib -L/opt/homebrew/opt/openssl@1.1/lib -fstack-protector-strong"); assert_eq!(flags.next(), Some("-L.")); assert_eq!( flags.next(), Some("-L/Users/ianks/.asdf/installs/ruby/3.1.1/lib") ); assert_eq!(flags.next(), Some("-L/opt/homebrew/opt/openssl@1.1/lib")); assert_eq!(flags.next(), Some("-fstack-protector-strong")); assert_eq!(flags.next(), None); } #[test] fn test_dashed_flag_with_dashed_val() { let mut flags = Flags::new("-ltest -fsomething-foo bar-val"); assert_eq!(flags.next(), Some("-ltest")); assert_eq!(flags.next(), Some("-fsomething-foo bar-val")); assert_eq!(flags.next(), None); } } rb-sys-build-0.9.97/src/rb_config/library.rs000064400000000000000000000061121046102023000167770ustar 00000000000000/// Represents the kind of library. #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum LibraryKind { Framework, Dylib, Static, None, } /// Represents a search path that can be linked with Cargo. #[derive(Debug, PartialEq, Eq)] pub struct Library { pub kind: LibraryKind, pub name: String, } impl Library { pub fn is_static(&self) -> bool { self.kind == LibraryKind::Static } } impl From<&str> for LibraryKind { fn from(s: &str) -> Self { match s { "framework" => LibraryKind::Framework, "dylib" => LibraryKind::Dylib, "static" => LibraryKind::Static, _ => LibraryKind::None, } } } impl From<&str> for Library { fn from(s: &str) -> Self { let parts: Vec<_> = s.splitn(2, '=').collect(); match parts.len() { 1 => (LibraryKind::None, parts[0]).into(), 2 => (parts[0], parts[1]).into(), _ => panic!("Invalid library specification: {}", s), } } } impl From for Library { fn from(s: String) -> Self { s.as_str().into() } } fn sanitize_library_name(name: &str) -> &str { name.trim_end_matches(".lib").trim_start_matches("-l") } impl From<(K, L)> for Library where K: Into, L: Into, { fn from((kind, name): (K, L)) -> Self { Self { kind: kind.into(), name: sanitize_library_name(&name.into()).into(), } } } impl std::fmt::Display for Library { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match self.kind { LibraryKind::Framework => write!(f, "framework={}", self.name), LibraryKind::Dylib => write!(f, "dylib={}", self.name), LibraryKind::Static => write!(f, "static={}", self.name), LibraryKind::None => write!(f, "{}", self.name), } } } #[cfg(test)] mod tests { use super::*; #[test] fn test_trim_leading_link_flag() { let result: Library = "-lfoo".to_string().into(); assert_eq!(result.name, "foo"); } #[test] fn test_trim_trailing_lib_extension() { let result: Library = "foo.lib".to_string().into(); assert_eq!(result.name, "foo"); } #[test] fn test_trim_leading_link_flag_and_trailing_lib_extension() { let result: Library = "-lfoo.lib".to_string().into(); assert_eq!(result.name, "foo"); } #[test] fn test_display_framework() { let result: Library = "framework=foo".to_string().into(); assert_eq!(result.to_string(), "framework=foo"); } #[test] fn test_display_dylib() { let result: Library = "dylib=foo".to_string().into(); assert_eq!(result.to_string(), "dylib=foo"); } #[test] fn test_display_static() { let result: Library = "static=-lfoo".to_string().into(); assert_eq!(result.to_string(), "static=foo"); } #[test] fn test_display_none() { let result: Library = "foo".to_string().into(); assert_eq!(result.to_string(), "foo"); } } rb-sys-build-0.9.97/src/rb_config/search_path.rs000064400000000000000000000026331046102023000176200ustar 00000000000000/// Represents the kind of search path. #[derive(Debug, PartialEq, Eq)] pub enum SearchPathKind { Native, Framework, } /// Represents a library taht can be linked with Cargo. #[derive(Debug, PartialEq, Eq)] pub struct SearchPath { pub kind: SearchPathKind, pub name: String, } impl From<&str> for SearchPathKind { fn from(s: &str) -> Self { match s { "framework" => SearchPathKind::Framework, "native" => SearchPathKind::Native, _ => panic!("Unknown lib kind: {}", s), } } } impl From<&str> for SearchPath { fn from(s: &str) -> Self { let parts: Vec<_> = s.split('=').collect(); match parts.len() { 1 => (SearchPathKind::Native, parts[0]).into(), 2 => (parts[0], parts[1]).into(), _ => panic!("Invalid library specification: {}", s), } } } impl From<(K, T)> for SearchPath where K: Into, T: Into, { fn from((kind, name): (K, T)) -> Self { Self { kind: kind.into(), name: name.into(), } } } impl std::fmt::Display for SearchPath { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self.kind { SearchPathKind::Framework => write!(f, "framework={}", self.name), SearchPathKind::Native => write!(f, "native={}", self.name), } } } rb-sys-build-0.9.97/src/rb_config.rs000064400000000000000000000601431046102023000153370ustar 00000000000000use std::{ collections::{hash_map::Keys, HashMap}, env, path::PathBuf, process::Command, }; use regex::Regex; mod flags; mod library; mod search_path; use library::*; use search_path::*; use std::ffi::OsString; use crate::{ debug_log, memoize, utils::{is_msvc, shellsplit}, }; use self::flags::Flags; /// Extracts structured information from raw compiler/linker flags to make /// compiling Ruby gems easier. #[derive(Debug, PartialEq, Eq)] pub struct RbConfig { pub search_paths: Vec, pub libs: Vec, pub link_args: Vec, pub cflags: Vec, pub blocklist_lib: Vec, pub blocklist_link_arg: Vec, use_rpath: bool, value_map: HashMap, } impl Default for RbConfig { fn default() -> Self { Self::new() } } impl RbConfig { /// Creates a new, blank `RbConfig`. You likely want to use `RbConfig::current()` instead. pub(crate) fn new() -> RbConfig { RbConfig { blocklist_lib: vec![], blocklist_link_arg: vec![], search_paths: Vec::new(), libs: Vec::new(), link_args: Vec::new(), cflags: Vec::new(), value_map: HashMap::new(), use_rpath: false, } } /// All keys in the `RbConfig`'s value map. pub fn all_keys(&self) -> Keys<'_, String, String> { self.value_map.keys() } /// Instantiates a new `RbConfig` for the current Ruby. pub fn current() -> RbConfig { println!("cargo:rerun-if-env-changed=RUBY"); let mut rbconfig = RbConfig::new(); // Never use the current Ruby's RbConfig if we're cross compiling, or // else bad things happen let parsed = if rbconfig.is_cross_compiling() { HashMap::new() } else { let output = memoize!(String: { let ruby = env::var_os("RUBY").unwrap_or_else(|| OsString::from("ruby")); let config = Command::new(ruby) .arg("--disable-gems") .arg("-rrbconfig") .arg("-e") .arg("print RbConfig::CONFIG.map {|kv| kv.join(\"\x1F\")}.join(\"\x1E\")") .output() .unwrap_or_else(|e| panic!("ruby not found: {}", e)); if !config.status.success() { panic!("non-zero exit status while dumping RbConfig: {:?}", config); } String::from_utf8(config.stdout).expect("RbConfig value not UTF-8!") }); let mut parsed = HashMap::new(); for line in output.split('\x1E') { let mut parts = line.splitn(2, '\x1F'); if let (Some(key), Some(val)) = (parts.next(), parts.next()) { parsed.insert(key.to_owned(), val.to_owned()); } } parsed }; parsed.get("cflags").map(|f| rbconfig.push_cflags(f)); parsed.get("DLDFLAGS").map(|f| rbconfig.push_dldflags(f)); rbconfig.value_map = parsed; rbconfig } /// Pushes the `LIBRUBYARG` flags so Ruby will be linked. pub fn link_ruby(&mut self, is_static: bool) -> &mut Self { let libdir = self.get("libdir"); self.push_search_path(libdir.as_str()); self.push_dldflags(&format!("-L{}", &self.get("libdir"))); let librubyarg = if is_static { self.get("LIBRUBYARG_STATIC") } else { self.get("LIBRUBYARG_SHARED") }; if is_msvc() { for lib in librubyarg.split_whitespace() { self.push_library(lib); } let mut to_link: Vec = vec![]; if let Some(libs) = self.get_optional("LIBS") { to_link.extend(libs.split_whitespace().map(|s| s.to_string())); } if let Some(libs) = self.get_optional("LOCAL_LIBS") { to_link.extend(libs.split_whitespace().map(|s| s.to_string())); } for lib in to_link { self.push_library(lib); } } else { self.push_dldflags(&librubyarg); if cfg!(unix) { self.use_rpath(); } } self } /// Get the name for libruby-static (i.e. `ruby.3.1-static`). pub fn libruby_static_name(&self) -> String { self.get("LIBRUBY_A") .trim_start_matches("lib") .trim_end_matches(".a") .to_string() } /// Get the name for libruby (i.e. `ruby.3.1`) pub fn libruby_so_name(&self) -> String { self.get("RUBY_SO_NAME") } /// Get the platform for the current ruby. pub fn platform(&self) -> String { self.get_optional("platform") .unwrap_or_else(|| self.get("arch")) } /// Filter the libs, removing the ones that are not needed. pub fn blocklist_lib(&mut self, name: &str) -> &mut RbConfig { self.blocklist_lib.push(name.to_string()); self } /// Blocklist a link argument. pub fn blocklist_link_arg(&mut self, name: &str) -> &mut RbConfig { self.blocklist_link_arg.push(name.to_string()); self } /// Returns the current ruby program version. pub fn ruby_program_version(&self) -> String { if let Some(progv) = self.get_optional("RUBY_PROGRAM_VERSION") { progv } else { format!( "{}.{}.{}", self.get("MAJOR"), self.get("MINOR"), self.get("TEENY") ) } } /// Get the CPPFLAGS from the RbConfig, making sure to subsitute variables. pub fn cppflags(&self) -> Vec { if let Some(cppflags) = self.get_optional("CPPFLAGS") { let flags = self.subst_shell_variables(&cppflags); shellsplit(flags) } else { vec![] } } /// Returns the value of the given key from the either the matching /// `RBCONFIG_{key}` environment variable or `RbConfig::CONFIG[{key}]` hash. pub fn get(&self, key: &str) -> String { self.get_optional(key) .unwrap_or_else(|| panic!("Key not found: {}", key)) } /// Returns true if the current Ruby is cross compiling. pub fn is_cross_compiling(&self) -> bool { if let Some(cross) = self.get_optional("CROSS_COMPILING") { cross == "yes" || cross == "1" } else { false } } /// Returns the value of the given key from the either the matching /// `RBCONFIG_{key}` environment variable or `RbConfig::CONFIG[{key}]` hash. pub fn get_optional(&self, key: &str) -> Option { self.try_rbconfig_env(key) .or_else(|| self.try_value_map(key)) } /// Enables the use of rpath for linking. pub fn use_rpath(&mut self) -> &mut RbConfig { self.use_rpath = true; self } /// Push cflags string pub fn push_cflags(&mut self, cflags: &str) -> &mut Self { for flag in shellsplit(cflags) { if !self.cflags.contains(&flag) { self.cflags.push(flag.to_string()); } } self } /// Get major/minor version tuple of Ruby pub fn major_minor(&self) -> (u32, u32) { let major = self.get("MAJOR").parse::().unwrap(); let minor = self.get("MINOR").parse::().unwrap(); (major, minor) } /// Get the rb_config output for cargo pub fn cargo_args(&self) -> Vec { let mut result = vec![]; let mut search_paths = vec![]; for search_path in &self.search_paths { result.push(format!("cargo:rustc-link-search={}", search_path)); search_paths.push(search_path.name.as_str()); } for lib in &self.libs { if !self.blocklist_lib.iter().any(|b| lib.name.contains(b)) { result.push(format!("cargo:rustc-link-lib={}", lib)); } if self.use_rpath && !lib.is_static() { result.push(format!("cargo:rustc-link-arg=-Wl,-rpath,{}", lib)); } } for link_arg in &self.link_args { if !self.blocklist_link_arg.iter().any(|b| link_arg == b) { result.push(format!("cargo:rustc-link-arg={}", link_arg)); } } result } /// Print to rb_config output for cargo pub fn print_cargo_args(&self) { let cargo_args = self.cargo_args(); for arg in &cargo_args { println!("{}", arg); } debug_log!("INFO: printing cargo args ({:?})", cargo_args); let encoded_cargo_args = cargo_args.join("\x1E"); let encoded_cargo_args = encoded_cargo_args.replace('\n', "\x1F"); println!("cargo:encoded_cargo_args={}", encoded_cargo_args); } /// Adds items to the rb_config based on a string from LDFLAGS/DLDFLAGS pub fn push_dldflags(&mut self, input: &str) -> &mut Self { let input = self.subst_shell_variables(input); let split_args = Flags::new(input.as_str()); let search_path_regex = Regex::new(r"^-L\s*(?P.*)$").unwrap(); let lib_regex_short = Regex::new(r"^-l\s*(?P\w+\S+)$").unwrap(); let lib_regex_long = Regex::new(r"^--library=(?P\w+\S+)$").unwrap(); let dynamic_lib_regex = Regex::new(r"^-l\s*:lib(?P\S+).(so|dylib|dll)$").unwrap(); let framework_regex_short = Regex::new(r"^-F\s*(?P.*)$").unwrap(); let framework_regex_long = Regex::new(r"^-framework\s*(?P.*)$").unwrap(); for arg in split_args { let arg = arg.trim().to_owned(); if let Some(name) = capture_name(&search_path_regex, &arg) { self.push_search_path(name.as_str()); } else if let Some(name) = capture_name(&lib_regex_long, &arg) { self.push_library(name); } else if let Some(name) = capture_name(&lib_regex_short, &arg) { if name.contains("ruby") && name.contains("-static") { self.push_library((LibraryKind::Static, name)); } else { self.push_library(name); } } else if let Some(name) = capture_name(&dynamic_lib_regex, &arg) { self.push_library((LibraryKind::Dylib, name)); } else if let Some(name) = capture_name(&framework_regex_short, &arg) { self.push_search_path((SearchPathKind::Framework, name)); } else if let Some(name) = capture_name(&framework_regex_long, &arg) { self.push_library((LibraryKind::Framework, name)); } else { self.push_link_arg(arg); } } self } /// Sets a value for a key pub fn set_value_for_key(&mut self, key: &str, value: String) { self.value_map.insert(key.to_owned(), value); } // Check if has ABI version pub fn has_ruby_dln_check_abi(&self) -> bool { let major = self.get("MAJOR").parse::().unwrap(); let minor = self.get("MINOR").parse::().unwrap(); let patchlevel = self.get("PATCHLEVEL").parse::().unwrap(); // Ruby has ABI version on verion 3.2 and later only on development // versions major >= 3 && minor >= 2 && patchlevel == -1 && !cfg!(target_family = "windows") } // Examines the string from shell variables and expands them with values in the value_map fn subst_shell_variables(&self, input: &str) -> String { let mut result = String::new(); let mut chars = input.chars().enumerate(); while let Some((_, c)) = chars.next() { if c == '$' { if let Some((i, c)) = chars.next() { if c == '(' { let start = i + 1; let mut end = start; for (i, c) in chars.by_ref() { if c == ')' { end = i; break; } } let key = &input[start..end]; if let Some(val) = self.get_optional(key) { result.push_str(&val); } else if let Some(val) = env::var_os(key) { result.push_str(&val.to_string_lossy()); } else { // Consume whitespace chars.next(); } } else { result.push(c); } } } else { result.push(c); } } result } pub fn have_ruby_header>(&self, header: T) -> bool { let ruby_include_dir: PathBuf = self.get("rubyhdrdir").into(); ruby_include_dir.join(header.as_ref()).exists() } fn push_search_path>(&mut self, path: T) -> &mut Self { let path = path.into(); if !self.search_paths.contains(&path) { self.search_paths.push(path); } self } fn push_library>(&mut self, lib: T) -> &mut Self { let lib = lib.into(); if !self.libs.contains(&lib) { self.libs.push(lib); } self } fn push_link_arg>(&mut self, arg: T) -> &mut Self { let arg = arg.into(); if !self.link_args.contains(&arg) { self.link_args.push(arg); } self } fn try_value_map(&self, key: &str) -> Option { self.value_map .get(key) .map(|val| val.trim_matches('\n').to_owned()) } fn try_rbconfig_env(&self, key: &str) -> Option { let key = format!("RBCONFIG_{}", key); println!("cargo:rerun-if-env-changed={}", key); env::var(key).map(|v| v.trim_matches('\n').to_owned()).ok() } } fn capture_name(regex: &Regex, arg: &str) -> Option { regex .captures(arg) .map(|cap| cap.name("name").unwrap().as_str().trim().to_owned()) } #[cfg(test)] mod tests { use super::*; use std::{sync::Mutex, vec}; lazy_static::lazy_static! { static ref ENV_LOCK: Mutex<()> = Mutex::new(()); } fn with_locked_env(f: F) -> T where F: FnOnce() -> T, { let _guard = ENV_LOCK.lock().unwrap(); f() } #[test] fn test_extract_lib_search_paths() { let mut rb_config = RbConfig::new(); rb_config.push_dldflags("-L/usr/local/lib -L/usr/lib"); assert_eq!( rb_config.search_paths, vec!["/usr/local/lib".into(), "/usr/lib".into()] ); } #[test] fn test_search_path_basic() { let mut rb_config = RbConfig::new(); rb_config.push_dldflags("-L/usr/local/lib"); assert_eq!(rb_config.search_paths, vec!["native=/usr/local/lib".into()]); } #[test] fn test_search_path_space() { let mut rb_config = RbConfig::new(); rb_config.push_dldflags("-L /usr/local/lib"); assert_eq!(rb_config.search_paths, vec!["/usr/local/lib".into()]); } #[test] fn test_search_path_space_in_path() { let mut rb_config = RbConfig::new(); rb_config.push_dldflags("-L/usr/local/my lib"); assert_eq!( rb_config.search_paths, vec!["native=/usr/local/my lib".into()] ); } #[test] fn test_simple_lib() { let mut rb_config = RbConfig::new(); rb_config.push_dldflags("-lfoo"); assert_eq!(rb_config.libs, ["foo".into()]); } #[test] fn test_lib_with_nonascii() { let mut rb_config = RbConfig::new(); rb_config.push_dldflags("-lws2_32"); assert_eq!(rb_config.libs, ["ws2_32".into()]); } #[test] fn test_simple_lib_space() { let mut rb_config = RbConfig::new(); rb_config.push_dldflags("-l foo"); assert_eq!(rb_config.libs, ["foo".into()]); } #[test] fn test_verbose_lib_space() { let mut rb_config = RbConfig::new(); rb_config.push_dldflags("--library=foo"); assert_eq!(rb_config.libs, ["foo".into()]); } #[test] fn test_dylib_with_colon_space() { let mut rb_config = RbConfig::new(); rb_config.push_dldflags("-l :libssp.dylib"); assert_eq!(rb_config.libs, ["dylib=ssp".into()]); } #[test] fn test_so_with_colon_space() { let mut rb_config = RbConfig::new(); rb_config.push_dldflags("-l :libssp.so"); assert_eq!(rb_config.libs, ["dylib=ssp".into()]); } #[test] fn test_dll_with_colon_space() { let mut rb_config = RbConfig::new(); rb_config.push_dldflags("-l :libssp.dll"); assert_eq!(rb_config.libs, ["dylib=ssp".into()]); } #[test] fn test_framework() { let mut rb_config = RbConfig::new(); rb_config.push_dldflags("-F/some/path"); assert_eq!(rb_config.search_paths, ["framework=/some/path".into()]); } #[test] fn test_framework_space() { let mut rb_config = RbConfig::new(); rb_config.push_dldflags("-F /some/path"); assert_eq!( rb_config.search_paths, [SearchPath { kind: SearchPathKind::Framework, name: "/some/path".into(), }] ); } #[test] fn test_framework_arg_real() { let mut rb_config = RbConfig::new(); rb_config.push_dldflags("-framework CoreFoundation"); assert_eq!( rb_config.libs, [Library { kind: LibraryKind::Framework, name: "CoreFoundation".into(), }] ); } #[test] fn test_libruby_static() { let mut rb_config = RbConfig::new(); rb_config.push_dldflags("-lruby.3.1-static"); assert_eq!( rb_config.cargo_args(), ["cargo:rustc-link-lib=static=ruby.3.1-static"] ); } #[test] fn test_libruby_dynamic() { let mut rb_config = RbConfig::new(); rb_config.push_dldflags("-lruby.3.1"); assert_eq!(rb_config.cargo_args(), ["cargo:rustc-link-lib=ruby.3.1"]); } #[test] fn test_non_lib_dash_l() { let mut rb_config = RbConfig::new(); rb_config.push_dldflags("test_rubygems_20220413-976-lemgf9/prefix"); assert_eq!( rb_config.link_args, vec!["test_rubygems_20220413-976-lemgf9/prefix"] ); } #[test] fn test_real_dldflags() { let mut rb_config = RbConfig::new(); 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"); assert_eq!( rb_config.link_args, vec![ "-Wl,-undefined,dynamic_lookup", "-Wl,-multiply_defined,suppress" ] ); assert_eq!( rb_config.search_paths, vec![ SearchPath { kind: SearchPathKind::Native, name: "/Users/ianks/.asdf/installs/ruby/3.1.1/lib".to_string() }, SearchPath { kind: SearchPathKind::Native, name: "/opt/homebrew/opt/openssl@1.1/lib".to_string() }, ] ); } #[test] fn test_crazy_cases() { let mut rb_config = RbConfig::new(); rb_config.push_dldflags("-F /something -l:libssp.a -static-libgcc "); assert_eq!(rb_config.link_args, vec!["-l:libssp.a", "-static-libgcc"]); assert_eq!( rb_config.search_paths, vec![SearchPath { kind: SearchPathKind::Framework, name: "/something".to_string() },] ); } #[test] fn test_printing_cargo_args() { let mut rb_config = RbConfig::new(); rb_config.push_dldflags("-L/Users/ianks/.asdf/installs/ruby/3.1.1/lib"); rb_config.push_dldflags("-lfoo"); rb_config.push_dldflags("-static-libgcc"); let result = rb_config.cargo_args(); assert_eq!( vec![ "cargo:rustc-link-search=native=/Users/ianks/.asdf/installs/ruby/3.1.1/lib", "cargo:rustc-link-lib=foo", "cargo:rustc-link-arg=-static-libgcc" ], result ); } #[test] fn test_use_rpath() { let mut rb_config = RbConfig::new(); rb_config.push_dldflags("-lfoo"); assert_eq!(vec!["cargo:rustc-link-lib=foo"], rb_config.cargo_args()); rb_config.use_rpath(); assert_eq!( vec![ "cargo:rustc-link-lib=foo", "cargo:rustc-link-arg=-Wl,-rpath,foo" ], rb_config.cargo_args() ); } #[test] fn test_link_mswin() { with_locked_env(|| { let old_var = env::var("TARGET").ok(); env::set_var("TARGET", "x86_64-pc-windows-msvc"); let mut rb_config = RbConfig::new(); rb_config.set_value_for_key("LIBRUBYARG_SHARED", "x64-vcruntime140-ruby320.lib".into()); rb_config.set_value_for_key("libdir", "D:/ruby-mswin/lib".into()); rb_config.set_value_for_key("LIBS", "user32.lib".into()); rb_config.link_ruby(false); assert_eq!( vec![ "cargo:rustc-link-search=native=D:/ruby-mswin/lib", "cargo:rustc-link-lib=x64-vcruntime140-ruby320", "cargo:rustc-link-lib=user32", ], rb_config.cargo_args() ); if let Some(old_var) = old_var { env::set_var("TARGET", old_var); } else { env::remove_var("TARGET"); } }) } #[test] fn test_link_static() { with_locked_env(|| { let mut rb_config = RbConfig::new(); rb_config.set_value_for_key("LIBRUBYARG_STATIC", "-lruby-static".into()); rb_config.set_value_for_key("libdir", "/opt/ruby".into()); rb_config.link_ruby(true); assert_eq!( vec![ "cargo:rustc-link-search=native=/opt/ruby", "cargo:rustc-link-lib=static=ruby-static", ], rb_config.cargo_args() ); }); } #[test] fn test_prioritizes_rbconfig_env() { with_locked_env(|| { env::set_var("RBCONFIG_libdir", "/foo"); let rb_config = RbConfig::new(); assert_eq!(rb_config.get("libdir"), "/foo"); assert_eq!(rb_config.get_optional("libdir"), Some("/foo".into())); env::remove_var("RBCONFIG_libdir"); }); } #[test] fn test_never_loads_shell_rbconfig_if_cross_compiling() { with_locked_env(|| { env::set_var("RBCONFIG_CROSS_COMPILING", "yes"); let rb_config = RbConfig::current(); assert!(rb_config.value_map.is_empty()); }); } #[test] fn test_loads_shell_rbconfig_if_not_cross_compiling() { with_locked_env(|| { env::set_var("RBCONFIG_CROSS_COMPILING", "no"); let rb_config = RbConfig::current(); assert!(!rb_config.value_map.is_empty()); }); } #[test] fn test_libstatic() { let mut rb_config = RbConfig::new(); rb_config.push_dldflags("-l:libssp.a"); assert_eq!(rb_config.link_args, ["-l:libssp.a".to_string()]); } #[test] fn test_link_arg_blocklist() { let mut rb_config = RbConfig::new(); rb_config.blocklist_link_arg("-Wl,--compress-debug-sections=zlib"); rb_config.blocklist_link_arg("-s"); rb_config.push_dldflags( "-lfoo -Wl,--compress-debug-sections=zlib -s -somethingthatshouldnotbeblocked", ); assert_eq!( vec![ "cargo:rustc-link-lib=foo", "cargo:rustc-link-arg=-somethingthatshouldnotbeblocked" ], rb_config.cargo_args() ); } } rb-sys-build-0.9.97/src/utils.rs000064400000000000000000000022571046102023000145510ustar 00000000000000use crate::debug_log; /// Check if current platform is mswin. pub fn is_msvc() -> bool { if let Ok(target) = std::env::var("TARGET") { target.contains("msvc") } else { false } } /// Check if current platform is mswin or mingw. pub fn is_mswin_or_mingw() -> bool { if let Ok(target) = std::env::var("TARGET") { target.contains("msvc") || target.contains("pc-windows-gnu") } else { false } } /// Splits shell words. pub fn shellsplit>(s: S) -> Vec { let s = s.as_ref(); match shell_words::split(s) { Ok(v) => v, Err(e) => { debug_log!("shellsplit failed: {}", e); s.split_whitespace().map(Into::into).collect() } } } #[macro_export] macro_rules! memoize { ($type:ty: $val:expr) => {{ static INIT: std::sync::Once = std::sync::Once::new(); static mut VALUE: Option<$type> = None; unsafe { INIT.call_once(|| { VALUE = Some($val); }); VALUE.as_ref().unwrap() } }}; } #[macro_export] macro_rules! debug_log { ($($arg:tt)*) => { eprintln!($($arg)*); }; }