@@ -95,46 +95,86 @@ fn is_mvn_startup_noise(line: &str) -> bool {
9595 false
9696}
9797
98- /// Auto-detect mvnw wrapper; fall back to system `mvn`.
99- fn mvn_command ( ) -> std:: process:: Command {
100- if Path :: new ( "mvnw" ) . exists ( ) {
101- resolved_command ( "./mvnw" )
102- } else {
103- resolved_command ( "mvn" )
98+ /// Which Maven binary to invoke. `Mvn` auto-detects the `mvnw` wrapper and
99+ /// falls back to system `mvn`; `Mvnd` always uses the Maven Daemon (`mvnd`),
100+ /// which is incompatible with the wrapper.
101+ #[ derive( Debug , Clone , Copy , PartialEq , Eq ) ]
102+ pub enum MvnBinary {
103+ Mvn ,
104+ Mvnd ,
105+ }
106+
107+ impl MvnBinary {
108+ fn as_str ( self ) -> & ' static str {
109+ match self {
110+ MvnBinary :: Mvn => "mvn" ,
111+ MvnBinary :: Mvnd => "mvnd" ,
112+ }
113+ }
114+ }
115+
116+ impl std:: fmt:: Display for MvnBinary {
117+ fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
118+ f. write_str ( self . as_str ( ) )
119+ }
120+ }
121+
122+ /// Build the `(tool_name, tee_label)` pair used for tracking a run of
123+ /// `<binary> <goal>`. Tee labels use `_` separators (filesystem-safe); tool
124+ /// names use a space (human-readable in `rtk gain`). Kept as a single helper
125+ /// so the `{binary}`/`_` convention stays consistent across all mvn/mvnd runs.
126+ fn mvn_labels ( binary : MvnBinary , goal : & str , tee_slug : & str ) -> ( String , String ) {
127+ ( format ! ( "{binary} {goal}" ) , format ! ( "{binary}_{tee_slug}" ) )
128+ }
129+
130+ /// Build the base command for the selected binary. For `Mvn`, auto-detects the
131+ /// `mvnw` wrapper and falls back to system `mvn`. For `Mvnd`, always invokes
132+ /// `mvnd` directly (the daemon does not use wrapper scripts).
133+ fn mvn_command ( binary : MvnBinary ) -> std:: process:: Command {
134+ match binary {
135+ MvnBinary :: Mvn => {
136+ if Path :: new ( "mvnw" ) . exists ( ) {
137+ resolved_command ( "./mvnw" )
138+ } else {
139+ resolved_command ( "mvn" )
140+ }
141+ }
142+ MvnBinary :: Mvnd => resolved_command ( "mvnd" ) ,
104143 }
105144}
106145
107- /// Run `mvn test` with state-machine filtered output.
108- pub fn run_test ( args : & [ String ] , verbose : u8 ) -> Result < i32 > {
109- let mut cmd = mvn_command ( ) ;
146+ /// Run `<binary> test` with state-machine filtered output.
147+ pub fn run_test ( binary : MvnBinary , args : & [ String ] , verbose : u8 ) -> Result < i32 > {
148+ let mut cmd = mvn_command ( binary ) ;
110149 cmd. arg ( "test" ) ;
111150
112151 for arg in args {
113152 cmd. arg ( arg) ;
114153 }
115154
116155 if verbose > 0 {
117- eprintln ! ( "Running: mvn test {}" , args. join( " " ) ) ;
156+ eprintln ! ( "Running: {binary} test {}" , args. join( " " ) ) ;
118157 }
119158
120159 let started_at = std:: time:: SystemTime :: now ( ) ;
121160 let cwd = std:: env:: current_dir ( ) . unwrap_or_else ( |e| {
122- eprintln ! ( "rtk mvn : could not determine cwd: {e}" ) ;
161+ eprintln ! ( "rtk {binary} : could not determine cwd: {e}" ) ;
123162 std:: path:: PathBuf :: from ( "." )
124163 } ) ;
125164 let app_pkgs = crate :: cmds:: java:: pom_groupid:: detect ( & cwd) ;
126165
127166 let cwd_for_filter = cwd. clone ( ) ;
128167
168+ let ( tool_name, tee_label) = mvn_labels ( binary, "test" , "test" ) ;
129169 runner:: run_filtered (
130170 cmd,
131- "mvn test" ,
171+ & tool_name ,
132172 & args. join ( " " ) ,
133173 move |raw : & str | {
134174 let filtered = filter_mvn_test ( raw) ;
135175 enrich_with_reports ( & filtered, & cwd_for_filter, started_at, & app_pkgs)
136176 } ,
137- runner:: RunOptions :: with_tee ( "mvn_test" ) ,
177+ runner:: RunOptions :: with_tee ( & tee_label ) ,
138178 )
139179}
140180
@@ -143,101 +183,105 @@ pub fn run_test(args: &[String], verbose: u8) -> Result<i32> {
143183/// `compile` is itself a Maven lifecycle phase (not a goal name we invented),
144184/// so no implicit default is added when `args` is empty — `mvn compile` runs
145185/// the compile phase directly.
146- pub fn run_compile ( args : & [ String ] , verbose : u8 ) -> Result < i32 > {
147- run_compile_like ( "compile" , args, verbose)
186+ pub fn run_compile ( binary : MvnBinary , args : & [ String ] , verbose : u8 ) -> Result < i32 > {
187+ run_compile_like ( binary , "compile" , args, verbose)
148188}
149189
150- /// Shared implementation for compile-phase-like goals: runs `mvn <goal> <args>`
190+ /// Shared implementation for compile-phase-like goals: runs `<binary> <goal> <args>`
151191/// through `filter_mvn_compile`. Used directly by `run_compile` and reused by
152192/// `run_other` to route `process-classes` / `test-compile` through the same
153193/// filter while preserving the original goal name in the invocation and in
154194/// the tracking label.
155- fn run_compile_like ( goal : & str , args : & [ String ] , verbose : u8 ) -> Result < i32 > {
156- let mut cmd = mvn_command ( ) ;
195+ fn run_compile_like ( binary : MvnBinary , goal : & str , args : & [ String ] , verbose : u8 ) -> Result < i32 > {
196+ let mut cmd = mvn_command ( binary ) ;
157197 cmd. arg ( goal) ;
158198 for arg in args {
159199 cmd. arg ( arg) ;
160200 }
161201
162202 if verbose > 0 {
163- eprintln ! ( "Running: mvn {} {}" , goal, args. join( " " ) ) ;
203+ eprintln ! ( "Running: {binary} {} {}" , goal, args. join( " " ) ) ;
164204 }
165205
166- let ( tool_name, tee_label) = compile_like_labels ( goal) ;
206+ let ( tool_name, tee_label) = compile_like_labels ( binary , goal) ;
167207
168208 runner:: run_filtered (
169209 cmd,
170- tool_name,
210+ & tool_name,
171211 & args. join ( " " ) ,
172212 filter_mvn_compile,
173- runner:: RunOptions :: with_tee ( tee_label) ,
213+ runner:: RunOptions :: with_tee ( & tee_label) ,
174214 )
175215}
176216
177- /// Run `mvn checkstyle:check` with compact output — strips mvn/JVM startup
217+ /// Run `<binary> checkstyle:check` with compact output — strips mvn/JVM startup
178218/// noise, keeps violations and BUILD SUCCESS/FAILURE summary.
179- pub fn run_checkstyle ( args : & [ String ] , verbose : u8 ) -> Result < i32 > {
180- let mut cmd = mvn_command ( ) ;
219+ pub fn run_checkstyle ( binary : MvnBinary , args : & [ String ] , verbose : u8 ) -> Result < i32 > {
220+ let mut cmd = mvn_command ( binary ) ;
181221 cmd. arg ( "checkstyle:check" ) ;
182222 for arg in args {
183223 cmd. arg ( arg) ;
184224 }
185225
186226 if verbose > 0 {
187- eprintln ! ( "Running: mvn checkstyle:check {}" , args. join( " " ) ) ;
227+ eprintln ! ( "Running: {binary} checkstyle:check {}" , args. join( " " ) ) ;
188228 }
189229
230+ let ( tool_name, tee_label) = mvn_labels ( binary, "checkstyle:check" , "checkstyle" ) ;
190231 runner:: run_filtered (
191232 cmd,
192- "mvn checkstyle:check" ,
233+ & tool_name ,
193234 & args. join ( " " ) ,
194235 filter_mvn_checkstyle,
195- runner:: RunOptions :: with_tee ( "mvn_checkstyle" ) ,
236+ runner:: RunOptions :: with_tee ( & tee_label ) ,
196237 )
197238}
198239
199- /// Run `mvn dependency:tree` with filtered output — strips duplicates and boilerplate.
200- pub fn run_dep_tree ( args : & [ String ] , verbose : u8 ) -> Result < i32 > {
201- let mut cmd = mvn_command ( ) ;
240+ /// Run `<binary> dependency:tree` with filtered output — strips duplicates and boilerplate.
241+ pub fn run_dep_tree ( binary : MvnBinary , args : & [ String ] , verbose : u8 ) -> Result < i32 > {
242+ let mut cmd = mvn_command ( binary ) ;
202243 cmd. arg ( "dependency:tree" ) ;
203244
204245 for arg in args {
205246 cmd. arg ( arg) ;
206247 }
207248
208249 if verbose > 0 {
209- eprintln ! ( "Running: mvn dependency:tree {}" , args. join( " " ) ) ;
250+ eprintln ! ( "Running: {binary} dependency:tree {}" , args. join( " " ) ) ;
210251 }
211252
253+ let ( tool_name, tee_label) = mvn_labels ( binary, "dependency:tree" , "dep_tree" ) ;
212254 runner:: run_filtered (
213255 cmd,
214- "mvn dependency:tree" ,
256+ & tool_name ,
215257 & args. join ( " " ) ,
216258 filter_mvn_dep_tree,
217- runner:: RunOptions :: with_tee ( "mvn_dep_tree" ) ,
259+ runner:: RunOptions :: with_tee ( & tee_label ) ,
218260 )
219261}
220262
221263/// Goals whose output looks like `mvn compile` (same noise profile: plugin
222- /// codegen, npm lifecycle, Liquibase, Docker). Tuples are
223- /// `(goal, tool_name, tee_label)` — single source of truth for routing,
224- /// tracking labels, and tee filenames .
225- const COMPILE_LIKE_GOALS : & [ ( & str , & str , & str ) ] = & [
226- ( "compile" , "mvn compile" , "mvn_compile ") ,
227- ( "process-classes" , "mvn process-classes" , "mvn_process_classes ") ,
228- ( "test-compile" , "mvn test-compile" , "mvn_test_compile ") ,
264+ /// codegen, npm lifecycle, Liquibase, Docker). Tuples are `(goal, tee_slug)`
265+ /// — tool names are prefixed with the active binary at runtime to keep mvn
266+ /// and mvnd metrics separate in `rtk gain` .
267+ const COMPILE_LIKE_GOALS : & [ ( & str , & str ) ] = & [
268+ ( "compile" , "compile" ) ,
269+ ( "process-classes" , "process_classes " ) ,
270+ ( "test-compile" , "test_compile " ) ,
229271] ;
230272
231- /// Look up the `(tool_name, tee_label)` pair for a compile-like goal. Callers
232- /// are gated on `route_goal` / `COMPILE_LIKE_GOALS`, so the fallback is only
233- /// reached if that invariant is violated.
234- fn compile_like_labels ( goal : & str ) -> ( & ' static str , & ' static str ) {
235- for & ( g, tool, tee) in COMPILE_LIKE_GOALS {
236- if g == goal {
237- return ( tool, tee) ;
238- }
239- }
240- ( "mvn compile" , "mvn_compile" )
273+ /// Look up the `(tool_name, tee_label)` pair for a compile-like goal, prefixed
274+ /// with the active binary (`mvn` or `mvnd`). Callers are gated on `route_goal`
275+ /// / `COMPILE_LIKE_GOALS`, so a miss here means that invariant was broken.
276+ fn compile_like_labels ( binary : MvnBinary , goal : & str ) -> ( String , String ) {
277+ let tee_slug = COMPILE_LIKE_GOALS
278+ . iter ( )
279+ . find_map ( |& ( g, slug) | ( g == goal) . then_some ( slug) )
280+ . unwrap_or_else ( || {
281+ debug_assert ! ( false , "compile_like_labels called with non-compile goal: {goal}" ) ;
282+ "compile"
283+ } ) ;
284+ mvn_labels ( binary, goal, tee_slug)
241285}
242286
243287/// Routing decision for a raw mvn subcommand seen on `run_other` — i.e. the
@@ -253,7 +297,7 @@ enum GoalRouting {
253297}
254298
255299fn route_goal ( subcommand : & str ) -> GoalRouting {
256- if COMPILE_LIKE_GOALS . iter ( ) . any ( |( g, _, _ ) | * g == subcommand) {
300+ if COMPILE_LIKE_GOALS . iter ( ) . any ( |( g, _) | * g == subcommand) {
257301 return GoalRouting :: Compile ;
258302 }
259303 if subcommand == "checkstyle:check" || subcommand == "checkstyle" {
@@ -277,46 +321,46 @@ fn trailing_args(args: &[OsString]) -> Vec<String> {
277321/// `checkstyle:check` go through `filter_mvn_checkstyle`; everything else
278322/// streams directly via `status()` (safe for long-running goals like
279323/// `spring-boot:run`, and metric-only for rare ones like `package`).
280- pub fn run_other ( args : & [ OsString ] , verbose : u8 ) -> Result < i32 > {
324+ pub fn run_other ( binary : MvnBinary , args : & [ OsString ] , verbose : u8 ) -> Result < i32 > {
281325 if args. is_empty ( ) {
282- anyhow:: bail!( "mvn : no subcommand specified" ) ;
326+ anyhow:: bail!( "{binary} : no subcommand specified" ) ;
283327 }
284328
285329 let subcommand = args[ 0 ] . to_string_lossy ( ) ;
286330
287331 if verbose > 0 {
288- eprintln ! ( "Running: mvn {} ..." , subcommand) ;
332+ eprintln ! ( "Running: {binary} {} ..." , subcommand) ;
289333 }
290334
291335 match route_goal ( & subcommand) {
292336 GoalRouting :: Compile => {
293- return run_compile_like ( & subcommand, & trailing_args ( args) , verbose) ;
337+ return run_compile_like ( binary , & subcommand, & trailing_args ( args) , verbose) ;
294338 }
295339 GoalRouting :: Checkstyle => {
296- return run_checkstyle ( & trailing_args ( args) , verbose) ;
340+ return run_checkstyle ( binary , & trailing_args ( args) , verbose) ;
297341 }
298342 GoalRouting :: Passthrough => { }
299343 }
300344
301345 // Everything else: passthrough with streaming (safe for spring-boot:run etc.)
302346 let timer = tracking:: TimedExecution :: start ( ) ;
303347
304- let mut cmd = mvn_command ( ) ;
348+ let mut cmd = mvn_command ( binary ) ;
305349 for arg in args {
306350 cmd. arg ( arg) ;
307351 }
308352
309353 let status = cmd
310354 . status ( )
311- . with_context ( || format ! ( "Failed to run mvn {}" , subcommand) ) ?;
355+ . with_context ( || format ! ( "Failed to run {binary} {}" , subcommand) ) ?;
312356
313357 let args_str = tracking:: args_display ( args) ;
314358 timer. track_passthrough (
315- & format ! ( "mvn {}" , args_str) ,
316- & format ! ( "rtk mvn {} (passthrough)" , args_str) ,
359+ & format ! ( "{binary} {}" , args_str) ,
360+ & format ! ( "rtk {binary} {} (passthrough)" , args_str) ,
317361 ) ;
318362
319- Ok ( exit_code_from_status ( & status, "mvn" ) )
363+ Ok ( exit_code_from_status ( & status, binary . as_str ( ) ) )
320364}
321365
322366// ---------------------------------------------------------------------------
@@ -1837,7 +1881,7 @@ mod tests {
18371881
18381882 #[ test]
18391883 fn test_run_other_empty_args_errors ( ) {
1840- let result = run_other ( & [ ] , 0 ) ;
1884+ let result = run_other ( MvnBinary :: Mvn , & [ ] , 0 ) ;
18411885 assert ! ( result. is_err( ) ) ;
18421886 let err_msg = result. unwrap_err ( ) . to_string ( ) ;
18431887 assert ! (
@@ -1847,6 +1891,29 @@ mod tests {
18471891 ) ;
18481892 }
18491893
1894+ #[ test]
1895+ fn test_run_other_empty_args_errors_mvnd ( ) {
1896+ let result = run_other ( MvnBinary :: Mvnd , & [ ] , 0 ) ;
1897+ assert ! ( result. is_err( ) ) ;
1898+ let err_msg = result. unwrap_err ( ) . to_string ( ) ;
1899+ assert ! (
1900+ err_msg. contains( "mvnd: no subcommand" ) ,
1901+ "expected 'mvnd: no subcommand' error, got: {}" ,
1902+ err_msg
1903+ ) ;
1904+ }
1905+
1906+ #[ test]
1907+ fn test_compile_like_labels_mvnd ( ) {
1908+ let ( tool, tee) = compile_like_labels ( MvnBinary :: Mvnd , "compile" ) ;
1909+ assert_eq ! ( tool, "mvnd compile" ) ;
1910+ assert_eq ! ( tee, "mvnd_compile" ) ;
1911+
1912+ let ( tool, tee) = compile_like_labels ( MvnBinary :: Mvnd , "test-compile" ) ;
1913+ assert_eq ! ( tool, "mvnd test-compile" ) ;
1914+ assert_eq ! ( tee, "mvnd_test_compile" ) ;
1915+ }
1916+
18501917 // --- checkstyle filter tests ---
18511918
18521919 #[ test]
0 commit comments