use std::{collections::HashMap, path::Path, str::FromStr};
use opendp_tooling::{
Function,
bootstrap::docstring::{get_proof_path, insert_proof_attribute},
proven::filesystem::{find_proof_paths, get_src_dir, write_proof_paths},
};
use proc_macro2::TokenStream;
use syn::{File, Item, ItemFn, Meta, Token, punctuated::Punctuated};
pub fn main() {
println!("cargo:rerun-if-env-changed=OPENDP_SPHINX_PORT");
println!("cargo:rerun-if-env-changed=OPENDP_RUSTDOC_PORT");
println!("cargo:rerun-if-env-changed=OPENDP_REMOTE_SPHINX_URI");
println!("cargo:rerun-if-env-changed=OPENDP_REMOTE_RUSTDOC_URI");
let src_dir = get_src_dir().unwrap();
let proof_paths = find_proof_paths(&src_dir).unwrap();
write_proof_paths(&proof_paths).unwrap();
#[cfg(feature = "bindings")]
generate_header();
if let Some(_modules) = parse_crate(&src_dir, proof_paths).unwrap() {
#[cfg(feature = "bindings")]
{
use opendp_tooling::codegen;
use std::fs::canonicalize;
let base_dir = canonicalize("../python/src/opendp").unwrap();
codegen::write_bindings(base_dir, codegen::python::generate_bindings(&_modules));
let base_dir = canonicalize("../R/opendp").unwrap();
codegen::write_bindings(base_dir.clone(), codegen::r::generate_bindings(&_modules));
std::fs::copy("opendp.h", base_dir.join("src/opendp.h")).unwrap();
}
}
}
#[cfg(feature = "bindings")]
fn generate_header() {
use cbindgen::{Config, Language};
use std::env;
let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
let mut config = Config::default();
config.language = Language::C;
config.pragma_once = true;
match cbindgen::generate_with_config(&crate_dir, config) {
Ok(bindings) => bindings.write_to_file("opendp.h"),
Err(cbindgen::Error::ParseSyntaxError { .. }) => return, Err(err) => panic!("{:?}", err),
};
}
fn parse_crate(
src_dir: &Path,
proof_paths: HashMap<String, Option<String>>,
) -> std::io::Result<Option<HashMap<String, Vec<Function>>>> {
let mut modules = HashMap::new();
for entry in std::fs::read_dir(src_dir)? {
let path = entry?.path();
println!("cargo:rerun-if-changed={}", path.display());
let module_name =
if let Some(name) = path.file_name().expect("paths are canonicalized").to_str() {
name.to_string()
} else {
continue;
};
if path.is_dir() {
if let Some(module) = parse_file_tree(&path, &proof_paths, &module_name)? {
if !module.is_empty() {
let mut module = module.into_iter().collect::<Vec<Function>>();
module.sort_by_key(|func| func.name.clone());
modules.insert(module_name, module);
}
} else {
return Ok(None);
}
}
}
Ok(Some(modules))
}
fn parse_file_tree(
dir: &Path,
proof_paths: &HashMap<String, Option<String>>,
module_name: &str,
) -> std::io::Result<Option<Vec<Function>>> {
use std::{fs::File, io::Read};
let mut matches = Vec::new();
if dir.is_dir() {
for entry in std::fs::read_dir(dir)? {
let path = entry?.path();
if path.is_dir() {
if let Some(parsed) = parse_file_tree(&path, &proof_paths, &module_name)? {
matches.extend(parsed);
} else {
return Ok(None);
};
} else {
if path.extension().unwrap_or_default() != "rs" {
continue;
}
let mut contents = String::new();
File::open(&path)?.read_to_string(&mut contents)?;
if let Some(funcs) = parse_file(contents, &proof_paths, module_name) {
matches.extend(funcs);
} else {
return Ok(None);
}
};
}
}
Ok(Some(matches))
}
fn parse_file(
text: String,
proof_paths: &HashMap<String, Option<String>>,
module_name: &str,
) -> Option<Vec<Function>> {
let ts = TokenStream::from_str(&text).ok()?;
let items = syn::parse2::<File>(ts).ok()?.items;
fn flatten_fns(item: Item) -> Vec<ItemFn> {
match item {
Item::Fn(func) => vec![func],
Item::Mod(module) => module
.content
.map(|v| v.1.into_iter().flat_map(flatten_fns).collect())
.unwrap_or_else(Vec::new),
_ => Vec::new(),
}
}
fn path_is_eq(path: &syn::Path, name: &str) -> bool {
path.get_ident().map(ToString::to_string).as_deref() == Some(name)
}
(items.into_iter())
.flat_map(flatten_fns)
.filter(|func| (func.attrs.iter()).any(|attr| path_is_eq(&attr.path(), "bootstrap")))
.map(|mut item_fn| {
let idx = (item_fn.attrs.iter())
.position(|attr| path_is_eq(&attr.path(), "bootstrap"))
.expect("bootstrap attr always exists because of filter");
let bootstrap_attr = item_fn.attrs.remove(idx);
let attr_args = match bootstrap_attr.meta {
Meta::List(ml) => ml
.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)
.ok()?
.into_iter()
.collect(),
_ => return None,
};
if let Some(proof_path) = get_proof_path(&attr_args, &item_fn, &proof_paths).ok()? {
insert_proof_attribute(&mut item_fn.attrs, proof_path).ok()?;
}
Function::from_ast(attr_args, item_fn, Some(module_name)).ok()
})
.collect()
}