diff --git a/tool/src/common/mod.rs b/tool/src/common/mod.rs index faf94ec31..3bc5ff3b5 100644 --- a/tool/src/common/mod.rs +++ b/tool/src/common/mod.rs @@ -21,6 +21,7 @@ pub struct FileMap { } impl FileMap { + #[allow(dead_code)] pub fn new(files: HashMap) -> Self { FileMap { files: RefCell::new(files), diff --git a/tool/src/cpp/docs.rs b/tool/src/cpp/docs.rs index 722bb286c..28bd73eca 100644 --- a/tool/src/cpp/docs.rs +++ b/tool/src/cpp/docs.rs @@ -2,7 +2,7 @@ use colored::*; use diplomat_core::Env; use std::fmt::Write; use std::fs; -use std::path::PathBuf; +use std::path::Path; use std::{collections::HashMap, fmt}; use diplomat_core::ast; @@ -14,7 +14,7 @@ use crate::docs_util::{CppRst, FromMarkdown}; /// Generate RST-formatted Sphinx docs for all FFI types. pub fn gen_docs( env: &Env, - library_config_path: &Option, + library_config_path: Option<&Path>, outs: &mut HashMap, docs_url_gen: &ast::DocsUrlGenerator, ) -> fmt::Result { diff --git a/tool/src/cpp/mod.rs b/tool/src/cpp/mod.rs index 9a8869996..a5e3ace11 100644 --- a/tool/src/cpp/mod.rs +++ b/tool/src/cpp/mod.rs @@ -3,7 +3,7 @@ use std::collections::{HashMap, HashSet}; use std::fmt; use std::fmt::Write; use std::fs; -use std::path::PathBuf; +use std::path::Path; use diplomat_core::ast; use diplomat_core::Env; @@ -52,7 +52,7 @@ fn render_header(typ_name: &ast::Ident, headers: &[String]) -> String { pub fn gen_bindings( env: &Env, - library_config_path: &Option, + library_config_path: Option<&Path>, docs_url_gen: &ast::DocsUrlGenerator, outs: &mut HashMap, ) -> fmt::Result { diff --git a/tool/src/cpp/test_util.rs b/tool/src/cpp/test_util.rs index 06a815d8e..977a8f75a 100644 --- a/tool/src/cpp/test_util.rs +++ b/tool/src/cpp/test_util.rs @@ -20,7 +20,7 @@ macro_rules! test_file { let mut out_texts = std::collections::HashMap::new(); - crate::cpp::gen_bindings(&env, &None, &Default::default(), &mut out_texts).unwrap(); + crate::cpp::gen_bindings(&env, None, &Default::default(), &mut out_texts).unwrap(); out_texts.retain(|k, _| !k.ends_with(".h")); out_texts.remove("diplomat_runtime.hpp"); @@ -58,7 +58,7 @@ macro_rules! test_file_using_library_config { use std::path::PathBuf; let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); path.push("src/cpp/configs/mfbt.toml"); - crate::cpp::gen_bindings(&env, &Some(path), &Default::default(), &mut out_texts).unwrap(); + crate::cpp::gen_bindings(&env, Some(&path), &Default::default(), &mut out_texts).unwrap(); out_texts.retain(|k, _| !k.ends_with(".h")); out_texts.remove("diplomat_runtime.hpp"); diff --git a/tool/src/cpp2/formatter.rs b/tool/src/cpp2/formatter.rs index 88a6ca004..26e34b79e 100644 --- a/tool/src/cpp2/formatter.rs +++ b/tool/src/cpp2/formatter.rs @@ -88,6 +88,7 @@ impl<'tcx> Cpp2Formatter<'tcx> { self.c.fmt_ptr(ident, mutability) } + #[allow(dead_code)] pub fn fmt_optional<'a>(&self, ident: &'a str) -> Cow<'a, str> { format!("std::optional<{ident}>").into() } diff --git a/tool/src/dotnet/mod.rs b/tool/src/dotnet/mod.rs index 2130b3691..6f97a013e 100644 --- a/tool/src/dotnet/mod.rs +++ b/tool/src/dotnet/mod.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use std::fmt; use std::fmt::Write; use std::fs; -use std::path::PathBuf; +use std::path::Path; use colored::*; use diplomat_core::Env; @@ -26,7 +26,7 @@ const SCOPE_CLOSING: &str = "}"; pub fn gen_bindings( env: &Env, - library_config_path: &Option, + library_config_path: Option<&Path>, docs_url_gen: &diplomat_core::ast::DocsUrlGenerator, outs: &mut HashMap, ) -> fmt::Result { diff --git a/tool/src/js/display.rs b/tool/src/js/display.rs index a1b00e74d..133492287 100644 --- a/tool/src/js/display.rs +++ b/tool/src/js/display.rs @@ -130,6 +130,7 @@ where } /// An `fmt::Display` type returned by [`iife`]. +#[allow(clippy::upper_case_acronyms)] pub struct IIFE(F) where F: Fn(Indented) -> Result; diff --git a/tool/src/lib.rs b/tool/src/lib.rs index 5a85b2ed2..46f3a5675 100644 --- a/tool/src/lib.rs +++ b/tool/src/lib.rs @@ -1,13 +1,225 @@ // Enable once https://github.com/rust-lang/rust/issues/89554 is stable // #![deny(non_exhaustive_omitted_patterns)] // diplomat_core uses non_exhaustive a lot; we should never miss its patterns +#[doc(hidden)] pub mod c; +#[doc(hidden)] pub mod c2; +#[doc(hidden)] pub mod common; +#[doc(hidden)] pub mod cpp; +#[doc(hidden)] pub mod cpp2; -mod docs_util; +#[doc(hidden)] pub mod dotnet; +#[doc(hidden)] pub mod js; + +mod docs_util; mod layout; mod util; + +use colored::*; +use core::panic; +use diplomat_core::{ast, hir}; +use std::collections::HashMap; +use std::fs::File; +use std::io::Write; +use std::path::Path; + +pub use ast::DocsUrlGenerator; + +pub fn gen( + entry: &Path, + target_language: &str, + out_folder: &Path, + docs_out_folder: Option<&Path>, + docs_url_gen: &ast::DocsUrlGenerator, + library_config: Option<&Path>, + silent: bool, +) -> std::io::Result<()> { + // Check that user-provided paths exist. Exit early with a nice error message + // if anything doesn't exist. + exit_if_path_missing( + entry, + if entry.file_name().map(|e| e == "lib.rs").unwrap_or_default() { + "Could not find the lib.rs file to process." + } else { + "The entry file does not exist." + }, + ); + exit_if_path_missing(out_folder, "The out folder does not exist."); + if let Some(docs_out_folder) = docs_out_folder { + exit_if_path_missing(docs_out_folder, "The docs folder does not exist."); + } + if let Some(library_config) = library_config { + exit_if_path_missing( + library_config, + "The library configuration file does not exist.", + ); + } + + let lib_file = syn_inline_mod::parse_and_inline_modules(entry); + let diplomat_file = ast::File::from(&lib_file); + let env = diplomat_file.all_types(); + + let errors = diplomat_file.check_validity(&env); + if !errors.is_empty() { + for e in errors { + eprintln!("{e}"); + } + panic!(); + } + + let mut out_texts: HashMap = HashMap::new(); + + let mut errors_found = false; + + match target_language { + "js" => js::gen_bindings(&env, &mut out_texts, Some(docs_url_gen)).unwrap(), + "c" => c::gen_bindings(&env, &mut out_texts).unwrap(), + "cpp" => { + c::gen_bindings(&env, &mut out_texts).unwrap(); + cpp::gen_bindings(&env, library_config, docs_url_gen, &mut out_texts).unwrap() + } + "dotnet" => { + dotnet::gen_bindings(&env, library_config, docs_url_gen, &mut out_texts).unwrap() + } + "c2" | "cpp-c2" | "cpp2" => { + let mut attr_validator = hir::BasicAttributeValidator::new(target_language); + + if target_language == "c2" { + attr_validator.other_backend_names.push("c".into()); + } else { + attr_validator.other_backend_names.push("cpp".into()); + // C backends cannot rename types using backend attributes + // In the future we may add a c_rename attribute + attr_validator.support.renaming = true; + } + + attr_validator.support.disabling = true; + // cpp-c2 is a testing backend, we're not going to treat it as a real c/cpp backend + // since the ast-cpp backend doesn't know about attributes. + + let tcx = match hir::TypeContext::from_ast(&env, attr_validator) { + Ok(context) => context, + Err(e) => { + for err in e { + eprintln!("Lowering error: {err}"); + } + std::process::exit(1); + } + }; + let files = common::FileMap::default(); + let mut context = c2::CContext::new(&tcx, files); + context.run(); + + let errors = context.errors.take_all(); + + if !errors.is_empty() { + eprintln!("Found errors whilst generating {target_language}:"); + for error in errors { + eprintln!("\t{}: {}", error.0, error.1); + } + errors_found = true; + } + + out_texts = context.files.take_files(); + + if target_language == "cpp-c2" { + cpp::gen_bindings(&env, library_config, docs_url_gen, &mut out_texts).unwrap() + } + if target_language == "cpp2" { + let files = common::FileMap::default(); + let mut context = cpp2::Cpp2Context::new(&tcx, files); + context.run(); + out_texts.extend(context.files.take_files()); + + let errors = context.errors.take_all(); + + if !errors.is_empty() { + eprintln!("Found errors whilst generating {target_language}:"); + for error in errors { + eprintln!("\t{}: {}", error.0, error.1); + } + errors_found = true; + } + } + } + o => panic!("Unknown target: {}", o), + } + + if errors_found { + eprintln!("Not generating files due to errors"); + // Eventually this should use eyre or something + std::process::exit(1); + } + + if !silent { + println!( + "{}", + format!("Generating {} bindings:", target_language) + .green() + .bold() + ); + } + + for (subpath, text) in out_texts { + let out_path = out_folder.join(subpath); + let mut out_file = File::create(&out_path)?; + out_file.write_all(text.as_bytes())?; + if !silent { + println!("{}", format!(" {}", out_path.display()).dimmed()); + } + } + + if let Some(docs_out_folder) = docs_out_folder { + if !silent { + println!( + "{}", + format!("Generating {} docs:", target_language) + .green() + .bold() + ); + } + + let mut docs_out_texts: HashMap = HashMap::new(); + + match target_language { + "js" => js::docs::gen_docs(&env, &mut docs_out_texts, docs_url_gen).unwrap(), + "cpp" | "cpp-c2" => { + cpp::docs::gen_docs(&env, library_config, &mut docs_out_texts, docs_url_gen) + .unwrap() + } + "c" => todo!("Docs generation for C"), + "dotnet" => todo!("Docs generation for .NET?"), + o => panic!("Unknown target: {}", o), + } + + for (subpath, text) in docs_out_texts { + let out_path = docs_out_folder.join(subpath); + let mut out_file = File::create(&out_path)?; + out_file.write_all(text.as_bytes())?; + if !silent { + println!("{}", format!(" {}", out_path.display()).dimmed()); + } + } + } + + Ok(()) +} + +/// Provide nice error messages if a folder doesn't exist. +fn exit_if_path_missing(path: &Path, message: &str) { + if !path.exists() { + let current_dir = std::env::current_dir().expect("Filed to load current directory."); + eprintln!( + "{}{}\n{}", + "Error: ".red().bold(), + message, + format!("{}", Path::new(¤t_dir).join(path).display()).red() + ); + std::process::exit(1); + } +} diff --git a/tool/src/main.rs b/tool/src/main.rs index 0e76da288..7830aac63 100644 --- a/tool/src/main.rs +++ b/tool/src/main.rs @@ -1,16 +1,5 @@ -use core::panic; -use std::{ - collections::HashMap, - fs::File, - io::Write, - path::{Path, PathBuf}, - process, -}; - use clap::Parser; -use colored::*; -use diplomat_core::{ast, hir}; -use diplomat_tool::{c, c2, common, cpp, cpp2, dotnet, js}; +use std::path::PathBuf; /// diplomat-tool CLI options, as parsed by [clap-derive]. #[derive(Debug, Parser)] @@ -33,9 +22,9 @@ struct Opt { #[clap(short = 'u', long)] docs_base_urls: Vec, - /// The path to the lib.rs file. Defaults to `src/lib.rs`. - #[clap(short, long, value_parser)] - entry: Option, + /// The path to the lib.rs file. + #[clap(short, long, value_parser, default_value = "src/lib.rs")] + entry: PathBuf, /// The path to an optional config file to override code generation defaults. /// This is currently used by the cpp generator to allow for code to be @@ -47,220 +36,35 @@ struct Opt { silent: bool, } -/// Provide nice error messages if a folder doesn't exist. -fn exit_if_path_missing(path: &Path, message: &str) { - if !path.exists() { - let current_dir = std::env::current_dir().expect("Filed to load current directory."); - eprintln!( - "{}{}\n{}", - "Error: ".red().bold(), - message, - format!("{}", Path::new(¤t_dir).join(path).display()).red() - ); - std::process::exit(1); - } -} - fn main() -> std::io::Result<()> { let opt = Opt::parse(); - let is_custom_entry = opt.entry.is_some(); - let path = opt.entry.unwrap_or_else(|| PathBuf::from("src/lib.rs")); - // Check that user-provided paths exist. Exit early with a nice error message - // if anything doesn't exist. - exit_if_path_missing( - &path, - if is_custom_entry { - "The entry file specified by --entry does not exist." - } else { - "Could not find the lib.rs file to process. Set it manually with the --entry option." - }, - ); - exit_if_path_missing( + diplomat_tool::gen( + &opt.entry, + &opt.target_language, &opt.out_folder, - "The out folder (the second argument) does not exist.", - ); - if let Some(ref docs) = opt.docs { - exit_if_path_missing(docs, "The docs folder specified by --docs does not exist."); - } - if let Some(ref library_config) = opt.library_config { - exit_if_path_missing( - library_config, - "The library configuration file specified by --library-config does not exist.", - ); - } - - let lib_file = syn_inline_mod::parse_and_inline_modules(path.as_path()); - let diplomat_file = ast::File::from(&lib_file); - let env = diplomat_file.all_types(); - - let errors = diplomat_file.check_validity(&env); - if !errors.is_empty() { - for e in errors { - eprintln!("{e}"); - } - panic!(); - } - - let docs_url_gen = ast::DocsUrlGenerator::with_base_urls( - opt.docs_base_urls - .iter() - .filter_map(|entry| entry.strip_prefix("*:").map(ToString::to_string)) - .next(), - opt.docs_base_urls - .iter() - .filter(|entry| !entry.starts_with('*')) - .map(|entry| { - let mut parts = entry.splitn(2, ':'); - ( - parts.next().unwrap().to_string(), - parts - .next() - .expect("Expected syntax |*:") - .to_string(), - ) - }) - .collect(), - ); - - let mut out_texts: HashMap = HashMap::new(); - - let target_language = opt.target_language.as_str(); - let mut errors_found = false; - - match target_language { - "js" => js::gen_bindings(&env, &mut out_texts, Some(&docs_url_gen)).unwrap(), - "c" => c::gen_bindings(&env, &mut out_texts).unwrap(), - "cpp" => { - c::gen_bindings(&env, &mut out_texts).unwrap(); - cpp::gen_bindings(&env, &opt.library_config, &docs_url_gen, &mut out_texts).unwrap() - } - "dotnet" => { - dotnet::gen_bindings(&env, &opt.library_config, &docs_url_gen, &mut out_texts).unwrap() - } - "c2" | "cpp-c2" | "cpp2" => { - let mut attr_validator = hir::BasicAttributeValidator::new(target_language); - - if target_language == "c2" { - attr_validator.other_backend_names.push("c".into()); - } else { - attr_validator.other_backend_names.push("cpp".into()); - // C backends cannot rename types using backend attributes - // In the future we may add a c_rename attribute - attr_validator.support.renaming = true; - } - - attr_validator.support.disabling = true; - // cpp-c2 is a testing backend, we're not going to treat it as a real c/cpp backend - // since the ast-cpp backend doesn't know about attributes. - - let tcx = match hir::TypeContext::from_ast(&env, attr_validator) { - Ok(context) => context, - Err(e) => { - for err in e { - eprintln!("Lowering error: {err}"); - } - std::process::exit(1); - } - }; - let files = common::FileMap::default(); - let mut context = c2::CContext::new(&tcx, files); - context.run(); - - let errors = context.errors.take_all(); - - if !errors.is_empty() { - eprintln!("Found errors whilst generating {target_language}:"); - for error in errors { - eprintln!("\t{}: {}", error.0, error.1); - } - errors_found = true; - } - - out_texts = context.files.take_files(); - - if target_language == "cpp-c2" { - cpp::gen_bindings(&env, &opt.library_config, &docs_url_gen, &mut out_texts).unwrap() - } - if target_language == "cpp2" { - let files = common::FileMap::default(); - let mut context = cpp2::Cpp2Context::new(&tcx, files); - context.run(); - out_texts.extend(context.files.take_files()); - - let errors = context.errors.take_all(); - - if !errors.is_empty() { - eprintln!("Found errors whilst generating {target_language}:"); - for error in errors { - eprintln!("\t{}: {}", error.0, error.1); - } - errors_found = true; - } - } - } - o => panic!("Unknown target: {}", o), - } - - if errors_found { - eprintln!("Not generating files due to errors"); - // Eventually this should use eyre or something - process::exit(1); - } - - if !opt.silent { - println!( - "{}", - format!("Generating {} bindings:", opt.target_language) - .green() - .bold() - ); - } - - for (subpath, text) in out_texts { - let out_path = opt.out_folder.join(subpath); - let mut out_file = File::create(&out_path)?; - out_file.write_all(text.as_bytes())?; - if !opt.silent { - println!("{}", format!(" {}", out_path.display()).dimmed()); - } - } - - if let Some(docs) = opt.docs { - if !opt.silent { - println!( - "{}", - format!("Generating {} docs:", opt.target_language) - .green() - .bold() - ); - } - - let mut docs_out_texts: HashMap = HashMap::new(); - - match opt.target_language.as_str() { - "js" => js::docs::gen_docs(&env, &mut docs_out_texts, &docs_url_gen).unwrap(), - "cpp" | "cpp-c2" => cpp::docs::gen_docs( - &env, - &opt.library_config, - &mut docs_out_texts, - &docs_url_gen, - ) - .unwrap(), - "c" => todo!("Docs generation for C"), - "dotnet" => todo!("Docs generation for .NET?"), - o => panic!("Unknown target: {}", o), - } - - for (subpath, text) in docs_out_texts { - let out_path = docs.join(subpath); - let mut out_file = File::create(&out_path)?; - out_file.write_all(text.as_bytes())?; - if !opt.silent { - println!("{}", format!(" {}", out_path.display()).dimmed()); - } - } - } - - Ok(()) + opt.docs.as_deref(), + &diplomat_core::ast::DocsUrlGenerator::with_base_urls( + opt.docs_base_urls + .iter() + .filter_map(|entry| entry.strip_prefix("*:").map(ToString::to_string)) + .next(), + opt.docs_base_urls + .iter() + .filter(|entry| !entry.starts_with('*')) + .map(|entry| { + let mut parts = entry.splitn(2, ':'); + ( + parts.next().unwrap().to_string(), + parts + .next() + .expect("Expected syntax |*:") + .to_string(), + ) + }) + .collect(), + ), + opt.library_config.as_deref(), + opt.silent, + ) } diff --git a/tool/tests/dotnet_target.rs b/tool/tests/dotnet_target.rs index a9139c10c..f03129295 100644 --- a/tool/tests/dotnet_target.rs +++ b/tool/tests/dotnet_target.rs @@ -8,7 +8,7 @@ macro_rules! test_file { let mut out_texts = std::collections::HashMap::new(); - gen_bindings(&env, &None, &Default::default(), &mut out_texts).unwrap(); + gen_bindings(&env, None, &Default::default(), &mut out_texts).unwrap(); out_texts.remove("DiplomatRuntime.cs"); for out in out_texts.keys() { @@ -30,7 +30,7 @@ macro_rules! test_file_using_library_config { use std::path::PathBuf; let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); path.push("tests/configs/dotnet_example.toml"); - gen_bindings(&env, &Some(path), &Default::default(), &mut out_texts).unwrap(); + gen_bindings(&env, Some(&path), &Default::default(), &mut out_texts).unwrap(); out_texts.remove("DiplomatRuntime.cs"); for out in out_texts.keys() {