use anyhow::{Context, Error, Result};
use cairo_lang_sierra::program::Program;
use clap::{Parser, Subcommand};
use console::style;
use mimalloc::MiMalloc;
use serde_json::Value;
use std::fs::File;
use std::io::{self, BufWriter, Write};
use std::path::PathBuf;
mod cache;
mod commands;
use cache::SierraKind;
use commands::compile_contract::CompileContract;
use commands::compile_raw::CompileRaw;
#[global_allocator]
static GLOBAL: MiMalloc = MiMalloc;
#[derive(Parser)]
#[command(version)]
struct Cli {
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
CompileContract(CompileContract),
CompileRaw(CompileRaw),
}
fn print_error_message(error: &Error) {
let error_tag = style("ERROR").red();
eprintln!("[{error_tag}] {error}");
}
#[tracing::instrument(skip_all, level = "info")]
fn deserialize_json<T: for<'de> serde_core::de::Deserialize<'de>>(bytes: &[u8]) -> Result<T> {
serde_json::from_slice(bytes).context("Unable to deserialize JSON")
}
#[tracing::instrument(skip_all, level = "info")]
fn output_casm(output: &Value, output_file_path: Option<PathBuf>) -> Result<()> {
if let Some(output_path) = output_file_path {
let file = File::create(output_path).context("Unable to open/create casm json file")?;
let mut writer = BufWriter::new(file);
serde_json::to_writer(&mut writer, output).context("Unable to save casm json file")?;
writer.flush().context("Unable to save casm json file")?;
} else {
let mut stdout = io::stdout().lock();
serde_json::to_writer(&mut stdout, output).context("Unable to write casm json")?;
writeln!(stdout).context("Unable to write casm json")?;
}
Ok(())
}
fn main_execution() -> Result<bool> {
let cli = Cli::parse();
let _g = init_logging();
match cli.command {
Commands::CompileContract(compile_contract) => {
let sierra_path = compile_contract.sierra_path;
let casm_json = cache::compile_with_cache(
&sierra_path,
SierraKind::Contract,
compile_contract.cache_dir.as_deref(),
|sierra_content| {
let sierra_json = deserialize_json(sierra_content)?;
commands::compile_contract::compile(sierra_json)
},
)?;
output_casm(&casm_json, compile_contract.output_path)?;
}
Commands::CompileRaw(compile_raw) => {
let sierra_path = compile_raw.sierra_path;
let cairo_program_json = cache::compile_with_cache(
&sierra_path,
SierraKind::Raw,
compile_raw.cache_dir.as_deref(),
|sierra_content| {
let sierra_program: Program = deserialize_json(sierra_content).context(
"Unable to deserialize Sierra program. Make sure it is in a correct format",
)?;
commands::compile_raw::compile(&sierra_program)
},
)?;
output_casm(&cairo_program_json, compile_raw.output_path)?;
}
}
Ok(true)
}
fn main() {
match main_execution() {
Ok(true) => std::process::exit(0),
Ok(false) => std::process::exit(1),
Err(error) => {
print_error_message(&error);
std::process::exit(2);
}
};
}
#[cfg(not(feature = "tracing"))]
fn init_logging() -> Option<impl Drop> {
struct Zst;
impl Drop for Zst {
fn drop(&mut self) {}
}
Option::<Zst>::None
}
#[cfg(feature = "tracing")]
fn init_logging() -> Option<impl Drop> {
use chrono::Local;
use std::fs;
use std::path::PathBuf;
use tracing_chrome::ChromeLayerBuilder;
use tracing_subscriber::filter::{EnvFilter, LevelFilter, Targets};
use tracing_subscriber::fmt::time::Uptime;
use tracing_subscriber::fmt::Layer;
use tracing_subscriber::prelude::*;
let mut guard = None;
let fmt_layer = Layer::new()
.with_writer(std::io::stderr)
.with_timer(Uptime::default())
.with_filter(
EnvFilter::builder()
.with_default_directive(LevelFilter::TRACE.into())
.with_env_var("USC_LOG")
.from_env_lossy(),
);
let tracing_profile = is_truthy_env("USC_TRACING_PROFILE", false);
let profile_layer = if tracing_profile {
let mut path = PathBuf::from(format!("./usc-profile-{}.json", Local::now().to_rfc3339()));
let profile_file = fs::File::create(&path).expect("failed to create profile file");
if let Ok(canonical) = fs::canonicalize(&path) {
path = canonical;
}
eprintln!(
"this USC run will output tracing profile to: {}",
path.display()
);
eprintln!(
"open that file with https://ui.perfetto.dev (or chrome://tracing) to analyze it"
);
let (profile_layer, profile_layer_guard) = ChromeLayerBuilder::new()
.writer(profile_file)
.include_args(true)
.build();
let profile_layer = profile_layer.with_filter(
Targets::new()
.with_default(LevelFilter::TRACE)
.with_target("salsa", LevelFilter::WARN),
);
guard = Some(profile_layer_guard);
Some(profile_layer)
} else {
None
};
tracing::subscriber::set_global_default(
tracing_subscriber::registry()
.with(fmt_layer)
.with(profile_layer),
)
.expect("could not set up global logger");
guard
}
#[cfg(feature = "tracing")]
#[must_use]
pub fn is_truthy_env(name: &str, default: bool) -> bool {
std::env::var(name).ok().map_or(default, |var| {
let s = var.as_str();
s == "true" || s == "1"
})
}