diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 9d3d9eccf4..63b5ef6538 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -36,3 +36,23 @@ jobs: - name: Run tests run: | ./gradlew clean build jacocoTestReport --stacktrace + + gradle9-java17-test: + name: Gradle 9 compatibility test (Java 17) + runs-on: ubuntu-latest + permissions: + contents: read + env: + TERM: dumb + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-java@v3 + with: + distribution: 'adopt' + java-version: 17 + - uses: gradle/gradle-build-action@v2 + with: + gradle-version: 9.2.1 + - name: Run Gradle 9 compatibility test + run: | + gradle :jib-gradle-plugin:test --tests "*FilesTaskV2Test.testFilesTask_multiProjectComplexService" -x jacocoTestReport --stacktrace \ No newline at end of file diff --git a/build.gradle b/build.gradle index e83cba6046..47a94f676c 100644 --- a/build.gradle +++ b/build.gradle @@ -1,9 +1,24 @@ +import org.gradle.util.GradleVersion + +// Conditional plugin versions based on Gradle version +buildscript { + ext.isGradle9Plus = GradleVersion.current() >= GradleVersion.version('9.0') + ext.releasePluginVersion = isGradle9Plus ? '3.1.0' : '2.8.1' + ext.pluginPublishVersion = isGradle9Plus ? '1.2.0' : '0.21.0' + + repositories { + gradlePluginPortal() + } + dependencies { + classpath "net.researchgate:gradle-release:${releasePluginVersion}" + classpath "com.gradle.publish:plugin-publish-plugin:${pluginPublishVersion}" + } +} + // define all versioned plugins here and apply in subprojects as necessary without version plugins { id 'com.github.sherter.google-java-format' version '0.9' apply false id 'net.ltgt.errorprone' version '3.1.0' apply false - id 'net.researchgate.release' version '2.8.1' apply false - id 'com.gradle.plugin-publish' version '1.2.0' apply false id 'io.freefair.maven-plugin' version '5.3.3.3' apply false // apply so that we can collect quality metrics at the root project level @@ -79,18 +94,23 @@ subprojects { apply plugin: 'net.ltgt.errorprone' apply plugin: 'jacoco' - // Guava update breaks unit tests. Workaround mentioned in https://github.com/google/guava/issues/6612#issuecomment-1614992368. - sourceSets.all { - configurations.getByName(runtimeClasspathConfigurationName) { - attributes.attribute(Attribute.of("org.gradle.jvm.environment", String), "standard-jvm") - } - configurations.getByName(compileClasspathConfigurationName) { - attributes.attribute(Attribute.of("org.gradle.jvm.environment", String), "standard-jvm") + // Guava update breaks unit tests in Gradle 6.x. Workaround mentioned in https://github.com/google/guava/issues/6612#issuecomment-1614992368. + // Only needed for Gradle < 7, as Gradle 7+ handles this automatically + if (GradleVersion.current() < GradleVersion.version('7.0')) { + sourceSets.all { + configurations.getByName(runtimeClasspathConfigurationName) { + attributes.attribute(Attribute.of("org.gradle.jvm.environment", String), "standard-jvm") + } + configurations.getByName(compileClasspathConfigurationName) { + attributes.attribute(Attribute.of("org.gradle.jvm.environment", String), "standard-jvm") + } } } - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 + java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } compileJava.options.encoding = 'UTF-8' compileJava.options.compilerArgs += [ '-Xlint:deprecation' ] compileTestJava.options.compilerArgs += [ '-Xlint:deprecation' ] @@ -177,7 +197,7 @@ subprojects { /* TEST CONFIG */ tasks.withType(Test).configureEach { - reports.html.outputLocation = file("${reporting.baseDir}/${name}") + reports.html.outputLocation = file("${project.buildDir}/reports/${name}") } test { @@ -215,8 +235,7 @@ subprojects { configurations { integrationTestImplementation.extendsFrom testImplementation - integrationTestImplementation.setCanBeResolved(true) - integrationTestRuntime.extendsFrom testRuntime + integrationTestRuntimeOnly.extendsFrom testRuntimeOnly } // Integration tests must be run explicitly @@ -397,7 +416,12 @@ subprojects { sourceProjects += dependencyProject // if we find any project dependencies that were brought in transitively, go remove them project.configurations.implementation.dependencies.removeAll { d -> - return d instanceof ProjectDependency && sourceProjects.contains(d.dependencyProject) + if (d instanceof ProjectDependency) { + // Gradle 6.9.2 uses dependencyProject, Gradle 9+ uses path + def depProject = d.hasProperty('dependencyProject') ? d.dependencyProject : project.project(d.path) + return sourceProjects.contains(depProject) + } + return false } // adds dependencyProject's classes to jar (fat jar-esque) jar { diff --git a/jib-gradle-plugin/build.gradle b/jib-gradle-plugin/build.gradle index 46f416ab1e..61f1836c32 100644 --- a/jib-gradle-plugin/build.gradle +++ b/jib-gradle-plugin/build.gradle @@ -1,3 +1,5 @@ +import org.gradle.util.GradleVersion + plugins { id 'java-gradle-plugin' id 'net.researchgate.release' @@ -51,7 +53,7 @@ dependencies { /* RELEASE */ // Prepare release release { - tagTemplate = 'v$version-gradle' + tagTemplate = 'v$version-gradle-plugin' ignoredSnapshotDependencies = [ 'com.google.cloud.tools:jib-core', 'com.google.cloud.tools:jib-plugins-common', @@ -60,20 +62,32 @@ release { requireBranch = /^gradle-release-v\d+.*$/ //regex } } -// Gradle Plugin Portal releases -pluginBundle { - website = 'https://github.com/GoogleContainerTools/jib/' - vcsUrl = 'https://github.com/GoogleContainerTools/jib/' - tags = ['google', 'java', 'containers', 'docker', 'kubernetes', 'microservices'] + +// Gradle Plugin Portal releases - conditional based on Gradle version +if (GradleVersion.current() < GradleVersion.version('9.0')) { + // Gradle 6.x uses pluginBundle + pluginBundle { + website = 'https://github.com/GoogleContainerTools/jib/' + vcsUrl = 'https://github.com/GoogleContainerTools/jib/' + tags = ['google', 'java', 'containers', 'docker', 'kubernetes', 'microservices'] + } } gradlePlugin { + // Gradle 9+ uses these properties directly in gradlePlugin + if (GradleVersion.current() >= GradleVersion.version('9.0')) { + website = 'https://github.com/GoogleContainerTools/jib/' + vcsUrl = 'https://github.com/GoogleContainerTools/jib/' + } testSourceSets sourceSets.integrationTest, sourceSets.test plugins { jibPlugin { id = 'com.google.cloud.tools.jib' displayName = 'Jib' description = 'Containerize your Java application' + if (GradleVersion.current() >= GradleVersion.version('9.0')) { + tags = ['google', 'java', 'containers', 'docker', 'kubernetes', 'microservices'] + } implementationClass = 'com.google.cloud.tools.jib.gradle.JibPlugin' } } diff --git a/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/GradleProjectProperties.java b/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/GradleProjectProperties.java index 6a1978fe19..647ff100a6 100644 --- a/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/GradleProjectProperties.java +++ b/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/GradleProjectProperties.java @@ -224,7 +224,8 @@ public JibContainerBuilder createJibContainerBuilder( SourceSet mainSourceSet = getMainSourceSet(); FileCollection classesOutputDirectories = mainSourceSet.getOutput().getClassesDirs().filter(File::exists); - Path resourcesOutputDirectory = mainSourceSet.getOutput().getResourcesDir().toPath(); + File resourcesDir = mainSourceSet.getOutput().getResourcesDir(); + Path resourcesOutputDirectory = resourcesDir != null ? resourcesDir.toPath() : null; FileCollection allFiles = project.getConfigurations().getByName(configurationName).filter(File::exists); @@ -232,7 +233,10 @@ public JibContainerBuilder createJibContainerBuilder( allFiles .minus(classesOutputDirectories) .minus(projectDependencies) - .filter(file -> !file.toPath().equals(resourcesOutputDirectory)); + .filter( + file -> + resourcesOutputDirectory == null + || !file.toPath().equals(resourcesOutputDirectory)); FileCollection snapshotDependencies = nonProjectDependencies.filter(file -> file.getName().contains("SNAPSHOT")); @@ -254,7 +258,7 @@ public JibContainerBuilder createJibContainerBuilder( switch (containerizingMode) { case EXPLODED: // Adds resource files - if (Files.exists(resourcesOutputDirectory)) { + if (resourcesOutputDirectory != null && Files.exists(resourcesOutputDirectory)) { javaContainerBuilder.addResources(resourcesOutputDirectory); } @@ -270,9 +274,11 @@ public JibContainerBuilder createJibContainerBuilder( case PACKAGED: // Add a JAR Jar jarTask = (Jar) project.getTasks().findByName("jar"); - Path jarPath = jarTask.getArchiveFile().get().getAsFile().toPath(); - log(LogEvent.debug("Using JAR: " + jarPath)); - javaContainerBuilder.addToClasspath(jarPath); + if (jarTask != null) { + Path jarPath = jarTask.getArchiveFile().get().getAsFile().toPath(); + log(LogEvent.debug("Using JAR: " + jarPath)); + javaContainerBuilder.addToClasspath(jarPath); + } break; default: diff --git a/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/skaffold/FilesTaskV2.java b/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/skaffold/FilesTaskV2.java index e5e482d355..6a72637225 100644 --- a/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/skaffold/FilesTaskV2.java +++ b/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/skaffold/FilesTaskV2.java @@ -41,7 +41,6 @@ import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.SourceSetContainer; import org.gradle.api.tasks.TaskAction; -import org.gradle.util.GradleVersion; /** * Prints out changing source dependencies on a project. @@ -51,8 +50,6 @@ */ public class FilesTaskV2 extends DefaultTask { - private static final GradleVersion GRADLE_9 = GradleVersion.version("9.0"); - private final SkaffoldFilesOutput skaffoldFilesOutput = new SkaffoldFilesOutput(); @Nullable private JibExtension jibExtension; @@ -91,16 +88,16 @@ public void listFiles() throws IOException { Set projectDependencyJars = new HashSet<>(); for (ProjectDependency projectDependency : projectDependencies) { - addProjectFiles(projectDependency.getDependencyProject()); + Project dependentProject = getDependentProject(projectDependency); + addProjectFiles(dependentProject); // Keep track of project dependency jars for filtering out later String configurationName = projectDependency.getTargetConfiguration(); if (configurationName == null) { configurationName = "default"; } - Project dependencyProject = projectDependency.getDependencyProject(); for (Configuration targetConfiguration : - dependencyProject.getConfigurations().getByName(configurationName).getHierarchy()) { + dependentProject.getConfigurations().getByName(configurationName).getHierarchy()) { for (PublishArtifact artifact : targetConfiguration.getArtifacts()) { projectDependencyJars.add(artifact.getFile()); } @@ -142,13 +139,7 @@ private void addGradleFiles(Project project) { skaffoldFilesOutput.addBuild(project.getBuildFile().toPath()); // Add settings.gradle - if (GradleVersion.current().compareTo(GRADLE_9) < 0 - && project.getGradle().getStartParameter().getSettingsFile() != null) { - skaffoldFilesOutput.addBuild( - project.getGradle().getStartParameter().getSettingsFile().toPath()); - } else if (Files.exists(projectPath.resolve(Settings.DEFAULT_SETTINGS_FILE))) { - skaffoldFilesOutput.addBuild(projectPath.resolve(Settings.DEFAULT_SETTINGS_FILE)); - } + addSettingsFile(project, projectPath); // Add gradle.properties if (Files.exists(projectPath.resolve("gradle.properties"))) { @@ -156,6 +147,36 @@ private void addGradleFiles(Project project) { } } + /** + * Adds the settings.gradle file for a project. + * + *

Uses reflection to call getSettingsFile() for compatibility with both Gradle 6 and 9 + * (getSettingsFile() was removed in Gradle 9). + * + * @param project the project + * @param projectPath the project directory path + */ + private void addSettingsFile(Project project, Path projectPath) { + boolean settingsFileAdded = false; + try { + Object startParameter = project.getGradle().getStartParameter(); + java.lang.reflect.Method getSettingsFileMethod = + startParameter.getClass().getMethod("getSettingsFile"); + File settingsFile = (File) getSettingsFileMethod.invoke(startParameter); + if (settingsFile != null) { + skaffoldFilesOutput.addBuild(settingsFile.toPath()); + settingsFileAdded = true; + } + } catch (ReflectiveOperationException e) { + // Fall through to default settings file check + } + + // Fall back to default settings file location if not already added + if (!settingsFileAdded && Files.exists(projectPath.resolve(Settings.DEFAULT_SETTINGS_FILE))) { + skaffoldFilesOutput.addBuild(projectPath.resolve(Settings.DEFAULT_SETTINGS_FILE)); + } + } + /** * Prints build files, sources, and resources associated with a project. * @@ -212,7 +233,7 @@ private Set findProjectDependencies(Project project) { // If this is a project dependency, save it ProjectDependency projectDependency = (ProjectDependency) dependency; if (!projectDependencies.contains(projectDependency)) { - projects.push(projectDependency.getDependencyProject()); + projects.push(getDependentProject(projectDependency)); projectDependencies.add(projectDependency); } } @@ -222,4 +243,37 @@ private Set findProjectDependencies(Project project) { } return projectDependencies; } + + /** + * Resolves a {@link ProjectDependency} to its corresponding {@link Project} instance. + * + *

Uses reflection to handle both Gradle 6 (getDependencyProject()) and Gradle 9+ (getPath()). + * + * @param projectDependency the project dependency to resolve + * @return the resolved project + * @throws RuntimeException if the dependent project could not be resolved + */ + private Project getDependentProject(ProjectDependency projectDependency) { + // Try getDependencyProject() first (Gradle 6-8) + try { + java.lang.reflect.Method getDependencyProjectMethod = + projectDependency.getClass().getMethod("getDependencyProject"); + return (Project) getDependencyProjectMethod.invoke(projectDependency); + } catch (NoSuchMethodException e) { + // Fall through to getPath() approach (Gradle 9+) + } catch (ReflectiveOperationException e) { + throw new RuntimeException( + "Failed to resolve dependent project from " + projectDependency, e); + } + + // Try getPath() approach (Gradle 9+) + try { + java.lang.reflect.Method getPathMethod = projectDependency.getClass().getMethod("getPath"); + String path = (String) getPathMethod.invoke(projectDependency); + return getProject().project(path); + } catch (ReflectiveOperationException e) { + throw new RuntimeException( + "Failed to resolve dependent project from " + projectDependency, e); + } + } } diff --git a/jib-gradle-plugin/src/test/java/com/google/cloud/tools/jib/gradle/skaffold/FilesTaskV2Test.java b/jib-gradle-plugin/src/test/java/com/google/cloud/tools/jib/gradle/skaffold/FilesTaskV2Test.java index 3df3a86154..fba1de1298 100644 --- a/jib-gradle-plugin/src/test/java/com/google/cloud/tools/jib/gradle/skaffold/FilesTaskV2Test.java +++ b/jib-gradle-plugin/src/test/java/com/google/cloud/tools/jib/gradle/skaffold/FilesTaskV2Test.java @@ -44,7 +44,13 @@ public class FilesTaskV2Test { @ClassRule public static final TestProject skaffoldTestProject = new TestProject("skaffold-config"); - @ClassRule public static final TestProject multiTestProject = new TestProject("multi-service"); + @ClassRule + public static final TestProject multiTestProject = + org.gradle.util.GradleVersion.current() + .compareTo(org.gradle.util.GradleVersion.version("9.0")) + >= 0 + ? new TestProject("multi-service").withGradleVersion("9.0") + : new TestProject("multi-service"); @ClassRule public static final TestProject platformProject = diff --git a/jib-gradle-plugin/src/test/resources/gradle/projects/multi-service/build.gradle b/jib-gradle-plugin/src/test/resources/gradle/projects/multi-service/build.gradle index ee3d6cd81d..5a3d35fa37 100644 --- a/jib-gradle-plugin/src/test/resources/gradle/projects/multi-service/build.gradle +++ b/jib-gradle-plugin/src/test/resources/gradle/projects/multi-service/build.gradle @@ -3,8 +3,10 @@ plugins { id 'com.google.cloud.tools.jib' } -sourceCompatibility = 1.8 -targetCompatibility = 1.8 +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +} repositories { mavenCentral() diff --git a/jib-gradle-plugin/src/test/resources/gradle/projects/multi-service/complex-service/build.gradle b/jib-gradle-plugin/src/test/resources/gradle/projects/multi-service/complex-service/build.gradle index 596d80f423..1fd83e855a 100644 --- a/jib-gradle-plugin/src/test/resources/gradle/projects/multi-service/complex-service/build.gradle +++ b/jib-gradle-plugin/src/test/resources/gradle/projects/multi-service/complex-service/build.gradle @@ -3,8 +3,10 @@ plugins { id 'com.google.cloud.tools.jib' } -sourceCompatibility = 1.8 -targetCompatibility = 1.8 +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +} repositories { mavenCentral() diff --git a/jib-gradle-plugin/src/test/resources/gradle/projects/multi-service/lib/build.gradle b/jib-gradle-plugin/src/test/resources/gradle/projects/multi-service/lib/build.gradle index 313ac21fc2..6e536f8d23 100644 --- a/jib-gradle-plugin/src/test/resources/gradle/projects/multi-service/lib/build.gradle +++ b/jib-gradle-plugin/src/test/resources/gradle/projects/multi-service/lib/build.gradle @@ -2,8 +2,10 @@ plugins { id 'java' } -sourceCompatibility = 1.8 -targetCompatibility = 1.8 +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +} repositories { mavenCentral() diff --git a/jib-gradle-plugin/src/test/resources/gradle/projects/multi-service/simple-service/build.gradle b/jib-gradle-plugin/src/test/resources/gradle/projects/multi-service/simple-service/build.gradle index ee3d6cd81d..5a3d35fa37 100644 --- a/jib-gradle-plugin/src/test/resources/gradle/projects/multi-service/simple-service/build.gradle +++ b/jib-gradle-plugin/src/test/resources/gradle/projects/multi-service/simple-service/build.gradle @@ -3,8 +3,10 @@ plugins { id 'com.google.cloud.tools.jib' } -sourceCompatibility = 1.8 -targetCompatibility = 1.8 +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +} repositories { mavenCentral()