Skip to content

Commit ebfa77b

Browse files
authored
Allow releasing with warnings (#264)
* Make release build failure diagnostics consistent * Allow releasing with warnings Still prompts for confirmation
1 parent 84da2ef commit ebfa77b

File tree

6 files changed

+95
-87
lines changed

6 files changed

+95
-87
lines changed

crates/pcb/src/build.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ pub fn build(
7575
passes: Vec<Box<dyn pcb_zen_core::DiagnosticsPass>>,
7676
deny_warnings: bool,
7777
has_errors: &mut bool,
78+
has_warnings: &mut bool,
7879
) -> Option<Schematic> {
7980
let file_name = zen_path.file_name().unwrap().to_string_lossy();
8081

@@ -129,6 +130,10 @@ pub fn build(
129130
.any(|d| !d.suppressed && matches!(d.severity, starlark::errors::EvalSeverity::Error));
130131
let should_fail = has_unsuppressed_errors || (deny_warnings && has_unsuppressed_warnings);
131132

133+
if has_unsuppressed_warnings {
134+
*has_warnings = true;
135+
}
136+
132137
if should_fail {
133138
*has_errors = true;
134139
eprintln!(
@@ -158,6 +163,7 @@ pub fn execute(args: BuildArgs) -> Result<()> {
158163

159164
// Process each .zen file
160165
let deny_warnings = args.deny.contains(&"warnings".to_string());
166+
let mut has_warnings = false;
161167
for zen_path in &zen_files {
162168
let file_name = zen_path.file_name().unwrap().to_string_lossy();
163169
let Some(schematic) = build(
@@ -166,6 +172,7 @@ pub fn execute(args: BuildArgs) -> Result<()> {
166172
create_diagnostics_passes(&args.suppress),
167173
deny_warnings,
168174
&mut has_errors,
175+
&mut has_warnings,
169176
) else {
170177
continue;
171178
};

crates/pcb/src/layout.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ pub fn execute(args: LayoutArgs) -> Result<()> {
5959
}
6060

6161
let mut has_errors = false;
62+
let mut has_warnings = false;
6263
let mut generated_layouts = Vec::new();
6364

6465
// Process each .zen file
@@ -70,6 +71,7 @@ pub fn execute(args: LayoutArgs) -> Result<()> {
7071
create_diagnostics_passes(&[]),
7172
false, // don't deny warnings for layout command
7273
&mut has_errors,
74+
&mut has_warnings,
7375
) else {
7476
continue;
7577
};

crates/pcb/src/release.rs

Lines changed: 76 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use anyhow::{Context, Result};
22
use clap::{Args, ValueEnum};
3-
3+
use inquire::Confirm;
44
use log::{debug, info, warn};
55
use pcb_kicad::{KiCadCliBuilder, PythonScriptBuilder};
66
use pcb_ui::{Colorize, Spinner, Style, StyledText};
@@ -13,7 +13,7 @@ use pcb_zen_core::{EvalOutput, WithDiagnostics};
1313
use crate::workspace::{gather_workspace_info, WorkspaceInfo};
1414

1515
use std::fs;
16-
use std::io::Write;
16+
use std::io::{IsTerminal, Write};
1717

1818
use chrono::Utc;
1919
use std::path::{Path, PathBuf};
@@ -136,6 +136,10 @@ pub struct ReleaseArgs {
136136
/// Exclude specific manufacturing artifacts from the release (can be specified multiple times)
137137
#[arg(long, value_enum)]
138138
pub exclude: Vec<ArtifactType>,
139+
140+
/// Skip confirmation prompt when warnings are present during validation
141+
#[arg(long)]
142+
pub yes: bool,
139143
}
140144

141145
/// All information gathered during the release preparation phase
@@ -160,6 +164,8 @@ pub struct ReleaseInfo {
160164
pub output_dir: PathBuf,
161165
/// Name of the output .zip file
162166
pub output_name: String,
167+
/// Skip confirmation prompt for warnings
168+
pub yes: bool,
163169
}
164170

165171
type TaskFn = fn(&ReleaseInfo) -> Result<()>;
@@ -205,43 +211,55 @@ fn get_manufacturing_tasks(excluded: &[ArtifactType]) -> Vec<(&'static str, Task
205211
fn execute_tasks(info: &ReleaseInfo, tasks: &[(&str, TaskFn)]) -> Result<()> {
206212
for (name, task) in tasks {
207213
let spinner = Spinner::builder(*name).start();
208-
let res = task(info);
209-
210214
spinner.finish();
211-
match res {
212-
Ok(()) => eprintln!("{} {name}", "✓".green()),
213-
Err(e) => {
214-
eprintln!("{} {name} failed", "✗".red());
215-
return Err(e.context(format!("{name} failed")));
216-
}
217-
}
215+
216+
task(info)?;
217+
eprintln!("{} {name}", "✓".green());
218218
}
219219
Ok(())
220220
}
221221

222222
pub fn execute(args: ReleaseArgs) -> Result<()> {
223-
// Gather all release information based on whether board or file was provided
224223
let release_info = {
225224
let info_spinner = Spinner::builder("Gathering release information").start();
226-
let info = if let Some(board_name) = &args.board {
227-
gather_release_info(
228-
board_name.clone(),
229-
args.path.clone(),
230-
args.source_only,
231-
args.output_dir.clone(),
232-
args.output_name.clone(),
233-
)?
225+
226+
// Gather workspace info and evaluate the zen file
227+
let (workspace, board_name) = if let Some(board_name) = &args.board {
228+
let start_path = args.path.as_deref().unwrap_or(".");
229+
let workspace_info =
230+
get_workspace_info(&DefaultFileProvider::new(), Path::new(start_path))?;
231+
let board_info = workspace_info.find_board_by_name(board_name)?;
232+
let zen_path = board_info.absolute_zen_path(&workspace_info.root);
233+
let workspace = gather_workspace_info(zen_path, true)?;
234+
(workspace, board_name.clone())
234235
} else if let Some(zen_file) = &args.file {
235-
gather_release_info_from_file(
236-
zen_file.clone(),
237-
args.source_only,
238-
args.output_dir.clone(),
239-
args.output_name.clone(),
240-
)?
236+
let workspace = gather_workspace_info(zen_file.clone(), true)?;
237+
let board_name = workspace.board_display_name();
238+
(workspace, board_name)
241239
} else {
242240
unreachable!("Either board or file must be provided due to clap validation")
243241
};
242+
244243
info_spinner.finish();
244+
245+
// Render diagnostics and fail early if there are errors
246+
let diagnostics = &workspace.eval_result.diagnostics;
247+
if diagnostics.has_errors() || workspace.eval_result.output.is_none() {
248+
let mut diagnostics = workspace.eval_result.diagnostics.clone();
249+
let passes = crate::build::create_diagnostics_passes(&[]);
250+
diagnostics.apply_passes(&passes);
251+
anyhow::bail!("Evaluation failed");
252+
}
253+
254+
let info = build_release_info(
255+
workspace,
256+
board_name,
257+
args.source_only,
258+
args.output_dir.clone(),
259+
args.output_name.clone(),
260+
args.yes,
261+
)?;
262+
245263
eprintln!("{} Release information gathered", "✓".green());
246264
info
247265
};
@@ -279,39 +297,14 @@ pub fn execute(args: ReleaseArgs) -> Result<()> {
279297
Ok(())
280298
}
281299

282-
/// Gather all information needed for the release using board name discovery
283-
fn gather_release_info(
284-
board_name: String,
285-
path: Option<String>,
286-
source_only: bool,
287-
output_dir: Option<PathBuf>,
288-
output_name: Option<String>,
289-
) -> Result<ReleaseInfo> {
290-
debug!("Starting release information gathering");
291-
292-
// Get workspace info using discovery
293-
let start_path = path.as_deref().unwrap_or(".");
294-
let workspace_info = get_workspace_info(&DefaultFileProvider::new(), Path::new(start_path))?;
295-
296-
// Find the zen file for the given board
297-
let board_info = workspace_info.find_board_by_name(&board_name)?;
298-
299-
let zen_path = board_info.absolute_zen_path(&workspace_info.root);
300-
301-
// Use common workspace info gathering with the found zen file
302-
let workspace = gather_workspace_info(zen_path, true)?;
303-
304-
// Use shared helper to build release info
305-
build_release_info(workspace, board_name, source_only, output_dir, output_name)
306-
}
307-
308300
/// Build ReleaseInfo from workspace info and other parameters
309301
fn build_release_info(
310302
workspace: WorkspaceInfo,
311303
board_name: String,
312304
source_only: bool,
313305
output_dir: Option<PathBuf>,
314306
output_name: Option<String>,
307+
yes: bool,
315308
) -> Result<ReleaseInfo> {
316309
// Get version and git hash from git
317310
let (version, git_hash) = git_version_and_hash(&workspace.config.root, &board_name)?;
@@ -373,30 +366,10 @@ fn build_release_info(
373366
kind,
374367
output_dir,
375368
output_name,
369+
yes,
376370
})
377371
}
378372

379-
/// Gather all information needed for the release using direct zen file path
380-
fn gather_release_info_from_file(
381-
zen_file_path: PathBuf,
382-
source_only: bool,
383-
output_dir: Option<PathBuf>,
384-
output_name: Option<String>,
385-
) -> Result<ReleaseInfo> {
386-
debug!(
387-
"Starting release information gathering from file: {}",
388-
zen_file_path.display()
389-
);
390-
391-
// Use common workspace info gathering with the zen file path
392-
let workspace = gather_workspace_info(zen_file_path, true)?;
393-
394-
// Get board name from workspace or fallback to zen file stem
395-
let board_name = workspace.board_display_name();
396-
397-
build_release_info(workspace, board_name, source_only, output_dir, output_name)
398-
}
399-
400373
/// Display all the gathered release information
401374
fn display_release_info(info: &ReleaseInfo, format: ReleaseOutputFormat) {
402375
let release_type = match info.kind {
@@ -599,7 +572,7 @@ pub fn extract_layout_path(zen_path: &Path, eval: &WithDiagnostics<EvalOutput>)
599572
let output = eval
600573
.output
601574
.as_ref()
602-
.context("No output in evaluation result")?;
575+
.context("Evaluation failed - see diagnostics above")?;
603576
let properties = output.sch_module.properties();
604577

605578
let layout_path_value = properties.get("layout_path")
@@ -867,22 +840,44 @@ fn validate_build(info: &ReleaseInfo) -> Result<()> {
867840

868841
debug!("Validating build of: {}", staged_zen_path.display());
869842

870-
// Use build function with strict settings - offline mode and deny warnings
843+
let file_name = staged_zen_path.file_name().unwrap().to_string_lossy();
844+
845+
// Use build function with offline mode but allow warnings
871846
let mut has_errors = false;
847+
let mut has_warnings = false;
872848
let _schematic = crate::build::build(
873849
&staged_zen_path,
874850
true, // offline mode since all dependencies should be vendored
875-
crate::build::create_diagnostics_passes(&[]), // no suppression for release
876-
true, // deny_warnings
851+
crate::build::create_diagnostics_passes(&[]),
852+
false, // don't deny warnings - we'll prompt user instead
877853
&mut has_errors,
854+
&mut has_warnings,
878855
);
879856

880857
if has_errors {
881-
anyhow::bail!(
882-
"Build validation failed for staged zen file: {}",
883-
staged_zen_path.display()
884-
);
858+
std::process::exit(1);
885859
}
860+
861+
// Handle warnings if present and --yes flag wasn't passed
862+
if has_warnings && !info.yes {
863+
// Non-interactive if stdin OR stdout is not a terminal
864+
if !std::io::stdin().is_terminal() || !std::io::stdout().is_terminal() {
865+
eprintln!(
866+
"{} {}: Build failed",
867+
pcb_ui::icons::error(),
868+
file_name.with_style(Style::Red).bold()
869+
);
870+
std::process::exit(1);
871+
}
872+
let confirmed =
873+
Confirm::new("Build completed with warnings. Do you want to proceed with the release?")
874+
.with_default(true)
875+
.prompt()?;
876+
if !confirmed {
877+
std::process::exit(1);
878+
}
879+
}
880+
886881
Ok(())
887882
}
888883

crates/pcb/src/sim.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,16 @@ pub fn execute(args: SimArgs) -> Result<()> {
4848

4949
// Reuse the shared build flow from build.rs
5050
let mut has_errors = false;
51+
let mut has_warnings = false;
5152
let passes = create_diagnostics_passes(&[]);
52-
let Some(schematic) = build_zen(&zen_path, false, passes, false, &mut has_errors) else {
53+
let Some(schematic) = build_zen(
54+
&zen_path,
55+
false,
56+
passes,
57+
false,
58+
&mut has_errors,
59+
&mut has_warnings,
60+
) else {
5361
if has_errors {
5462
anyhow::bail!("Build failed with errors");
5563
} else {

crates/pcb/src/tag.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@ fn run_release(info: &TagInfo) -> Result<()> {
163163
output_dir: None, // Use default
164164
output_name: None, // Use default
165165
exclude: info.exclude.clone(), // Pass through exclude list from tag command
166+
yes: false, // Prompt for warnings
166167
};
167168

168169
// Run the full release process - this validates everything

crates/pcb/tests/snapshots/path__path_local_mixed_release.snap

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,3 @@ Warning: Path 'missing_dir' does not exist
3434
│ ╰───────────────────── Path 'missing_dir' does not exist
3535

3636
✗ LocalPathTest.zen: Build failed
37-
✗ Validating build from staged sources failed
38-
Error: Validating build from staged sources failed
39-
40-
Caused by:
41-
Build validation failed for staged zen file: <TEMP_DIR>/.pcb/releases/LocalPathTest-unknown/src/boards/LocalPathTest.zen

0 commit comments

Comments
 (0)