mod ast;
mod cpp;
mod rust;
mod scan;
mod swift;
mod util;
mod wasm;
use ast::*;
use rust::*;
use std::fmt::Write;
use std::path::{Path, PathBuf};
use syn::spanned::Spanned;
use util::*;
pub use cpp::CppTarget;
pub use swift::SwiftTarget;
pub use wasm::WasmTarget;
use crate::scan::make_warning;
#[cfg(test)]
mod tests;
const DO_NOT_EDIT_COMMENT: &str = concat!(
"This file was generated by miniffi v",
env!("CARGO_PKG_VERSION"),
". Do not edit."
);
pub trait Target {
fn build(&self) -> BuildResult {
fn fail(error: &str) -> BuildResult {
let mut result = BuildResult::default();
result.errors.push(error.into());
return result;
}
let Ok(input_dir) = std::env::var("CARGO_MANIFEST_DIR") else {
return fail("missing environment variable: CARGO_MANIFEST_DIR");
};
let Ok(output_dir) = std::env::var("OUT_DIR") else {
return fail("missing environment variable: OUT_DIR");
};
let input_path = PathBuf::from(input_dir).join("src").join("lib.rs");
let output_path = PathBuf::from(output_dir).join("miniffi.rs");
match std::fs::read_to_string(&input_path) {
Err(err) => BuildResult {
errors: vec![format!("Failed to read from {input_path:?}: {err}")],
warnings: Vec::new(),
input_path,
output_files: Vec::new(),
finish_on_drop: true,
},
Ok(contents) => {
let input_file = FileData {
path: input_path,
contents,
};
let mut result = self.build_custom(input_file, output_path);
result.finish_on_drop = true;
result
}
}
}
fn build_custom(&self, input_file: FileData, output_path: PathBuf) -> BuildResult;
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct BuildResult {
pub input_path: PathBuf,
pub output_files: Vec<FileData>,
pub errors: Vec<String>,
pub warnings: Vec<Warning>,
pub finish_on_drop: bool,
}
impl BuildResult {
pub fn convert_warnings_to_errors(mut self) -> Self {
self.errors
.extend(self.warnings.drain(..).map(|w| w.to_human_string()));
self
}
pub fn finish(&mut self) {
println!("cargo::rerun-if-changed={}", self.input_path.display());
for file in &mut self.output_files {
println!("cargo::rerun-if-changed={}", file.path.display());
write_file(&file.path, &file.contents, &mut self.errors);
}
for error in &self.errors {
println!(
"cargo::error=[miniffi] {}",
error
.split('\n')
.collect::<Vec<_>>()
.join("\ncargo::error=")
);
}
for warning in &self.warnings {
println!(
"cargo::warning=[miniffi] {}",
warning
.to_human_string()
.split('\n')
.collect::<Vec<_>>()
.join("\ncargo::warning=")
);
}
self.finish_on_drop = false;
}
}
impl Drop for BuildResult {
fn drop(&mut self) {
if self.finish_on_drop {
self.finish();
}
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct Warning {
pub path: PathBuf,
pub line: usize,
pub column: usize,
pub len: usize,
pub message: String,
pub code: String,
pub note: String,
}
impl Warning {
pub fn to_short_string(&self) -> String {
format!(
"{}:{}:{}: {}{}",
self.path.display(),
self.line,
self.column,
self.message,
match self.note.as_str() {
"" => String::new(),
note => format!(" ({note})"),
}
)
}
pub fn to_human_string(&self) -> String {
let line = self.line.to_string();
let indent = " ".repeat(line.len());
format!(
"{}\n{indent}--> {}:{}:{}\n{indent} |\n{line} | {}\n{indent} |{}{}{}{}\n",
self.message,
self.path.display(),
self.line,
self.column,
self.code,
" ".repeat(self.column),
"^".repeat(self.len.max(1)),
if self.note.is_empty() { "" } else { " " },
self.note
)
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct FileData {
pub path: PathBuf,
pub contents: String,
}
trait Compile {
fn compile(&self, ast: AST, rust_path: PathBuf) -> Vec<FileData>;
}
impl<T: Compile> Target for T {
fn build_custom(&self, input_file: FileData, output_path: PathBuf) -> BuildResult {
let mut output_files = Vec::new();
let mut errors = Vec::new();
let mut warnings = Vec::new();
if !input_file.contents.contains("miniffi.rs") {
let code = r#"include!(concat!(env!("OUT_DIR"), "/miniffi.rs"));"#;
let file = input_file
.path
.file_name()
.unwrap_or(input_file.path.as_os_str())
.display();
errors.push(format!("Please add this to the end of {file:?}: {code}"));
}
match syn::parse_file(&input_file.contents) {
Err(err) => {
let message = format!("{err}");
warnings.push(make_warning(&input_file, message, "", err.span()));
}
Ok(ast) => {
let ast = scan::scan_ast(&input_file, &ast, &mut warnings);
output_files = self.compile(ast, output_path);
}
}
BuildResult {
input_path: input_file.path,
output_files,
errors,
warnings,
finish_on_drop: false,
}
}
}
pub struct NullTarget {
_force_new: (),
}
impl NullTarget {
pub fn new() -> NullTarget {
NullTarget { _force_new: () }
}
}
impl Compile for NullTarget {
fn compile(&self, _ast: AST, rust_path: PathBuf) -> Vec<FileData> {
vec![FileData {
path: rust_path,
contents: String::new(),
}]
}
}
fn write_file(path: &Path, contents: &str, errors: &mut Vec<String>) {
if let Some(parent) = path.parent() {
if let Err(err) = std::fs::create_dir_all(parent) {
errors.push(format!("Failed to create directory {parent:?}: {err}"));
}
}
if let Ok(existing) = std::fs::read(&path) {
if existing == contents.as_bytes() {
return; }
}
if let Err(err) = std::fs::write(&path, contents) {
errors.push(format!("Failed to write to {path:?}: {err}"));
}
}