11//! `vz build` -- Build Dockerfiles into the local vz OCI store.
22
33use std:: collections:: BTreeMap ;
4+ #[ cfg( target_os = "macos" ) ]
5+ use std:: io:: Write ;
46use std:: path:: { Path , PathBuf } ;
57
68use clap:: { Args , Subcommand , ValueEnum } ;
@@ -99,6 +101,8 @@ pub enum ProgressArg {
99101 Auto ,
100102 Plain ,
101103 Tty ,
104+ #[ value( name = "rawjson" ) ]
105+ RawJson ,
102106}
103107
104108impl From < ProgressArg > for vz_oci_macos:: buildkit:: BuildProgress {
@@ -107,6 +111,7 @@ impl From<ProgressArg> for vz_oci_macos::buildkit::BuildProgress {
107111 ProgressArg :: Auto => Self :: Auto ,
108112 ProgressArg :: Plain => Self :: Plain ,
109113 ProgressArg :: Tty => Self :: Tty ,
114+ ProgressArg :: RawJson => Self :: RawJson ,
110115 }
111116 }
112117}
@@ -126,6 +131,7 @@ pub async fn run(args: BuildArgs) -> anyhow::Result<()> {
126131 let build_args = parse_build_args ( & args. build_args ) ?;
127132 let secrets = parse_secrets ( & args. secrets ) ?;
128133 let output = parse_output_mode ( args. push , args. output . as_deref ( ) ) ?;
134+ let progress = args. progress ;
129135
130136 let request = vz_oci_macos:: BuildRequest {
131137 context_dir,
@@ -136,10 +142,15 @@ pub async fn run(args: BuildArgs) -> anyhow::Result<()> {
136142 secrets,
137143 no_cache : args. no_cache ,
138144 output,
139- progress : args . progress . into ( ) ,
145+ progress : progress. into ( ) ,
140146 } ;
141147
142- let result = vz_oci_macos:: buildkit:: build_image ( & config, request) . await ?;
148+ let mut streamer = BuildEventStreamer :: new ( progress) ;
149+ let result = vz_oci_macos:: buildkit:: build_image_with_events ( & config, request, |event| {
150+ streamer. handle ( event) ;
151+ } )
152+ . await ?;
153+ streamer. finish ( ) ;
143154 match ( & result. image_id , & result. output_path , result. pushed ) {
144155 ( Some ( image_id) , _, _) => println ! ( "Built {} as {}" , result. tag, image_id. 0 ) ,
145156 ( _, Some ( path) , _) => {
@@ -163,6 +174,89 @@ pub async fn run(args: BuildArgs) -> anyhow::Result<()> {
163174 }
164175}
165176
177+ #[ cfg( target_os = "macos" ) ]
178+ #[ derive( Debug , Clone , Copy , Default ) ]
179+ struct RawJsonEventCounters {
180+ vertexes : usize ,
181+ statuses : usize ,
182+ logs : usize ,
183+ warnings : usize ,
184+ }
185+
186+ #[ cfg( target_os = "macos" ) ]
187+ struct BuildEventStreamer {
188+ progress : ProgressArg ,
189+ stdout : std:: io:: Stdout ,
190+ stderr : std:: io:: Stderr ,
191+ rawjson : RawJsonEventCounters ,
192+ }
193+
194+ #[ cfg( target_os = "macos" ) ]
195+ impl BuildEventStreamer {
196+ fn new ( progress : ProgressArg ) -> Self {
197+ Self {
198+ progress,
199+ stdout : std:: io:: stdout ( ) ,
200+ stderr : std:: io:: stderr ( ) ,
201+ rawjson : RawJsonEventCounters :: default ( ) ,
202+ }
203+ }
204+
205+ fn handle ( & mut self , event : vz_oci_macos:: buildkit:: BuildEvent ) {
206+ use vz_oci_macos:: buildkit:: { BuildEvent , BuildLogStream } ;
207+
208+ match event {
209+ BuildEvent :: Status { message } => {
210+ let _ = writeln ! ( self . stderr, "==> {message}" ) ;
211+ let _ = self . stderr . flush ( ) ;
212+ }
213+ BuildEvent :: Output { stream, chunk } => match stream {
214+ BuildLogStream :: Stdout => {
215+ let _ = self . stdout . write_all ( & chunk) ;
216+ let _ = self . stdout . flush ( ) ;
217+ }
218+ BuildLogStream :: Stderr => {
219+ let _ = self . stderr . write_all ( & chunk) ;
220+ let _ = self . stderr . flush ( ) ;
221+ }
222+ } ,
223+ BuildEvent :: SolveStatus { status } => {
224+ if matches ! ( self . progress, ProgressArg :: RawJson ) {
225+ self . rawjson . vertexes += status. vertexes . len ( ) ;
226+ self . rawjson . statuses += status. statuses . len ( ) ;
227+ self . rawjson . logs += status. logs . len ( ) ;
228+ self . rawjson . warnings += status. warnings . len ( ) ;
229+ }
230+ }
231+ BuildEvent :: RawJsonDecodeError { line, error } => {
232+ if matches ! ( self . progress, ProgressArg :: RawJson ) {
233+ let _ = writeln ! (
234+ self . stderr,
235+ "warning: failed to parse BuildKit rawjson line ({error}): {line}"
236+ ) ;
237+ let _ = self . stderr . flush ( ) ;
238+ }
239+ }
240+ }
241+ }
242+
243+ fn finish ( & mut self ) {
244+ let _ = self . stdout . flush ( ) ;
245+ let _ = self . stderr . flush ( ) ;
246+ if matches ! ( self . progress, ProgressArg :: RawJson ) {
247+ let _ = writeln ! (
248+ self . stderr,
249+ "rawjson summary: vertexes={}, statuses={}, logs={}, warnings={}" ,
250+ self . rawjson. vertexes,
251+ self . rawjson. statuses,
252+ self . rawjson. logs,
253+ self . rawjson. warnings
254+ ) ;
255+ let _ = self . stderr . flush ( ) ;
256+ }
257+ }
258+ }
259+
166260#[ cfg( target_os = "macos" ) ]
167261async fn run_subcommand (
168262 config : vz_oci_macos:: RuntimeConfig ,
@@ -307,6 +401,7 @@ mod tests {
307401 #![ allow( clippy:: unwrap_used) ]
308402
309403 use super :: * ;
404+ use clap:: ValueEnum ;
310405
311406 #[ test]
312407 fn parse_build_args_supports_multiple_values ( ) {
@@ -380,4 +475,19 @@ mod tests {
380475 let tag = default_tag ( Path :: new ( "/tmp/My App" ) ) ;
381476 assert_eq ! ( tag, "my-app:latest" ) ;
382477 }
478+
479+ #[ test]
480+ fn progress_arg_supports_rawjson_value ( ) {
481+ let parsed = ProgressArg :: from_str ( "rawjson" , true ) . unwrap ( ) ;
482+ assert ! ( matches!( parsed, ProgressArg :: RawJson ) ) ;
483+ }
484+
485+ #[ test]
486+ fn progress_arg_maps_to_buildkit_progress ( ) {
487+ let mapped: vz_oci_macos:: buildkit:: BuildProgress = ProgressArg :: RawJson . into ( ) ;
488+ assert ! ( matches!(
489+ mapped,
490+ vz_oci_macos:: buildkit:: BuildProgress :: RawJson
491+ ) ) ;
492+ }
383493}
0 commit comments