diff --git a/plugin/src/main/java/com/microsoft/java/bs/gradle/plugin/SourceSetsModelBuilder.java b/plugin/src/main/java/com/microsoft/java/bs/gradle/plugin/SourceSetsModelBuilder.java index cfdf7a47..ba990376 100644 --- a/plugin/src/main/java/com/microsoft/java/bs/gradle/plugin/SourceSetsModelBuilder.java +++ b/plugin/src/main/java/com/microsoft/java/bs/gradle/plugin/SourceSetsModelBuilder.java @@ -13,17 +13,23 @@ import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedList; import java.util.List; import java.util.LinkedList; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.gradle.api.Project; import org.gradle.api.file.Directory; import org.gradle.api.file.FileCollection; import org.gradle.api.file.SourceDirectorySet; +import org.gradle.api.internal.tasks.compile.DefaultJavaCompileSpec; +import org.gradle.api.internal.tasks.compile.JavaCompilerArgumentsBuilder; import org.gradle.api.plugins.JavaPluginExtension; import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.SourceSetContainer; @@ -116,9 +122,11 @@ public Object buildAll(String modelName, Project rootProject) { gradleSourceSet.setJavaHome(defaultJavaHome); gradleSourceSet.setJavaVersion(javaVersion); gradleSourceSet.setGradleVersion(gradleVersion); - gradleSourceSet.setSourceCompatibility(getSourceCompatibility(project, sourceSet)); - gradleSourceSet.setTargetCompatibility(getTargetCompatibility(project, sourceSet)); gradleSourceSet.setCompilerArgs(getCompilerArgs(project, sourceSet)); + gradleSourceSet.setSourceCompatibility( + getSourceCompatibility(gradleSourceSet.getCompilerArgs())); + gradleSourceSet.setTargetCompatibility( + getTargetCompatibility(gradleSourceSet.getCompilerArgs())); gradleSourceSets.add(gradleSourceSet); // tests @@ -344,28 +352,75 @@ private File getSourceOutputDir(SourceSet sourceSet) { return null; } + private Optional findCompilerArg(List compilerArgs, String arg) { + int idx = compilerArgs.indexOf(arg); + if (idx >= 0 && idx < compilerArgs.size() - 1) { + return Optional.of(compilerArgs.get(idx + 1)); + } + return Optional.empty(); + } + + private Optional findFirstCompilerArgMatch(List compilerArgs, + Stream args) { + return args.map(arg -> findCompilerArg(compilerArgs, arg)) + .filter(Optional::isPresent) + .map(Optional::get) + .findFirst(); + } + /** * Get the source compatibility level of the source set. */ - private String getSourceCompatibility(Project project, SourceSet sourceSet) { - JavaCompile javaCompile = getJavaCompileTask(project, sourceSet); - if (javaCompile != null) { - return javaCompile.getSourceCompatibility(); - } - - return ""; + private String getSourceCompatibility(List compilerArgs) { + return findFirstCompilerArgMatch(compilerArgs, + Stream.of("-source", "--source", "--release")) + .orElse(""); } /** * Get the target compatibility level of the source set. */ - private String getTargetCompatibility(Project project, SourceSet sourceSet) { - JavaCompile javaCompile = getJavaCompileTask(project, sourceSet); - if (javaCompile != null) { - return javaCompile.getTargetCompatibility(); + private String getTargetCompatibility(List compilerArgs) { + return findFirstCompilerArgMatch(compilerArgs, + Stream.of("-target", "--target", "--release")) + .orElse(""); + } + + private DefaultJavaCompileSpec getJavaCompileSpec(JavaCompile javaCompile) { + CompileOptions options = javaCompile.getOptions(); + + DefaultJavaCompileSpec specs = new DefaultJavaCompileSpec(); + specs.setCompileOptions(options); + + // check the project hasn't already got the target or source defined in the + // compiler args so they're not overwritten below + List originalArgs = options.getCompilerArgs(); + String argsSourceCompatibility = getSourceCompatibility(originalArgs); + String argsTargetCompatibility = getTargetCompatibility(originalArgs); + + if (!argsSourceCompatibility.isEmpty() && !argsTargetCompatibility.isEmpty()) { + return specs; } - return ""; + if (GradleVersion.current().compareTo(GradleVersion.version("6.6")) >= 0) { + if (options.getRelease().isPresent()) { + specs.setRelease(options.getRelease().get()); + return specs; + } + } + if (argsSourceCompatibility.isEmpty() && specs.getSourceCompatibility() == null) { + String sourceCompatibility = javaCompile.getSourceCompatibility(); + if (sourceCompatibility != null) { + specs.setSourceCompatibility(sourceCompatibility); + } + } + if (argsTargetCompatibility.isEmpty() && specs.getTargetCompatibility() == null) { + String targetCompatibility = javaCompile.getTargetCompatibility(); + if (targetCompatibility != null) { + specs.setTargetCompatibility(targetCompatibility); + } + } + return specs; } /** @@ -374,7 +429,29 @@ private String getTargetCompatibility(Project project, SourceSet sourceSet) { private List getCompilerArgs(Project project, SourceSet sourceSet) { JavaCompile javaCompile = getJavaCompileTask(project, sourceSet); if (javaCompile != null) { - return javaCompile.getOptions().getCompilerArgs(); + CompileOptions options = javaCompile.getOptions(); + + try { + DefaultJavaCompileSpec specs = getJavaCompileSpec(javaCompile); + + JavaCompilerArgumentsBuilder builder = new JavaCompilerArgumentsBuilder(specs) + .includeMainOptions(true) + .includeClasspath(false) + .includeSourceFiles(false) + .includeLauncherOptions(false); + return builder.build(); + } catch (Exception e) { + // DefaultJavaCompileSpec and JavaCompilerArgumentsBuilder are internal so may not exist. + // Fallback to returning just the compiler arguments the build has specified. + // This will miss a lot of arguments derived from the CompileOptions e.g. sourceCompatibilty + // Arguments must be cast and converted to String because Groovy can use GStringImpl + // which then throws IllegalArgumentException when passed back over the tooling connection. + List compilerArgs = new LinkedList<>(options.getCompilerArgs()); + return compilerArgs + .stream() + .map(Object::toString) + .collect(Collectors.toList()); + } } return Collections.emptyList(); diff --git a/plugin/src/test/java/com/microsoft/java/bs/gradle/plugin/GradleBuildServerPluginTest.java b/plugin/src/test/java/com/microsoft/java/bs/gradle/plugin/GradleBuildServerPluginTest.java index 91c168eb..5ec52954 100644 --- a/plugin/src/test/java/com/microsoft/java/bs/gradle/plugin/GradleBuildServerPluginTest.java +++ b/plugin/src/test/java/com/microsoft/java/bs/gradle/plugin/GradleBuildServerPluginTest.java @@ -4,6 +4,7 @@ package com.microsoft.java.bs.gradle.plugin; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -155,4 +156,191 @@ void testSourceInference() throws IOException { assertEquals(4, generatedSourceDirCount); } } + + @Test + void testJavaCompilerArgs1() throws IOException { + File projectDir = projectPath.resolve("java-compilerargs-1").toFile(); + GradleConnector connector = GradleConnector.newConnector().forProjectDirectory(projectDir); + connector.useBuildDistribution(); + try (ProjectConnection connect = connector.connect()) { + ModelBuilder modelBuilder = connect.model(GradleSourceSets.class); + File initScript = PluginHelper.getInitScript(); + modelBuilder.addArguments("--init-script", initScript.getAbsolutePath()); + GradleSourceSets gradleSourceSets = modelBuilder.get(); + assertEquals(2, gradleSourceSets.getGradleSourceSets().size()); + for (GradleSourceSet gradleSourceSet : gradleSourceSets.getGradleSourceSets()) { + String args = "|" + String.join("|", gradleSourceSet.getCompilerArgs()); + assertFalse(args.contains("|--source|"), () -> "Available args: " + args); + assertFalse(args.contains("|--target|"), () -> "Available args: " + args); + assertFalse(args.contains("|--release|"), () -> "Available args: " + args); + assertTrue(args.contains("|-source|1.8"), () -> "Available args: " + args); + assertTrue(args.contains("|-target|9"), () -> "Available args: " + args); + assertTrue(args.contains("|-Xlint:all"), () -> "Available args: " + args); + assertEquals("1.8", gradleSourceSet.getSourceCompatibility(), + () -> "Available args: " + args); + assertEquals("9", gradleSourceSet.getTargetCompatibility(), + () -> "Available args: " + args); + } + } + } + + @Test + void testJavaCompilerArgs2() throws IOException { + File projectDir = projectPath.resolve("java-compilerargs-2").toFile(); + GradleConnector connector = GradleConnector.newConnector().forProjectDirectory(projectDir); + connector.useBuildDistribution(); + try (ProjectConnection connect = connector.connect()) { + ModelBuilder modelBuilder = connect.model(GradleSourceSets.class); + File initScript = PluginHelper.getInitScript(); + modelBuilder.addArguments("--init-script", initScript.getAbsolutePath()); + GradleSourceSets gradleSourceSets = modelBuilder.get(); + assertEquals(2, gradleSourceSets.getGradleSourceSets().size()); + for (GradleSourceSet gradleSourceSet : gradleSourceSets.getGradleSourceSets()) { + String args = "|" + String.join("|", gradleSourceSet.getCompilerArgs()); + assertFalse(args.contains("|--source|"), () -> "Available args: " + args); + assertFalse(args.contains("|--target|"), () -> "Available args: " + args); + assertFalse(args.contains("|-source|"), () -> "Available args: " + args); + assertFalse(args.contains("|-target|"), () -> "Available args: " + args); + assertTrue(args.contains("|--release|9"), () -> "Available args: " + args); + assertTrue(args.contains("|-Xlint:all"), () -> "Available args: " + args); + assertEquals("9", gradleSourceSet.getSourceCompatibility(), + () -> "Available args: " + args); + assertEquals("9", gradleSourceSet.getTargetCompatibility(), + () -> "Available args: " + args); + } + } + } + + @Test + void testJavaCompilerArgs3() throws IOException { + File projectDir = projectPath.resolve("java-compilerargs-3").toFile(); + GradleConnector connector = GradleConnector.newConnector().forProjectDirectory(projectDir); + connector.useBuildDistribution(); + try (ProjectConnection connect = connector.connect()) { + ModelBuilder modelBuilder = connect.model(GradleSourceSets.class); + File initScript = PluginHelper.getInitScript(); + modelBuilder.addArguments("--init-script", initScript.getAbsolutePath()); + GradleSourceSets gradleSourceSets = modelBuilder.get(); + assertEquals(2, gradleSourceSets.getGradleSourceSets().size()); + for (GradleSourceSet gradleSourceSet : gradleSourceSets.getGradleSourceSets()) { + String args = "|" + String.join("|", gradleSourceSet.getCompilerArgs()); + assertFalse(args.contains("|--source|"), () -> "Available args: " + args); + assertFalse(args.contains("|--target|"), () -> "Available args: " + args); + assertFalse(args.contains("|-source|"), () -> "Available args: " + args); + assertFalse(args.contains("|-target|"), () -> "Available args: " + args); + assertTrue(args.contains("|--release|9"), () -> "Available args: " + args); + assertTrue(args.contains("|-Xlint:all"), () -> "Available args: " + args); + assertEquals("9", gradleSourceSet.getSourceCompatibility(), + () -> "Available args: " + args); + assertEquals("9", gradleSourceSet.getTargetCompatibility(), + () -> "Available args: " + args); + } + } + } + + @Test + void testJavaCompilerArgs4() throws IOException { + File projectDir = projectPath.resolve("java-compilerargs-4").toFile(); + GradleConnector connector = GradleConnector.newConnector().forProjectDirectory(projectDir); + connector.useBuildDistribution(); + try (ProjectConnection connect = connector.connect()) { + ModelBuilder modelBuilder = connect.model(GradleSourceSets.class); + File initScript = PluginHelper.getInitScript(); + modelBuilder.addArguments("--init-script", initScript.getAbsolutePath()); + GradleSourceSets gradleSourceSets = modelBuilder.get(); + assertEquals(2, gradleSourceSets.getGradleSourceSets().size()); + for (GradleSourceSet gradleSourceSet : gradleSourceSets.getGradleSourceSets()) { + String args = "|" + String.join("|", gradleSourceSet.getCompilerArgs()); + assertFalse(args.contains("|--release|"), () -> "Available args: " + args); + assertFalse(args.contains("|-source|"), () -> "Available args: " + args); + assertFalse(args.contains("|-target|"), () -> "Available args: " + args); + assertTrue(args.contains("|--source|1.8"), () -> "Available args: " + args); + assertTrue(args.contains("|--target|9"), () -> "Available args: " + args); + assertTrue(args.contains("|-Xlint:all"), () -> "Available args: " + args); + assertEquals("1.8", gradleSourceSet.getSourceCompatibility(), + () -> "Available args: " + args); + assertEquals("9", gradleSourceSet.getTargetCompatibility(), + () -> "Available args: " + args); + } + } + } + + @Test + void testJavaCompilerArgs5() throws IOException { + File projectDir = projectPath.resolve("java-compilerargs-5").toFile(); + GradleConnector connector = GradleConnector.newConnector().forProjectDirectory(projectDir); + connector.useBuildDistribution(); + try (ProjectConnection connect = connector.connect()) { + ModelBuilder modelBuilder = connect.model(GradleSourceSets.class); + File initScript = PluginHelper.getInitScript(); + modelBuilder.addArguments("--init-script", initScript.getAbsolutePath()); + GradleSourceSets gradleSourceSets = modelBuilder.get(); + assertEquals(2, gradleSourceSets.getGradleSourceSets().size()); + for (GradleSourceSet gradleSourceSet : gradleSourceSets.getGradleSourceSets()) { + String args = "|" + String.join("|", gradleSourceSet.getCompilerArgs()); + assertFalse(args.contains("|--release|"), () -> "Available args: " + args); + assertFalse(args.contains("|--source|"), () -> "Available args: " + args); + assertFalse(args.contains("|--target|"), () -> "Available args: " + args); + assertTrue(args.contains("|-source|1.8"), () -> "Available args: " + args); + assertTrue(args.contains("|-target|9"), () -> "Available args: " + args); + assertTrue(args.contains("|-Xlint:all"), () -> "Available args: " + args); + assertEquals("1.8", gradleSourceSet.getSourceCompatibility(), + () -> "Available args: " + args); + assertEquals("9", gradleSourceSet.getTargetCompatibility(), + () -> "Available args: " + args); + } + } + } + + @Test + void testJavaCompilerArgs6() throws IOException { + File projectDir = projectPath.resolve("java-compilerargs-6").toFile(); + GradleConnector connector = GradleConnector.newConnector().forProjectDirectory(projectDir); + connector.useBuildDistribution(); + try (ProjectConnection connect = connector.connect()) { + ModelBuilder modelBuilder = connect.model(GradleSourceSets.class); + File initScript = PluginHelper.getInitScript(); + modelBuilder.addArguments("--init-script", initScript.getAbsolutePath()); + GradleSourceSets gradleSourceSets = modelBuilder.get(); + assertEquals(2, gradleSourceSets.getGradleSourceSets().size()); + for (GradleSourceSet gradleSourceSet : gradleSourceSets.getGradleSourceSets()) { + String args = "|" + String.join("|", gradleSourceSet.getCompilerArgs()); + assertFalse(args.contains("|--release|"), () -> "Available args: " + args); + assertFalse(args.contains("|--source|"), () -> "Available args: " + args); + assertFalse(args.contains("|--target|"), () -> "Available args: " + args); + assertTrue(args.contains("|-source|"), () -> "Available args: " + args); + assertTrue(args.contains("|-target|"), () -> "Available args: " + args); + assertFalse(gradleSourceSet.getSourceCompatibility().isEmpty(), + () -> "Available args: " + args); + assertFalse(gradleSourceSet.getTargetCompatibility().isEmpty(), + () -> "Available args: " + args); + } + } + } + + @Test + void testJavaCompilerArgsToolchain() throws IOException { + File projectDir = projectPath.resolve("java-compilerargs-toolchain").toFile(); + GradleConnector connector = GradleConnector.newConnector().forProjectDirectory(projectDir); + connector.useBuildDistribution(); + try (ProjectConnection connect = connector.connect()) { + ModelBuilder modelBuilder = connect.model(GradleSourceSets.class); + File initScript = PluginHelper.getInitScript(); + modelBuilder.addArguments("--init-script", initScript.getAbsolutePath()); + GradleSourceSets gradleSourceSets = modelBuilder.get(); + assertEquals(2, gradleSourceSets.getGradleSourceSets().size()); + for (GradleSourceSet gradleSourceSet : gradleSourceSets.getGradleSourceSets()) { + String args = "|" + String.join("|", gradleSourceSet.getCompilerArgs()); + assertFalse(args.contains("|--release|"), () -> "Available args: " + args); + assertFalse(args.contains("|--source|"), () -> "Available args: " + args); + assertFalse(args.contains("|--target|"), () -> "Available args: " + args); + assertTrue(args.contains("|-source|17|"), () -> "Available args: " + args); + assertTrue(args.contains("|-target|17|"), () -> "Available args: " + args); + assertFalse(gradleSourceSet.getSourceCompatibility().isEmpty(), + () -> "Available args: " + args); + assertFalse(gradleSourceSet.getTargetCompatibility().isEmpty(), + () -> "Available args: " + args); + } + } + } } diff --git a/testProjects/java-compilerargs-1/build.gradle b/testProjects/java-compilerargs-1/build.gradle new file mode 100644 index 00000000..80321000 --- /dev/null +++ b/testProjects/java-compilerargs-1/build.gradle @@ -0,0 +1,10 @@ +plugins { + id 'java' + id 'java-gradle-plugin' +} + +tasks.withType(JavaCompile) { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_9 + options.compilerArgs << "-Xlint:all" +} diff --git a/testProjects/java-compilerargs-1/settings.gradle b/testProjects/java-compilerargs-1/settings.gradle new file mode 100644 index 00000000..46dd366d --- /dev/null +++ b/testProjects/java-compilerargs-1/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'java-compilerargs-1' \ No newline at end of file diff --git a/testProjects/java-compilerargs-2/build.gradle b/testProjects/java-compilerargs-2/build.gradle new file mode 100644 index 00000000..1ad96db8 --- /dev/null +++ b/testProjects/java-compilerargs-2/build.gradle @@ -0,0 +1,9 @@ +plugins { + id 'java' + id 'java-gradle-plugin' +} + +tasks.withType(JavaCompile) { + options.release = 9 + options.compilerArgs << "-Xlint:all" +} diff --git a/testProjects/java-compilerargs-2/settings.gradle b/testProjects/java-compilerargs-2/settings.gradle new file mode 100644 index 00000000..902416d1 --- /dev/null +++ b/testProjects/java-compilerargs-2/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'java-compilerargs-2' \ No newline at end of file diff --git a/testProjects/java-compilerargs-3/build.gradle b/testProjects/java-compilerargs-3/build.gradle new file mode 100644 index 00000000..b944f28f --- /dev/null +++ b/testProjects/java-compilerargs-3/build.gradle @@ -0,0 +1,8 @@ +plugins { + id 'java' + id 'java-gradle-plugin' +} + +tasks.withType(JavaCompile) { + options.compilerArgs << "-Xlint:all" << "--release" << "9" +} diff --git a/testProjects/java-compilerargs-3/settings.gradle b/testProjects/java-compilerargs-3/settings.gradle new file mode 100644 index 00000000..833a7b60 --- /dev/null +++ b/testProjects/java-compilerargs-3/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'java-compilerargs-3' \ No newline at end of file diff --git a/testProjects/java-compilerargs-4/build.gradle b/testProjects/java-compilerargs-4/build.gradle new file mode 100644 index 00000000..5f13c932 --- /dev/null +++ b/testProjects/java-compilerargs-4/build.gradle @@ -0,0 +1,8 @@ +plugins { + id 'java' + id 'java-gradle-plugin' +} + +tasks.withType(JavaCompile) { + options.compilerArgs << "-Xlint:all" << "--source" << "1.8" << "--target" << "9" +} diff --git a/testProjects/java-compilerargs-4/settings.gradle b/testProjects/java-compilerargs-4/settings.gradle new file mode 100644 index 00000000..79d5bfe2 --- /dev/null +++ b/testProjects/java-compilerargs-4/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'java-compilerargs-4' \ No newline at end of file diff --git a/testProjects/java-compilerargs-5/build.gradle b/testProjects/java-compilerargs-5/build.gradle new file mode 100644 index 00000000..474751d5 --- /dev/null +++ b/testProjects/java-compilerargs-5/build.gradle @@ -0,0 +1,8 @@ +plugins { + id 'java' + id 'java-gradle-plugin' +} + +tasks.withType(JavaCompile) { + options.compilerArgs << "-Xlint:all" << "-source" << "1.8" << "-target" << "9" +} diff --git a/testProjects/java-compilerargs-5/settings.gradle b/testProjects/java-compilerargs-5/settings.gradle new file mode 100644 index 00000000..937edf37 --- /dev/null +++ b/testProjects/java-compilerargs-5/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'java-compilerargs-5' \ No newline at end of file diff --git a/testProjects/java-compilerargs-6/build.gradle b/testProjects/java-compilerargs-6/build.gradle new file mode 100644 index 00000000..99f1e6cb --- /dev/null +++ b/testProjects/java-compilerargs-6/build.gradle @@ -0,0 +1,6 @@ +plugins { + id 'java' + id 'java-gradle-plugin' +} + +// no source, target, release set - Gradle should automatically set from the running jvm \ No newline at end of file diff --git a/testProjects/java-compilerargs-6/settings.gradle b/testProjects/java-compilerargs-6/settings.gradle new file mode 100644 index 00000000..388cb789 --- /dev/null +++ b/testProjects/java-compilerargs-6/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'java-compilerargs-6' \ No newline at end of file diff --git a/testProjects/java-compilerargs-toolchain/build.gradle b/testProjects/java-compilerargs-toolchain/build.gradle new file mode 100644 index 00000000..f6de2db0 --- /dev/null +++ b/testProjects/java-compilerargs-toolchain/build.gradle @@ -0,0 +1,10 @@ +plugins { + id 'java' + id 'java-gradle-plugin' +} + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(17) + } +} \ No newline at end of file diff --git a/testProjects/java-compilerargs-toolchain/settings.gradle b/testProjects/java-compilerargs-toolchain/settings.gradle new file mode 100644 index 00000000..47bb0cd2 --- /dev/null +++ b/testProjects/java-compilerargs-toolchain/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'java-compilerargs-toolchain' \ No newline at end of file