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 (). toFile (), 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,8 @@ 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 (
96+ manifest .getParent ().toFile (), mvnTreeCmd .toArray (String []::new ), mvnEnvs );
9497 if (debugLoggingIsNeeded ()) {
9598 String stackAnalysisDependencyTree = Files .readString (tmpFile );
9699 log .info (
@@ -137,7 +140,7 @@ private Content generateSbomFromEffectivePom() throws IOException {
137140 manifest .toString ()
138141 };
139142 // execute the effective pom command
140- Operations .runProcess (mvnEffPomCmd , getMvnExecEnvs ());
143+ Operations .runProcess (manifest . getParent (). toFile (), mvnEffPomCmd , getMvnExecEnvs ());
141144 if (debugLoggingIsNeeded ()) {
142145 String CaEffectivePoM = Files .readString (tmpEffPom );
143146 log .info (
@@ -366,4 +369,68 @@ public int hashCode() {
366369 return Objects .hash (groupId , artifactId , version );
367370 }
368371 }
372+
373+ private String selectMvnRuntime (final Path manifestPath ) {
374+ boolean preferWrapper = Operations .getWrapperPreference (MVN );
375+ if (preferWrapper ) {
376+ String wrapperName = isWindows () ? "mvnw.cmd" : "mvnw" ;
377+ String mvnw = traverseForMvnw (wrapperName , manifestPath .toString ());
378+ if (mvnw != null ) {
379+ try {
380+ // verify maven wrapper is accessible
381+ Operations .runProcess (manifest .getParent ().toFile (), mvnw , ARG_VERSION );
382+ log .fine ("using maven wrapper from : " + mvnw );
383+ return mvnw ;
384+ } catch (Exception e ) {
385+ log .warning (
386+ "Failed to check for mvnw due to: " + e .getMessage () + " Fail back to use mvn" );
387+ }
388+ }
389+ }
390+ // If maven wrapper is not requested or not accessible, fail back to use mvn
391+ String mvn = Operations .getExecutable (MVN , ARG_VERSION );
392+ log .fine ("using mvn executable from : " + mvn );
393+ return mvn ;
394+ }
395+
396+ private String traverseForMvnw (String wrapperName , String startingManifest ) {
397+ return traverseForMvnw (wrapperName , startingManifest , null );
398+ }
399+
400+ public static String traverseForMvnw (
401+ String wrapperName , String startingManifest , String repoRoot ) {
402+ String normalizedManifest = normalizePath (startingManifest );
403+
404+ Path path = Paths .get (normalizedManifest );
405+ if (repoRoot == null ) {
406+ repoRoot =
407+ Operations .getGitRootDir (path .getParent ().toString ())
408+ .orElse (path .toAbsolutePath ().getRoot ().toString ());
409+ }
410+
411+ Path wrapperPath = path .getParent ().resolve (wrapperName ).toAbsolutePath ();
412+
413+ if (Files .isExecutable (wrapperPath )) {
414+ return wrapperPath .toString ();
415+ } else {
416+ String currentDir = path .getParent ().toAbsolutePath ().toString ();
417+ if (currentDir .equals (repoRoot )) {
418+ return null ;
419+ }
420+ return traverseForMvnw (wrapperName , path .getParent ().toString (), repoRoot );
421+ }
422+ }
423+
424+ public static String normalizePath (String thePath ) {
425+ Path normalized = Paths .get (thePath ).toAbsolutePath ().normalize ();
426+ String result = normalized .toString ();
427+ if (isWindows ()) {
428+ result = result .toLowerCase ();
429+ }
430+ return result ;
431+ }
432+
433+ private static boolean isWindows () {
434+ return System .getProperty ("os.name" ).toLowerCase ().contains ("win" );
435+ }
369436}
0 commit comments