#![doc(html_logo_url = "https://sixtyfps.io/resources/logo.drawio.svg")]
#![warn(missing_docs)]
use std::env;
use std::io::Write;
use std::path::Path;
use sixtyfps_compilerlib::diagnostics::BuildDiagnostics;
pub struct CompilerConfiguration {
config: sixtyfps_compilerlib::CompilerConfiguration,
}
impl Default for CompilerConfiguration {
fn default() -> Self {
Self {
config: sixtyfps_compilerlib::CompilerConfiguration::new(
sixtyfps_compilerlib::generator::OutputFormat::Rust,
),
}
}
}
impl CompilerConfiguration {
pub fn new() -> Self {
Self::default()
}
pub fn with_include_paths(self, include_paths: Vec<std::path::PathBuf>) -> Self {
let mut config = self.config;
config.include_paths = include_paths;
Self { config }
}
pub fn with_style(self, style: String) -> Self {
let mut config = self.config;
config.style = Some(style);
Self { config }
}
}
#[derive(thiserror::Error, Debug)]
pub enum CompileError {
#[error("Cannot read environment variable CARGO_MANIFEST_DIR or OUT_DIR. The build script need to be run via cargo.")]
NotRunViaCargo,
#[error("{0:?}")]
CompileError(Vec<String>),
#[error("Cannot write the generated file: {0}")]
SaveError(std::io::Error),
}
struct CodeFormatter<Sink> {
indentation: usize,
in_string: bool,
sink: Sink,
}
impl<Sink: Write> Write for CodeFormatter<Sink> {
fn write(&mut self, mut s: &[u8]) -> std::io::Result<usize> {
let len = s.len();
while let Some(idx) = s.iter().position(|c| match c {
b'{' if !self.in_string => {
self.indentation += 1;
true
}
b'}' if !self.in_string => {
self.indentation -= 1;
true
}
b';' if !self.in_string => true,
b'"' if !self.in_string => {
self.in_string = true;
false
}
b'"' if self.in_string => {
self.in_string = false;
false
}
_ => false,
}) {
let idx = idx + 1;
self.sink.write_all(&s[..idx])?;
self.sink.write_all(b"\n")?;
for _ in 0..self.indentation {
self.sink.write_all(b" ")?;
}
s = &s[idx..];
}
self.sink.write_all(s)?;
Ok(len)
}
fn flush(&mut self) -> std::io::Result<()> {
self.sink.flush()
}
}
pub fn compile(path: impl AsRef<std::path::Path>) -> Result<(), CompileError> {
compile_with_config(path, CompilerConfiguration::default())
}
pub fn compile_with_config(
path: impl AsRef<std::path::Path>,
config: CompilerConfiguration,
) -> Result<(), CompileError> {
let path = Path::new(&env::var_os("CARGO_MANIFEST_DIR").ok_or(CompileError::NotRunViaCargo)?)
.join(path.as_ref());
let mut diag = BuildDiagnostics::default();
let syntax_node = sixtyfps_compilerlib::parser::parse_file(&path, &mut diag);
if diag.has_error() {
let vec = diag.to_string_vec();
diag.print();
return Err(CompileError::CompileError(vec));
}
let mut compiler_config = config.config;
if let (Ok(target), Ok(host)) = (env::var("TARGET"), env::var("HOST")) {
if target != host {
compiler_config.embed_resources = true;
}
};
if std::env::var_os("SIXTYFPS_STYLE").is_none() && compiler_config.style.is_none() {
compiler_config.style = std::env::var_os("OUT_DIR").and_then(|path| {
let path = Path::new(&path).parent()?.parent()?.join("SIXTYFPS_DEFAULT_STYLE.txt");
println!("cargo:rerun-if-changed={}", path.display());
let style = std::fs::read_to_string(path).ok()?;
Some(style.trim().into())
});
}
let syntax_node = syntax_node.expect("diags contained no compilation errors");
let (doc, mut diag) = spin_on::spin_on(sixtyfps_compilerlib::compile_syntax_node(
syntax_node,
diag,
compiler_config,
));
if diag.has_error() {
let vec = diag.to_string_vec();
diag.print();
return Err(CompileError::CompileError(vec));
}
let output_file_path = Path::new(&env::var_os("OUT_DIR").ok_or(CompileError::NotRunViaCargo)?)
.join(
path.file_stem()
.map(Path::new)
.unwrap_or_else(|| Path::new("sixtyfps_out"))
.with_extension("rs"),
);
let file = std::fs::File::create(&output_file_path).map_err(CompileError::SaveError)?;
let mut code_formatter = CodeFormatter { indentation: 0, in_string: false, sink: file };
let generated = match sixtyfps_compilerlib::generator::rust::generate(&doc, &mut diag) {
Some(code) => {
for x in &diag.all_loaded_files {
if x.is_absolute() {
println!("cargo:rerun-if-changed={}", x.display());
}
}
diag.diagnostics_as_string().lines().for_each(|w| {
if !w.is_empty() {
println!("cargo:warning={}", w.strip_prefix("warning: ").unwrap_or(w))
}
});
code
}
None => {
let vec = diag.to_string_vec();
diag.print();
return Err(CompileError::CompileError(vec));
}
};
write!(code_formatter, "{}", generated).map_err(CompileError::SaveError)?;
println!("cargo:rerun-if-changed={}", path.display());
for resource in doc.root_component.embedded_file_resources.borrow().keys() {
println!("cargo:rerun-if-changed={}", resource);
}
println!("cargo:rerun-if-env-changed=SIXTYFPS_STYLE");
println!("cargo:rustc-env=SIXTYFPS_INCLUDE_GENERATED={}", output_file_path.display());
Ok(())
}