use crate::{detect_pg_config, env_tracked, is_for_release};
use bindgen::NonCopyUnionStyle;
use bindgen::callbacks::{DeriveTrait, EnumVariantValue, ImplementsTrait, MacroParsingBehavior};
use eyre::{WrapErr, eyre};
use pgrx_pg_config::{PgConfig, PgMinorVersion, PgVersion, Pgrx, SUPPORTED_VERSIONS};
use quote::{ToTokens, quote};
use std::cell::RefCell;
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
use std::fs;
use std::path::{self, Path, PathBuf}; use std::process::{Command, Output};
use std::rc::Rc;
use syn::{Item, ItemConst};
const BLOCKLISTED_TYPES: [&str; 4] = ["Datum", "NullableDatum", "Oid", "TransactionId"];
const YANKED_POSTGRES_VERSIONS: &[PgVersion] = &[
PgVersion::new(17, PgMinorVersion::Release(1), None),
PgVersion::new(16, PgMinorVersion::Release(5), None),
PgVersion::new(15, PgMinorVersion::Release(9), None),
PgVersion::new(14, PgMinorVersion::Release(14), None),
PgVersion::new(13, PgMinorVersion::Release(17), None),
];
pub(super) mod clang;
#[derive(Debug)]
struct BindingOverride {
ignore_macros: HashSet<&'static str>,
enum_names: InnerMut<EnumMap>,
}
type InnerMut<T> = Rc<RefCell<T>>;
type EnumMap = BTreeMap<String, Vec<(String, EnumVariantValue)>>;
impl BindingOverride {
fn new_from(enum_names: InnerMut<EnumMap>) -> Self {
Self {
ignore_macros: HashSet::from_iter([
"FP_INFINITE",
"FP_NAN",
"FP_NORMAL",
"FP_SUBNORMAL",
"FP_ZERO",
"IPPORT_RESERVED",
"M_E",
"M_LOG2E",
"M_LOG10E",
"M_LN2",
"M_LN10",
"M_PI",
"M_PI_2",
"M_PI_4",
"M_1_PI",
"M_2_PI",
"M_SQRT2",
"M_SQRT1_2",
"M_2_SQRTPI",
]),
enum_names,
}
}
}
impl bindgen::callbacks::ParseCallbacks for BindingOverride {
fn will_parse_macro(&self, name: &str) -> MacroParsingBehavior {
if self.ignore_macros.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 int_macro(&self, _name: &str, _value: i64) -> Option<bindgen::callbacks::IntKind> {
None
}
fn func_macro(&self, _name: &str, _value: &[&[u8]]) {}
fn enum_variant_behavior(
&self,
enum_name: Option<&str>,
variant_name: &str,
variant_value: bindgen::callbacks::EnumVariantValue,
) -> Option<bindgen::callbacks::EnumVariantCustomBehavior> {
enum_name.inspect(|name| match name.strip_prefix("enum").unwrap_or(name).trim() {
"NodeTag" => (),
name if name.contains("unnamed at") || name.contains("anonymous at") => (),
_ if variant_name.contains("OID") => (),
name => self
.enum_names
.borrow_mut()
.entry(name.to_string())
.or_default()
.push((variant_name.to_string(), variant_value)),
});
None
}
fn field_visibility(
&self,
_info: bindgen::callbacks::FieldInfo<'_>,
) -> Option<bindgen::FieldVisibilityKind> {
None
}
}
pub fn main() -> eyre::Result<()> {
println!("cargo:rustc-check-cfg=cfg(docsrs)");
println!("cargo:rerun-if-env-changed=DOCS_RS");
if env_tracked("DOCS_RS").as_deref() == Some("1") {
println!("cargo:rustc-cfg=docsrs");
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 build_paths = BuildPaths::from_env();
eprintln!("build_paths={build_paths:?}");
emit_rerun_if_changed();
let pg_configs = detect_pg_config()?;
for (_, pg_config) in &pg_configs {
let version = pg_config.get_version()?;
if YANKED_POSTGRES_VERSIONS.contains(&version) {
panic!(
"Postgres v{}{} is incompatible with \
other versions in this major series and is not supported by pgrx. Please upgrade \
to the latest version in the v{} series.",
version.major, version.minor, version.major
);
}
}
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(),
compile_cshim,
)
})
})
.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 {
let active_major_version = active_pg_major_version()?;
let pg_config = pg_configs
.iter()
.find(|(major_version, _)| *major_version == active_major_version)
.map(|(_, pg_config)| pg_config)
.ok_or_else(|| {
eyre!("could not find pg_config for active feature pg{active_major_version}")
})?;
build_shim(&build_paths.shim_src, &build_paths.shim_dst, pg_config)?;
}
Ok(())
}
fn active_pg_major_version() -> eyre::Result<u16> {
let found = SUPPORTED_VERSIONS()
.iter()
.filter_map(|pgver| {
env_tracked(&format!("CARGO_FEATURE_PG{}", pgver.major)).map(|_| pgver.major)
})
.collect::<Vec<_>>();
match &found[..] {
[major_version] => Ok(*major_version),
[] => Err(eyre!("did not find a pg$VERSION feature while compiling the cshim")),
versions => Err(eyre!(
"multiple pg$VERSION features found while compiling the cshim: {}",
versions.iter().map(|version| format!("pg{version}")).collect::<Vec<_>>().join(", ")
)),
}
}
fn cshim_static_wrapper_name(major_version: u16) -> String {
format!("pgrx-cshim-static-pg{major_version}")
}
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=pgrx-cshim.c");
if let Ok(pgrx_config) = Pgrx::config_toml() {
println!("cargo:rerun-if-changed={}", pgrx_config.display());
}
}
fn generate_bindings(
major_version: u16,
pg_config: &PgConfig,
build_paths: &BuildPaths,
is_for_release: bool,
enable_cshim: bool,
) -> eyre::Result<()> {
let mut include_h = build_paths.manifest_dir.clone();
include_h.push("include");
include_h.push(format!("pg{major_version}.h"));
let bindgen_output = get_bindings(major_version, pg_config, &include_h, enable_cshim)
.wrap_err_with(|| format!("bindgen failed for pg{major_version}"))?;
let oids = extract_oids(&bindgen_output);
let rewritten_items = rewrite_items(major_version, 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{major_version}.rs"));
write_rs_file(
rewritten_items.clone(),
&bindings_file,
quote! {
use crate as pg_sys;
use crate::{Datum, MultiXactId, Oid, PgNode, TransactionId};
},
is_for_release,
)
.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{major_version}_oids.rs"));
write_rs_file(oids.clone(), &oids_file, quote! {}, is_for_release).wrap_err_with(|| {
format!(
"Unable to write oids file for pg{} to `{}`",
major_version,
oids_file.display()
)
})?;
}
let lib_dir = pg_config.lib_dir()?;
println!(
"cargo:rustc-link-search={}",
lib_dir.to_str().ok_or(eyre!("{lib_dir:?} is not valid UTF-8 string"))?
);
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("pgrx-cshim.c"),
shim_dst: out_dir.join("pgrx-cshim.c"),
out_dir,
manifest_dir,
}
}
}
fn write_rs_file(
code: proc_macro2::TokenStream,
file_path: &Path,
header: proc_macro2::TokenStream,
is_for_release: bool,
) -> eyre::Result<()> {
use std::io::Write;
let mut contents = header;
contents.extend(code);
let mut file = fs::File::create(file_path)?;
write!(file, "/* Automatically generated by bindgen. Do not hand-edit.")?;
if is_for_release {
write!(
file,
"\n
This code is generated for documentation purposes, so that it is
easy to reference on docs.rs. Bindings are regenerated for your
build of pgrx, and the values of your Postgres version may differ.
*/"
)
} else {
write!(file, " */")
}?;
write!(file, "{contents}")?;
rust_fmt(file_path)
}
fn rewrite_items(
major_version: u16,
mut file: syn::File,
oids: &BTreeMap<syn::Ident, Box<syn::Expr>>,
) -> eyre::Result<proc_macro2::TokenStream> {
rewrite_c_abi_to_c_unwind(&mut file);
let items_vec = rewrite_oid_consts(&file.items, oids);
let mut items = apply_pg_guard(&items_vec)?;
let pgnode_impls = impl_pg_node(major_version, &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 {
let Item::Const(ItemConst { ident, ty, expr, .. }) = item else { continue };
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 {
name.ends_with("OID") && name != "HEAP_HASOID"
|| name.ends_with("RelationId")
|| name == "TemplateDbOid"
}
fn rewrite_oid_consts(
items: &[syn::Item],
oids: &BTreeMap<syn::Ident, Box<syn::Expr>>,
) -> Vec<syn::Item> {
items
.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(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(major_version: u16, items: &[syn::Item]) -> eyre::Result<proc_macro2::TokenStream> {
let type_graph = TypeGraph::from(items);
let mut node_tags: BTreeSet<String> = BTreeSet::new();
let mut possible_alias_tags = HashMap::new();
for item in items {
match item {
syn::Item::Enum(item_enum) if item_enum.ident == "NodeTag" => {
node_tags.extend(item_enum.variants.iter().map(|v| v.ident.to_string()))
}
syn::Item::Type(item_type)
if let syn::Type::Path(p) = &*item_type.ty
&& let Some(last) = p.path.segments.last()
&& type_graph.name_tab.contains_key(&last.ident.to_string()) =>
{
let target_name = last.ident.to_string();
let alias_name = item_type.ident.to_string();
let tag_name = format!("T_{}", alias_name);
possible_alias_tags
.entry(target_name)
.or_insert_with(BTreeSet::new)
.insert(tag_name);
}
_ => continue,
}
}
let mut identified_nodes = BTreeMap::new();
for descriptor in type_graph.descriptors.iter() {
let is_node = match descriptor.kind {
TypeKind::Struct(struct_) => {
let first_field = if let syn::Fields::Named(fields) = &struct_.fields {
fields.named.first()
} else if let syn::Fields::Unnamed(fields) = &struct_.fields {
fields.unnamed.first()
} else {
None
};
if let Some(first_field) = first_field
&& let syn::Type::Path(p) = &first_field.ty
&& let Some(last) = p.path.segments.last()
{
last.ident == "NodeTag"
} else {
false
}
}
TypeKind::Union(union_) => union_.fields.named.iter().any(|field| {
if let syn::Type::Path(p) = &field.ty
&& let Some(last) = p.path.segments.last()
{
last.ident == "Node"
} else {
false
}
}),
};
if is_node {
resolve_pg_node_tags(
descriptor,
&type_graph,
&node_tags,
&possible_alias_tags,
&mut identified_nodes,
);
}
}
if (major_version == 13 || major_version == 14)
&& let Some(cast_tags) = identified_nodes.get_mut("Value")
{
cast_tags.extend(
["T_Integer", "T_Float", "T_String", "T_BitString", "T_Null"].map(|s| s.to_string()),
);
}
let mut impls = proc_macro2::TokenStream::new();
for (type_name, cast_tags) in identified_nodes {
let ident_type_name = syn::Ident::new(&type_name, proc_macro2::Span::call_site());
let ident_cast_tags: Vec<syn::Ident> =
cast_tags.iter().map(|t| syn::Ident::new(t, proc_macro2::Span::call_site())).collect();
impls.extend(quote! {
impl pg_sys::seal::Sealed for #ident_type_name {}
});
impls.extend(match type_name.as_str() {
"Node" => quote! {
impl pg_sys::PgNode for #ident_type_name {
const CAST_TAGS: &'static [pg_sys::NodeTag] = &[];
#[inline]
fn try_cast<T: pg_sys::PgNode>(node: &T) -> Option<&Self> {
Some(node.as_node())
}
}
},
_ => quote! {
impl pg_sys::PgNode for #ident_type_name {
const CAST_TAGS: &'static [pg_sys::NodeTag] = &[
#(pg_sys::NodeTag::#ident_cast_tags),*
];
}
},
});
impls.extend(quote! {
impl ::core::fmt::Display for #ident_type_name {
fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
self.display_node().fmt(f)
}
}
});
}
Ok(impls)
}
fn resolve_pg_node_tags<'graph>(
descriptor: &'graph TypeDescriptor<'graph>,
type_graph: &'graph TypeGraph<'graph>,
node_tags: &BTreeSet<String>,
possible_alias_tags: &HashMap<String, BTreeSet<String>>,
identified_nodes: &mut BTreeMap<String, BTreeSet<String>>,
) -> BTreeSet<String> {
let type_name = descriptor.ident.to_string();
if let Some(tags) = identified_nodes.get(&type_name) {
return tags.clone();
}
let mut cast_tags = BTreeSet::new();
let possible_tag_name = format!("T_{}", type_name);
if node_tags.contains(&possible_tag_name) {
cast_tags.insert(possible_tag_name);
}
if let Some(possible_tags) = possible_alias_tags.get(&type_name) {
for possible_tag_name in possible_tags {
if node_tags.contains(possible_tag_name) {
cast_tags.insert(possible_tag_name.clone());
}
}
}
if let TypeKind::Struct(_) = descriptor.kind {
for child in descriptor.children(type_graph) {
cast_tags.extend(resolve_pg_node_tags(
child,
type_graph,
node_tags,
possible_alias_tags,
identified_nodes,
));
}
}
identified_nodes.insert(type_name, cast_tags.clone());
cast_tags
}
#[derive(Clone, Debug)]
enum TypeKind<'a> {
Struct(&'a syn::ItemStruct),
Union(&'a syn::ItemUnion),
}
#[derive(Clone, Debug)]
struct TypeGraph<'a> {
#[allow(dead_code)]
name_tab: HashMap<String, usize>,
#[allow(dead_code)]
item_offset_tab: Vec<Option<usize>>,
descriptors: Vec<TypeDescriptor<'a>>,
}
impl<'a> From<&'a [syn::Item]> for TypeGraph<'a> {
fn from(items: &'a [syn::Item]) -> Self {
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() {
let (kind, ident) = match item {
syn::Item::Struct(struct_) => (TypeKind::Struct(struct_), struct_.ident.clone()),
syn::Item::Union(union_) => (TypeKind::Union(union_), union_.ident.clone()),
_ => continue,
};
let next_offset = descriptors.len();
descriptors.push(TypeDescriptor {
kind,
ident: ident.clone(),
items_offset: i,
parent: None,
children: Vec::new(),
});
name_tab.insert(ident.to_string(), next_offset);
item_offset_tab[i] = Some(next_offset);
}
for item in items.iter() {
match item {
syn::Item::Struct(struct_) => {
let first_field = if let syn::Fields::Named(fields) = &struct_.fields {
fields.named.first()
} else if let syn::Fields::Unnamed(fields) = &struct_.fields {
fields.unnamed.first()
} else {
None
};
if let Some(first_field) = first_field
&& let syn::Type::Path(p) = &first_field.ty
&& let Some(last_segment) = p.path.segments.last()
&& let Some(parent_offset) = name_tab.get(&last_segment.ident.to_string())
{
let child_offset = name_tab[&struct_.ident.to_string()];
descriptors[child_offset].parent = Some(*parent_offset);
descriptors[*parent_offset].children.push(child_offset);
}
}
syn::Item::Union(union_) => {
let union_offset = name_tab[&union_.ident.to_string()];
for field in &union_.fields.named {
if let syn::Type::Path(p) = &field.ty
&& let Some(last_segment) = p.path.segments.last()
&& let Some(child_offset) =
name_tab.get(&last_segment.ident.to_string())
{
descriptors[*child_offset].parent = Some(union_offset);
descriptors[union_offset].children.push(*child_offset);
}
}
}
_ => continue,
}
}
TypeGraph { name_tab, item_offset_tab, descriptors }
}
}
impl<'a> TypeDescriptor<'a> {
fn children(&'a self, graph: &'a TypeGraph) -> TypeDescriptorChildren<'a> {
TypeDescriptorChildren { offset: 0, descriptor: self, graph }
}
}
struct TypeDescriptorChildren<'a> {
offset: usize,
descriptor: &'a TypeDescriptor<'a>,
graph: &'a TypeGraph<'a>,
}
impl<'a> std::iter::Iterator for TypeDescriptorChildren<'a> {
type Item = &'a TypeDescriptor<'a>;
fn next(&mut self) -> Option<&'a TypeDescriptor<'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)]
struct TypeDescriptor<'a> {
kind: TypeKind<'a>,
ident: syn::Ident,
#[allow(dead_code)]
items_offset: usize,
parent: Option<usize>,
children: Vec<usize>,
}
fn get_bindings(
major_version: u16,
pg_config: &PgConfig,
include_h: &path::Path,
enable_cshim: bool,
) -> 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, enable_cshim)?;
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: &path::Path,
enable_cshim: bool,
) -> 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 pg_target_includes = pg_target_includes(major_version, pg_config)?;
eprintln!("pg_target_includes = {pg_target_includes:?}");
let (autodetect, includes) = clang::detect_include_paths_for(preferred_clang);
let mut binder = bindgen::Builder::default();
binder = add_blocklists(binder, major_version, enable_cshim);
binder = add_allowlists(binder, pg_target_includes.iter().map(|x| x.as_str()));
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 enum_names = Rc::new(RefCell::new(BTreeMap::new()));
let overrides = BindingOverride::new_from(Rc::clone(&enum_names));
let out_path = PathBuf::from(std::env::var("OUT_DIR").unwrap());
let bindings = binder
.header(include_h.display().to_string())
.clang_args(extra_bindgen_clang_args(pg_config)?)
.clang_args(pg_target_includes.iter().map(|x| format!("-I{x}")))
.detect_include_paths(autodetect)
.parse_callbacks(Box::new(overrides))
.default_enum_style(bindgen::EnumVariation::ModuleConsts)
.rustified_non_exhaustive_enum("NodeTag")
.size_t_is_usize(true)
.merge_extern_blocks(true)
.wrap_unsafe_ops(true)
.use_core()
.generate_cstr(true)
.disable_nested_struct_naming()
.formatter(bindgen::Formatter::None)
.layout_tests(false)
.default_non_copy_union_style(NonCopyUnionStyle::ManuallyDrop)
.wrap_static_fns(enable_cshim)
.wrap_static_fns_path(out_path.join(cshim_static_wrapper_name(major_version)))
.wrap_static_fns_suffix("__pgrx_cshim")
.generate()
.wrap_err_with(|| format!("Unable to generate bindings for pg{major_version}"))?;
Ok(bindings.to_string())
}
fn add_blocklists(
bind: bindgen::Builder,
major_version: u16,
enable_cshim: bool,
) -> bindgen::Builder {
let bind = if major_version >= 19 {
bind.blocklist_function("TransactionId(Precedes|PrecedesOrEquals|Follows|FollowsOrEquals)")
} else {
bind
};
let bind = if major_version < 16 || !enable_cshim {
bind.blocklist_function("BufferGetBlock").blocklist_function("BufferGetPage")
} else {
bind
};
bind.blocklist_type("Datum") .blocklist_type("Oid") .blocklist_type("TransactionId") .blocklist_type("MultiXactId") .blocklist_var("CONFIGURE_ARGS") .blocklist_var("_*(?:HAVE|have)_.*") .blocklist_var("_[A-Z_]+_H") .blocklist_function("pg_re_throw")
.blocklist_function("err(start|code|msg|detail|context_msg|hint|finish)")
.blocklist_function("heap_getattr")
.blocklist_function("BufferIsLocal")
.blocklist_function("GetMemoryChunkContext")
.blocklist_function("GETSTRUCT")
.blocklist_function("MAXALIGN")
.blocklist_function("MemoryContextIsValid")
.blocklist_function("MemoryContextSwitchTo")
.blocklist_function("TYPEALIGN")
.blocklist_function("TransactionIdIsNormal")
.blocklist_function("expression_tree_walker")
.blocklist_function("get_pg_major_minor_version_string")
.blocklist_function("get_pg_major_version_num")
.blocklist_function("get_pg_major_version_string")
.blocklist_function("get_pg_version_string")
.blocklist_function("heap_tuple_get_struct")
.blocklist_function("planstate_tree_walker")
.blocklist_function("query_or_expression_tree_walker")
.blocklist_function("query_tree_walker")
.blocklist_function("range_table_entry_walker")
.blocklist_function("range_table_walker")
.blocklist_function("raw_expression_tree_walker")
.blocklist_function("type_is_array")
.blocklist_function("varsize_any")
.blocklist_function("PageValidateSpecialPointer")
.blocklist_function("PageIsValid")
.blocklist_item("ERROR")
.blocklist_function("IsQueryIdEnabled")
}
fn add_allowlists<'a>(
mut bind: bindgen::Builder,
pg_target_includes: impl Iterator<Item = &'a str>,
) -> bindgen::Builder {
for pg_target_include in pg_target_includes {
bind = bind.allowlist_file(format!("{}.*", regex::escape(pg_target_include)))
}
bind.allowlist_item("PGERROR").allowlist_item("SIG.*")
}
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 target_env_tracked(s: &str) -> Option<String> {
let target = env_tracked("TARGET").unwrap();
env_tracked(&format!("{s}_{target}")).or_else(|| env_tracked(s))
}
fn find_include(
pg_version: u16,
var: &str,
default: impl Fn() -> eyre::Result<PathBuf>,
) -> eyre::Result<String> {
let value =
target_env_tracked(&format!("{var}_PG{pg_version}")).or_else(|| target_env_tracked(var));
let path = match value {
None => default()?,
Some(overridden) => Path::new(&overridden).to_path_buf(),
};
let path = std::fs::canonicalize(&path)
.wrap_err(format!("cannot find {path:?} for C header files"))?
.join("") .display()
.to_string();
if let Some(path) = path.strip_prefix("\\\\?\\") { Ok(path.to_string()) } else { Ok(path) }
}
fn pg_target_includes(pg_version: u16, pg_config: &PgConfig) -> eyre::Result<Vec<String>> {
let mut result =
vec![find_include(pg_version, "PGRX_INCLUDEDIR_SERVER", || pg_config.includedir_server())?];
if let Some("msvc") = env_tracked("CARGO_CFG_TARGET_ENV").as_deref() {
result.push(find_include(pg_version, "PGRX_PKGINCLUDEDIR", || pg_config.pkgincludedir())?);
result.push(find_include(pg_version, "PGRX_INCLUDEDIR_SERVER_PORT_WIN32", || {
pg_config.includedir_server_port_win32()
})?);
result.push(find_include(pg_version, "PGRX_INCLUDEDIR_SERVER_PORT_WIN32_MSVC", || {
pg_config.includedir_server_port_win32_msvc()
})?);
}
Ok(result)
}
fn build_shim(
shim_src: &path::Path,
shim_dst: &path::Path,
pg_config: &PgConfig,
) -> eyre::Result<()> {
let major_version = pg_config.major_version()?;
let generated_wrapper = format!("\"{}.c\"", cshim_static_wrapper_name(major_version));
std::fs::copy(shim_src, shim_dst).unwrap();
let mut build = cc::Build::new();
build.define("PGRX_CSHIM_STATIC", Some(generated_wrapper.as_str()));
let compiler = build.get_compiler();
if compiler.is_like_gnu() || compiler.is_like_clang() {
build.flag("-ffunction-sections");
build.flag("-fdata-sections");
}
if compiler.is_like_msvc() {
build.flag("/Gy");
build.flag("/Gw");
}
for pg_target_include in pg_target_includes(major_version, pg_config)?.iter() {
build.flag(format!("-I{pg_target_include}"));
}
for flag in extra_bindgen_clang_args(pg_config)? {
build.flag(&flag);
}
build.file(shim_dst);
build.compile("pgrx-cshim");
Ok(())
}
fn extra_bindgen_clang_args(pg_config: &PgConfig) -> eyre::Result<Vec<String>> {
let mut out = vec![];
let flags = shlex::split(&pg_config.cppflags()?.to_string_lossy()).unwrap_or_default();
if env_tracked("CARGO_CFG_TARGET_OS").as_deref() != Some("windows") {
out.extend(flags.iter().cloned());
}
if env_tracked("CARGO_CFG_TARGET_OS").as_deref() == Some("macos") {
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!("[{version}] -------- {command:?} -------- \n"));
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!("{line}\n"));
} else {
dbg.push_str(&format!("[{version}] [stdout] {line}\n"));
}
}
}
if !output.stderr.is_empty() {
for line in String::from_utf8(output.stderr).unwrap().lines() {
dbg.push_str(&format!("[{version}] [stderr] {line}\n"));
}
}
dbg.push_str(&format!("[{version}] /----------------------------------------\n"));
eprintln!("{dbg}");
Ok(rc)
}
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) => {
out.extend(quote! {
#[pgrx_macros::pg_guard]
#block
});
}
_ => {
out.extend(item.into_token_stream());
}
}
}
Ok(out)
}
fn rewrite_c_abi_to_c_unwind(file: &mut syn::File) {
use proc_macro2::Span;
use syn::LitStr;
use syn::visit_mut::VisitMut;
pub struct Visitor {}
impl VisitMut for Visitor {
fn visit_abi_mut(&mut self, abi: &mut syn::Abi) {
if let Some(name) = &mut abi.name
&& name.value() == "C"
{
*name = LitStr::new("C-unwind", Span::call_site());
}
}
}
Visitor {}.visit_file_mut(file);
}
fn rust_fmt(path: &Path) -> eyre::Result<()> {
let rustfmt = env_tracked("RUSTFMT").unwrap_or_else(|| "rustfmt".into());
let mut command = Command::new(rustfmt);
command.arg(path).args(["--edition", "2021"]).current_dir(".");
let out = run_command(&mut command, "[bindings_diff]");
match out {
Ok(output) if output.status.success() => Ok(()),
Ok(output) => {
let rustfmt_output = format!(
r#"Problems running rustfmt: {command:?}:
{}
{}"#,
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
for line in rustfmt_output.lines() {
println!("cargo:warning={line}");
}
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),
}
}