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::cmp::Ordering;
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 {
BindingOverride {
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<()> {
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 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(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(
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(&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(items: &[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
&& let Some(last_segment) = p.path.segments.last()
{
last_segment.ident.to_string()
} 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
&& 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[&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<'a> {
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: &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);
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) -> bindgen::Builder {
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("BufferGetBlock")
.blocklist_function("BufferGetPage")
.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),
}
}