diff --git a/README.md b/README.md index 256b513..62e330d 100644 --- a/README.md +++ b/README.md @@ -492,6 +492,16 @@ patchModules.config = [ ] ``` +Compilation +=== + +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. + Limitations === diff --git a/src/main/java/org/javamodularity/moduleplugin/ModuleSystemPlugin.java b/src/main/java/org/javamodularity/moduleplugin/ModuleSystemPlugin.java index 0509498..0fbbc08 100644 --- a/src/main/java/org/javamodularity/moduleplugin/ModuleSystemPlugin.java +++ b/src/main/java/org/javamodularity/moduleplugin/ModuleSystemPlugin.java @@ -20,6 +20,7 @@ private void configureModularity(Project project, String moduleName) { extensions.create("patchModules", PatchModuleExtension.class); new CompileTask(project).configureCompileJava(); + new CompileModuleInfoTask(project).configureCompileModuleInfoJava(); new CompileTestTask().configureCompileTestJava(project, moduleName); new TestTask().configureTestJava(project, moduleName); new RunTask().configureRun(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/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 75ccd2a..eb8871b 100644 --- a/src/main/java/org/javamodularity/moduleplugin/tasks/CompileJavaTaskMutator.java +++ b/src/main/java/org/javamodularity/moduleplugin/tasks/CompileJavaTaskMutator.java @@ -1,8 +1,10 @@ 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; @@ -11,27 +13,48 @@ class CompileJavaTaskMutator { private static final String COMPILE_KOTLIN_TASK_NAME = "compileKotlin"; - static void mutateJavaCompileTask(Project project, JavaCompile compileJava) { - ModuleOptions moduleOptions = compileJava.getExtensions().getByType(ModuleOptions.class); + 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()); // 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() { + @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 75039a5..7f6b6e7 100644 --- a/src/main/java/org/javamodularity/moduleplugin/tasks/CompileTask.java +++ b/src/main/java/org/javamodularity/moduleplugin/tasks/CompileTask.java @@ -5,34 +5,45 @@ import org.gradle.api.Task; import org.gradle.api.plugins.JavaPlugin; import org.gradle.api.tasks.compile.JavaCompile; -import org.javamodularity.moduleplugin.JavaProjectHelper; +import org.javamodularity.moduleplugin.extensions.CompileModuleOptions; -public class CompileTask { - - private final Project project; +public class CompileTask extends AbstractCompileTask { public CompileTask(Project project) { - this.project = project; + super(project); } + /** + * @see CompileModuleInfoTask#configureCompileModuleInfoJava() + */ public void configureCompileJava() { helper().findCompileJavaTask(JavaPlugin.COMPILE_JAVA_TASK_NAME) .ifPresent(this::configureCompileJava); } private void configureCompileJava(JavaCompile compileJava) { - compileJava.getExtensions().create("moduleOptions", ModuleOptions.class, project); + 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() { @Override public void execute(Task task) { - CompileJavaTaskMutator.mutateJavaCompileTask(project, compileJava); + mutator.modularizeJavaCompileTask(compileJava); } }); } - private JavaProjectHelper helper() { - return new JavaProjectHelper(project); - } } 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 addOpens) { this.addOpens = addOpens; } - void mutateArgs(String moduleName, List args) { + protected void mutateArgs(String moduleName, List args) { if (!getAddModules().isEmpty()) { String addModules = String.join(",", getAddModules()); args.add("--add-modules"); diff --git a/src/test/java/org/javamodularity/moduleplugin/tasks/CompileJavaTaskMutatorTest.java b/src/test/java/org/javamodularity/moduleplugin/tasks/CompileJavaTaskMutatorTest.java index 54a6177..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 twoLastArguments = twoLastCompilerArgs(compileJava); diff --git a/test-project-kotlin/greeter.api/build.gradle.kts b/test-project-kotlin/greeter.api/build.gradle.kts index 011320c..566106a 100644 --- a/test-project-kotlin/greeter.api/build.gradle.kts +++ b/test-project-kotlin/greeter.api/build.gradle.kts @@ -1,12 +1,13 @@ -import org.javamodularity.moduleplugin.tasks.ModuleOptions +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(ModuleOptions::class) { + extensions.configure(CompileModuleOptions::class) { addModules = listOf() + compileModuleInfoSeparately = false } } diff --git a/test-project/greeter.api/build.gradle b/test-project/greeter.api/build.gradle index 46380ea..c676ee8 100644 --- a/test-project/greeter.api/build.gradle +++ b/test-project/greeter.api/build.gradle @@ -6,6 +6,7 @@ plugins { compileJava { moduleOptions { addModules = [] + compileModuleInfoSeparately = false } }