diff --git a/Cargo.lock b/Cargo.lock index 1dfe70b..282b3af 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -96,7 +96,7 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "axiom-sdk" -version = "1.3.0" +version = "1.4.0" dependencies = [ "bytes", "cargo_metadata", @@ -166,7 +166,7 @@ dependencies = [ [[package]] name = "cargo-axiom" -version = "1.3.0" +version = "1.4.0" dependencies = [ "axiom-sdk", "cargo_metadata", diff --git a/Cargo.toml b/Cargo.toml index 5347a19..65bb8c1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace.package] -version = "1.3.0" +version = "1.4.0" edition = "2024" authors = ["Axiom Team"] license = "MIT" diff --git a/crates/cli/src/commands/build.rs b/crates/cli/src/commands/build.rs index 635d98c..f0a8142 100644 --- a/crates/cli/src/commands/build.rs +++ b/crates/cli/src/commands/build.rs @@ -2,7 +2,7 @@ use std::io::{self, Write}; use axiom_sdk::{ AxiomSdk, - build::{BuildSdk, ConfigSource}, + build::{BuildSdk, UploadExeArgs}, }; use clap::{Parser, Subcommand}; use comfy_table; @@ -11,7 +11,10 @@ use eyre::Result; use crate::{formatting::Formatter, progress::CliProgressCallback}; #[derive(Debug, Parser)] -#[command(name = "build", about = "Build the project on Axiom Proving Service")] +#[command( + name = "build", + about = "Upload a locally-built program (ELF + VMEXE) to the Axiom Proving Service" +)] pub struct BuildCmd { #[command(subcommand)] command: Option, @@ -53,42 +56,23 @@ enum BuildSubcommand { #[clap(long, value_name = "TYPE", value_parser = ["exe", "elf", "source", "app_exe_commit", "all"])] artifact: String, }, - - /// Download build logs for a program - Logs { - /// The program ID to download logs for - #[clap(long, value_name = "ID")] - program_id: String, - }, } #[derive(Debug, Parser)] pub struct BuildArgs { - /// The configuration ID to use for the build - #[clap(long, value_name = "ID", conflicts_with = "config")] + /// The configuration ID to use (must reference an already-keyed config) + #[clap(long, value_name = "ID")] config_id: Option, - /// Path to an OpenVM TOML configuration file - #[clap(long, value_name = "PATH")] - config: Option, - - /// The binary to build, if there are multiple binaries in the project + /// The binary to upload, if the project has multiple binaries #[clap(long, value_name = "BIN")] bin: Option, - /// Keep the tar archive after uploading - #[clap(long)] - keep_tarball: Option, - - /// Comma-separated list of file patterns to exclude (e.g. "*.log,temp/*") - #[clap(long, value_name = "PATTERNS")] - exclude_files: Option, - - /// Comma-separated list of directories to include even if not tracked by git - #[clap(long, value_name = "DIRS")] - include_dirs: Option, + /// Custom program name + #[clap(long, value_name = "NAME")] + program_name: Option, - /// The project ID to associate with the build + /// The project ID to associate with the program #[arg(long, value_name = "ID")] project_id: Option, @@ -96,17 +80,9 @@ pub struct BuildArgs { #[clap(long)] detach: bool, - /// Allow building with uncommitted changes - #[clap(long)] - allow_dirty: bool, - /// Specify default_num_gpus for this program #[clap(long)] default_num_gpus: Option, - - /// OpenVM Rust toolchain version (e.g., nightly-2025-02-14) - #[clap(long, value_name = "VERSION")] - openvm_rust_toolchain: Option, } impl BuildCmd { @@ -168,14 +144,8 @@ impl BuildCmd { program_id, artifact, }) => sdk.download_program(&program_id, &artifact), - Some(BuildSubcommand::Logs { program_id }) => sdk.download_build_logs(&program_id), None => { let program_dir = std::env::current_dir()?; - let config_source = match (self.build_args.config_id, self.build_args.config) { - (Some(config_id), _) => Some(ConfigSource::ConfigId(config_id)), - (_, Some(config)) => Some(ConfigSource::ConfigPath(config)), - (None, None) => None, - }; let project_id = { let cache_path = program_dir.join(".axiom").join("project-id"); @@ -204,19 +174,15 @@ impl BuildCmd { if name.is_empty() { None } else { Some(name) } }; - let args = axiom_sdk::build::BuildArgs { - config_source, - bin: self.build_args.bin, - keep_tarball: self.build_args.keep_tarball, - exclude_files: self.build_args.exclude_files, - include_dirs: self.build_args.include_dirs, + let args = UploadExeArgs { + config_id: self.build_args.config_id, project_id, project_name: project_name_for_creation.clone(), - allow_dirty: self.build_args.allow_dirty, + bin_name: self.build_args.bin, + program_name: self.build_args.program_name, default_num_gpus: self.build_args.default_num_gpus, - openvm_rust_toolchain: self.build_args.openvm_rust_toolchain, }; - let program_id = sdk.register_new_program(&program_dir, args)?; + let program_id = sdk.upload_exe(&program_dir, args)?; // Always fetch the latest build status to get project ID and print console URL let status = sdk.get_build_status(&program_id)?; diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index 641b5de..e56ffaf 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -39,7 +39,7 @@ enum AxiomCommands { Init(InitCmd), /// Register Axiom API credentials Register(RegisterCmd), - /// Build the project on Axiom Proving Service + /// Upload a locally-built program (ELF + VMEXE) to the Axiom Proving Service Build(BuildCmd), /// Generate a proof using the Axiom Proving Service Prove(ProveCmd), diff --git a/crates/sdk/src/build.rs b/crates/sdk/src/build.rs index b5ecaa9..5efd8f0 100644 --- a/crates/sdk/src/build.rs +++ b/crates/sdk/src/build.rs @@ -108,7 +108,6 @@ pub trait BuildSdk { fn get_app_exe_commit(&self, program_id: &str) -> Result>; fn download_program(&self, program_id: &str, program_type: &str) -> Result<()>; - fn download_build_logs(&self, program_id: &str) -> Result<()>; fn register_new_program( &self, program_dir: impl AsRef, @@ -357,28 +356,6 @@ impl BuildSdk for AxiomSdk { } } - fn download_build_logs(&self, program_id: &str) -> Result<()> { - let url = format!("{}/programs/{}/logs", self.config.api_url, program_id); - let build_dir = std::path::PathBuf::from("axiom-artifacts") - .join(format!("program-{}", program_id)) - .join("artifacts"); - std::fs::create_dir_all(&build_dir).context(format!( - "Failed to create build directory: {}", - build_dir.display() - ))?; - - let filename = build_dir.join("logs.txt"); - let response = authenticated_get(&self.config, &url)?; - crate::download_file_streaming( - response, - filename.clone(), - "Failed to download build logs", - )?; - self.callback - .on_success(&format!("✓ {}", filename.display())); - Ok(()) - } - fn register_new_program( &self, program_dir: impl AsRef, @@ -839,93 +816,84 @@ impl AxiomSdk { eyre::bail!("Not in a Rust project. Make sure Cargo.toml exists."); } - // Look for the openvm build output directory - let target_dir = program_dir.as_ref().join("target").join("openvm"); - if !target_dir.exists() { + // openvm v2 `cargo openvm build` output layout (run from the guest crate dir): + // - transpiled VMEXE: /openvm//.vmexe (SIBLING of target/) + // - raw ELF: /target/// (no extension) + // NOTE: assumes the default target dir; custom CARGO_TARGET_DIR / workspace target + // dirs are not handled yet. + let profile = "release"; + let vmexe_dir = program_dir.as_ref().join("openvm").join(profile); + if !vmexe_dir.exists() { eyre::bail!( "OpenVM build output not found. Please run 'cargo openvm build' first.\nExpected directory: {}", - target_dir.display() - ); - } - - // Find the release directory - // TODO: Support other profiles beyond release (e.g., dev, custom profiles) - let release_dir = target_dir.join("release"); - if !release_dir.exists() { - eyre::bail!( - "OpenVM release build not found. Please run 'cargo openvm build' first.\nExpected directory: {}", - release_dir.display() + vmexe_dir.display() ); } - // Find ELF and VMEXE files - let elf_files: Vec<_> = std::fs::read_dir(&release_dir)? + // Find the transpiled VMEXE files. + let vmexe_files: Vec<_> = std::fs::read_dir(&vmexe_dir)? .filter_map(Result::ok) - .filter(|entry| entry.path().extension().and_then(|s| s.to_str()) == Some("elf")) + .filter(|entry| entry.path().extension().and_then(|s| s.to_str()) == Some("vmexe")) .collect(); - if elf_files.is_empty() { + if vmexe_files.is_empty() { eyre::bail!( - "No ELF files found in {}. Please run 'cargo openvm build' first.", - release_dir.display() + "No VMEXE files found in {}. Please run 'cargo openvm build' first.", + vmexe_dir.display() ); } - // Filter by bin_name if provided and there are multiple ELFs - let elf_path = if elf_files.len() > 1 { - if let Some(bin_name) = &args.bin_name { - // Filter ELF files by bin_name - let matching_elf = elf_files.iter().find(|entry| { - entry - .path() + let available_bins = |files: &[std::fs::DirEntry]| -> Vec { + files + .iter() + .filter_map(|e| { + e.path() .file_stem() .and_then(|s| s.to_str()) - .map(|name| name == bin_name) - .unwrap_or(false) - }); - - if let Some(elf_entry) = matching_elf { - elf_entry.path() - } else { - let available: Vec<_> = elf_files - .iter() - .filter_map(|e| { - e.path() - .file_stem() - .and_then(|s| s.to_str()) - .map(|s| s.to_string()) - }) - .collect(); - eyre::bail!( - "ELF file '{}' not found. Available ELF files: {}", + .map(|s| s.to_string()) + }) + .collect() + }; + + // Select the VMEXE (by --bin-name when the project has multiple binaries). + let vmexe_path = if vmexe_files.len() > 1 { + if let Some(bin_name) = &args.bin_name { + match vmexe_files.iter().find(|entry| { + entry.path().file_stem().and_then(|s| s.to_str()) == Some(bin_name.as_str()) + }) { + Some(entry) => entry.path(), + None => eyre::bail!( + "VMEXE for bin '{}' not found. Available: {}", bin_name, - available.join(", ") - ); + available_bins(&vmexe_files).join(", ") + ), } } else { - let available: Vec<_> = elf_files - .iter() - .filter_map(|e| { - e.path() - .file_stem() - .and_then(|s| s.to_str()) - .map(|s| s.to_string()) - }) - .collect(); eyre::bail!( - "Multiple ELF files found. Please specify which one to upload using --bin-name. Available: {}", - available.join(", ") + "Multiple binaries found. Specify which one with --bin-name. Available: {}", + available_bins(&vmexe_files).join(", ") ); } } else { - elf_files[0].path() + vmexe_files[0].path() }; - let vmexe_path = elf_path.with_extension("vmexe"); - if !vmexe_path.exists() { + // Derive the binary name from the VMEXE, then locate the matching raw ELF. + let bin = vmexe_path + .file_stem() + .and_then(|s| s.to_str()) + .ok_or_eyre("Could not determine binary name from VMEXE path")?; + let elf_path = program_dir + .as_ref() + .join("target") + .join(OPENVM_RUSTC_TARGET) + .join(profile) + .join(bin); + + if !elf_path.exists() { eyre::bail!( - "VMEXE file not found at {}. Please run 'cargo openvm build' first.", - vmexe_path.display() + "ELF file not found at {}. Please run 'cargo openvm build' first.", + elf_path.display() ); } @@ -962,11 +930,10 @@ impl AxiomSdk { .ok_or_eyre("No config_id provided and no default config_id in ~/.axiom/config.json")?; callback.on_field("Config ID", config_id); - // Build URL with query parameters - let mut url = format!( - "{}/programs/upload-exe?config_id={}", - self.config.api_url, config_id - ); + // Build URL with query parameters. API 2.0 unified registration onto POST /programs + // (the /programs/upload-exe endpoint and the source-tarball build path were removed), + // and dropped the server-side bin_name param (the binary is chosen at build time). + let mut url = format!("{}/programs?config_id={}", self.config.api_url, config_id); if let Some(project_id) = &args.project_id { url.push_str(&format!("&project_id={}", project_id)); @@ -976,9 +943,6 @@ impl AxiomSdk { url::form_urlencoded::byte_serialize(project_name.as_bytes()).collect(); url.push_str(&format!("&project_name={}", encoded)); } - if let Some(bin_name) = &args.bin_name { - url.push_str(&format!("&bin_name={}", bin_name)); - } if let Some(program_name) = &args.program_name { let encoded: String = url::form_urlencoded::byte_serialize(program_name.as_bytes()).collect();