Skip to content

Commit 98d341a

Browse files
compiling cario code
1 parent d2b3815 commit 98d341a

File tree

8 files changed

+334
-0
lines changed

8 files changed

+334
-0
lines changed

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ members = [
44
"crates/cairo-program-runner-lib",
55
"crates/stwo_run_and_prove",
66
"crates/vm_runner",
7+
"crates/concat-aggregator",
8+
"crates/cairo-compile-utils",
79
]
810
resolver = "2"
911

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
[package]
2+
name = "cairo-compile-utils"
3+
version = "0.1.0"
4+
edition = "2021"
5+
description = "The source (Cairo) code of the dummy concat aggregator."
6+
7+
[features]
8+
dump_source_files = []
9+
10+
[dependencies]
11+
bincode.workspace = true
12+
cairo-vm.workspace = true
13+
cairo-lang-executable.workspace = true
14+
cairo-lang-runner.workspace = true
15+
cairo-lang-casm.workspace = true
16+
cairo-lang-execute-utils.workspace = true
17+
clap.workspace = true
18+
num-traits.workspace = true
19+
serde = { workspace = true, features = ["derive"] }
20+
serde_json.workspace = true
21+
thiserror.workspace = true
22+
thiserror-no-std.workspace = true
23+
regex.workspace = true
24+
num-bigint.workspace = true
25+
walkdir = "2"
26+
anyhow = "1.0"
27+
28+
[build-dependencies]
29+
serde_json.workspace = true
30+
31+
[dev-dependencies]
32+
assert_matches = "1.5.0"
33+
rstest = "0.19.0"
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
use std::process::Command;
2+
use std::io::{self, Write};
3+
use std::path::{Path, PathBuf};
4+
use walkdir::WalkDir; // A useful crate for walking directories
5+
use std::env; // Import the env module
6+
use anyhow::{Result, Context};
7+
8+
use std::fs;
9+
10+
fn find_main_file(project_root: &PathBuf) -> anyhow::Result<PathBuf> {
11+
for entry in WalkDir::new(project_root)
12+
.into_iter()
13+
.filter_map(Result::ok)
14+
.filter(|e| e.file_type().is_file() && e.path().extension().map_or(false, |ext| ext == "cairo"))
15+
{
16+
let content = fs::read_to_string(entry.path())?;
17+
if content.contains("func main") {
18+
return Ok(entry.path().to_path_buf());
19+
}
20+
}
21+
22+
Err(anyhow::anyhow!(
23+
"No Cairo 0 file with `func main` found under {:?}",
24+
project_root
25+
))
26+
}
27+
28+
/// Builds a shell-like string representation of a Command for logging/debugging.
29+
/// This is a simplified representation and might not handle all edge cases (e.g., complex quoting).
30+
fn format_command_for_display(cmd: &Command) -> String {
31+
let mut cmd_str = String::new();
32+
33+
// Add the program name
34+
if let Some(program) = cmd.get_program().to_str() {
35+
cmd_str.push_str(program);
36+
}
37+
38+
// Add arguments
39+
for arg in cmd.get_args() {
40+
if let Some(s) = arg.to_str() {
41+
cmd_str.push(' ');
42+
// Basic quoting for arguments that might contain spaces
43+
if s.contains(' ') || s.contains('"') || s.contains('\'') {
44+
cmd_str.push('"');
45+
cmd_str.push_str(&s.replace('"', "\\\"")); // Escape existing double quotes
46+
cmd_str.push('"');
47+
} else {
48+
cmd_str.push_str(s);
49+
}
50+
}
51+
}
52+
53+
cmd_str
54+
}
55+
56+
// current_dir should hold the relevant cairo code (only one main)
57+
pub fn compile_cairo_code(current_dir: &PathBuf) -> anyhow::Result<()> {
58+
59+
println!("Current working directory: {:?}", current_dir);
60+
61+
// Define the output path for the compiled program (CASM)
62+
let output_casm_file = PathBuf::from("./target/my_combined_program.json");
63+
64+
// Ensure the target directory exists
65+
std::fs::create_dir_all(output_casm_file.parent().unwrap())?;
66+
67+
println!("Searching for Cairo 0 files in: {:?}", current_dir);
68+
69+
// Define the main Cairo file to compile
70+
let main_cairo_file = find_main_file(&current_dir)?;
71+
println!("Detected main Cairo file: {:?}", main_cairo_file);
72+
73+
// Check that it exists
74+
if !main_cairo_file.exists() {
75+
return Err(anyhow::anyhow!("Main Cairo file not found: {:?}", main_cairo_file));
76+
}
77+
78+
println!("Attempting to compile Cairo 0 project to: {:?}", output_casm_file);
79+
80+
// Build the `cairo-compile` command
81+
let mut command = Command::new("cairo-compile");
82+
83+
// Add only the main file
84+
command.arg(&main_cairo_file);
85+
86+
println!("Executing the following command: {}", format_command_for_display(&command));
87+
// Specify the output file
88+
command.arg("--output").arg(&output_casm_file);
89+
90+
let cairo_project_root = current_dir.clone();
91+
// Add the --cairo_path argument using the determined project root
92+
// This is crucial for resolving 'from starkware.cairo...' imports if 'starkware'
93+
// is a top-level directory directly under your project root.
94+
command.arg("--cairo_path").arg(&cairo_project_root);
95+
96+
// Execute the command
97+
let output = command.output()?;
98+
99+
// Check if the command was successful
100+
if output.status.success() {
101+
println!("Cairo 0 compilation successful!");
102+
println!("Compiled program written to: {:?}", output_casm_file);
103+
io::stdout().write_all(&output.stdout)?;
104+
} else {
105+
eprintln!("Cairo 0 compilation failed!");
106+
eprintln!("Status: {:?}", output.status);
107+
eprintln!("Stderr:\n{}", String::from_utf8_lossy(&output.stderr));
108+
return Err(anyhow::anyhow!("Cairo 0 compilation failed: {}", String::from_utf8_lossy(&output.stderr)));
109+
}
110+
111+
println!("\nTo run the compiled Cairo 0 program, you would typically use `cairo-run`:");
112+
println!("`cairo-run --program {}`", output_casm_file.display());
113+
println!("If `main` is an external function for a StarkNet contract, you'd deploy it.");
114+
115+
Ok(())
116+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
[package]
2+
name = "concat-aggregator"
3+
version = "0.1.0"
4+
edition = "2021"
5+
description = "The source (Cairo) code of the dummy concat aggregator."
6+
7+
[features]
8+
dump_source_files = []
9+
10+
[dependencies]
11+
bincode.workspace = true
12+
cairo-vm.workspace = true
13+
cairo-lang-executable.workspace = true
14+
cairo-lang-runner.workspace = true
15+
cairo-lang-casm.workspace = true
16+
cairo-lang-execute-utils.workspace = true
17+
clap.workspace = true
18+
num-traits.workspace = true
19+
serde = { workspace = true, features = ["derive"] }
20+
serde_json.workspace = true
21+
thiserror.workspace = true
22+
thiserror-no-std.workspace = true
23+
regex.workspace = true
24+
num-bigint.workspace = true
25+
walkdir = "2"
26+
anyhow = "1.0"
27+
cairo-compile-utils = { path = "../cairo-compile-utils"}
28+
29+
[build-dependencies]
30+
serde_json.workspace = true
31+
32+
[dev-dependencies]
33+
assert_matches = "1.5.0"
34+
rstest = "0.19.0"

crates/concat-aggregator/build/main.rs

Whitespace-only changes.
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// Parses the task outputs from the bootloader output. Writes their outputs to the output_ptr.
2+
// Returns the number of tasks.
3+
func parse_tasks{output_ptr: felt*}() -> felt {
4+
alloc_locals;
5+
6+
local n_tasks: felt;
7+
8+
%{
9+
def parse_bootloader_tasks_outputs(output):
10+
"""
11+
Parses the output of the bootloader, returning the raw outputs of the tasks.
12+
"""
13+
output_iter = iter(output)
14+
# Skip the bootloader_config.
15+
[next(output_iter) for _ in range(3)]
16+
17+
n_tasks = next(output_iter)
18+
tasks_outputs = []
19+
for _ in range(n_tasks):
20+
task_output_size = next(output_iter)
21+
tasks_outputs.append([next(output_iter) for _ in range(task_output_size - 1)])
22+
23+
assert next(output_iter, None) is None, "Bootloader output wasn't fully consumed."
24+
25+
return tasks_outputs
26+
27+
tasks_outputs = parse_bootloader_tasks_outputs(program_input["bootloader_output"])
28+
assert len(tasks_outputs) > 0, "No tasks found in the bootloader output."
29+
ids.n_tasks = len(tasks_outputs)
30+
%}
31+
32+
assert [output_ptr] = n_tasks;
33+
let output_ptr = output_ptr + 1;
34+
35+
// Output the task outputs as they are.
36+
output_tasks(n_tasks=n_tasks);
37+
38+
return n_tasks;
39+
}
40+
41+
// Outputs the task outputs, each with the size of the output (to match the bootloader output
42+
// format).
43+
func output_tasks{output_ptr: felt*}(n_tasks: felt) {
44+
if (n_tasks == 0) {
45+
return ();
46+
}
47+
48+
let output_size = output_ptr[0];
49+
let output_ptr = output_ptr + 1;
50+
51+
%{
52+
task_index = len(tasks_outputs) - ids.n_tasks
53+
segments.load_data(ptr=ids.output_ptr, data=tasks_outputs[task_index])
54+
ids.output_size = len(tasks_outputs[task_index]) + 1
55+
%}
56+
57+
let output_ptr = output_ptr + output_size - 1;
58+
59+
return output_tasks(n_tasks=n_tasks - 1);
60+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
%builtins output range_check poseidon
2+
3+
from aggregator_tasks_utils import parse_tasks
4+
from starkware.cairo.common.cairo_builtins import PoseidonBuiltin
5+
6+
// Simple aggregation program that concatenates task outputs. Used for tests. It is not sound.
7+
//
8+
// Hint arguments:
9+
// program_input - List of task outputs, in the format of the bootloader output.
10+
func main{output_ptr: felt*, range_check_ptr, poseidon_ptr: PoseidonBuiltin*}() {
11+
alloc_locals;
12+
13+
let n_tasks = parse_tasks();
14+
local output_start: felt* = output_ptr;
15+
16+
// Output the concatenated task outputs.
17+
output_concatenated_output(n_tasks=n_tasks);
18+
19+
%{
20+
from starkware.python.math_utils import div_ceil
21+
22+
output_length = ids.output_ptr - ids.output_start
23+
page_size = 10
24+
next_page_start = min(ids.output_start + page_size, ids.output_ptr)
25+
next_page_id = 1
26+
while next_page_start < ids.output_ptr:
27+
output_builtin.add_page(
28+
page_id=next_page_id,
29+
page_start=next_page_start,
30+
page_size=min(ids.output_ptr - next_page_start, page_size),
31+
)
32+
next_page_start += page_size
33+
next_page_id += 1
34+
if next_page_id == 1:
35+
# Single page. Use trivial fact topology.
36+
output_builtin.add_attribute('gps_fact_topology', [
37+
1,
38+
0,
39+
])
40+
else:
41+
output_builtin.add_attribute('gps_fact_topology', [
42+
next_page_id,
43+
next_page_id - 1,
44+
0,
45+
2,
46+
])
47+
%}
48+
return ();
49+
}
50+
51+
// Outputs the task outputs, without the output sizes.
52+
func output_concatenated_output{output_ptr: felt*}(n_tasks: felt) {
53+
alloc_locals;
54+
if (n_tasks == 0) {
55+
return ();
56+
}
57+
58+
local output_size: felt;
59+
60+
%{
61+
task_index = len(tasks_outputs) - ids.n_tasks
62+
segments.load_data(ptr=ids.output_ptr, data=tasks_outputs[task_index])
63+
ids.output_size = len(tasks_outputs[task_index])
64+
%}
65+
66+
let output_ptr = output_ptr + output_size;
67+
68+
return output_concatenated_output(n_tasks=n_tasks - 1);
69+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
use std::process::Command;
2+
use std::io::{self, Write};
3+
use std::path::{Path, PathBuf};
4+
use walkdir::WalkDir; // A useful crate for walking directories
5+
use std::env; // Import the env module
6+
use anyhow::{Result, Context};
7+
8+
use cairo_compile_utils::compile_cairo_code;
9+
use std::fs;
10+
11+
fn main() -> anyhow::Result<()> {
12+
13+
// Get the current working directory of the Rust program
14+
let current_dir = env::current_dir()
15+
.context("Failed to get current working directory")?.join("crates").join("concat-aggregator").join("src");
16+
17+
compile_cairo_code(&current_dir)?;
18+
19+
Ok(())
20+
}

0 commit comments

Comments
 (0)