Skip to content

Commit

Permalink
Ship the miserable hack?
Browse files Browse the repository at this point in the history
  • Loading branch information
workingjubilee committed Nov 15, 2023
1 parent f77d4d7 commit 5581b63
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 53 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pgrx-pg-sys/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,4 @@ syn = { version = "1.0.109", features = [ "extra-traits", "full", "fold", "parsi
eyre = "0.6.8"
shlex = "1.2.0" # shell lexing, also used by many of our deps
once_cell = "1.18.0"
walkdir = "2"
59 changes: 6 additions & 53 deletions pgrx-pg-sys/build.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use bindgen::ClangVersion;
//LICENSE Portions Copyright 2019-2021 ZomboDB, LLC.
//LICENSE
//LICENSE Portions Copyright 2021-2023 Technology Concepts & Design, Inc.
Expand All @@ -25,6 +24,7 @@ use syn::{ForeignItem, Item, ItemConst};
const BLOCKLISTED_TYPES: [&str; 3] = ["Datum", "NullableDatum", "Oid"];

mod build {
pub(super) mod clang;
pub(super) mod sym_blocklist;
}

Expand Down Expand Up @@ -718,11 +718,15 @@ fn run_bindgen(
let configure = pg_config.configure()?;
let preferred_clang: Option<&std::path::Path> = configure.get("CLANG").map(|s| s.as_ref());
eprintln!("pg_config --configure CLANG = {:?}", preferred_clang);
let (autodetect, _includes) = detect_include_paths_for_correct_clang(preferred_clang);
let (autodetect, includes) = build::clang::detect_include_paths_for(preferred_clang);
eprintln!("passed detect_include_paths_for_correct_clang");
let mut binder = bindgen::Builder::default();
binder = add_blocklists(binder);
binder = add_derives(binder);
if !autodetect {
let builtin_includes = includes.iter().filter_map(|p| Some(format!("-I{}", p.to_str()?)));
binder = binder.clang_args(builtin_includes);
};
let bindings = binder
.header(include_h.display().to_string())
.clang_arg("-v")
Expand All @@ -743,57 +747,6 @@ fn run_bindgen(
Ok(bindings.to_string())
}

/// pgrx's bindgen needs to detect include paths, to keep code building,
/// but the way rust-bindgen does it breaks on Postgres 16 due to code like
/// ```c
/// #include <emmintrin.h>
/// ```
/// This will pull in builtin headers, but rust-bindgen uses a $CLANG_PATH lookup from clang-sys
/// which is not guaranteed to find the clang that uses the $LIBCLANG_PATH that bindgen intends.
///
/// Returns the set of paths to include.
fn detect_include_paths_for_correct_clang(
preferred_clang: Option<&std::path::Path>,
) -> (bool, Vec<PathBuf>) {
if target_env_tracked("PGRX_BINDGEN_NO_DETECT_INCLUDES").is_some() {
return (false, vec![]);
}

// By asking bindgen for the version, we force it to pull an appropriate libclang,
// allowing users to override it however they would usually override bindgen.
let clang_major = match bindgen::clang_version() {
ClangVersion { parsed: Some((major, _)), full } => {
eprintln!("Bindgen found {full}");
major
}
ClangVersion { full, .. } => {
// If bindgen doesn't know what version it has, bail and hope for the best.
eprintln!("Bindgen failed to parse clang version: {full}");
return (true, vec![]);
}
};

// If Postgres is configured --with-llvm, then it may have recorded a CLANG to use
// Ask if there's a clang at the path that Postgres would use for JIT purposes.
// If it matches the major version that bindgen has pulled for us, we can use it.
if let Some(clang_sys::support::Clang { path, version: Some(v), c_search_paths, .. }) =
clang_sys::support::Clang::find(preferred_clang, &[])
{
if Some(&*path) == preferred_clang && v.Major as u32 == clang_major {
return (false, c_search_paths.unwrap_or_default());
}
}

// Oh no, still here?
// Let's go behind bindgen's back to get libclang's path
let libclang_path = clang_sys::get_library().expect("libclang should have been loaded?").path().to_owned();
eprintln!("found libclang at {}", libclang_path.display());
// libclang will be in a library directory,
// which means it will probably be adjacent to its headers.

todo!()
}

fn add_blocklists(bind: bindgen::Builder) -> bindgen::Builder {
bind.blocklist_type("Datum") // manually wrapping datum for correctness
.blocklist_type("Oid") // "Oid" is not just any u32
Expand Down
112 changes: 112 additions & 0 deletions pgrx-pg-sys/build/clang.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
use crate::target_env_tracked;
use bindgen::ClangVersion;
use clang_sys::support::Clang as ClangSys;
use std::{ffi::OsStr, path::PathBuf};
use walkdir::{DirEntry, WalkDir};

/// pgrx's bindgen needs to detect include paths, to keep code building,
/// but the way rust-bindgen does it breaks on Postgres 16 due to code like
/// ```c
/// #include <emmintrin.h>
/// ```
/// This will pull in builtin headers, but rust-bindgen uses a $CLANG_PATH lookup from clang-sys
/// which is not guaranteed to find the clang that uses the $LIBCLANG_PATH that bindgen intends.
///
/// Returns the set of paths to include.
pub(crate) fn detect_include_paths_for(
preferred_clang: Option<&std::path::Path>,
) -> (bool, Vec<PathBuf>) {
if target_env_tracked("PGRX_BINDGEN_NO_DETECT_INCLUDES").is_some() {
return (false, vec![]);
}

// By asking bindgen for the version, we force it to pull an appropriate libclang,
// allowing users to override it however they would usually override bindgen.
let clang_major = match bindgen::clang_version() {
ClangVersion { parsed: Some((major, _)), full } => {
eprintln!("Bindgen found {full}");
major
}
ClangVersion { full, .. } => {
// If bindgen doesn't know what version it has, bail and hope for the best.
eprintln!("Bindgen failed to parse clang version: {full}");
return (true, vec![]);
}
};

// If Postgres is configured --with-llvm, then it may have recorded a CLANG to use
// Ask if there's a clang at the path that Postgres would use for JIT purposes.
// Unfortunately, the responses from clang-sys include clangs from far-off paths,
// so we can only use clangs that match bindgen's libclang major version.
if let Some(ClangSys { path, version: Some(v), c_search_paths, .. }) =
ClangSys::find(preferred_clang, &[])
{
if Some(&*path) == preferred_clang && v.Major as u32 == clang_major {
return (false, c_search_paths.unwrap_or_default());
}
}

// Oh no, still here?
// Let's go behind bindgen's back to get libclang's path
let mut libclang_path =
clang_sys::get_library().expect("libclang should have been loaded?").path().to_owned();
eprintln!("found libclang at {}", libclang_path.display());
// libclang will probably be in a dynamic library directory,
// which means it will probably be adjacent to its headers
libclang_path.pop();
let clang_major_fmt = clang_major.to_string();
eprintln!("displayed libclang dir: {}", libclang_path.display());
let mut paths = WalkDir::new(libclang_path)
.min_depth(1)
.max_depth(6)
.sort_by_file_name()
.into_iter()
// On Unix-y systems this will be like "/usr/lib/clang/$CLANG_MAJOR/include"
// so don't even descend if the directory doesn't have one of those parts
.filter_entry(|entry| {
!is_hidden(entry) && {
entry_contains(entry, "clang")
|| entry_contains(entry, "include")
|| entry_contains(entry, &*clang_major_fmt)
}
})
.filter_map(|e| e.ok()) // be discreet
// We now need something that looks like it actually satisfies all our constraints
.filter(|entry| {
entry_contains(entry, &*clang_major_fmt)
&& entry_contains(entry, "clang")
&& entry_contains(entry, "include")
})
// we need to pull the actual directories that include the SIMD headers
.filter(|entry| {
os_str_contains(entry.file_name(), "emmintrin.h")
|| os_str_contains(entry.file_name(), "arm_neon.h")
})
.filter_map(|entry| {
let mut pbuf = entry.into_path();
if pbuf.pop() && pbuf.is_dir() && os_str_contains(&*pbuf.file_name()?, "include") {
Some(pbuf)
} else {
None
}
})
.collect::<Vec<_>>();
paths.sort();
paths.dedup();
// If we have anything better to recommend, don't autodetect!
let autodetect = paths.len() == 0;
eprintln!("Found include dirs {:?}", paths);
(autodetect, paths)
}

fn is_hidden(entry: &DirEntry) -> bool {
entry.file_name().to_str().map(|s| s.starts_with(".")).unwrap_or(false)
}

fn entry_contains(entry: &DirEntry, needle: &str) -> bool {
entry.path().components().any(|part| os_str_contains(part.as_os_str(), needle))
}

fn os_str_contains(os_s: &OsStr, needle: &str) -> bool {
os_s.to_str().filter(|part| part.contains(needle)).is_some()
}

0 comments on commit 5581b63

Please sign in to comment.