11package io .quarkus .maven ;
22
33import static io .quarkus .analytics .dto .segment .TrackEventType .DEV_MODE ;
4+ import static io .quarkus .maven .QuarkusBootstrapMojo .CLOSE_BOOTSTRAPPED_APP_PARAM ;
5+ import static io .quarkus .maven .QuarkusBootstrapMojo .MODE_PARAM ;
46import static io .smallrye .common .expression .Expression .Flag .LENIENT_SYNTAX ;
57import static io .smallrye .common .expression .Expression .Flag .NO_TRIM ;
68import static java .util .Collections .emptyMap ;
@@ -557,27 +559,31 @@ public void close() throws IOException {
557559 }
558560 return ;
559561 }
560- final Set < Path > changed = new HashSet <> ();
562+ List < String > changedPoms = List . of ();
561563 for (Map .Entry <Path , Long > e : pomFiles .entrySet ()) {
562564 long t = Files .getLastModifiedTime (e .getKey ()).toMillis ();
563565 if (t > e .getValue ()) {
564- changed .add (e .getKey ());
566+ if (changedPoms .isEmpty ()) {
567+ // unless it's a git or some other command, there won't be many POMs modified in 100 milliseconds
568+ changedPoms = new ArrayList <>(1 );
569+ }
570+ changedPoms .add (e .getKey ().toString ());
565571 pomFiles .put (e .getKey (), t );
566572 }
567573 }
568- if (!changed .isEmpty ()) {
569- getLog (). info ( "Changes detected to " + changed + ", restarting dev mode" );
574+ if (!changedPoms .isEmpty ()) {
575+ logPomChanges ( changedPoms );
570576
571577 // stop the runner before we build the new one as the debug port being free
572578 // is tested when building the runner
573579 runner .stop ();
574580
575581 final DevModeRunner newRunner ;
576582 try {
577- bootstrapId = handleAutoCompile ();
583+ bootstrapId = handleAutoCompile (changedPoms );
578584 newRunner = new DevModeRunner (runner .commandLine .getDebugPort (), bootstrapId );
579585 } catch (Exception e ) {
580- getLog ().info ("Could not load changed pom.xml file, changes not applied" , e );
586+ getLog ().info ("Could not load changedPoms pom.xml file, changes not applied" , e );
581587 continue ;
582588 }
583589 newRunner .run ();
@@ -590,6 +596,15 @@ public void close() throws IOException {
590596 }
591597 }
592598
599+ private void logPomChanges (List <String > changedPoms ) {
600+ final StringBuilder sb = new StringBuilder ().append ("Restarting dev mode following changes in " );
601+ sb .append (changedPoms .get (0 ));
602+ for (int i = 1 ; i < changedPoms .size (); ++i ) {
603+ sb .append (", " ).append (changedPoms .get (i ));
604+ }
605+ getLog ().info (sb .toString ());
606+ }
607+
593608 /**
594609 * if the process is forcibly killed then the terminal may be left in raw mode, which
595610 * messes everything up. This attempts to fix that by saving the state so it can be restored
@@ -649,7 +664,18 @@ private void restoreTerminalState() {
649664 }
650665
651666 private String handleAutoCompile () throws MojoExecutionException {
667+ return handleAutoCompile (List .of ());
668+ }
652669
670+ /**
671+ * Invokes Maven project goals that are meant to be executed before quarkus:dev,
672+ * unless they have already been executed.
673+ *
674+ * @param reloadPoms a list of POM files that should be reloaded from disk instead of read from the reactor
675+ * @return bootstrap id
676+ * @throws MojoExecutionException in case of an error
677+ */
678+ private String handleAutoCompile (List <String > reloadPoms ) throws MojoExecutionException {
653679 List <String > goals = session .getGoals ();
654680 // check for default goal(s) if none were specified explicitly,
655681 // see also org.apache.maven.lifecycle.internal.DefaultLifecycleTaskSegmentCalculator
@@ -761,10 +787,7 @@ private String handleAutoCompile() throws MojoExecutionException {
761787 }
762788 }
763789
764- final Map <String , String > quarkusGoalParams = Map .of (
765- "mode" , LaunchMode .DEVELOPMENT .name (),
766- QuarkusBootstrapMojo .CLOSE_BOOTSTRAPPED_APP , "false" ,
767- "bootstrapId" , bootstrapId );
790+ Map <String , String > quarkusGoalParams = null ;
768791 for (int phaseIndex = latestHandledPhaseIndex + 1 ; phaseIndex < PRE_DEV_MODE_PHASES .size (); ++phaseIndex ) {
769792 var executions = phaseExecutions .get (PRE_DEV_MODE_PHASES .get (phaseIndex ));
770793 if (executions == null ) {
@@ -774,6 +797,9 @@ private String handleAutoCompile() throws MojoExecutionException {
774797 var executedGoals = executedPluginGoals .getOrDefault (pe .plugin .getId (), List .of ());
775798 for (String goal : pe .execution .getGoals ()) {
776799 if (!executedGoals .contains (goal )) {
800+ if (quarkusGoalParams == null ) {
801+ quarkusGoalParams = getQuarkusGoalParams (bootstrapId , reloadPoms );
802+ }
777803 try {
778804 executeGoal (pe , goal ,
779805 pe .getPluginId ().equals (quarkusPluginId ) ? quarkusGoalParams : Map .of ());
@@ -793,6 +819,35 @@ private String handleAutoCompile() throws MojoExecutionException {
793819 return bootstrapId ;
794820 }
795821
822+ /**
823+ * Returns a map of parameters for the Quarkus plugin goals to be invoked.
824+ *
825+ * @param bootstrapId bootstrap id
826+ * @param reloadPoms POM files to be reloaded from disk instead of taken from the reactor
827+ * @return map of parameters for the Quarkus plugin goals
828+ */
829+ private static Map <String , String > getQuarkusGoalParams (String bootstrapId , List <String > reloadPoms ) {
830+ final Map <String , String > result = new HashMap <>(4 );
831+ result .put (QuarkusBootstrapMojo .MODE_PARAM , LaunchMode .DEVELOPMENT .name ());
832+ result .put (QuarkusBootstrapMojo .CLOSE_BOOTSTRAPPED_APP_PARAM , "false" );
833+ result .put (QuarkusBootstrapMojo .BOOTSTRAP_ID_PARAM , bootstrapId );
834+ if (reloadPoms != null && !reloadPoms .isEmpty ()) {
835+ String reloadPomsStr ;
836+ if (reloadPoms .size () == 1 ) {
837+ reloadPomsStr = reloadPoms .get (0 );
838+ } else {
839+ final StringBuilder sb = new StringBuilder ();
840+ sb .append (reloadPoms .get (0 ));
841+ for (int i = 1 ; i < reloadPoms .size (); ++i ) {
842+ sb .append ("," ).append (reloadPoms .get (i ));
843+ }
844+ reloadPomsStr = sb .toString ();
845+ }
846+ result .put (QuarkusBootstrapMojo .RELOAD_POMS_PARAM , reloadPomsStr );
847+ }
848+ return result ;
849+ }
850+
796851 private String getCurrentGoal () {
797852 return mojoExecution .getMojoDescriptor ().getPluginDescriptor ().getGoalPrefix () + ":"
798853 + mojoExecution .getGoal ();
@@ -1004,7 +1059,7 @@ && matchesExecution(executionId, exec.getId())) {
10041059 }
10051060 }
10061061
1007- if (( Xpp3Dom ) plugin .getConfiguration () != null ) {
1062+ if (plugin .getConfiguration () != null ) {
10081063 mergedConfig = mergedConfig == null ? (Xpp3Dom ) plugin .getConfiguration ()
10091064 : Xpp3Dom .mergeXpp3Dom (mergedConfig , (Xpp3Dom ) plugin .getConfiguration (), true );
10101065 }
@@ -1300,17 +1355,15 @@ void run() throws Exception {
13001355 process = processBuilder .start ();
13011356
13021357 //https://github.com/quarkusio/quarkus/issues/232
1303- Runtime .getRuntime ().addShutdownHook (new Thread (new Runnable () {
1304- @ Override
1305- public void run () {
1306- process .destroy ();
1307- try {
1308- process .waitFor ();
1309- } catch (InterruptedException e ) {
1310- getLog ().warn ("Unable to properly wait for dev-mode end" , e );
1311- }
1312- }
1313- }, "Development Mode Shutdown Hook" ));
1358+ Runtime .getRuntime ().addShutdownHook (new Thread (this ::safeStop , "Development Mode Shutdown Hook" ));
1359+ }
1360+
1361+ private void safeStop () {
1362+ try {
1363+ stop ();
1364+ } catch (InterruptedException e ) {
1365+ getLog ().warn ("Unable to properly wait for dev-mode end" , e );
1366+ }
13141367 }
13151368
13161369 void stop () throws InterruptedException {
@@ -1467,7 +1520,7 @@ private DevModeCommandLine newLauncher(String actualDebugPort, String bootstrapI
14671520 .setRootProjectDir (rootProjectDir );
14681521
14691522 // There are a couple of reasons we don't want to use the original Maven session:
1470- // 1) a reload could be triggered by a change in a pom.xml, in which case the Maven session might not be in sync any more with the effective POM;
1523+ // 1) a reload could be triggered by a change in a pom.xml, in which case the Maven session might not be in sync anymore with the effective POM;
14711524 // 2) in case there is a local module that has a snapshot version, which is also available in a remote snapshot repository,
14721525 // the Maven resolver will be checking for newer snapshots in the remote repository and might end up resolving the artifact from there.
14731526 final BootstrapMavenContext mvnCtx = workspaceProvider .createMavenContext (mvnConfig );
0 commit comments