diff --git a/README.md b/README.md index 8077515..3107d47 100644 --- a/README.md +++ b/README.md @@ -265,7 +265,10 @@ See `src/test/java/module-info.test` and `src/test/java/greeter/ScriptingTest.ja Fall-back to classpath mode ---- -If for whatever reason this is unwanted or introduces problems, you can enable classpath mode, which essentially turns of the plugin while running tests. +If for whatever reason this is unwanted or introduces problems, you can enable classpath mode, which essentially turns off the plugin while running tests. + +<details open> +<summary>Groovy DSL</summary> ```groovy test { @@ -275,6 +278,22 @@ test { } ``` +</details> +<details> +<summary>Kotlin DSL</summary> + +```kotlin +tasks { + test { + extensions.configure(TestModuleOptions::class) { + runOnClasspath = true + } + } +} +``` + +</details> + Blackbox testing === @@ -286,7 +305,7 @@ This module `requires` and/or `uses` the module under test, and tests it's exter In the following example we test a module `greeter.provider`, which provides a service implementation of type `Greeter`. The `Greeter` type is provided by yet another module `greeter.api`. -The test module would typically be named something similar to the the module it's testing, e.g. `greeter.provider.test`. +The test module would typically be named something similar to the module it's testing, e.g. `greeter.provider.test`. In `src/main/java` it has some code that looks like code that you would normally write to use the module that's being tested. For example, we do a service lookup. @@ -473,6 +492,75 @@ patchModules.config = [ ] ``` +Compilation +=== + +Compilation to a specific Java release +---- + +You might want to run your builds on a recent JDK (e.g. JDK 12), but target an older version of Java, e.g.: +- Java 11, which is the current [Long-Term Support (LTS) release](https://www.oracle.com/technetwork/java/java-se-support-roadmap.html), +- Java 8, whose production use in 2018 was almost 85%, according to [this survey](https://www.baeldung.com/java-in-2018). + +You can do that by setting the Java compiler [`--release`][javacRelease] option +(e.g. to `6` for Java 6, etc.). Note that when you build using: +- JDK 11: you can only target Java 6-11 using its +[`--release`](https://docs.oracle.com/en/java/javase/11/tools/javac.html) option, +- JDK 12: you can only target Java 7-12 using its +[`--release`](https://docs.oracle.com/en/java/javase/12/tools/javac.html) option, +- etc. + +Finally, note that JPMS was introduced in Java 9, so you can't compile `module-info.java` to Java release 6-8 +(this plugin provides a workaround for that, though — see below). + +Concluding, to configure your project to support JPMS and target: +- Java **6-8**: call the [`modularity.mixedJavaRelease`][ModularityExtension] function +(see [Separate compilation of `module-info.java`](#separate-compilation-of-module-infojava) for details), +- Java **9+**: call the [`modularity.standardJavaRelease`][ModularityExtension] function, + +and the plugin will take care of setting the [`--release`][javacRelease] option(s) appropriately. + + +Separate compilation of `module-info.java` +---- + +If you need to compile the main `module-info.java` separately from the rest of `src/main/java` +files, you can enable `compileModuleInfoSeparately` option on `compileJava` task. It will exclude `module-info.java` +from `compileJava` and introduce a dedicated `compileModuleInfoJava` task. + +Typically, this feature would be used by libraries which target JDK 6-8 but want to make the most of JPMS by: +- providing `module-info.class` for consumers who put the library on module path, +- compiling `module-info.java` against the remaining classes of this module and against other modules +(which provides better encapsulation and prevents introducing split packages). + +This plugin provides an easy way to do just that by means of its +[`modularity.mixedJavaRelease`][ModularityExtension] function, which implicitly sets +`compileJava.compileModuleInfoSeparately = true` and configures the [`--release`][javacRelease] compiler options. + +For example, if your library targets JDK 8, and you want your `module-info.class` to target JDK 9 +(default), put the following line in your `build.gradle(.kts)`: + +<details open> +<summary>Groovy DSL</summary> + +```groovy +modularity.mixedJavaRelease 8 +``` + +</details> +<details> +<summary>Kotlin DSL</summary> + +```kotlin +modularity.mixedJavaRelease(8) +``` + +</details> + +Note that `modularity.mixedJavaRelease` does *not* configure a +[multi-release JAR](https://docs.oracle.com/javase/9/docs/specs/jar/jar.html#Multi-release) +(in other words, `module-info.class` remains in the root directory of the JAR). + Limitations === @@ -495,3 +583,7 @@ Contributions are very much welcome. Please open a Pull Request with your changes. Make sure to rebase before creating the PR so that the PR only contains your changes, this makes the review process much easier. Again, bonus points for providing tests for your changes. + + +[javacRelease]: http://openjdk.java.net/jeps/247 +[ModularityExtension]: src/main/java/org/javamodularity/moduleplugin/extensions/ModularityExtension.java diff --git a/build.gradle b/build.gradle index 1619d50..beb54fc 100644 --- a/build.gradle +++ b/build.gradle @@ -58,6 +58,7 @@ test { testLogging { events 'PASSED', 'FAILED', 'SKIPPED' + stackTraceFilters = [] } } diff --git a/src/main/java/org/javamodularity/moduleplugin/JavaProjectHelper.java b/src/main/java/org/javamodularity/moduleplugin/JavaProjectHelper.java new file mode 100644 index 0000000..0e2150f --- /dev/null +++ b/src/main/java/org/javamodularity/moduleplugin/JavaProjectHelper.java @@ -0,0 +1,63 @@ +package org.javamodularity.moduleplugin; + +import org.gradle.api.Project; +import org.gradle.api.Task; +import org.gradle.api.plugins.JavaPlugin; +import org.gradle.api.tasks.SourceSet; +import org.gradle.api.tasks.SourceSetContainer; +import org.gradle.api.tasks.compile.JavaCompile; + +import java.util.Optional; + +/** + * Generic helper for Gradle {@link Project} API that has {@link JavaPlugin} applied. + */ +public final class JavaProjectHelper { + + private final Project project; + + public JavaProjectHelper(Project project) { + this.project = project; + } + + public Project project() { + return project; + } + + //region SOURCE SETS + public SourceSetContainer sourceSets() { + return project.getExtensions().getByType(SourceSetContainer.class); + } + + public SourceSet sourceSet(String sourceSetName) { + return sourceSets().getByName(sourceSetName); + } + + public SourceSet mainSourceSet() { + return sourceSet(SourceSet.MAIN_SOURCE_SET_NAME); + } + + public SourceSet testSourceSet(String sourceSetName) { + return sourceSet(SourceSet.TEST_SOURCE_SET_NAME); + } + //endregion + + //region TASKS + public Task task(String taskName) { + return project.getTasks().getByName(taskName); + } + + public JavaCompile compileJavaTask(String taskName) { + return (JavaCompile) task(taskName); + } + + public Optional<Task> findTask(String taskName) { + return Optional.ofNullable(project.getTasks().findByName(taskName)); + } + + public Optional<JavaCompile> findCompileJavaTask(String taskName) { + return findTask(taskName).map(JavaCompile.class::cast); + } + //endregion + +} diff --git a/src/main/java/org/javamodularity/moduleplugin/ModuleSystemPlugin.java b/src/main/java/org/javamodularity/moduleplugin/ModuleSystemPlugin.java index 47d51a6..4f31a40 100644 --- a/src/main/java/org/javamodularity/moduleplugin/ModuleSystemPlugin.java +++ b/src/main/java/org/javamodularity/moduleplugin/ModuleSystemPlugin.java @@ -2,28 +2,33 @@ import org.gradle.api.Plugin; import org.gradle.api.Project; +import org.gradle.api.plugins.ExtensionContainer; import org.gradle.api.plugins.JavaPlugin; +import org.javamodularity.moduleplugin.extensions.DefaultModularityExtension; +import org.javamodularity.moduleplugin.extensions.ModularityExtension; import org.javamodularity.moduleplugin.tasks.*; -import java.util.Optional; - public class ModuleSystemPlugin implements Plugin<Project> { @Override public void apply(Project project) { project.getPlugins().apply(JavaPlugin.class); - Optional<String> foundModuleName = new ModuleName().findModuleName(project); - foundModuleName.ifPresent(moduleName -> { - project.getExtensions().add("moduleName", moduleName); - project.getExtensions().create("patchModules", PatchModuleExtension.class); + new ModuleName().findModuleName(project).ifPresent(moduleName -> configureModularity(project, moduleName)); + } + + private void configureModularity(Project project, String moduleName) { + ExtensionContainer extensions = project.getExtensions(); + extensions.add("moduleName", moduleName); + extensions.create("patchModules", PatchModuleExtension.class); + extensions.create(ModularityExtension.class, "modularity", DefaultModularityExtension.class, project); - new CompileTask().configureCompileJava(project); - new CompileTestTask().configureCompileTestJava(project, moduleName); - new TestTask().configureTestJava(project, moduleName); - new RunTask().configureRun(project, moduleName); - new JavadocTask().configureJavaDoc(project); - ModularJavaExec.configure(project, moduleName); - ModularCreateStartScripts.configure(project, moduleName); - }); + new CompileTask(project).configureCompileJava(); + new CompileModuleInfoTask(project).configureCompileModuleInfoJava(); + new CompileTestTask().configureCompileTestJava(project, moduleName); + new TestTask().configureTestJava(project, moduleName); + new RunTask().configureRun(project, moduleName); + new JavadocTask().configureJavaDoc(project); + ModularJavaExec.configure(project, moduleName); + ModularCreateStartScripts.configure(project, moduleName); } } diff --git a/src/main/java/org/javamodularity/moduleplugin/extensions/CompileModuleOptions.java b/src/main/java/org/javamodularity/moduleplugin/extensions/CompileModuleOptions.java new file mode 100644 index 0000000..d4800fd --- /dev/null +++ b/src/main/java/org/javamodularity/moduleplugin/extensions/CompileModuleOptions.java @@ -0,0 +1,35 @@ +package org.javamodularity.moduleplugin.extensions; + +import org.gradle.api.Project; +import org.gradle.api.tasks.compile.JavaCompile; +import org.javamodularity.moduleplugin.tasks.ModuleOptions; + +public class CompileModuleOptions extends ModuleOptions { + + /** + * Name of the extra Java compile task created if {@code compileModuleInfoSeparately} is {@code true}. + */ + public static final String COMPILE_MODULE_INFO_TASK_NAME = "compileModuleInfoJava"; + + private final Project project; + + private boolean compileModuleInfoSeparately = false; + + public CompileModuleOptions(Project project) { + super(project); + this.project = project; + } + + public boolean getCompileModuleInfoSeparately() { + return compileModuleInfoSeparately; + } + + public void setCompileModuleInfoSeparately(boolean compileModuleInfoSeparately) { + if (compileModuleInfoSeparately) { + // we need to create "compileModuleInfoJava" task eagerly so that the user can configure it immediately + project.getTasks().maybeCreate(COMPILE_MODULE_INFO_TASK_NAME, JavaCompile.class); + } + this.compileModuleInfoSeparately = compileModuleInfoSeparately; + } + +} diff --git a/src/main/java/org/javamodularity/moduleplugin/extensions/DefaultModularityExtension.java b/src/main/java/org/javamodularity/moduleplugin/extensions/DefaultModularityExtension.java new file mode 100644 index 0000000..f173a70 --- /dev/null +++ b/src/main/java/org/javamodularity/moduleplugin/extensions/DefaultModularityExtension.java @@ -0,0 +1,90 @@ +package org.javamodularity.moduleplugin.extensions; + +import org.gradle.api.JavaVersion; +import org.gradle.api.Project; +import org.gradle.api.plugins.JavaPlugin; +import org.gradle.api.tasks.compile.JavaCompile; +import org.javamodularity.moduleplugin.JavaProjectHelper; + +import java.util.List; + +public class DefaultModularityExtension implements ModularityExtension { + + private final Project project; + + public DefaultModularityExtension(Project project) { + this.project = project; + } + + @Override + public void standardJavaRelease(int mainJavaRelease) { + if (mainJavaRelease < 9) { + throw new IllegalArgumentException(String.format( + "Invalid main --release value: %d. Use 'mixedJavaRelease' instead.", mainJavaRelease + )); + } + project.afterEvaluate(p -> configureStandardJavaRelease(mainJavaRelease)); + } + + private void configureStandardJavaRelease(int mainJavaRelease) { + JavaCompile compileJava = helper().compileJavaTask(JavaPlugin.COMPILE_JAVA_TASK_NAME); + setJavaRelease(compileJava, mainJavaRelease); + } + + @Override + public void mixedJavaRelease(int mainJavaRelease, int moduleInfoJavaRelease) { + validateMixedJavaReleaseArgs(mainJavaRelease, moduleInfoJavaRelease); + + CompileModuleOptions moduleOptions = helper().compileJavaTask(JavaPlugin.COMPILE_JAVA_TASK_NAME) + .getExtensions().getByType(CompileModuleOptions.class); + moduleOptions.setCompileModuleInfoSeparately(true); + + project.afterEvaluate(p -> configureMixedJavaRelease(mainJavaRelease, moduleInfoJavaRelease)); + } + + private static void validateMixedJavaReleaseArgs(int mainJavaRelease, int moduleInfoJavaRelease) { + if (mainJavaRelease < 6) { + throw new IllegalArgumentException("Invalid main --release value: " + mainJavaRelease); + } + if (mainJavaRelease > 8) { + throw new IllegalArgumentException(String.format( + "Invalid main --release value: %d. Use 'standardJavaRelease' instead.", mainJavaRelease + )); + } + if (moduleInfoJavaRelease < 9) { + throw new IllegalArgumentException("Invalid module-info --release value: " + moduleInfoJavaRelease); + } + } + + private void configureMixedJavaRelease(int mainJavaRelease, int moduleInfoJavaRelease) { + var compileJava = helper().compileJavaTask(JavaPlugin.COMPILE_JAVA_TASK_NAME); + setJavaRelease(compileJava, mainJavaRelease); + + var compileModuleInfoJava = helper().compileJavaTask(CompileModuleOptions.COMPILE_MODULE_INFO_TASK_NAME); + setJavaRelease(compileModuleInfoJava, moduleInfoJavaRelease); + } + + // TODO: Remove this method when Gradle supports it natively: https://github.com/gradle/gradle/issues/2510 + private void setJavaRelease(JavaCompile javaCompile, int javaRelease) { + String currentJavaVersion = JavaVersion.current().toString(); + if (!javaCompile.getSourceCompatibility().equals(currentJavaVersion)) { + throw new IllegalStateException("sourceCompatibility should not be set together with --release option"); + } + if (!javaCompile.getTargetCompatibility().equals(currentJavaVersion)) { + throw new IllegalStateException("targetCompatibility should not be set together with --release option"); + } + + List<String> compilerArgs = javaCompile.getOptions().getCompilerArgs(); + if (compilerArgs.contains("--release")) { + throw new IllegalStateException("--release option is already set in compiler args"); + } + + compilerArgs.add("--release"); + compilerArgs.add(String.valueOf(javaRelease)); + } + + private JavaProjectHelper helper() { + return new JavaProjectHelper(project); + } + +} diff --git a/src/main/java/org/javamodularity/moduleplugin/extensions/ModularityExtension.java b/src/main/java/org/javamodularity/moduleplugin/extensions/ModularityExtension.java new file mode 100644 index 0000000..f564d72 --- /dev/null +++ b/src/main/java/org/javamodularity/moduleplugin/extensions/ModularityExtension.java @@ -0,0 +1,46 @@ +package org.javamodularity.moduleplugin.extensions; + +/** + * A project-wide extension that provides the most common modularity-related actions. + * + * @see DefaultModularityExtension + */ +public interface ModularityExtension { + + /** + * Calling this method results in all Java classes being compiled to Java release 9+ (as given by the + * {@code mainJavaRelease} parameter). + * <p> + * See details about the {@code --release} option + * <a href="https://docs.oracle.com/en/java/javase/11/tools/javac.html">here</a>. + * + * @param mainJavaRelease value for the {@code --release} option of {@code compileJava} task (allowed range: 9+) + */ + void standardJavaRelease(int mainJavaRelease); + + /** + * Calling this method results in all Java classes being compiled to Java release 6-8 (as given by the + * {@code mainJavaRelease} parameter), with the exception of {@code module-info.java} being compiled to + * Java release 9. + * + * @param mainJavaRelease value for the {@code --release} option of {@code compileJava} task (allowed range: 6-8) + */ + default void mixedJavaRelease(int mainJavaRelease) { + mixedJavaRelease(mainJavaRelease, 9); + } + + /** + * Calling this method results in all Java classes being compiled to Java release 6-8 (as given by the + * {@code mainJavaRelease} parameter), with the exception of {@code module-info.java} being compiled to + * Java release 9+ (as given by the {@code moduleInfoJavaRelease} parameter). + * <p> + * See details about the {@code --release} option + * <a href="https://docs.oracle.com/en/java/javase/11/tools/javac.html">here</a>. + * + * @param mainJavaRelease value for the {@code --release} option of {@code compileJava} task + * (allowed range: 6-8) + * @param moduleInfoJavaRelease value for the {@code --release} option of {@code compileModuleInfoJava} task + * (allowed range: 9+) + */ + void mixedJavaRelease(int mainJavaRelease, int moduleInfoJavaRelease); +} diff --git a/src/main/java/org/javamodularity/moduleplugin/tasks/AbstractCompileTask.java b/src/main/java/org/javamodularity/moduleplugin/tasks/AbstractCompileTask.java new file mode 100644 index 0000000..732bad8 --- /dev/null +++ b/src/main/java/org/javamodularity/moduleplugin/tasks/AbstractCompileTask.java @@ -0,0 +1,24 @@ +package org.javamodularity.moduleplugin.tasks; + +import org.gradle.api.Project; +import org.gradle.api.tasks.compile.JavaCompile; +import org.javamodularity.moduleplugin.JavaProjectHelper; +import org.javamodularity.moduleplugin.extensions.CompileModuleOptions; + +abstract class AbstractCompileTask { + + protected final Project project; + + AbstractCompileTask(Project project) { + this.project = project; + } + + final CompileJavaTaskMutator createCompileJavaTaskMutator( + JavaCompile compileJava, CompileModuleOptions moduleOptions) { + return new CompileJavaTaskMutator(project, compileJava.getClasspath(), moduleOptions); + } + + final JavaProjectHelper helper() { + return new JavaProjectHelper(project); + } +} diff --git a/src/main/java/org/javamodularity/moduleplugin/tasks/CompileJavaTaskMutator.java b/src/main/java/org/javamodularity/moduleplugin/tasks/CompileJavaTaskMutator.java index 73e2ed0..eb8871b 100644 --- a/src/main/java/org/javamodularity/moduleplugin/tasks/CompileJavaTaskMutator.java +++ b/src/main/java/org/javamodularity/moduleplugin/tasks/CompileJavaTaskMutator.java @@ -1,34 +1,60 @@ package org.javamodularity.moduleplugin.tasks; import org.gradle.api.Project; +import org.gradle.api.file.FileCollection; import org.gradle.api.tasks.compile.AbstractCompile; import org.gradle.api.tasks.compile.JavaCompile; +import org.javamodularity.moduleplugin.extensions.CompileModuleOptions; import java.util.ArrayList; import java.util.List; class CompileJavaTaskMutator { - static void mutateJavaCompileTask(Project project, JavaCompile compileJava) { - ModuleOptions moduleOptions = compileJava.getExtensions().getByType(ModuleOptions.class); + private static final String COMPILE_KOTLIN_TASK_NAME = "compileKotlin"; + + private final Project project; + /** + * {@linkplain JavaCompile#getClasspath() Classpath} of {@code compileJava} task. + */ + private final FileCollection compileJavaClasspath; + /** + * {@link CompileModuleOptions} of {@code compileJava} task. + */ + private final CompileModuleOptions moduleOptions; + + CompileJavaTaskMutator(Project project, FileCollection compileJavaClasspath, CompileModuleOptions moduleOptions) { + this.project = project; + this.compileJavaClasspath = compileJavaClasspath; + this.moduleOptions = moduleOptions; + } + + /** + * The argument is a {@link JavaCompile} task whose modularity is to be configured. + * + * @param javaCompile {@code compileJava} if {@link CompileModuleOptions#getCompileModuleInfoSeparately()} + * is {@code false}, {@code compileModuleInfoJava} if it is {@code true} + */ + void modularizeJavaCompileTask(JavaCompile javaCompile) { PatchModuleExtension patchModuleExtension = project.getExtensions().getByType(PatchModuleExtension.class); - var compilerArgs = new ArrayList<>(compileJava.getOptions().getCompilerArgs()); + var compilerArgs = new ArrayList<>(javaCompile.getOptions().getCompilerArgs()); - compilerArgs.addAll(List.of("--module-path", compileJava.getClasspath() + compilerArgs.addAll(List.of("--module-path", compileJavaClasspath .filter(patchModuleExtension::isUnpatched) .getAsPath())); String moduleName = (String) project.getExtensions().findByName("moduleName"); moduleOptions.mutateArgs(moduleName, compilerArgs); - compilerArgs.addAll(patchModuleExtension.configure(compileJava.getClasspath())); - compileJava.getOptions().setCompilerArgs(compilerArgs); - compileJava.setClasspath(project.files()); + compilerArgs.addAll(patchModuleExtension.configure(compileJavaClasspath)); + javaCompile.getOptions().setCompilerArgs(compilerArgs); + javaCompile.setClasspath(project.files()); - AbstractCompile compileKotlin = (AbstractCompile) project.getTasks().findByName("compileKotlin"); + // https://github.com/java9-modularity/gradle-modules-plugin/issues/45 + AbstractCompile compileKotlin = (AbstractCompile) project.getTasks().findByName(COMPILE_KOTLIN_TASK_NAME); if (compileKotlin != null) { - compileJava.setDestinationDir(compileKotlin.getDestinationDir()); + javaCompile.setDestinationDir(compileKotlin.getDestinationDir()); } } diff --git a/src/main/java/org/javamodularity/moduleplugin/tasks/CompileModuleInfoTask.java b/src/main/java/org/javamodularity/moduleplugin/tasks/CompileModuleInfoTask.java new file mode 100644 index 0000000..b4a6f27 --- /dev/null +++ b/src/main/java/org/javamodularity/moduleplugin/tasks/CompileModuleInfoTask.java @@ -0,0 +1,81 @@ +package org.javamodularity.moduleplugin.tasks; + +import org.gradle.api.Action; +import org.gradle.api.Project; +import org.gradle.api.Task; +import org.gradle.api.plugins.JavaPlugin; +import org.gradle.api.tasks.compile.JavaCompile; +import org.javamodularity.moduleplugin.extensions.CompileModuleOptions; + +import java.nio.file.Files; +import java.nio.file.Path; + +public class CompileModuleInfoTask extends AbstractCompileTask { + + public CompileModuleInfoTask(Project project) { + super(project); + } + + /** + * @see CompileTask#configureCompileJava() + */ + public void configureCompileModuleInfoJava() { + helper().findCompileJavaTask(JavaPlugin.COMPILE_JAVA_TASK_NAME) + .ifPresent(this::configureCompileModuleInfoJava); + } + + private void configureCompileModuleInfoJava(JavaCompile compileJava) { + var moduleOptions = compileJava.getExtensions().getByType(CompileModuleOptions.class); + project.afterEvaluate(p -> { + if (moduleOptions.getCompileModuleInfoSeparately()) { + configureModularityForCompileModuleInfoJava(compileJava, moduleOptions); + } + }); + } + + /** + * @see CompileTask#configureModularityForCompileJava + */ + void configureModularityForCompileModuleInfoJava( + JavaCompile compileJava, CompileModuleOptions moduleOptions) { + JavaCompile compileModuleInfoJava = preconfigureCompileModuleInfoJava(compileJava); + CompileJavaTaskMutator mutator = createCompileJavaTaskMutator(compileJava, moduleOptions); + + // don't convert to lambda: https://github.com/java9-modularity/gradle-modules-plugin/issues/54 + compileModuleInfoJava.doFirst(new Action<Task>() { + @Override + public void execute(Task task) { + mutator.modularizeJavaCompileTask(compileModuleInfoJava); + } + }); + } + + /** + * Preconfigures a separate task that is meant to compile {@code module-info.java} separately. + * Final (modular) configuration is performed later by {@link CompileJavaTaskMutator}. + */ + private JavaCompile preconfigureCompileModuleInfoJava(JavaCompile compileJava) { + var compileModuleInfoJava = helper().compileJavaTask(CompileModuleOptions.COMPILE_MODULE_INFO_TASK_NAME); + + compileModuleInfoJava.setClasspath(project.files()); // empty + compileModuleInfoJava.setSource(pathToModuleInfoJava()); + compileModuleInfoJava.setDestinationDir(compileJava.getDestinationDir()); + + // we need all the compiled classes before compiling module-info.java + compileModuleInfoJava.dependsOn(compileJava); + + // make "classes" trigger module-info.java compilation + helper().task(JavaPlugin.CLASSES_TASK_NAME).dependsOn(compileModuleInfoJava); + + return compileModuleInfoJava; + } + + private Path pathToModuleInfoJava() { + return helper().mainSourceSet().getJava().getSrcDirs().stream() + .map(srcDir -> srcDir.toPath().resolve("module-info.java")) + .filter(Files::exists) + .findFirst() + .orElseThrow(() -> new IllegalStateException("module-info.java not found")); + } + +} diff --git a/src/main/java/org/javamodularity/moduleplugin/tasks/CompileTask.java b/src/main/java/org/javamodularity/moduleplugin/tasks/CompileTask.java index 755d1b8..7f6b6e7 100644 --- a/src/main/java/org/javamodularity/moduleplugin/tasks/CompileTask.java +++ b/src/main/java/org/javamodularity/moduleplugin/tasks/CompileTask.java @@ -5,26 +5,45 @@ import org.gradle.api.Task; import org.gradle.api.plugins.JavaPlugin; import org.gradle.api.tasks.compile.JavaCompile; +import org.javamodularity.moduleplugin.extensions.CompileModuleOptions; -public class CompileTask { +public class CompileTask extends AbstractCompileTask { - public void configureCompileJava(Project project) { - JavaCompile compileJava = (JavaCompile) project.getTasks().findByName(JavaPlugin.COMPILE_JAVA_TASK_NAME); - if (compileJava != null) { - compileJava.getExtensions().create("moduleOptions", ModuleOptions.class, project); + public CompileTask(Project project) { + super(project); + } - compileJava.doFirst(new Action<Task>() { + /** + * @see CompileModuleInfoTask#configureCompileModuleInfoJava() + */ + public void configureCompileJava() { + helper().findCompileJavaTask(JavaPlugin.COMPILE_JAVA_TASK_NAME) + .ifPresent(this::configureCompileJava); + } - /* (non-Javadoc) - * @see org.gradle.api.Action#execute(java.lang.Object) - */ - @Override - public void execute(Task task) { - CompileJavaTaskMutator.mutateJavaCompileTask(project, compileJava); - } + private void configureCompileJava(JavaCompile compileJava) { + var moduleOptions = compileJava.getExtensions().create("moduleOptions", CompileModuleOptions.class, project); + project.afterEvaluate(p -> { + if (moduleOptions.getCompileModuleInfoSeparately()) { + compileJava.exclude("module-info.java"); + } else { + configureModularityForCompileJava(compileJava, moduleOptions); + } + }); + } - }); - } + /** + * @see CompileModuleInfoTask#configureModularityForCompileModuleInfoJava + */ + void configureModularityForCompileJava(JavaCompile compileJava, CompileModuleOptions moduleOptions) { + CompileJavaTaskMutator mutator = createCompileJavaTaskMutator(compileJava, moduleOptions); + // don't convert to lambda: https://github.com/java9-modularity/gradle-modules-plugin/issues/54 + compileJava.doFirst(new Action<Task>() { + @Override + public void execute(Task task) { + mutator.modularizeJavaCompileTask(compileJava); + } + }); } } diff --git a/src/main/java/org/javamodularity/moduleplugin/tasks/CompileTestTask.java b/src/main/java/org/javamodularity/moduleplugin/tasks/CompileTestTask.java index 4137650..334d53b 100644 --- a/src/main/java/org/javamodularity/moduleplugin/tasks/CompileTestTask.java +++ b/src/main/java/org/javamodularity/moduleplugin/tasks/CompileTestTask.java @@ -1,9 +1,5 @@ package org.javamodularity.moduleplugin.tasks; -import java.util.ArrayList; -import java.util.List; -import java.util.function.Consumer; - import org.gradle.api.Action; import org.gradle.api.Project; import org.gradle.api.Task; @@ -13,6 +9,9 @@ import org.gradle.api.tasks.compile.JavaCompile; import org.javamodularity.moduleplugin.TestEngine; +import java.util.ArrayList; +import java.util.List; + public class CompileTestTask { public void configureCompileTestJava(Project project, String moduleName) { @@ -23,11 +22,8 @@ public void configureCompileTestJava(Project project, String moduleName) { compileTestJava.getExtensions().create("moduleOptions", ModuleOptions.class, project); SourceSet testSourceSet = javaConvention.getSourceSets().getByName(SourceSet.TEST_SOURCE_SET_NAME); + // don't convert to lambda: https://github.com/java9-modularity/gradle-modules-plugin/issues/54 compileTestJava.doFirst(new Action<Task>() { - - /* (non-Javadoc) - * @see org.gradle.api.Action#execute(java.lang.Object) - */ @Override public void execute(Task task) { var args = new ArrayList<>(compileTestJava.getOptions().getCompilerArgs()); @@ -38,19 +34,10 @@ public void execute(Task task) { "--patch-module", moduleName + "=" + testSourceSet.getJava().getSourceDirectories().getAsPath() )); - TestEngine.select(project).ifPresent(new Consumer<TestEngine>() { - - /* (non-Javadoc) - * @see java.util.function.Consumer#accept(java.lang.Object) - */ - @Override - public void accept(TestEngine testEngine) { - args.addAll(List.of( - "--add-modules", testEngine.moduleName, - "--add-reads", moduleName + "=" + testEngine.moduleName)); - } - - }); + TestEngine.select(project).ifPresent(testEngine -> args.addAll(List.of( + "--add-modules", testEngine.moduleName, + "--add-reads", moduleName + "=" + testEngine.moduleName + ))); ModuleOptions moduleOptions = compileTestJava.getExtensions().getByType(ModuleOptions.class); moduleOptions.mutateArgs(moduleName, args); diff --git a/src/main/java/org/javamodularity/moduleplugin/tasks/JavadocTask.java b/src/main/java/org/javamodularity/moduleplugin/tasks/JavadocTask.java index 3238f3a..816b4fd 100644 --- a/src/main/java/org/javamodularity/moduleplugin/tasks/JavadocTask.java +++ b/src/main/java/org/javamodularity/moduleplugin/tasks/JavadocTask.java @@ -4,7 +4,6 @@ import org.gradle.api.Project; import org.gradle.api.Task; import org.gradle.api.plugins.JavaPlugin; -import org.gradle.api.tasks.compile.JavaCompile; import org.gradle.api.tasks.javadoc.Javadoc; import org.gradle.external.javadoc.CoreJavadocOptions; @@ -16,11 +15,8 @@ public void configureJavaDoc(Project project) { javadoc.getExtensions().create("moduleOptions", ModuleOptions.class, project); PatchModuleExtension patchModuleExtension = project.getExtensions().getByType(PatchModuleExtension.class); + // don't convert to lambda: https://github.com/java9-modularity/gradle-modules-plugin/issues/54 javadoc.doFirst(new Action<Task>() { - - /* (non-Javadoc) - * @see org.gradle.api.Action#execute(java.lang.Object) - */ @Override public void execute(Task task) { ModuleOptions moduleOptions = javadoc.getExtensions().getByType(ModuleOptions.class); diff --git a/src/main/java/org/javamodularity/moduleplugin/tasks/ModuleOptions.java b/src/main/java/org/javamodularity/moduleplugin/tasks/ModuleOptions.java index 0d9fca2..33fa964 100644 --- a/src/main/java/org/javamodularity/moduleplugin/tasks/ModuleOptions.java +++ b/src/main/java/org/javamodularity/moduleplugin/tasks/ModuleOptions.java @@ -52,7 +52,7 @@ public void setAddOpens(Map<String, String> addOpens) { this.addOpens = addOpens; } - void mutateArgs(String moduleName, List<String> args) { + protected void mutateArgs(String moduleName, List<String> args) { if (!getAddModules().isEmpty()) { String addModules = String.join(",", getAddModules()); args.add("--add-modules"); diff --git a/src/main/java/org/javamodularity/moduleplugin/tasks/RunTaskMutator.java b/src/main/java/org/javamodularity/moduleplugin/tasks/RunTaskMutator.java index 266e41b..5cadbc3 100644 --- a/src/main/java/org/javamodularity/moduleplugin/tasks/RunTaskMutator.java +++ b/src/main/java/org/javamodularity/moduleplugin/tasks/RunTaskMutator.java @@ -6,8 +6,7 @@ import org.gradle.api.Task; import org.gradle.api.distribution.Distribution; import org.gradle.api.distribution.DistributionContainer; -import org.gradle.api.file.CopySpec; -import org.gradle.api.file.FileCollection; +import org.gradle.api.file.RelativePath; import org.gradle.api.logging.Logger; import org.gradle.api.logging.Logging; import org.gradle.api.plugins.JavaPluginConvention; @@ -21,7 +20,6 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; -import java.util.Collections; import java.util.List; public class RunTaskMutator { @@ -54,6 +52,7 @@ public void updateStartScriptsTask(String taskStartScriptsName) { public void updateStartScriptsTask(CreateStartScripts startScriptsTask) { PatchModuleExtension patchModuleExtension = project.getExtensions().getByType(PatchModuleExtension.class); + // don't convert to lambda: https://github.com/java9-modularity/gradle-modules-plugin/issues/54 startScriptsTask.doFirst(new Action<Task>() { @Override public void execute(final Task task) { @@ -86,6 +85,7 @@ public void execute(final Task task) { } }); + // don't convert to lambda: https://github.com/java9-modularity/gradle-modules-plugin/issues/54 startScriptsTask.doLast(new Action<Task>() { @Override public void execute(final Task task) { @@ -100,20 +100,18 @@ public void execute(final Task task) { public void movePatchedLibs() { PatchModuleExtension patchModuleExtension = project.getExtensions().getByType(PatchModuleExtension.class); - if(!patchModuleExtension.getConfig().isEmpty()) { + if (!patchModuleExtension.getConfig().isEmpty()) { Distribution distribution = ((DistributionContainer) project.getExtensions().getByName("distributions")).getByName("main"); - distribution.contents(new Action<CopySpec>() { - @Override - public void execute(CopySpec copySpec) { - copySpec.filesMatching(patchModuleExtension.getJars(), (action) -> { - action.setRelativePath(action.getRelativePath().getParent().getParent().append(true, "patchlibs", action.getName())); - }); - } - }); + distribution.contents(copySpec -> copySpec.filesMatching(patchModuleExtension.getJars(), action -> { + RelativePath relativePath = action.getRelativePath().getParent().getParent() + .append(true, "patchlibs", action.getName()); + action.setRelativePath(relativePath); + })); } } private void updateJavaExecTask() { + // don't convert to lambda: https://github.com/java9-modularity/gradle-modules-plugin/issues/54 execTask.doFirst(new Action<Task>() { @Override public void execute(final Task task) { diff --git a/src/main/java/org/javamodularity/moduleplugin/tasks/TestModuleOptions.java b/src/main/java/org/javamodularity/moduleplugin/tasks/TestModuleOptions.java index 0e2682b..15fd3b2 100644 --- a/src/main/java/org/javamodularity/moduleplugin/tasks/TestModuleOptions.java +++ b/src/main/java/org/javamodularity/moduleplugin/tasks/TestModuleOptions.java @@ -10,7 +10,7 @@ public TestModuleOptions(Project project) { super(project); } - public boolean isRunOnClasspath() { + public boolean getRunOnClasspath() { return runOnClasspath; } diff --git a/src/main/java/org/javamodularity/moduleplugin/tasks/TestTask.java b/src/main/java/org/javamodularity/moduleplugin/tasks/TestTask.java index d56ecce..05dfeff 100644 --- a/src/main/java/org/javamodularity/moduleplugin/tasks/TestTask.java +++ b/src/main/java/org/javamodularity/moduleplugin/tasks/TestTask.java @@ -1,21 +1,5 @@ package org.javamodularity.moduleplugin.tasks; -import static java.io.File.pathSeparator; - -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Set; -import java.util.TreeSet; -import java.util.function.Consumer; -import java.util.regex.Pattern; -import java.util.stream.Collectors; -import java.util.stream.Stream; - import org.codehaus.groovy.tools.Utilities; import org.gradle.api.Action; import org.gradle.api.GradleException; @@ -29,6 +13,17 @@ import org.gradle.api.tasks.testing.Test; import org.javamodularity.moduleplugin.TestEngine; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.*; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static java.io.File.pathSeparator; + public class TestTask { private static final Logger LOGGER = Logging.getLogger(TestTask.class); private static Pattern CLASS_FILE_SPLITTER = Pattern.compile("[./\\\\]"); @@ -42,23 +37,21 @@ public void configureTestJava(Project project, String moduleName) { SourceSet mainSourceSet = javaConvention.getSourceSets().getByName(SourceSet.MAIN_SOURCE_SET_NAME); testJava.getExtensions().create("moduleOptions", TestModuleOptions.class, project); + // don't convert to lambda: https://github.com/java9-modularity/gradle-modules-plugin/issues/54 testJava.doFirst(new Action<Task>() { - - /* (non-Javadoc) - * @see org.gradle.api.Action#execute(java.lang.Object) - */ @Override public void execute(Task task) { TestModuleOptions testModuleOptions = testJava.getExtensions().getByType(TestModuleOptions.class); - if (testModuleOptions.isRunOnClasspath()) { + if (testModuleOptions.getRunOnClasspath()) { LOGGER.lifecycle("Running tests on classpath"); return; } var args = new ArrayList<>(testJava.getJvmArgs()); - String testClassesDirs = testSourceSet.getOutput().getClassesDirs().getFiles() - .stream().map(File::getPath).collect(Collectors.joining(pathSeparator)); + String testClassesDirs = testSourceSet.getOutput().getClassesDirs().getFiles().stream() + .map(File::getPath) + .collect(Collectors.joining(pathSeparator)); args.addAll(List.of( "--module-path", testJava.getClasspath() @@ -74,30 +67,14 @@ public void execute(Task task) { args.addAll(patchModuleExtension.configure(testJava.getClasspath())); - TestEngine.select(project).ifPresent(new Consumer<TestEngine>() { - - /* (non-Javadoc) - * @see java.util.function.Consumer#accept(java.lang.Object) - */ - @Override - public void accept(TestEngine testEngine) { - args.addAll(List.of("--add-reads", moduleName + "=" + testEngine.moduleName)); - - Set<File> testDirs = testSourceSet.getOutput().getClassesDirs().getFiles(); - getPackages(testDirs).forEach(new Consumer<String>() { - - /* (non-Javadoc) - * @see java.util.function.Consumer#accept(java.lang.Object) - */ - @Override - public void accept(String p) { - args.add("--add-opens"); - args.add(String.format("%s/%s=%s", moduleName, p, testEngine.addOpens)); - } - - }); - } + TestEngine.select(project).ifPresent(testEngine -> { + args.addAll(List.of("--add-reads", moduleName + "=" + testEngine.moduleName)); + Set<File> testDirs = testSourceSet.getOutput().getClassesDirs().getFiles(); + getPackages(testDirs).forEach(p -> { + args.add("--add-opens"); + args.add(String.format("%s/%s=%s", moduleName, p, testEngine.addOpens)); + }); }); ModuleInfoTestHelper.mutateArgs(project, moduleName, args::add); diff --git a/src/test/java/org/javamodularity/moduleplugin/ModulePluginSmokeTest.java b/src/test/java/org/javamodularity/moduleplugin/ModulePluginSmokeTest.java index 8b9f2e4..4d1f66e 100644 --- a/src/test/java/org/javamodularity/moduleplugin/ModulePluginSmokeTest.java +++ b/src/test/java/org/javamodularity/moduleplugin/ModulePluginSmokeTest.java @@ -2,16 +2,17 @@ import com.google.common.base.Charsets; import com.google.common.io.Resources; +import org.gradle.testkit.runner.BuildResult; import org.gradle.testkit.runner.GradleRunner; -import org.gradle.testkit.runner.TaskOutcome; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; -import java.io.*; +import java.io.File; +import java.io.IOException; import java.nio.file.Path; import java.util.List; -import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -20,47 +21,86 @@ @SuppressWarnings("ConstantConditions") class ModulePluginSmokeTest { + private static final String GRADLE_VERSION = "5.0"; + private List<File> pluginClasspath; @BeforeEach void before() throws IOException { pluginClasspath = Resources.readLines(Resources.getResource("plugin-classpath.txt"), Charsets.UTF_8) .stream() - .map(fname -> new File(fname)) + .map(File::new) .collect(Collectors.toList()); } @ParameterizedTest - @ValueSource(strings = { "test-project", "test-project-kotlin" }) + @ValueSource(strings = {"test-project", "test-project-kotlin"}) void smokeTest(String projectName) { var result = GradleRunner.create() .withProjectDir(new File(projectName + "/")) .withPluginClasspath(pluginClasspath) - .withGradleVersion("4.10.2") + .withGradleVersion(GRADLE_VERSION) .withArguments("-c", "smoke_test_settings.gradle", "clean", "build", "run", "--stacktrace") .forwardOutput() .build(); - assertEquals(TaskOutcome.SUCCESS, result.task(":greeter.api:build").getOutcome(), "Failed Build!"); - assertEquals(TaskOutcome.SUCCESS, result.task(":greeter.provider:build").getOutcome(), "Failed Build!"); - assertEquals(TaskOutcome.SUCCESS, result.task(":greeter.provider.test:build").getOutcome(), "Failed Build!"); - assertEquals(TaskOutcome.SUCCESS, result.task(":greeter.runner:build").getOutcome(), "Failed Build!"); - assertEquals(TaskOutcome.SUCCESS, result.task(":greeter.runner:run").getOutcome(), "Failed Build!"); + assertTasksSuccessful(result, "greeter.api", "build"); + assertTasksSuccessful(result, "greeter.provider", "build"); + assertTasksSuccessful(result, "greeter.provider.test", "build"); + assertTasksSuccessful(result, "greeter.runner", "build", "run"); + } + + @Test + void smokeTestMixed() throws IOException { + var result = GradleRunner.create() + .withProjectDir(new File("test-project-mixed")) + .withPluginClasspath(pluginClasspath) + .withGradleVersion(GRADLE_VERSION) + .withArguments("-c", "smoke_test_settings.gradle", "clean", "build", "--stacktrace") + .forwardOutput() + .build(); + + verifyMixedTestResult(result, "greeter.api-jdk8", 8, 9); + + verifyMixedTestResult(result, "greeter.provider-jdk8", 8, 9); + verifyMixedTestResult(result, "greeter.provider-jdk8.test-jdk8", 8, 9); + verifyMixedTestResult(result, "greeter.provider-jdk8.test-jdk11", 11, 11); + + verifyMixedTestResult(result, "greeter.provider-jdk11", 11, 11); + verifyMixedTestResult(result, "greeter.provider-jdk11.test-jdk11", 11, 11); + } + + private static void verifyMixedTestResult( + BuildResult result, String subprojectName, + int mainJavaRelease, int moduleInfoJavaRelease) throws IOException { + assertTasksSuccessful(result, subprojectName, "build"); + assertExpectedClassFileFormats(subprojectName, mainJavaRelease, moduleInfoJavaRelease); + } + + private static void assertExpectedClassFileFormats( + String subprojectName, int mainJavaRelease, int moduleInfoJavaRelease) throws IOException { + Path classesDir = Path.of("test-project-mixed").resolve(subprojectName).resolve("build/classes/java/main"); + + Path moduleInfoClassPath = classesDir.resolve("module-info.class"); + SmokeTestHelper.assertClassFileJavaVersion(moduleInfoJavaRelease, moduleInfoClassPath); + + Path nonModuleInfoClassPath = SmokeTestHelper.anyNonModuleInfoClassFilePath(classesDir); + SmokeTestHelper.assertClassFileJavaVersion(mainJavaRelease, nonModuleInfoClassPath); } @ParameterizedTest - @ValueSource(strings = "test-project") + @ValueSource(strings = "test-project") void smokeTestDist(String projectName) { var result = GradleRunner.create() .withProjectDir(new File(projectName + "/")) .withPluginClasspath(pluginClasspath) - .withGradleVersion("4.10.2") + .withGradleVersion(GRADLE_VERSION) .withArguments("-c", "smoke_test_settings.gradle", "clean", "build", ":greeter.runner:installDist", "--stacktrace") .forwardOutput() .build(); - assertEquals(TaskOutcome.SUCCESS, result.task(":greeter.runner:installDist").getOutcome(), "Failed Build!"); - Path installDir = Path.of(projectName + "/greeter.runner/build/install/greeter.runner"); + assertTasksSuccessful(result, "greeter.runner", "installDist"); + Path installDir = Path.of(projectName + "/greeter.runner/build/install/greeter.runner"); assertTrue(installDir.toFile().exists(), "Install dir was not created"); Path libDir = installDir.resolve("lib"); @@ -73,26 +113,25 @@ void smokeTestDist(String projectName) { assertTrue(patchedLib.toFile().exists(), "Patched lib should be in patchlibs dir"); assertEquals(0, libDir.toFile().listFiles(f -> f.getName().equals("jsr305-3.0.2.jar")).length, "Patched libs should not be in lib dir"); - assertEquals(4, libDir.toFile().listFiles().length, "Unexepcted number of jars in lib dir"); + assertEquals(4, libDir.toFile().listFiles().length, "Unexpected number of jars in lib dir"); Path binDir = installDir.resolve("bin"); assertTrue(getAppOutput(binDir.toString(), "greeter.runner").contains("welcome")); } @ParameterizedTest - @ValueSource(strings = { "test-project", "test-project-kotlin" }) + @ValueSource(strings = {"test-project", "test-project-kotlin"}) void smokeTestRunDemo(String projectName) { var result = GradleRunner.create() .withProjectDir(new File(projectName + "/")) .withPluginClasspath(pluginClasspath) - .withGradleVersion("4.10.2") + .withGradleVersion(GRADLE_VERSION) .withArguments("-c", "smoke_test_settings.gradle", "clean", "build", ":greeter.javaexec:runDemo1", ":greeter.javaexec:runDemo2", "--info", "--stacktrace") .forwardOutput() .build(); - assertEquals(TaskOutcome.SUCCESS, result.task(":greeter.javaexec:runDemo1").getOutcome(), "Failed Build!"); - assertEquals(TaskOutcome.SUCCESS, result.task(":greeter.javaexec:runDemo2").getOutcome(), "Failed Build!"); + assertTasksSuccessful(result, "greeter.javaexec", "runDemo1", "runDemo2"); } @ParameterizedTest @@ -101,12 +140,12 @@ void smokeTestRunStartScripts(String projectName) { var result = GradleRunner.create() .withProjectDir(new File(projectName + "/")) .withPluginClasspath(pluginClasspath) - .withGradleVersion("4.10.2") + .withGradleVersion(GRADLE_VERSION) .withArguments("-c", "smoke_test_settings.gradle", "clean", ":greeter.startscripts:installDist", "--info", "--stacktrace") .forwardOutput() .build(); - assertEquals(TaskOutcome.SUCCESS, result.task(":greeter.startscripts:installDist").getOutcome(), "Failed Build!"); + assertTasksSuccessful(result, "greeter.startscripts", "installDist"); String binDir = projectName + "/greeter.startscripts/build/install/demo/bin"; assertEquals("MainDemo: welcome", getAppOutput(binDir, "demo")); @@ -115,35 +154,13 @@ void smokeTestRunStartScripts(String projectName) { } private static String getAppOutput(String binDirPath, String appName) { - boolean windows = System.getProperty("os.name").toLowerCase().contains("windows"); - String scriptName = windows ? (appName + ".bat") : appName ; - - File binDir = new File(binDirPath).getAbsoluteFile(); - Process process; - try { - process = new ProcessBuilder() - .directory(binDir) - .command(new File(binDir, scriptName).getPath()) - .start(); - process.waitFor(30, TimeUnit.SECONDS); - } catch (Exception e) { - e.printStackTrace(); - return e.toString(); - } - if(process.exitValue() != 0) { - String errText = getText(process.getErrorStream()); - System.err.println("Process terminated with exit code " + process.exitValue() + ": " + errText); - return errText; - } - return getText(process.getInputStream()); + return SmokeTestHelper.getAppOutput(binDirPath, appName); } - public static String getText(InputStream inputStream){ - try (BufferedReader buffer = new BufferedReader(new InputStreamReader(inputStream))) { - return buffer.lines().collect(Collectors.joining("\n")); - } catch(IOException e) { - e.printStackTrace(); - return e.getMessage(); + private static void assertTasksSuccessful(BuildResult result, String subprojectName, String... taskNames) { + for (String taskName : taskNames) { + SmokeTestHelper.assertTaskSuccessful(result, subprojectName, taskName); } } + } diff --git a/src/test/java/org/javamodularity/moduleplugin/SmokeTestHelper.java b/src/test/java/org/javamodularity/moduleplugin/SmokeTestHelper.java new file mode 100644 index 0000000..406a87f --- /dev/null +++ b/src/test/java/org/javamodularity/moduleplugin/SmokeTestHelper.java @@ -0,0 +1,93 @@ +package org.javamodularity.moduleplugin; + +import org.gradle.testkit.runner.BuildResult; +import org.gradle.testkit.runner.BuildTask; +import org.gradle.testkit.runner.TaskOutcome; + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Objects; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +final class SmokeTestHelper { + + static void assertTaskSuccessful(BuildResult result, String subprojectName, String taskName) { + String fullTaskName = String.format(":%s:%s", subprojectName, taskName); + BuildTask task = Objects.requireNonNull(result.task(fullTaskName), fullTaskName); + assertEquals(TaskOutcome.SUCCESS, task.getOutcome(), () -> fullTaskName + " failed!"); + } + + //region CLASS FILE FORMAT + static void assertClassFileJavaVersion(int expectedJavaVersion, Path classFilePath) throws IOException { + int actualJavaVersion = classFileJavaVersion(classFilePath); + assertEquals(expectedJavaVersion, actualJavaVersion, classFilePath::toString); + } + + static Path anyNonModuleInfoClassFilePath(Path classesDir) throws IOException { + return Files.walk(classesDir) + .filter(SmokeTestHelper::isClassFile) + .filter(path -> !path.endsWith("module-info.class")) + .findAny() + .orElseThrow(() -> new IllegalStateException("Main class file not found in " + classesDir)); + } + + private static int classFileJavaVersion(Path classFilePath) throws IOException { + // https://en.wikipedia.org/wiki/Java_class_file#General_layout + return classFileFormat(classFilePath) - 44; + } + + private static int classFileFormat(Path classFilePath) throws IOException { + if (!isClassFile(classFilePath)) { + throw new IllegalArgumentException(classFilePath.toString()); + } + + try (InputStream inputStream = Files.newInputStream(classFilePath)) { + // https://en.wikipedia.org/wiki/Java_class_file#General_layout + return inputStream.readNBytes(8)[7]; // 8th byte: major version number + } + } + + private static boolean isClassFile(Path classFilePath) { + return classFilePath.toString().endsWith(".class"); + } + //endregion + + //region APP OUTPUT + static String getAppOutput(String binDirPath, String appName) { + boolean windows = System.getProperty("os.name").toLowerCase().contains("windows"); + String scriptName = windows ? (appName + ".bat") : appName; + + File binDir = new File(binDirPath).getAbsoluteFile(); + Process process; + try { + process = new ProcessBuilder() + .directory(binDir) + .command(new File(binDir, scriptName).getPath()) + .start(); + process.waitFor(30, TimeUnit.SECONDS); + } catch (Exception e) { + e.printStackTrace(); + return e.toString(); + } + if (process.exitValue() != 0) { + String errText = getText(process.getErrorStream()); + System.err.println("Process terminated with exit code " + process.exitValue() + ": " + errText); + return errText; + } + return getText(process.getInputStream()); + } + + private static String getText(InputStream inputStream) { + try (BufferedReader buffer = new BufferedReader(new InputStreamReader(inputStream))) { + return buffer.lines().collect(Collectors.joining("\n")); + } catch (IOException e) { + e.printStackTrace(); + return e.getMessage(); + } + } + //endregion +} diff --git a/src/test/java/org/javamodularity/moduleplugin/tasks/CompileJavaTaskMutatorTest.java b/src/test/java/org/javamodularity/moduleplugin/tasks/CompileJavaTaskMutatorTest.java index b6064fd..f1a7e0a 100644 --- a/src/test/java/org/javamodularity/moduleplugin/tasks/CompileJavaTaskMutatorTest.java +++ b/src/test/java/org/javamodularity/moduleplugin/tasks/CompileJavaTaskMutatorTest.java @@ -4,27 +4,30 @@ import org.gradle.api.plugins.JavaPlugin; import org.gradle.api.tasks.compile.JavaCompile; import org.gradle.testfixtures.ProjectBuilder; +import org.javamodularity.moduleplugin.extensions.CompileModuleOptions; import org.junit.jupiter.api.Test; import java.io.File; import java.util.Arrays; import java.util.List; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; class CompileJavaTaskMutatorTest { @Test - void mutateJavaCompileTask() { + void modularizeJavaCompileTask() { // given Project project = ProjectBuilder.builder().withProjectDir(new File("test-project/")).build(); project.getPlugins().apply("java"); - final JavaCompile compileJava = (JavaCompile) project.getTasks().findByName(JavaPlugin.COMPILE_JAVA_TASK_NAME); - compileJava.getExtensions().create("moduleOptions", ModuleOptions.class, project); + JavaCompile compileJava = (JavaCompile) project.getTasks().getByName(JavaPlugin.COMPILE_JAVA_TASK_NAME); + CompileModuleOptions moduleOptions = compileJava.getExtensions() + .create("moduleOptions", CompileModuleOptions.class, project); project.getExtensions().create("patchModules", PatchModuleExtension.class); + CompileJavaTaskMutator mutator = new CompileJavaTaskMutator(project, compileJava.getClasspath(), moduleOptions); // when - CompileJavaTaskMutator.mutateJavaCompileTask(project, compileJava); + mutator.modularizeJavaCompileTask(compileJava); // then List<String> twoLastArguments = twoLastCompilerArgs(compileJava); @@ -37,7 +40,7 @@ void mutateJavaCompileTask() { private List<String> twoLastCompilerArgs(JavaCompile compileJava) { List<String> allCompilerArgs = compileJava.getOptions().getAllCompilerArgs(); int size = allCompilerArgs.size(); - return allCompilerArgs.subList(size-2, size); + return allCompilerArgs.subList(size - 2, size); } -} \ No newline at end of file +} diff --git a/test-project-kotlin/README.md b/test-project-kotlin/README.md index 77f95dc..401316c 100644 --- a/test-project-kotlin/README.md +++ b/test-project-kotlin/README.md @@ -33,8 +33,8 @@ Internal test project === This mode is enabled in `ModulePluginSmokeTest` by passing an extra parameter (`-c smoke_test_settings.gradle`) -that points to `smoke_test_build.gradle` instead of `build.gradle`. It doesn't resolve a plugin jar. -Instead, it relies on the smoke test to makes the plugin under development available +that points to `smoke_test_build.gradle.kts` instead of `build.gradle.kts`. It doesn't resolve a plugin jar. +Instead, it relies on the smoke test to make the plugin under development available to the test project by sharing a classpath (using Gradle TestKit). -__CAUTION:__ This approach won't work outside of the smoke test, it will break the build because the plugin jar won't be resolved. \ No newline at end of file +__CAUTION:__ This approach won't work outside of the smoke test, it will break the build because the plugin jar won't be resolved. diff --git a/test-project-kotlin/build.gradle.kts b/test-project-kotlin/build.gradle.kts index 2d628c5..3c915ae 100644 --- a/test-project-kotlin/build.gradle.kts +++ b/test-project-kotlin/build.gradle.kts @@ -10,7 +10,7 @@ buildscript { } plugins { - val kotlinVersion = "1.3.10" + val kotlinVersion = "1.3.20" kotlin("jvm") version kotlinVersion } @@ -40,6 +40,7 @@ subprojects { testLogging { events("PASSED", "FAILED", "SKIPPED") + stackTraceFilters = listOf() } } diff --git a/test-project-kotlin/greeter.api/build.gradle.kts b/test-project-kotlin/greeter.api/build.gradle.kts index 2b02113..d0dcd7b 100644 --- a/test-project-kotlin/greeter.api/build.gradle.kts +++ b/test-project-kotlin/greeter.api/build.gradle.kts @@ -1,5 +1,28 @@ +import org.javamodularity.moduleplugin.extensions.CompileModuleOptions +import org.javamodularity.moduleplugin.tasks.TestModuleOptions import org.jetbrains.kotlin.gradle.tasks.KotlinCompile +//region NO-OP (DSL testing) +tasks { + compileJava { + extensions.configure(CompileModuleOptions::class) { + addModules = listOf() + compileModuleInfoSeparately = false + } + } + + test { + extensions.configure(TestModuleOptions::class) { + addModules = listOf() + runOnClasspath = false + } + } +} + +modularity { +} +//endregion + val compileKotlin: KotlinCompile by tasks compileKotlin.kotlinOptions { jvmTarget = "1.8" @@ -7,4 +30,4 @@ compileKotlin.kotlinOptions { val compileTestKotlin: KotlinCompile by tasks compileTestKotlin.kotlinOptions { jvmTarget = "1.8" -} \ No newline at end of file +} diff --git a/test-project-kotlin/local_maven_build.gradle.kts b/test-project-kotlin/local_maven_build.gradle.kts index 9df463f..60893c7 100644 --- a/test-project-kotlin/local_maven_build.gradle.kts +++ b/test-project-kotlin/local_maven_build.gradle.kts @@ -9,7 +9,7 @@ buildscript { } plugins { - val kotlinVersion = "1.3.10" + val kotlinVersion = "1.3.20" kotlin("jvm") version kotlinVersion } diff --git a/test-project-kotlin/smoke_test_build.gradle.kts b/test-project-kotlin/smoke_test_build.gradle.kts index aa79bb0..2a3d888 100644 --- a/test-project-kotlin/smoke_test_build.gradle.kts +++ b/test-project-kotlin/smoke_test_build.gradle.kts @@ -6,7 +6,7 @@ buildscript { } } plugins { - val kotlinVersion = "1.3.10" + val kotlinVersion = "1.3.20" kotlin("jvm") version kotlinVersion id("org.javamodularity.moduleplugin") } diff --git a/test-project-mixed/README.md b/test-project-mixed/README.md new file mode 100644 index 0000000..bfc107a --- /dev/null +++ b/test-project-mixed/README.md @@ -0,0 +1,48 @@ +Introduction +=== + +This mixed test project can be used as a standalone test project to verify the published plugin. +It is also used as an internal test project for testing unpublished plugin changes. + +This project is "mixed" in two ways: + +1. It produces classes targeting mixed JDKs (JDK 8 and JDK 11). The project makes use of the +[`ModularityExtension`](../src/main/java/org/javamodularity/moduleplugin/extensions/ModularityExtension.java), +which in turn makes use of the Java compiler +[`--release`](https://docs.oracle.com/en/java/javase/11/tools/javac.html) option. +2. It contains mixed build files: `build.gradle` (Groovy DSL) and `build.gradle.kts` (Kotlin DSL). + +Standalone test product +=== +To run this product as a standalone test product use this command (launched from `test-project-mixed` directory): +``` +../gradlew clean build +``` + +It will use the most recent plugin version from Gradle maven repository to compile the test project with +modules and run the unit tests. + +Testing locally published plugin +=== + +You can publish the plugin locally by running this command from the root directory: + +`./gradlew publishToMavenLocal` + +You can test the locally published plugin by running the following command from `test-project-mixed` directory. + +`../gradlew -c local_maven_settings.gradle clean build` + +It will use the latest locally published version of the plugin to compile the test project with +modules and run the unit tests. + + +Internal test project +=== + +This mode is enabled in `ModulePluginSmokeTest` by passing an extra parameter (`-c smoke_test_settings.gradle`). +`smoke_test_settings.gradle` script configures plugin management so that the plugin cannot be resolved from +a Gradle plugin repository. Instead, it relies on the smoke test to make the plugin under development available +to the test project by sharing a classpath (using Gradle TestKit). + +__CAUTION:__ This approach won't work outside of the smoke test, it will break the build because the plugin jar won't be resolved. diff --git a/test-project-mixed/build.gradle b/test-project-mixed/build.gradle new file mode 100644 index 0000000..f628f27 --- /dev/null +++ b/test-project-mixed/build.gradle @@ -0,0 +1,28 @@ +plugins { + id 'org.javamodularity.moduleplugin' version '1.4.1' apply false +} + +subprojects { + apply plugin: 'java' + apply plugin: 'org.javamodularity.moduleplugin' + + repositories { + mavenCentral() + } + + test { + useJUnitPlatform() + + testLogging { + events 'PASSED', 'FAILED', 'SKIPPED' + } + } + + dependencies { + testImplementation "org.junit.jupiter:junit-jupiter-api:$jUnitVersion" + testImplementation "org.junit.jupiter:junit-jupiter-params:$jUnitVersion" + testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$jUnitVersion" + } + + build.dependsOn javadoc +} diff --git a/test-project-mixed/gradle.properties b/test-project-mixed/gradle.properties new file mode 100644 index 0000000..95bdec6 --- /dev/null +++ b/test-project-mixed/gradle.properties @@ -0,0 +1 @@ +jUnitVersion = 5.3.1 diff --git a/test-project-mixed/gradle/shared/greeter.provider.gradle b/test-project-mixed/gradle/shared/greeter.provider.gradle new file mode 100644 index 0000000..5b1452e --- /dev/null +++ b/test-project-mixed/gradle/shared/greeter.provider.gradle @@ -0,0 +1,17 @@ +dependencies { + implementation project(':greeter.api-jdk8') + testImplementation('org.hamcrest:hamcrest:2.1+') + + compile "javax.annotation:javax.annotation-api:1.3.2" + compile "com.google.code.findbugs:jsr305:3.0.2" +} + +patchModules.config = [ + "java.annotation=jsr305-3.0.2.jar" +] + +javadoc { + moduleOptions { + addModules = ['java.sql'] + } +} diff --git a/test-project-mixed/gradle/shared/greeter.provider.test.gradle b/test-project-mixed/gradle/shared/greeter.provider.test.gradle new file mode 100644 index 0000000..1453328 --- /dev/null +++ b/test-project-mixed/gradle/shared/greeter.provider.test.gradle @@ -0,0 +1,7 @@ +dependencies { + implementation project(':greeter.api-jdk8') +} + +patchModules.config = [ + "java.annotation=jsr305-3.0.2.jar" +] diff --git a/test-project-mixed/greeter.api-jdk8/build.gradle.kts b/test-project-mixed/greeter.api-jdk8/build.gradle.kts new file mode 100644 index 0000000..1d4b17a --- /dev/null +++ b/test-project-mixed/greeter.api-jdk8/build.gradle.kts @@ -0,0 +1 @@ +modularity.mixedJavaRelease(8) diff --git a/test-project-mixed/greeter.api-jdk8/src/main/java/examples/greeter/api/Greeter.java b/test-project-mixed/greeter.api-jdk8/src/main/java/examples/greeter/api/Greeter.java new file mode 100644 index 0000000..b47b41a --- /dev/null +++ b/test-project-mixed/greeter.api-jdk8/src/main/java/examples/greeter/api/Greeter.java @@ -0,0 +1,5 @@ +package examples.greeter.api; + +public interface Greeter { + String hello(); +} diff --git a/test-project-mixed/greeter.api-jdk8/src/main/java/module-info.java b/test-project-mixed/greeter.api-jdk8/src/main/java/module-info.java new file mode 100644 index 0000000..60982b1 --- /dev/null +++ b/test-project-mixed/greeter.api-jdk8/src/main/java/module-info.java @@ -0,0 +1,3 @@ +module greeter.api { + exports examples.greeter.api; +} diff --git a/test-project-mixed/greeter.provider-jdk11.test-jdk11/build.gradle.kts b/test-project-mixed/greeter.provider-jdk11.test-jdk11/build.gradle.kts new file mode 100644 index 0000000..ddb0fe5 --- /dev/null +++ b/test-project-mixed/greeter.provider-jdk11.test-jdk11/build.gradle.kts @@ -0,0 +1,7 @@ +modularity.standardJavaRelease(11) + +dependencies { + runtimeOnly(project(":greeter.provider-jdk11")) +} + +apply(from = "$rootDir/gradle/shared/greeter.provider.test.gradle") diff --git a/test-project-mixed/greeter.provider-jdk11.test-jdk11/src/main/java/module-info.java b/test-project-mixed/greeter.provider-jdk11.test-jdk11/src/main/java/module-info.java new file mode 100644 index 0000000..97a2e27 --- /dev/null +++ b/test-project-mixed/greeter.provider-jdk11.test-jdk11/src/main/java/module-info.java @@ -0,0 +1,7 @@ +import examples.greeter.api.Greeter; + +module greeter.provider.test { + requires greeter.api; + + uses Greeter; +} \ No newline at end of file diff --git a/test-project-mixed/greeter.provider-jdk11.test-jdk11/src/main/java/tests/GreeterLocator.java b/test-project-mixed/greeter.provider-jdk11.test-jdk11/src/main/java/tests/GreeterLocator.java new file mode 100644 index 0000000..c75c4b2 --- /dev/null +++ b/test-project-mixed/greeter.provider-jdk11.test-jdk11/src/main/java/tests/GreeterLocator.java @@ -0,0 +1,11 @@ +package tests; + +import examples.greeter.api.Greeter; + +import java.util.ServiceLoader; + +public class GreeterLocator { + public Greeter findGreeter() { + return ServiceLoader.load(Greeter.class).findFirst().orElseThrow(() -> new RuntimeException("No Greeter found")); + } +} diff --git a/test-project-mixed/greeter.provider-jdk11.test-jdk11/src/test/java/tests/GreeterTest.java b/test-project-mixed/greeter.provider-jdk11.test-jdk11/src/test/java/tests/GreeterTest.java new file mode 100644 index 0000000..fb859f2 --- /dev/null +++ b/test-project-mixed/greeter.provider-jdk11.test-jdk11/src/test/java/tests/GreeterTest.java @@ -0,0 +1,14 @@ +package tests; + +import examples.greeter.api.Greeter; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertFalse; + +class GreeterTest { + @Test + void testLocate() { + Greeter greeter = new GreeterLocator().findGreeter(); + assertFalse(greeter.hello().isBlank()); + } +} diff --git a/test-project-mixed/greeter.provider-jdk11/build.gradle.kts b/test-project-mixed/greeter.provider-jdk11/build.gradle.kts new file mode 100644 index 0000000..627309b --- /dev/null +++ b/test-project-mixed/greeter.provider-jdk11/build.gradle.kts @@ -0,0 +1,3 @@ +modularity.standardJavaRelease(11) + +apply(from = "$rootDir/gradle/shared/greeter.provider.gradle") diff --git a/test-project-mixed/greeter.provider-jdk11/src/main/java/examples/greeter/Friendly.java b/test-project-mixed/greeter.provider-jdk11/src/main/java/examples/greeter/Friendly.java new file mode 100644 index 0000000..5753012 --- /dev/null +++ b/test-project-mixed/greeter.provider-jdk11/src/main/java/examples/greeter/Friendly.java @@ -0,0 +1,18 @@ +package examples.greeter; + +import examples.greeter.api.Greeter; +import java.io.*; +import java.util.*; +import javax.annotation.Nonnull; + +public class Friendly implements Greeter { + @Override @Nonnull + public String hello() { + var stream = this.getClass().getResourceAsStream("/greeting.txt"); + try (BufferedReader reader = new BufferedReader(new InputStreamReader(stream, "utf-8"))) { + return reader.readLine(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/test-project-mixed/greeter.provider-jdk11/src/main/java/module-info.java b/test-project-mixed/greeter.provider-jdk11/src/main/java/module-info.java new file mode 100644 index 0000000..8f9ab24 --- /dev/null +++ b/test-project-mixed/greeter.provider-jdk11/src/main/java/module-info.java @@ -0,0 +1,8 @@ +import examples.greeter.api.Greeter; + +module greeter.provider { + requires greeter.api; + requires java.annotation; + + provides Greeter with examples.greeter.Friendly; +} \ No newline at end of file diff --git a/test-project-mixed/greeter.provider-jdk11/src/main/resources/greeting.txt b/test-project-mixed/greeter.provider-jdk11/src/main/resources/greeting.txt new file mode 100644 index 0000000..b6cbc2d --- /dev/null +++ b/test-project-mixed/greeter.provider-jdk11/src/main/resources/greeting.txt @@ -0,0 +1 @@ +welcome \ No newline at end of file diff --git a/test-project-mixed/greeter.provider-jdk11/src/test/java/examples/greeter/FriendlyTest.java b/test-project-mixed/greeter.provider-jdk11/src/test/java/examples/greeter/FriendlyTest.java new file mode 100644 index 0000000..c665b67 --- /dev/null +++ b/test-project-mixed/greeter.provider-jdk11/src/test/java/examples/greeter/FriendlyTest.java @@ -0,0 +1,14 @@ +package examples.greeter; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class FriendlyTest { + @Test + void testGreeting() { + String greeting = new Friendly().hello(); + assertTrue(greeting.contains("welcome")); + } + +} \ No newline at end of file diff --git a/test-project-mixed/greeter.provider-jdk11/src/test/java/examples/greeter/ScriptingTest.java b/test-project-mixed/greeter.provider-jdk11/src/test/java/examples/greeter/ScriptingTest.java new file mode 100644 index 0000000..2399998 --- /dev/null +++ b/test-project-mixed/greeter.provider-jdk11/src/test/java/examples/greeter/ScriptingTest.java @@ -0,0 +1,15 @@ +package examples.greeter; + +import javax.script.*; +import org.junit.jupiter.api.*; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; + +class ScriptingTest { + + @Test + void testScripting() { + ScriptEngineManager manager = new ScriptEngineManager(); + assertThat(manager.getEngineFactories(), not(nullValue())); + } +} diff --git a/test-project-mixed/greeter.provider-jdk11/src/test/java/module-info.test b/test-project-mixed/greeter.provider-jdk11/src/test/java/module-info.test new file mode 100644 index 0000000..4aa1e83 --- /dev/null +++ b/test-project-mixed/greeter.provider-jdk11/src/test/java/module-info.test @@ -0,0 +1,7 @@ +// make module visible +--add-modules + java.scripting,org.hamcrest + +// "requires java.scripting" +--add-reads + greeter.provider=java.scripting,org.hamcrest diff --git a/test-project-mixed/greeter.provider-jdk8.test-jdk11/build.gradle b/test-project-mixed/greeter.provider-jdk8.test-jdk11/build.gradle new file mode 100644 index 0000000..c417edf --- /dev/null +++ b/test-project-mixed/greeter.provider-jdk8.test-jdk11/build.gradle @@ -0,0 +1,7 @@ +modularity.standardJavaRelease 11 + +dependencies { + runtimeOnly project(':greeter.provider-jdk8') +} + +apply from: "$rootDir/gradle/shared/greeter.provider.test.gradle" diff --git a/test-project-mixed/greeter.provider-jdk8.test-jdk11/src/main/java/module-info.java b/test-project-mixed/greeter.provider-jdk8.test-jdk11/src/main/java/module-info.java new file mode 100644 index 0000000..97a2e27 --- /dev/null +++ b/test-project-mixed/greeter.provider-jdk8.test-jdk11/src/main/java/module-info.java @@ -0,0 +1,7 @@ +import examples.greeter.api.Greeter; + +module greeter.provider.test { + requires greeter.api; + + uses Greeter; +} \ No newline at end of file diff --git a/test-project-mixed/greeter.provider-jdk8.test-jdk11/src/main/java/tests/GreeterLocator.java b/test-project-mixed/greeter.provider-jdk8.test-jdk11/src/main/java/tests/GreeterLocator.java new file mode 100644 index 0000000..c75c4b2 --- /dev/null +++ b/test-project-mixed/greeter.provider-jdk8.test-jdk11/src/main/java/tests/GreeterLocator.java @@ -0,0 +1,11 @@ +package tests; + +import examples.greeter.api.Greeter; + +import java.util.ServiceLoader; + +public class GreeterLocator { + public Greeter findGreeter() { + return ServiceLoader.load(Greeter.class).findFirst().orElseThrow(() -> new RuntimeException("No Greeter found")); + } +} diff --git a/test-project-mixed/greeter.provider-jdk8.test-jdk11/src/test/java/tests/GreeterTest.java b/test-project-mixed/greeter.provider-jdk8.test-jdk11/src/test/java/tests/GreeterTest.java new file mode 100644 index 0000000..fb859f2 --- /dev/null +++ b/test-project-mixed/greeter.provider-jdk8.test-jdk11/src/test/java/tests/GreeterTest.java @@ -0,0 +1,14 @@ +package tests; + +import examples.greeter.api.Greeter; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertFalse; + +class GreeterTest { + @Test + void testLocate() { + Greeter greeter = new GreeterLocator().findGreeter(); + assertFalse(greeter.hello().isBlank()); + } +} diff --git a/test-project-mixed/greeter.provider-jdk8.test-jdk8/build.gradle b/test-project-mixed/greeter.provider-jdk8.test-jdk8/build.gradle new file mode 100644 index 0000000..dd24d3d --- /dev/null +++ b/test-project-mixed/greeter.provider-jdk8.test-jdk8/build.gradle @@ -0,0 +1,7 @@ +modularity.mixedJavaRelease 8 + +dependencies { + runtimeOnly project(':greeter.provider-jdk8') +} + +apply from: "$rootDir/gradle/shared/greeter.provider.test.gradle" diff --git a/test-project-mixed/greeter.provider-jdk8.test-jdk8/src/main/java/module-info.java b/test-project-mixed/greeter.provider-jdk8.test-jdk8/src/main/java/module-info.java new file mode 100644 index 0000000..97a2e27 --- /dev/null +++ b/test-project-mixed/greeter.provider-jdk8.test-jdk8/src/main/java/module-info.java @@ -0,0 +1,7 @@ +import examples.greeter.api.Greeter; + +module greeter.provider.test { + requires greeter.api; + + uses Greeter; +} \ No newline at end of file diff --git a/test-project-mixed/greeter.provider-jdk8.test-jdk8/src/main/java/tests/GreeterLocator.java b/test-project-mixed/greeter.provider-jdk8.test-jdk8/src/main/java/tests/GreeterLocator.java new file mode 100644 index 0000000..15387a8 --- /dev/null +++ b/test-project-mixed/greeter.provider-jdk8.test-jdk8/src/main/java/tests/GreeterLocator.java @@ -0,0 +1,11 @@ +package tests; + +import examples.greeter.api.Greeter; + +import java.util.ServiceLoader; + +public class GreeterLocator { + public Greeter findGreeter() { + return ServiceLoader.load(Greeter.class).iterator().next(); // no ServiceLoader.findFirst() in JDK 8 + } +} diff --git a/test-project-mixed/greeter.provider-jdk8.test-jdk8/src/test/java/tests/GreeterTest.java b/test-project-mixed/greeter.provider-jdk8.test-jdk8/src/test/java/tests/GreeterTest.java new file mode 100644 index 0000000..e3f485f --- /dev/null +++ b/test-project-mixed/greeter.provider-jdk8.test-jdk8/src/test/java/tests/GreeterTest.java @@ -0,0 +1,14 @@ +package tests; + +import examples.greeter.api.Greeter; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertFalse; + +class GreeterTest { + @Test + void testLocate() { + Greeter greeter = new GreeterLocator().findGreeter(); + assertFalse(greeter.hello().isEmpty()); // no String.isBlank() in JDK 8 + } +} diff --git a/test-project-mixed/greeter.provider-jdk8/build.gradle b/test-project-mixed/greeter.provider-jdk8/build.gradle new file mode 100644 index 0000000..c2b11c9 --- /dev/null +++ b/test-project-mixed/greeter.provider-jdk8/build.gradle @@ -0,0 +1,3 @@ +modularity.mixedJavaRelease 8 + +apply from: "$rootDir/gradle/shared/greeter.provider.gradle" diff --git a/test-project-mixed/greeter.provider-jdk8/src/main/java/examples/greeter/Friendly.java b/test-project-mixed/greeter.provider-jdk8/src/main/java/examples/greeter/Friendly.java new file mode 100644 index 0000000..87f7f16 --- /dev/null +++ b/test-project-mixed/greeter.provider-jdk8/src/main/java/examples/greeter/Friendly.java @@ -0,0 +1,18 @@ +package examples.greeter; + +import examples.greeter.api.Greeter; +import java.io.*; +import java.util.*; +import javax.annotation.Nonnull; + +public class Friendly implements Greeter { + @Override @Nonnull + public String hello() { + InputStream stream = this.getClass().getResourceAsStream("/greeting.txt"); + try (BufferedReader reader = new BufferedReader(new InputStreamReader(stream, "utf-8"))) { + return reader.readLine(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/test-project-mixed/greeter.provider-jdk8/src/main/java/module-info.java b/test-project-mixed/greeter.provider-jdk8/src/main/java/module-info.java new file mode 100644 index 0000000..8f9ab24 --- /dev/null +++ b/test-project-mixed/greeter.provider-jdk8/src/main/java/module-info.java @@ -0,0 +1,8 @@ +import examples.greeter.api.Greeter; + +module greeter.provider { + requires greeter.api; + requires java.annotation; + + provides Greeter with examples.greeter.Friendly; +} \ No newline at end of file diff --git a/test-project-mixed/greeter.provider-jdk8/src/main/resources/META-INF/services/examples.greeter.api.Greeter b/test-project-mixed/greeter.provider-jdk8/src/main/resources/META-INF/services/examples.greeter.api.Greeter new file mode 100644 index 0000000..78131dd --- /dev/null +++ b/test-project-mixed/greeter.provider-jdk8/src/main/resources/META-INF/services/examples.greeter.api.Greeter @@ -0,0 +1 @@ +examples.greeter.Friendly diff --git a/test-project-mixed/greeter.provider-jdk8/src/main/resources/greeting.txt b/test-project-mixed/greeter.provider-jdk8/src/main/resources/greeting.txt new file mode 100644 index 0000000..b6cbc2d --- /dev/null +++ b/test-project-mixed/greeter.provider-jdk8/src/main/resources/greeting.txt @@ -0,0 +1 @@ +welcome \ No newline at end of file diff --git a/test-project-mixed/greeter.provider-jdk8/src/test/java/examples/greeter/FriendlyTest.java b/test-project-mixed/greeter.provider-jdk8/src/test/java/examples/greeter/FriendlyTest.java new file mode 100644 index 0000000..c665b67 --- /dev/null +++ b/test-project-mixed/greeter.provider-jdk8/src/test/java/examples/greeter/FriendlyTest.java @@ -0,0 +1,14 @@ +package examples.greeter; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class FriendlyTest { + @Test + void testGreeting() { + String greeting = new Friendly().hello(); + assertTrue(greeting.contains("welcome")); + } + +} \ No newline at end of file diff --git a/test-project-mixed/greeter.provider-jdk8/src/test/java/examples/greeter/ScriptingTest.java b/test-project-mixed/greeter.provider-jdk8/src/test/java/examples/greeter/ScriptingTest.java new file mode 100644 index 0000000..2399998 --- /dev/null +++ b/test-project-mixed/greeter.provider-jdk8/src/test/java/examples/greeter/ScriptingTest.java @@ -0,0 +1,15 @@ +package examples.greeter; + +import javax.script.*; +import org.junit.jupiter.api.*; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; + +class ScriptingTest { + + @Test + void testScripting() { + ScriptEngineManager manager = new ScriptEngineManager(); + assertThat(manager.getEngineFactories(), not(nullValue())); + } +} diff --git a/test-project-mixed/greeter.provider-jdk8/src/test/java/module-info.test b/test-project-mixed/greeter.provider-jdk8/src/test/java/module-info.test new file mode 100644 index 0000000..4aa1e83 --- /dev/null +++ b/test-project-mixed/greeter.provider-jdk8/src/test/java/module-info.test @@ -0,0 +1,7 @@ +// make module visible +--add-modules + java.scripting,org.hamcrest + +// "requires java.scripting" +--add-reads + greeter.provider=java.scripting,org.hamcrest diff --git a/test-project-mixed/local_maven_settings.gradle b/test-project-mixed/local_maven_settings.gradle new file mode 100644 index 0000000..c326381 --- /dev/null +++ b/test-project-mixed/local_maven_settings.gradle @@ -0,0 +1,8 @@ +pluginManagement { + repositories { + mavenLocal() // for moduleplugin published locally + mavenCentral() // for dependencies of moduleplugin + } +} + +apply from: 'settings.gradle' diff --git a/test-project-mixed/settings.gradle b/test-project-mixed/settings.gradle new file mode 100644 index 0000000..2848ffa --- /dev/null +++ b/test-project-mixed/settings.gradle @@ -0,0 +1,9 @@ +rootProject.name = 'moduleplugintests-mixed' +include 'greeter.api-jdk8' + +include 'greeter.provider-jdk8' +include 'greeter.provider-jdk8.test-jdk8' +include 'greeter.provider-jdk8.test-jdk11' + +include 'greeter.provider-jdk11' +include 'greeter.provider-jdk11.test-jdk11' diff --git a/test-project-mixed/smoke_test_settings.gradle b/test-project-mixed/smoke_test_settings.gradle new file mode 100644 index 0000000..ee0e79e --- /dev/null +++ b/test-project-mixed/smoke_test_settings.gradle @@ -0,0 +1,8 @@ +pluginManagement { + repositories { + // no real repositories (plugin will be added to the classpath by the smoke test) + flatDir name: 'empty', dirs: ['.fake-dir'] + } +} + +apply from: 'settings.gradle' diff --git a/test-project/build.gradle b/test-project/build.gradle index bd41f07..186d39a 100644 --- a/test-project/build.gradle +++ b/test-project/build.gradle @@ -26,6 +26,7 @@ subprojects { testLogging { events 'PASSED', 'FAILED', 'SKIPPED' + stackTraceFilters = [] } } diff --git a/test-project/greeter.api/build.gradle b/test-project/greeter.api/build.gradle index 15cc768..ae58af7 100644 --- a/test-project/greeter.api/build.gradle +++ b/test-project/greeter.api/build.gradle @@ -1,3 +1,22 @@ plugins { id 'org.javamodularity.moduleplugin' -} \ No newline at end of file +} + +//region NO-OP (DSL testing) +compileJava { + moduleOptions { + addModules = [] + compileModuleInfoSeparately = false + } +} + +test { + moduleOptions { + addModules = [] + runOnClasspath = false + } +} + +modularity { +} +//endregion