11//! Build analysis logging infrastructure.
22
3+ use std:: hash:: Hash ;
34use std:: io:: { BufWriter , Write } ;
45use std:: mem:: ManuallyDrop ;
56use std:: path:: Path ;
67use std:: sync:: mpsc;
78use std:: sync:: mpsc:: Sender ;
89use std:: thread:: JoinHandle ;
910
11+ use anyhow:: Context as _;
1012use cargo_util:: paths;
1113
1214use crate :: CargoResult ;
@@ -17,7 +19,7 @@ use crate::util::short_hash;
1719/// Logger for `-Zbuild-analysis`.
1820pub struct BuildLogger {
1921 tx : ManuallyDrop < Sender < LogMessage > > ,
20- run_id : String ,
22+ run_id : RunId ,
2123 handle : Option < JoinHandle < ( ) > > ,
2224}
2325
@@ -38,7 +40,7 @@ impl BuildLogger {
3840 }
3941
4042 fn new ( ws : & Workspace < ' _ > ) -> CargoResult < Self > {
41- let run_id = Self :: generate_run_id ( ws) ? ;
43+ let run_id = Self :: generate_run_id ( ws) ;
4244
4345 let log_dir = ws. gctx ( ) . home ( ) . join ( "log" ) ;
4446 paths:: create_dir_all ( log_dir. as_path_unlocked ( ) ) ?;
@@ -52,11 +54,11 @@ impl BuildLogger {
5254
5355 let ( tx, rx) = mpsc:: channel :: < LogMessage > ( ) ;
5456
55- let run_id_clone = run_id. clone ( ) ;
57+ let run_id_str = run_id. to_string ( ) ;
5658 let handle = std:: thread:: spawn ( move || {
5759 let mut writer = BufWriter :: new ( log_file) ;
5860 for msg in rx {
59- let _ = msg. write_json_log ( & mut writer, & run_id_clone ) ;
61+ let _ = msg. write_json_log ( & mut writer, & run_id_str ) ;
6062 }
6163 let _ = writer. flush ( ) ;
6264 } ) ;
@@ -69,18 +71,12 @@ impl BuildLogger {
6971 }
7072
7173 /// Generates a unique run ID.
72- ///
73- /// The format is `{timestamp}-{hash}`, with `:` and `.` in the timestamp
74- /// removed to make it safe for filenames.
75- /// For example, `20251024T194502773638Z-f891d525d52ecab3`.
76- pub fn generate_run_id ( ws : & Workspace < ' _ > ) -> CargoResult < String > {
77- let hash = short_hash ( & ws. root ( ) ) ;
78- let timestamp = jiff:: Timestamp :: now ( ) . to_string ( ) . replace ( [ ':' , '.' ] , "" ) ;
79- Ok ( format ! ( "{timestamp}-{hash}" ) )
74+ pub fn generate_run_id ( ws : & Workspace < ' _ > ) -> RunId {
75+ RunId :: new ( & ws. root ( ) )
8076 }
8177
8278 /// Returns the run ID for this build session.
83- pub fn run_id ( & self ) -> & str {
79+ pub fn run_id ( & self ) -> & RunId {
8480 & self . run_id
8581 }
8682
@@ -103,3 +99,74 @@ impl Drop for BuildLogger {
10399 }
104100 }
105101}
102+
103+ /// A unique identifier for a Cargo invocation.
104+ #[ derive( Clone ) ]
105+ pub struct RunId {
106+ timestamp : jiff:: Timestamp ,
107+ hash : String ,
108+ }
109+
110+ impl RunId {
111+ const FORMAT : & str = "%Y-%m-%dT%H%M%S%3fZ" ;
112+
113+ pub fn new < H : Hash > ( h : & H ) -> RunId {
114+ RunId {
115+ timestamp : jiff:: Timestamp :: now ( ) ,
116+ hash : short_hash ( h) ,
117+ }
118+ }
119+
120+ pub fn timestamp ( & self ) -> & jiff:: Timestamp {
121+ & self . timestamp
122+ }
123+
124+ /// Checks whether ID was generated from the same workspace.
125+ pub fn same_workspace ( & self , other : & RunId ) -> bool {
126+ self . hash == other. hash
127+ }
128+ }
129+
130+ impl std:: fmt:: Display for RunId {
131+ fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
132+ let hash = & self . hash ;
133+ let timestamp = self . timestamp . strftime ( Self :: FORMAT ) ;
134+ write ! ( f, "{timestamp}-{hash}" )
135+ }
136+ }
137+
138+ impl std:: str:: FromStr for RunId {
139+ type Err = anyhow:: Error ;
140+
141+ fn from_str ( s : & str ) -> Result < Self , Self :: Err > {
142+ let msg =
143+ || format ! ( "expect run ID in format `2006-07-24T012128000Z-<16-char-hex>`, got `{s}`" ) ;
144+ let Some ( ( timestamp, hash) ) = s. rsplit_once ( '-' ) else {
145+ anyhow:: bail!( msg( ) ) ;
146+ } ;
147+
148+ if hash. len ( ) != 16 || !hash. chars ( ) . all ( |c| c. is_ascii_hexdigit ( ) ) {
149+ anyhow:: bail!( msg( ) ) ;
150+ }
151+ let timestamp = jiff:: civil:: DateTime :: strptime ( Self :: FORMAT , timestamp)
152+ . and_then ( |dt| dt. to_zoned ( jiff:: tz:: TimeZone :: UTC ) )
153+ . map ( |zoned| zoned. timestamp ( ) )
154+ . with_context ( msg) ?;
155+
156+ Ok ( RunId {
157+ timestamp,
158+ hash : hash. into ( ) ,
159+ } )
160+ }
161+ }
162+
163+ #[ cfg( test) ]
164+ mod tests {
165+ use super :: * ;
166+
167+ #[ test]
168+ fn run_id_round_trip ( ) {
169+ let id = "2006-07-24T012128000Z-b0fd440798ab3cfb" ;
170+ assert_eq ! ( id, & id. parse:: <RunId >( ) . unwrap( ) . to_string( ) ) ;
171+ }
172+ }
0 commit comments