diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/HotDeploymentWatchedFileBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/HotDeploymentWatchedFileBuildItem.java
index a7efbbbc4f677..fccb78cd8afb6 100644
--- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/HotDeploymentWatchedFileBuildItem.java
+++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/HotDeploymentWatchedFileBuildItem.java
@@ -1,22 +1,51 @@
package io.quarkus.deployment.builditem;
+import java.util.function.Predicate;
+
import io.quarkus.builder.item.MultiBuildItem;
/**
- * A file that if modified may result in a hot redeployment when in the dev mode.
+ * Identifies a file that if modified may result in a hot redeployment when in the dev mode.
+ *
+ * A file may be identified with an exact location or a matching predicate. See {@link Builder#setLocation(String)} and
+ * {@link Builder#setPredicate(Predicate)}.
*/
public final class HotDeploymentWatchedFileBuildItem extends MultiBuildItem {
+ public static Builder builder() {
+ return new Builder();
+ }
+
private final String location;
+ private final Predicate predicate;
private final boolean restartNeeded;
+ /**
+ *
+ * @param location
+ * @see #builder()
+ */
public HotDeploymentWatchedFileBuildItem(String location) {
this(location, true);
}
+ /**
+ *
+ * @param location
+ * @param restartNeeded
+ * @see #builder()
+ */
public HotDeploymentWatchedFileBuildItem(String location, boolean restartNeeded) {
+ this(location, null, restartNeeded);
+ }
+
+ private HotDeploymentWatchedFileBuildItem(String location, Predicate predicate, boolean restartNeeded) {
+ if (location == null && predicate == null) {
+ throw new IllegalArgumentException("Either location or predicate must be set");
+ }
this.location = location;
+ this.predicate = predicate;
this.restartNeeded = restartNeeded;
}
@@ -24,6 +53,18 @@ public String getLocation() {
return location;
}
+ public boolean hasLocation() {
+ return location != null;
+ }
+
+ public Predicate getPredicate() {
+ return predicate;
+ }
+
+ public boolean hasPredicate() {
+ return predicate != null;
+ }
+
/**
*
* @return {@code true} if a file change should result in an application restart, {@code false} otherwise
@@ -32,4 +73,37 @@ public boolean isRestartNeeded() {
return restartNeeded;
}
+ public static class Builder {
+
+ private String location;
+ private Predicate predicate;
+ private boolean restartNeeded = true;
+
+ public Builder setLocation(String location) {
+ if (predicate != null) {
+ throw new IllegalArgumentException("Predicate already set");
+ }
+ this.location = location;
+ return this;
+ }
+
+ public Builder setPredicate(Predicate predicate) {
+ if (location != null) {
+ throw new IllegalArgumentException("Location already set");
+ }
+ this.predicate = predicate;
+ return this;
+ }
+
+ public Builder setRestartNeeded(boolean restartNeeded) {
+ this.restartNeeded = restartNeeded;
+ return this;
+ }
+
+ public HotDeploymentWatchedFileBuildItem build() {
+ return new HotDeploymentWatchedFileBuildItem(location, predicate, restartNeeded);
+ }
+
+ }
+
}
diff --git a/core/deployment/src/main/java/io/quarkus/deployment/dev/HotDeploymentWatchedFileBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/dev/HotDeploymentWatchedFileBuildStep.java
index d4b832420f7cb..42019a63fc767 100644
--- a/core/deployment/src/main/java/io/quarkus/deployment/dev/HotDeploymentWatchedFileBuildStep.java
+++ b/core/deployment/src/main/java/io/quarkus/deployment/dev/HotDeploymentWatchedFileBuildStep.java
@@ -2,6 +2,8 @@
import java.util.List;
import java.util.Map;
+import java.util.Map.Entry;
+import java.util.function.Predicate;
import java.util.stream.Collectors;
import io.quarkus.deployment.annotations.BuildStep;
@@ -18,14 +20,21 @@ ServiceStartBuildItem setupWatchedFileHotDeployment(List watchedFilePaths = files.stream()
+
+ Map watchedFilePaths = files.stream().filter(HotDeploymentWatchedFileBuildItem::hasLocation)
.collect(Collectors.toMap(HotDeploymentWatchedFileBuildItem::getLocation,
HotDeploymentWatchedFileBuildItem::isRestartNeeded,
(isRestartNeeded1, isRestartNeeded2) -> isRestartNeeded1 || isRestartNeeded2));
+
+ List, Boolean>> watchedFilePredicates = files.stream()
+ .filter(HotDeploymentWatchedFileBuildItem::hasPredicate)
+ .map(f -> Map.entry(f.getPredicate(), f.isRestartNeeded()))
+ .collect(Collectors.toUnmodifiableList());
+
if (launchModeBuildItem.isAuxiliaryApplication()) {
- TestWatchedFiles.setWatchedFilePaths(watchedFilePaths);
+ TestWatchedFiles.setWatchedFilePaths(watchedFilePaths, watchedFilePredicates);
} else {
- processor.setWatchedFilePaths(watchedFilePaths, false);
+ processor.setWatchedFilePaths(watchedFilePaths, watchedFilePredicates, false);
}
}
return null;
diff --git a/core/deployment/src/main/java/io/quarkus/deployment/dev/RuntimeUpdatesProcessor.java b/core/deployment/src/main/java/io/quarkus/deployment/dev/RuntimeUpdatesProcessor.java
index 63dad57889b28..ca34f9df5d3fe 100644
--- a/core/deployment/src/main/java/io/quarkus/deployment/dev/RuntimeUpdatesProcessor.java
+++ b/core/deployment/src/main/java/io/quarkus/deployment/dev/RuntimeUpdatesProcessor.java
@@ -29,6 +29,7 @@
import java.util.List;
import java.util.Locale;
import java.util.Map;
+import java.util.Map.Entry;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
@@ -331,11 +332,10 @@ private void periodicTestCompile() {
}
Set filesChanges = new HashSet<>(checkForFileChange(s -> s.getTest().orElse(null), test));
filesChanges.addAll(checkForFileChange(DevModeContext.ModuleInfo::getMain, test));
- boolean configFileRestartNeeded = filesChanges.stream().map(test.watchedFilePaths::get)
- .anyMatch(Boolean.TRUE::equals);
+ boolean fileRestartNeeded = filesChanges.stream().anyMatch(test::isWatchedFileRestartNeeded);
ClassScanResult merged = ClassScanResult.merge(changedTestClassResult, changedApp);
- if (configFileRestartNeeded) {
+ if (fileRestartNeeded) {
if (testCompileProblem == null) {
testSupport.runTests(null);
}
@@ -462,21 +462,19 @@ public boolean doScan(boolean userInitiated, boolean forceRestart) {
main, false);
Set filesChanged = checkForFileChange(DevModeContext.ModuleInfo::getMain, main);
- boolean configFileRestartNeeded = forceRestart || filesChanged.stream().map(main.watchedFilePaths::get)
- .anyMatch(Boolean.TRUE::equals);
+ boolean fileRestartNeeded = forceRestart || filesChanged.stream().anyMatch(main::isWatchedFileRestartNeeded);
boolean instrumentationChange = false;
List changedFilesForRestart = new ArrayList<>();
- if (configFileRestartNeeded) {
- changedFilesForRestart
- .addAll(filesChanged.stream().filter(fn -> Boolean.TRUE.equals(main.watchedFilePaths.get(fn)))
- .map(Paths::get).collect(Collectors.toList()));
+ if (fileRestartNeeded) {
+ filesChanged.stream().filter(main::isWatchedFileRestartNeeded).map(Paths::get)
+ .forEach(changedFilesForRestart::add);
}
changedFilesForRestart.addAll(changedClassResults.getChangedClasses());
changedFilesForRestart.addAll(changedClassResults.getAddedClasses());
changedFilesForRestart.addAll(changedClassResults.getDeletedClasses());
- if (ClassChangeAgent.getInstrumentation() != null && lastStartIndex != null && !configFileRestartNeeded
+ if (ClassChangeAgent.getInstrumentation() != null && lastStartIndex != null && !fileRestartNeeded
&& devModeType != DevModeType.REMOTE_LOCAL_SIDE && instrumentationEnabled()) {
//attempt to do an instrumentation based reload
//if only code has changed and not the class structure, then we can do a reload
@@ -530,7 +528,7 @@ public boolean doScan(boolean userInitiated, boolean forceRestart) {
//all broken we just assume the reason that they have refreshed is because they have fixed something
//trying to watch all resource files is complex and this is likely a good enough solution for what is already an edge case
boolean restartNeeded = !instrumentationChange && (changedClassResults.isChanged()
- || (IsolatedDevModeMain.deploymentProblem != null && userInitiated) || configFileRestartNeeded);
+ || (IsolatedDevModeMain.deploymentProblem != null && userInitiated) || fileRestartNeeded);
if (restartNeeded) {
String changeString = changedFilesForRestart.stream().map(Path::getFileName).map(Object::toString)
.collect(Collectors.joining(", "));
@@ -1083,12 +1081,14 @@ public RuntimeUpdatesProcessor setDisableInstrumentationForIndexPredicate(
return this;
}
- public RuntimeUpdatesProcessor setWatchedFilePaths(Map watchedFilePaths, boolean isTest) {
+ public RuntimeUpdatesProcessor setWatchedFilePaths(Map watchedFilePaths,
+ List, Boolean>> watchedFilePredicates, boolean isTest) {
if (isTest) {
setWatchedFilePathsInternal(watchedFilePaths, test,
s -> s.getTest().isPresent() ? asList(s.getTest().get(), s.getMain()) : singletonList(s.getMain()));
} else {
main.watchedFileTimestamps.clear();
+ main.watchedFilePredicates = watchedFilePredicates;
setWatchedFilePathsInternal(watchedFilePaths, main, s -> singletonList(s.getMain()));
}
return this;
@@ -1225,6 +1225,7 @@ static class TimestampSet {
final Map classFilePathToSourceFilePath = new ConcurrentHashMap<>();
// file path -> isRestartNeeded
private volatile Map watchedFilePaths = Collections.emptyMap();
+ volatile List, Boolean>> watchedFilePredicates = Collections.emptyList();
public void merge(TimestampSet other) {
watchedFileTimestamps.putAll(other.watchedFileTimestamps);
@@ -1233,7 +1234,21 @@ public void merge(TimestampSet other) {
Map newVal = new HashMap<>(watchedFilePaths);
newVal.putAll(other.watchedFilePaths);
watchedFilePaths = newVal;
+ // The list of predicates should be effectively immutable
+ watchedFilePredicates = other.watchedFilePredicates;
+ }
+ boolean isWatchedFileRestartNeeded(String changedFile) {
+ Boolean ret = watchedFilePaths.get(changedFile);
+ if (ret == null) {
+ for (Entry, Boolean> e : watchedFilePredicates) {
+ if (e.getKey().test(changedFile)) {
+ ret = e.getValue();
+ break;
+ }
+ }
+ }
+ return ret == null ? false : ret;
}
}
diff --git a/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/TestSupport.java b/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/TestSupport.java
index b72f5753c298e..a03138e2f7e3a 100644
--- a/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/TestSupport.java
+++ b/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/TestSupport.java
@@ -154,7 +154,8 @@ private static Pattern getCompiledPatternOrNull(Optional patternStr) {
public void init() {
if (moduleRunners.isEmpty()) {
- TestWatchedFiles.setWatchedFilesListener((s) -> RuntimeUpdatesProcessor.INSTANCE.setWatchedFilePaths(s, true));
+ TestWatchedFiles.setWatchedFilesListener(
+ (paths, predicates) -> RuntimeUpdatesProcessor.INSTANCE.setWatchedFilePaths(paths, predicates, true));
final Pattern includeModulePattern = getCompiledPatternOrNull(config.includeModulePattern);
final Pattern excludeModulePattern = getCompiledPatternOrNull(config.excludeModulePattern);
for (var module : context.getAllModules()) {
diff --git a/core/devmode-spi/src/main/java/io/quarkus/dev/testing/TestWatchedFiles.java b/core/devmode-spi/src/main/java/io/quarkus/dev/testing/TestWatchedFiles.java
index dd42190200d8c..f80c0cec06541 100644
--- a/core/devmode-spi/src/main/java/io/quarkus/dev/testing/TestWatchedFiles.java
+++ b/core/devmode-spi/src/main/java/io/quarkus/dev/testing/TestWatchedFiles.java
@@ -1,7 +1,10 @@
package io.quarkus.dev.testing;
+import java.util.List;
import java.util.Map;
-import java.util.function.Consumer;
+import java.util.Map.Entry;
+import java.util.function.BiConsumer;
+import java.util.function.Predicate;
/**
* provides a way for a test run to tell the external application about watched paths.
@@ -11,19 +14,23 @@
public class TestWatchedFiles {
private static volatile Map watchedFilePaths;
- private static volatile Consumer