diff --git a/Cargo.lock b/Cargo.lock index b120a52288b..9381477c5cd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1327,6 +1327,7 @@ dependencies = [ "serde", "serde_json", "socket2 0.5.8", + "thiserror 1.0.69", "tokio", "toml", "tracing", diff --git a/crates/apollo_infra_utils/Cargo.toml b/crates/apollo_infra_utils/Cargo.toml index 4c3bdcf0109..96326e92143 100644 --- a/crates/apollo_infra_utils/Cargo.toml +++ b/crates/apollo_infra_utils/Cargo.toml @@ -20,6 +20,7 @@ num_enum.workspace = true serde = { workspace = true, features = ["derive"] } serde_json.workspace = true socket2 = { workspace = true, optional = true } +thiserror.workspace = true tokio = { workspace = true, features = ["process", "rt", "time"] } toml = { workspace = true, optional = true } tracing.workspace = true diff --git a/crates/apollo_infra_utils/src/cairo0_compiler.rs b/crates/apollo_infra_utils/src/cairo0_compiler.rs index eaf971d63bb..3d9b35d586f 100644 --- a/crates/apollo_infra_utils/src/cairo0_compiler.rs +++ b/crates/apollo_infra_utils/src/cairo0_compiler.rs @@ -1,12 +1,19 @@ -use std::fs; use std::path::PathBuf; use std::process::Command; use std::sync::LazyLock; use crate::path::resolve_project_relative_path; +#[cfg(test)] +#[path = "cairo0_compiler_test.rs"] +pub mod test; + +pub const STARKNET_COMPILE_DEPRECATED: &str = "starknet-compile-deprecated"; +pub const CAIRO0_COMPILE: &str = "cairo-compile"; +pub const EXPECTED_CAIRO0_VERSION: &str = "0.14.0a1"; + /// The local python requirements used to determine the cairo0 compiler version. -static PIP_REQUIREMENTS_FILE: LazyLock = +pub(crate) static PIP_REQUIREMENTS_FILE: LazyLock = LazyLock::new(|| resolve_project_relative_path("scripts/requirements.txt").unwrap()); static ENTER_VENV_INSTRUCTIONS: LazyLock = LazyLock::new(|| { @@ -19,40 +26,67 @@ pip install -r {:#?}"#, ) }); +#[derive(thiserror::Error, Debug)] +pub enum Cairo0CompilerVersionError { + #[error( + "{compiler} version is not correct: required {required}, got {existing}. Are you in the \ + venv? If not, run the following commands:\n{}", *ENTER_VENV_INSTRUCTIONS + )] + IncorrectVersion { compiler: String, existing: String, required: String }, + #[error( + "{0}. Are you in the venv? If not, run the following commands:\n{}", + *ENTER_VENV_INSTRUCTIONS + )] + NotFound(String), +} + +pub fn cairo0_compilers_correct_version() -> Result<(), Cairo0CompilerVersionError> { + for compiler in [CAIRO0_COMPILE, STARKNET_COMPILE_DEPRECATED] { + let version = match Command::new(compiler).arg("--version").output() { + Ok(output) => String::from_utf8_lossy(&output.stdout).to_string(), + Err(error) => { + return Err(Cairo0CompilerVersionError::NotFound(format!( + "Failed to get {compiler} version: {error}." + ))); + } + }; + if version + .trim() + .replace("==", " ") + .split(" ") + .nth(1) + .ok_or(Cairo0CompilerVersionError::NotFound("No compiler version found.".to_string()))? + != EXPECTED_CAIRO0_VERSION + { + return Err(Cairo0CompilerVersionError::IncorrectVersion { + compiler: compiler.to_string(), + existing: version, + required: EXPECTED_CAIRO0_VERSION.to_string(), + }); + } + } + + Ok(()) +} + /// Verifies that the required Cairo0 compiler is available; panics if unavailable. +/// For use in tests only. If cairo0 compiler verification is required in business logic, use +/// `crate::cairo0_compiler::cairo0_compilers_correct_version` instead. +#[cfg(any(test, feature = "testing"))] pub fn verify_cairo0_compiler_deps() { - // Python compiler. Verify correct version. - let cairo_lang_version_output = - Command::new("sh").arg("-c").arg("pip freeze | grep cairo-lang").output().unwrap().stdout; - let cairo_lang_version_untrimmed = String::from_utf8(cairo_lang_version_output).unwrap(); - let cairo_lang_version = - cairo_lang_version_untrimmed.trim().split("==").nth(1).unwrap_or_else(|| { - panic!( - "Unexpected cairo-lang version format '{cairo_lang_version_untrimmed}'. Are you \ - in a venv? If not, run:\n{}", - *ENTER_VENV_INSTRUCTIONS - ) - }); - let requirements_contents = fs::read_to_string(&*PIP_REQUIREMENTS_FILE).unwrap(); - let expected_cairo_lang_version = requirements_contents - .lines() - .find(|line| line.starts_with("cairo-lang")) - .unwrap_or_else(|| panic!("Could not find cairo-lang in {:?}.", *PIP_REQUIREMENTS_FILE)) - .trim() - .split("==") - .nth(1) - .unwrap_or_else(|| { - panic!( - "Malformed cairo-lang dependency (expected 'cairo-lang==X') in {:?}.", - *PIP_REQUIREMENTS_FILE - ) - }); - - assert_eq!( - expected_cairo_lang_version, cairo_lang_version, - "cairo-lang version {expected_cairo_lang_version} not found (installed version: \ - {cairo_lang_version}). Run the following commands (enter a python venv and install \ - dependencies) and retry:\n{}", + let specific_error = match cairo0_compilers_correct_version() { + Ok(_) => { + return; + } + Err(Cairo0CompilerVersionError::NotFound(_)) => "no installed cairo-lang found".to_string(), + Err(Cairo0CompilerVersionError::IncorrectVersion { existing, .. }) => { + format!("installed version: {existing}") + } + }; + + panic!( + "cairo-lang version {EXPECTED_CAIRO0_VERSION} not found ({specific_error}). Please enter \ + a venv and rerun the test:\n{}", *ENTER_VENV_INSTRUCTIONS ); } diff --git a/crates/apollo_infra_utils/src/cairo0_compiler_test.rs b/crates/apollo_infra_utils/src/cairo0_compiler_test.rs new file mode 100644 index 00000000000..64030330fdd --- /dev/null +++ b/crates/apollo_infra_utils/src/cairo0_compiler_test.rs @@ -0,0 +1,20 @@ +use crate::cairo0_compiler::{EXPECTED_CAIRO0_VERSION, PIP_REQUIREMENTS_FILE}; + +#[test] +fn test_cairo0_version_pip_requirements() { + let requirements_contents = std::fs::read_to_string(&*PIP_REQUIREMENTS_FILE).unwrap(); + let pip_cairo_lang_version = requirements_contents + .lines() + .find(|line| line.starts_with("cairo-lang")) + .unwrap_or_else(|| panic!("Could not find cairo-lang in {:?}.", *PIP_REQUIREMENTS_FILE)) + .trim() + .split("==") + .nth(1) + .unwrap_or_else(|| { + panic!( + "Malformed cairo-lang dependency (expected 'cairo-lang==X') in {:?}.", + *PIP_REQUIREMENTS_FILE + ) + }); + assert_eq!(pip_cairo_lang_version, format!("{EXPECTED_CAIRO0_VERSION}")); +} diff --git a/crates/blockifier_test_utils/src/cairo_compile.rs b/crates/blockifier_test_utils/src/cairo_compile.rs index 3ecc5c9f4ba..ee847e205eb 100644 --- a/crates/blockifier_test_utils/src/cairo_compile.rs +++ b/crates/blockifier_test_utils/src/cairo_compile.rs @@ -2,7 +2,10 @@ use std::io::Write; use std::path::PathBuf; use std::process::{Command, Output, Stdio}; -use apollo_infra_utils::cairo0_compiler::verify_cairo0_compiler_deps; +use apollo_infra_utils::cairo0_compiler::{ + verify_cairo0_compiler_deps, + STARKNET_COMPILE_DEPRECATED, +}; use apollo_infra_utils::cairo_compiler_version::cairo1_compiler_version; use apollo_infra_utils::path::project_path; use tempfile::NamedTempFile; @@ -92,7 +95,7 @@ pub fn cairo0_compile( debug_info: bool, ) -> CompilationArtifacts { verify_cairo0_compiler_deps(); - let mut command = Command::new("starknet-compile-deprecated"); + let mut command = Command::new(STARKNET_COMPILE_DEPRECATED); command.arg(&path); if let Some(extra_arg) = extra_arg { command.arg(extra_arg);