3030import java .io .IOException ;
3131import java .nio .file .Files ;
3232import java .nio .file .Path ;
33+ import java .nio .file .Paths ;
3334import java .util .ArrayList ;
3435import java .util .Collections ;
3536import java .util .List ;
@@ -53,20 +54,21 @@ public final class JavaMavenProvider extends BaseJavaProvider {
5354 private static final String PROP_JAVA_HOME = "JAVA_HOME" ;
5455 private static final Logger log = LoggersFactory .getLogger (JavaMavenProvider .class .getName ());
5556 private final String mvnExecutable ;
57+ private static final String MVN = "mvn" ;
58+ private static final String ARG_VERSION = "-v" ;
5659
5760 public JavaMavenProvider (Path manifest ) {
5861 super (Type .MAVEN , manifest );
59- // check for custom mvn executable
60- this .mvnExecutable = Operations . getExecutable ( "mvn" , "-v" );
62+ // check for custom mvn executable mvn or mvn wrapper
63+ this .mvnExecutable = selectMvnRuntime ( manifest );
6164 }
6265
6366 @ Override
6467 public Content provideStack () throws IOException {
65- // clean command used to clean build target
6668 var mvnCleanCmd = new String [] {mvnExecutable , "clean" , "-f" , manifest .toString ()};
6769 var mvnEnvs = getMvnExecEnvs ();
6870 // execute the clean command
69- Operations .runProcess (mvnCleanCmd , mvnEnvs );
71+ Operations .runProcess (manifest . getParent (), mvnCleanCmd , mvnEnvs );
7072 // create a temp file for storing the dependency tree in
7173 var tmpFile = Files .createTempFile ("exhort_dot_graph_" , null );
7274 // the tree command will build the project and create the dependency tree in the temp file
@@ -90,7 +92,7 @@ public Content provideStack() throws IOException {
9092 .map (PackageURL ::getCoordinates )
9193 .collect (Collectors .toList ());
9294 // execute the tree command
93- Operations .runProcess (mvnTreeCmd .toArray (String []::new ), mvnEnvs );
95+ Operations .runProcess (manifest . getParent (), mvnTreeCmd .toArray (String []::new ), mvnEnvs );
9496 if (debugLoggingIsNeeded ()) {
9597 String stackAnalysisDependencyTree = Files .readString (tmpFile );
9698 log .info (
@@ -137,7 +139,7 @@ private Content generateSbomFromEffectivePom() throws IOException {
137139 manifest .toString ()
138140 };
139141 // execute the effective pom command
140- Operations .runProcess (mvnEffPomCmd , getMvnExecEnvs ());
142+ Operations .runProcess (manifest . getParent (), mvnEffPomCmd , getMvnExecEnvs ());
141143 if (debugLoggingIsNeeded ()) {
142144 String CaEffectivePoM = Files .readString (tmpEffPom );
143145 log .info (
@@ -366,4 +368,68 @@ public int hashCode() {
366368 return Objects .hash (groupId , artifactId , version );
367369 }
368370 }
371+
372+ private String selectMvnRuntime (final Path manifestPath ) {
373+ boolean preferWrapper = Operations .getWrapperPreference (MVN );
374+ if (preferWrapper ) {
375+ String wrapperName = isWindows () ? "mvnw.cmd" : "mvnw" ;
376+ String mvnw = traverseForMvnw (wrapperName , manifestPath .toString ());
377+ if (mvnw != null ) {
378+ try {
379+ // verify maven wrapper is accessible
380+ Operations .runProcess (manifest .getParent (), mvnw , ARG_VERSION );
381+ log .fine ("using maven wrapper from : " + mvnw );
382+ return mvnw ;
383+ } catch (Exception e ) {
384+ log .warning (
385+ "Failed to check for mvnw due to: " + e .getMessage () + " Fail back to use mvn" );
386+ }
387+ }
388+ }
389+ // If maven wrapper is not requested or not accessible, fail back to use mvn
390+ String mvn = Operations .getExecutable (MVN , ARG_VERSION );
391+ log .fine ("using mvn executable from : " + mvn );
392+ return mvn ;
393+ }
394+
395+ private String traverseForMvnw (String wrapperName , String startingManifest ) {
396+ return traverseForMvnw (wrapperName , startingManifest , null );
397+ }
398+
399+ public static String traverseForMvnw (
400+ String wrapperName , String startingManifest , String repoRoot ) {
401+ String normalizedManifest = normalizePath (startingManifest );
402+
403+ Path path = Paths .get (normalizedManifest );
404+ if (repoRoot == null ) {
405+ repoRoot =
406+ Operations .getGitRootDir (path .getParent ().toString ())
407+ .orElse (path .toAbsolutePath ().getRoot ().toString ());
408+ }
409+
410+ Path wrapperPath = path .getParent ().resolve (wrapperName ).toAbsolutePath ();
411+
412+ if (Files .isExecutable (wrapperPath )) {
413+ return wrapperPath .toString ();
414+ } else {
415+ String currentDir = path .getParent ().toAbsolutePath ().toString ();
416+ if (currentDir .equals (repoRoot )) {
417+ return null ;
418+ }
419+ return traverseForMvnw (wrapperName , path .getParent ().toString (), repoRoot );
420+ }
421+ }
422+
423+ public static String normalizePath (String thePath ) {
424+ Path normalized = Paths .get (thePath ).toAbsolutePath ().normalize ();
425+ String result = normalized .toString ();
426+ if (isWindows ()) {
427+ result = result .toLowerCase ();
428+ }
429+ return result ;
430+ }
431+
432+ private static boolean isWindows () {
433+ return System .getProperty ("os.name" ).toLowerCase ().contains ("win" );
434+ }
369435}
0 commit comments