diff --git a/.gitignore b/.gitignore index 2c8892d8f..6d83aa177 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,8 @@ target/ .classpath .factorypath .idea/ +.vscode/ .DS_Store *.versionsBackup /.metadata/ -*.jar \ No newline at end of file +*.jar diff --git a/liberty-maven-plugin/pom.xml b/liberty-maven-plugin/pom.xml index aca56524d..73f3455a0 100644 --- a/liberty-maven-plugin/pom.xml +++ b/liberty-maven-plugin/pom.xml @@ -100,7 +100,7 @@ io.openliberty.tools ci.common - 1.8.41 + 1.8.42-SNAPSHOT org.apache.commons diff --git a/liberty-maven-plugin/src/it/dev-it/src/test/java/net/wasdev/wlp/test/dev/it/BaseDevTest.java b/liberty-maven-plugin/src/it/dev-it/src/test/java/net/wasdev/wlp/test/dev/it/BaseDevTest.java index 676e32b57..c83b2a060 100644 --- a/liberty-maven-plugin/src/it/dev-it/src/test/java/net/wasdev/wlp/test/dev/it/BaseDevTest.java +++ b/liberty-maven-plugin/src/it/dev-it/src/test/java/net/wasdev/wlp/test/dev/it/BaseDevTest.java @@ -1,5 +1,5 @@ /******************************************************************************* - * (c) Copyright IBM Corporation 2019, 2023. + * (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. @@ -72,7 +72,7 @@ public class BaseDevTest { final static String RUNNING_GENERATE_FEATURES = "Running liberty:generate-features"; final static String REGENERATE_FEATURES = "Regenerated the following features:"; final static String GENERATE_FEATURES = "Generated the following features:"; - final static String SERVER_XML_COMMENT = "Plugin has generated Liberty features"; // the explanation added to server.xml + // final static String SERVER_XML_COMMENT = "Plugin has generated Liberty features"; // the explanation added to server.xml final static String NEW_FILE_INFO_MESSAGE = "This file was generated by the Liberty Maven Plugin and will be overwritten"; // the explanation added to the generated features file final static String SERVER_CONFIG_SUCCESS = "CWWKZ0003I:";// CWWKZ0003I: The application xxx updated in y.yyy seconds. final static String SERVER_UPDATE_COMPLETE = "CWWKF0008I:"; // Feature update completed in 0.649 seconds. diff --git a/liberty-maven-plugin/src/it/dev-it/src/test/java/net/wasdev/wlp/test/dev/it/DevGenerateFeaturesDependenciesTest.java b/liberty-maven-plugin/src/it/dev-it/src/test/java/net/wasdev/wlp/test/dev/it/DevGenerateFeaturesDependenciesTest.java index 3faf4e1e7..7aabb5a0e 100644 --- a/liberty-maven-plugin/src/it/dev-it/src/test/java/net/wasdev/wlp/test/dev/it/DevGenerateFeaturesDependenciesTest.java +++ b/liberty-maven-plugin/src/it/dev-it/src/test/java/net/wasdev/wlp/test/dev/it/DevGenerateFeaturesDependenciesTest.java @@ -1,5 +1,5 @@ /******************************************************************************* - * (c) Copyright IBM Corporation 2022. + * (c) Copyright IBM Corporation 2022, 2025 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,13 +30,13 @@ public class DevGenerateFeaturesDependenciesTest extends BaseDevTest { @BeforeClass - public static void setUpBeforeClass() throws Exception { - setUpBeforeClass(null, "../resources/basic-dev-project-umbrella-deps"); - } + public static void setUpBeforeClass() throws Exception { + setUpBeforeClass(null, "../resources/basic-dev-project-umbrella-deps"); + } @AfterClass public static void cleanUpAfterClass() throws Exception { - BaseDevTest.cleanUpAfterClass(); + BaseDevTest.cleanUpAfterClass(); } @Test @@ -45,16 +45,17 @@ public void updateDependencyTest() throws Exception { assertTrue(verifyLogMessageExists("Listening for transport dt_socket at address: 8077", 20000) || verifyLogMessageExists("The debug port 8077 is not available.",20000)); assertTrue(verifyLogMessageExists("Liberty is running in dev mode.", 10000)); - File generatedFeaturesFile = getGeneratedFeaturesFile(); + //File generatedFeaturesFile = getGeneratedFeaturesFile(); File targetGeneratedFeaturesFile = getTargetGeneratedFeaturesFile(); assertTrue(pom.exists()); - assertTrue(generatedFeaturesFile.exists()); + //assertTrue(generatedFeaturesFile.exists()); assertTrue(targetGeneratedFeaturesFile.exists()); - long lastModified = generatedFeaturesFile.lastModified(); + // long lastModified = generatedFeaturesFile.lastModified(); + long lastModified = targetGeneratedFeaturesFile.lastModified(); waitLongEnough(); // verify mpHealth-2.2 is in generated features file - assertTrue(verifyLogMessageExists("mpHealth-2.2", 10000, generatedFeaturesFile)); + assertTrue(verifyLogMessageExists("mpHealth-2.2", 10000, targetGeneratedFeaturesFile)); assertTrue(verifyLogMessageExists("mpHealth-2.2", 10000)); // should appear in the message "CWWKF0012I: The server installed the following features:" int generateFeaturesCount = countOccurrences("Running liberty:generate-features", logFile); @@ -77,16 +78,20 @@ public void updateDependencyTest() throws Exception { // Dev mode should now run the generate features mojo assertTrue(getLogTail(), verifyLogMessageExists("Generated the following features:", 15000, logFile, ++generateFeaturesCount)); // mojo ran - assertTrue(generatedFeaturesFile.exists()); - assertTrue(getLogTail(), lastModified < generatedFeaturesFile.lastModified()); + // assertTrue(generatedFeaturesFile.exists()); + // assertTrue(getLogTail(), lastModified < generatedFeaturesFile.lastModified()); assertTrue(targetGeneratedFeaturesFile.exists()); + assertTrue(getLogTail(), lastModified < targetGeneratedFeaturesFile.lastModified()); + //assertTrue(targetGeneratedFeaturesFile.exists()); // verify that mpHealth-3.0 is now in the generated features file - assertTrue(getLogTail(), verifyLogMessageExists("mpHealth-3.1", 10000, generatedFeaturesFile)); + // assertTrue(getLogTail(), verifyLogMessageExists("mpHealth-3.1", 10000, generatedFeaturesFile)); + assertTrue(getLogTail(), verifyLogMessageExists("mpHealth-3.1", 10000, targetGeneratedFeaturesFile)); assertTrue(getLogTail(), verifyLogMessageExists("mpHealth-3.1", 10000)); // should appear in the message "CWWKF0012I: The server installed the following features:" // verify that mpHealth-2.2 is no longer in the generated features file - assertFalse(getLogTail(), verifyLogMessageExists("mpHealth-2.2", 10000, generatedFeaturesFile)); + // assertFalse(getLogTail(), verifyLogMessageExists("mpHealth-2.2", 10000, generatedFeaturesFile)); + assertFalse(getLogTail(), verifyLogMessageExists("mpHealth-2.2", 10000, targetGeneratedFeaturesFile)); } } diff --git a/liberty-maven-plugin/src/it/dev-it/src/test/java/net/wasdev/wlp/test/dev/it/DevTest.java b/liberty-maven-plugin/src/it/dev-it/src/test/java/net/wasdev/wlp/test/dev/it/DevTest.java index 54bcae1f1..ea6247302 100644 --- a/liberty-maven-plugin/src/it/dev-it/src/test/java/net/wasdev/wlp/test/dev/it/DevTest.java +++ b/liberty-maven-plugin/src/it/dev-it/src/test/java/net/wasdev/wlp/test/dev/it/DevTest.java @@ -1,5 +1,5 @@ /******************************************************************************* - * (c) Copyright IBM Corporation 2019, 2022. + * (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. @@ -276,7 +276,7 @@ public void generateFeatureTest() throws Exception { int runGenerateFeaturesCount = countOccurrences(RUNNING_GENERATE_FEATURES, logFile); int installedFeaturesCount = countOccurrences(SERVER_INSTALLED_FEATURES, logFile); - File newFeatureFile = getGeneratedFeaturesFile(); + // File newFeatureFile = getGeneratedFeaturesFile(); File newTargetFeatureFile = getTargetGeneratedFeaturesFile(); File serverXmlFile = new File(tempProj, "/src/main/liberty/config/server.xml"); assertTrue(serverXmlFile.exists()); @@ -293,11 +293,11 @@ public void generateFeatureTest() throws Exception { verifyFileExists(helloBatchObj, 15000); // ... and run the proper mojo. assertTrue(verifyLogMessageExists(RUNNING_GENERATE_FEATURES, 10000, ++runGenerateFeaturesCount)); // mojo ran - assertTrue(verifyFileExists(newFeatureFile, 5000)); // mojo created file + // assertTrue(verifyFileExists(newFeatureFile, 5000)); // mojo created file assertTrue(verifyFileExists(newTargetFeatureFile, 5000)); // dev mode copied file - assertTrue(verifyLogMessageExists("batch-1.0", 10000, newFeatureFile)); - assertTrue(verifyLogMessageExists(NEW_FILE_INFO_MESSAGE, 10000, newFeatureFile)); - assertTrue(verifyLogMessageExists(SERVER_XML_COMMENT, 10000, serverXmlFile)); + assertTrue(verifyLogMessageExists("batch-1.0", 10000, newTargetFeatureFile)); + assertTrue(verifyLogMessageExists(NEW_FILE_INFO_MESSAGE, 10000, newTargetFeatureFile)); + // assertTrue(verifyLogMessageExists(SERVER_XML_COMMENT, 10000, serverXmlFile)); // "CWWKF0012I: The server installed the following features:" assume batch-1.0 is in there // batch-1.0 pulls in other features that can take a long time to download. assertTrue(verifyLogMessageExists(SERVER_INSTALLED_FEATURES, 123000, ++installedFeaturesCount)); @@ -345,7 +345,7 @@ public void generateFeatureTest() throws Exception { writer.write("o\n"); // on optimize regenerate writer.flush(); assertTrue(verifyLogMessageExists(GENERATE_FEATURES, 10000, logFile, ++generateFeaturesCount)); - assertTrue(verifyLogMessageExists("batch-1.0", 10000, newFeatureFile, 0)); // exist 0 times + assertTrue(verifyLogMessageExists("batch-1.0", 10000, newTargetFeatureFile, 0)); // exist 0 times // Check for server response to newly generated feature list. assertTrue(verifyLogMessageExists(SERVER_UPDATE_COMPLETE, 10000, serverUpdateCount+1)); // Need to ensure server finished updating before the next test starts. diff --git a/liberty-maven-plugin/src/it/dev-it/src/test/java/net/wasdev/wlp/test/dev/it/MultiModuleUpdatePomsTest.java b/liberty-maven-plugin/src/it/dev-it/src/test/java/net/wasdev/wlp/test/dev/it/MultiModuleUpdatePomsTest.java index be9d05d09..0798ad340 100644 --- a/liberty-maven-plugin/src/it/dev-it/src/test/java/net/wasdev/wlp/test/dev/it/MultiModuleUpdatePomsTest.java +++ b/liberty-maven-plugin/src/it/dev-it/src/test/java/net/wasdev/wlp/test/dev/it/MultiModuleUpdatePomsTest.java @@ -1,5 +1,5 @@ /******************************************************************************* - * (c) Copyright IBM Corporation 2022. + * (c) Copyright IBM Corporation 2022, 2025 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -55,7 +55,8 @@ public void updatePomsTest() throws Exception { int earTestsCount = countOccurrences("guide-maven-multimodules-ear tests compilation was successful.", logFile); // verify that generated-features.xml file exists - File newFeatureFile = getGeneratedFeaturesFile("ear"); + // File newFeatureFile = getGeneratedFeaturesFile("ear"); + File newFeatureFile = getTargetGeneratedFeaturesFile("ear"); assertTrue(getLogTail(), verifyFileExists(newFeatureFile, 1000)); long newFeatureFileLastModified = newFeatureFile.lastModified(); waitLongEnough(); diff --git a/liberty-maven-plugin/src/it/generate-features-it/src/test/java/net/wasdev/wlp/test/dev/it/BaseGenerateFeaturesTest.java b/liberty-maven-plugin/src/it/generate-features-it/src/test/java/net/wasdev/wlp/test/dev/it/BaseGenerateFeaturesTest.java index f5d2c10bb..c8c44d0dc 100644 --- a/liberty-maven-plugin/src/it/generate-features-it/src/test/java/net/wasdev/wlp/test/dev/it/BaseGenerateFeaturesTest.java +++ b/liberty-maven-plugin/src/it/generate-features-it/src/test/java/net/wasdev/wlp/test/dev/it/BaseGenerateFeaturesTest.java @@ -1,5 +1,5 @@ /******************************************************************************* - * (c) Copyright IBM Corporation 2022. + * (c) Copyright IBM Corporation 2022, 2025 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -63,11 +63,14 @@ public class BaseGenerateFeaturesTest { static File targetDir; static String processOutput = ""; static File newFeatureFile; + static File newFeatureFileSrc; static File pom; static File serverXmlFile; static final String GENERATED_FEATURES_FILE_NAME = "generated-features.xml"; - static final String GENERATED_FEATURES_FILE_PATH = "/src/main/liberty/config/configDropins/overrides/" + GENERATED_FEATURES_FILE_NAME; + static final String GENERATED_FEATURES_FILE_PATH = "/target/liberty/wlp/usr/servers/defaultServer/configDropins/overrides/" + GENERATED_FEATURES_FILE_NAME; + static final String GENERATED_FEATURES_FILE_PATH_SRC = "/src/main/liberty/config/configDropins/overrides/" + GENERATED_FEATURES_FILE_NAME; + static final String SERVER_MISSING_MESSAGE = "The 'generate-features' goal requires an existing Liberty server"; protected static void setUpBeforeTest(String projectRoot) throws IOException, InterruptedException { basicProj = new File(projectRoot); @@ -81,6 +84,7 @@ protected static void setUpBeforeTest(String projectRoot) throws IOException, In assertTrue(logFile.createNewFile()); newFeatureFile = new File(tempProj, GENERATED_FEATURES_FILE_PATH); + newFeatureFileSrc = new File(tempProj, GENERATED_FEATURES_FILE_PATH_SRC); pom = new File(tempProj, "pom.xml"); assertTrue(pom.exists()); replaceVersion(tempProj); @@ -223,7 +227,12 @@ protected static Set readFeatures(File configurationFile) throws Excepti } protected void runCompileAndGenerateFeatures() throws IOException, InterruptedException { - runProcess("compile liberty:generate-features"); + runProcess("clean compile liberty:create liberty:generate-features"); + } + + protected void runCompileAndGenerateFeaturesToSrc() throws IOException, InterruptedException { + // do not create liberty when generating to src + runProcess("clean compile liberty:generate-features -DgenerateToSrc=true"); } protected void runGenerateFeaturesGoal() throws IOException, InterruptedException { diff --git a/liberty-maven-plugin/src/it/generate-features-it/src/test/java/net/wasdev/wlp/test/dev/it/GenerateFeaturesTest.java b/liberty-maven-plugin/src/it/generate-features-it/src/test/java/net/wasdev/wlp/test/dev/it/GenerateFeaturesTest.java index 379a66bba..53136e9e9 100644 --- a/liberty-maven-plugin/src/it/generate-features-it/src/test/java/net/wasdev/wlp/test/dev/it/GenerateFeaturesTest.java +++ b/liberty-maven-plugin/src/it/generate-features-it/src/test/java/net/wasdev/wlp/test/dev/it/GenerateFeaturesTest.java @@ -1,5 +1,5 @@ /******************************************************************************* - * (c) Copyright IBM Corporation 2022. + * (c) Copyright IBM Corporation 2022, 2025 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -52,15 +52,26 @@ public void cleanUp() throws Exception { @Test public void basicTest() throws Exception { - runCompileAndGenerateFeatures(); - // verify that the target directory was created + executeBasicTests(false); + } + + private void executeBasicTests(boolean generateToSrc) throws Exception { + File featureFile; + if (generateToSrc) { + featureFile = newFeatureFileSrc; + runCompileAndGenerateFeaturesToSrc(); + } else { + featureFile = newFeatureFile; + runCompileAndGenerateFeatures(); + } + // verify that the target directory was created by compile goal assertTrue(targetDir.exists()); // verify that the generated features file was created - assertTrue(formatOutput(processOutput), newFeatureFile.exists()); + assertTrue(formatOutput(processOutput), featureFile.exists()); // verify that the correct features are in the generated-features.xml - Set features = readFeatures(newFeatureFile); + Set features = readFeatures(featureFile); Set expectedFeatures = getExpectedGeneratedFeaturesSet(); assertEquals(expectedFeatures.size(), features.size()); assertEquals(expectedFeatures, features); @@ -72,11 +83,26 @@ public void basicTest() throws Exception { "\n", serverXmlFile); - runGenerateFeaturesGoal(); // no additional features should be generated - assertTrue(newFeatureFile.exists()); - features = readFeatures(newFeatureFile); - assertEquals(0, features.size()); + if (generateToSrc) { + // In src dir the generated file is preserved and contains no new features + runCompileAndGenerateFeaturesToSrc(); + assertTrue(featureFile.exists()); + features = readFeatures(featureFile); + assertEquals(formatOutput(processOutput), 0, features.size()); + } else { + // the server dir is in target directory and a mvn clean is performed so it has been removed + runCompileAndGenerateFeatures(); + assertFalse(featureFile.exists()); // after clean no feature file is created + } + } + + @Test + public void generateToSrcTest() throws Exception { + newFeatureFile.delete(); // delete file if it exists but do not assert + assertFalse(newFeatureFileSrc.exists()); // assuming no other test creates this file + executeBasicTests(true); + assertTrue(newFeatureFileSrc.delete()); // clean up the generated file } @Test @@ -91,6 +117,18 @@ public void noClassFiles() throws Exception { assertTrue(processOutput.contains(GenerateFeaturesMojo.NO_CLASSES_DIR_WARNING)); } + @Test + public void noServer() throws Exception { + // do not create the server before running generate-features + runGenerateFeaturesGoal(); + + // verify that generated features file was not created + assertFalse(newFeatureFile.exists()); + + // verify server not found warning message + assertTrue(processOutput.contains(SERVER_MISSING_MESSAGE)); + } + @Test public void customFeaturesTest() throws Exception { // complete the setup of the test @@ -115,7 +153,7 @@ public void customFeaturesTest() throws Exception { @Test public void serverXmlCommentNoFMTest() throws Exception { // initially the expected comment is not found in server.xml - assertFalse(verifyLogMessageExists(GenerateFeaturesMojo.FEATURES_FILE_MESSAGE, 10, serverXmlFile)); + // assertFalse(verifyLogMessageExists(GenerateFeaturesMojo.FEATURES_FILE_MESSAGE, 10, serverXmlFile)); // also we wish to test behaviour when there is no element so test that assertFalse(verifyLogMessageExists("", 10, serverXmlFile)); @@ -128,8 +166,8 @@ public void serverXmlCommentNoFMTest() throws Exception { Charset charset = StandardCharsets.UTF_8; String serverXmlContents = new String(Files.readAllBytes(serverXmlFile.toPath()), charset); serverXmlContents = "\n" + serverXmlContents; - assertTrue(serverXmlContents, - verifyLogMessageExists(GenerateFeaturesMojo.FEATURES_FILE_MESSAGE, 100, serverXmlFile)); + // assertTrue(serverXmlContents, + // verifyLogMessageExists(GenerateFeaturesMojo.FEATURES_FILE_MESSAGE, 100, serverXmlFile)); } @Test @@ -142,7 +180,7 @@ public void serverXmlCommentFMTest() throws Exception { serverXmlFile); // initially the expected comment is not found in server.xml - assertFalse(verifyLogMessageExists(GenerateFeaturesMojo.FEATURES_FILE_MESSAGE, 10, serverXmlFile)); + // assertFalse(verifyLogMessageExists(GenerateFeaturesMojo.FEATURES_FILE_MESSAGE, 10, serverXmlFile)); runCompileAndGenerateFeatures(); @@ -153,8 +191,8 @@ public void serverXmlCommentFMTest() throws Exception { Charset charset = StandardCharsets.UTF_8; String serverXmlContents = new String(Files.readAllBytes(serverXmlFile.toPath()), charset); serverXmlContents = "\n" + serverXmlContents; - assertTrue(serverXmlContents, - verifyLogMessageExists(GenerateFeaturesMojo.FEATURES_FILE_MESSAGE, 100, serverXmlFile)); + // assertTrue(serverXmlContents, + // verifyLogMessageExists(GenerateFeaturesMojo.FEATURES_FILE_MESSAGE, 100, serverXmlFile)); } /** diff --git a/liberty-maven-plugin/src/it/generate-features-it/src/test/java/net/wasdev/wlp/test/dev/it/MultiModuleGenerateFeaturesTest.java b/liberty-maven-plugin/src/it/generate-features-it/src/test/java/net/wasdev/wlp/test/dev/it/MultiModuleGenerateFeaturesTest.java index 707420936..c2ee7eb92 100644 --- a/liberty-maven-plugin/src/it/generate-features-it/src/test/java/net/wasdev/wlp/test/dev/it/MultiModuleGenerateFeaturesTest.java +++ b/liberty-maven-plugin/src/it/generate-features-it/src/test/java/net/wasdev/wlp/test/dev/it/MultiModuleGenerateFeaturesTest.java @@ -1,5 +1,5 @@ /******************************************************************************* - * (c) Copyright IBM Corporation 2022. + * (c) Copyright IBM Corporation 2022, 2025 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -40,6 +40,7 @@ public void setUp() throws Exception { assertTrue(pom.exists()); replaceVersion(new File(tempProj, "pom")); // "pom" module is liberty configuration module newFeatureFile = new File(tempProj, "pom" + GENERATED_FEATURES_FILE_PATH); + newFeatureFileSrc = new File(tempProj, "pom" + GENERATED_FEATURES_FILE_PATH_SRC); serverXmlFile = new File(tempProj, "pom/src/main/liberty/config/server.xml"); targetDir = new File(tempProj, "war/target"); runProcess("install"); @@ -48,14 +49,20 @@ public void setUp() throws Exception { @Override protected void runCompileAndGenerateFeatures() throws IOException, InterruptedException { - runProcess("compile io.openliberty.tools:liberty-maven-plugin:" + System.getProperty("mavenPluginVersion") - + ":generate-features"); + String lmp = "io.openliberty.tools:liberty-maven-plugin:" + System.getProperty("mavenPluginVersion"); + runProcess("clean compile " + lmp + ":create " + lmp + ":generate-features"); + } + + @Override + protected void runCompileAndGenerateFeaturesToSrc() throws IOException, InterruptedException { + String lmp = "io.openliberty.tools:liberty-maven-plugin:" + System.getProperty("mavenPluginVersion"); + runProcess("clean compile " + lmp + ":generate-features -DgenerateToSrc=true"); } @Override protected void runGenerateFeaturesGoal() throws IOException, InterruptedException { runProcess("io.openliberty.tools:liberty-maven-plugin:" + System.getProperty("mavenPluginVersion") - + ":generate-features"); + + ":generate-features "); } @Override diff --git a/liberty-maven-plugin/src/main/java/io/openliberty/tools/maven/BasicSupport.java b/liberty-maven-plugin/src/main/java/io/openliberty/tools/maven/BasicSupport.java index 9a98d5299..1b06fcd14 100644 --- a/liberty-maven-plugin/src/main/java/io/openliberty/tools/maven/BasicSupport.java +++ b/liberty-maven-plugin/src/main/java/io/openliberty/tools/maven/BasicSupport.java @@ -22,9 +22,7 @@ import java.text.MessageFormat; import java.util.ArrayList; import java.util.Enumeration; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.Properties; import java.util.ResourceBundle; import java.util.zip.ZipEntry; @@ -61,8 +59,6 @@ public abstract class BasicSupport extends AbstractLibertySupport { protected boolean defaultOutputDirSet = false; - protected boolean skipServerConfigSetup = false; - /** * Skips the specific goal */ @@ -206,10 +202,6 @@ protected void init() throws MojoExecutionException { super.init(); - if (skipServerConfigSetup) { - return; - } - try { // First check if installDirectory is set, if it is, then we can skip this if (installDirectory != null) { diff --git a/liberty-maven-plugin/src/main/java/io/openliberty/tools/maven/server/DevMojo.java b/liberty-maven-plugin/src/main/java/io/openliberty/tools/maven/server/DevMojo.java index 11dca8229..f9173b59e 100644 --- a/liberty-maven-plugin/src/main/java/io/openliberty/tools/maven/server/DevMojo.java +++ b/liberty-maven-plugin/src/main/java/io/openliberty/tools/maven/server/DevMojo.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. @@ -137,8 +137,15 @@ public class DevMojo extends LooseAppSupport { @Parameter(property = "container", defaultValue = "false") private boolean container; - @Parameter(property = "generateFeatures", defaultValue = "false") - private boolean generateFeatures; + @Parameter(property = "generateFeatures", defaultValue = "true") + private boolean generateFeatures; + + /** + * If generateToSrc is true, then create the file containing new features in the src directory + * Otherwise, place the file in the target directory where the Liberty server is defined. + */ + @Parameter(property = "generateToSrc", defaultValue = "false") + private boolean generateToSrc; /** * Whether to recompile dependencies. Defaults to false for single module @@ -353,7 +360,7 @@ public DevMojoUtil(File installDir, File userDir, File serverDirectory, File sou File testSourceDirectory, File configDirectory, File projectDirectory, File multiModuleProjectDirectory, List resourceDirs, JavaCompilerOptions compilerOptions, String mavenCacheLocation, List upstreamProjects, List upstreamMavenProjects, boolean recompileDeps, - File pom, Map> parentPoms, boolean generateFeatures, boolean skipInstallFeature, + File pom, Map> parentPoms, boolean generateFeatures, boolean generateToSrc, boolean skipInstallFeature, Set compileArtifactPaths, Set testArtifactPaths, List webResourceDirs, File serverOutputDirectory) throws IOException, PluginExecutionException { super(new File(project.getBuild().getDirectory()), serverDirectory, sourceDirectory, testSourceDirectory, configDirectory, projectDirectory, multiModuleProjectDirectory, resourceDirs, changeOnDemandTestsAction, hotTests, skipTests, @@ -361,7 +368,7 @@ public DevMojoUtil(File installDir, File userDir, File serverDirectory, File sou ((long) (compileWait * 1000L)), libertyDebug, false, false, pollingTest, container, containerfile, containerBuildContext, containerRunOpts, containerBuildTimeout, skipDefaultPorts, compilerOptions, keepTempContainerfile, mavenCacheLocation, upstreamProjects, recompileDeps, project.getPackaging(), - pom, parentPoms, generateFeatures, compileArtifactPaths, testArtifactPaths, webResourceDirs, compileMojoError); + pom, parentPoms, generateFeatures, generateToSrc, compileArtifactPaths, testArtifactPaths, webResourceDirs, compileMojoError); this.libertyDirPropertyFiles = LibertyPropFilesUtility.getLibertyDirectoryPropertyFiles(new CommonLogger(getLog()), installDir, userDir, serverDirectory, serverOutputDirectory); @@ -449,7 +456,7 @@ public void libertyCreate() throws PluginExecutionException { } @Override - public boolean libertyGenerateFeatures(Collection classes, boolean optimize) { + public boolean libertyGenerateFeatures(Collection classes, boolean optimize, boolean genToSrc, boolean useTmpDirOut, boolean useTmpDirIn) { try { if (classes != null) { Element[] classesElem = new Element[classes.size()]; @@ -459,11 +466,10 @@ public boolean libertyGenerateFeatures(Collection classes, boolean optim i++; } // generate features for only the classFiles passed - runLibertyMojoGenerateFeatures(element(name("classFiles"), classesElem), optimize); + runLibertyMojoGenerateFeatures(element(name("classFiles"), classesElem), optimize, genToSrc, useTmpDirOut, useTmpDirIn); } else { - // pass null for classFiles so that features are generated for ALL of the - // classes - runLibertyMojoGenerateFeatures(null, optimize); + // pass null for classFiles so that features are generated for ALL of the classes + runLibertyMojoGenerateFeatures(null, optimize, genToSrc, useTmpDirOut, useTmpDirIn); } return true; // successfully generated features } catch (MojoExecutionException e) { @@ -786,19 +792,28 @@ public boolean updateArtifactPaths(ProjectModule projectModule, boolean redeploy // detect compile dependency changes if (!dependencyListsEquals(getCompileDependency(deps), getCompileDependency(oldDeps))) { + boolean generateFeaturesSuccess = false; // optimize generate features if (generateFeatures) { getLog().debug("Detected a change in the compile dependencies for " + buildFile + " , regenerating features"); - boolean generateFeaturesSuccess = libertyGenerateFeatures(null, true); - if (generateFeaturesSuccess) { - util.getJavaSourceClassPaths().clear(); + // If generateToSrc is false then we must copy new generated features file from temp dir to server dir after install + generateFeaturesSuccess = optimizeGenerateFeatures(!generateToSrc, false); + // install new generated features because deploy will copy config files to server dir. + // It will not trigger install-feature if the feature list has not changed + util.installFeaturesToTempDir(generateFeaturesFile, generateFeaturesOutputDir, null, generateFeaturesSuccess); + // When generating to the src dir, mojo deploy will copy the files from src to the server. + // When not generating to the src dir, we must copy the generated features file here. + if (!generateToSrc) { + // Copy the file here to be used by updateExistingFeatures() below + util.copyGeneratedFeaturesFile(serverDirectory); // finalize the generate-features operation } - // install new generated features, will not trigger install-feature if the feature list has not changed - util.installFeaturesToTempDir(generatedFeaturesFile, configDirectory, null, - generateFeaturesSuccess); } runLibertyMojoDeploy(); + // Update the features after deploy mojo has copied the config files to server dir + if (generateFeaturesSuccess) { + updateExistingFeatures(); // update the dev mode cache of features in the server + } } } } catch (ProjectBuildingException | DependencyResolutionRequiredException | IOException @@ -996,6 +1011,7 @@ public boolean recompileBuildFile(File buildFile, Set compileArtifactPat boolean redeployApp = false; boolean runBoostPackage = false; boolean optimizeGenerateFeatures = false; + boolean generateFeaturesSuccess = false; ProjectBuildingResult build; try { @@ -1110,19 +1126,17 @@ public boolean recompileBuildFile(File buildFile, Set compileArtifactPat compileArtifactPaths.addAll(project.getCompileClasspathElements()); testArtifactPaths.addAll(project.getTestClasspathElements()); - boolean generateFeaturesSuccess = false; if (optimizeGenerateFeatures && generateFeatures) { getLog().debug("Detected a change in the compile dependencies, regenerating features"); // always optimize generate features on dependency change - generateFeaturesSuccess = libertyGenerateFeatures(null, true); - if (generateFeaturesSuccess) { - util.getJavaSourceClassPaths().clear(); - } else { + // If generateToSrc is false then we must copy (below) new generated features file from temp dir to server dir + generateFeaturesSuccess = optimizeGenerateFeatures(!generateToSrc, false); + util.installFeaturesToTempDir(generateFeaturesFile, generateFeaturesOutputDir, null, generateFeaturesSuccess); + if (!generateFeaturesSuccess) { installFeature = false; // skip installing features if generate features fails } - } - + // We don't currently have the ability to dynamically add new directories to be watched // There is so much that we are dynamically able to do that this could be surprising. // For now issue a warning @@ -1161,9 +1175,16 @@ public boolean recompileBuildFile(File buildFile, Set compileArtifactPat } else if (createServer) { runLibertyMojoCreate(); } else if (redeployApp) { - util.installFeaturesToTempDir(generatedFeaturesFile, configDirectory, null, - generateFeaturesSuccess); + // Copy the file here to be used by updateExistingFeatures() below + // If generateToSrc is false then we must copy new generated features file from temp dir to server dir after install + if (generateFeaturesSuccess && !generateToSrc) { + util.copyGeneratedFeaturesFile(serverDirectory); // finalize the generate-features operation + } runLibertyMojoDeploy(); + // Update the features after deploy mojo has copied the config files to server dir and generated features file added + if (generateFeaturesSuccess) { + updateExistingFeatures(); // update the dev mode cache of features in the server + } } if (installFeature) { runLibertyMojoInstallFeature(null, null, super.getContainerName()); @@ -1534,35 +1555,18 @@ private void doDevMode() throws MojoExecutionException { getLog().info("Running boost:package"); runBoostMojo("package"); } else { + // If generate features to server directory then create server first. if (generateFeatures) { - // generate features on startup - provide all classes and only user specified - // features to binary scanner - try { - 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(); - } - getLog().warn( - "The source configuration directory will be modified. Features will automatically be generated in a new file: " - + generatedFileCanonicalPath); - runLibertyMojoGenerateFeatures(null, true); - } catch (MojoExecutionException e) { - if (e.getCause() != null && e.getCause() instanceof PluginExecutionException) { - // PluginExecutionException indicates that the binary scanner jar could not be found - getLog().error(e.getMessage() + ".\nDisabling the automatic generation of features."); - generateFeatures = false; - } else { - throw new MojoExecutionException(e.getMessage() - + " To disable the automatic generation of features, start dev mode with -DgenerateFeatures=false.", - e); - } + if (generateToSrc) { + generateFeaturesOnStartup(); + runLibertyMojoCreate(); + } else { + runLibertyMojoCreate(); + generateFeaturesOnStartup(); } + } else { + runLibertyMojoCreate(); } - runLibertyMojoCreate(); // If non-container, install features before starting server. Otherwise, user // should have "RUN features.sh" in their Containerfile/Dockerfile if they want features to be // installed. @@ -1668,7 +1672,7 @@ private void doDevMode() throws MojoExecutionException { util = new DevMojoUtil(installDirectory, userDirectory, serverDirectory, sourceDirectory, testSourceDirectory, configDirectory, project.getBasedir(), multiModuleProjectDirectory, resourceDirs, compilerOptions, settings.getLocalRepository(), upstreamProjects, upstreamMavenProjects, recompileDeps, pom, parentPoms, - generateFeatures, skipInstallFeature, compileArtifactPaths, testArtifactPaths, webResourceDirs, new File(super.outputDirectory,serverName)); + generateFeatures, generateToSrc, skipInstallFeature, compileArtifactPaths, testArtifactPaths, webResourceDirs, new File(super.outputDirectory,serverName)); } catch (IOException | PluginExecutionException |DependencyResolutionRequiredException e) { throw new MojoExecutionException("Error initializing dev mode.", e); } @@ -1701,6 +1705,39 @@ private void doDevMode() throws MojoExecutionException { } } + private void generateFeaturesOnStartup() throws MojoExecutionException { + // generate features on startup - provide all classes and only user specified + // features to binary scanner + try { + 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) { + getLog().info( + "The source configuration directory will be modified. Features will automatically be generated in a new file: " + + generatedFileCanonicalPath); + } + // During dev mode start up the server is not running yet so we will generate features to the correct + // output directory and then install features in the next step. + runLibertyMojoGenerateFeatures(null, true, generateToSrc, false, false); + } catch (MojoExecutionException e) { + if (e.getCause() != null && e.getCause() instanceof PluginExecutionException) { + // PluginExecutionException indicates that the binary scanner jar could not be found + getLog().error(e.getMessage() + ".\nDisabling the automatic generation of features."); + generateFeatures = false; + } else { + throw new MojoExecutionException(e.getMessage() + + " To disable the automatic generation of features, type 'g' and press 'Enter' once dev mode is running or restart dev mode with -DgenerateFeatures=false.", + e); + } + } + } + @Override public void execute() throws MojoExecutionException { init(); @@ -2320,7 +2357,7 @@ protected void runLibertyMojoCreate() throws MojoExecutionException { * @throws MojoExecutionException */ @Override - protected void runLibertyMojoGenerateFeatures(Element classFiles, boolean optimize) throws MojoExecutionException { - super.runLibertyMojoGenerateFeatures(classFiles, optimize); + protected void runLibertyMojoGenerateFeatures(Element classFiles, boolean optimize, boolean genToSrc, boolean useTmpDirOut, boolean useTmpDirIn) throws MojoExecutionException { + super.runLibertyMojoGenerateFeatures(classFiles, optimize, genToSrc, useTmpDirOut, useTmpDirIn); } } diff --git a/liberty-maven-plugin/src/main/java/io/openliberty/tools/maven/server/GenerateFeaturesMojo.java b/liberty-maven-plugin/src/main/java/io/openliberty/tools/maven/server/GenerateFeaturesMojo.java index cefc8be81..d1294a319 100644 --- a/liberty-maven-plugin/src/main/java/io/openliberty/tools/maven/server/GenerateFeaturesMojo.java +++ b/liberty-maven-plugin/src/main/java/io/openliberty/tools/maven/server/GenerateFeaturesMojo.java @@ -1,5 +1,5 @@ /** - * (C) Copyright IBM Corporation 2021, 2024. + * (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. You may obtain a copy of @@ -21,11 +21,14 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.TransformerException; +import org.apache.maven.artifact.Artifact; +import org.apache.maven.artifact.versioning.ComparableVersion; import org.apache.maven.execution.ProjectDependencyGraph; import org.apache.maven.model.Dependency; import org.apache.maven.plugin.MojoExecutionException; @@ -35,16 +38,14 @@ import org.apache.maven.project.ProjectBuildingException; import org.apache.maven.project.ProjectBuildingResult; import org.w3c.dom.Element; -import org.xml.sax.SAXException; import io.openliberty.tools.common.plugins.config.ServerConfigXmlDocument; -import io.openliberty.tools.common.plugins.config.XmlDocument; import io.openliberty.tools.common.plugins.util.BinaryScannerUtil; import static io.openliberty.tools.common.plugins.util.BinaryScannerUtil.*; import io.openliberty.tools.common.plugins.util.PluginExecutionException; import io.openliberty.tools.common.plugins.util.ServerFeatureUtil; import io.openliberty.tools.common.plugins.util.ServerFeatureUtil.FeaturesPlatforms; -import io.openliberty.tools.maven.ServerFeatureSupport; +import io.openliberty.tools.common.plugins.util.VersionUtility; /** * This mojo generates the features required in the featureManager element in @@ -57,16 +58,20 @@ @Mojo(name = "generate-features", threadSafe = true) public class GenerateFeaturesMojo extends PluginConfigSupport { - public static final String FEATURES_FILE_MESSAGE = "The Liberty Maven Plugin has generated Liberty features necessary for your application in " - + GENERATED_FEATURES_FILE_PATH; public static final String HEADER = "This file was generated by the Liberty Maven Plugin and will be overwritten on subsequent runs of the liberty:generate-features goal." - + "\n It is recommended that you do not edit this file and that you commit this file to your version control."; + + "\n It is recommended that you do not edit this file."; public static final String GENERATED_FEATURES_COMMENT = "The following features were generated based on API usage detected in your application"; public static final String NO_NEW_FEATURES_COMMENT = "No additional features generated"; public static final String NO_CLASSES_DIR_WARNING = "Could not find classes directory to generate features against. Liberty features will not be generated. " + "Ensure your project has first been compiled."; - - private File binaryScanner; + public static final String VERSIONLESS_FEATURE_DETECTED_DEVMODE = "Versionless features are detected in the server configuration. " + + "If you would like to continue using the automatic generation of features, remove all versionless features from your server configuration. " + + "If you would like to continue using versionless features, you can disable the automatic generation of features."; + public static final String VERSIONLESS_FEATURE_DETECTED = "Versionless features are detected in the server configuration. " + + "To use the 'generate-features' goal, remove all versionless features from your server configuration. " + + "If you would like to continue using versionless features, you cannot use the 'generate-features' goal."; + private static final String OPEN_LIBERTY_PRODUCT_ID = "io.openliberty"; + private static final String WEBSPHERE_LIBERTY_PRODUCT_ID = "com.ibm.websphere.appserver.runtime"; @Parameter(property = "classFiles") private List classFiles; @@ -78,16 +83,71 @@ public class GenerateFeaturesMojo extends PluginConfigSupport { @Parameter(property = "optimize", defaultValue = "true") private boolean optimize; + /** + * If generateToSrc is true, then create the file containing new features in the src directory + * Otherwise, place the file in the target directory where the Liberty server is defined. + */ + @Parameter(property = "generateToSrc", defaultValue = "false") + private boolean generateToSrc; + + /** + * The isDevMode parameter is for internal use only. It is not for users. + * This parameter must only be set to true when this mojo is called from dev mode and false otherwise. + * This parameter is added to support generateToSrc and to cause writing the generated features file + * to the server directory (if it exists) in stand-alone mode (not dev mode) in order to save the user + * having to copy it manually. + */ + @Parameter(property = "isDevMode", defaultValue = "false") + private boolean isDevMode; + + /** + * The useTempDirAsOutput parameter is for internal use only. It is not for users. + * The parameter is only used when generateToSrc is false meaning we generate to serverDir. + * It is needed in dev mode because the server is running and we need to ensure the features + * that are generated are installed before we update a running server. + * When the parameter is true we will write the generated features file to the special generate-features + * temp directory. + */ + @Parameter(property = "useTempDirAsOutput", defaultValue = "false") + private boolean useTempDirAsOutput; + + /** + * The useTempDirAsContext parameter is for internal use only. It is not for users. + * It is needed in dev mode when the user updates a server config file which might affect + * the features that will be generated. In such a case we will required the caller to copy the + * serverDir configuration files into the special generate-features temp directory and augment it + * with the file changed by the user. We do not do this all the time because of the performance + * cost of copying all the files. + * When the parameter is true we will use the generate-features temp directory as the context for + * feature generation. + */ + @Parameter(property = "useTempDirAsContext", defaultValue = "false") + private boolean useTempDirAsContext; + + /** + * Generating features is performed relative to a certain server. We only generate features + * that are missing from a server config. By default we generate features that are missing + * from the server directory in target/liberty/wlp/usr/servers/. + * If generateToSrc is specified then we generate features which are missing from the Liberty + * config specified in the src directory src/main/liberty/config. + * We will select one server config as the context of this operation. + */ + private File generationContextDir; + + /** + * Liberty features are generated in the context of a certain server and they are stored in + * an XML file in the configDir, the serverDir or a temporary directory. When using dev + * mode we must write to the tempDir and call install-features before we can use the + * features in the running server. When not using dev mode we can just write the features + * to the configDir or serverDir as indicated by the generateToSrc option. + */ + private File generationOutputDir; + @Override protected void init() throws MojoExecutionException { - // @see io.openliberty.tools.maven.BasicSupport#init() skip server config - // setup as generate features does not require the server to be set up install - // dir, wlp dir, outputdir, etc. - this.skipServerConfigSetup = true; - super.init(); - } - + } + @Override public void execute() throws MojoExecutionException { init(); @@ -104,7 +164,7 @@ public void execute() throws MojoExecutionException { } /** - * Generates features for the application given the API usage detected and + * Generates features for the application given the API usage detected by the binary scanner and * taking any user specified features into account * * @throws MojoExecutionException @@ -129,7 +189,7 @@ private void generateFeatures() throws MojoExecutionException, PluginExecutionEx getLog().info("\nSkipping module " + project.getArtifactId() + " which is not configured for the generate-features goal.\n"); return; } - + List downstreamProjects = graph.getDownstreamProjects(project, true); if (!downstreamProjects.isEmpty()) { getLog().debug("Downstream projects: " + downstreamProjects); @@ -156,13 +216,47 @@ private void generateFeatures() throws MojoExecutionException, PluginExecutionEx return; } } + // Ensure server dir exists to generate features to the $serverDirectory/configDropins/overrides/generated-features.xml + if (!generateToSrc && !serverDirectory.exists()) { + throw new MojoExecutionException("The 'generate-features' goal requires an existing Liberty server in directory " + serverDirectory.getPath() + ". Please run the 'liberty:create' goal before 'generate-features'."); + } + // Detect if there is a generate-features.xml file in src already when generating to server dir + if (!generateToSrc && new File(configDirectory, GENERATED_FEATURES_FILE_PATH).exists()) { + if (isDevMode) { // this is serious because dev mode will overwrite the generated file when copying from src + getLog().error("A 'generated-features.xml' file was detected in the source Liberty configuration directory. " + + "It will overwrite the file generated to the server directory by the automatic generation of features. " + + "To continue to generate features to the server directory, you must delete the 'generated-features.xml' file " + + "in the source Liberty configuration directory. To generate features to the source Liberty configuration " + + "directory instead, you can type 's' + Enter."); + } else { // command line mojo just a warning that this configuration is not expected + getLog().warn("A 'generated-features.xml' file was detected in the source Liberty configuration directory. " + + "To generate features to the server directory, it is recommended you delete the 'generated-features.xml' " + + "file in the source Liberty configuration directory. To generate features to the source Liberty " + + "configuration directory, you must use the option -DgenerateToSrc=true."); + } + } + + if (useTempDirAsContext) { + // When this parameter is true it is required that the caller has copied the config into this dir. + generationContextDir = getGeneratedFeaturesTempDir(); + } else { + // The config dir is the one in the src directory. Otherwise generate for the target/liberty/wlp dir. + generationContextDir = generateToSrc ? configDirectory : serverDirectory; + } + // When using dev mode we always generate to a temporary directory so we can call install before writing to server dir. + generationOutputDir = useTempDirAsOutput ? getGeneratedFeaturesTempDir() : generationContextDir; - binaryScanner = getBinaryScannerJarFromRepository(); - BinaryScannerHandler binaryScannerHandler = new BinaryScannerHandler(binaryScanner); + // The executable file used to scan binaries for the Liberty features they use. + File binaryScannerJar = getBinaryScannerJarFromRepository(); + BinaryScannerHandler binaryScannerHandler = new BinaryScannerHandler(binaryScannerJar); getLog().debug("--- Generate Features values ---"); - getLog().debug("Binary scanner jar: " + binaryScanner.getName()); + getLog().debug("Binary scanner jar: " + binaryScannerJar.getName()); getLog().debug("optimize generate features: " + optimize); + getLog().debug("useTempDirAsOutput (dev mode only): " + useTempDirAsOutput); + getLog().debug("useTempDirAsContext (dev mode only): " + useTempDirAsContext); + getLog().debug("generate to directory: " + generationOutputDir.getAbsolutePath()); + if (classFiles != null && !classFiles.isEmpty()) { getLog().debug("Generate features for the following class files: " + classFiles.toString()); } @@ -184,7 +278,7 @@ private void generateFeatures() throws MojoExecutionException, PluginExecutionEx // getSpecifiedFeatures may not return the features in the correct case // Set featuresToInstall = getSpecifiedFeatures(null); - // get existing server features from source directory + // get existing server features from directory of interest ServerFeatureUtil servUtil = getServerFeatureUtil(true, null); Set generatedFiles = new HashSet(); @@ -209,13 +303,28 @@ private void generateFeatures() throws MojoExecutionException, PluginExecutionEx // user specified features getLog().warn(NO_CLASSES_DIR_WARNING); } - eeVersion = getEEVersion(mavenProjects); - mpVersion = getMPVersion(mavenProjects); + eeVersion = getEEVersion(mavenProjects, servUtil); + mpVersion = getMPVersion(mavenProjects, servUtil); String logLocation = project.getBuild().getDirectory(); String eeVersionArg = composeEEVersion(eeVersion); String mpVersionArg = composeMPVersion(mpVersion); - scannedFeatureList = binaryScannerHandler.runBinaryScanner(nonCustomFeatures, classFiles, directories, logLocation, eeVersionArg, mpVersionArg, optimize); + + Map featureListFileMap = new HashMap(); + String libertyGroupId = getLibertyRuntimeGroupId(); + getLog().debug("Resolve the liberty groupId used to fetch feature lists, getLibertyRuntimeGroupId()="+libertyGroupId); + if (WEBSPHERE_LIBERTY_PRODUCT_ID.equals(libertyGroupId)) { + File baseFeatureListFile = getWebSphereFeatureListFile(FEATURE_LIST_BASE); + featureListFileMap.put(WSBASE_FEATURELIST_KEY, baseFeatureListFile); + File coreFeatureListFile = getWebSphereFeatureListFile(FEATURE_LIST_CORE); + featureListFileMap.put(WSCORE_FEATURELIST_KEY, coreFeatureListFile); + } else if (OPEN_LIBERTY_PRODUCT_ID.equals(libertyGroupId)) { + File featureListFile = getOpenFeatureListFile(); + featureListFileMap.put(OL_FEATURELIST_KEY, featureListFile); + } // else should not happen, just pass empty map + + scannedFeatureList = binaryScannerHandler.runBinaryScanner(nonCustomFeatures, classFiles, directories, logLocation, + eeVersionArg, mpVersionArg, featureListFileMap, optimize); } catch (BinaryScannerUtil.NoRecommendationException noRecommendation) { throw new MojoExecutionException(String.format(BinaryScannerUtil.BINARY_SCANNER_CONFLICT_MESSAGE3, noRecommendation.getConflicts())); } catch (BinaryScannerUtil.FeatureModifiedException featuresModified) { @@ -250,6 +359,8 @@ private void generateFeatures() throws MojoExecutionException, PluginExecutionEx } catch (BinaryScannerUtil.IllegalTargetException illegalTargets) { String messages = buildInvalidArgExceptionMessage(illegalTargets.getEELevel(), illegalTargets.getMPLevel(), eeVersion, mpVersion); throw new MojoExecutionException(messages); + } catch (BinaryScannerUtil.VersionlessFeatureDetectedException versionless) { + throw new MojoExecutionException(isDevMode ? VERSIONLESS_FEATURE_DETECTED_DEVMODE : VERSIONLESS_FEATURE_DETECTED); } catch (PluginExecutionException x) { // throw an error when there is a problem not caught in runBinaryScanner() Object o = x.getCause(); @@ -267,7 +378,7 @@ private void generateFeatures() throws MojoExecutionException, PluginExecutionEx servUtil.setLowerCaseFeatures(false); // get set of user defined features so they can be omitted from the generated // file that will be written - FeaturesPlatforms fp = servUtil.getServerFeatures(configDirectory, serverXmlFile, new HashMap(), + FeaturesPlatforms fp = servUtil.getServerFeatures(generationContextDir, serverXmlFile, new HashMap(), generatedFiles); Set userDefinedFeatures = optimize ? existingFeatures : (fp !=null) ? fp.getFeatures(): new HashSet(); getLog().debug("User defined features:" + userDefinedFeatures); @@ -278,14 +389,20 @@ private void generateFeatures() throws MojoExecutionException, PluginExecutionEx } getLog().debug("Features detected by binary scanner which are not in server.xml" + missingLibertyFeatures); - File newServerXmlSrc = new File(configDirectory, GENERATED_FEATURES_FILE_PATH); - File serverXml = findConfigFile("server.xml", serverXmlFile); - ServerConfigXmlDocument doc = getServerXmlDocFromConfig(serverXml); - getLog().debug("Xml document we'll try to update after generate features doc=" + doc + " file=" + serverXml); + // generate the new features into an xml file in the correct context directory + // The ServerConfigXmlDocument class will create the directories if needed. + File generatedXmlFile = new File(generationOutputDir, GENERATED_FEATURES_FILE_PATH); + + // For standalone goal (not dev mode) with generateToSrc=true, also write to server dir if it exists + boolean shouldWriteToServerDir = !isDevMode && generateToSrc && (serverDirectory != null) && serverDirectory.exists(); + File serverDirXmlFile = null; + if (shouldWriteToServerDir) { + serverDirXmlFile = new File(serverDirectory, GENERATED_FEATURES_FILE_PATH); + } try { if (missingLibertyFeatures.size() > 0) { - Set existingGeneratedFeatures = getGeneratedFeatures(servUtil, newServerXmlSrc); + Set existingGeneratedFeatures = getGeneratedFeatures(servUtil, generatedXmlFile); if (!missingLibertyFeatures.equals(existingGeneratedFeatures)) { // Create special XML file to contain generated features. ServerConfigXmlDocument configDocument = ServerConfigXmlDocument.newInstance(); @@ -296,55 +413,105 @@ private void generateFeatures() throws MojoExecutionException, PluginExecutionEx getLog().debug(String.format("Adding missing feature %s to %s.", missing, GENERATED_FEATURES_FILE_PATH)); configDocument.createFeature(missing); } - // Generate log message before writing file as the file change event kicks off other dev mode actions + // Output log messages before writing file as the file change event kicks off other dev mode actions getLog().info("Generated the following features: " + missingLibertyFeatures); - configDocument.writeXMLDocument(newServerXmlSrc); - getLog().debug("Created file " + newServerXmlSrc); - // Add a reference to this new file in existing server.xml. - addGenerationCommentToConfig(doc, serverXml); + printCommonMessages(eeVersion, mpVersion, generatedXmlFile); + configDocument.writeXMLDocument(generatedXmlFile); + getLog().debug("Created file " + generatedXmlFile.getAbsolutePath()); + + // For standalone mode with generateToSrc=true, also write to server directory + if (shouldWriteToServerDir) { + if (writeToServerDir(configDocument, serverDirXmlFile)) { + getLog().debug("Also created file " + serverDirXmlFile.getAbsolutePath()); + } + } } else { - getLog().info("Regenerated the following features: " + missingLibertyFeatures); + getLog().info("No change to the generated feature list: " + missingLibertyFeatures); + printCommonMessages(eeVersion, mpVersion, generatedXmlFile); } } else { getLog().info("No additional features were generated."); - if (newServerXmlSrc.exists()) { + if (generatedXmlFile.exists()) { // generated-features.xml exists but no additional features were generated // create empty features list with comment ServerConfigXmlDocument configDocument = ServerConfigXmlDocument.newInstance(); configDocument.createComment(HEADER); Element featureManagerElem = configDocument.createFeatureManager(); configDocument.createComment(featureManagerElem, NO_NEW_FEATURES_COMMENT); - configDocument.writeXMLDocument(newServerXmlSrc); + configDocument.writeXMLDocument(generatedXmlFile); + + // For standalone mode with generateToSrc=true, also write to server directory + if (shouldWriteToServerDir && serverDirXmlFile.exists()) { + writeToServerDir(configDocument, serverDirXmlFile); + } } } } catch (ParserConfigurationException | TransformerException | IOException e) { getLog().debug("Exception creating the server features file", e); - throw new MojoExecutionException( - "Automatic generation of features failed. Error attempting to create the " - + GENERATED_FEATURES_FILE_NAME - + ". Ensure your id has write permission to the server configuration directory.", - e); + throw new MojoExecutionException( + "Automatic generation of features failed. Error attempting to create the " + + generatedXmlFile.getAbsolutePath() + + ". Ensure your ID has write permission to the directory the generated features file will be written to.", + e); } + + } + + /* + * This method prints the output for both cases when features are generated (i.e., new features were generated or the same set) + */ + private void printCommonMessages(String eeVersion, String mpVersion, File generatedXmlFile) { + getLog().info("The generated feature list has been written to the following file: " + generatedXmlFile.getAbsolutePath()); + if (eeVersion == null && mpVersion == null) { + getLog().info("Verify the generated feature list meets the needs of your application." + + " If you see features missing or features at unexpected versions, please add the expected features to your server configuration" + + (isDevMode ? "." : " and run the generate-features goal again.")); + } + } + + /* + * This routine writes an xml document to the specififed file and returns false if there is an exception + * while writing or true otherwise. + */ + private boolean writeToServerDir(ServerConfigXmlDocument configDocument, File serverDirXmlFile) { + try { + configDocument.writeXMLDocument(serverDirXmlFile); + } catch (TransformerException | IOException e) { + getLog().warn("Failed to write generated-features.xml to server directory: " + + serverDirXmlFile.getAbsolutePath() + ". Ensure your ID has write permission to the server configuration directory. " + + "Message from the Exception: " + e.getMessage()); + return false; + } + return true; } - // Get the features from the server config and optionally exclude the specified config files from the search. private Set getServerFeatures(ServerFeatureUtil servUtil, Set generatedFiles, boolean excludeGenerated) { + return getServerFeaturesPlatforms(servUtil, generatedFiles, excludeGenerated, true); + } + private Set getServerPlatforms(ServerFeatureUtil servUtil, Set generatedFiles, boolean excludeGenerated) { + return getServerFeaturesPlatforms(servUtil, generatedFiles, excludeGenerated, false); // platforms + } + + // Get the features from the server config and optionally exclude the specified config files from the search. + private Set getServerFeaturesPlatforms(ServerFeatureUtil servUtil, Set generatedFiles, boolean excludeGenerated, boolean features) { servUtil.setLowerCaseFeatures(false); // if optimizing, ignore generated files when passing in existing features to // binary scanner - FeaturesPlatforms fp = servUtil.getServerFeatures(configDirectory, serverXmlFile, + FeaturesPlatforms fp = servUtil.getServerFeatures(generationContextDir, serverXmlFile, new HashMap(), excludeGenerated ? generatedFiles : null); // pass generatedFiles to exclude them servUtil.setLowerCaseFeatures(true); if (fp == null) { return new HashSet(); - } - return fp.getFeatures(); + } else if (features) { + return fp.getFeatures(); + } // else + return fp.getPlatforms(); } - // returns the features specified in the generated-features.xml file + // returns the features specified in the generated-features.xml file in the generation context directory private Set getGeneratedFeatures(ServerFeatureUtil servUtil, File generatedFeaturesFile) { servUtil.setLowerCaseFeatures(false); - FeaturesPlatforms result = servUtil.getServerXmlFeatures(new FeaturesPlatforms(), configDirectory, + FeaturesPlatforms result = servUtil.getServerXmlFeatures(new FeaturesPlatforms(), generationContextDir, generatedFeaturesFile, null, null); servUtil.setLowerCaseFeatures(true); Set features = new HashSet(); @@ -354,6 +521,11 @@ private Set getGeneratedFeatures(ServerFeatureUtil servUtil, File genera return features; } + // returns the hidden directory we use for generate-features special purposes + private File getGeneratedFeaturesTempDir() { + return new File(project.getBuild().getDirectory(), GENERATED_FEATURES_TEMP_DIR); + } + /** * Gets the binary scanner jar file from the local cache. * Downloads it first from connected repositories such as Maven Central if a newer release is available than the cached version. @@ -375,51 +547,94 @@ private File getBinaryScannerJarFromRepository() throws PluginExecutionException } } - private ServerConfigXmlDocument getServerXmlDocFromConfig(File serverXml) { - if (serverXml == null || !serverXml.exists()) { + /* + * Gets the file containing the Open Liberty feature list from the local cache. + * Downloads it first from connected repositories such as Maven Central if a newer release is available than the cached version. + * Note: Maven updates artifacts daily by default based on the last updated timestamp. Users should use 'mvn -U' to force + * updates if needed. + * + * @return The File object of the feature list in the local cache or null if version number not available + */ + private static String OPEN_LIBERTY_FEATURE_LIST_START = "25.0.0.7"; + private File getOpenFeatureListFile() throws MojoExecutionException { + String libertyVersion = getLibertyRuntimeVersion(); + if (libertyVersion == null) { return null; } - try { - return ServerConfigXmlDocument.newInstance(serverXml); - } catch (ParserConfigurationException | SAXException | IOException e) { - getLog().debug("Exception creating server.xml object model", e); + // Feature lists were first published for 25.0.0.7. For liberty releases prior to this simply use the + // earliest available feature list, 25.0.0.7. + if (VersionUtility.compareArtifactVersion(libertyVersion, OPEN_LIBERTY_FEATURE_LIST_START, true) < 0) { + libertyVersion = OPEN_LIBERTY_FEATURE_LIST_START; } - return null; + libertyVersion = "[" + libertyVersion + "]"; // Maven syntax to specify an exact version, not a range + return getArtifact(OL_FEATURELIST_GROUP_ID, OL_FEATURELIST_ARTIFACT_ID, OL_FEATURELIST_TYPE, libertyVersion).getFile(); } - /** - * Remove the comment in server.xml that warns we created another file with features in it. + /* + * Gets the file containing the indicated Websphere feature list from the local cache. + * Downloads it first from connected repositories such as Maven Central if a newer release is available than the cached version. + * Note: Maven updates artifacts daily by default based on the last updated timestamp. Users should use 'mvn -U' to force + * updates if needed. + * + * @return The File object of the feature list in the local cache. */ - private void removeGenerationCommentFromConfig(ServerConfigXmlDocument doc, File serverXml) { - if (doc == null) { - return; + private static String FEATURE_LIST_BASE = "base"; + private static String FEATURE_LIST_CORE = "core"; + private File getWebSphereFeatureListFile(String featureListVar) throws MojoExecutionException { + // For releases earlier than 25.0.0.7 use 25.0.0.7 Maven coordinates (batch 1). + // For releases 25.0.0.8 to 25.0.0.12 use batch 2 coordinates. + // For 26.0.0.1 and later use the third batch. + String libertyGroupId, libertyArtifactId; + String libertyVersion = getLibertyRuntimeVersion(); + if (libertyVersion == null) { + return null; } - try { - doc.removeFMComment(FEATURES_FILE_MESSAGE); - doc.writeXMLDocument(serverXml); - } catch (IOException | TransformerException e) { - getLog().debug("Exception removing comment from server.xml", e); + // Publishing started with 25.0.0.7 so for any liberty <25.0.0.7 use the 07 values + if (VersionUtility.compareArtifactVersion(libertyVersion, WS_FEATURE_LIST_VERSION_BATCH1, true) <= 0) { + libertyGroupId = WS_FEATURELIST_GROUP_ID_BATCH1; + libertyArtifactId = (featureListVar.equals(FEATURE_LIST_BASE)) ? WS_BASE_FEATURE_LIST_ARTIFACT_ID_BATCH1 : WS_CORE_FEATURE_LIST_ARTIFACT_ID_BATCH1; + libertyVersion = WS_FEATURE_LIST_VERSION_BATCH1; // must set for versions before 07 + } else if (VersionUtility.compareArtifactVersion(libertyVersion, WS_FEATURE_LIST_VERSION_BATCH3, true) < 0) { + // 25.0.0.8 to 25.0.0.12 + libertyGroupId = WS_FEATURELIST_GROUP_ID_BATCH2; + libertyArtifactId = (featureListVar.equals(FEATURE_LIST_BASE)) ? WS_BASE_FEATURE_LIST_ARTIFACT_ID_BATCH2 : WS_CORE_FEATURE_LIST_ARTIFACT_ID_BATCH2; + } else { // 26.0.0.1 and up + libertyGroupId = WS_FEATURELIST_GROUP_ID_BATCH3; + libertyArtifactId = (featureListVar.equals(FEATURE_LIST_BASE)) ? WS_BASE_FEATURE_LIST_ARTIFACT_ID_BATCH3 : WS_CORE_FEATURE_LIST_ARTIFACT_ID_BATCH3; } - return; + libertyVersion = "[" + libertyVersion + "]"; // Maven syntax to specify an exact version, not a range + getLog().debug("WebSphere Liberty feature list coordinates, libertyGroupId="+libertyGroupId+" libertyArtifactId="+libertyArtifactId+" WS_FEATURELIST_TYPE="+WS_FEATURELIST_TYPE+" libertyVersion="+libertyVersion); + return getArtifact(libertyGroupId, libertyArtifactId, WS_FEATURELIST_TYPE, libertyVersion).getFile(); } - /** - * Add a comment to server.xml to warn them we created another file with features in it. - * Only writes the file if the comment does not exist yet. - */ - private void addGenerationCommentToConfig(ServerConfigXmlDocument doc, File serverXml) { - if (doc == null) { - return; + // resolve the Liberty version from one of the sources + private String getLibertyRuntimeVersion() throws MojoExecutionException { + // Check if libertyRuntimeVersion property is set (highest priority) + if (libertyRuntimeVersion != null && !libertyRuntimeVersion.isEmpty()) { + return libertyRuntimeVersion; + } else if (assemblyArtifact != null) { + // Resolve the artifact to get its version + Artifact artifact = getResolvedArtifact(assemblyArtifact); + if (artifact != null) { + return artifact.getVersion(); + } } - try { - if (doc.createFMComment(FEATURES_FILE_MESSAGE)) { - doc.writeXMLDocument(serverXml); - XmlDocument.addNewlineBeforeFirstElement(serverXml); + return null; + } + + // resolve the Liberty GroupId from one of the sources + private String getLibertyRuntimeGroupId() throws MojoExecutionException { + // Check if libertyRuntimeGroupId property is set (highest priority) + if (libertyRuntimeGroupId != null && !libertyRuntimeGroupId.isEmpty()) { + return libertyRuntimeGroupId; + } else if (assemblyArtifact != null) { + // Resolve the artifact to get its values + Artifact artifact = getResolvedArtifact(assemblyArtifact); + if (artifact != null) { + return artifact.getGroupId(); } - } catch (IOException | TransformerException e) { - getLog().debug("Exception adding comment to server.xml", e); } - return; + return null; } // Return a list containing the classes directory of the Maven projects (upstream projects and main project) @@ -462,36 +677,36 @@ private String getClassesDirectory(String outputDir) { * @return the latest version of EE detected across multiple project modules, * null if an EE version is not found or the version number is out of range */ - public String getEEVersion(List mavenProjects) { + private static final String JAKARTA_PLATFORM_NAME="jakartaee-"; // jakartaee-10.0 etc. + private static final String JAVAEE_PLATFORM_NAME="javaee-"; // javaee-7.0 etc. + public String getEEVersion(List mavenProjects, ServerFeatureUtil servUtil) { String eeVersion = null; if (mavenProjects != null) { Set eeVersionsDetected = new HashSet(); for (MavenProject mavenProject : mavenProjects) { - try { - String ver = getEEVersion(mavenProject); - getLog().debug("Java and/or Jakarta EE umbrella dependency found in project: " + mavenProject.getName()); - if (ver != null) { - eeVersionsDetected.add(ver); - } - } catch (NoUmbrellaDependencyException e) { - // umbrella dependency does not exist, do nothing - } - } - if (!eeVersionsDetected.isEmpty()) { - eeVersion = eeVersionsDetected.iterator().next(); - // if multiple EE versions are found across multiple modules, return the latest version - for (String ver : eeVersionsDetected) { - if (ver.compareTo(eeVersion) > 0) { - eeVersion = ver; - } + String ver = getEEVersion(mavenProject); + getLog().debug("Java and/or Jakarta EE umbrella dependency version: " + ver + " found in project: " + mavenProject.getName()); + if (ver != null) { + eeVersionsDetected.add(ver); } } + eeVersion = findMaxVersion(eeVersionsDetected); if (eeVersionsDetected.size() > 1) { - getLog().debug( - "Multiple Java and/or Jakarta EE versions found across multiple project modules, using the latest version (" - + eeVersion + ") found to generate Liberty features."); + getLog().info("Multiple Java EE and/or Jakarta EE versions found across multiple project modules, using the latest version (" + + eeVersion + ") found to generate Liberty features."); } } + // if the dependencies do not indicate the Jakarta version then reference the platform specified in server.xml + // E.g. pom may specify jakarta.persistence:jakarta.persistence-api:2.2.3 to compile but does not specify Jakarta 9.1 + if (eeVersion == null) { + Set platformVersions = new HashSet(); + // Gather all Jakarta EE platform versions + platformVersions.addAll(getAllPlatformVersions(JAKARTA_PLATFORM_NAME, servUtil)); + // Gather all Java EE platform versions + platformVersions.addAll(getAllPlatformVersions(JAVAEE_PLATFORM_NAME, servUtil)); + // Find the maximum version from all platforms + eeVersion = findMaxVersion(platformVersions); + } return eeVersion; } @@ -502,22 +717,57 @@ public String getEEVersion(List mavenProjects) { * * @param project the MavenProject to search * @return EE major version corresponding to the EE umbrella dependency - * @throws NoUmbrellaDependencyException indicates that the umbrella dependency was not found */ - private String getEEVersion(MavenProject project) throws NoUmbrellaDependencyException { - if (project != null) { - List dependencies = project.getDependencies(); - for (Dependency d : dependencies) { - if (!d.getScope().equals("provided")) { - continue; - } - if ((d.getGroupId().equals("javax") && d.getArtifactId().equals("javaee-api")) || - (d.getGroupId().equals("jakarta.platform") && d.getArtifactId().equals("jakarta.jakartaee-api"))) { - return d.getVersion(); + private String getEEVersion(MavenProject project) { + if (project == null) { + return null; + } + List dependencies = project.getDependencies(); + Set eeVersionsDetected = new HashSet(); + for (Dependency d : dependencies) { + String scope = d.getScope(); + String groupId = d.getGroupId(); + String artifactId = d.getArtifactId(); + if (scope == null || groupId == null || artifactId == null) { + continue; + } + if (!scope.equals("provided") && !scope.equals("compile") && !scope.equals("import")) { + continue; + } + if ((groupId.equals("javax") && artifactId.equals("javaee-api")) || + (groupId.equals("jakarta.platform") && + (artifactId.equals("jakarta.jakartaee-api") || + artifactId.equals("jakarta.jakartaee-web-api") || + artifactId.equals("jakarta.jakartaee-core-api") || + artifactId.equals("jakarta.jakartaee-bom") || + artifactId.equals("jakartaee-api-parent")))) { + eeVersionsDetected.add(d.getVersion()); + } + } + return findMaxVersion(eeVersionsDetected); + } + + // Find the highest version number in the set of strings + // returns null if the set of strings is empty + private String findMaxVersion(Set versionsDetected) { + String maxVersion = null; + if (!versionsDetected.isEmpty()) { + maxVersion = versionsDetected.iterator().next(); + if (versionsDetected.size() == 1) { + return maxVersion; + } + ComparableVersion cMaxVersion = new ComparableVersion(maxVersion); + // if multiple EE/MP versions are found across multiple modules, return the latest version + for (String ver : versionsDetected) { + ComparableVersion cVer = new ComparableVersion(ver); + getLog().debug("GenerateFeraturesMojo.findMaxVersion, ver=" + ver); + if (cVer.compareTo(cMaxVersion) > 0) { + maxVersion = ver; + cMaxVersion = cVer; } } } - throw new NoUmbrellaDependencyException(); + return maxVersion; } /** @@ -527,36 +777,28 @@ private String getEEVersion(MavenProject project) throws NoUmbrellaDependencyExc * @return the latest version of MP detected across multiple project modules, * null if an MP version is not found or the version number is out of range */ - public String getMPVersion(List mavenProjects) { + private static final String MP_PLATFORM_NAME="microProfile-"; // microProfile-7.0 etc. + public String getMPVersion(List mavenProjects, ServerFeatureUtil servUtil) { String mpVersion = null; if (mavenProjects != null) { Set mpVersionsDetected = new HashSet(); for (MavenProject mavenProject : mavenProjects) { - try { - String ver = getMPVersion(mavenProject); - getLog().debug("MicroProfile umbrella dependency found in project: " + mavenProject.getName()); - if (ver != null) { - mpVersionsDetected.add(ver); - } - } catch (NoUmbrellaDependencyException e) { - // umbrella dependency does not exist, do nothing - } - } - if (!mpVersionsDetected.isEmpty()) { - mpVersion = mpVersionsDetected.iterator().next(); - // if multiple MP versions are found across multiple modules, return the latest version - for (String ver : mpVersionsDetected) { - if (ver.compareTo(mpVersion) > 0) { - mpVersion = ver; - } + String ver = getMPVersion(mavenProject); + getLog().debug("MicroProfile umbrella dependency version: " + ver + " found in project: " + mavenProject.getName()); + if (ver != null) { + mpVersionsDetected.add(ver); } } + // if multiple MP versions are found across multiple modules, return the latest version + mpVersion = findMaxVersion(mpVersionsDetected); if (mpVersionsDetected.size() > 1) { - getLog().debug( - "Multiple MicroProfile versions found across multiple project modules, using the latest version (" - + mpVersion + ") found to generate Liberty features."); + getLog().info("Multiple MicroProfile versions found across multiple project modules, using the latest version (" + + mpVersion + ") found to generate Liberty features."); } } + if (mpVersion == null) { + mpVersion = findMaxVersion(getAllPlatformVersions(MP_PLATFORM_NAME, servUtil)); + } return mpVersion; } @@ -567,24 +809,39 @@ public String getMPVersion(List mavenProjects) { * * @param project the MavenProject to search * @return MP exact version code corresponding to the MP umbrella dependency - * @throws NoUmbrellaDependencyException indicates that the umbrella dependency was not found */ - public String getMPVersion(MavenProject project) throws NoUmbrellaDependencyException { // figure out correct level of MP from declared dependencies - if (project != null) { - List dependencies = project.getDependencies(); - for (Dependency d : dependencies) { - if (!d.getScope().equals("provided")) { - continue; - } - if (d.getGroupId().equals("org.eclipse.microprofile") && - d.getArtifactId().equals("microprofile")) { - return d.getVersion(); - } + public String getMPVersion(MavenProject project) { + if (project == null) { + return null; + } + List dependencies = project.getDependencies(); + for (Dependency d : dependencies) { + if (!d.getScope().equals("provided")) { + continue; + } + if (d.getGroupId().equals("org.eclipse.microprofile") && + d.getArtifactId().equals("microprofile")) { + return d.getVersion(); + } + } + return null; + } + + // Retrieve all platforms from the server.xml and related files that match the platform specified. + // Platforms have the format jakartaee-10.0 or microProfile-7.1. Return all version numbers (10.0, 7.1, etc.) that match. + private Set getAllPlatformVersions(String platformName, ServerFeatureUtil servUtil) { + Set platformVersions = new HashSet(); + Set platforms = getServerPlatforms(servUtil, null, false); + for (String p : platforms) { + getLog().debug("GenerateFeaturesMojo.getAllPlatformVersions, searching for platform:" + platformName + " platform=" + p); + if (p.startsWith(platformName)) { + platformVersions.add(p.substring(platformName.length())); } } - throw new NoUmbrellaDependencyException(); + return platformVersions; } + // Define the logging functions of the binary scanner handler and make it available in this plugin private class BinaryScannerHandler extends BinaryScannerUtil { BinaryScannerHandler(File scannerFile) { @@ -622,12 +879,4 @@ private MavenProject getMavenProject(File buildFile) throws ProjectBuildingExcep session.getProjectBuildingRequest().setResolveDependencies(true)); return build.getProject(); } - - /** - * Class to indicate that an umbrella dependency was not found in the build file - */ - public class NoUmbrellaDependencyException extends Exception { - private static final long serialVersionUID = 1L; - } - } diff --git a/liberty-maven-plugin/src/main/java/io/openliberty/tools/maven/server/StartDebugMojoSupport.java b/liberty-maven-plugin/src/main/java/io/openliberty/tools/maven/server/StartDebugMojoSupport.java index 7931950c0..948f7b44e 100644 --- a/liberty-maven-plugin/src/main/java/io/openliberty/tools/maven/server/StartDebugMojoSupport.java +++ b/liberty-maven-plugin/src/main/java/io/openliberty/tools/maven/server/StartDebugMojoSupport.java @@ -1,5 +1,5 @@ /** - * (C) Copyright IBM Corporation 2014, 2025. + * (C) Copyright IBM Corporation 2014, 2026 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -242,12 +242,16 @@ protected void runLibertyMojoInstallFeature(Element features, File serverDir, St runLibertyMojo("install-feature", config); } - protected void runLibertyMojoGenerateFeatures(Element classFiles, boolean optimize) throws MojoExecutionException { + protected void runLibertyMojoGenerateFeatures(Element classFiles, boolean optimize, boolean generateToSrc, boolean useTmpDirOut, boolean useTmpDirIn) throws MojoExecutionException { Xpp3Dom config = ExecuteMojoUtil.getPluginGoalConfig(getLibertyPlugin(), "generate-features", getLog()); if (classFiles != null) { config = Xpp3Dom.mergeXpp3Dom(configuration(classFiles), config); } + config.addChild(element(name("isDevMode"), "true").toDom()); config.addChild(element(name("optimize"), Boolean.toString(optimize)).toDom()); + config.addChild(element(name("generateToSrc"), Boolean.toString(generateToSrc)).toDom()); + config.addChild(element(name("useTempDirAsOutput"), Boolean.toString(useTmpDirOut)).toDom()); + config.addChild(element(name("useTempDirAsContext"), Boolean.toString(useTmpDirIn)).toDom()); runLibertyMojo("generate-features", config); }