diff --git a/model/src/main/java/com/microsoft/java/bs/gradle/model/impl/DefaultGradleSourceSet.java b/model/src/main/java/com/microsoft/java/bs/gradle/model/impl/DefaultGradleSourceSet.java index 97151aae..89be55bd 100644 --- a/model/src/main/java/com/microsoft/java/bs/gradle/model/impl/DefaultGradleSourceSet.java +++ b/model/src/main/java/com/microsoft/java/bs/gradle/model/impl/DefaultGradleSourceSet.java @@ -66,7 +66,7 @@ public class DefaultGradleSourceSet implements GradleSourceSet { public DefaultGradleSourceSet() {} /** - * Copy constructor. + * Copy constructor. To create a POJO from the proxy that is returned from Gradle tooling API * * @param gradleSourceSet the source set to copy from. */ @@ -99,12 +99,13 @@ public DefaultGradleSourceSet(GradleSourceSet gradleSourceSet) { } private LanguageExtension convertLanguageExtension(LanguageExtension object) { + // use copy constructors to create a POJO from proxy returned by Gradle tooling if (object.isJavaExtension()) { - return object.getAsJavaExtension(); + return new DefaultJavaExtension(object.getAsJavaExtension()); } if (object.isScalaExtension()) { - return object.getAsScalaExtension(); + return new DefaultScalaExtension(object.getAsScalaExtension()); } throw new IllegalArgumentException("No conversion methods defined for object: " + object); diff --git a/model/src/main/java/com/microsoft/java/bs/gradle/model/impl/DefaultJavaExtension.java b/model/src/main/java/com/microsoft/java/bs/gradle/model/impl/DefaultJavaExtension.java index ad8ddcb9..b7fb71ee 100644 --- a/model/src/main/java/com/microsoft/java/bs/gradle/model/impl/DefaultJavaExtension.java +++ b/model/src/main/java/com/microsoft/java/bs/gradle/model/impl/DefaultJavaExtension.java @@ -35,6 +35,25 @@ public class DefaultJavaExtension implements JavaExtension { private File classesDir; + /** + * copy constructor. To create a POJO from the proxy that is returned from Gradle tooling API + * + * @param javaExtension extension to copy + */ + public DefaultJavaExtension(JavaExtension javaExtension) { + this.javaHome = javaExtension.getJavaHome(); + this.javaVersion = javaExtension.getJavaVersion(); + this.sourceCompatibility = javaExtension.getSourceCompatibility(); + this.targetCompatibility = javaExtension.getTargetCompatibility(); + this.compilerArgs = javaExtension.getCompilerArgs(); + this.sourceDirs = javaExtension.getSourceDirs(); + this.generatedSourceDirs = javaExtension.getGeneratedSourceDirs(); + this.compileTaskName = javaExtension.getCompileTaskName(); + this.classesDir = javaExtension.getClassesDir(); + } + + public DefaultJavaExtension() {} + @Override public File getJavaHome() { return javaHome; diff --git a/model/src/main/java/com/microsoft/java/bs/gradle/model/impl/DefaultScalaExtension.java b/model/src/main/java/com/microsoft/java/bs/gradle/model/impl/DefaultScalaExtension.java index b340d149..90de456e 100644 --- a/model/src/main/java/com/microsoft/java/bs/gradle/model/impl/DefaultScalaExtension.java +++ b/model/src/main/java/com/microsoft/java/bs/gradle/model/impl/DefaultScalaExtension.java @@ -35,6 +35,25 @@ public class DefaultScalaExtension implements ScalaExtension { private File classesDir; + /** + * copy constructor. To create a POJO from the proxy that is returned from Gradle tooling API + * + * @param scalaExtension extension to copy + */ + public DefaultScalaExtension(ScalaExtension scalaExtension) { + this.scalaCompilerArgs = scalaExtension.getScalaCompilerArgs(); + this.scalaOrganization = scalaExtension.getScalaOrganization(); + this.scalaVersion = scalaExtension.getScalaVersion(); + this.scalaBinaryVersion = scalaExtension.getScalaBinaryVersion(); + this.scalaJars = scalaExtension.getScalaJars(); + this.sourceDirs = scalaExtension.getSourceDirs(); + this.generatedSourceDirs = scalaExtension.getGeneratedSourceDirs(); + this.compileTaskName = scalaExtension.getCompileTaskName(); + this.classesDir = scalaExtension.getClassesDir(); + } + + public DefaultScalaExtension() {} + @Override public List getScalaCompilerArgs() { return scalaCompilerArgs; diff --git a/plugin/src/main/java/com/microsoft/java/bs/gradle/plugin/JavaLanguageModelBuilder.java b/plugin/src/main/java/com/microsoft/java/bs/gradle/plugin/JavaLanguageModelBuilder.java index c658eefe..add681d2 100644 --- a/plugin/src/main/java/com/microsoft/java/bs/gradle/plugin/JavaLanguageModelBuilder.java +++ b/plugin/src/main/java/com/microsoft/java/bs/gradle/plugin/JavaLanguageModelBuilder.java @@ -7,6 +7,8 @@ import java.io.File; import java.io.FileReader; import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.util.HashSet; import java.util.LinkedList; import java.util.List; @@ -20,11 +22,16 @@ import com.microsoft.java.bs.gradle.model.GradleModuleDependency; import com.microsoft.java.bs.gradle.model.JavaExtension; import com.microsoft.java.bs.gradle.model.SupportedLanguage; +import com.microsoft.java.bs.gradle.model.SupportedLanguages; +import com.microsoft.java.bs.gradle.model.impl.DefaultJavaExtension; +import com.microsoft.java.bs.gradle.plugin.utils.Utils; +import org.gradle.api.JavaVersion; import org.gradle.api.Project; import org.gradle.api.file.Directory; 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.compile.AbstractCompile; import org.gradle.api.tasks.compile.CompileOptions; @@ -32,9 +39,6 @@ import org.gradle.plugins.ide.internal.tooling.java.DefaultInstalledJdk; import org.gradle.util.GradleVersion; -import com.microsoft.java.bs.gradle.model.SupportedLanguages; -import com.microsoft.java.bs.gradle.model.impl.DefaultJavaExtension; - /** * The language model builder for Java language. */ @@ -69,6 +73,37 @@ public DefaultJavaExtension getExtensionFor(Project project, SourceSet sourceSet extension.setCompilerArgs(compilerArgs); extension.setSourceCompatibility(getSourceCompatibility(compilerArgs)); extension.setTargetCompatibility(getTargetCompatibility(compilerArgs)); + + // it's possible to set source/target compatibility in different places. + // Java plugin setting should override javacoptions + JavaVersion sourceVersion = null; + JavaVersion targetVersion = null; + if (GradleVersion.current().compareTo(GradleVersion.version("7.1")) >= 0) { + Object plugin = project.getExtensions().findByName("java"); + // must get `raw` versions or Gradle will substitute with toolchain versions + sourceVersion = Utils.invokeMethodIgnoreFail(plugin, "getRawSourceCompatibility"); + targetVersion = Utils.invokeMethodIgnoreFail(plugin, "getRawTargetCompatibility"); + } else { + // use deprecated `convention` + try { + Object convention = Utils.invokeMethod(project, "getConvention"); + Object plugins = Utils.invokeMethod(convention, "getPlugins"); + Method getGet = plugins.getClass().getMethod("get", Object.class); + Object plugin = getGet.invoke(plugins, "java"); + // must get `raw` versions or Gradle will substitute with toolchain versions + sourceVersion = Utils.invokeMethodIgnoreFail(plugin, "getSourceCompatibility"); + targetVersion = Utils.invokeMethodIgnoreFail(plugin, "getTargetCompatibility"); + } catch (NoSuchMethodException | SecurityException | IllegalAccessException + | IllegalArgumentException | InvocationTargetException e) { + // ignore + } + } + if (sourceVersion != null) { + extension.setSourceCompatibility(sourceVersion.toString()); + } + if (targetVersion != null) { + extension.setTargetCompatibility(targetVersion.toString()); + } return extension; } return null; 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 5b20f596..c00dce5c 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 @@ -37,6 +37,7 @@ import com.microsoft.java.bs.gradle.model.impl.DefaultGradleSourceSet; import com.microsoft.java.bs.gradle.model.impl.DefaultGradleSourceSets; import com.microsoft.java.bs.gradle.plugin.dependency.DependencyCollector; +import com.microsoft.java.bs.gradle.plugin.utils.Utils; /** * The model builder for Gradle source sets. @@ -148,9 +149,8 @@ private DefaultGradleSourceSet getSourceSet(Project project, SourceSet sourceSet break; } } else { - try { - Method getTestClassesDir = testTask.getClass().getMethod("getTestClassesDir"); - Object testClassesDir = getTestClassesDir.invoke(testTask); + Object testClassesDir = Utils.invokeMethodIgnoreFail(testTask, "getTestClassesDir"); + if (testClassesDir != null) { for (File sourceOutputDir : sourceOutputDirs) { if (sourceOutputDir.equals(testClassesDir)) { gradleSourceSet.setHasTests(true); @@ -160,9 +160,6 @@ private DefaultGradleSourceSet getSourceSet(Project project, SourceSet sourceSet if (gradleSourceSet.hasTests()) { break; } - } catch (NoSuchMethodException | SecurityException | IllegalAccessException - | IllegalArgumentException | InvocationTargetException e) { - // ignore } } } @@ -276,15 +273,12 @@ private Collection getSourceSetContainer(Project project) { // query the java plugin. This limits support to Java only if other // languages add their own sourcesets // use reflection because `getConvention` will be removed in Gradle 9.0 - Method getConvention = project.getClass().getMethod("getConvention"); - Object convention = getConvention.invoke(project); - Method getPlugins = convention.getClass().getMethod("getPlugins"); - Object plugins = getPlugins.invoke(convention); + Object convention = Utils.invokeMethod(project, "getConvention"); + Object plugins = Utils.invokeMethod(convention, "getPlugins"); Method getGet = plugins.getClass().getMethod("get", Object.class); Object pluginConvention = getGet.invoke(plugins, "java"); if (pluginConvention != null) { - Method getSourceSetsMethod = pluginConvention.getClass().getMethod("getSourceSets"); - return (SourceSetContainer) getSourceSetsMethod.invoke(pluginConvention); + return Utils.invokeMethod(pluginConvention, "getSourceSets"); } } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { @@ -329,19 +323,13 @@ private Set getArchiveSourcePaths(CopySpec copySpec) { sourcePaths.addAll(getArchiveSourcePaths(child)); } } else { - try { - Method getChildren = defaultCopySpec.getClass().getMethod("getChildren"); - Object children = getChildren.invoke(defaultCopySpec); - if (children instanceof Iterable) { - for (Object child : (Iterable) children) { - if (child instanceof CopySpec) { - sourcePaths.addAll(getArchiveSourcePaths((CopySpec) child)); - } + Object children = Utils.invokeMethodIgnoreFail(defaultCopySpec, "getChildren"); + if (children instanceof Iterable) { + for (Object child : (Iterable) children) { + if (child instanceof CopySpec) { + sourcePaths.addAll(getArchiveSourcePaths((CopySpec) child)); } } - } catch (NoSuchMethodException | IllegalAccessException - | IllegalArgumentException | InvocationTargetException e) { - // cannot get archive information } } } diff --git a/plugin/src/main/java/com/microsoft/java/bs/gradle/plugin/utils/Utils.java b/plugin/src/main/java/com/microsoft/java/bs/gradle/plugin/utils/Utils.java new file mode 100644 index 00000000..08ce6e4c --- /dev/null +++ b/plugin/src/main/java/com/microsoft/java/bs/gradle/plugin/utils/Utils.java @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +package com.microsoft.java.bs.gradle.plugin.utils; + +import java.lang.reflect.InvocationTargetException; + +/** + * Helper functions. + */ +public class Utils { + + /** + * Invoke a method by reflection and pass back any exceptions thrown. + * Method takes no parameters. + * + * @param expected return type + * @param object instance to invoke method on. + * @param methodName name of method to invoke. + * @return result of method called. + */ + @SuppressWarnings("unchecked") + public static A invokeMethod(Object object, String methodName) + throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { + return (A) object.getClass().getMethod(methodName).invoke(object); + } + + /** + * Invoke a method by reflection and return null if method cannot be called. + * Method takes no parameters. + * + * @param expected return type + * @param object instance to invoke method on. + * @param methodName name of method to invoke. + * @return result of method called. + */ + public static A invokeMethodIgnoreFail(Object object, String methodName) { + try { + return invokeMethod(object, methodName); + } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) { + return null; + } + } +} 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 7c75c4ce..8b70f7dc 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 @@ -16,7 +16,6 @@ import java.util.List; import java.util.function.Consumer; import java.util.stream.Stream; -import java.util.ArrayList; import com.microsoft.java.bs.gradle.model.GradleSourceSet; import com.microsoft.java.bs.gradle.model.GradleSourceSets; @@ -57,8 +56,8 @@ private GradleSourceSets getGradleSourceSets(ProjectConnection connect) throws I .addArguments("-Dorg.gradle.logging.level=quiet") .addJvmArguments("-Dbsp.gradle.supportedLanguages=" + String.join(",", SupportedLanguages.allBspNames)); - GradleSourceSets sourceSets = action.run(); - return new DefaultGradleSourceSets(new ArrayList<>(sourceSets.getGradleSourceSets())); + + return new DefaultGradleSourceSets(action.run()); } private interface ConnectionConsumer { @@ -465,6 +464,31 @@ void testJavaCompilerArgsToolchain(GradleVersion gradleVersion) throws IOExcepti }); } + @ParameterizedTest(name = "testJavaSourceTarget {0}") + @MethodSource("versionProvider") + void testJavaSourceTarget(GradleVersion gradleVersion) throws IOException { + // `java` cannot be used before 5.0 + assumeTrue(gradleVersion.compareTo(GradleVersion.version("5.0")) >= 0); + withSourceSets("java-source-target", gradleVersion, gradleSourceSets -> { + assertEquals(2, gradleSourceSets.getGradleSourceSets().size()); + for (GradleSourceSet gradleSourceSet : gradleSourceSets.getGradleSourceSets()) { + JavaExtension javaExtension = SupportedLanguages.JAVA.getExtension(gradleSourceSet); + assertNotNull(javaExtension); + String args = "|" + String.join("|", javaExtension.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|8"), () -> "Available args: " + args); + String version9 = gradleVersion.compareTo(GradleVersion.version("8.0")) >= 0 ? "9" : "1.9"; + assertEquals(version9, javaExtension.getSourceCompatibility(), + () -> "Available args: " + args); + assertEquals("1.8", javaExtension.getTargetCompatibility(), + () -> "Available args: " + args); + } + }); + } + @ParameterizedTest(name = "testScala2ModelBuilder {0}") @MethodSource("versionProvider") void testScala2ModelBuilder(GradleVersion gradleVersion) throws IOException { diff --git a/testProjects/java-source-target/build.gradle b/testProjects/java-source-target/build.gradle new file mode 100644 index 00000000..4c5b6a9d --- /dev/null +++ b/testProjects/java-source-target/build.gradle @@ -0,0 +1,12 @@ +plugins { + id 'java' +} + +java { + targetCompatibility = JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.VERSION_1_9 +} + +tasks.withType(JavaCompile) { + options.compilerArgs.addAll(['--release', '8']) +} diff --git a/testProjects/java-source-target/settings.gradle b/testProjects/java-source-target/settings.gradle new file mode 100644 index 00000000..65359d0d --- /dev/null +++ b/testProjects/java-source-target/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'java-source-target' \ No newline at end of file