use bindgen::callbacks::{DeriveTrait, ImplementsTrait, MacroParsingBehavior};
use eyre::{eyre, WrapErr};
use once_cell::sync::Lazy;
use pgrx_pg_config::{
is_supported_major_version, prefix_path, PgConfig, PgConfigSelector, Pgrx, SUPPORTED_VERSIONS,
};
use quote::{quote, ToTokens};
use std::cmp::Ordering;
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
use std::fs;
use std::path::{self, PathBuf}; use std::process::{Command, Output};
use syn::{ForeignItem, Item, ItemConst};
const BLOCKLISTED_TYPES: [&str; 3] = ["Datum", "NullableDatum", "Oid"];
mod build {
pub(super) mod clang;
pub(super) mod sym_blocklist;
}
#[derive(Debug)]
struct PgrxOverrides(HashSet<String>);
fn is_nightly() -> bool {
if env_tracked("CARGO_CFG_PLRUSTC").is_some() {
return false;
}
let rustc = env_tracked("RUSTC").map(PathBuf::from).unwrap_or_else(|| "rustc".into());
let output = match std::process::Command::new(rustc).arg("--version").output() {
Ok(out) if out.status.success() => String::from_utf8_lossy(&out.stdout).trim().to_owned(),
_ => return false,
};
output.starts_with("rustc ") && (output.contains("-nightly") || output.contains("-dev"))
}
impl PgrxOverrides {
fn default() -> Self {
PgrxOverrides(
vec![
"FP_INFINITE".into(),
"FP_NAN".into(),
"FP_NORMAL".into(),
"FP_SUBNORMAL".into(),
"FP_ZERO".into(),
"IPPORT_RESERVED".into(),
]
.into_iter()
.collect(),
)
}
}
impl bindgen::callbacks::ParseCallbacks for PgrxOverrides {
fn will_parse_macro(&self, name: &str) -> MacroParsingBehavior {
if self.0.contains(name) {
bindgen::callbacks::MacroParsingBehavior::Ignore
} else {
bindgen::callbacks::MacroParsingBehavior::Default
}
}
fn blocklisted_type_implements_trait(
&self,
name: &str,
derive_trait: DeriveTrait,
) -> Option<ImplementsTrait> {
if !BLOCKLISTED_TYPES.contains(&name) {
return None;
}
let implements_trait = match derive_trait {
DeriveTrait::Copy => ImplementsTrait::Yes,
DeriveTrait::Debug => ImplementsTrait::Yes,
_ => ImplementsTrait::No,
};
Some(implements_trait)
}
}
fn main() -> eyre::Result<()> {
if env_tracked("DOCS_RS").as_deref() == Some("1") {
return Ok(());
}
if env_tracked("PGRX_BUILD_VERBOSE").as_deref() == Some("true") {
for (k, v) in std::env::vars() {
eprintln!("{}={}", k, v);
}
}
let compile_cshim = env_tracked("CARGO_FEATURE_CSHIM").as_deref() == Some("1");
let is_for_release =
env_tracked("PGRX_PG_SYS_GENERATE_BINDINGS_FOR_RELEASE").as_deref() == Some("1");
if is_nightly() {
println!("cargo:rustc-cfg=nightly")
};
let build_paths = BuildPaths::from_env();
eprintln!("build_paths={build_paths:?}");
emit_rerun_if_changed();
let pg_configs: Vec<(u16, PgConfig)> = if is_for_release {
Pgrx::from_config()?.iter(PgConfigSelector::All)
.map(|r| r.expect("invalid pg_config"))
.map(|c| (c.major_version().expect("invalid major version"), c))
.filter_map(|t| {
if is_supported_major_version(t.0) {
Some(t)
} else {
println!(
"cargo:warning={} contains a configuration for pg{}, which pgrx does not support.",
Pgrx::config_toml()
.expect("Could not get PGRX configuration TOML")
.to_string_lossy(),
t.0
);
None
}
})
.collect()
} else {
let mut found = Vec::new();
for pgver in SUPPORTED_VERSIONS() {
if let Some(_) = env_tracked(&format!("CARGO_FEATURE_PG{}", pgver.major)) {
found.push(pgver);
}
}
let found_ver = match &found[..] {
[ver] => ver,
[] => {
return Err(eyre!(
"Did not find `pg$VERSION` feature. `pgrx-pg-sys` requires one of {} to be set",
SUPPORTED_VERSIONS()
.iter()
.map(|pgver| format!("`pg{}`", pgver.major))
.collect::<Vec<_>>()
.join(", ")
))
}
versions => {
return Err(eyre!(
"Multiple `pg$VERSION` features found.\n`--no-default-features` may be required.\nFound: {}",
versions
.into_iter()
.map(|version| format!("pg{}", version.major))
.collect::<Vec<String>>()
.join(", ")
))
}
};
let found_major = found_ver.major;
if let Ok(pg_config) = PgConfig::from_env() {
let major_version = pg_config.major_version()?;
if major_version != found_major {
panic!("Feature flag `pg{found_major}` does not match version from the environment-described PgConfig (`{major_version}`)")
}
vec![(major_version, pg_config)]
} else {
let specific = Pgrx::from_config()?.get(&format!("pg{}", found_ver.major))?;
vec![(found_ver.major, specific)]
}
};
std::thread::scope(|scope| {
let threads = pg_configs
.iter()
.map(|(pg_major_ver, pg_config)| {
scope.spawn(|| {
generate_bindings(*pg_major_ver, pg_config, &build_paths, is_for_release)
})
})
.collect::<Vec<_>>();
let results = threads
.into_iter()
.map(|thread| thread.join().expect("thread panicked while generating bindings"))
.collect::<Vec<eyre::Result<_>>>();
results.into_iter().try_for_each(|r| r)
})?;
if compile_cshim {
for (_version, pg_config) in pg_configs {
build_shim(&build_paths.shim_src, &build_paths.shim_dst, &pg_config)?;
}
}
Ok(())
}
fn emit_rerun_if_changed() {
println!("cargo:rerun-if-env-changed=PGRX_PG_CONFIG_PATH");
println!("cargo:rerun-if-env-changed=PGRX_PG_CONFIG_AS_ENV");
println!("cargo:rerun-if-env-changed=LLVM_CONFIG_PATH");
println!("cargo:rerun-if-env-changed=LIBCLANG_PATH");
println!("cargo:rerun-if-env-changed=LIBCLANG_STATIC_PATH");
println!("cargo:rerun-if-env-changed=BINDGEN_EXTRA_CLANG_ARGS");
if let Some(target) = env_tracked("TARGET") {
println!("cargo:rerun-if-env-changed=BINDGEN_EXTRA_CLANG_ARGS_{target}");
println!(
"cargo:rerun-if-env-changed=BINDGEN_EXTRA_CLANG_ARGS_{}",
target.replace('-', "_"),
);
}
println!("cargo:rerun-if-env-changed=PGRX_PG_SYS_GENERATE_BINDINGS_FOR_RELEASE");
println!("cargo:rerun-if-changed=include");
println!("cargo:rerun-if-changed=cshim");
if let Ok(pgrx_config) = Pgrx::config_toml() {
println!("cargo:rerun-if-changed={}", pgrx_config.display().to_string());
}
}
fn generate_bindings(
major_version: u16,
pg_config: &PgConfig,
build_paths: &BuildPaths,
is_for_release: bool,
) -> eyre::Result<()> {
let mut include_h = build_paths.manifest_dir.clone();
include_h.push("include");
include_h.push(format!("pg{}.h", major_version));
let bindgen_output = get_bindings(major_version, &pg_config, &include_h)
.wrap_err_with(|| format!("bindgen failed for pg{}", major_version))?;
let oids = extract_oids(&bindgen_output);
let rewritten_items = rewrite_items(&bindgen_output, &oids)
.wrap_err_with(|| format!("failed to rewrite items for pg{}", major_version))?;
let oids = format_builtin_oid_impl(oids);
let dest_dirs = if is_for_release {
vec![build_paths.out_dir.clone(), build_paths.src_dir.clone()]
} else {
vec![build_paths.out_dir.clone()]
};
for dest_dir in dest_dirs {
let mut bindings_file = dest_dir.clone();
bindings_file.push(&format!("pg{}.rs", major_version));
write_rs_file(
rewritten_items.clone(),
&bindings_file,
quote! {
use crate as pg_sys;
#[cfg(any(feature = "pg12", feature = "pg13", feature = "pg14", feature = "pg15", feature = "pg16"))]
use crate::NullableDatum;
use crate::{Datum, Oid, PgNode};
},
)
.wrap_err_with(|| {
format!(
"Unable to write bindings file for pg{} to `{}`",
major_version,
bindings_file.display()
)
})?;
let mut oids_file = dest_dir.clone();
oids_file.push(&format!("pg{}_oids.rs", major_version));
write_rs_file(oids.clone(), &oids_file, quote! {}).wrap_err_with(|| {
format!(
"Unable to write oids file for pg{} to `{}`",
major_version,
oids_file.display()
)
})?;
}
Ok(())
}
#[derive(Debug, Clone)]
struct BuildPaths {
manifest_dir: PathBuf,
out_dir: PathBuf,
src_dir: PathBuf,
shim_src: PathBuf,
shim_dst: PathBuf,
}
impl BuildPaths {
fn from_env() -> Self {
let manifest_dir = env_tracked("CARGO_MANIFEST_DIR").map(PathBuf::from).unwrap();
let out_dir = env_tracked("OUT_DIR").map(PathBuf::from).unwrap();
Self {
src_dir: manifest_dir.join("src/include"),
shim_src: manifest_dir.join("cshim"),
shim_dst: out_dir.join("cshim"),
out_dir,
manifest_dir,
}
}
}
fn write_rs_file(
code: proc_macro2::TokenStream,
file: &PathBuf,
header: proc_macro2::TokenStream,
) -> eyre::Result<()> {
let mut contents = header;
contents.extend(code);
std::fs::write(&file, contents.to_string())?;
rust_fmt(&file)
}
fn rewrite_items(
file: &syn::File,
oids: &BTreeMap<syn::Ident, Box<syn::Expr>>,
) -> eyre::Result<proc_macro2::TokenStream> {
let items_vec = rewrite_oid_consts(&file.items, oids);
let mut items = apply_pg_guard(&items_vec)?;
let pgnode_impls = impl_pg_node(&items_vec)?;
items.extend(pgnode_impls);
Ok(items)
}
fn extract_oids(code: &syn::File) -> BTreeMap<syn::Ident, Box<syn::Expr>> {
let mut oids = BTreeMap::new(); for item in &code.items {
match item {
Item::Const(ItemConst { ident, ty, expr, .. }) => {
let name = ident.to_string();
let ty_str = ty.to_token_stream().to_string();
if ty_str == "u32" && is_builtin_oid(&name) {
oids.insert(ident.clone(), expr.clone());
}
}
_ => {}
}
}
oids
}
fn is_builtin_oid(name: &str) -> bool {
if name.ends_with("OID") && name != "HEAP_HASOID" {
true
} else if name.ends_with("RelationId") {
true
} else if name == "TemplateDbOid" {
true
} else {
false
}
}
fn rewrite_oid_consts(
items: &Vec<syn::Item>,
oids: &BTreeMap<syn::Ident, Box<syn::Expr>>,
) -> Vec<syn::Item> {
items
.into_iter()
.map(|item| match item {
Item::Const(ItemConst { ident, ty, expr, .. })
if ty.to_token_stream().to_string() == "u32" && oids.get(ident) == Some(expr) =>
{
syn::parse2(quote! { pub const #ident : Oid = Oid(#expr); }).unwrap()
}
item => item.clone(),
})
.collect()
}
fn format_builtin_oid_impl<'a>(
oids: BTreeMap<syn::Ident, Box<syn::Expr>>,
) -> proc_macro2::TokenStream {
let enum_variants: proc_macro2::TokenStream;
let from_impl: proc_macro2::TokenStream;
(enum_variants, from_impl) = oids
.iter()
.map(|(ident, expr)| {
(quote! { #ident = #expr, }, quote! { #expr => Ok(BuiltinOid::#ident), })
})
.unzip();
quote! {
use crate::{NotBuiltinOid};
#[derive(Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd, Debug)]
pub enum BuiltinOid {
#enum_variants
}
impl BuiltinOid {
pub const fn from_u32(uint: u32) -> Result<BuiltinOid, NotBuiltinOid> {
match uint {
0 => Err(NotBuiltinOid::Invalid),
#from_impl
_ => Err(NotBuiltinOid::Ambiguous),
}
}
}
}
}
fn impl_pg_node(items: &Vec<syn::Item>) -> eyre::Result<proc_macro2::TokenStream> {
let mut pgnode_impls = proc_macro2::TokenStream::new();
let struct_graph: StructGraph = StructGraph::from(&items[..]);
let mut root_node_structs = Vec::new();
for descriptor in struct_graph.descriptors.iter() {
let first_field = match &descriptor.struct_.fields {
syn::Fields::Named(fields) => {
if let Some(first_field) = fields.named.first() {
first_field
} else {
continue;
}
}
syn::Fields::Unnamed(fields) => {
if let Some(first_field) = fields.unnamed.first() {
first_field
} else {
continue;
}
}
_ => continue,
};
let ty_name = if let syn::Type::Path(p) = &first_field.ty {
if let Some(last_segment) = p.path.segments.last() {
last_segment.ident.to_string()
} else {
continue;
}
} else {
continue;
};
if ty_name == "NodeTag" {
root_node_structs.push(descriptor);
}
}
let mut node_set = BTreeSet::new();
for root in root_node_structs.into_iter() {
dfs_find_nodes(root, &struct_graph, &mut node_set);
}
for node_struct in node_set.into_iter() {
let struct_name = &node_struct.struct_.ident;
pgnode_impls.extend(quote! {
impl pg_sys::seal::Sealed for #struct_name {}
impl pg_sys::PgNode for #struct_name {}
});
pgnode_impls.extend(quote! {
impl ::core::fmt::Display for #struct_name {
fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
self.display_node().fmt(f)
}
}
});
}
Ok(pgnode_impls)
}
fn dfs_find_nodes<'graph>(
node: &'graph StructDescriptor<'graph>,
graph: &'graph StructGraph<'graph>,
node_set: &mut BTreeSet<StructDescriptor<'graph>>,
) {
node_set.insert(node.clone());
for child in node.children(graph) {
if node_set.contains(child) {
continue;
}
dfs_find_nodes(child, graph, node_set);
}
}
#[derive(Clone, Debug)]
struct StructGraph<'a> {
#[allow(dead_code)]
name_tab: HashMap<String, usize>,
#[allow(dead_code)]
item_offset_tab: Vec<Option<usize>>,
descriptors: Vec<StructDescriptor<'a>>,
}
impl<'a> From<&'a [syn::Item]> for StructGraph<'a> {
fn from(items: &'a [syn::Item]) -> StructGraph<'a> {
let mut descriptors = Vec::new();
let mut name_tab: HashMap<String, usize> = HashMap::new();
let mut item_offset_tab: Vec<Option<usize>> = vec![None; items.len()];
for (i, item) in items.iter().enumerate() {
if let &syn::Item::Struct(struct_) = &item {
let next_offset = descriptors.len();
descriptors.push(StructDescriptor {
struct_,
items_offset: i,
parent: None,
children: Vec::new(),
});
name_tab.insert(struct_.ident.to_string(), next_offset);
item_offset_tab[i] = Some(next_offset);
}
}
for item in items.iter() {
let (id, first_field) = match &item {
&syn::Item::Struct(syn::ItemStruct {
ident: id,
fields: syn::Fields::Named(fields),
..
}) => {
if let Some(first_field) = fields.named.first() {
(id.to_string(), first_field)
} else {
continue;
}
}
&syn::Item::Struct(syn::ItemStruct {
ident: id,
fields: syn::Fields::Unnamed(fields),
..
}) => {
if let Some(first_field) = fields.unnamed.first() {
(id.to_string(), first_field)
} else {
continue;
}
}
_ => continue,
};
if let syn::Type::Path(p) = &first_field.ty {
if let Some(last_segment) = p.path.segments.last() {
if let Some(parent_offset) = name_tab.get(&last_segment.ident.to_string()) {
let child_offset = name_tab[&id];
descriptors[child_offset].parent = Some(*parent_offset);
descriptors[*parent_offset].children.push(child_offset);
}
}
}
}
StructGraph { name_tab, item_offset_tab, descriptors }
}
}
impl<'a> StructDescriptor<'a> {
fn children(&'a self, graph: &'a StructGraph) -> StructDescriptorChildren {
StructDescriptorChildren { offset: 0, descriptor: self, graph }
}
}
struct StructDescriptorChildren<'a> {
offset: usize,
descriptor: &'a StructDescriptor<'a>,
graph: &'a StructGraph<'a>,
}
impl<'a> std::iter::Iterator for StructDescriptorChildren<'a> {
type Item = &'a StructDescriptor<'a>;
fn next(&mut self) -> Option<&'a StructDescriptor<'a>> {
if self.offset >= self.descriptor.children.len() {
None
} else {
let ret = Some(&self.graph.descriptors[self.descriptor.children[self.offset]]);
self.offset += 1;
ret
}
}
}
#[derive(Clone, Debug, Hash, Eq, PartialEq)]
struct StructDescriptor<'a> {
struct_: &'a syn::ItemStruct,
items_offset: usize,
parent: Option<usize>,
children: Vec<usize>,
}
impl PartialOrd for StructDescriptor<'_> {
#[inline]
fn partial_cmp(&self, other: &StructDescriptor) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for StructDescriptor<'_> {
#[inline]
fn cmp(&self, other: &StructDescriptor) -> Ordering {
self.struct_.ident.cmp(&other.struct_.ident)
}
}
fn get_bindings(
major_version: u16,
pg_config: &PgConfig,
include_h: &PathBuf,
) -> eyre::Result<syn::File> {
let bindings = if let Some(info_dir) =
target_env_tracked(&format!("PGRX_TARGET_INFO_PATH_PG{major_version}"))
{
let bindings_file = format!("{info_dir}/pg{major_version}_raw_bindings.rs");
std::fs::read_to_string(&bindings_file)
.wrap_err_with(|| format!("failed to read raw bindings from {bindings_file}"))?
} else {
let bindings = run_bindgen(major_version, pg_config, include_h)?;
if let Some(path) = env_tracked("PGRX_PG_SYS_EXTRA_OUTPUT_PATH") {
std::fs::write(&path, &bindings)?;
}
bindings
};
syn::parse_file(bindings.as_str()).wrap_err_with(|| "failed to parse generated bindings")
}
fn run_bindgen(
major_version: u16,
pg_config: &PgConfig,
include_h: &PathBuf,
) -> eyre::Result<String> {
eprintln!("Generating bindings for pg{major_version}");
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) = build::clang::detect_include_paths_for(preferred_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_args(&extra_bindgen_clang_args(pg_config)?)
.clang_args(pg_target_include_flags(major_version, pg_config)?)
.detect_include_paths(autodetect)
.parse_callbacks(Box::new(PgrxOverrides::default()))
.rustified_non_exhaustive_enum("NodeTag")
.size_t_is_usize(true)
.merge_extern_blocks(true)
.formatter(bindgen::Formatter::None)
.layout_tests(false)
.generate()
.wrap_err_with(|| format!("Unable to generate bindings for pg{}", major_version))?;
Ok(bindings.to_string())
}
fn add_blocklists(bind: bindgen::Builder) -> bindgen::Builder {
bind.blocklist_type("(Nullable)?Datum") .blocklist_type("Oid") .blocklist_function("varsize_any") .blocklist_function("(?:query|expression)_tree_walker")
.blocklist_function(".*(?:set|long)jmp")
.blocklist_function("pg_re_throw")
.blocklist_function("err(start|code|msg|detail|context_msg|hint|finish)")
.blocklist_item("CONFIGURE_ARGS") .blocklist_item("_*(?:HAVE|have)_.*") .blocklist_item("_[A-Z_]+_H") .blocklist_item("__[A-Z].*") .blocklist_item("__darwin.*") .blocklist_function("pq(?:Strerror|Get.*)") .blocklist_function("log")
.blocklist_item(".*pthread.*)") .blocklist_item(".*(?i:va)_(?i:list|start|end|copy).*") .blocklist_function("(?:pg_|p)v(?:sn?|f)?printf")
.blocklist_function("appendStringInfoVA")
.blocklist_file("stdarg.h")
.blocklist_function("(?:sigstack|sigreturn|siggetmask|gets|vfork|te?mpnam(?:_r)?|mktemp)")
.blocklist_function("inet_net_pton.*")
}
fn add_derives(bind: bindgen::Builder) -> bindgen::Builder {
bind.derive_debug(true)
.derive_copy(true)
.derive_default(true)
.derive_eq(false)
.derive_partialeq(false)
.derive_hash(false)
.derive_ord(false)
.derive_partialord(false)
}
fn env_tracked(s: &str) -> Option<String> {
const CARGO_KEYS: &[&str] = &[
"BROWSER",
"DEBUG",
"DOCS_RS",
"HOST",
"HTTP_PROXY",
"HTTP_TIMEOUT",
"NUM_JOBS",
"OPT_LEVEL",
"OUT_DIR",
"PATH",
"PROFILE",
"TARGET",
"TERM",
];
let is_cargo_key =
s.starts_with("CARGO") || s.starts_with("RUST") || CARGO_KEYS.binary_search(&s).is_ok();
if !is_cargo_key {
println!("cargo:rerun-if-env-changed={s}");
}
std::env::var(s).ok()
}
fn target_env_tracked(s: &str) -> Option<String> {
let target = env_tracked("TARGET").unwrap();
env_tracked(&format!("{s}_{target}")).or_else(|| env_tracked(s))
}
fn pg_target_include_flags(pg_version: u16, pg_config: &PgConfig) -> eyre::Result<Option<String>> {
let var = "PGRX_INCLUDEDIR_SERVER";
let value =
target_env_tracked(&format!("{var}_PG{pg_version}")).or_else(|| target_env_tracked(var));
match value {
None => Ok(Some(format!("-I{}", pg_config.includedir_server()?.display()))),
Some(overridden) if overridden.is_empty() => Ok(None),
Some(overridden) => Ok(Some(format!("-I{overridden}"))),
}
}
fn build_shim(shim_src: &PathBuf, shim_dst: &PathBuf, pg_config: &PgConfig) -> eyre::Result<()> {
let major_version = pg_config.major_version()?;
let mut libpgrx_cshim: PathBuf = shim_dst.clone();
libpgrx_cshim.push(format!("libpgrx-cshim-{}.a", major_version));
eprintln!("libpgrx_cshim={}", libpgrx_cshim.display());
build_shim_for_version(&shim_src, &shim_dst, pg_config)?;
let envvar_name = format!("CARGO_FEATURE_PG{}", major_version);
if env_tracked(&envvar_name).is_some() {
println!("cargo:rustc-link-search={}", shim_dst.display());
println!("cargo:rustc-link-lib=static=pgrx-cshim-{}", major_version);
}
Ok(())
}
fn build_shim_for_version(
shim_src: &PathBuf,
shim_dst: &PathBuf,
pg_config: &PgConfig,
) -> eyre::Result<()> {
let path_env = prefix_path(pg_config.parent_path());
let major_version = pg_config.major_version()?;
eprintln!("PATH for build_shim={}", path_env);
eprintln!("shim_src={}", shim_src.display());
eprintln!("shim_dst={}", shim_dst.display());
fs::create_dir_all(shim_dst).unwrap();
let makefile_dst = path::Path::join(shim_dst, "./Makefile");
if !makefile_dst.try_exists()? {
fs::copy(path::Path::join(shim_src, "./Makefile"), makefile_dst).unwrap();
}
let cshim_dst = path::Path::join(shim_dst, "./pgrx-cshim.c");
if !cshim_dst.try_exists()? {
fs::copy(path::Path::join(shim_src, "./pgrx-cshim.c"), cshim_dst).unwrap();
}
let make = env_tracked("MAKE").unwrap_or_else(|| "make".to_string());
let rc = run_command(
Command::new(make)
.arg("clean")
.arg(&format!("libpgrx-cshim-{}.a", major_version))
.env("PG_TARGET_VERSION", format!("{}", major_version))
.env("PATH", path_env)
.env_remove("TARGET")
.env_remove("HOST")
.current_dir(shim_dst),
&format!("shim for PG v{}", major_version),
)?;
if rc.status.code().unwrap() != 0 {
return Err(eyre!("failed to make pgrx-cshim for v{}", major_version));
}
Ok(())
}
fn extra_bindgen_clang_args(pg_config: &PgConfig) -> eyre::Result<Vec<String>> {
let mut out = vec![];
if env_tracked("CARGO_CFG_TARGET_OS").as_deref() == Some("macos") {
let flags = pg_config.cppflags()?;
let flags = shlex::split(&flags.to_string_lossy()).unwrap_or_default();
out.extend(flags.iter().cloned());
for pair in flags.windows(2) {
if pair[0] == "-isysroot" {
if !std::path::Path::new(&pair[1]).exists() {
let major_version = pg_config.major_version()?;
println!(
"cargo:warning=postgres v{major_version} was compiled against an \
SDK Root which does not seem to exist on this machine ({}). You may \
need to re-run `cargo pgrx init` and/or update your command line tools.",
pair[1],
);
};
break;
}
}
}
Ok(out)
}
fn run_command(mut command: &mut Command, version: &str) -> eyre::Result<Output> {
let mut dbg = String::new();
command = command
.env_remove("DEBUG")
.env_remove("MAKEFLAGS")
.env_remove("MAKELEVEL")
.env_remove("MFLAGS")
.env_remove("DYLD_FALLBACK_LIBRARY_PATH")
.env_remove("OPT_LEVEL")
.env_remove("PROFILE")
.env_remove("OUT_DIR")
.env_remove("NUM_JOBS");
eprintln!("[{}] {:?}", version, command);
dbg.push_str(&format!("[{}] -------- {:?} -------- \n", version, command));
let output = command.output()?;
let rc = output.clone();
if !output.stdout.is_empty() {
for line in String::from_utf8(output.stdout).unwrap().lines() {
if line.starts_with("cargo:") {
dbg.push_str(&format!("{}\n", line));
} else {
dbg.push_str(&format!("[{}] [stdout] {}\n", version, line));
}
}
}
if !output.stderr.is_empty() {
for line in String::from_utf8(output.stderr).unwrap().lines() {
dbg.push_str(&format!("[{}] [stderr] {}\n", version, line));
}
}
dbg.push_str(&format!("[{}] /----------------------------------------\n", version));
eprintln!("{}", dbg);
Ok(rc)
}
static BLOCKLISTED: Lazy<BTreeSet<&'static str>> =
Lazy::new(|| build::sym_blocklist::SYMBOLS.iter().copied().collect::<BTreeSet<&str>>());
fn is_blocklisted_item(item: &ForeignItem) -> bool {
let sym_name = match item {
ForeignItem::Fn(f) => &f.sig.ident,
ForeignItem::Static(s) => &s.ident,
_ => return false,
};
BLOCKLISTED.contains(sym_name.to_string().as_str())
}
fn apply_pg_guard(items: &Vec<syn::Item>) -> eyre::Result<proc_macro2::TokenStream> {
let mut out = proc_macro2::TokenStream::new();
for item in items {
match item {
Item::ForeignMod(block) => {
let abi = &block.abi;
let (mut extern_funcs, mut others) = (Vec::new(), Vec::new());
block.items.iter().cloned().filter(|item| !is_blocklisted_item(item)).for_each(
|item| match item {
ForeignItem::Fn(func) => extern_funcs.push(func),
item => others.push(item),
},
);
out.extend(quote! {
#[pgrx_macros::pg_guard]
#abi { #(#extern_funcs)* }
});
out.extend(quote! { #abi { #(#others)* } });
}
_ => {
out.extend(item.into_token_stream());
}
}
}
Ok(out)
}
fn rust_fmt(path: &PathBuf) -> eyre::Result<()> {
let rustfmt = env_tracked("RUSTFMT").unwrap_or_else(|| "rustfmt".into());
let out = run_command(Command::new(rustfmt).arg(path).current_dir("."), "[bindings_diff]");
match out {
Ok(_) => Ok(()),
Err(e)
if e.downcast_ref::<std::io::Error>()
.ok_or(eyre!("Couldn't downcast error ref"))?
.kind()
== std::io::ErrorKind::NotFound =>
{
Err(e).wrap_err("Failed to run `rustfmt`, is it installed?")
}
Err(e) => Err(e.into()),
}
}