use std::fs::{self, File};
use std::io::Write;
use std::path::{Path, PathBuf};
use std::process::{Command, Output, Stdio};
use apollo_infra_utils::cairo0_compiler::Cairo0Script;
use apollo_infra_utils::cairo0_compiler_test_utils::verify_cairo0_compiler_deps;
use apollo_infra_utils::cairo_compiler_version::CAIRO1_COMPILER_VERSION;
use apollo_infra_utils::path::{project_path, resolve_project_relative_path};
use tempfile::NamedTempFile;
use tracing::info;
pub enum CompilationArtifacts {
Cairo0 { casm: Vec<u8> },
Cairo1 { casm: Vec<u8>, sierra: Vec<u8> },
}
pub fn cairo1_compiler_tag() -> String {
format!("v{CAIRO1_COMPILER_VERSION}")
}
fn cairo1_package_dir(version: &String) -> PathBuf {
project_path().unwrap().join(format!("target/bin/cairo_package__{version}"))
}
fn starknet_compile_binary_path(version: &String) -> PathBuf {
cairo1_package_dir(version).join("cairo/bin/starknet-compile")
}
fn starknet_sierra_compile_binary_path(version: &String) -> PathBuf {
cairo1_package_dir(version).join("cairo/bin/starknet-sierra-compile")
}
pub fn allowed_libfuncs_json_path() -> String {
resolve_project_relative_path("crates/apollo_compile_to_casm/src/allowed_libfuncs.json")
.unwrap()
.to_string_lossy()
.to_string()
}
pub fn allowed_libfuncs_legacy_json_path() -> String {
resolve_project_relative_path(
"crates/blockifier_test_utils/resources/allowed_libfuncs_legacy.json",
)
.unwrap()
.to_string_lossy()
.to_string()
}
pub fn generate_allowed_libfuncs_legacy_json() -> String {
let new_format_path = allowed_libfuncs_json_path();
let contents = std::fs::read_to_string(&new_format_path)
.unwrap_or_else(|err| panic!("Failed to read {new_format_path}: {err}"));
let parsed: serde_json::Value = serde_json::from_str(&contents)
.unwrap_or_else(|err| panic!("Failed to parse {new_format_path}: {err}"));
let libfuncs_map = parsed["allowed_libfuncs"]
.as_object()
.unwrap_or_else(|| panic!("Expected 'allowed_libfuncs' to be a map in {new_format_path}"));
let keys: Vec<&str> = libfuncs_map.keys().map(|k| k.as_str()).collect();
serde_json::json!({"allowed_libfuncs": keys}).to_string()
}
fn download_cairo_package(version: &String) {
let directory = cairo1_package_dir(version);
info!("Downloading Cairo package to {directory:?}.");
std::fs::create_dir_all(&directory).unwrap();
let filename = "release-x86_64-unknown-linux-musl.tar.gz";
let package_url =
format!("https://github.com/starkware-libs/cairo/releases/download/v{version}/{filename}");
let curl_result = run_and_verify_output(Command::new("curl").args(["-L", &package_url]));
let mut tar_command = Command::new("tar")
.args(["-xz", "-C", directory.to_str().unwrap()])
.stdin(Stdio::piped())
.spawn()
.unwrap();
let tar_command_stdin = tar_command.stdin.as_mut().unwrap();
tar_command_stdin.write_all(&curl_result.stdout).unwrap();
let output = tar_command.wait_with_output().unwrap();
if !output.status.success() {
let stderr_output = String::from_utf8(output.stderr).unwrap();
panic!("{stderr_output}");
}
info!("Done.");
}
fn cairo1_package_exists(version: &String) -> bool {
let cairo_compiler_path = starknet_compile_binary_path(version);
let sierra_compiler_path = starknet_sierra_compile_binary_path(version);
cairo_compiler_path.exists() && sierra_compiler_path.exists()
}
pub(crate) fn lock_path_for(path: &Path) -> PathBuf {
PathBuf::from(format!("{}.lock", path.display()))
}
pub(crate) fn with_file_lock(lock_path: &Path, is_done: impl Fn() -> bool, action: impl FnOnce()) {
if is_done() {
return;
}
fs::create_dir_all(lock_path.parent().unwrap())
.unwrap_or_else(|e| panic!("Failed to create directory for lock file: {e}"));
let lock_file = File::create(lock_path)
.unwrap_or_else(|e| panic!("Failed to create lock file {lock_path:?}: {e}"));
lock_file.lock().unwrap_or_else(|e| panic!("Failed to acquire lock on {lock_path:?}: {e}"));
if is_done() {
return;
}
action();
}
pub fn verify_cairo1_package(version: &String) {
let dir = cairo1_package_dir(version);
with_file_lock(
&lock_path_for(&dir),
|| cairo1_package_exists(version),
|| {
eprintln!(
"[cairo_compile] Cairo 1 compiler v{version} not found locally, downloading..."
);
download_cairo_package(version);
eprintln!("[cairo_compile] Cairo 1 compiler v{version} downloaded successfully.");
assert!(cairo1_package_exists(version));
},
);
}
fn run_and_verify_output(command: &mut Command) -> Output {
let output = command.output().unwrap();
if !output.status.success() {
let stderr_output = String::from_utf8(output.stderr).unwrap();
panic!("{stderr_output}");
}
output
}
pub fn cairo0_compile(
path: String,
extra_arg: Option<String>,
debug_info: bool,
) -> CompilationArtifacts {
let script_type = Cairo0Script::StarknetCompileDeprecated;
verify_cairo0_compiler_deps(&script_type);
let mut command = Command::new(script_type.script_name());
command.arg(&path);
if let Some(extra_arg) = extra_arg {
command.arg(extra_arg);
}
if !debug_info {
command.arg("--no_debug_info");
}
let compile_output = command.output().unwrap();
let stderr_output = String::from_utf8(compile_output.stderr).unwrap();
assert!(compile_output.status.success(), "{stderr_output}");
CompilationArtifacts::Cairo0 { casm: compile_output.stdout }
}
pub enum LibfuncArg {
ListName(String),
ListFile(String),
}
impl LibfuncArg {
pub fn add_to_command<'a>(&self, command: &'a mut Command) -> &'a mut Command {
match self {
Self::ListName(name) => command.args(["--allowed-libfuncs-list-name", name]),
Self::ListFile(file) => command.args(["--allowed-libfuncs-list-file", file]),
}
}
pub fn file_path(&self) -> &str {
match self {
Self::ListFile(path) => path,
Self::ListName(_) => panic!("LibfuncArg::ListName has no file path"),
}
}
}
pub fn cairo1_compile(
path: String,
version: String,
libfunc_list_arg: LibfuncArg,
) -> CompilationArtifacts {
assert!(cairo1_package_exists(&version));
let sierra_output = starknet_compile(path, &version, &libfunc_list_arg);
let mut temp_file = NamedTempFile::new().unwrap();
temp_file.write_all(&sierra_output).unwrap();
let temp_path_str = temp_file.into_temp_path();
let casm_output = starknet_sierra_compile(
temp_path_str.to_str().unwrap().to_string(),
&version,
&libfunc_list_arg,
);
CompilationArtifacts::Cairo1 { casm: casm_output, sierra: sierra_output }
}
pub fn starknet_compile(path: String, version: &String, libfunc_list_arg: &LibfuncArg) -> Vec<u8> {
let mut starknet_compile_commmand = Command::new(starknet_compile_binary_path(version));
starknet_compile_commmand.args(["--single-file", &path]);
libfunc_list_arg.add_to_command(&mut starknet_compile_commmand);
let sierra_output = run_and_verify_output(&mut starknet_compile_commmand);
sierra_output.stdout
}
fn starknet_sierra_compile(
path: String,
version: &String,
libfunc_list_arg: &LibfuncArg,
) -> Vec<u8> {
let mut sierra_compile_command = Command::new(starknet_sierra_compile_binary_path(version));
sierra_compile_command.args([&path]);
libfunc_list_arg.add_to_command(&mut sierra_compile_command);
let casm_output = run_and_verify_output(&mut sierra_compile_command);
casm_output.stdout
}