1use std::hash::BuildHasher;
2use std::io::{Read, Write};
3use std::path::Path;
4use std::time::Duration;
5
6use fm::FileManager;
7use nargo::ops::{collect_errors, compile_contract, compile_program, report_errors};
8use nargo::package::Package;
9use nargo::workspace::Workspace;
10use nargo::{insert_all_files_for_workspace_into_file_manager, parse_all};
11use nargo_toml::PackageSelection;
12use noir_artifact_cli::fs::artifact::{
13 read_program_from_file, save_contract_to_file, save_program_to_file,
14};
15use noirc_artifacts::contract::CompiledContract;
16use noirc_driver::NOIR_ARTIFACT_VERSION_STRING;
17use noirc_driver::{CompilationResult, CompileOptions};
18
19use clap::Args;
20use noirc_frontend::hir::ParsedFiles;
21use notify_debouncer_full::new_debouncer;
22use notify_debouncer_full::notify::{EventKind, RecursiveMode};
23
24use crate::errors::CliError;
25
26use super::{LockType, PackageOptions, WorkspaceCommand};
27use rayon::prelude::*;
28
29#[derive(Debug, Clone, Args)]
31pub struct CompileCommand {
32 #[clap(flatten)]
33 pub(super) package_options: PackageOptions,
34
35 #[clap(flatten)]
36 pub(super) compile_options: CompileOptions,
37
38 #[clap(long, hide = true)]
40 watch: bool,
41}
42
43impl WorkspaceCommand for CompileCommand {
44 fn package_selection(&self) -> PackageSelection {
45 self.package_options.package_selection()
46 }
47
48 fn lock_type(&self) -> LockType {
49 LockType::Exclusive
50 }
51}
52
53pub(crate) fn run(args: CompileCommand, workspace: Workspace) -> Result<(), CliError> {
54 if args.watch {
55 if args.compile_options.debug_compile_stdin {
56 return Err(CliError::CantWatchStdin);
57 }
58 watch_workspace(&workspace, &args.compile_options)
59 .map_err(|err| CliError::Generic(err.to_string()))?;
60 } else {
61 let debug_compile_stdin = None;
62 compile_workspace_full(&workspace, &args.compile_options, debug_compile_stdin)?;
63 }
64 Ok(())
65}
66
67fn watch_workspace(
69 workspace: &Workspace,
70 compile_options: &CompileOptions,
71) -> notify_debouncer_full::notify::Result<()> {
72 let (tx, rx) = std::sync::mpsc::channel();
73
74 let mut debouncer = new_debouncer(Duration::from_secs(1), None, tx)?;
76
77 debouncer.watch(&workspace.root_dir, RecursiveMode::Recursive)?;
80
81 let mut screen = std::io::stdout();
82 write!(screen, "{}", termion::cursor::Save).unwrap();
83 screen.flush().unwrap();
84 let debug_compile_stdin = None;
85 let _ = compile_workspace_full(workspace, compile_options, debug_compile_stdin);
86 for res in rx {
87 let debounced_events = res.map_err(|mut err| err.remove(0))?;
88
89 let noir_files_modified = debounced_events.iter().any(|event| {
91 let mut event_paths = event.event.paths.iter();
92 let event_affects_noir_file =
93 event_paths.any(|path| path.extension().is_some_and(|ext| ext == "nr"));
94
95 let is_relevant_event_kind = matches!(
96 event.kind,
97 EventKind::Create(_) | EventKind::Modify(_) | EventKind::Remove(_)
98 );
99
100 is_relevant_event_kind && event_affects_noir_file
101 });
102
103 if noir_files_modified {
104 write!(screen, "{}{}", termion::cursor::Restore, termion::clear::AfterCursor).unwrap();
105 screen.flush().unwrap();
106 let debug_compile_stdin = None;
107 let _ = compile_workspace_full(workspace, compile_options, debug_compile_stdin);
108 }
109 }
110
111 screen.flush().unwrap();
112
113 Ok(())
114}
115
116pub fn parse_workspace(
118 workspace: &Workspace,
119 debug_compile_stdin: Option<String>,
120) -> (FileManager, ParsedFiles) {
121 let mut file_manager = workspace.new_file_manager();
122
123 if let Some(main_nr) = debug_compile_stdin {
124 file_manager.add_file_with_source(Path::new("src/main.nr"), main_nr);
125 } else {
126 insert_all_files_for_workspace_into_file_manager(workspace, &mut file_manager);
127 }
128
129 let parsed_files = parse_all(&file_manager);
130 (file_manager, parsed_files)
131}
132
133pub fn compile_workspace_full(
136 workspace: &Workspace,
137 compile_options: &CompileOptions,
138 debug_compile_stdin: Option<String>, ) -> Result<(), CliError> {
140 let mut debug_compile_stdin = debug_compile_stdin;
141 if compile_options.debug_compile_stdin && debug_compile_stdin.is_none() {
142 let mut main_nr = String::new();
143 let stdin = std::io::stdin();
144 let mut stdin_handle = stdin.lock();
145 stdin_handle.read_to_string(&mut main_nr).expect("reading from stdin to succeed");
146 debug_compile_stdin = Some(main_nr);
147 }
148 let (workspace_file_manager, parsed_files) = parse_workspace(workspace, debug_compile_stdin);
149
150 let compiled_workspace =
151 compile_workspace(&workspace_file_manager, &parsed_files, workspace, compile_options)?;
152
153 report_errors(
154 compiled_workspace,
155 &workspace_file_manager,
156 &parsed_files,
157 compile_options.deny_warnings,
158 compile_options.silence_warnings,
159 )?;
160
161 Ok(())
162}
163
164fn compile_workspace(
167 file_manager: &FileManager,
168 parsed_files: &ParsedFiles,
169 workspace: &Workspace,
170 compile_options: &CompileOptions,
171) -> Result<CompilationResult<()>, CliError> {
172 let (binary_packages, contract_packages): (Vec<_>, Vec<_>) = workspace
173 .into_iter()
174 .filter(|package| !package.is_library())
175 .cloned()
176 .partition(|package| package.is_binary());
177
178 let program_warnings_or_errors: CompilationResult<()> =
180 compile_programs(file_manager, parsed_files, workspace, &binary_packages, compile_options)?;
181
182 let contract_warnings_or_errors: CompilationResult<()> = compile_contracts(
183 file_manager,
184 parsed_files,
185 &contract_packages,
186 compile_options,
187 &workspace.target_directory_path(),
188 )?;
189
190 let result = match (program_warnings_or_errors, contract_warnings_or_errors) {
191 (Ok((_, program_warnings)), Ok((_, contract_warnings))) => {
192 let warnings = [program_warnings, contract_warnings].concat();
193 Ok(((), warnings))
194 }
195 (Err(program_errors), Err(contract_errors)) => {
196 Err([program_errors, contract_errors].concat())
197 }
198 (Err(errors), _) | (_, Err(errors)) => Err(errors),
199 };
200 Ok(result)
201}
202
203fn compile_programs(
205 file_manager: &FileManager,
206 parsed_files: &ParsedFiles,
207 workspace: &Workspace,
208 binary_packages: &[Package],
209 compile_options: &CompileOptions,
210) -> Result<CompilationResult<()>, CliError> {
211 let load_cached_program = |package| {
214 let program_artifact_path = workspace.package_build_path(package);
215 read_program_from_file(&program_artifact_path)
216 .ok()
217 .filter(|p| p.noir_version == NOIR_ARTIFACT_VERSION_STRING)
218 .map(|p| p.into())
219 };
220
221 let compile_package = |package| -> Result<CompilationResult<()>, CliError> {
222 let cached_program = load_cached_program(package);
223
224 let cached_hash =
227 cached_program.as_ref().map(|prog| rustc_hash::FxBuildHasher.hash_one(prog));
228
229 match compile_program(
231 file_manager,
232 parsed_files,
233 workspace,
234 package,
235 compile_options,
236 cached_program,
237 ) {
238 Ok((program, warnings)) => {
239 if cached_hash == Some(rustc_hash::FxBuildHasher.hash_one(&program)) {
243 return Ok(Ok(((), warnings)));
244 }
245 let program = nargo::ops::optimize_program(program);
247 match nargo::ops::check_program(&program) {
249 Ok(()) => {
250 let _ = save_program_to_file(
252 &program.into(),
253 &package.name,
254 &workspace.target_directory_path(),
255 )?;
256 Ok(Ok(((), warnings)))
257 }
258 Err(errors_and_warnings) => Ok(Err(errors_and_warnings)),
259 }
260 }
261 Err(errors_and_warnings) => Ok(Err(errors_and_warnings)),
262 }
263 };
264
265 let num_threads = rayon::current_num_threads().min(binary_packages.len()).max(1);
268 let pool = rayon::ThreadPoolBuilder::new()
269 .num_threads(num_threads)
270 .stack_size(4 * 1024 * 1024)
271 .build()
272 .unwrap();
273 let program_results = pool.install(|| {
274 binary_packages.par_iter().map(compile_package).collect::<Result<Vec<_>, _>>()
275 })?;
276
277 Ok(collect_errors(program_results).map(|(_, warnings)| ((), warnings)))
279}
280
281fn compile_contracts(
283 file_manager: &FileManager,
284 parsed_files: &ParsedFiles,
285 contract_packages: &[Package],
286 compile_options: &CompileOptions,
287 target_dir: &Path,
288) -> Result<CompilationResult<()>, CliError> {
289 let contract_results = contract_packages
290 .par_iter()
291 .map(|package| -> Result<CompilationResult<()>, CliError> {
292 match compile_contract(file_manager, parsed_files, package, compile_options) {
293 Ok((contract, warnings)) => {
294 let contract = nargo::ops::optimize_contract(contract);
295 save_contract(
296 contract,
297 package,
298 target_dir,
299 compile_options.show_artifact_paths,
300 )?;
301 Ok(Ok(((), warnings)))
302 }
303 Err(errors_and_warnings) => Ok(Err(errors_and_warnings)),
304 }
305 })
306 .collect::<Result<Vec<_>, CliError>>()?;
307
308 let errors = collect_errors(contract_results).map(|(_, warnings)| ((), warnings));
310 Ok(errors)
311}
312
313fn save_contract(
314 contract: CompiledContract,
315 package: &Package,
316 target_dir: &Path,
317 show_artifact_paths: bool,
318) -> Result<(), CliError> {
319 let contract_name = contract.name.clone();
320 let artifact_path = save_contract_to_file(
321 &contract.into(),
322 &format!("{}-{}", package.name, contract_name),
323 target_dir,
324 )?;
325 if show_artifact_paths {
326 println!("Saved contract artifact to: {}", artifact_path.display());
327 }
328 Ok(())
329}