Skip to content

Commit cdba069

Browse files
authored
Merge pull request #47468 from aloubyansky/dev-mode-reload-poms
Reload POMs that have changed during dev mode
2 parents fd2f855 + 236c646 commit cdba069

File tree

19 files changed

+474
-289
lines changed

19 files changed

+474
-289
lines changed

Diff for: devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java

+76-23
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package io.quarkus.maven;
22

33
import 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;
46
import static io.smallrye.common.expression.Expression.Flag.LENIENT_SYNTAX;
57
import static io.smallrye.common.expression.Expression.Flag.NO_TRIM;
68
import 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);

Diff for: devtools/maven/src/main/java/io/quarkus/maven/QuarkusBootstrapMojo.java

+13-2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import java.util.Map;
99
import java.util.Optional;
1010
import java.util.Properties;
11+
import java.util.Set;
1112
import java.util.function.Consumer;
1213
import java.util.stream.Stream;
1314

@@ -36,7 +37,10 @@
3637

3738
public abstract class QuarkusBootstrapMojo extends AbstractMojo {
3839

39-
static final String CLOSE_BOOTSTRAPPED_APP = "closeBootstrappedApp";
40+
static final String BOOTSTRAP_ID_PARAM = "bootstrapId";
41+
static final String CLOSE_BOOTSTRAPPED_APP_PARAM = "closeBootstrappedApp";
42+
static final String MODE_PARAM = "mode";
43+
static final String RELOAD_POMS_PARAM = "reloadPoms";
4044

4145
static final String NATIVE_PROFILE_NAME = "native";
4246

@@ -158,7 +162,14 @@ public abstract class QuarkusBootstrapMojo extends AbstractMojo {
158162
* Whether to close the bootstrapped applications after the execution
159163
*/
160164
@Parameter(property = "quarkusCloseBootstrappedApp")
161-
private Boolean closeBootstrappedApp;
165+
Boolean closeBootstrappedApp;
166+
167+
/**
168+
* POM files from the workspace that should be reloaded from the disk instead of taken from the Maven reactor.
169+
* This parameter is not supposed to be configured by a user.
170+
*/
171+
@Parameter(property = "reloadPoms")
172+
Set<File> reloadPoms = Set.of();
162173

163174
private ArtifactKey projectId;
164175

Diff for: devtools/maven/src/main/java/io/quarkus/maven/QuarkusBootstrapProvider.java

+29-34
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@
66
import java.io.Closeable;
77
import java.io.File;
88
import java.io.IOException;
9-
import java.nio.file.Path;
10-
import java.util.HashMap;
119
import java.util.HashSet;
1210
import java.util.List;
1311
import java.util.Map;
@@ -21,7 +19,6 @@
2119
import javax.inject.Singleton;
2220

2321
import org.apache.maven.artifact.Artifact;
24-
import org.apache.maven.execution.MavenSession;
2522
import org.apache.maven.model.Model;
2623
import org.apache.maven.plugin.MojoExecutionException;
2724
import org.apache.maven.project.MavenProject;
@@ -80,25 +77,25 @@ static ArtifactKey getProjectId(MavenProject project) {
8077
return ArtifactKey.ga(project.getGroupId(), project.getArtifactId());
8178
}
8279

83-
static Map<Path, Model> getProjectMap(MavenSession session) {
84-
final List<MavenProject> allProjects = session.getAllProjects();
85-
if (allProjects == null) {
86-
return Map.of();
87-
}
88-
final Map<Path, Model> projectModels = new HashMap<>(allProjects.size());
89-
for (MavenProject mp : allProjects) {
90-
final Model model = getRawModel(mp);
91-
projectModels.put(mp.getFile().toPath(), model);
92-
// The Maven Model API determines the project directory as the directory containing the POM file.
93-
// However, in case when plugins manipulating POMs store their results elsewhere
94-
// (such as the flatten plugin storing the flattened POM under the target directory),
95-
// both the base directory and the directory containing the POM file should be added to the map.
96-
var pomDir = mp.getFile().getParentFile();
97-
if (!pomDir.equals(mp.getBasedir())) {
98-
projectModels.put(mp.getBasedir().toPath().resolve("pom.xml"), model);
80+
static void setProjectModels(QuarkusBootstrapMojo mojo, BootstrapMavenContextConfig<?> config) {
81+
final List<MavenProject> allProjects = mojo.mavenSession().getAllProjects();
82+
if (allProjects != null) {
83+
for (MavenProject mp : allProjects) {
84+
if (mojo.reloadPoms.contains(mp.getFile())) {
85+
continue;
86+
}
87+
final Model model = getRawModel(mp);
88+
config.addProvidedModule(mp.getFile().toPath(), model, mp.getModel());
89+
// The Maven Model API determines the project directory as the directory containing the POM file.
90+
// However, in case when plugins manipulating POMs store their results elsewhere
91+
// (such as the flatten plugin storing the flattened POM under the target directory),
92+
// both the base directory and the directory containing the POM file should be added to the map.
93+
var pomDir = mp.getFile().getParentFile();
94+
if (!pomDir.equals(mp.getBasedir())) {
95+
config.addProvidedModule(mp.getBasedir().toPath().resolve("pom.xml"), model, mp.getModel());
96+
}
9997
}
10098
}
101-
return projectModels;
10299
}
103100

104101
/**
@@ -123,8 +120,6 @@ private static Model getRawModel(MavenProject mp) {
123120
model.setDependencies(mp.getDependencies());
124121
}
125122
model.setPomFile(mp.getFile());
126-
// activated profiles or custom extensions may have overridden the build defaults
127-
model.setBuild(mp.getModel().getBuild());
128123
return model;
129124
}
130125

@@ -227,18 +222,18 @@ public class QuarkusMavenAppBootstrap implements Closeable {
227222
private MavenArtifactResolver artifactResolver(QuarkusBootstrapMojo mojo, LaunchMode mode) {
228223
try {
229224
if (mode == LaunchMode.DEVELOPMENT || mode == LaunchMode.TEST || isWorkspaceDiscovery(mojo)) {
230-
var resolver = workspaceProvider.createArtifactResolver(
231-
BootstrapMavenContext.config()
232-
// it's important to pass user settings in case the process was not launched using the original mvn script
233-
// for example using org.codehaus.plexus.classworlds.launcher.Launcher
234-
.setUserSettings(mojo.mavenSession().getRequest().getUserSettingsFile())
235-
.setCurrentProject(mojo.mavenProject().getFile().toString())
236-
.setPreferPomsFromWorkspace(true)
237-
.setProjectModelProvider(getProjectMap(mojo.mavenSession()))
238-
// pass the repositories since Maven extensions could manipulate repository configs
239-
.setRemoteRepositories(mojo.remoteRepositories())
240-
.setEffectiveModelBuilder(BootstrapMavenContextConfig
241-
.getEffectiveModelBuilderProperty(mojo.mavenProject().getProperties())));
225+
final BootstrapMavenContextConfig<?> config = BootstrapMavenContext.config()
226+
// it's important to pass user settings in case the process was not launched using the original mvn script,
227+
// for example, using org.codehaus.plexus.classworlds.launcher.Launcher
228+
.setUserSettings(mojo.mavenSession().getRequest().getUserSettingsFile())
229+
.setCurrentProject(mojo.mavenProject().getFile().toString())
230+
.setPreferPomsFromWorkspace(true)
231+
// pass the repositories since Maven extensions could manipulate repository configs
232+
.setRemoteRepositories(mojo.remoteRepositories())
233+
.setEffectiveModelBuilder(BootstrapMavenContextConfig
234+
.getEffectiveModelBuilderProperty(mojo.mavenProject().getProperties()));
235+
setProjectModels(mojo, config);
236+
var resolver = workspaceProvider.createArtifactResolver(config);
242237
final LocalProject currentProject = resolver.getMavenContext().getCurrentProject();
243238
if (currentProject != null && workspaceId == 0) {
244239
workspaceId = currentProject.getWorkspace().getId();

Diff for: independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/BootstrapAppModelFactory.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -171,9 +171,9 @@ private BootstrapAppModelResolver initAppModelResolver(MavenArtifactResolver art
171171
.setDevMode(devMode);
172172
var project = artifactResolver.getMavenContext().getCurrentProject();
173173
if (project != null) {
174-
final Properties modelProps = project.getModelBuildingResult() == null
174+
final Properties modelProps = project.getEffectiveModel() == null
175175
? project.getRawModel().getProperties()
176-
: project.getModelBuildingResult().getEffectiveModel().getProperties();
176+
: project.getEffectiveModel().getProperties();
177177
appModelResolver.setLegacyModelResolver(BootstrapAppModelResolver.isLegacyModelResolver(modelProps));
178178
}
179179
return appModelResolver;

Diff for: independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/BootstrapMavenContext.java

+4-3
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@
7777
import io.quarkus.bootstrap.resolver.maven.workspace.LocalProject;
7878
import io.quarkus.bootstrap.resolver.maven.workspace.LocalWorkspace;
7979
import io.quarkus.bootstrap.resolver.maven.workspace.ModelUtils;
80+
import io.quarkus.bootstrap.resolver.maven.workspace.WorkspaceModulePom;
8081
import io.quarkus.bootstrap.util.PropertyUtils;
8182
import io.quarkus.maven.dependency.ArtifactCoords;
8283
import io.smallrye.beanbag.maven.MavenFactory;
@@ -194,7 +195,7 @@ public BootstrapMavenContext(BootstrapMavenContextConfig<?> config)
194195
this.currentPom = currentProject.getRawModel().getPomFile().toPath();
195196
this.workspace = config.currentProject.getWorkspace();
196197
} else if (config.workspaceDiscovery) {
197-
currentProject = resolveCurrentProject(config.modelProvider);
198+
currentProject = resolveCurrentProject(config.providedModules);
198199
this.workspace = currentProject == null ? null : currentProject.getWorkspace();
199200
if (workspace != null) {
200201
if (config.repoSession == null && repoSession != null && repoSession.getWorkspaceReader() == null) {
@@ -377,9 +378,9 @@ private boolean getLocalRepoTailIgnoreAvailability() {
377378
: localRepoTailIgnoreAvailability;
378379
}
379380

380-
private LocalProject resolveCurrentProject(Map<Path, Model> modelProvider) throws BootstrapMavenException {
381+
private LocalProject resolveCurrentProject(List<WorkspaceModulePom> providedModules) throws BootstrapMavenException {
381382
try {
382-
return LocalProject.loadWorkspace(this, modelProvider);
383+
return LocalProject.loadWorkspace(this, providedModules);
383384
} catch (Exception e) {
384385
throw new BootstrapMavenException("Failed to load current project at " + getCurrentProjectPomOrNull(), e);
385386
}

0 commit comments

Comments
 (0)