Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ java {
}

def libertyAntVersion = "1.9.18"
def libertyCommonVersion = "1.8.42-SNAPSHOT"
def libertyCommonVersion = "1.8.42"


dependencies {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ import org.gradle.api.artifacts.ResolvedDependency
import org.gradle.api.artifacts.UnknownConfigurationException
import org.gradle.api.file.FileCollection
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.SourceSet
import org.gradle.api.tasks.SourceSetContainer
import org.gradle.api.tasks.TaskAction
import org.w3c.dom.Element

Expand Down Expand Up @@ -392,22 +394,24 @@ class DeployTask extends AbstractServerTask {
if (!isCurrentProject && siblingProject != null) {
Element archive = looseApp.addArchive(parent, "/WEB-INF/lib/" + dep.getName());
//Add sibling project class directories to <archive/> as <dir>
siblingProject.sourceSets.main.getOutput().getClassesDirs().each {
SourceSetContainer siblingSourceSets = siblingProject.extensions.findByType(SourceSetContainer)
SourceSet siblingMainSourceSet = siblingSourceSets?.findByName('main')
siblingMainSourceSet?.getOutput()?.getClassesDirs()?.each {
looseApp.getConfig().addDir(archive, it, "/");
}
Task resourceTask = siblingProject.getTasks().findByPath(":" + projectDependencyString + ":processResources");
if (resourceTask != null && resourceTask.getDestinationDir() != null) {
looseApp.addOutputDir(archive, resourceTask.getDestinationDir(), "/");
}
File resourceDir = siblingProject.sourceSets.main.getOutput().getResourcesDir()
File resourceDir = siblingMainSourceSet?.getOutput()?.getResourcesDir()
File manifestFile = null
if (resourceDir.exists() && resourceDir.listFiles().length > 0) {
if (resourceDir != null && resourceDir.exists() && resourceDir.listFiles().length > 0) {
File metaInfDir = new File(resourceDir, "META-INF")
if (metaInfDir.exists() && metaInfDir.listFiles().length > 0) {
manifestFile = new File(metaInfDir, "MANIFEST.MF")
}
}
looseApp.addManifestFileWithParent(archive, manifestFile, resourceDir.getParentFile().getCanonicalPath());
looseApp.addManifestFileWithParent(archive, manifestFile, resourceDir?.getParentFile()?.getCanonicalPath());
} else if (FilenameUtils.getExtension(dep.getAbsolutePath()).equalsIgnoreCase("jar")) {
addLibrary(parent, looseApp, "/WEB-INF/lib/", dep);
} else {
Expand Down
61 changes: 37 additions & 24 deletions src/main/groovy/io/openliberty/tools/gradle/tasks/DevTask.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,13 @@ import io.openliberty.tools.gradle.utils.DevTaskHelper
import org.gradle.api.GradleException
import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration
import org.gradle.api.internal.file.DefaultFilePropertyFactory
import org.gradle.api.logging.LogLevel
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputDirectory
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.SourceSet
import org.gradle.api.tasks.SourceSetContainer
import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.options.Option
import org.gradle.testfixtures.ProjectBuilder
Expand Down Expand Up @@ -1210,7 +1210,7 @@ class DevTask extends AbstractFeatureTask {

// If a argument has not been set using CLI arguments set a default value
// Using the ServerExtension properties if available, otherwise use hardcoded defaults
private void initializeDefaultValues() throws Exception {
protected void initializeDefaultValues() throws Exception {
if (verifyAppStartTimeout == null) {
if (server.verifyAppStartTimeout != 0) {
verifyAppStartTimeout = server.verifyAppStartTimeout;
Expand Down Expand Up @@ -1271,14 +1271,27 @@ class DevTask extends AbstractFeatureTask {
void action() {
initializeDefaultValues();

SourceSet mainSourceSet = project.sourceSets.main;
SourceSet testSourceSet = project.sourceSets.test;
SourceSetContainer sourceSets = project.extensions.findByType(SourceSetContainer)
SourceSet mainSourceSet = sourceSets?.findByName('main')
SourceSet testSourceSet = sourceSets?.findByName('test')

sourceDirectory = mainSourceSet.java.srcDirs.iterator().next()
testSourceDirectory = testSourceSet.java.srcDirs.iterator().next()
DefaultFilePropertyFactory.DefaultDirectoryVar outputDirectory = mainSourceSet.java.classesDirectory;
DefaultFilePropertyFactory.DefaultDirectoryVar testOutputDirectory = testSourceSet.java.classesDirectory;
List<File> resourceDirs = mainSourceSet.resources.srcDirs.toList();
if (mainSourceSet != null && !mainSourceSet.java.srcDirs.isEmpty()) {
sourceDirectory = mainSourceSet.java.srcDirs.iterator().next()
} else {
sourceDirectory = new File(project.projectDir, "src/main/java")
Comment thread
cherylking marked this conversation as resolved.
}
if (testSourceSet != null && !testSourceSet.java.srcDirs.isEmpty()) {
testSourceDirectory = testSourceSet.java.srcDirs.iterator().next()
} else {
testSourceDirectory = new File(project.projectDir, "src/test/java")
}
File outputDirectory = mainSourceSet != null
? mainSourceSet.java.classesDirectory.asFile.get()
: new File(project.getLayout().getBuildDirectory().getAsFile().get(), "classes/java/main");
File testOutputDirectory = testSourceSet != null
? testSourceSet.java.classesDirectory.asFile.get()
: new File(project.getLayout().getBuildDirectory().getAsFile().get(), "classes/java/test");
List<File> resourceDirs = mainSourceSet != null ? mainSourceSet.resources.srcDirs.toList() : new ArrayList<File>();

File serverDirectory = getServerDir(project);
String serverName = server.name;
Expand All @@ -1300,7 +1313,7 @@ class DevTask extends AbstractFeatureTask {
} // else TODO check if the container is already running?

if (resourceDirs.isEmpty()) {
File defaultResourceDir = new File(project.getRootDir() + "/src/main/resources");
File defaultResourceDir = new File(project.projectDir, "src/main/resources");
logger.info("No resource directory detected, using default directory: " + defaultResourceDir);
resourceDirs.add(defaultResourceDir);
}
Expand Down Expand Up @@ -1464,7 +1477,7 @@ class DevTask extends AbstractFeatureTask {
// which is where the server.xml is located if a specific serverXmlFile
// configuration parameter is not specified.
try {
util.watchFiles(outputDirectory.get().asFile, testOutputDirectory.get().asFile, executor, serverXMLFile,
util.watchFiles(outputDirectory, testOutputDirectory, executor, serverXMLFile,
project.liberty.server.bootstrapPropertiesFile, project.liberty.server.jvmOptionsFile);
} catch (PluginScenarioException e) {
if (e.getMessage() != null) {
Expand All @@ -1475,26 +1488,26 @@ class DevTask extends AbstractFeatureTask {
}
}

private List<ProjectModule> getProjectModules() {
@Internal
protected List<ProjectModule> getProjectModules() {
List<ProjectModule> upstreamProjects = new ArrayList<ProjectModule>();
for (Project dependencyProject : DevTaskHelper.getAllUpstreamProjects(project)) {
// In Maven , there is a step to set compiler options for upstream project
// Gradle does not need to manually inject compiler options because
// we are directly calling compileJava task, which internally takes the compiler options
// from task definition or command line arguments
JavaCompilerOptions upstreamCompilerOptions = new JavaCompilerOptions();
SourceSet mainSourceSet = dependencyProject.sourceSets.main;
SourceSet testSourceSet = dependencyProject.sourceSets.test;
DefaultFilePropertyFactory.DefaultDirectoryVar outputDirectory = mainSourceSet.java.classesDirectory;
DefaultFilePropertyFactory.DefaultDirectoryVar testOutputDirectory = testSourceSet.java.classesDirectory;
SourceSetContainer depSourceSets = dependencyProject.extensions.findByType(SourceSetContainer)
SourceSet mainSourceSet = depSourceSets?.findByName('main')
SourceSet testSourceSet = depSourceSets?.findByName('test')
Set<String> compileArtifacts = new HashSet<String>();
Set<String> testArtifacts = new HashSet<String>();
File upstreamSourceDir = new File(mainSourceSet.java.sourceDirectories.asPath)
File upstreamOutputDir = outputDirectory.asFile.get();
File upstreamTestSourceDir = new File(testSourceSet.java.sourceDirectories.asPath);
File upstreamTestOutputDir = testOutputDirectory.asFile.get();
File upstreamSourceDir = (mainSourceSet != null && !mainSourceSet.java.srcDirs.isEmpty()) ? new File(mainSourceSet.java.sourceDirectories.asPath) : new File(dependencyProject.projectDir, "src/main/java");
File upstreamOutputDir = mainSourceSet != null ? mainSourceSet.java.classesDirectory.asFile.get() : new File(dependencyProject.layout.buildDirectory.asFile.get(), "classes/java/main");
File upstreamTestSourceDir = (testSourceSet != null && !testSourceSet.java.srcDirs.isEmpty()) ? new File(testSourceSet.java.sourceDirectories.asPath) : new File(dependencyProject.projectDir, "src/test/java");
File upstreamTestOutputDir = testSourceSet != null ? testSourceSet.java.classesDirectory.asFile.get() : new File(dependencyProject.layout.buildDirectory.asFile.get(), "classes/java/test");
// resource directories
List<File> upstreamResourceDirs = mainSourceSet.resources.srcDirs.toList();
List<File> upstreamResourceDirs = mainSourceSet != null ? mainSourceSet.resources.srcDirs.toList() : new ArrayList<File>();
//get gradle project properties. It is observed that project properties contain all gradle properties
// properties are overridden automatically with the highest precedence
// in gradle, we are only using skipTests
Expand Down Expand Up @@ -1530,7 +1543,7 @@ class DevTask extends AbstractFeatureTask {
return upstreamProjects;
}

private boolean isInstallDirChanged(Project project, File currentInstallDir) {
protected boolean isInstallDirChanged(Project project, File currentInstallDir) {
if (project.getLayout().getBuildDirectory().getAsFile().get().exists() && new File(project.getLayout().getBuildDirectory().getAsFile().get(), 'liberty-plugin-config.xml').exists()) {
XmlParser pluginXmlParser = new XmlParser()
Node libertyPluginConfig = pluginXmlParser.parse(new File(project.getLayout().getBuildDirectory().getAsFile().get(), 'liberty-plugin-config.xml'))
Expand All @@ -1550,7 +1563,7 @@ class DevTask extends AbstractFeatureTask {
}


private void addLibertyRuntimeProperties(BuildLauncher gradleBuildLauncher) {
protected void addLibertyRuntimeProperties(BuildLauncher gradleBuildLauncher) {
Set<Entry<Object, Object>> entries = project.getProperties().entrySet()
for (Entry<Object, Object> entry : entries) {
String key = (String) entry.getKey()
Expand All @@ -1572,7 +1585,7 @@ class DevTask extends AbstractFeatureTask {
}

// Get dev extension parameter values from build.gradle if not specified on the command line
private void processDevExtensionParams() throws Exception {
protected void processDevExtensionParams() throws Exception {
// process parameters from dev extension
if (container == null) {
boolean buildContainerSetting = project.liberty.dev.container; // get from build.gradle or from -Pdev_mode_container=true
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
/*
* (C) Copyright IBM Corporation 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 the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.openliberty.tools.gradle

import org.junit.AfterClass
import org.junit.BeforeClass
import org.junit.Test

import static org.junit.Assert.assertTrue

/**
* Integration test for the fix to:
* "Could not get unknown property 'main' for SourceSet container"
*
* Reproduces the exact user scenario: an EAR submodule that applies ONLY
* 'ear' + 'liberty' plugins (no 'java' plugin), while WAR and JAR submodules
* each apply 'java' themselves.
*
* Prior to the fix, DevTask.groovy accessed project.sourceSets.main using
* Groovy dynamic property syntax, which throws on Gradle 9 when the 'java'
* plugin is not applied (so 'main' source set does not exist).
*
* The fix switches to sourceSets.findByName('main') / findByName('test'),
* which returns null safely, and falls back to conventional default paths.
*
* This test verifies that libertyDev starts successfully on such a project
* and that hot-recompilation of a JAR module Java file still works.
*/
class TestMultiModuleEarNoJavaPluginDevMode extends BaseDevTest {

static File resourceDir = new File("build/resources/test/multi-module-loose-ear-no-java-plugin-test")
static File buildDir = new File(integTestDir, "/multi-module-loose-ear-no-java-plugin-test")
static String buildFilename = "build.gradle"

@BeforeClass
static void setup() throws IOException, InterruptedException, FileNotFoundException {
createDir(buildDir)
createTestProject(buildDir, resourceDir, buildFilename)
new File(buildDir, "build").mkdirs()

// Start libertyDev with --skipTests so we don't need test infrastructure.
// This exercises the fixed code path in DevTask.action() and
// DevTask.getProjectModules() for an EAR project without 'java' plugin.
runDevMode("--skipTests", buildDir)
}

/**
* Primary regression test: libertyDev must start without throwing
* "Could not get unknown property 'main' for SourceSet container"
*
* If the bug is present, setup() would have failed with a BuildException
* before this test even runs. The fact that the server reached dev mode
* proves the fix is effective.
*/
@Test
void earModuleWithoutJavaPluginStartsDevMode() throws Exception {
// Server startup is verified in setup() via verifyLogMessage("Liberty is running in dev mode.").
// This test documents the intent and provides a named regression marker.
assertTrue("libertyDev should be running in dev mode",
verifyLogMessage(5000, "Liberty is running in dev mode."))
}

/**
* Verify that modifying a Java source file in the JAR submodule
* (which DOES have the 'java' plugin) triggers recompilation while
* the EAR module's absence of sourceSets.main does not cause a crash.
*/
@Test
void modifyJavaFileInJarSubmoduleTriggersRecompilation() throws Exception {
File srcGreeter = new File(buildDir,
"jar/src/main/java/io/openliberty/guides/multimodules/lib/Greeter.java")
File targetGreeter = new File(buildDir,
"jar/build/classes/java/main/io/openliberty/guides/multimodules/lib/Greeter.class")

assertTrue("Greeter.java source file must exist", srcGreeter.exists())
assertTrue("Greeter.class must exist after initial build", targetGreeter.exists())

long lastModified = targetGreeter.lastModified()
waitLongEnough()

// Append a no-op comment to trigger the file watcher
BufferedWriter javaWriter = new BufferedWriter(new FileWriter(srcGreeter, true))
javaWriter.append(" // recompile trigger")
javaWriter.close()

assertTrue("Greeter.class should be recompiled after source change",
waitForCompilation(targetGreeter, lastModified, 12000))
}

/**
* Verify that modifying the EAR build.gradle triggers the expected
* "change detected in build file" dev mode warning.
*/
@Test
void modifyEarBuildGradleTriggersWarning() throws Exception {
waitLongEnough()

File earBuildGradle = new File(buildDir, "ear/build.gradle")
assertTrue("ear/build.gradle must exist", earBuildGradle.exists())

BufferedWriter buildWriter = new BufferedWriter(new FileWriter(earBuildGradle, true))
buildWriter.append(" // testing")
buildWriter.close()

assertTrue("Expected build file change warning in dev mode output",
verifyLogMessage(12000,
"A change was detected in a build file. The libertyDev task could not determine if a server restart is required. To restart server, type 'r' and press Enter."))
}

@AfterClass
static void cleanUpAfterClass() throws Exception {
String stdout = getContents(logFile, "Dev mode std output")
System.out.println(stdout)
String stderr = getContents(errFile, "Dev mode std error")
System.out.println(stderr)
cleanUpAfterClass(true)
}
}
Loading
Loading