From 3ba3b8ec70a5796b59d49753f0443077deb2cfa1 Mon Sep 17 00:00:00 2001 From: Paul Gooderham Date: Tue, 8 Apr 2025 11:56:34 -0400 Subject: [PATCH 01/46] Specify version 24.0.0.2 for binary scanner Signed-off-by: Paul Gooderham --- .../tools/common/plugins/util/BinaryScannerUtil.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/io/openliberty/tools/common/plugins/util/BinaryScannerUtil.java b/src/main/java/io/openliberty/tools/common/plugins/util/BinaryScannerUtil.java index bbdbfda75..f7fabd0c4 100644 --- a/src/main/java/io/openliberty/tools/common/plugins/util/BinaryScannerUtil.java +++ b/src/main/java/io/openliberty/tools/common/plugins/util/BinaryScannerUtil.java @@ -30,7 +30,7 @@ public abstract class BinaryScannerUtil { public static final String BINARY_SCANNER_MAVEN_GROUP_ID = "com.ibm.websphere.appmod.tools"; public static final String BINARY_SCANNER_MAVEN_ARTIFACT_ID = "binary-app-scanner"; public static final String BINARY_SCANNER_MAVEN_TYPE = "jar"; - public static final String BINARY_SCANNER_MAVEN_VERSION = "[21.0.0.5-SNAPSHOT,)"; + public static final String BINARY_SCANNER_MAVEN_VERSION = "[24.0.0.2]"; public static final String GENERATED_FEATURES_FILE_NAME = "generated-features.xml"; public static final String GENERATED_FEATURES_FILE_PATH = "configDropins/overrides/" + GENERATED_FEATURES_FILE_NAME; From 8159bbd92ea7cb4d99cd9bc83ab4b0a6f6855ab4 Mon Sep 17 00:00:00 2001 From: Paul Gooderham Date: Wed, 11 Jun 2025 16:41:13 -0400 Subject: [PATCH 02/46] Add string for temp dir for generate features Signed-off-by: Paul Gooderham --- .../openliberty/tools/common/plugins/util/BinaryScannerUtil.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/io/openliberty/tools/common/plugins/util/BinaryScannerUtil.java b/src/main/java/io/openliberty/tools/common/plugins/util/BinaryScannerUtil.java index f7fabd0c4..1071b6d2c 100644 --- a/src/main/java/io/openliberty/tools/common/plugins/util/BinaryScannerUtil.java +++ b/src/main/java/io/openliberty/tools/common/plugins/util/BinaryScannerUtil.java @@ -34,6 +34,7 @@ public abstract class BinaryScannerUtil { public static final String GENERATED_FEATURES_FILE_NAME = "generated-features.xml"; public static final String GENERATED_FEATURES_FILE_PATH = "configDropins/overrides/" + GENERATED_FEATURES_FILE_NAME; + public static final String GENERATED_FEATURES_TEMP_PATH = ".libertyFeatures/" + GENERATED_FEATURES_FILE_NAME; private static final String FEATURE_MODIFIED_EXCEPTION = "com.ibm.websphere.binary.cmdline.exceptions.RequiredFeatureModifiedException"; private static final String FEATURE_CONFLICT_EXCEPTION = "com.ibm.websphere.binary.cmdline.exceptions.FeatureConflictException"; private static final String PROVIDED_FEATURE_EXCEPTION = "com.ibm.websphere.binary.cmdline.exceptions.ProvidedFeatureConflictException"; From 8a9d6c54a22ed04b5055871663f057a3193deb16 Mon Sep 17 00:00:00 2001 From: Paul Gooderham Date: Wed, 28 May 2025 18:28:14 -0400 Subject: [PATCH 03/46] Add generateToSrc option to dev mode Signed-off-by: Paul Gooderham --- .../tools/common/plugins/util/DevUtil.java | 11 ++++++++--- .../tools/common/plugins/util/BaseDevUtilTest.java | 4 ++-- 2 files changed, 10 insertions(+), 5 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 7e4ae04d4..1d7ee4c71 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 @@ -433,6 +433,7 @@ private enum FileTrackMode { /** Map of parent build files (parent build file, list of children build files) */ protected Map> parentBuildFiles; private boolean generateFeatures; + private boolean generateToSrc; private Set generatedFeaturesSet; // set of features in generated-features.xml file private boolean generatedFeaturesModified; private Set compileArtifactPaths; @@ -453,7 +454,7 @@ public DevUtil(File buildDirectory, File serverDirectory, File sourceDirectory, File containerfile, File containerBuildContext, String containerRunOpts, int containerBuildTimeout, boolean skipDefaultPorts, JavaCompilerOptions compilerOptions, boolean keepTempContainerfile, String mavenCacheLocation, List upstreamProjects, boolean recompileDependencies, - String packagingType, File buildFile, Map> parentBuildFiles, boolean generateFeatures, + String packagingType, File buildFile, Map> parentBuildFiles, boolean generateFeatures, boolean generateToSrc, Set compileArtifactPaths, Set testArtifactPaths, List monitoredWebResourceDirs, Map projectRecompileMap) { this(buildDirectory, serverDirectory, sourceDirectory, testSourceDirectory, configDirectory, projectDirectory, multiModuleProjectDirectory, resourceDirs, changeOnDemandTestsAction, @@ -463,7 +464,7 @@ public DevUtil(File buildDirectory, File serverDirectory, File sourceDirectory, containerfile, containerBuildContext, containerRunOpts, containerBuildTimeout, skipDefaultPorts, compilerOptions, keepTempContainerfile, mavenCacheLocation, upstreamProjects, recompileDependencies, - packagingType, buildFile, parentBuildFiles, generateFeatures, + packagingType, buildFile, parentBuildFiles, generateFeatures, generateToSrc, compileArtifactPaths, testArtifactPaths, monitoredWebResourceDirs); // setting projectRecompileMap as empty if input is null from ci.maven this.projectRecompileMap = projectRecompileMap != null ? projectRecompileMap : new HashMap<>(); @@ -478,7 +479,7 @@ public DevUtil(File buildDirectory, File serverDirectory, File sourceDirectory, File containerfile, File containerBuildContext, String containerRunOpts, int containerBuildTimeout, boolean skipDefaultPorts, JavaCompilerOptions compilerOptions, boolean keepTempContainerfile, String mavenCacheLocation, List upstreamProjects, boolean recompileDependencies, - String packagingType, File buildFile, Map> parentBuildFiles, boolean generateFeatures, + String packagingType, File buildFile, Map> parentBuildFiles, boolean generateFeatures, boolean generateToSrc, Set compileArtifactPaths, Set testArtifactPaths, List monitoredWebResourceDirs) { this.buildDirectory = buildDirectory; this.serverDirectory = serverDirectory; @@ -553,6 +554,7 @@ public DevUtil(File buildDirectory, File serverDirectory, File sourceDirectory, this.parentBuildFiles = parentBuildFiles; } this.generateFeatures = generateFeatures; + this.generateToSrc = generateToSrc; this.compileArtifactPaths = compileArtifactPaths; this.testArtifactPaths = testArtifactPaths; this.monitoredWebResourceDirs = monitoredWebResourceDirs; @@ -5817,10 +5819,13 @@ private boolean serverFeaturesModified() { if (generateFeatures) { // generateFeatures scenario: check if a generated feature has been manually added to other config files + // Here we pass generated-features.xml instead of server.xml to calculate the generated ones FeaturesPlatforms fp = servUtil.getServerXmlFeatures(new FeaturesPlatforms(), serverDirectory, generatedFeaturesFile, null, null); if (fp != null) generatedFeatureSet = fp.getFeatures(); + + // Calculate the features specified in the config excluding those in generated-features Set generatedFiles = new HashSet(); generatedFiles.add(generatedFeaturesFile.getName()); // if serverXmlFile is null, getServerFeatures will use the default server.xml diff --git a/src/test/java/io/openliberty/tools/common/plugins/util/BaseDevUtilTest.java b/src/test/java/io/openliberty/tools/common/plugins/util/BaseDevUtilTest.java index 508feddfd..70fa1bd67 100644 --- a/src/test/java/io/openliberty/tools/common/plugins/util/BaseDevUtilTest.java +++ b/src/test/java/io/openliberty/tools/common/plugins/util/BaseDevUtilTest.java @@ -41,13 +41,13 @@ public DevTestUtil(File serverDirectory, File sourceDirectory, File testSourceDi List resourceDirs, List webResourceDirs, boolean hotTests, boolean skipTests) throws IOException { super(temp.newFolder(), serverDirectory, sourceDirectory, testSourceDirectory, configDirectory, null, null, resourceDirs, false, hotTests, skipTests, false, false, false, null, 30, 30, 5, 500, true, false, false, false, - false, null, null, null, 0, false, null, false, null, null, false, null, null, null, false, null, null, webResourceDirs, Collections.emptyMap()); + false, null, null, null, 0, false, null, false, null, null, false, null, null, null, false, false, null, null, webResourceDirs, Collections.emptyMap()); } public DevTestUtil(File serverDirectory, File buildDir) { super(buildDir, serverDirectory, null, null, null, null, null, null, false, false, false, false, false, false, null, 30, 30, 5, 500, true, false, false, false, - false, null, null, null, 0, false, null, false, null, null, false, null, null, null, false, null, null, null, Collections.emptyMap()); + false, null, null, null, 0, false, null, false, null, null, false, null, null, null, false, false, null, null, null, Collections.emptyMap()); } @Override From b04e985af6469cf10bd434bf1db272182cfda584 Mon Sep 17 00:00:00 2001 From: Paul Gooderham Date: Thu, 29 May 2025 18:13:31 -0400 Subject: [PATCH 04/46] Toggle generateToSrc option using hotkey Signed-off-by: Paul Gooderham --- .../tools/common/plugins/util/DevUtil.java | 62 ++++++++++++++++--- 1 file changed, 52 insertions(+), 10 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 1d7ee4c71..c582db62d 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 @@ -438,7 +438,8 @@ private enum FileTrackMode { private boolean generatedFeaturesModified; private Set compileArtifactPaths; private Set testArtifactPaths; - protected final File generatedFeaturesFile; + protected File generatedFeaturesFile; + protected File genContextDir; private File modifiedSrcBuildFile; protected boolean skipInstallFeature; @@ -555,10 +556,10 @@ public DevUtil(File buildDirectory, File serverDirectory, File sourceDirectory, } this.generateFeatures = generateFeatures; this.generateToSrc = generateToSrc; + initGenerationContext(); this.compileArtifactPaths = compileArtifactPaths; this.testArtifactPaths = testArtifactPaths; this.monitoredWebResourceDirs = monitoredWebResourceDirs; - this.generatedFeaturesFile = new File(configDirectory, BinaryScannerUtil.GENERATED_FEATURES_FILE_PATH); this.generatedFeaturesModified = false; if (this.generateFeatures) { this.generatedFeaturesSet = getGeneratedFeatures(); @@ -568,6 +569,10 @@ public DevUtil(File buildDirectory, File serverDirectory, File sourceDirectory, this.modifiedSrcBuildFile = null; } + private void initGenerationContext() { + this.genContextDir = generateToSrc ? configDirectory : serverDirectory; + this.generatedFeaturesFile = new File(genContextDir, BinaryScannerUtil.GENERATED_FEATURES_FILE_PATH); + } /** * Run unit and/or integration tests * @@ -2593,6 +2598,7 @@ private void printHelpMessages() { private void printFeatureGenerationStatus() { info(formatAttentionMessage("Automatic generation of features: " + getFormattedBooleanString(generateFeatures))); + info(formatAttentionMessage("Generation of features to src directory: " + getFormattedBooleanString(generateToSrc))); } private void printFeatureGenerationHotkeys() { @@ -2600,6 +2606,7 @@ private void printFeatureGenerationHotkeys() { "g - toggle the automatic generation of features, type 'g' and press Enter.")); info(formatAttentionMessage( " A new server configuration file will be generated in the SOURCE configDropins/overrides configuration directory.")); + info(formatAttentionMessage("s - generate features to src directory, type 's' and press Enter.")); if (generateFeatures) { // If generateFeatures is enabled, then also describe the optimize hotkey info(formatAttentionMessage("o - optimize the list of generated features, type 'o' and press Enter.")); @@ -2628,13 +2635,9 @@ private void toggleFeatureGeneration() { generateFeatures = !generateFeatures; logFeatureGenerationStatus(); if (generateFeatures) { - String generatedFileCanonicalPath; - try { - generatedFileCanonicalPath = new File(configDirectory, BinaryScannerUtil.GENERATED_FEATURES_FILE_PATH).getCanonicalPath(); - } catch (IOException e) { - generatedFileCanonicalPath = new File(configDirectory, BinaryScannerUtil.GENERATED_FEATURES_FILE_PATH).toString(); + if (generateToSrc) { + warnSrcDirModified(); } - warn("The source configuration directory will be modified. Features will automatically be generated in a new file: " + generatedFileCanonicalPath); // If hotkey is toggled to “true”, generate features right away. optimizeGenerateFeatures(); } @@ -2644,6 +2647,42 @@ private void logFeatureGenerationStatus() { info("Setting automatic generation of features to: " + getFormattedBooleanString(generateFeatures)); } + private void toggleGenerateToSrc() { + generateToSrc = !generateToSrc; + logGenerateToSrcStatus(); + initGenerationContext(); + if (!generateToSrc) { // when you toggle off generate to src, delete the generated file in src + File srcGenFeaturesFile = new File(configDirectory, BinaryScannerUtil.GENERATED_FEATURES_FILE_PATH); + if (srcGenFeaturesFile.exists()) { + if (!srcGenFeaturesFile.delete()) { + debug("Error trying to delete the generated features file:"+srcGenFeaturesFile.getAbsolutePath()); + } + } + } + if (generateFeatures) { + if (generateToSrc) { + warnSrcDirModified(); + } + // If this option is toggled, generate features right away. + optimizeGenerateFeatures(); + } + } + + private void logGenerateToSrcStatus() { + info("Setting generation of features in src directory to: " + getFormattedBooleanString(generateToSrc)); + } + + private void warnSrcDirModified() { + String generatedFileCanonicalPath; + try { + generatedFileCanonicalPath = new File(configDirectory, BinaryScannerUtil.GENERATED_FEATURES_FILE_PATH).getCanonicalPath(); + } catch (IOException e) { + generatedFileCanonicalPath = new File(configDirectory, BinaryScannerUtil.GENERATED_FEATURES_FILE_PATH).toString(); + } + warn("The source configuration directory will be modified. Features will automatically be generated in a new file: " + generatedFileCanonicalPath); + } + + // called by Liberty plugins protected void setFeatureGeneration(boolean generateFeatures) { this.generateFeatures = generateFeatures; logFeatureGenerationStatus(); @@ -2721,6 +2760,7 @@ private void readInput() { HotKey h = new HotKey("h", "help"); HotKey r = new HotKey("r"); HotKey g = new HotKey("g"); + HotKey s = new HotKey("s"); HotKey o = new HotKey("o"); HotKey t = new HotKey("t"); HotKey enter = new HotKey(""); @@ -2753,6 +2793,8 @@ private void readInput() { info(formatAttentionBarrier()); } else if (g.isPressed(line)) { toggleFeatureGeneration(); + } else if (s.isPressed(line)) { + toggleGenerateToSrc(); } else if (o.isPressed(line)) { if (generateFeatures) { optimizeGenerateFeatures(); @@ -4701,8 +4743,8 @@ private boolean isContainerfileDirectoryChanged(File... files) throws IOExceptio } /** - * Determines if the corresponding target config file was generated by a Liberty - * plugin + * Determines if the specified config file in target dir was generated by a Liberty + * plugin: bootstrap.properties or jvm.options * * @param fileChanged the file that was changed * @param srcDir the directory of the file changed From 495a11826bacae68ea84fe9d2e68db4b70050af2 Mon Sep 17 00:00:00 2001 From: Paul Gooderham Date: Fri, 7 Nov 2025 15:54:31 -0500 Subject: [PATCH 05/46] Update src/main/java/io/openliberty/tools/common/plugins/util/DevUtil.java Co-authored-by: Trevor Crawford --- .../java/io/openliberty/tools/common/plugins/util/DevUtil.java | 2 +- 1 file changed, 1 insertion(+), 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 c582db62d..1751b628c 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 @@ -2655,7 +2655,7 @@ private void toggleGenerateToSrc() { File srcGenFeaturesFile = new File(configDirectory, BinaryScannerUtil.GENERATED_FEATURES_FILE_PATH); if (srcGenFeaturesFile.exists()) { if (!srcGenFeaturesFile.delete()) { - debug("Error trying to delete the generated features file:"+srcGenFeaturesFile.getAbsolutePath()); + debug("Error trying to delete the generated features file:" + srcGenFeaturesFile.getAbsolutePath()); } } } From ba2777d78983e50461001ece4cfb8c5d616e1e16 Mon Sep 17 00:00:00 2001 From: Paul Gooderham Date: Mon, 16 Jun 2025 10:35:56 -0400 Subject: [PATCH 06/46] Handle the generateToSrc option in devmode Signed-off-by: Paul Gooderham --- .../tools/common/plugins/util/DevUtil.java | 58 ++++++++++++++----- 1 file changed, 44 insertions(+), 14 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 1751b628c..8cc710e59 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 @@ -1,5 +1,5 @@ /** - * (C) Copyright IBM Corporation 2019, 2024. + * (C) Copyright IBM Corporation 2019, 2025. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -439,6 +439,7 @@ private enum FileTrackMode { private Set compileArtifactPaths; private Set testArtifactPaths; protected File generatedFeaturesFile; + protected File generatedFeaturesFileTemp; protected File genContextDir; private File modifiedSrcBuildFile; @@ -572,6 +573,12 @@ public DevUtil(File buildDirectory, File serverDirectory, File sourceDirectory, private void initGenerationContext() { this.genContextDir = generateToSrc ? configDirectory : serverDirectory; this.generatedFeaturesFile = new File(genContextDir, BinaryScannerUtil.GENERATED_FEATURES_FILE_PATH); + this.generatedFeaturesFileTemp = new File(buildDirectory, BinaryScannerUtil.GENERATED_FEATURES_TEMP_PATH); + } + + public void copyTempFeatureFileToServer() throws IOException { + File generatedFeaturesFileTempParentDir = generatedFeaturesFileTemp.getParentFile(); + copyFile(generatedFeaturesFileTemp, generatedFeaturesFileTempParentDir, serverDirectory, null); } /** * Run unit and/or integration tests @@ -1915,8 +1922,20 @@ private void addUserId(List commandElements) { } } + /** + * Create a Liberty server using the default name "defaultServer" or the server + * specified by the common parameter "serverName" in pom.xml. + * + * @throws PluginExecutionException + */ public abstract void libertyCreate() throws PluginExecutionException; + /** + * Deploy the default apps on the Liberty server or deploy the apps + * specified in the configuration of the deploy goal in pom.xml. + * + * @throws PluginExecutionException + */ public abstract void libertyDeploy() throws PluginExecutionException; /** @@ -1924,9 +1943,10 @@ private void addUserId(List commandElements) { * * @param classes class file paths features should be generated for (can be null if no modified classes) * @param optimize if true, generate optimized feature list + * @param useTmpDir if true, generate feature file in a hidden directory named in BinaryScannerUtil * @return true if feature generation was successful */ - public abstract boolean libertyGenerateFeatures(Collection classes, boolean optimize); + public abstract boolean libertyGenerateFeatures(Collection classes, boolean optimize, boolean useTmpDir); /** * Install features in regular dev mode. This method should not be used in container mode. @@ -2639,7 +2659,7 @@ private void toggleFeatureGeneration() { warnSrcDirModified(); } // If hotkey is toggled to “true”, generate features right away. - optimizeGenerateFeatures(); + optimizeGenerateFeatures(false); } } @@ -2664,7 +2684,7 @@ private void toggleGenerateToSrc() { warnSrcDirModified(); } // If this option is toggled, generate features right away. - optimizeGenerateFeatures(); + optimizeGenerateFeatures(false); } } @@ -2691,10 +2711,10 @@ protected void setFeatureGeneration(boolean generateFeatures) { /** * Generate features using all classes and only user specified features. */ - private boolean optimizeGenerateFeatures() { + private boolean optimizeGenerateFeatures(boolean useTmpDir) { debug("Generating optimized features list..."); // scan all class files and provide only user specified features - boolean generatedFeatures = libertyGenerateFeatures(null, true); + boolean generatedFeatures = libertyGenerateFeatures(null, true, useTmpDir); if (generatedFeatures) { modifiedClasses.clear(); failedToGenerateClasses.clear(); @@ -2713,7 +2733,7 @@ private boolean incrementGenerateFeatures() { boolean generatedFeatures = false; try { Collection classPaths = getClassPaths(modifiedClasses); - generatedFeatures = libertyGenerateFeatures(classPaths, false); + generatedFeatures = libertyGenerateFeatures(classPaths, false, false); if (generatedFeatures) { modifiedClasses.clear(); failedToGenerateClasses.clear(); @@ -2797,7 +2817,7 @@ private void readInput() { toggleGenerateToSrc(); } else if (o.isPressed(line)) { if (generateFeatures) { - optimizeGenerateFeatures(); + optimizeGenerateFeatures(false); } else { warn("Cannot optimize features because automatic generation of features is off."); warn("To toggle the automatic generation of features, type 'g' and press Enter."); @@ -4563,14 +4583,14 @@ private void processConfigFileChange(File fileChanged, ChangeType changeType, Th // generate features whenever features have changed and an XML file is modified, // excluding the generated features file + // if generateToSrc is false then we must copy generated file to serverDir after install to temp if (generateFeatures && (fileChanged.getName().endsWith(".xml") && !isGeneratedFeaturesFile) && serverFeaturesModified) { - generateFeaturesSuccess = optimizeGenerateFeatures(); + generateFeaturesSuccess = optimizeGenerateFeatures(!generateToSrc); } if (serverFeaturesModified) { - // suppress install feature warning - property must be set before calling - // installFeaturesToTempDir + // suppress install feature warning - property must be set before installing using temp dir System.setProperty(SKIP_BETA_INSTALL_WARNING, Boolean.TRUE.toString()); installFeaturesToTempDir(fileChanged, fileChangedParentDir, targetFileName, generateFeaturesSuccess); } @@ -4582,7 +4602,12 @@ private void processConfigFileChange(File fileChanged, ChangeType changeType, Th if (generateFeaturesSuccess && generatedFeaturesModified && !isGeneratedFeaturesFile) { // this logic is not entered if the fileChanged is the generated features file // copy generated features file to server dir - copyFile(generatedFeaturesFile, configDirectory, serverDirectory, null); + if (generateToSrc) { // copy generated-features.xml file from src dir + copyFile(generatedFeaturesFile, configDirectory, serverDirectory, null); + } else { // copy generated-features.xml file from the temp dir. used by generate features + File tempParent = generatedFeaturesFileTemp.getParentFile(); + copyFile(generatedFeaturesFileTemp, tempParent, serverDirectory, null); + } generatedFeaturesModified = false; } if (serverFeaturesModified) { @@ -4625,7 +4650,7 @@ private void processConfigFileChange(File fileChanged, ChangeType changeType, Th if (generateFeatures && (fileChanged.getName().endsWith(".xml") && !fileChanged.equals(generatedFeaturesFile)) && serverFeaturesModified()) { - optimizeGenerateFeatures(); + optimizeGenerateFeatures(false); } // Let this restart if needed for container mode. Otherwise, nothing else needs to be done for config file delete. if (isContainerfileDirectoryChanged(serverDirectory, fileChanged)) { @@ -4820,7 +4845,12 @@ public boolean accept(File pathname) { copyFile(fileChanged, srcDir, tempConfig, targetFileName); if (generateFeatures && generateFeaturesSuccess && !fileChanged.equals(generatedFeaturesFile)) { // copy generated-features.xml file - copyFile(generatedFeaturesFile, srcDir, tempConfig, generatedFeaturesFile.getName()); + if (generateToSrc) { // copy generated-features.xml file from src dir + copyFile(generatedFeaturesFile, srcDir, tempConfig, generatedFeaturesFile.getName()); + } else { // copy generated-features.xml file from the temp dir. used by generate features + File tempParent = generatedFeaturesFileTemp.getParentFile(); + copyFile(generatedFeaturesFileTemp, tempParent, tempConfig, generatedFeaturesFile.getName()); + } } installFeatures(fileChanged, tempConfig, generateFeatures); cleanUpTempConfig(); From fea5c1c839118e54d1f5156a99246f03ce0ba95c Mon Sep 17 00:00:00 2001 From: Paul Gooderham Date: Mon, 16 Jun 2025 11:30:55 -0400 Subject: [PATCH 07/46] Update test case Signed-off-by: Paul Gooderham --- .../openliberty/tools/common/plugins/util/BaseDevUtilTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/io/openliberty/tools/common/plugins/util/BaseDevUtilTest.java b/src/test/java/io/openliberty/tools/common/plugins/util/BaseDevUtilTest.java index 70fa1bd67..b2f3a3395 100644 --- a/src/test/java/io/openliberty/tools/common/plugins/util/BaseDevUtilTest.java +++ b/src/test/java/io/openliberty/tools/common/plugins/util/BaseDevUtilTest.java @@ -197,7 +197,7 @@ public void libertyInstallFeature() { } @Override - public boolean libertyGenerateFeatures(Collection classes, boolean optimize) { + public boolean libertyGenerateFeatures(Collection classes, boolean optimize, boolean useTemp) { // not needed for tests return true; } From b94075a081988f300b9b86109992749c575ae265 Mon Sep 17 00:00:00 2001 From: Paul Gooderham Date: Mon, 16 Jun 2025 14:55:46 -0400 Subject: [PATCH 08/46] Display the correct info message when server file is in a temp dir Signed-off-by: Paul Gooderham --- .../tools/common/plugins/util/ServerFeatureUtil.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/main/java/io/openliberty/tools/common/plugins/util/ServerFeatureUtil.java b/src/main/java/io/openliberty/tools/common/plugins/util/ServerFeatureUtil.java index 5f5b90f6c..340f4d226 100644 --- a/src/main/java/io/openliberty/tools/common/plugins/util/ServerFeatureUtil.java +++ b/src/main/java/io/openliberty/tools/common/plugins/util/ServerFeatureUtil.java @@ -358,8 +358,8 @@ public FeaturesPlatforms getServerXmlFeatures(FeaturesPlatforms origResult, File debug("Exception received: "+e.getMessage(), e); return result; } - - info("Parsing the server file for features and includes: " + getRelativeServerFilePath(serverDirectory, serverFile)); + + info("Parsing the server file for features and includes: " + getRelativeServerFilePathForDisplay(serverDirectory, serverFile)); updatedParsedXmls.add(canonicalServerFile); if (!canonicalServerFile.exists()) { warn("The server file " + canonicalServerFile + " does not exist. Skipping its features."); @@ -669,12 +669,16 @@ private Properties getPropertiesFromFile(File propertiesFile) { // return the server file path relative to the server directory - private String getRelativeServerFilePath(File serverDirectory, File serverFile) { + private String getRelativeServerFilePathForDisplay(File serverDirectory, File serverFile) { try { File canonicalServerDirectory = serverDirectory.getCanonicalFile(); URI serverDirectoryUri = canonicalServerDirectory.toURI(); URI serverFileUri = serverFile.toURI(); - return serverDirectory.getName() + File.separator + serverDirectoryUri.relativize(serverFileUri).getPath(); + if (serverDirectoryUri.relativize(serverFileUri).equals(serverFileUri)) { + return serverFileUri.getPath().toString(); + } else { + return serverDirectory.getName() + File.separator + serverDirectoryUri.relativize(serverFileUri).getPath(); + } } catch (IOException e1) { debug("Unable to determine the file path of " + serverFile + " relative to the server directory " + serverDirectory); From c89b196eb452bd946187fc25733117e67d9ea231 Mon Sep 17 00:00:00 2001 From: Paul Gooderham Date: Mon, 16 Jun 2025 17:17:30 -0400 Subject: [PATCH 09/46] Better comments Signed-off-by: Paul Gooderham --- .../java/io/openliberty/tools/common/plugins/util/DevUtil.java | 1 + 1 file changed, 1 insertion(+) 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 8cc710e59..1354843ee 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 @@ -5939,6 +5939,7 @@ private boolean generatedFeaturesModified() { } // returns the features specified in the generated-features.xml file + // generated-features.xml has a element so it is also a "serverFile" private Set getGeneratedFeatures() { ServerFeatureUtil servUtil = getServerFeatureUtilObj(); FeaturesPlatforms fp = servUtil.getServerXmlFeatures(new FeaturesPlatforms(), configDirectory, From a14c7ec0da8207492231c7d5dfacaa10dad5e86d Mon Sep 17 00:00:00 2001 From: Paul Gooderham Date: Mon, 16 Jun 2025 19:35:28 -0400 Subject: [PATCH 10/46] Add constant for generated features overrides directory Signed-off-by: Paul Gooderham --- .../tools/common/plugins/util/BinaryScannerUtil.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/io/openliberty/tools/common/plugins/util/BinaryScannerUtil.java b/src/main/java/io/openliberty/tools/common/plugins/util/BinaryScannerUtil.java index 1071b6d2c..9223d7e0b 100644 --- a/src/main/java/io/openliberty/tools/common/plugins/util/BinaryScannerUtil.java +++ b/src/main/java/io/openliberty/tools/common/plugins/util/BinaryScannerUtil.java @@ -33,7 +33,8 @@ public abstract class BinaryScannerUtil { public static final String BINARY_SCANNER_MAVEN_VERSION = "[24.0.0.2]"; public static final String GENERATED_FEATURES_FILE_NAME = "generated-features.xml"; - public static final String GENERATED_FEATURES_FILE_PATH = "configDropins/overrides/" + GENERATED_FEATURES_FILE_NAME; + public static final String GENERATED_FEATURES_DIR_PATH = "configDropins/overrides"; + public static final String GENERATED_FEATURES_FILE_PATH = GENERATED_FEATURES_DIR_PATH + File.pathSeparator + GENERATED_FEATURES_FILE_NAME; public static final String GENERATED_FEATURES_TEMP_PATH = ".libertyFeatures/" + GENERATED_FEATURES_FILE_NAME; private static final String FEATURE_MODIFIED_EXCEPTION = "com.ibm.websphere.binary.cmdline.exceptions.RequiredFeatureModifiedException"; private static final String FEATURE_CONFLICT_EXCEPTION = "com.ibm.websphere.binary.cmdline.exceptions.FeatureConflictException"; From b0a2ab8ca48abec3b18c9fb81481634ebf2530f3 Mon Sep 17 00:00:00 2001 From: Paul Gooderham Date: Mon, 16 Jun 2025 19:37:11 -0400 Subject: [PATCH 11/46] Add support for feature gen to server dir for class file change Signed-off-by: Paul Gooderham --- .../tools/common/plugins/util/DevUtil.java | 35 ++++++++++++++----- 1 file changed, 26 insertions(+), 9 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 1354843ee..4580667d4 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 @@ -2712,7 +2712,7 @@ protected void setFeatureGeneration(boolean generateFeatures) { * Generate features using all classes and only user specified features. */ private boolean optimizeGenerateFeatures(boolean useTmpDir) { - debug("Generating optimized features list..."); + debug("Generating optimized features list...use temp directory=" + useTmpDir); // scan all class files and provide only user specified features boolean generatedFeatures = libertyGenerateFeatures(null, true, useTmpDir); if (generatedFeatures) { @@ -2728,12 +2728,12 @@ private boolean optimizeGenerateFeatures(boolean useTmpDir) { * Generate features using updated classes and all existing features. * Returns true if successful */ - private boolean incrementGenerateFeatures() { - debug("Generating feature list from incremental changes..."); + private boolean incrementGenerateFeatures(boolean useTmpDir) { + debug("Generating feature list from incremental changes...use temp directory=" + useTmpDir); boolean generatedFeatures = false; try { Collection classPaths = getClassPaths(modifiedClasses); - generatedFeatures = libertyGenerateFeatures(classPaths, false, false); + generatedFeatures = libertyGenerateFeatures(classPaths, false, useTmpDir); if (generatedFeatures) { modifiedClasses.clear(); failedToGenerateClasses.clear(); @@ -3132,7 +3132,13 @@ public void watchFiles(File outputDirectory, File testOutputDirectory, final Thr modifiedSrcBuildFile = null; // only needed when recompileDependencies is true long generatedTime = generatedFeaturesFile.lastModified(); int numApplicationUpdatedMessages = countApplicationUpdatedMessages(); - incrementGenerateFeatures(); + boolean generateFeaturesSuccess = incrementGenerateFeatures(!generateToSrc); + if (generateFeaturesSuccess && !generateToSrc) { + // generated features to file in temp directory. Install then copy to server dir + File tempParentDir = generatedFeaturesFileTemp.getParentFile(); + installFeaturesToTempDir(generatedFeaturesFileTemp, tempParentDir, null, generateFeaturesSuccess); + copyGFTemp(serverDirectory); + } if (!generatedFeaturesFile.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) @@ -4842,20 +4848,31 @@ public boolean accept(File pathname) { return !skip; } }, true); - copyFile(fileChanged, srcDir, tempConfig, targetFileName); - if (generateFeatures && generateFeaturesSuccess && !fileChanged.equals(generatedFeaturesFile)) { + if (fileChanged.equals(generatedFeaturesFileTemp)) { + copyGFTemp(tempConfig); + } else { + copyFile(fileChanged, srcDir, tempConfig, targetFileName); + } + if (generateFeatures && generateFeaturesSuccess && + !fileChanged.equals(generatedFeaturesFile) && + !fileChanged.equals(generatedFeaturesFileTemp)) { // copy generated-features.xml file if (generateToSrc) { // copy generated-features.xml file from src dir copyFile(generatedFeaturesFile, srcDir, tempConfig, generatedFeaturesFile.getName()); } else { // copy generated-features.xml file from the temp dir. used by generate features - File tempParent = generatedFeaturesFileTemp.getParentFile(); - copyFile(generatedFeaturesFileTemp, tempParent, tempConfig, generatedFeaturesFile.getName()); + copyGFTemp(tempConfig); } } installFeatures(fileChanged, tempConfig, generateFeatures); cleanUpTempConfig(); } + private void copyGFTemp(File tempConfig) throws IOException { + File tempParent = generatedFeaturesFileTemp.getParentFile(); + File overrides = new File(tempConfig, BinaryScannerUtil.GENERATED_FEATURES_DIR_PATH); + copyFile(generatedFeaturesFileTemp, tempParent, overrides, null); + } + /** * Whether dev mode should ignore a file or directory. * From c73102dc12fa3265230f93199bcc38eab0030e79 Mon Sep 17 00:00:00 2001 From: Paul Gooderham Date: Mon, 16 Jun 2025 19:54:05 -0400 Subject: [PATCH 12/46] Clean up method copyTempFeatureFileToServer Signed-off-by: Paul Gooderham --- .../tools/common/plugins/util/DevUtil.java | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 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 4580667d4..7b251b9c2 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 @@ -576,9 +576,10 @@ private void initGenerationContext() { this.generatedFeaturesFileTemp = new File(buildDirectory, BinaryScannerUtil.GENERATED_FEATURES_TEMP_PATH); } - public void copyTempFeatureFileToServer() throws IOException { + public void copyTempFeatureFileToServer(File tempConfig) throws IOException { File generatedFeaturesFileTempParentDir = generatedFeaturesFileTemp.getParentFile(); - copyFile(generatedFeaturesFileTemp, generatedFeaturesFileTempParentDir, serverDirectory, null); + File overrides = new File(tempConfig, BinaryScannerUtil.GENERATED_FEATURES_DIR_PATH); + copyFile(generatedFeaturesFileTemp, generatedFeaturesFileTempParentDir, overrides, null); } /** * Run unit and/or integration tests @@ -3137,7 +3138,7 @@ public void watchFiles(File outputDirectory, File testOutputDirectory, final Thr // generated features to file in temp directory. Install then copy to server dir File tempParentDir = generatedFeaturesFileTemp.getParentFile(); installFeaturesToTempDir(generatedFeaturesFileTemp, tempParentDir, null, generateFeaturesSuccess); - copyGFTemp(serverDirectory); + copyTempFeatureFileToServer(serverDirectory); } if (!generatedFeaturesFile.exists()) { // run tests if generated-features.xml does not exist as there are no new features to install @@ -4849,7 +4850,7 @@ public boolean accept(File pathname) { } }, true); if (fileChanged.equals(generatedFeaturesFileTemp)) { - copyGFTemp(tempConfig); + copyTempFeatureFileToServer(tempConfig); } else { copyFile(fileChanged, srcDir, tempConfig, targetFileName); } @@ -4860,19 +4861,13 @@ public boolean accept(File pathname) { if (generateToSrc) { // copy generated-features.xml file from src dir copyFile(generatedFeaturesFile, srcDir, tempConfig, generatedFeaturesFile.getName()); } else { // copy generated-features.xml file from the temp dir. used by generate features - copyGFTemp(tempConfig); + copyTempFeatureFileToServer(tempConfig); } } installFeatures(fileChanged, tempConfig, generateFeatures); cleanUpTempConfig(); } - private void copyGFTemp(File tempConfig) throws IOException { - File tempParent = generatedFeaturesFileTemp.getParentFile(); - File overrides = new File(tempConfig, BinaryScannerUtil.GENERATED_FEATURES_DIR_PATH); - copyFile(generatedFeaturesFileTemp, tempParent, overrides, null); - } - /** * Whether dev mode should ignore a file or directory. * From a6f7106032be96c22bf718802c59f949edb0848e Mon Sep 17 00:00:00 2001 From: Paul Gooderham Date: Tue, 17 Jun 2025 19:26:55 -0400 Subject: [PATCH 13/46] Keep temp generate features file in configDropins dir even in temp dir Signed-off-by: Paul Gooderham --- .../tools/common/plugins/util/BinaryScannerUtil.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/io/openliberty/tools/common/plugins/util/BinaryScannerUtil.java b/src/main/java/io/openliberty/tools/common/plugins/util/BinaryScannerUtil.java index 9223d7e0b..1c8e4827b 100644 --- a/src/main/java/io/openliberty/tools/common/plugins/util/BinaryScannerUtil.java +++ b/src/main/java/io/openliberty/tools/common/plugins/util/BinaryScannerUtil.java @@ -33,9 +33,10 @@ public abstract class BinaryScannerUtil { public static final String BINARY_SCANNER_MAVEN_VERSION = "[24.0.0.2]"; public static final String GENERATED_FEATURES_FILE_NAME = "generated-features.xml"; - public static final String GENERATED_FEATURES_DIR_PATH = "configDropins/overrides"; - public static final String GENERATED_FEATURES_FILE_PATH = GENERATED_FEATURES_DIR_PATH + File.pathSeparator + GENERATED_FEATURES_FILE_NAME; - public static final String GENERATED_FEATURES_TEMP_PATH = ".libertyFeatures/" + GENERATED_FEATURES_FILE_NAME; + public static final String GENERATED_FEATURES_DIR_PATH = "configDropins/overrides/"; + public static final String GENERATED_FEATURES_FILE_PATH = GENERATED_FEATURES_DIR_PATH + GENERATED_FEATURES_FILE_NAME; + public static final String GENERATED_FEATURES_TEMP_DIR = ".libertyFeatures"; + public static final String GENERATED_FEATURES_TEMP_PATH = GENERATED_FEATURES_TEMP_DIR + File.separator + GENERATED_FEATURES_FILE_PATH; private static final String FEATURE_MODIFIED_EXCEPTION = "com.ibm.websphere.binary.cmdline.exceptions.RequiredFeatureModifiedException"; private static final String FEATURE_CONFLICT_EXCEPTION = "com.ibm.websphere.binary.cmdline.exceptions.FeatureConflictException"; private static final String PROVIDED_FEATURE_EXCEPTION = "com.ibm.websphere.binary.cmdline.exceptions.ProvidedFeatureConflictException"; From 52fe283e33d0bfab8ae9c38485a33dfa3c490e6b Mon Sep 17 00:00:00 2001 From: Paul Gooderham Date: Tue, 17 Jun 2025 19:34:51 -0400 Subject: [PATCH 14/46] generatFeaturesFile is either configDir or tempDir Signed-off-by: Paul Gooderham --- .../tools/common/plugins/util/DevUtil.java | 52 +++++++------------ 1 file changed, 18 insertions(+), 34 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 7b251b9c2..7b4f5a9d8 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 @@ -439,8 +439,7 @@ private enum FileTrackMode { private Set compileArtifactPaths; private Set testArtifactPaths; protected File generatedFeaturesFile; - protected File generatedFeaturesFileTemp; - protected File genContextDir; + protected File generatedFeaturesFileParent; private File modifiedSrcBuildFile; protected boolean skipInstallFeature; @@ -571,15 +570,12 @@ public DevUtil(File buildDirectory, File serverDirectory, File sourceDirectory, } private void initGenerationContext() { - this.genContextDir = generateToSrc ? configDirectory : serverDirectory; - this.generatedFeaturesFile = new File(genContextDir, BinaryScannerUtil.GENERATED_FEATURES_FILE_PATH); - this.generatedFeaturesFileTemp = new File(buildDirectory, BinaryScannerUtil.GENERATED_FEATURES_TEMP_PATH); + this.generatedFeaturesFileParent = generateToSrc ? configDirectory : new File(buildDirectory, BinaryScannerUtil.GENERATED_FEATURES_TEMP_DIR); + this.generatedFeaturesFile = new File(generatedFeaturesFileParent, BinaryScannerUtil.GENERATED_FEATURES_FILE_PATH); } - public void copyTempFeatureFileToServer(File tempConfig) throws IOException { - File generatedFeaturesFileTempParentDir = generatedFeaturesFileTemp.getParentFile(); - File overrides = new File(tempConfig, BinaryScannerUtil.GENERATED_FEATURES_DIR_PATH); - copyFile(generatedFeaturesFileTemp, generatedFeaturesFileTempParentDir, overrides, null); + public void copyTempFeatureFileToServer(File to) throws IOException { + copyFile(generatedFeaturesFile, generatedFeaturesFileParent, to, null); } /** * Run unit and/or integration tests @@ -2660,7 +2656,7 @@ private void toggleFeatureGeneration() { warnSrcDirModified(); } // If hotkey is toggled to “true”, generate features right away. - optimizeGenerateFeatures(false); + optimizeGenerateFeatures(!generateToSrc); } } @@ -2684,8 +2680,8 @@ private void toggleGenerateToSrc() { if (generateToSrc) { warnSrcDirModified(); } - // If this option is toggled, generate features right away. - optimizeGenerateFeatures(false); + // If hotkey is toggled, generate features right away. + optimizeGenerateFeatures(!generateToSrc); } } @@ -3136,8 +3132,7 @@ public void watchFiles(File outputDirectory, File testOutputDirectory, final Thr boolean generateFeaturesSuccess = incrementGenerateFeatures(!generateToSrc); if (generateFeaturesSuccess && !generateToSrc) { // generated features to file in temp directory. Install then copy to server dir - File tempParentDir = generatedFeaturesFileTemp.getParentFile(); - installFeaturesToTempDir(generatedFeaturesFileTemp, tempParentDir, null, generateFeaturesSuccess); + installFeaturesToTempDir(generatedFeaturesFile, generatedFeaturesFileParent, null, generateFeaturesSuccess); copyTempFeatureFileToServer(serverDirectory); } if (!generatedFeaturesFile.exists()) { @@ -4581,7 +4576,9 @@ private void processConfigFileChange(File fileChanged, ChangeType changeType, Th throws IOException, PluginExecutionException { boolean isGeneratedFeaturesFile = configuredServerXml ? false : fileChanged.equals(generatedFeaturesFile); String targetFileName = configuredServerXml ? "server.xml" : null; // if null file will retain the same name when copied - File fileChangedParentDir = configuredServerXml ? serverXmlFileParent : configDirectory; + // three possible values for the parent directory + File fileChangedParentDir = configuredServerXml ? serverXmlFileParent : + isGeneratedFeaturesFile ? generatedFeaturesFileParent : configDirectory; if (fileChanged.exists() && (changeType == ChangeType.MODIFY || changeType == ChangeType.CREATE)) { debug("Config file modified: " + fileChanged); @@ -4609,12 +4606,7 @@ private void processConfigFileChange(File fileChanged, ChangeType changeType, Th if (generateFeaturesSuccess && generatedFeaturesModified && !isGeneratedFeaturesFile) { // this logic is not entered if the fileChanged is the generated features file // copy generated features file to server dir - if (generateToSrc) { // copy generated-features.xml file from src dir - copyFile(generatedFeaturesFile, configDirectory, serverDirectory, null); - } else { // copy generated-features.xml file from the temp dir. used by generate features - File tempParent = generatedFeaturesFileTemp.getParentFile(); - copyFile(generatedFeaturesFileTemp, tempParent, serverDirectory, null); - } + copyTempFeatureFileToServer(serverDirectory); generatedFeaturesModified = false; } if (serverFeaturesModified) { @@ -4849,20 +4841,10 @@ public boolean accept(File pathname) { return !skip; } }, true); - if (fileChanged.equals(generatedFeaturesFileTemp)) { + File parentDir = fileChanged.equals(generatedFeaturesFile) ? generatedFeaturesFileParent : srcDir; + copyFile(fileChanged, parentDir, tempConfig, targetFileName); + if (generateFeatures && generateFeaturesSuccess && !fileChanged.equals(generatedFeaturesFile)) { copyTempFeatureFileToServer(tempConfig); - } else { - copyFile(fileChanged, srcDir, tempConfig, targetFileName); - } - if (generateFeatures && generateFeaturesSuccess && - !fileChanged.equals(generatedFeaturesFile) && - !fileChanged.equals(generatedFeaturesFileTemp)) { - // copy generated-features.xml file - if (generateToSrc) { // copy generated-features.xml file from src dir - copyFile(generatedFeaturesFile, srcDir, tempConfig, generatedFeaturesFile.getName()); - } else { // copy generated-features.xml file from the temp dir. used by generate features - copyTempFeatureFileToServer(tempConfig); - } } installFeatures(fileChanged, tempConfig, generateFeatures); cleanUpTempConfig(); @@ -5904,6 +5886,7 @@ private boolean serverFeaturesModified() { if (generateFeatures) { // generateFeatures scenario: check if a generated feature has been manually added to other config files // Here we pass generated-features.xml instead of server.xml to calculate the generated ones + // The second parameter will not be used for generated-features.xml. FeaturesPlatforms fp = servUtil.getServerXmlFeatures(new FeaturesPlatforms(), serverDirectory, generatedFeaturesFile, null, null); if (fp != null) @@ -5952,6 +5935,7 @@ private boolean generatedFeaturesModified() { // returns the features specified in the generated-features.xml file // generated-features.xml has a element so it is also a "serverFile" + // The second parameter will not be used for generated-features.xml. private Set getGeneratedFeatures() { ServerFeatureUtil servUtil = getServerFeatureUtilObj(); FeaturesPlatforms fp = servUtil.getServerXmlFeatures(new FeaturesPlatforms(), configDirectory, From 49cbf359fee6b18e1b697aaac7c8bf8de23d8303 Mon Sep 17 00:00:00 2001 From: Paul Gooderham Date: Tue, 17 Jun 2025 19:48:17 -0400 Subject: [PATCH 15/46] When you press hotkey o optionally generate to temp dir Signed-off-by: Paul Gooderham --- .../java/io/openliberty/tools/common/plugins/util/DevUtil.java | 2 +- 1 file changed, 1 insertion(+), 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 7b4f5a9d8..f743e28b3 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 @@ -2814,7 +2814,7 @@ private void readInput() { toggleGenerateToSrc(); } else if (o.isPressed(line)) { if (generateFeatures) { - optimizeGenerateFeatures(false); + optimizeGenerateFeatures(!generateToSrc); } else { warn("Cannot optimize features because automatic generation of features is off."); warn("To toggle the automatic generation of features, type 'g' and press Enter."); From 4c0052aa1e9811814e77ad71f41fce4172b89e8f Mon Sep 17 00:00:00 2001 From: Paul Gooderham Date: Wed, 18 Jun 2025 19:07:19 -0400 Subject: [PATCH 16/46] Register the generated features temp dir with the file watcher Signed-off-by: Paul Gooderham --- .../tools/common/plugins/util/DevUtil.java | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 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 f743e28b3..fa3f4aa68 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 @@ -440,6 +440,7 @@ private enum FileTrackMode { private Set testArtifactPaths; protected File generatedFeaturesFile; protected File generatedFeaturesFileParent; + protected File generatedFeaturesTmpDir; private File modifiedSrcBuildFile; protected boolean skipInstallFeature; @@ -556,6 +557,7 @@ public DevUtil(File buildDirectory, File serverDirectory, File sourceDirectory, } this.generateFeatures = generateFeatures; this.generateToSrc = generateToSrc; + this.generatedFeaturesTmpDir = new File(buildDirectory, BinaryScannerUtil.GENERATED_FEATURES_TEMP_DIR); initGenerationContext(); this.compileArtifactPaths = compileArtifactPaths; this.testArtifactPaths = testArtifactPaths; @@ -570,7 +572,7 @@ public DevUtil(File buildDirectory, File serverDirectory, File sourceDirectory, } private void initGenerationContext() { - this.generatedFeaturesFileParent = generateToSrc ? configDirectory : new File(buildDirectory, BinaryScannerUtil.GENERATED_FEATURES_TEMP_DIR); + this.generatedFeaturesFileParent = generateToSrc ? configDirectory : generatedFeaturesTmpDir; this.generatedFeaturesFile = new File(generatedFeaturesFileParent, BinaryScannerUtil.GENERATED_FEATURES_FILE_PATH); } @@ -3024,6 +3026,10 @@ public void watchFiles(File outputDirectory, File testOutputDirectory, final Thr registerSingleFile(containerfileUsed, executor); } + // Always register this hidden temp directory even if not used + generatedFeaturesTmpDir.mkdirs(); + registerSingleFile(new File(generatedFeaturesTmpDir, "dummy"), executor); + HashMap resourceMap = new HashMap(); for (File resourceDir : resourceDirs) { resourceMap.put(resourceDir, false); @@ -4174,6 +4180,7 @@ private void processFileChanges( Path testSrcPath = this.testSourceDirectory.getCanonicalFile().toPath(); Path configPath = this.configDirectory.getCanonicalFile().toPath(); Path outputPath = this.outputDirectory.getCanonicalFile().toPath(); + Path gfTmpDirPath = this.generatedFeaturesTmpDir.getCanonicalFile().toPath(); Path directory = fileChanged.getParentFile().getCanonicalFile().toPath(); @@ -4449,9 +4456,10 @@ private void processFileChanges( // This is for server.xml specified by the configuration parameter // server will load new properties processConfigFileChange(fileChanged, changeType, executor, numApplicationUpdatedMessages, true); - } else if (directory.startsWith(configPath) + } else if ((directory.startsWith(configPath) + || directory.startsWith(gfTmpDirPath)) && !isGeneratedConfigFile(fileChanged, configDirectory, serverDirectory)) { - // configuration file + // configuration file or generate-features.xml in temp directory processConfigFileChange(fileChanged, changeType, executor, numApplicationUpdatedMessages, false); } else if (bootstrapPropertiesFileParent != null && directory.equals(bootstrapPropertiesFileParent.getCanonicalFile().toPath()) From b8679df2da68f66ec90ba5960da531e029f130ef Mon Sep 17 00:00:00 2001 From: Paul Gooderham Date: Thu, 19 Jun 2025 15:00:57 -0400 Subject: [PATCH 17/46] Remove code added to process the generated features file in favour of processing by the file watcher Signed-off-by: Paul Gooderham --- .../io/openliberty/tools/common/plugins/util/DevUtil.java | 7 +------ 1 file changed, 1 insertion(+), 6 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 fa3f4aa68..2629b765e 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 @@ -3135,12 +3135,7 @@ public void watchFiles(File outputDirectory, File testOutputDirectory, final Thr modifiedSrcBuildFile = null; // only needed when recompileDependencies is true long generatedTime = generatedFeaturesFile.lastModified(); int numApplicationUpdatedMessages = countApplicationUpdatedMessages(); - boolean generateFeaturesSuccess = incrementGenerateFeatures(!generateToSrc); - if (generateFeaturesSuccess && !generateToSrc) { - // generated features to file in temp directory. Install then copy to server dir - installFeaturesToTempDir(generatedFeaturesFile, generatedFeaturesFileParent, null, generateFeaturesSuccess); - copyTempFeatureFileToServer(serverDirectory); - } + incrementGenerateFeatures(!generateToSrc); if (!generatedFeaturesFile.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) From bc93cb1b83721481ec682513436b2ff2f323f656 Mon Sep 17 00:00:00 2001 From: Paul Gooderham Date: Mon, 23 Jun 2025 11:36:58 -0400 Subject: [PATCH 18/46] Use generateToSrc option in generate features after config file deletion Signed-off-by: Paul Gooderham --- .../java/io/openliberty/tools/common/plugins/util/DevUtil.java | 2 +- 1 file changed, 1 insertion(+), 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 2629b765e..a4419f6f4 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 @@ -4652,7 +4652,7 @@ private void processConfigFileChange(File fileChanged, ChangeType changeType, Th if (generateFeatures && (fileChanged.getName().endsWith(".xml") && !fileChanged.equals(generatedFeaturesFile)) && serverFeaturesModified()) { - optimizeGenerateFeatures(false); + optimizeGenerateFeatures(!generateToSrc); } // Let this restart if needed for container mode. Otherwise, nothing else needs to be done for config file delete. if (isContainerfileDirectoryChanged(serverDirectory, fileChanged)) { From 3ae5530b764f7b48e0f246664f1fd3c428aa3fca Mon Sep 17 00:00:00 2001 From: Paul Gooderham Date: Wed, 25 Jun 2025 14:37:44 -0400 Subject: [PATCH 19/46] Register gen. features file to watch in dev mode Signed-off-by: Paul Gooderham --- .../io/openliberty/tools/common/plugins/util/DevUtil.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 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 a4419f6f4..ba804db8d 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 @@ -3026,9 +3026,10 @@ public void watchFiles(File outputDirectory, File testOutputDirectory, final Thr registerSingleFile(containerfileUsed, executor); } - // Always register this hidden temp directory even if not used - generatedFeaturesTmpDir.mkdirs(); - registerSingleFile(new File(generatedFeaturesTmpDir, "dummy"), executor); + // Always register this hidden temp directory because generateToSrc can be turned on in dev mode + File hiddenTempGenerateFeaturesFile = new File(generatedFeaturesTmpDir, BinaryScannerUtil.GENERATED_FEATURES_FILE_PATH); + hiddenTempGenerateFeaturesFile.getParentFile().mkdirs(); // must only mkdir on the directories + registerSingleFile(hiddenTempGenerateFeaturesFile, executor); HashMap resourceMap = new HashMap(); for (File resourceDir : resourceDirs) { From 67d7b340bc6a52a0a489d62bccf147766ab76c3e Mon Sep 17 00:00:00 2001 From: Paul Gooderham Date: Thu, 4 Dec 2025 18:23:58 -0500 Subject: [PATCH 20/46] Update copyright Signed-off-by: Paul Gooderham --- .../tools/common/plugins/util/BinaryScannerUtil.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/io/openliberty/tools/common/plugins/util/BinaryScannerUtil.java b/src/main/java/io/openliberty/tools/common/plugins/util/BinaryScannerUtil.java index 1c8e4827b..b9dce7356 100644 --- a/src/main/java/io/openliberty/tools/common/plugins/util/BinaryScannerUtil.java +++ b/src/main/java/io/openliberty/tools/common/plugins/util/BinaryScannerUtil.java @@ -1,5 +1,5 @@ /** - * (C) Copyright IBM Corporation 2021, 2022. + * (C) Copyright IBM Corporation 2021, 2025. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. From 980d0666b20d160c10177fc6c28956e53c56c8ec Mon Sep 17 00:00:00 2001 From: Paul Gooderham Date: Fri, 5 Dec 2025 14:59:01 -0500 Subject: [PATCH 21/46] Clarify a comment Signed-off-by: Paul Gooderham --- .../java/io/openliberty/tools/common/plugins/util/DevUtil.java | 2 +- 1 file changed, 1 insertion(+), 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 ba804db8d..90702496b 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 @@ -5890,7 +5890,7 @@ private boolean serverFeaturesModified() { if (generateFeatures) { // generateFeatures scenario: check if a generated feature has been manually added to other config files // Here we pass generated-features.xml instead of server.xml to calculate the generated ones - // The second parameter will not be used for generated-features.xml. + // The second parameter will only be used if generated-features.xml contains element. FeaturesPlatforms fp = servUtil.getServerXmlFeatures(new FeaturesPlatforms(), serverDirectory, generatedFeaturesFile, null, null); if (fp != null) From 502d3db0edc777370dadc9c3e74d676b476e18f7 Mon Sep 17 00:00:00 2001 From: Paul Gooderham Date: Fri, 5 Dec 2025 16:38:37 -0500 Subject: [PATCH 22/46] Rename a method to make more sense Signed-off-by: Paul Gooderham --- .../io/openliberty/tools/common/plugins/util/DevUtil.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 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 90702496b..16a204255 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 @@ -576,8 +576,8 @@ private void initGenerationContext() { this.generatedFeaturesFile = new File(generatedFeaturesFileParent, BinaryScannerUtil.GENERATED_FEATURES_FILE_PATH); } - public void copyTempFeatureFileToServer(File to) throws IOException { - copyFile(generatedFeaturesFile, generatedFeaturesFileParent, to, null); + public void copyGeneratedFeaturesFile(File destinationDir) throws IOException { + copyFile(generatedFeaturesFile, generatedFeaturesFileParent, destinationDir, null); } /** * Run unit and/or integration tests @@ -4610,7 +4610,7 @@ private void processConfigFileChange(File fileChanged, ChangeType changeType, Th if (generateFeaturesSuccess && generatedFeaturesModified && !isGeneratedFeaturesFile) { // this logic is not entered if the fileChanged is the generated features file // copy generated features file to server dir - copyTempFeatureFileToServer(serverDirectory); + copyGeneratedFeaturesFile(serverDirectory); generatedFeaturesModified = false; } if (serverFeaturesModified) { @@ -4848,7 +4848,7 @@ public boolean accept(File pathname) { File parentDir = fileChanged.equals(generatedFeaturesFile) ? generatedFeaturesFileParent : srcDir; copyFile(fileChanged, parentDir, tempConfig, targetFileName); if (generateFeatures && generateFeaturesSuccess && !fileChanged.equals(generatedFeaturesFile)) { - copyTempFeatureFileToServer(tempConfig); + copyGeneratedFeaturesFile(tempConfig); } installFeatures(fileChanged, tempConfig, generateFeatures); cleanUpTempConfig(); From 1c6fb1688b48d58023e4a5894fbbe26473fc9748 Mon Sep 17 00:00:00 2001 From: Paul Gooderham Date: Thu, 11 Dec 2025 14:53:29 -0500 Subject: [PATCH 23/46] Rename a field Signed-off-by: Paul Gooderham --- .../tools/common/plugins/util/DevUtil.java | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 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 16a204255..7523b7f2e 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 @@ -439,7 +439,7 @@ private enum FileTrackMode { private Set compileArtifactPaths; private Set testArtifactPaths; protected File generatedFeaturesFile; - protected File generatedFeaturesFileParent; + protected File generatedFeaturesContextDir; protected File generatedFeaturesTmpDir; private File modifiedSrcBuildFile; @@ -572,12 +572,12 @@ public DevUtil(File buildDirectory, File serverDirectory, File sourceDirectory, } private void initGenerationContext() { - this.generatedFeaturesFileParent = generateToSrc ? configDirectory : generatedFeaturesTmpDir; - this.generatedFeaturesFile = new File(generatedFeaturesFileParent, BinaryScannerUtil.GENERATED_FEATURES_FILE_PATH); + this.generatedFeaturesContextDir = generateToSrc ? configDirectory : generatedFeaturesTmpDir; + this.generatedFeaturesFile = new File(generatedFeaturesContextDir, BinaryScannerUtil.GENERATED_FEATURES_FILE_PATH); } public void copyGeneratedFeaturesFile(File destinationDir) throws IOException { - copyFile(generatedFeaturesFile, generatedFeaturesFileParent, destinationDir, null); + copyFile(generatedFeaturesFile, generatedFeaturesContextDir, destinationDir, null); } /** * Run unit and/or integration tests @@ -3026,7 +3026,7 @@ public void watchFiles(File outputDirectory, File testOutputDirectory, final Thr registerSingleFile(containerfileUsed, executor); } - // Always register this hidden temp directory because generateToSrc can be turned on in dev mode + // Always register the generated file in the temp dir. because generateToSrc can be toggled on and off in dev mode File hiddenTempGenerateFeaturesFile = new File(generatedFeaturesTmpDir, BinaryScannerUtil.GENERATED_FEATURES_FILE_PATH); hiddenTempGenerateFeaturesFile.getParentFile().mkdirs(); // must only mkdir on the directories registerSingleFile(hiddenTempGenerateFeaturesFile, executor); @@ -4582,7 +4582,7 @@ private void processConfigFileChange(File fileChanged, ChangeType changeType, Th String targetFileName = configuredServerXml ? "server.xml" : null; // if null file will retain the same name when copied // three possible values for the parent directory File fileChangedParentDir = configuredServerXml ? serverXmlFileParent : - isGeneratedFeaturesFile ? generatedFeaturesFileParent : configDirectory; + isGeneratedFeaturesFile ? generatedFeaturesContextDir : configDirectory; if (fileChanged.exists() && (changeType == ChangeType.MODIFY || changeType == ChangeType.CREATE)) { debug("Config file modified: " + fileChanged); @@ -4845,7 +4845,7 @@ public boolean accept(File pathname) { return !skip; } }, true); - File parentDir = fileChanged.equals(generatedFeaturesFile) ? generatedFeaturesFileParent : srcDir; + File parentDir = fileChanged.equals(generatedFeaturesFile) ? generatedFeaturesContextDir : srcDir; copyFile(fileChanged, parentDir, tempConfig, targetFileName); if (generateFeatures && generateFeaturesSuccess && !fileChanged.equals(generatedFeaturesFile)) { copyGeneratedFeaturesFile(tempConfig); @@ -5890,7 +5890,8 @@ private boolean serverFeaturesModified() { if (generateFeatures) { // generateFeatures scenario: check if a generated feature has been manually added to other config files // Here we pass generated-features.xml instead of server.xml to calculate the generated ones - // The second parameter will only be used if generated-features.xml contains element. + // The second parameter will only be used if generated-features.xml contains an element and + // the current design does not allow this. FeaturesPlatforms fp = servUtil.getServerXmlFeatures(new FeaturesPlatforms(), serverDirectory, generatedFeaturesFile, null, null); if (fp != null) From a42ffc85d7ebb46987f5c89f66f80785392ac8ee Mon Sep 17 00:00:00 2001 From: Paul Gooderham Date: Wed, 17 Dec 2025 17:26:45 -0500 Subject: [PATCH 24/46] Rename generate features fields Signed-off-by: Paul Gooderham --- .../tools/common/plugins/util/DevUtil.java | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 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 7523b7f2e..0aac91ac9 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 @@ -438,9 +438,9 @@ private enum FileTrackMode { private boolean generatedFeaturesModified; private Set compileArtifactPaths; private Set testArtifactPaths; - protected File generatedFeaturesFile; - protected File generatedFeaturesContextDir; - protected File generatedFeaturesTmpDir; + protected File generateFeaturesFile; // the file that is created from the generate-features goal/task + 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 skipInstallFeature; @@ -557,7 +557,7 @@ public DevUtil(File buildDirectory, File serverDirectory, File sourceDirectory, } this.generateFeatures = generateFeatures; this.generateToSrc = generateToSrc; - this.generatedFeaturesTmpDir = new File(buildDirectory, BinaryScannerUtil.GENERATED_FEATURES_TEMP_DIR); + this.generateFeaturesTmpDir = new File(buildDirectory, BinaryScannerUtil.GENERATED_FEATURES_TEMP_DIR); initGenerationContext(); this.compileArtifactPaths = compileArtifactPaths; this.testArtifactPaths = testArtifactPaths; @@ -572,12 +572,12 @@ public DevUtil(File buildDirectory, File serverDirectory, File sourceDirectory, } private void initGenerationContext() { - this.generatedFeaturesContextDir = generateToSrc ? configDirectory : generatedFeaturesTmpDir; - this.generatedFeaturesFile = new File(generatedFeaturesContextDir, BinaryScannerUtil.GENERATED_FEATURES_FILE_PATH); + this.generateFeaturesOutputDir = generateToSrc ? configDirectory : generateFeaturesTmpDir; + this.generateFeaturesFile = new File(generateFeaturesOutputDir, BinaryScannerUtil.GENERATED_FEATURES_FILE_PATH); } public void copyGeneratedFeaturesFile(File destinationDir) throws IOException { - copyFile(generatedFeaturesFile, generatedFeaturesContextDir, destinationDir, null); + copyFile(generateFeaturesFile, generateFeaturesOutputDir, destinationDir, null); } /** * Run unit and/or integration tests @@ -3027,7 +3027,7 @@ public void watchFiles(File outputDirectory, File testOutputDirectory, final Thr } // Always register the generated file in the temp dir. because generateToSrc can be toggled on and off in dev mode - File hiddenTempGenerateFeaturesFile = new File(generatedFeaturesTmpDir, BinaryScannerUtil.GENERATED_FEATURES_FILE_PATH); + File hiddenTempGenerateFeaturesFile = new File(generateFeaturesTmpDir, BinaryScannerUtil.GENERATED_FEATURES_FILE_PATH); hiddenTempGenerateFeaturesFile.getParentFile().mkdirs(); // must only mkdir on the directories registerSingleFile(hiddenTempGenerateFeaturesFile, executor); @@ -3134,10 +3134,10 @@ public void watchFiles(File outputDirectory, File testOutputDirectory, final Thr // reset lastChangeCompiled and modifiedSrcBuildFile lastChangeCompiled = false; // only needed when recompileDependencies is true modifiedSrcBuildFile = null; // only needed when recompileDependencies is true - long generatedTime = generatedFeaturesFile.lastModified(); + long generatedTime = generateFeaturesFile.lastModified(); int numApplicationUpdatedMessages = countApplicationUpdatedMessages(); incrementGenerateFeatures(!generateToSrc); - if (!generatedFeaturesFile.exists()) { + 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) if (isMultiModuleProject()) { @@ -3145,7 +3145,7 @@ public void watchFiles(File outputDirectory, File testOutputDirectory, final Thr } else { runTestThread(false, executor, -1, false, false, buildFile); } - } else if (generatedFeaturesFile.lastModified() == generatedTime) { + } else if (generateFeaturesFile.lastModified() == generatedTime) { // The generated-features.xml file was not modified by adding or removing features as a // result of the compilation so call tests now. If it had been changed tests would be called // after processing the config file change. @@ -4176,7 +4176,7 @@ private void processFileChanges( Path testSrcPath = this.testSourceDirectory.getCanonicalFile().toPath(); Path configPath = this.configDirectory.getCanonicalFile().toPath(); Path outputPath = this.outputDirectory.getCanonicalFile().toPath(); - Path gfTmpDirPath = this.generatedFeaturesTmpDir.getCanonicalFile().toPath(); + Path gfTmpDirPath = this.generateFeaturesTmpDir.getCanonicalFile().toPath(); Path directory = fileChanged.getParentFile().getCanonicalFile().toPath(); @@ -4211,7 +4211,7 @@ private void processFileChanges( if (it.hasNext()) { File newlyRegisteredFile = it.next(); // confirm that the newly registered file is generated features file in configDropins/overrides - if (newlyRegisteredFile.equals(generatedFeaturesFile)) { + if (newlyRegisteredFile.equals(generateFeaturesFile)) { // process file changes for the generated features file so that newly generated features are installed debug("Registered configDropins/overrides directory, processing file changes for generated features file: " + newlyRegisteredFile); @@ -4578,11 +4578,11 @@ private void processFileChanges( private void processConfigFileChange(File fileChanged, ChangeType changeType, ThreadPoolExecutor executor, int numApplicationUpdatedMessages, boolean configuredServerXml) throws IOException, PluginExecutionException { - boolean isGeneratedFeaturesFile = configuredServerXml ? false : fileChanged.equals(generatedFeaturesFile); + boolean isGeneratedFeaturesFile = configuredServerXml ? false : fileChanged.equals(generateFeaturesFile); String targetFileName = configuredServerXml ? "server.xml" : null; // if null file will retain the same name when copied // three possible values for the parent directory File fileChangedParentDir = configuredServerXml ? serverXmlFileParent : - isGeneratedFeaturesFile ? generatedFeaturesContextDir : configDirectory; + isGeneratedFeaturesFile ? generateFeaturesOutputDir : configDirectory; if (fileChanged.exists() && (changeType == ChangeType.MODIFY || changeType == ChangeType.CREATE)) { debug("Config file modified: " + fileChanged); @@ -4651,7 +4651,7 @@ private void processConfigFileChange(File fileChanged, ChangeType changeType, Th // generate features whenever features have changed and an XML file is deleted, // excluding the generated-features.xml file if (generateFeatures && (fileChanged.getName().endsWith(".xml") - && !fileChanged.equals(generatedFeaturesFile)) + && !fileChanged.equals(generateFeaturesFile)) && serverFeaturesModified()) { optimizeGenerateFeatures(!generateToSrc); } @@ -4845,9 +4845,9 @@ public boolean accept(File pathname) { return !skip; } }, true); - File parentDir = fileChanged.equals(generatedFeaturesFile) ? generatedFeaturesContextDir : srcDir; + File parentDir = fileChanged.equals(generateFeaturesFile) ? generateFeaturesOutputDir : srcDir; copyFile(fileChanged, parentDir, tempConfig, targetFileName); - if (generateFeatures && generateFeaturesSuccess && !fileChanged.equals(generatedFeaturesFile)) { + if (generateFeatures && generateFeaturesSuccess && !fileChanged.equals(generateFeaturesFile)) { copyGeneratedFeaturesFile(tempConfig); } installFeatures(fileChanged, tempConfig, generateFeatures); @@ -5893,13 +5893,13 @@ private boolean serverFeaturesModified() { // The second parameter will only be used if generated-features.xml contains an element and // the current design does not allow this. FeaturesPlatforms fp = servUtil.getServerXmlFeatures(new FeaturesPlatforms(), serverDirectory, - generatedFeaturesFile, null, null); + generateFeaturesFile, null, null); if (fp != null) generatedFeatureSet = fp.getFeatures(); // Calculate the features specified in the config excluding those in generated-features Set generatedFiles = new HashSet(); - generatedFiles.add(generatedFeaturesFile.getName()); + generatedFiles.add(generateFeaturesFile.getName()); // if serverXmlFile is null, getServerFeatures will use the default server.xml // in the configDirectory fp = servUtil.getServerFeatures(configDirectory, serverXmlFile, @@ -5944,7 +5944,7 @@ private boolean generatedFeaturesModified() { private Set getGeneratedFeatures() { ServerFeatureUtil servUtil = getServerFeatureUtilObj(); FeaturesPlatforms fp = servUtil.getServerXmlFeatures(new FeaturesPlatforms(), configDirectory, - generatedFeaturesFile, null, null); + generateFeaturesFile, null, null); return fp!=null ? fp.getFeatures() : new HashSet(); } From 5905974a7cfa11007173c80d6f5a24b9252ebdf1 Mon Sep 17 00:00:00 2001 From: Paul Gooderham Date: Wed, 21 Jan 2026 15:05:12 -0500 Subject: [PATCH 25/46] Copy config and changed file to temp dir for feature generation Signed-off-by: Paul Gooderham --- .../tools/common/plugins/util/DevUtil.java | 75 +++++++++++++------ .../common/plugins/util/BaseDevUtilTest.java | 2 +- 2 files changed, 54 insertions(+), 23 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 0aac91ac9..272b0885b 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 @@ -1,5 +1,5 @@ /** - * (C) Copyright IBM Corporation 2019, 2025. + * (C) Copyright IBM Corporation 2019, 2026. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -1942,10 +1942,12 @@ private void addUserId(List commandElements) { * * @param classes class file paths features should be generated for (can be null if no modified classes) * @param optimize if true, generate optimized feature list - * @param useTmpDir if true, generate feature file in a hidden directory named in BinaryScannerUtil + * @param useTmpDirOut if true, generate feature file in a hidden directory named in BinaryScannerUtil + * @param useTmpDirIn if true, the hidden directory named in BinaryScannerUtil will be used as the + * context or input values to generate features * @return true if feature generation was successful */ - public abstract boolean libertyGenerateFeatures(Collection classes, boolean optimize, boolean useTmpDir); + public abstract boolean libertyGenerateFeatures(Collection classes, boolean optimize, boolean useTmpDirOut, boolean useTmpDirIn); /** * Install features in regular dev mode. This method should not be used in container mode. @@ -2176,9 +2178,9 @@ public void cleanUpServerEnv() { } } - public void cleanUpTempConfig() { - if (this.tempConfigPath != null) { - File tempConfig = this.tempConfigPath.toFile(); + public void cleanUpTempConfig(Path myTempConfigPath) { + if (myTempConfigPath != null) { + File tempConfig = myTempConfigPath.toFile(); if (tempConfig.exists()) { try { FileUtils.deleteDirectory(tempConfig); @@ -2233,7 +2235,7 @@ private void runShutdownHook(final ThreadPoolExecutor executor) { } setDevStop(true); - cleanUpTempConfig(); + cleanUpTempConfig(this.tempConfigPath); cleanUpServerEnv(); if (hotkeyReader != null) { @@ -2707,13 +2709,18 @@ protected void setFeatureGeneration(boolean generateFeatures) { logFeatureGenerationStatus(); } + private boolean optimizeGenerateFeatures(boolean useTmpDir) { + debug("Generating optimized features list...use temp directory (for output)=" + useTmpDir); + return optimizeGenerateFeatures(useTmpDir, false); + } + /** * Generate features using all classes and only user specified features. */ - private boolean optimizeGenerateFeatures(boolean useTmpDir) { - debug("Generating optimized features list...use temp directory=" + useTmpDir); + private boolean optimizeGenerateFeatures(boolean useTmpDirOut, boolean useTmpDirIn) { + debug("Generating optimized features list...use temp directory for output=" + useTmpDirOut + " use temp directory for input=" + useTmpDirIn); // scan all class files and provide only user specified features - boolean generatedFeatures = libertyGenerateFeatures(null, true, useTmpDir); + boolean generatedFeatures = libertyGenerateFeatures(null, true, useTmpDirOut, useTmpDirIn); if (generatedFeatures) { modifiedClasses.clear(); failedToGenerateClasses.clear(); @@ -2732,7 +2739,7 @@ private boolean incrementGenerateFeatures(boolean useTmpDir) { boolean generatedFeatures = false; try { Collection classPaths = getClassPaths(modifiedClasses); - generatedFeatures = libertyGenerateFeatures(classPaths, false, useTmpDir); + generatedFeatures = libertyGenerateFeatures(classPaths, false, useTmpDir, false); if (generatedFeatures) { modifiedClasses.clear(); failedToGenerateClasses.clear(); @@ -4595,7 +4602,17 @@ private void processConfigFileChange(File fileChanged, ChangeType changeType, Th if (generateFeatures && (fileChanged.getName().endsWith(".xml") && !isGeneratedFeaturesFile) && serverFeaturesModified) { - generateFeaturesSuccess = optimizeGenerateFeatures(!generateToSrc); + // If generating to server dir we use the server dir config files and also the + // modified xml file in src dir. Copy them all to the gen. feat. temp dir to + // combine them for feature generation. + if (!generateToSrc) { + cleanUpTempConfig(generateFeaturesTmpDir.toPath()); + // copy config files to temp dir + copyToTempDir(serverDirectory, generateFeaturesTmpDir); + // copy changed file to temp dir + copyFile(fileChanged, fileChangedParentDir, generateFeaturesTmpDir, targetFileName); + } + generateFeaturesSuccess = optimizeGenerateFeatures(!generateToSrc, !generateToSrc); } if (serverFeaturesModified) { // suppress install feature warning - property must be set before installing using temp dir @@ -4831,27 +4848,41 @@ public void installFeaturesToTempDir(File fileChanged, File srcDir, String targe File tempConfig = tempConfigPath.toFile(); debug("Temporary configuration folder created: " + tempConfig); - FileUtils.copyDirectory(serverDirectory, tempConfig, new FileFilter() { + copyToTempDir(serverDirectory, tempConfig); + File parentDir = fileChanged.equals(generateFeaturesFile) ? generateFeaturesOutputDir : srcDir; + copyFile(fileChanged, parentDir, tempConfig, targetFileName); + if (generateFeatures && generateFeaturesSuccess && !fileChanged.equals(generateFeaturesFile)) { + copyGeneratedFeaturesFile(tempConfig); + } + installFeatures(fileChanged, tempConfig, generateFeatures); + cleanUpTempConfig(this.tempConfigPath); + } + + /** + * Copy the liberty config in the sourceDir directory into the supplied temp directory. + * Filter out certain directories used in Liberty configuration: workarea, logs, messaging + * and also the files dev mode usually ignores e.g. .dir, .file, xxx.dmp etc + * + * @param sourceDir copy files from this directory + * @param tempDir target directory to which files are copied + */ + public File copyToTempDir(File sourceDir, File tempConfig) throws IOException { + FileUtils.copyDirectory(sourceDir, tempConfig, new FileFilter() { public boolean accept(File pathname) { String name = pathname.getName(); String parent = pathname.getParentFile().getName(); - String serverDirName = serverDirectory.getName(); + String sourceDirName = sourceDir.getName(); // skip: // - ignore list // - workarea, messaging, and logs dirs from the server directory, since those can be // changing boolean skip = ignoreFileOrDir(pathname) || (pathname.isDirectory() && - (name.equals("workarea") || name.equals("logs") || (name.equals("messaging") && parent.equals(serverDirName)))); + (name.equals("workarea") || name.equals("logs") || (name.equals("messaging") && parent.equals(sourceDirName)))); return !skip; } }, true); - File parentDir = fileChanged.equals(generateFeaturesFile) ? generateFeaturesOutputDir : srcDir; - copyFile(fileChanged, parentDir, tempConfig, targetFileName); - if (generateFeatures && generateFeaturesSuccess && !fileChanged.equals(generateFeaturesFile)) { - copyGeneratedFeaturesFile(tempConfig); - } - installFeatures(fileChanged, tempConfig, generateFeatures); - cleanUpTempConfig(); + + return tempConfig; } /** diff --git a/src/test/java/io/openliberty/tools/common/plugins/util/BaseDevUtilTest.java b/src/test/java/io/openliberty/tools/common/plugins/util/BaseDevUtilTest.java index b2f3a3395..d2c246944 100644 --- a/src/test/java/io/openliberty/tools/common/plugins/util/BaseDevUtilTest.java +++ b/src/test/java/io/openliberty/tools/common/plugins/util/BaseDevUtilTest.java @@ -197,7 +197,7 @@ public void libertyInstallFeature() { } @Override - public boolean libertyGenerateFeatures(Collection classes, boolean optimize, boolean useTemp) { + public boolean libertyGenerateFeatures(Collection classes, boolean optimize, boolean useTempOut, boolean useTempIn) { // not needed for tests return true; } From 2fd65f3a735093582bd0c5ccabdb0a8a8d62a9f2 Mon Sep 17 00:00:00 2001 From: Paul Gooderham Date: Mon, 2 Feb 2026 19:46:26 -0500 Subject: [PATCH 26/46] Code review updates: remove unneccessary directory and update comments Signed-off-by: Paul Gooderham --- .../tools/common/plugins/util/DevUtil.java | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 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 272b0885b..bd2fd81aa 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 @@ -2184,7 +2184,7 @@ public void cleanUpTempConfig(Path myTempConfigPath) { if (tempConfig.exists()) { try { FileUtils.deleteDirectory(tempConfig); - debug("Successfully deleted liberty:dev temporary configuration folder"); + debug("Successfully deleted liberty:dev temporary configuration folder: " + myTempConfigPath); } catch (IOException e) { warn("Could not delete liberty:dev temporary configuration folder: " + e.getMessage()); } @@ -2710,7 +2710,7 @@ protected void setFeatureGeneration(boolean generateFeatures) { } private boolean optimizeGenerateFeatures(boolean useTmpDir) { - debug("Generating optimized features list...use temp directory (for output)=" + useTmpDir); + debug("Entering optimizeGenerateFeatures(boolean)"); return optimizeGenerateFeatures(useTmpDir, false); } @@ -4606,6 +4606,9 @@ private void processConfigFileChange(File fileChanged, ChangeType changeType, Th // modified xml file in src dir. Copy them all to the gen. feat. temp dir to // combine them for feature generation. if (!generateToSrc) { + // Deleting generateFeaturesTmpDir also deletes generateFeaturesFile which we "watch" in + // dev mode. This causes a deletion event and we are counting on the handler (this method, + // below) not to call generate features and recreate the file. cleanUpTempConfig(generateFeaturesTmpDir.toPath()); // copy config files to temp dir copyToTempDir(serverDirectory, generateFeaturesTmpDir); @@ -4666,7 +4669,10 @@ private void processConfigFileChange(File fileChanged, ChangeType changeType, Th info("Config file deleted: " + fileChanged.getName()); deleteFile(fileChanged, fileChangedParentDir, serverDirectory, targetFileName); // generate features whenever features have changed and an XML file is deleted, - // excluding the generated-features.xml file + // excluding the generated-features.xml file. This is important also when we delete the + // generateFeaturesTmpDir in the process of handling an xml config modicifcation. + // Deleting that directory could cause generated-features.xml to be deleted and we + // need to be careful how to handle that event e.g. don't call optimizeGenerateFeatures(). if (generateFeatures && (fileChanged.getName().endsWith(".xml") && !fileChanged.equals(generateFeaturesFile)) && serverFeaturesModified()) { @@ -4849,8 +4855,7 @@ public void installFeaturesToTempDir(File fileChanged, File srcDir, String targe debug("Temporary configuration folder created: " + tempConfig); copyToTempDir(serverDirectory, tempConfig); - File parentDir = fileChanged.equals(generateFeaturesFile) ? generateFeaturesOutputDir : srcDir; - copyFile(fileChanged, parentDir, tempConfig, targetFileName); + copyFile(fileChanged, srcDir, tempConfig, targetFileName); if (generateFeatures && generateFeaturesSuccess && !fileChanged.equals(generateFeaturesFile)) { copyGeneratedFeaturesFile(tempConfig); } From 877e147aa18d045e5979ae0becd52b7ed1b5a766 Mon Sep 17 00:00:00 2001 From: Paul Gooderham Date: Wed, 28 Jan 2026 17:24:58 -0500 Subject: [PATCH 27/46] Pass the generateToSrc option when generating features Signed-off-by: Paul Gooderham --- .../tools/common/plugins/util/DevUtil.java | 31 ++++++++++++------- .../common/plugins/util/BaseDevUtilTest.java | 2 +- 2 files changed, 21 insertions(+), 12 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 bd2fd81aa..31ef9ff40 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 @@ -1942,12 +1942,13 @@ private void addUserId(List commandElements) { * * @param classes class file paths features should be generated for (can be null if no modified classes) * @param optimize if true, generate optimized feature list + * @param generateToSrc if true, generate feature list into file in src/main/liberty * @param useTmpDirOut if true, generate feature file in a hidden directory named in BinaryScannerUtil * @param useTmpDirIn if true, the hidden directory named in BinaryScannerUtil will be used as the * context or input values to generate features * @return true if feature generation was successful */ - public abstract boolean libertyGenerateFeatures(Collection classes, boolean optimize, boolean useTmpDirOut, boolean useTmpDirIn); + public abstract boolean libertyGenerateFeatures(Collection classes, boolean optimize, boolean generateToSrc, boolean useTmpDirOut, boolean useTmpDirIn); /** * Install features in regular dev mode. This method should not be used in container mode. @@ -2672,13 +2673,11 @@ private void toggleGenerateToSrc() { generateToSrc = !generateToSrc; logGenerateToSrcStatus(); initGenerationContext(); - if (!generateToSrc) { // when you toggle off generate to src, delete the generated file in src - File srcGenFeaturesFile = new File(configDirectory, BinaryScannerUtil.GENERATED_FEATURES_FILE_PATH); - if (srcGenFeaturesFile.exists()) { - if (!srcGenFeaturesFile.delete()) { - debug("Error trying to delete the generated features file:" + srcGenFeaturesFile.getAbsolutePath()); - } - } + // When you toggle generateToSrc delete the old file you no longer need + if (generateToSrc) { + deleteGenFeaturesFile(generateFeaturesTmpDir); // delete the old gen file in tmpdir + } else { + deleteGenFeaturesFile(configDirectory); // delete the old gen file in src/main/liberty/config } if (generateFeatures) { if (generateToSrc) { @@ -2689,6 +2688,16 @@ private void toggleGenerateToSrc() { } } + private void deleteGenFeaturesFile(File dir) { + // processConfigFileChange() ignores deletion of generated features file + File srcGenFeaturesFile = new File(dir, BinaryScannerUtil.GENERATED_FEATURES_FILE_PATH); + if (srcGenFeaturesFile.exists()) { + if (!srcGenFeaturesFile.delete()) { + debug("Error trying to delete the generated features file:" + srcGenFeaturesFile.getAbsolutePath()); + } + } + } + private void logGenerateToSrcStatus() { info("Setting generation of features in src directory to: " + getFormattedBooleanString(generateToSrc)); } @@ -2720,7 +2729,7 @@ private boolean optimizeGenerateFeatures(boolean useTmpDir) { private boolean optimizeGenerateFeatures(boolean useTmpDirOut, boolean useTmpDirIn) { debug("Generating optimized features list...use temp directory for output=" + useTmpDirOut + " use temp directory for input=" + useTmpDirIn); // scan all class files and provide only user specified features - boolean generatedFeatures = libertyGenerateFeatures(null, true, useTmpDirOut, useTmpDirIn); + boolean generatedFeatures = libertyGenerateFeatures(null, true, generateToSrc, useTmpDirOut, useTmpDirIn); if (generatedFeatures) { modifiedClasses.clear(); failedToGenerateClasses.clear(); @@ -2739,7 +2748,7 @@ private boolean incrementGenerateFeatures(boolean useTmpDir) { boolean generatedFeatures = false; try { Collection classPaths = getClassPaths(modifiedClasses); - generatedFeatures = libertyGenerateFeatures(classPaths, false, useTmpDir, false); + generatedFeatures = libertyGenerateFeatures(classPaths, false, generateToSrc, useTmpDir, false); if (generatedFeatures) { modifiedClasses.clear(); failedToGenerateClasses.clear(); @@ -4674,7 +4683,7 @@ private void processConfigFileChange(File fileChanged, ChangeType changeType, Th // Deleting that directory could cause generated-features.xml to be deleted and we // need to be careful how to handle that event e.g. don't call optimizeGenerateFeatures(). if (generateFeatures && (fileChanged.getName().endsWith(".xml") - && !fileChanged.equals(generateFeaturesFile)) + && !fileChanged.getName().equals(generateFeaturesFile.getName())) && serverFeaturesModified()) { optimizeGenerateFeatures(!generateToSrc); } diff --git a/src/test/java/io/openliberty/tools/common/plugins/util/BaseDevUtilTest.java b/src/test/java/io/openliberty/tools/common/plugins/util/BaseDevUtilTest.java index d2c246944..3a546b9c2 100644 --- a/src/test/java/io/openliberty/tools/common/plugins/util/BaseDevUtilTest.java +++ b/src/test/java/io/openliberty/tools/common/plugins/util/BaseDevUtilTest.java @@ -197,7 +197,7 @@ public void libertyInstallFeature() { } @Override - public boolean libertyGenerateFeatures(Collection classes, boolean optimize, boolean useTempOut, boolean useTempIn) { + public boolean libertyGenerateFeatures(Collection classes, boolean optimize, boolean generateToSrc, boolean useTempOut, boolean useTempIn) { // not needed for tests return true; } From 2e5931780190e611df99c12e1bc5c7f3978725ca Mon Sep 17 00:00:00 2001 From: Paul Gooderham Date: Wed, 4 Feb 2026 17:49:55 -0500 Subject: [PATCH 28/46] Code review: rename variable and update comment Signed-off-by: Paul Gooderham --- .../tools/common/plugins/util/DevUtil.java | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 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 31ef9ff40..e483f0fbe 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 @@ -1,5 +1,5 @@ /** - * (C) Copyright IBM Corporation 2019, 2026. + * (C) Copyright IBM Corporation 2019, 2026 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -2689,11 +2689,11 @@ private void toggleGenerateToSrc() { } private void deleteGenFeaturesFile(File dir) { - // processConfigFileChange() ignores deletion of generated features file - File srcGenFeaturesFile = new File(dir, BinaryScannerUtil.GENERATED_FEATURES_FILE_PATH); - if (srcGenFeaturesFile.exists()) { - if (!srcGenFeaturesFile.delete()) { - debug("Error trying to delete the generated features file:" + srcGenFeaturesFile.getAbsolutePath()); + // N.B. processConfigFileChange() will be called upon deletion of generated features file, it should be ignored + File oldGenFeaturesFile = new File(dir, BinaryScannerUtil.GENERATED_FEATURES_FILE_PATH); + if (oldGenFeaturesFile.exists()) { + if (!oldGenFeaturesFile.delete()) { + debug("Error trying to delete the generated features file:" + oldGenFeaturesFile.getAbsolutePath()); } } } @@ -4682,6 +4682,9 @@ private void processConfigFileChange(File fileChanged, ChangeType changeType, Th // generateFeaturesTmpDir in the process of handling an xml config modicifcation. // Deleting that directory could cause generated-features.xml to be deleted and we // need to be careful how to handle that event e.g. don't call optimizeGenerateFeatures(). + // Another scenario, when we toggle generateToSrc option we delete the old file and change the + // value of generateFeaturesFile. Therefore we must only use the base name of generateFeaturesFile + // in this file name check. if (generateFeatures && (fileChanged.getName().endsWith(".xml") && !fileChanged.getName().equals(generateFeaturesFile.getName())) && serverFeaturesModified()) { From 1eb9ff4b04076aa503d52be6ed473ba17ed6b454 Mon Sep 17 00:00:00 2001 From: Paul Gooderham Date: Tue, 10 Feb 2026 18:09:42 -0500 Subject: [PATCH 29/46] Make optimizeGenerateFeatures() protected Signed-off-by: Paul Gooderham --- .../io/openliberty/tools/common/plugins/util/DevUtil.java | 4 ++-- 1 file changed, 2 insertions(+), 2 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 3d24511de..380cd2993 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 @@ -2755,7 +2755,7 @@ protected void setFeatureGeneration(boolean generateFeatures) { logFeatureGenerationStatus(); } - private boolean optimizeGenerateFeatures(boolean useTmpDir) { + protected boolean optimizeGenerateFeatures(boolean useTmpDir) { debug("Entering optimizeGenerateFeatures(boolean)"); return optimizeGenerateFeatures(useTmpDir, false); } @@ -2763,7 +2763,7 @@ private boolean optimizeGenerateFeatures(boolean useTmpDir) { /** * Generate features using all classes and only user specified features. */ - private boolean optimizeGenerateFeatures(boolean useTmpDirOut, boolean useTmpDirIn) { + protected boolean optimizeGenerateFeatures(boolean useTmpDirOut, boolean useTmpDirIn) { debug("Generating optimized features list...use temp directory for output=" + useTmpDirOut + " use temp directory for input=" + useTmpDirIn); // scan all class files and provide only user specified features boolean generatedFeatures = libertyGenerateFeatures(null, true, generateToSrc, useTmpDirOut, useTmpDirIn); From 7b2bf2f8bfcab6ba2180093b7df2310d56b9024e Mon Sep 17 00:00:00 2001 From: Paul Gooderham Date: Wed, 11 Feb 2026 17:50:04 -0500 Subject: [PATCH 30/46] Remove unused method getJavaSourceClassPaths() Signed-off-by: Paul Gooderham --- .../openliberty/tools/common/plugins/util/DevUtil.java | 9 --------- 1 file changed, 9 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 380cd2993..cc62370dd 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 @@ -3445,15 +3445,6 @@ public void watchFiles(File outputDirectory, File testOutputDirectory, final Thr } } - /** - * - * @return {@code Collection} of class paths - * @throws IOException - */ - public Collection getJavaSourceClassPaths() throws IOException { - return getClassPaths(modifiedClasses); - } - /** * * @param classFiles javaSourceClassFiles that have been modified From 79a1d1604df23aea3c1eb4ba6d5baa83e648e5af Mon Sep 17 00:00:00 2001 From: Paul Gooderham Date: Wed, 18 Feb 2026 18:58:03 -0500 Subject: [PATCH 31/46] Track when generate-features file is copied early to prevent copying twice Signed-off-by: Paul Gooderham --- .../tools/common/plugins/util/DevUtil.java | 31 ++++++++++++++----- 1 file changed, 24 insertions(+), 7 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 cc62370dd..67c185292 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 @@ -438,6 +438,7 @@ private enum FileTrackMode { private boolean generateToSrc; private Set generatedFeaturesSet; // set of features in generated-features.xml file private boolean generatedFeaturesModified; + private boolean generatedFeaturesCopied; private Set compileArtifactPaths; private Set testArtifactPaths; protected File generateFeaturesFile; // the file that is created from the generate-features goal/task @@ -565,6 +566,7 @@ public DevUtil(File buildDirectory, File serverDirectory, File sourceDirectory, this.testArtifactPaths = testArtifactPaths; this.monitoredWebResourceDirs = monitoredWebResourceDirs; this.generatedFeaturesModified = false; + this.generatedFeaturesCopied = false; if (this.generateFeatures) { this.generatedFeaturesSet = getGeneratedFeatures(); } else { @@ -580,6 +582,9 @@ private void initGenerationContext() { public void copyGeneratedFeaturesFile(File destinationDir) throws IOException { copyFile(generateFeaturesFile, generateFeaturesOutputDir, destinationDir, null); + if (destinationDir.equals(serverDirectory)) { + generatedFeaturesCopied = true; // features copied into server dir and not some temp dir + } } /** * Run unit and/or integration tests @@ -4673,14 +4678,26 @@ private void processConfigFileChange(File fileChanged, ChangeType changeType, Th System.setProperty(SKIP_BETA_INSTALL_WARNING, Boolean.TRUE.toString()); installFeaturesToTempDir(fileChanged, fileChangedParentDir, targetFileName, generateFeaturesSuccess); } - copyFile(fileChanged, fileChangedParentDir, serverDirectory, targetFileName); - - // if the generated features file was modified as a result of another config - // file modification, copy it over to target so the server picks up the changes - // together + // Copy the config file which was changed to the server directory unless it is + // the generated features file. The generated features file may have been copied + // to the server directory already as a result of a change to the build file (pom.xml) + // or the server.xml. + if (!isGeneratedFeaturesFile) { // all other config files + copyFile(fileChanged, fileChangedParentDir, serverDirectory, targetFileName); + } else { + if (!generatedFeaturesCopied) { + copyGeneratedFeaturesFile(serverDirectory); + } + generatedFeaturesCopied = false; + } + // If the generated features file was modified as a result of another config file modification + // (usually server.xml), copy it over to target so the server picks up the two changes together if (generateFeaturesSuccess && generatedFeaturesModified && !isGeneratedFeaturesFile) { - // this logic is not entered if the fileChanged is the generated features file - // copy generated features file to server dir + // This logic is not entered if the fileChanged is the generated features file. + // Copy generated features file to server dir and set generatedFeaturesCopied true + // (not referring to generatedFeaturesModified). + // Leave generatedFeaturesCopied true because the call to optimize/incrementalGenerateFeatures that was + // required to get into this IF block will also generate a file change event for generatedFeaturesFile copyGeneratedFeaturesFile(serverDirectory); generatedFeaturesModified = false; } From bde6685a189c4de8f31d56b4587907aaa23c0314 Mon Sep 17 00:00:00 2001 From: Paul Gooderham Date: Thu, 2 Apr 2026 15:34:04 -0400 Subject: [PATCH 32/46] Add coordinates for feature files Signed-off-by: Paul Gooderham --- .../plugins/util/BinaryScannerUtil.java | 50 ++++++++++++++++--- 1 file changed, 42 insertions(+), 8 deletions(-) diff --git a/src/main/java/io/openliberty/tools/common/plugins/util/BinaryScannerUtil.java b/src/main/java/io/openliberty/tools/common/plugins/util/BinaryScannerUtil.java index 9bc44c34a..a4c4f1adb 100644 --- a/src/main/java/io/openliberty/tools/common/plugins/util/BinaryScannerUtil.java +++ b/src/main/java/io/openliberty/tools/common/plugins/util/BinaryScannerUtil.java @@ -1,5 +1,5 @@ /** - * (C) Copyright IBM Corporation 2021, 2025. + * (C) Copyright IBM Corporation 2021, 2026 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,6 +32,36 @@ public abstract class BinaryScannerUtil { public static final String BINARY_SCANNER_MAVEN_TYPE = "jar"; public static final String BINARY_SCANNER_MAVEN_VERSION = "[25.0.0.2]"; + // The coordinates to use for Open Liberty versions 25.0.0.7 and up + public static final String OLBASE_FEATURELIST_GROUP_ID = "io.openliberty.features"; + public static final String OLBASE_FEATURELIST_ARTIFACT_ID = "open_liberty_featurelist"; + public static final String OLBASE_FEATURELIST_TYPE = "compile"; + public static final String OLBASE_FEATURELIST_VERSION = "[25.0.0.7)"; + + // The coordinates to use for WebSphere Liberty versions 25.0.0.7 to 25.0.0.9 + public static final String WSBASE1_FEATURELIST_GROUP_ID = "io.openliberty.features"; + public static final String WSBASE1_FEATURELIST_ARTIFACT_ID = "websphere_liberty_base__featurelist"; + public static final String WSBASE1_FEATURELIST_TYPE = "compile"; + public static final String WSBASE1_FEATURELIST_VERSION = "[25.0.0.7]"; + + // The coordinates to use for WebSphere Liberty versions 25.0.0.10 and up + public static final String WSBASE2_FEATURELIST_GROUP_ID = "com.ibm.websphere.appserver.features"; + public static final String WSBASE2_FEATURELIST_ARTIFACT_ID = "websphere_liberty_base__featurelist"; + public static final String WSBASE2_FEATURELIST_TYPE = "compile"; + public static final String WSBASE2_FEATURELIST_VERSION = "[25.0.0.10)"; + + // The coordinates to use for WebSphere Liberty versions 25.0.0.7 to 25.0.0.9 + public static final String WSCORE1_FEATURELIST_GROUP_ID = "io.openliberty.features"; + public static final String WSCORE1_FEATURELIST_ARTIFACT_ID = "websphere_liberty_core__featurelist"; + public static final String WSCORE1_FEATURELIST_TYPE = "compile"; + public static final String WSCORE1_FEATURELIST_VERSION = "[25.0.0.7]"; + + // The coordinates to use for WebSphere Liberty versions 25.0.0.10 and up + public static final String WSCORE2_FEATURELIST_GROUP_ID = "com.ibm.websphere.appserver.features"; + public static final String WSCORE2_FEATURELIST_ARTIFACT_ID = "websphere_liberty_core__featurelist"; + public static final String WSCORE2_FEATURELIST_TYPE = "compile"; + public static final String WSCORE2_FEATURELIST_VERSION = "[25.0.0.10)"; + public static final String GENERATED_FEATURES_FILE_NAME = "generated-features.xml"; public static final String GENERATED_FEATURES_DIR_PATH = "configDropins/overrides/"; public static final String GENERATED_FEATURES_FILE_PATH = GENERATED_FEATURES_DIR_PATH + GENERATED_FEATURES_FILE_NAME; @@ -82,13 +112,13 @@ public abstract class BinaryScannerUtil { public abstract boolean isDebugEnabled(); // The jar file containing the binary scanner code - private File binaryScanner; + private File binaryScannerJar; private URLClassLoader binaryScannerClassLoader = null; private Class binaryScannerClass = null; private Method binaryScannerMethod = null; public BinaryScannerUtil(File binaryScanner) { - this.binaryScanner = binaryScanner; + this.binaryScannerJar = binaryScanner; } /** @@ -125,7 +155,7 @@ public Set runBinaryScanner(Set currentFeatureSet, List throws PluginExecutionException, NoRecommendationException, RecommendationSetException, FeatureModifiedException, FeatureUnavailableException, IllegalTargetException, IllegalTargetComboException { Set featureList = null; - if (binaryScanner != null && binaryScanner.exists()) { + if (binaryScannerJar != null && binaryScannerJar.exists()) { // if we are already generating features for all class files (optimize=true) and // we are not passing any user specified features (currentFeatureSet is empty) // we do not need to rerun the binary scanner if it fails @@ -141,7 +171,7 @@ public Set runBinaryScanner(Set currentFeatureSet, List logLevel = null; logLocation = null; } - debug("Calling " + binaryScanner.getName() + " with the following inputs...\n" + + debug("Calling " + binaryScannerJar.getName() + " with the following inputs...\n" + " binaryInputs: " + binaryInputs + "\n" + " targetJavaEE: " + targetJavaEE + "\n" + " targetMicroP: " + targetMicroProfile + "\n" + @@ -232,10 +262,10 @@ public Set runBinaryScanner(Set currentFeatureSet, List throw new PluginExecutionException("An error occurred when trying to call the binary scanner jar: " + loadingException.toString()); } } else { - if (binaryScanner == null) { + if (binaryScannerJar == null) { throw new PluginExecutionException("The binary scanner jar location is not defined."); } else { - throw new PluginExecutionException("Could not find the binary scanner jar at " + binaryScanner.getAbsolutePath()); + throw new PluginExecutionException("Could not find the binary scanner jar at " + binaryScannerJar.getAbsolutePath()); } } return featureList; @@ -317,7 +347,7 @@ private Set getNoSampleFeatureList() { private ClassLoader getScannerClassLoader() throws MalformedURLException { if (binaryScannerClassLoader == null) { ClassLoader cl = this.getClass().getClassLoader(); - binaryScannerClassLoader = new URLClassLoader(new URL[] { binaryScanner.toURI().toURL() }, cl); + binaryScannerClassLoader = new URLClassLoader(new URL[] { binaryScannerJar.toURI().toURL() }, cl); } return binaryScannerClassLoader; } @@ -335,6 +365,10 @@ private Method getScannerMethod() throws MalformedURLException, ClassNotFoundExc Class driveScan = getScannerClass(); // args: Set, String, String, Set, String, String, Locale // names: binaryInputs, targetJavaEE, targetMicroProfile, currentFeatures, logLocation, logLevel, locale + // Set generateFeatureList() + // Set, String, String, Set, File, File, String, String, Locale + // binaryInputs, targetJavaEE, targetMicroProfile, currentFeatures, baseFeatureListFile, coreFeatureListFile, logLocation, logLevel, locale + binaryScannerMethod = driveScan.getMethod("generateFeatureList", Set.class, String.class, String.class, Set.class, String.class, String.class, java.util.Locale.class); if (binaryScannerMethod == null) { From e8eba653faca103816bb574880b7840d6a03f516 Mon Sep 17 00:00:00 2001 From: Paul Gooderham Date: Thu, 2 Apr 2026 19:09:14 -0400 Subject: [PATCH 33/46] Add feature list file parameters to binary scanner Signed-off-by: Paul Gooderham --- .../plugins/util/BinaryScannerUtil.java | 41 ++++++++++++------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/src/main/java/io/openliberty/tools/common/plugins/util/BinaryScannerUtil.java b/src/main/java/io/openliberty/tools/common/plugins/util/BinaryScannerUtil.java index a4c4f1adb..b5db2342d 100644 --- a/src/main/java/io/openliberty/tools/common/plugins/util/BinaryScannerUtil.java +++ b/src/main/java/io/openliberty/tools/common/plugins/util/BinaryScannerUtil.java @@ -30,7 +30,7 @@ public abstract class BinaryScannerUtil { public static final String BINARY_SCANNER_MAVEN_GROUP_ID = "com.ibm.websphere.appmod.tools"; public static final String BINARY_SCANNER_MAVEN_ARTIFACT_ID = "binary-app-scanner"; public static final String BINARY_SCANNER_MAVEN_TYPE = "jar"; - public static final String BINARY_SCANNER_MAVEN_VERSION = "[25.0.0.2]"; + public static final String BINARY_SCANNER_MAVEN_VERSION = "[25.0.0.2.1]"; // The coordinates to use for Open Liberty versions 25.0.0.7 and up public static final String OLBASE_FEATURELIST_GROUP_ID = "io.openliberty.features"; @@ -151,7 +151,7 @@ public BinaryScannerUtil(File binaryScanner) { * scanner when used in combination with each other. E.g. EE 7 and MP 2.1 */ public Set runBinaryScanner(Set currentFeatureSet, List classFiles, Set allClassesDirectories, - String logLocation, String targetJavaEE, String targetMicroProfile, boolean optimize) + String logLocation, String targetJavaEE, String targetMicroProfile, File baseFeatureListFile, File coreFeatureListFile, boolean optimize) throws PluginExecutionException, NoRecommendationException, RecommendationSetException, FeatureModifiedException, FeatureUnavailableException, IllegalTargetException, IllegalTargetComboException { Set featureList = null; @@ -176,11 +176,24 @@ public Set runBinaryScanner(Set currentFeatureSet, List " targetJavaEE: " + targetJavaEE + "\n" + " targetMicroP: " + targetMicroProfile + "\n" + " currentFeatures: " + currentFeatureSet + "\n" + + " baseFeatureListFile: " + ((baseFeatureListFile == null) ? "null" : baseFeatureListFile.getAbsolutePath()) + "\n" + + " coreFeatureListFile: " + ((coreFeatureListFile == null) ? "null" : coreFeatureListFile.getAbsolutePath()) + "\n" + + " logLocation: " + logLocation + "\n" + + " logLevel: " + logLevel + "\n" + + " locale: " + java.util.Locale.getDefault()); + warn ("Calling " + binaryScannerJar.getName() + " with the following inputs...\n" + + " binaryInputs: " + binaryInputs + "\n" + + " targetJavaEE: " + targetJavaEE + "\n" + + " targetMicroP: " + targetMicroProfile + "\n" + + " currentFeatures: " + currentFeatureSet + "\n" + + " baseFeatureListFile: " + ((baseFeatureListFile==null) ? "null" : baseFeatureListFile.getAbsolutePath()) + "\n" + + " baseFeatureListFile: " + baseFeatureListFile + "\n" + + " coreFeatureListFile: " + ((coreFeatureListFile==null) ? "null" : coreFeatureListFile.getAbsolutePath()) + "\n" + " logLocation: " + logLocation + "\n" + " logLevel: " + logLevel + "\n" + " locale: " + java.util.Locale.getDefault()); featureList = (Set) generateFeatureSetMethod.invoke(null, binaryInputs, targetJavaEE, targetMicroProfile, - currentFeatureSet, logLocation, logLevel, java.util.Locale.getDefault()); + currentFeatureSet, baseFeatureListFile, coreFeatureListFile, logLocation, logLevel, java.util.Locale.getDefault()); for (String s : featureList) {debug(s);}; } catch (InvocationTargetException ite) { // This is the exception from the JVM that indicates there was an exception in the method we @@ -201,7 +214,7 @@ public Set runBinaryScanner(Set currentFeatureSet, List // The list of features from the app is passed in but it contains conflicts Set conflicts = getFeatures(scannerException); // always rerun binary scanner in this scenario, this exception only occurs if a current feature list is passed to binary scanner - Set sampleFeatureList = reRunBinaryScanner(allClassesDirectories, logLocation, targetJavaEE, targetMicroProfile); + Set sampleFeatureList = reRunBinaryScanner(allClassesDirectories, logLocation, targetJavaEE, targetMicroProfile, baseFeatureListFile, coreFeatureListFile); if (sampleFeatureList == null) { throw new NoRecommendationException(conflicts); } else { @@ -211,7 +224,7 @@ public Set runBinaryScanner(Set currentFeatureSet, List // The scanned files conflict with each other or with current features Set conflicts = getFeatures(scannerException); // rerun binary scanner with all class files and without the current feature set to get feature recommendations - Set sampleFeatureList = reRunIfFailed ? reRunBinaryScanner(allClassesDirectories, logLocation, targetJavaEE, targetMicroProfile): null; + Set sampleFeatureList = reRunIfFailed ? reRunBinaryScanner(allClassesDirectories, logLocation, targetJavaEE, targetMicroProfile, baseFeatureListFile, coreFeatureListFile): null; if (sampleFeatureList == null) { throw new NoRecommendationException(conflicts); } else { @@ -221,7 +234,7 @@ public Set runBinaryScanner(Set currentFeatureSet, List // The scanned files conflict and the scanner suggests modifying some features Set modifications = getFeatures(scannerException); // rerun binary scanner with all class files and without the current feature set - Set sampleFeatureList = reRunIfFailed ? reRunBinaryScanner(allClassesDirectories, logLocation, targetJavaEE, targetMicroProfile) : null; + Set sampleFeatureList = reRunIfFailed ? reRunBinaryScanner(allClassesDirectories, logLocation, targetJavaEE, targetMicroProfile, baseFeatureListFile, coreFeatureListFile) : null; throw new FeatureModifiedException(modifications, (sampleFeatureList == null) ? getNoSampleFeatureList() : sampleFeatureList, scannerException.getLocalizedMessage()); } else if (scannerException.getClass().getName().equals(FEATURE_NOT_AVAILABLE_EXCEPTION)) { @@ -285,8 +298,8 @@ public Set runBinaryScanner(Set currentFeatureSet, List * @return - a set of features that will allow the application to run in a Liberty server * @throws PluginExecutionException - any exception that prevents the scanner from running */ - public Set reRunBinaryScanner(Set allClassesDirectories, String logLocation, String targetJavaEE, String targetMicroProfile) - throws PluginExecutionException { + public Set reRunBinaryScanner(Set allClassesDirectories, String logLocation, String targetJavaEE, String targetMicroProfile, + File baseFeatureListFile, File coreFeatureListFile) throws PluginExecutionException { Set featureList = null; try { Method generateFeatureSetMethod = getScannerMethod(); @@ -308,7 +321,7 @@ public Set reRunBinaryScanner(Set allClassesDirectories, String " logLevel: " + logLevel + "\n" + " locale: " + java.util.Locale.getDefault()); featureList = (Set) generateFeatureSetMethod.invoke(null, binaryInputs, targetJavaEE, targetMicroProfile, - currentFeaturesSet, logLocation, logLevel, java.util.Locale.getDefault()); + currentFeaturesSet, baseFeatureListFile, coreFeatureListFile, logLocation, logLevel, java.util.Locale.getDefault()); for (String s : featureList) {debug(s);}; } catch (InvocationTargetException ite) { Throwable scannerException = ite.getCause(); @@ -363,14 +376,12 @@ private Class getScannerClass() throws MalformedURLException, ClassNotFoundExcep private Method getScannerMethod() throws MalformedURLException, ClassNotFoundException, NoSuchMethodException, PluginExecutionException, SecurityException { if (binaryScannerMethod == null) { Class driveScan = getScannerClass(); - // args: Set, String, String, Set, String, String, Locale - // names: binaryInputs, targetJavaEE, targetMicroProfile, currentFeatures, logLocation, logLevel, locale - // Set generateFeatureList() - // Set, String, String, Set, File, File, String, String, Locale - // binaryInputs, targetJavaEE, targetMicroProfile, currentFeatures, baseFeatureListFile, coreFeatureListFile, logLocation, logLevel, locale + // Method name and return type: Set generateFeatureList() + // arg types: Set, String, String, Set, File, File, String, String, Locale + // arg names: binaryInputs, targetJavaEE, targetMicroProfile, currentFeatures, baseFeatureListFile, coreFeatureListFile, logLocation, logLevel, locale binaryScannerMethod = driveScan.getMethod("generateFeatureList", Set.class, String.class, String.class, - Set.class, String.class, String.class, java.util.Locale.class); + Set.class, File.class, File.class, String.class, String.class, java.util.Locale.class); if (binaryScannerMethod == null) { throw new PluginExecutionException("Error finding binary scanner method using reflection"); } From 7f137d08c6745bd9394a95d8eb5f88051a1f89c8 Mon Sep 17 00:00:00 2001 From: Paul Gooderham Date: Thu, 2 Apr 2026 19:36:54 -0400 Subject: [PATCH 34/46] Update the type of file to find for liberty base feature list Signed-off-by: Paul Gooderham --- .../tools/common/plugins/util/BinaryScannerUtil.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/openliberty/tools/common/plugins/util/BinaryScannerUtil.java b/src/main/java/io/openliberty/tools/common/plugins/util/BinaryScannerUtil.java index b5db2342d..4e1a91230 100644 --- a/src/main/java/io/openliberty/tools/common/plugins/util/BinaryScannerUtil.java +++ b/src/main/java/io/openliberty/tools/common/plugins/util/BinaryScannerUtil.java @@ -35,8 +35,8 @@ public abstract class BinaryScannerUtil { // The coordinates to use for Open Liberty versions 25.0.0.7 and up public static final String OLBASE_FEATURELIST_GROUP_ID = "io.openliberty.features"; public static final String OLBASE_FEATURELIST_ARTIFACT_ID = "open_liberty_featurelist"; - public static final String OLBASE_FEATURELIST_TYPE = "compile"; - public static final String OLBASE_FEATURELIST_VERSION = "[25.0.0.7)"; + public static final String OLBASE_FEATURELIST_TYPE = "xml"; + public static final String OLBASE_FEATURELIST_VERSION = "[25.0.0.7]"; // The coordinates to use for WebSphere Liberty versions 25.0.0.7 to 25.0.0.9 public static final String WSBASE1_FEATURELIST_GROUP_ID = "io.openliberty.features"; From 515e3a80b6cef8e13698f749630f00912b85d7f8 Mon Sep 17 00:00:00 2001 From: Paul Gooderham Date: Mon, 6 Apr 2026 16:59:36 -0400 Subject: [PATCH 35/46] Reduce the duplication in the feature list coordinates Signed-off-by: Paul Gooderham --- .../plugins/util/BinaryScannerUtil.java | 32 ++++++++----------- 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/src/main/java/io/openliberty/tools/common/plugins/util/BinaryScannerUtil.java b/src/main/java/io/openliberty/tools/common/plugins/util/BinaryScannerUtil.java index 4e1a91230..d67375747 100644 --- a/src/main/java/io/openliberty/tools/common/plugins/util/BinaryScannerUtil.java +++ b/src/main/java/io/openliberty/tools/common/plugins/util/BinaryScannerUtil.java @@ -36,31 +36,25 @@ public abstract class BinaryScannerUtil { public static final String OLBASE_FEATURELIST_GROUP_ID = "io.openliberty.features"; public static final String OLBASE_FEATURELIST_ARTIFACT_ID = "open_liberty_featurelist"; public static final String OLBASE_FEATURELIST_TYPE = "xml"; - public static final String OLBASE_FEATURELIST_VERSION = "[25.0.0.7]"; + // the version number is generated at the point of use // The coordinates to use for WebSphere Liberty versions 25.0.0.7 to 25.0.0.9 - public static final String WSBASE1_FEATURELIST_GROUP_ID = "io.openliberty.features"; - public static final String WSBASE1_FEATURELIST_ARTIFACT_ID = "websphere_liberty_base__featurelist"; - public static final String WSBASE1_FEATURELIST_TYPE = "compile"; - public static final String WSBASE1_FEATURELIST_VERSION = "[25.0.0.7]"; + // We will use them for releases prior to 25.0.0.7 + // io.openliberty.features:websphere_liberty_base__featurelist:xml:25.0.0.7 + // io.openliberty.features:websphere_liberty_core__featurelist:xml:25.0.0.7 // The coordinates to use for WebSphere Liberty versions 25.0.0.10 and up - public static final String WSBASE2_FEATURELIST_GROUP_ID = "com.ibm.websphere.appserver.features"; - public static final String WSBASE2_FEATURELIST_ARTIFACT_ID = "websphere_liberty_base__featurelist"; - public static final String WSBASE2_FEATURELIST_TYPE = "compile"; - public static final String WSBASE2_FEATURELIST_VERSION = "[25.0.0.10)"; + // Publishing stopped with 25.0.0.12 so this value is used for 26.0.0.1 and up. + // com.ibm.websphere.appserver.features:websphere_liberty_base__featurelist:xml:25.0.0.xx + // com.ibm.websphere.appserver.features:websphere_liberty_core__featurelist:xml:25.0.0.xx - // The coordinates to use for WebSphere Liberty versions 25.0.0.7 to 25.0.0.9 - public static final String WSCORE1_FEATURELIST_GROUP_ID = "io.openliberty.features"; - public static final String WSCORE1_FEATURELIST_ARTIFACT_ID = "websphere_liberty_core__featurelist"; - public static final String WSCORE1_FEATURELIST_TYPE = "compile"; - public static final String WSCORE1_FEATURELIST_VERSION = "[25.0.0.7]"; + // These coordinates are used in different combinations to access WebSphere Liberty feature lists 25.0.0.7 and up + public static final String WS1_FEATURELIST_GROUP_ID = "io.openliberty.features"; + public static final String WS2_FEATURELIST_GROUP_ID = "com.ibm.websphere.appserver.features"; + public static final String WSBASE_FEATURELIST_ARTIFACT_ID = "websphere_liberty_base__featurelist"; + public static final String WSCORE_FEATURELIST_ARTIFACT_ID = "websphere_liberty_core__featurelist"; + public static final String WS_FEATURELIST_TYPE = "xml"; - // The coordinates to use for WebSphere Liberty versions 25.0.0.10 and up - public static final String WSCORE2_FEATURELIST_GROUP_ID = "com.ibm.websphere.appserver.features"; - public static final String WSCORE2_FEATURELIST_ARTIFACT_ID = "websphere_liberty_core__featurelist"; - public static final String WSCORE2_FEATURELIST_TYPE = "compile"; - public static final String WSCORE2_FEATURELIST_VERSION = "[25.0.0.10)"; public static final String GENERATED_FEATURES_FILE_NAME = "generated-features.xml"; public static final String GENERATED_FEATURES_DIR_PATH = "configDropins/overrides/"; From e43e66ee50a1ece4586dca31a1cff04dbd1762fd Mon Sep 17 00:00:00 2001 From: Paul Gooderham Date: Mon, 6 Apr 2026 18:00:32 -0400 Subject: [PATCH 36/46] Remove debug statement Signed-off-by: Paul Gooderham --- .../tools/common/plugins/util/BinaryScannerUtil.java | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/main/java/io/openliberty/tools/common/plugins/util/BinaryScannerUtil.java b/src/main/java/io/openliberty/tools/common/plugins/util/BinaryScannerUtil.java index d67375747..731b81fbf 100644 --- a/src/main/java/io/openliberty/tools/common/plugins/util/BinaryScannerUtil.java +++ b/src/main/java/io/openliberty/tools/common/plugins/util/BinaryScannerUtil.java @@ -175,17 +175,6 @@ public Set runBinaryScanner(Set currentFeatureSet, List " logLocation: " + logLocation + "\n" + " logLevel: " + logLevel + "\n" + " locale: " + java.util.Locale.getDefault()); - warn ("Calling " + binaryScannerJar.getName() + " with the following inputs...\n" + - " binaryInputs: " + binaryInputs + "\n" + - " targetJavaEE: " + targetJavaEE + "\n" + - " targetMicroP: " + targetMicroProfile + "\n" + - " currentFeatures: " + currentFeatureSet + "\n" + - " baseFeatureListFile: " + ((baseFeatureListFile==null) ? "null" : baseFeatureListFile.getAbsolutePath()) + "\n" + - " baseFeatureListFile: " + baseFeatureListFile + "\n" + - " coreFeatureListFile: " + ((coreFeatureListFile==null) ? "null" : coreFeatureListFile.getAbsolutePath()) + "\n" + - " logLocation: " + logLocation + "\n" + - " logLevel: " + logLevel + "\n" + - " locale: " + java.util.Locale.getDefault()); featureList = (Set) generateFeatureSetMethod.invoke(null, binaryInputs, targetJavaEE, targetMicroProfile, currentFeatureSet, baseFeatureListFile, coreFeatureListFile, logLocation, logLevel, java.util.Locale.getDefault()); for (String s : featureList) {debug(s);}; From 4a34e5977dc0780d59221faf0de6c16776542649 Mon Sep 17 00:00:00 2001 From: Paul Gooderham Date: Fri, 17 Apr 2026 11:44:39 -0400 Subject: [PATCH 37/46] Rework: clarify the name of open liberty Maven coordinates Signed-off-by: Paul Gooderham --- .../tools/common/plugins/util/BinaryScannerUtil.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/io/openliberty/tools/common/plugins/util/BinaryScannerUtil.java b/src/main/java/io/openliberty/tools/common/plugins/util/BinaryScannerUtil.java index 731b81fbf..a72bae4a7 100644 --- a/src/main/java/io/openliberty/tools/common/plugins/util/BinaryScannerUtil.java +++ b/src/main/java/io/openliberty/tools/common/plugins/util/BinaryScannerUtil.java @@ -33,9 +33,9 @@ public abstract class BinaryScannerUtil { public static final String BINARY_SCANNER_MAVEN_VERSION = "[25.0.0.2.1]"; // The coordinates to use for Open Liberty versions 25.0.0.7 and up - public static final String OLBASE_FEATURELIST_GROUP_ID = "io.openliberty.features"; - public static final String OLBASE_FEATURELIST_ARTIFACT_ID = "open_liberty_featurelist"; - public static final String OLBASE_FEATURELIST_TYPE = "xml"; + public static final String OL_FEATURELIST_GROUP_ID = "io.openliberty.features"; + public static final String OL_FEATURELIST_ARTIFACT_ID = "open_liberty_featurelist"; + public static final String OL_FEATURELIST_TYPE = "xml"; // the version number is generated at the point of use // The coordinates to use for WebSphere Liberty versions 25.0.0.7 to 25.0.0.9 From 8f717ff1a3342447ee6cdb2b41f7faa97df48cea Mon Sep 17 00:00:00 2001 From: Trevor Crawford Date: Mon, 20 Apr 2026 18:14:01 -0400 Subject: [PATCH 38/46] Updated user facing output --- .../common/plugins/util/BinaryScannerUtil.java | 2 +- .../tools/common/plugins/util/DevUtil.java | 14 +++++--------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/main/java/io/openliberty/tools/common/plugins/util/BinaryScannerUtil.java b/src/main/java/io/openliberty/tools/common/plugins/util/BinaryScannerUtil.java index a72bae4a7..9be6ac5ce 100644 --- a/src/main/java/io/openliberty/tools/common/plugins/util/BinaryScannerUtil.java +++ b/src/main/java/io/openliberty/tools/common/plugins/util/BinaryScannerUtil.java @@ -328,7 +328,7 @@ public Set reRunBinaryScanner(Set allClassesDirectories, String debug("Caused by exception2:"+loadingException.getCause().getClass().getName()); debug("Caused by exception message2:"+loadingException.getCause().getMessage()); } - throw new PluginExecutionException("An error occurred when trying to call the binary scanner jar for recommendations: " + loadingException.toString()); + throw new PluginExecutionException("An error occurred when trying to call the binary scanner jar for Liberty feature recommendations: " + loadingException.toString()); } return featureList; } 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 67c185292..47f892900 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 @@ -2668,14 +2668,10 @@ private void printFeatureGenerationStatus() { private void printFeatureGenerationHotkeys() { info(formatAttentionMessage( "g - toggle the automatic generation of features, type 'g' and press Enter.")); - info(formatAttentionMessage( - " A new server configuration file will be generated in the SOURCE configDropins/overrides configuration directory.")); - info(formatAttentionMessage("s - generate features to src directory, type 's' and press Enter.")); + info(formatAttentionMessage("s - toggle the option to generate features to the src directory, type 's' and press Enter.")); if (generateFeatures) { // If generateFeatures is enabled, then also describe the optimize hotkey info(formatAttentionMessage("o - optimize the list of generated features, type 'o' and press Enter.")); - info(formatAttentionMessage( - " A new server configuration file will be generated in the SOURCE configDropins/overrides configuration directory.")); } } @@ -2700,7 +2696,7 @@ private void toggleFeatureGeneration() { logFeatureGenerationStatus(); if (generateFeatures) { if (generateToSrc) { - warnSrcDirModified(); + infoSrcDirModified(); } // If hotkey is toggled to “true”, generate features right away. optimizeGenerateFeatures(!generateToSrc); @@ -2723,7 +2719,7 @@ private void toggleGenerateToSrc() { } if (generateFeatures) { if (generateToSrc) { - warnSrcDirModified(); + infoSrcDirModified(); } // If hotkey is toggled, generate features right away. optimizeGenerateFeatures(!generateToSrc); @@ -2744,14 +2740,14 @@ private void logGenerateToSrcStatus() { info("Setting generation of features in src directory to: " + getFormattedBooleanString(generateToSrc)); } - private void warnSrcDirModified() { + private void infoSrcDirModified() { String generatedFileCanonicalPath; try { generatedFileCanonicalPath = new File(configDirectory, BinaryScannerUtil.GENERATED_FEATURES_FILE_PATH).getCanonicalPath(); } catch (IOException e) { generatedFileCanonicalPath = new File(configDirectory, BinaryScannerUtil.GENERATED_FEATURES_FILE_PATH).toString(); } - warn("The source configuration directory will be modified. Features will automatically be generated in a new file: " + generatedFileCanonicalPath); + info("The source configuration directory will be modified. Features will automatically be generated in a new file: " + generatedFileCanonicalPath); } // called by Liberty plugins From ab17bc34b728db20e99e415f10fde1b990088b11 Mon Sep 17 00:00:00 2001 From: Paul Gooderham Date: Mon, 20 Apr 2026 18:16:58 -0400 Subject: [PATCH 39/46] Use the binary scanner API with a Map Signed-off-by: Paul Gooderham --- .../plugins/util/BinaryScannerUtil.java | 40 +++++++++++++------ 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/src/main/java/io/openliberty/tools/common/plugins/util/BinaryScannerUtil.java b/src/main/java/io/openliberty/tools/common/plugins/util/BinaryScannerUtil.java index a72bae4a7..6e19bec8d 100644 --- a/src/main/java/io/openliberty/tools/common/plugins/util/BinaryScannerUtil.java +++ b/src/main/java/io/openliberty/tools/common/plugins/util/BinaryScannerUtil.java @@ -23,6 +23,7 @@ import java.net.URLClassLoader; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; public abstract class BinaryScannerUtil { @@ -30,13 +31,15 @@ public abstract class BinaryScannerUtil { public static final String BINARY_SCANNER_MAVEN_GROUP_ID = "com.ibm.websphere.appmod.tools"; public static final String BINARY_SCANNER_MAVEN_ARTIFACT_ID = "binary-app-scanner"; public static final String BINARY_SCANNER_MAVEN_TYPE = "jar"; - public static final String BINARY_SCANNER_MAVEN_VERSION = "[25.0.0.2.1]"; + public static final String BINARY_SCANNER_MAVEN_VERSION = "[26.0.0.2-20260420]"; // The coordinates to use for Open Liberty versions 25.0.0.7 and up public static final String OL_FEATURELIST_GROUP_ID = "io.openliberty.features"; public static final String OL_FEATURELIST_ARTIFACT_ID = "open_liberty_featurelist"; public static final String OL_FEATURELIST_TYPE = "xml"; // the version number is generated at the point of use + // The following key is used when passing the feature list file to the scanner + public static final String OL_FEATURELIST_KEY = "openLiberty"; // The coordinates to use for WebSphere Liberty versions 25.0.0.7 to 25.0.0.9 // We will use them for releases prior to 25.0.0.7 @@ -54,6 +57,9 @@ public abstract class BinaryScannerUtil { public static final String WSBASE_FEATURELIST_ARTIFACT_ID = "websphere_liberty_base__featurelist"; public static final String WSCORE_FEATURELIST_ARTIFACT_ID = "websphere_liberty_core__featurelist"; public static final String WS_FEATURELIST_TYPE = "xml"; + // The following keys are used when passing the feature list files to the scanner + public static final String WSBASE_FEATURELIST_KEY = "liberty"; + public static final String WSCORE_FEATURELIST_KEY = "libertyCore"; public static final String GENERATED_FEATURES_FILE_NAME = "generated-features.xml"; @@ -145,7 +151,7 @@ public BinaryScannerUtil(File binaryScanner) { * scanner when used in combination with each other. E.g. EE 7 and MP 2.1 */ public Set runBinaryScanner(Set currentFeatureSet, List classFiles, Set allClassesDirectories, - String logLocation, String targetJavaEE, String targetMicroProfile, File baseFeatureListFile, File coreFeatureListFile, boolean optimize) + String logLocation, String targetJavaEE, String targetMicroProfile, Map featureListFileMap, boolean optimize) throws PluginExecutionException, NoRecommendationException, RecommendationSetException, FeatureModifiedException, FeatureUnavailableException, IllegalTargetException, IllegalTargetComboException { Set featureList = null; @@ -170,13 +176,21 @@ public Set runBinaryScanner(Set currentFeatureSet, List " targetJavaEE: " + targetJavaEE + "\n" + " targetMicroP: " + targetMicroProfile + "\n" + " currentFeatures: " + currentFeatureSet + "\n" + - " baseFeatureListFile: " + ((baseFeatureListFile == null) ? "null" : baseFeatureListFile.getAbsolutePath()) + "\n" + - " coreFeatureListFile: " + ((coreFeatureListFile == null) ? "null" : coreFeatureListFile.getAbsolutePath()) + "\n" + + " featureListFileMap: " + featureListFileMap + "\n" + + " logLocation: " + logLocation + "\n" + + " logLevel: " + logLevel + "\n" + + " locale: " + java.util.Locale.getDefault()); + warn ("Calling " + binaryScannerJar.getName() + " with the following inputs...\n" + + " binaryInputs: " + binaryInputs + "\n" + + " targetJavaEE: " + targetJavaEE + "\n" + + " targetMicroP: " + targetMicroProfile + "\n" + + " currentFeatures: " + currentFeatureSet + "\n" + + " featureListFileMap: " + featureListFileMap + "\n" + " logLocation: " + logLocation + "\n" + " logLevel: " + logLevel + "\n" + " locale: " + java.util.Locale.getDefault()); featureList = (Set) generateFeatureSetMethod.invoke(null, binaryInputs, targetJavaEE, targetMicroProfile, - currentFeatureSet, baseFeatureListFile, coreFeatureListFile, logLocation, logLevel, java.util.Locale.getDefault()); + currentFeatureSet, featureListFileMap, logLocation, logLevel, java.util.Locale.getDefault()); for (String s : featureList) {debug(s);}; } catch (InvocationTargetException ite) { // This is the exception from the JVM that indicates there was an exception in the method we @@ -197,7 +211,7 @@ public Set runBinaryScanner(Set currentFeatureSet, List // The list of features from the app is passed in but it contains conflicts Set conflicts = getFeatures(scannerException); // always rerun binary scanner in this scenario, this exception only occurs if a current feature list is passed to binary scanner - Set sampleFeatureList = reRunBinaryScanner(allClassesDirectories, logLocation, targetJavaEE, targetMicroProfile, baseFeatureListFile, coreFeatureListFile); + Set sampleFeatureList = reRunBinaryScanner(allClassesDirectories, logLocation, targetJavaEE, targetMicroProfile, featureListFileMap); if (sampleFeatureList == null) { throw new NoRecommendationException(conflicts); } else { @@ -207,7 +221,7 @@ public Set runBinaryScanner(Set currentFeatureSet, List // The scanned files conflict with each other or with current features Set conflicts = getFeatures(scannerException); // rerun binary scanner with all class files and without the current feature set to get feature recommendations - Set sampleFeatureList = reRunIfFailed ? reRunBinaryScanner(allClassesDirectories, logLocation, targetJavaEE, targetMicroProfile, baseFeatureListFile, coreFeatureListFile): null; + Set sampleFeatureList = reRunIfFailed ? reRunBinaryScanner(allClassesDirectories, logLocation, targetJavaEE, targetMicroProfile, featureListFileMap): null; if (sampleFeatureList == null) { throw new NoRecommendationException(conflicts); } else { @@ -217,7 +231,7 @@ public Set runBinaryScanner(Set currentFeatureSet, List // The scanned files conflict and the scanner suggests modifying some features Set modifications = getFeatures(scannerException); // rerun binary scanner with all class files and without the current feature set - Set sampleFeatureList = reRunIfFailed ? reRunBinaryScanner(allClassesDirectories, logLocation, targetJavaEE, targetMicroProfile, baseFeatureListFile, coreFeatureListFile) : null; + Set sampleFeatureList = reRunIfFailed ? reRunBinaryScanner(allClassesDirectories, logLocation, targetJavaEE, targetMicroProfile, featureListFileMap) : null; throw new FeatureModifiedException(modifications, (sampleFeatureList == null) ? getNoSampleFeatureList() : sampleFeatureList, scannerException.getLocalizedMessage()); } else if (scannerException.getClass().getName().equals(FEATURE_NOT_AVAILABLE_EXCEPTION)) { @@ -282,7 +296,7 @@ public Set runBinaryScanner(Set currentFeatureSet, List * @throws PluginExecutionException - any exception that prevents the scanner from running */ public Set reRunBinaryScanner(Set allClassesDirectories, String logLocation, String targetJavaEE, String targetMicroProfile, - File baseFeatureListFile, File coreFeatureListFile) throws PluginExecutionException { + Map featureListFileMap) throws PluginExecutionException { Set featureList = null; try { Method generateFeatureSetMethod = getScannerMethod(); @@ -304,7 +318,7 @@ public Set reRunBinaryScanner(Set allClassesDirectories, String " logLevel: " + logLevel + "\n" + " locale: " + java.util.Locale.getDefault()); featureList = (Set) generateFeatureSetMethod.invoke(null, binaryInputs, targetJavaEE, targetMicroProfile, - currentFeaturesSet, baseFeatureListFile, coreFeatureListFile, logLocation, logLevel, java.util.Locale.getDefault()); + currentFeaturesSet, featureListFileMap, logLocation, logLevel, java.util.Locale.getDefault()); for (String s : featureList) {debug(s);}; } catch (InvocationTargetException ite) { Throwable scannerException = ite.getCause(); @@ -360,11 +374,11 @@ private Method getScannerMethod() throws MalformedURLException, ClassNotFoundExc if (binaryScannerMethod == null) { Class driveScan = getScannerClass(); // Method name and return type: Set generateFeatureList() - // arg types: Set, String, String, Set, File, File, String, String, Locale - // arg names: binaryInputs, targetJavaEE, targetMicroProfile, currentFeatures, baseFeatureListFile, coreFeatureListFile, logLocation, logLevel, locale + // arg types: Set, String, String, Set, Map, String, String, Locale + // arg names: binaryInputs, targetJavaEE, targetMicroProfile, currentFeatures, featureListFileMap, logLocation, logLevel, locale binaryScannerMethod = driveScan.getMethod("generateFeatureList", Set.class, String.class, String.class, - Set.class, File.class, File.class, String.class, String.class, java.util.Locale.class); + Set.class, Map.class, String.class, String.class, java.util.Locale.class); if (binaryScannerMethod == null) { throw new PluginExecutionException("Error finding binary scanner method using reflection"); } From a3d7d38024248db76239dca83e6bb382ab5ecc0d Mon Sep 17 00:00:00 2001 From: Paul Gooderham Date: Tue, 21 Apr 2026 15:15:54 -0400 Subject: [PATCH 40/46] Rework, rename variable Signed-off-by: Paul Gooderham --- .../plugins/util/BinaryScannerUtil.java | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/src/main/java/io/openliberty/tools/common/plugins/util/BinaryScannerUtil.java b/src/main/java/io/openliberty/tools/common/plugins/util/BinaryScannerUtil.java index 6e19bec8d..1c7be8b0d 100644 --- a/src/main/java/io/openliberty/tools/common/plugins/util/BinaryScannerUtil.java +++ b/src/main/java/io/openliberty/tools/common/plugins/util/BinaryScannerUtil.java @@ -31,7 +31,7 @@ public abstract class BinaryScannerUtil { public static final String BINARY_SCANNER_MAVEN_GROUP_ID = "com.ibm.websphere.appmod.tools"; public static final String BINARY_SCANNER_MAVEN_ARTIFACT_ID = "binary-app-scanner"; public static final String BINARY_SCANNER_MAVEN_TYPE = "jar"; - public static final String BINARY_SCANNER_MAVEN_VERSION = "[26.0.0.2-20260420]"; + public static final String BINARY_SCANNER_MAVEN_VERSION = "[25.0.0.2.2]"; // The coordinates to use for Open Liberty versions 25.0.0.7 and up public static final String OL_FEATURELIST_GROUP_ID = "io.openliberty.features"; @@ -154,7 +154,7 @@ public Set runBinaryScanner(Set currentFeatureSet, List String logLocation, String targetJavaEE, String targetMicroProfile, Map featureListFileMap, boolean optimize) throws PluginExecutionException, NoRecommendationException, RecommendationSetException, FeatureModifiedException, FeatureUnavailableException, IllegalTargetException, IllegalTargetComboException { - Set featureList = null; + Set generatedFeatureList = null; if (binaryScannerJar != null && binaryScannerJar.exists()) { // if we are already generating features for all class files (optimize=true) and // we are not passing any user specified features (currentFeatureSet is empty) @@ -180,18 +180,9 @@ public Set runBinaryScanner(Set currentFeatureSet, List " logLocation: " + logLocation + "\n" + " logLevel: " + logLevel + "\n" + " locale: " + java.util.Locale.getDefault()); - warn ("Calling " + binaryScannerJar.getName() + " with the following inputs...\n" + - " binaryInputs: " + binaryInputs + "\n" + - " targetJavaEE: " + targetJavaEE + "\n" + - " targetMicroP: " + targetMicroProfile + "\n" + - " currentFeatures: " + currentFeatureSet + "\n" + - " featureListFileMap: " + featureListFileMap + "\n" + - " logLocation: " + logLocation + "\n" + - " logLevel: " + logLevel + "\n" + - " locale: " + java.util.Locale.getDefault()); - featureList = (Set) generateFeatureSetMethod.invoke(null, binaryInputs, targetJavaEE, targetMicroProfile, + generatedFeatureList = (Set) generateFeatureSetMethod.invoke(null, binaryInputs, targetJavaEE, targetMicroProfile, currentFeatureSet, featureListFileMap, logLocation, logLevel, java.util.Locale.getDefault()); - for (String s : featureList) {debug(s);}; + for (String s : generatedFeatureList) {debug(s);}; } catch (InvocationTargetException ite) { // This is the exception from the JVM that indicates there was an exception in the method we // called through reflection. We must extract the actual exception from the 'cause' field. @@ -278,7 +269,7 @@ public Set runBinaryScanner(Set currentFeatureSet, List throw new PluginExecutionException("Could not find the binary scanner jar at " + binaryScannerJar.getAbsolutePath()); } } - return featureList; + return generatedFeatureList; } /** From 3aa3cb86481ce790b1a9e7f16b519baea4d4710b Mon Sep 17 00:00:00 2001 From: Paul Gooderham Date: Tue, 21 Apr 2026 15:22:39 -0400 Subject: [PATCH 41/46] Rework, rename variable no. 2 Signed-off-by: Paul Gooderham --- .../common/plugins/util/BinaryScannerUtil.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/java/io/openliberty/tools/common/plugins/util/BinaryScannerUtil.java b/src/main/java/io/openliberty/tools/common/plugins/util/BinaryScannerUtil.java index 1c7be8b0d..2ff9b50b0 100644 --- a/src/main/java/io/openliberty/tools/common/plugins/util/BinaryScannerUtil.java +++ b/src/main/java/io/openliberty/tools/common/plugins/util/BinaryScannerUtil.java @@ -288,7 +288,7 @@ public Set runBinaryScanner(Set currentFeatureSet, List */ public Set reRunBinaryScanner(Set allClassesDirectories, String logLocation, String targetJavaEE, String targetMicroProfile, Map featureListFileMap) throws PluginExecutionException { - Set featureList = null; + Set generatedFeatureList = null; try { Method generateFeatureSetMethod = getScannerMethod(); Set binaryInputs = allClassesDirectories; @@ -308,21 +308,21 @@ public Set reRunBinaryScanner(Set allClassesDirectories, String " logLocation: " + logLocation + "\n" + " logLevel: " + logLevel + "\n" + " locale: " + java.util.Locale.getDefault()); - featureList = (Set) generateFeatureSetMethod.invoke(null, binaryInputs, targetJavaEE, targetMicroProfile, + generatedFeatureList = (Set) generateFeatureSetMethod.invoke(null, binaryInputs, targetJavaEE, targetMicroProfile, currentFeaturesSet, featureListFileMap, logLocation, logLevel, java.util.Locale.getDefault()); - for (String s : featureList) {debug(s);}; + for (String s : generatedFeatureList) {debug(s);}; } catch (InvocationTargetException ite) { Throwable scannerException = ite.getCause(); if (scannerException.getClass().getName().equals(PROVIDED_FEATURE_EXCEPTION)) { // this happens when the list of features passed in contains conflicts so now no recommendation possible debug("RuntimeException from re-run of binary scanner", scannerException); // shouldn't happen - featureList = null; + generatedFeatureList = null; } else if (scannerException.getClass().getName().equals(FEATURE_CONFLICT_EXCEPTION)) { // The features in the scanned files conflict with each other, no recommendation possible - featureList = getNoSampleFeatureList(); + generatedFeatureList = getNoSampleFeatureList(); } else if (scannerException.getClass().getName().equals(FEATURE_MODIFIED_EXCEPTION)) { // The features in the scanned files conflict with each other, no recommendation possible - featureList = getNoSampleFeatureList(); + generatedFeatureList = getNoSampleFeatureList(); } else { debug("Exception from rerunning binary scanner.", scannerException); throw new PluginExecutionException("Error scanning the application for Liberty feature recommendations: " + scannerException.toString()); @@ -335,7 +335,7 @@ public Set reRunBinaryScanner(Set allClassesDirectories, String } throw new PluginExecutionException("An error occurred when trying to call the binary scanner jar for recommendations: " + loadingException.toString()); } - return featureList; + return generatedFeatureList; } private Set getNoSampleFeatureList() { From 87817d9ce23725fbf0c08561d101b432ff92b90d Mon Sep 17 00:00:00 2001 From: Paul Gooderham Date: Thu, 23 Apr 2026 17:08:43 -0400 Subject: [PATCH 42/46] When a VersionlessFeatureException is detected from the binary scanner pass it to the caller Signed-off-by: Paul Gooderham --- .../tools/common/plugins/util/BinaryScannerUtil.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/main/java/io/openliberty/tools/common/plugins/util/BinaryScannerUtil.java b/src/main/java/io/openliberty/tools/common/plugins/util/BinaryScannerUtil.java index 2fbbacd31..f6e2c434e 100644 --- a/src/main/java/io/openliberty/tools/common/plugins/util/BinaryScannerUtil.java +++ b/src/main/java/io/openliberty/tools/common/plugins/util/BinaryScannerUtil.java @@ -73,6 +73,7 @@ public abstract class BinaryScannerUtil { private static final String FEATURE_NOT_AVAILABLE_EXCEPTION = "com.ibm.websphere.binary.cmdline.exceptions.FeatureNotAvailableAtRequestedLevelException"; private static final String ILLEGAL_TARGET_EXCEPTION = "com.ibm.websphere.binary.cmdline.exceptions.IllegalTargetException"; private static final String ILLEGAL_TARGET_COMBINATION_EXCEPTION = "com.ibm.websphere.binary.cmdline.exceptions.IllegalTargetCombinationException"; + private static final String VERSIONLESS_FEATURE_EXCEPTION = "com.ibm.websphere.binary.cmdline.exceptions.VersionlessFeatureException"; public static final String BINARY_SCANNER_CONFLICT_MESSAGE1 = "A working set of features could not be generated due to conflicts " + "between configured features and the application's API usage: %s. Review and update your server configuration and " + "application to ensure they are not using conflicting features and APIs from different levels of MicroProfile, " + @@ -153,7 +154,7 @@ public BinaryScannerUtil(File binaryScanner) { public Set runBinaryScanner(Set currentFeatureSet, List classFiles, Set allClassesDirectories, String logLocation, String targetJavaEE, String targetMicroProfile, Map featureListFileMap, boolean optimize) throws PluginExecutionException, NoRecommendationException, RecommendationSetException, FeatureModifiedException, - FeatureUnavailableException, IllegalTargetException, IllegalTargetComboException { + FeatureUnavailableException, IllegalTargetException, IllegalTargetComboException, VersionlessFeatureDetectedException { Set generatedFeatureList = null; if (binaryScannerJar != null && binaryScannerJar.exists()) { // if we are already generating features for all class files (optimize=true) and @@ -239,6 +240,9 @@ public Set runBinaryScanner(Set currentFeatureSet, List } else if (scannerException.getClass().getName().equals(ILLEGAL_TARGET_COMBINATION_EXCEPTION)) { // The EE and MP version numbers are in range but they are not compatible with each other based on the standards. throw new IllegalTargetComboException(getInvalidEETarget(scannerException), getInvalidMPTarget(scannerException)); + } else if (scannerException.getClass().getName().equals(VERSIONLESS_FEATURE_EXCEPTION)) { + // One of the existing features has no feature specified and the binary scanner does not support this. + throw new VersionlessFeatureDetectedException(); } else if (scannerException.getClass().getName().contains("java.lang.IllegalArgumentException")) { // Used by binary scanner 22.0.0.3, remove after 22.0.0.4 is in sonatype // TODO: Affected by issue #1558 @@ -629,4 +633,8 @@ public class IllegalTargetComboException extends AbstractIllegalTargetException super(eeLevel, mpLevel); } } + + public class VersionlessFeatureDetectedException extends Exception { + private static final long serialVersionUID = 1L; + } } From 8fe5729e1ddd0278e082fc04a6aace7c07915bc6 Mon Sep 17 00:00:00 2001 From: Paul Gooderham Date: Thu, 23 Apr 2026 17:12:23 -0400 Subject: [PATCH 43/46] Fix a comment Signed-off-by: Paul Gooderham --- .../tools/common/plugins/util/BinaryScannerUtil.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/io/openliberty/tools/common/plugins/util/BinaryScannerUtil.java b/src/main/java/io/openliberty/tools/common/plugins/util/BinaryScannerUtil.java index f6e2c434e..179715995 100644 --- a/src/main/java/io/openliberty/tools/common/plugins/util/BinaryScannerUtil.java +++ b/src/main/java/io/openliberty/tools/common/plugins/util/BinaryScannerUtil.java @@ -241,7 +241,7 @@ public Set runBinaryScanner(Set currentFeatureSet, List // The EE and MP version numbers are in range but they are not compatible with each other based on the standards. throw new IllegalTargetComboException(getInvalidEETarget(scannerException), getInvalidMPTarget(scannerException)); } else if (scannerException.getClass().getName().equals(VERSIONLESS_FEATURE_EXCEPTION)) { - // One of the existing features has no feature specified and the binary scanner does not support this. + // One of the existing features has no version specified and the binary scanner does not support this. throw new VersionlessFeatureDetectedException(); } else if (scannerException.getClass().getName().contains("java.lang.IllegalArgumentException")) { // Used by binary scanner 22.0.0.3, remove after 22.0.0.4 is in sonatype From d0d22ffcad7fc9eba76bea655e6de8f1fb32ecac Mon Sep 17 00:00:00 2001 From: Paul Gooderham Date: Fri, 1 May 2026 17:46:59 -0400 Subject: [PATCH 44/46] Add a check that a file is in a directory Signed-off-by: Paul Gooderham --- .../io/openliberty/tools/common/plugins/util/DevUtil.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 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 47f892900..9e0f83849 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 @@ -5014,8 +5014,11 @@ public void copyFile(File fileChanged, File srcDir, File targetDir, String targe private File getTargetFile(File fileChanged, File srcDir, File targetDir, String targetFileName) throws IOException { - String relPath = fileChanged.getCanonicalPath().substring( - fileChanged.getCanonicalPath().indexOf(srcDir.getCanonicalPath()) + srcDir.getCanonicalPath().length()); + int srcDirIndex = fileChanged.getCanonicalPath().indexOf(srcDir.getCanonicalPath()); + if (srcDirIndex < 0 ) { // warn plugin developer this input is invalid, should not happen in production + new IllegalArgumentException("fileChanged " + fileChanged + " is not in srcDir " + srcDir).printStackTrace(); + } + String relPath = fileChanged.getCanonicalPath().substring(srcDirIndex + srcDir.getCanonicalPath().length()); if (targetFileName != null) { relPath = relPath.substring(0, relPath.indexOf(fileChanged.getName())) + targetFileName; } From cc17ff96706e57f76677ce5354fa88fc929ee66c Mon Sep 17 00:00:00 2001 From: Paul Gooderham Date: Mon, 4 May 2026 12:40:19 -0400 Subject: [PATCH 45/46] Reorganize the Maven ccordinates used to download the Websphere feature lists for binary scanner Signed-off-by: Paul Gooderham --- .../plugins/util/BinaryScannerUtil.java | 37 ++++++++++--------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/src/main/java/io/openliberty/tools/common/plugins/util/BinaryScannerUtil.java b/src/main/java/io/openliberty/tools/common/plugins/util/BinaryScannerUtil.java index 2fbbacd31..f6961042e 100644 --- a/src/main/java/io/openliberty/tools/common/plugins/util/BinaryScannerUtil.java +++ b/src/main/java/io/openliberty/tools/common/plugins/util/BinaryScannerUtil.java @@ -31,7 +31,7 @@ public abstract class BinaryScannerUtil { public static final String BINARY_SCANNER_MAVEN_GROUP_ID = "com.ibm.websphere.appmod.tools"; public static final String BINARY_SCANNER_MAVEN_ARTIFACT_ID = "binary-app-scanner"; public static final String BINARY_SCANNER_MAVEN_TYPE = "jar"; - public static final String BINARY_SCANNER_MAVEN_VERSION = "[25.0.0.2.2]"; + public static final String BINARY_SCANNER_MAVEN_VERSION = "[26.0.0.2-20260420]"; // The coordinates to use for Open Liberty versions 25.0.0.7 and up public static final String OL_FEATURELIST_GROUP_ID = "io.openliberty.features"; @@ -41,27 +41,30 @@ public abstract class BinaryScannerUtil { // The following key is used when passing the feature list file to the scanner public static final String OL_FEATURELIST_KEY = "openLiberty"; - // The coordinates to use for WebSphere Liberty versions 25.0.0.7 to 25.0.0.9 - // We will use them for releases prior to 25.0.0.7 - // io.openliberty.features:websphere_liberty_base__featurelist:xml:25.0.0.7 - // io.openliberty.features:websphere_liberty_core__featurelist:xml:25.0.0.7 - - // The coordinates to use for WebSphere Liberty versions 25.0.0.10 and up - // Publishing stopped with 25.0.0.12 so this value is used for 26.0.0.1 and up. - // com.ibm.websphere.appserver.features:websphere_liberty_base__featurelist:xml:25.0.0.xx - // com.ibm.websphere.appserver.features:websphere_liberty_core__featurelist:xml:25.0.0.xx - - // These coordinates are used in different combinations to access WebSphere Liberty feature lists 25.0.0.7 and up - public static final String WS1_FEATURELIST_GROUP_ID = "io.openliberty.features"; - public static final String WS2_FEATURELIST_GROUP_ID = "com.ibm.websphere.appserver.features"; - public static final String WSBASE_FEATURELIST_ARTIFACT_ID = "websphere_liberty_base__featurelist"; - public static final String WSCORE_FEATURELIST_ARTIFACT_ID = "websphere_liberty_core__featurelist"; + // The coordinates to use for WebSphere Liberty versions describe three different + // cadres. Version 25.0.0.7 and earlier are the first cadre. Version 25.0.0.8 through + // to 25.0.0.12 are the second cadre. The last cadre is versions 26.0.0.1 and onward. + // Maven coordinates for cadre 1 + public static final String WS_FEATURE_LIST_VERSION_CADRE1 = "25.0.0.7"; + public static final String WS_FEATURELIST_GROUP_ID_CADRE1 = "io.openliberty.features"; + public static final String WS_BASE_FEATURE_LIST_ARTIFACT_ID_CADRE1 = "websphere_liberty_base__featurelist"; + public static final String WS_CORE_FEATURE_LIST_ARTIFACT_ID_CADRE1 = "websphere_liberty_core__featurelist"; + // Maven coordinates for cadre 2 + //public static final String WS_FEATURE_LIST_VERSION_CADRE2 = versions between cadres 1 and 3 + public static final String WS_FEATURELIST_GROUP_ID_CADRE2 = "com.ibm.websphere.appserver.features"; + public static final String WS_BASE_FEATURE_LIST_ARTIFACT_ID_CADRE2 = WS_BASE_FEATURE_LIST_ARTIFACT_ID_CADRE1; + public static final String WS_CORE_FEATURE_LIST_ARTIFACT_ID_CADRE2 = WS_CORE_FEATURE_LIST_ARTIFACT_ID_CADRE1; + // Maven coordinates for cadre 3 + public static final String WS_FEATURE_LIST_VERSION_CADRE3 = "26.0.0.1"; + public static final String WS_FEATURELIST_GROUP_ID_CADRE3 = WS_FEATURELIST_GROUP_ID_CADRE2; + public static final String WS_BASE_FEATURE_LIST_ARTIFACT_ID_CADRE3 = "websphere_liberty_base_featurelist"; + public static final String WS_CORE_FEATURE_LIST_ARTIFACT_ID_CADRE3 = "websphere_liberty_core_featurelist"; + // The remaining coordinate is the same for all three public static final String WS_FEATURELIST_TYPE = "xml"; // The following keys are used when passing the feature list files to the scanner public static final String WSBASE_FEATURELIST_KEY = "liberty"; public static final String WSCORE_FEATURELIST_KEY = "libertyCore"; - public static final String GENERATED_FEATURES_FILE_NAME = "generated-features.xml"; public static final String GENERATED_FEATURES_DIR_PATH = "configDropins/overrides/"; public static final String GENERATED_FEATURES_FILE_PATH = GENERATED_FEATURES_DIR_PATH + GENERATED_FEATURES_FILE_NAME; From e7f71a379cd2020fee0a24676b33e7ec9fccf4f8 Mon Sep 17 00:00:00 2001 From: Paul Gooderham Date: Fri, 8 May 2026 17:09:49 -0400 Subject: [PATCH 46/46] Change the term cadre to batch Signed-off-by: Paul Gooderham --- .../plugins/util/BinaryScannerUtil.java | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/main/java/io/openliberty/tools/common/plugins/util/BinaryScannerUtil.java b/src/main/java/io/openliberty/tools/common/plugins/util/BinaryScannerUtil.java index f6961042e..6921b6f96 100644 --- a/src/main/java/io/openliberty/tools/common/plugins/util/BinaryScannerUtil.java +++ b/src/main/java/io/openliberty/tools/common/plugins/util/BinaryScannerUtil.java @@ -42,23 +42,23 @@ public abstract class BinaryScannerUtil { public static final String OL_FEATURELIST_KEY = "openLiberty"; // The coordinates to use for WebSphere Liberty versions describe three different - // cadres. Version 25.0.0.7 and earlier are the first cadre. Version 25.0.0.8 through - // to 25.0.0.12 are the second cadre. The last cadre is versions 26.0.0.1 and onward. - // Maven coordinates for cadre 1 - public static final String WS_FEATURE_LIST_VERSION_CADRE1 = "25.0.0.7"; - public static final String WS_FEATURELIST_GROUP_ID_CADRE1 = "io.openliberty.features"; - public static final String WS_BASE_FEATURE_LIST_ARTIFACT_ID_CADRE1 = "websphere_liberty_base__featurelist"; - public static final String WS_CORE_FEATURE_LIST_ARTIFACT_ID_CADRE1 = "websphere_liberty_core__featurelist"; - // Maven coordinates for cadre 2 - //public static final String WS_FEATURE_LIST_VERSION_CADRE2 = versions between cadres 1 and 3 - public static final String WS_FEATURELIST_GROUP_ID_CADRE2 = "com.ibm.websphere.appserver.features"; - public static final String WS_BASE_FEATURE_LIST_ARTIFACT_ID_CADRE2 = WS_BASE_FEATURE_LIST_ARTIFACT_ID_CADRE1; - public static final String WS_CORE_FEATURE_LIST_ARTIFACT_ID_CADRE2 = WS_CORE_FEATURE_LIST_ARTIFACT_ID_CADRE1; - // Maven coordinates for cadre 3 - public static final String WS_FEATURE_LIST_VERSION_CADRE3 = "26.0.0.1"; - public static final String WS_FEATURELIST_GROUP_ID_CADRE3 = WS_FEATURELIST_GROUP_ID_CADRE2; - public static final String WS_BASE_FEATURE_LIST_ARTIFACT_ID_CADRE3 = "websphere_liberty_base_featurelist"; - public static final String WS_CORE_FEATURE_LIST_ARTIFACT_ID_CADRE3 = "websphere_liberty_core_featurelist"; + // batches. Version 25.0.0.7 and earlier are the first batch. Version 25.0.0.8 through + // to 25.0.0.12 are the second batch. The last batch is versions 26.0.0.1 and onward. + // Maven coordinates for batch 1 + public static final String WS_FEATURE_LIST_VERSION_BATCH1 = "25.0.0.7"; + public static final String WS_FEATURELIST_GROUP_ID_BATCH1 = "io.openliberty.features"; + public static final String WS_BASE_FEATURE_LIST_ARTIFACT_ID_BATCH1 = "websphere_liberty_base__featurelist"; + public static final String WS_CORE_FEATURE_LIST_ARTIFACT_ID_BATCH1 = "websphere_liberty_core__featurelist"; + // Maven coordinates for batch 2 + //public static final String WS_FEATURE_LIST_VERSION_BATCH2 = versions between batches 1 and 3 + public static final String WS_FEATURELIST_GROUP_ID_BATCH2 = "com.ibm.websphere.appserver.features"; + public static final String WS_BASE_FEATURE_LIST_ARTIFACT_ID_BATCH2 = WS_BASE_FEATURE_LIST_ARTIFACT_ID_BATCH1; + public static final String WS_CORE_FEATURE_LIST_ARTIFACT_ID_BATCH2 = WS_CORE_FEATURE_LIST_ARTIFACT_ID_BATCH1; + // Maven coordinates for batch 3 + public static final String WS_FEATURE_LIST_VERSION_BATCH3 = "26.0.0.1"; + public static final String WS_FEATURELIST_GROUP_ID_BATCH3 = WS_FEATURELIST_GROUP_ID_BATCH2; + public static final String WS_BASE_FEATURE_LIST_ARTIFACT_ID_BATCH3 = "websphere_liberty_base_featurelist"; + public static final String WS_CORE_FEATURE_LIST_ARTIFACT_ID_BATCH3 = "websphere_liberty_core_featurelist"; // The remaining coordinate is the same for all three public static final String WS_FEATURELIST_TYPE = "xml"; // The following keys are used when passing the feature list files to the scanner