From d1d256b3eabc313b88d418bdaa3b891408a144bc Mon Sep 17 00:00:00 2001 From: Paul Gooderham Date: Wed, 26 Aug 2026 18:23:30 -0400 Subject: [PATCH 1/5] Call generate features after java compilation is complete. When dependencies are modified use optimizeGF Signed-off-by: Paul Gooderham --- .../tools/common/plugins/util/DevUtil.java | 91 +++++++++++++++---- 1 file changed, 74 insertions(+), 17 deletions(-) diff --git a/src/main/java/io/openliberty/tools/common/plugins/util/DevUtil.java b/src/main/java/io/openliberty/tools/common/plugins/util/DevUtil.java index a7d099040..40c788d17 100644 --- a/src/main/java/io/openliberty/tools/common/plugins/util/DevUtil.java +++ b/src/main/java/io/openliberty/tools/common/plugins/util/DevUtil.java @@ -447,6 +447,7 @@ private enum FileTrackMode { protected File generateFeaturesOutputDir; // output directory for the generate-features goal/task (i.e. where the file is generated) protected File generateFeaturesTmpDir; // the location where the generated features file is written during dev mode loop when generateToSrc is false private File modifiedSrcBuildFile; + protected boolean modifiedDependencies; // important when the build file is modified and we need to regenerate the features protected boolean skipInstallFeature; // for gradle, this map will be kept as null @@ -577,6 +578,7 @@ public DevUtil(File buildDirectory, File serverDirectory, File sourceDirectory, this.generatedFeaturesSet = new HashSet(); } this.modifiedSrcBuildFile = null; + this.modifiedDependencies = false; } private void initGenerationContext() { @@ -3261,22 +3263,35 @@ public void watchFiles(File outputDirectory, File testOutputDirectory, final Thr } } - // Generate features from source or class file changes + // Generate features from class file changes // do not run generate features if there are classes failing to compile if (generateFeatures && !classesFailingToCompile() && !modifiedClasses.isEmpty()) { // recompileDependencies = no class file tracking, so we wait for compilation to be complete - // !recompileDepenencies = class file tracking, so waiting on class file changes - if ((recompileDependencies && lastChangeCompiled) || !recompileDependencies) { - if (!failedToGenerateClasses.isEmpty()) { - modifiedClasses.addAll(failedToGenerateClasses); - } + // !recompileDepenencies = class file tracking, class file changes aggregated in modifiedClasses + boolean generateFeaturesRequired = (recompileDependencies && lastChangeCompiled) || !recompileDependencies; + boolean optimizeRequired = generateFeaturesRequired && modifiedDependencies; + + if (generateFeaturesRequired) { debug("Detected a change in the following classes/directories: " + modifiedClasses); // reset lastChangeCompiled and modifiedSrcBuildFile lastChangeCompiled = false; // only needed when recompileDependencies is true modifiedSrcBuildFile = null; // only needed when recompileDependencies is true + modifiedDependencies = false; long generatedTime = generateFeaturesFile.lastModified(); int numApplicationUpdatedMessages = countApplicationUpdatedMessages(); - incrementGenerateFeatures(!generateToSrc); + // If the generated-features.xml file is modified by this call then an event will be + // fired and the file watcher will install new features and update the dev mode cache + if (optimizeRequired) { + // Even though we do not use the modifiedClasses in this case it is required + // to add classes to it and to reset lastChangeCompiled so that compilation takes + // place and lastChangeCompiled is set true before we get here. + optimizeGenerateFeatures(!generateToSrc); + } else { + if (!failedToGenerateClasses.isEmpty()) { + modifiedClasses.addAll(failedToGenerateClasses); + } + incrementGenerateFeatures(!generateToSrc); + } if (!generateFeaturesFile.exists()) { // run tests if generated-features.xml does not exist as there are no new features to install // (typically tests run after generate features & install when hotTests=true) @@ -4380,12 +4395,12 @@ private void processFileChanges( if (isMultiModuleProject()) { ProjectModule project = getFirstProjectModule(fileChanged); if (project != null) { - triggerUpstreamModuleCompile(project, false); + triggerUpstreamModuleCompile(project, false, generateFeatures); } else { - triggerMainModuleCompile(false); + triggerMainModuleCompile(false, generateFeatures); } } else { - triggerMainModuleCompile(false); + triggerMainModuleCompile(false, generateFeatures); } } else { // trigger recompile of failing source and test @@ -4486,9 +4501,9 @@ private void processFileChanges( && !project.getDependentModules().isEmpty()) { // parent project, trigger compile on first dependent module triggerUpstreamModuleCompile(getProjectModule(project.getDependentModules().get(0)), - false); + false, generateFeatures); } else { - triggerUpstreamModuleCompile(project, false); + triggerUpstreamModuleCompile(project, false, generateFeatures); } } else { // trigger java source recompile of all projects if there are compilation errors @@ -4644,7 +4659,7 @@ private void processFileChanges( // run all tests on build file change if (recompiledBuild) { if (recompileDependencies) { - triggerMainModuleCompile(false); + triggerMainModuleCompile(false, generateFeatures); } else { // trigger java source recompile if there are compilation errors if (!failedCompilationJavaSources.isEmpty()) { @@ -4733,7 +4748,7 @@ private void processConfigFileChange(File fileChanged, ChangeType changeType, Th isGeneratedFeaturesFile ? generateFeaturesOutputDir : configDirectory; if (fileChanged.exists() && (changeType == ChangeType.MODIFY || changeType == ChangeType.CREATE)) { - debug("Config file modified: " + fileChanged); + debug("Config file exists and is modified: " + fileChanged); boolean generateFeaturesSuccess = true; // default to true in case feature generation is disabled boolean serverFeaturesModified = serverFeaturesModified(); @@ -5892,8 +5907,26 @@ private File[] getAllBuildFiles(ProjectModule currentProject) { * @throws IOException */ protected void triggerMainModuleCompile(boolean testsOnly) throws IOException { - triggerProjectCompile(this.sourceDirectory, recompileJavaSources, this.testSourceDirectory, recompileJavaTests, - testsOnly, packagingType); + triggerMainModuleCompile(testsOnly, false); + } + + /** + * Trigger a compile of the main module and track the output directory for + * generate features. Used when we change the build file. + * + * @param testsOnly true if ONLY tests should be compiled + * @param trackForGenerateFeatures true to monitor the modified classes and set the + * flag to generate features after compilation + * @throws IOException + */ + protected void triggerMainModuleCompile(boolean testsOnly, boolean trackForGenerateFeatures) throws IOException { + triggerProjectCompile(this.sourceDirectory, recompileJavaSources, + this.testSourceDirectory, recompileJavaTests, testsOnly, packagingType); + if (trackForGenerateFeatures && outputDirectory != null && !recompileJavaSources.isEmpty()) { + modifiedClasses.add(outputDirectory); + lastChangeCompiled = false; // compilation of Java files has been triggered + modifiedSrcBuildFile = buildFile; + } } /** @@ -5906,8 +5939,32 @@ protected void triggerMainModuleCompile(boolean testsOnly) throws IOException { * @throws IOException */ protected void triggerUpstreamModuleCompile(ProjectModule project, boolean testsOnly) throws IOException { + new Exception().printStackTrace(); + triggerUpstreamModuleCompile(project, testsOnly, false); + } + + /** + * Trigger a compile of the specified upstream module and track its output + * directory for generate features. Used when we change the build file. + * + * @param project the module to be compiled + * @param testsOnly true if ONLY tests should be compiled + * @param trackForGenerateFeatures true to monitor the modified classes and and set the + * flag to generate features after compilation + * @throws IOException + */ + protected void triggerUpstreamModuleCompile(ProjectModule project, boolean testsOnly, + boolean trackForGenerateFeatures) throws IOException { triggerProjectCompile(project.getSourceDirectory(), project.recompileJavaSources, - project.getTestSourceDirectory(), project.recompileJavaTests, testsOnly, project.getPackagingType()); + project.getTestSourceDirectory(), project.recompileJavaTests, + testsOnly, project.getPackagingType()); + if (trackForGenerateFeatures && + project.getOutputDirectory() != null && + !project.recompileJavaSources.isEmpty()) { + modifiedClasses.add(project.getOutputDirectory()); + lastChangeCompiled = false; // compilation of Java files has been triggered + modifiedSrcBuildFile = project.getBuildFile(); + } } private void triggerProjectCompile(File sourceDir, Collection recompileJavaSourceSet, File testSourceDir, From ee4fd16e35f36e6031ff89bca9d9ab452e885618 Mon Sep 17 00:00:00 2001 From: Paul Gooderham Date: Fri, 11 Sep 2026 18:35:26 -0400 Subject: [PATCH 2/5] When recompilation succeeds set up for generate features Signed-off-by: Paul Gooderham --- .../io/openliberty/tools/common/plugins/util/DevUtil.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/main/java/io/openliberty/tools/common/plugins/util/DevUtil.java b/src/main/java/io/openliberty/tools/common/plugins/util/DevUtil.java index 40c788d17..96722ee4e 100644 --- a/src/main/java/io/openliberty/tools/common/plugins/util/DevUtil.java +++ b/src/main/java/io/openliberty/tools/common/plugins/util/DevUtil.java @@ -4151,6 +4151,13 @@ private void processJavaCompilation(File outputDirectory, File testOutputDirecto // The module with the latest src file change has compiled successfully debug("Setting lastChangeCompiled to true"); lastChangeCompiled = true; + // If the recompile was triggered by a pom.xml fix then the output directory may never + // have been registered with the file watcher (it was empty when the initial compile + // on start up failed), so class file events will not fire to populate + // modifiedClasses. Add the output directory explicitly so generate features runs. + if (triggerJavaSourceRecompile && generateFeatures && outputDirectory != null) { + modifiedClasses.add(outputDirectory); + } } } else { failedCompilationJavaSources.addAll(recompileJavaSources); @@ -4664,6 +4671,7 @@ private void processFileChanges( // trigger java source recompile if there are compilation errors if (!failedCompilationJavaSources.isEmpty()) { triggerJavaSourceRecompile = true; + modifiedSrcBuildFile = buildFile; } // trigger java test recompile if there are compilation errors if (!failedCompilationJavaTests.isEmpty()) { From b98124a437433433ecbb053bcea282d68735eda1 Mon Sep 17 00:00:00 2001 From: Paul Gooderham Date: Mon, 14 Sep 2026 18:31:29 -0400 Subject: [PATCH 3/5] When recompiling upstream projects for recompileDependencies false, set up for generate features Signed-off-by: Paul Gooderham --- .../io/openliberty/tools/common/plugins/util/DevUtil.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/java/io/openliberty/tools/common/plugins/util/DevUtil.java b/src/main/java/io/openliberty/tools/common/plugins/util/DevUtil.java index 96722ee4e..900c580ca 100644 --- a/src/main/java/io/openliberty/tools/common/plugins/util/DevUtil.java +++ b/src/main/java/io/openliberty/tools/common/plugins/util/DevUtil.java @@ -4082,6 +4082,11 @@ testOutputDirectory, getProjectName(), buildFile, compilerOptions, skipUTs, true currentProject.skipUTs(), true)) { // successful compilation so we can clear failedCompilation list currentProject.failedCompilationJavaSources.clear(); + if (generateFeatures && modifiedSrcBuildFile != null + && modifiedSrcBuildFile.equals(currentProject.getBuildFile()) + && currentProject.getOutputDirectory() != null) { + modifiedClasses.add(currentProject.getOutputDirectory()); + } } else { successfulSrcCompile = false; } @@ -4517,6 +4522,7 @@ private void processFileChanges( // in this project if (!project.failedCompilationJavaSources.isEmpty()) { triggerUpstreamJavaSourceRecompile = true; + modifiedSrcBuildFile = project.getBuildFile(); } // trigger java test recompile of just this project if there are existing test // compilation errors From e8383de8dad0fa9c083a2309165936f884d95c78 Mon Sep 17 00:00:00 2001 From: Paul Gooderham Date: Wed, 16 Sep 2026 17:48:52 -0400 Subject: [PATCH 4/5] Remove debug output statement Signed-off-by: Paul Gooderham --- .../java/io/openliberty/tools/common/plugins/util/DevUtil.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/io/openliberty/tools/common/plugins/util/DevUtil.java b/src/main/java/io/openliberty/tools/common/plugins/util/DevUtil.java index 900c580ca..264788298 100644 --- a/src/main/java/io/openliberty/tools/common/plugins/util/DevUtil.java +++ b/src/main/java/io/openliberty/tools/common/plugins/util/DevUtil.java @@ -5953,7 +5953,6 @@ protected void triggerMainModuleCompile(boolean testsOnly, boolean trackForGener * @throws IOException */ protected void triggerUpstreamModuleCompile(ProjectModule project, boolean testsOnly) throws IOException { - new Exception().printStackTrace(); triggerUpstreamModuleCompile(project, testsOnly, false); } From d8109bb0808383d6a77ed8df9d494f82f11fe3c5 Mon Sep 17 00:00:00 2001 From: Paul Gooderham Date: Tue, 22 Sep 2026 16:43:26 -0400 Subject: [PATCH 5/5] Add some to dos Signed-off-by: Paul Gooderham --- .../io/openliberty/tools/common/plugins/util/DevUtil.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/io/openliberty/tools/common/plugins/util/DevUtil.java b/src/main/java/io/openliberty/tools/common/plugins/util/DevUtil.java index 264788298..c8c3240e0 100644 --- a/src/main/java/io/openliberty/tools/common/plugins/util/DevUtil.java +++ b/src/main/java/io/openliberty/tools/common/plugins/util/DevUtil.java @@ -4082,6 +4082,8 @@ testOutputDirectory, getProjectName(), buildFile, compilerOptions, skipUTs, true currentProject.skipUTs(), true)) { // successful compilation so we can clear failedCompilation list currentProject.failedCompilationJavaSources.clear(); + // TODO: see if this is only needed when recompileDependencies==false, add that boolean here? + // But if recompileDependencies==false are the class files being tracked in the multimodule case? if (generateFeatures && modifiedSrcBuildFile != null && modifiedSrcBuildFile.equals(currentProject.getBuildFile()) && currentProject.getOutputDirectory() != null) { @@ -5937,6 +5939,7 @@ protected void triggerMainModuleCompile(boolean testsOnly, boolean trackForGener triggerProjectCompile(this.sourceDirectory, recompileJavaSources, this.testSourceDirectory, recompileJavaTests, testsOnly, packagingType); if (trackForGenerateFeatures && outputDirectory != null && !recompileJavaSources.isEmpty()) { + // TODO: this should be done later when the compilation is done. Remove from here? modifiedClasses.add(outputDirectory); lastChangeCompiled = false; // compilation of Java files has been triggered modifiedSrcBuildFile = buildFile; @@ -5974,6 +5977,7 @@ protected void triggerUpstreamModuleCompile(ProjectModule project, boolean tests if (trackForGenerateFeatures && project.getOutputDirectory() != null && !project.recompileJavaSources.isEmpty()) { + // TODO: this should be done later when the compilation is done. Remove from here? modifiedClasses.add(project.getOutputDirectory()); lastChangeCompiled = false; // compilation of Java files has been triggered modifiedSrcBuildFile = project.getBuildFile();