Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@
import org.gradle.api.file.FileCollection;
import org.gradle.api.logging.Logger;
import org.gradle.api.logging.Logging;
import org.gradle.api.tasks.compile.AbstractCompile;
import org.gradle.api.tasks.compile.JavaCompile;
import org.javamodularity.moduleplugin.JavaProjectHelper;
import org.javamodularity.moduleplugin.extensions.CompileModuleOptions;
import org.javamodularity.moduleplugin.extensions.PatchModuleContainer;
import org.javamodularity.moduleplugin.internal.MutatorHelper;
import org.javamodularity.moduleplugin.tasks.MergeClassesHelper.CompileTaskWrapper;

import java.util.ArrayList;
import java.util.List;
Expand Down Expand Up @@ -60,7 +60,7 @@ private List<String> buildCompilerArgs(JavaCompile javaCompile) {
helper().modularityExtension().optionContainer().getPatchModuleContainer());
String moduleName = helper().moduleName();
new MergeClassesHelper(project).otherCompileTaskStream()
.map(AbstractCompile::getDestinationDir)
.map(CompileTaskWrapper::getDestinationDir)
.forEach(dir -> patchModuleContainer.addDir(moduleName, dir.getAbsolutePath()));

// Keep only valid module-path entries (https://github.com/java9-modularity/gradle-modules-plugin/issues/190)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package org.javamodularity.moduleplugin.tasks;

import org.gradle.api.Project;
import org.gradle.api.Task;
import org.gradle.api.file.DirectoryProperty;
import org.gradle.api.file.DuplicatesStrategy;
import org.gradle.api.file.FileCollection;
import org.gradle.api.logging.Logger;
Expand All @@ -15,10 +17,13 @@
import org.javamodularity.moduleplugin.internal.StreamHelper;

import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.Supplier;
import java.util.stream.Stream;

public class MergeClassesHelper {
Expand All @@ -38,10 +43,11 @@ public Project project() {
return project;
}

public Stream<AbstractCompile> otherCompileTaskStream() {
public Stream<CompileTaskWrapper> otherCompileTaskStream() {
return otherCompileTaskNameStream()
.map(name -> helper().findTask(name, AbstractCompile.class))
.flatMap(Optional::stream);
.map(name -> helper().findTask(name, Task.class))
.flatMap(Optional::stream)
.map(this::toWrapper);
}

private Stream<String> otherCompileTaskNameStream() {
Expand All @@ -52,16 +58,16 @@ private Stream<String> otherCompileTaskNameStream() {
);
}

public JavaCompile javaCompileTask() {
return helper().task(JavaPlugin.COMPILE_JAVA_TASK_NAME, JavaCompile.class);
private CompileTaskWrapper javaCompileTask() {
return toWrapper(helper().task(JavaPlugin.COMPILE_JAVA_TASK_NAME, JavaCompile.class));
}

public Stream<AbstractCompile> allCompileTaskStream() {
public Stream<CompileTaskWrapper> allCompileTaskStream() {
return Stream.concat(Stream.of(javaCompileTask()), otherCompileTaskStream());
}

public boolean isMergeRequired() {
return otherCompileTaskStream().anyMatch(task -> !task.getSource().isEmpty());
return otherCompileTaskStream().anyMatch(CompileTaskWrapper::hasSource);
}

public Sync createMergeClassesTask() {
Expand All @@ -84,12 +90,112 @@ public FileCollection getMergeAdjustedClasspath(FileCollection classpath) {
}

Set<File> files = new HashSet<>(classpath.getFiles());
allCompileTaskStream().map(AbstractCompile::getDestinationDir).forEach(files::remove);
allCompileTaskStream().map(CompileTaskWrapper::getDestinationDir).forEach(files::remove);
files.add(helper().getMergedDir());
return project.files(files.toArray());
}

private CompileTaskWrapper toWrapper(Task task) {
return task instanceof AbstractCompile
? new GradleTaskWrapper((AbstractCompile) task)
: new ReflectionTaskWrapper(task);
}

private JavaProjectHelper helper() {
return new JavaProjectHelper(project);
}

public interface CompileTaskWrapper {
Task getTask();

File getDestinationDir();

boolean hasSource();
}

private static final class GradleTaskWrapper implements CompileTaskWrapper {

private final AbstractCompile task;

GradleTaskWrapper(AbstractCompile task) {
this.task = task;
}

@Override
public Task getTask() {
return task;
}

@Override
public File getDestinationDir() {
return task.getDestinationDir();
}

@Override
public boolean hasSource() {
return !task.getSource().isEmpty();
}
}

private static final class ReflectionTaskWrapper implements CompileTaskWrapper {

private final Task task;
private final Supplier<DirectoryProperty> destinationDir;
private final Supplier<FileCollection> source;

ReflectionTaskWrapper(Task task) {
this.task = task;
this.destinationDir = supplier(
task,
"getDestinationDirectory",
DirectoryProperty.class
);
this.source = supplier(
task,
"getSources",
FileCollection.class
);
}

@Override
public Task getTask() {
return task;
}

@Override
public File getDestinationDir() {
return destinationDir.get().getAsFile().getOrNull();
}

@Override
public boolean hasSource() {
return !source.get().isEmpty();
}

private static <T> Supplier<T> supplier(Task task, String getterName, Class<T> getterReturnType) {
final Method m = getMethod(task, getterName);

return () -> {
try {
final Object result = m.invoke(task);
return getterReturnType.cast(result);
} catch (InvocationTargetException e) {
if (e.getTargetException() instanceof RuntimeException) {
throw (RuntimeException) e.getTargetException();
}
throw new RuntimeException(e.getTargetException());
} catch (IllegalAccessException e) {
throw new RuntimeException("Failed to invoke " + getterName + " on " + task.getClass(), e);
}
};
}

private static Method getMethod(Task task, String name) {
try {
return task.getClass().getMethod(name);
} catch (NoSuchMethodException e) {
throw new IllegalStateException("Method " + name + " missing on " + task.getClass(), e);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,14 @@ public void configureMergeClasses() {
public void configureMergeClassesAfterEvaluate() {
var mergeClasses = mergeClassesHelper().createMergeClassesTask();

mergeClassesHelper().allCompileTaskStream().forEach(task -> {
mergeClassesHelper().allCompileTaskStream().forEach(taskWrapper -> {
List<String> modularTasks = List.of(JavaPlugin.COMPILE_JAVA_TASK_NAME, CompileModuleOptions.COMPILE_MODULE_INFO_TASK_NAME);
if(modularTasks.contains(task.getName())) {
mergeClasses.from(task.getDestinationDir());
if(modularTasks.contains(taskWrapper.getTask().getName())) {
mergeClasses.from(taskWrapper.getDestinationDir());
} else {
mergeClasses.from(task.getDestinationDir(), copySpec -> copySpec.exclude("**/module-info.class"));
mergeClasses.from(taskWrapper.getDestinationDir(), copySpec -> copySpec.exclude("**/module-info.class"));
}
mergeClasses.dependsOn(task);
mergeClasses.dependsOn(taskWrapper.getTask());
});
mergeClasses.into(helper().getMergedDir());
mergeClasses.onlyIf(task -> mergeClassesHelper().isMergeRequired());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ void before() throws IOException {
}

@CartesianProductTest(name = "smokeTest({arguments})")
@CartesianValueSource(strings = {"test-project", "test-project-kotlin", "test-project-groovy"})
@CartesianValueSource(strings = {"test-project", "test-project-kotlin-pre-1-7", "test-project-kotlin", "test-project-groovy"})
@CartesianValueSource(strings = {"5.1", "5.6", "6.3", "6.4.1", "6.5.1", "6.8.3", "7.0", "7.2"})
void smokeTest(String projectName, String gradleVersion) {
LOGGER.lifecycle("Executing smokeTest of {} with Gradle {}", projectName, gradleVersion);
Expand All @@ -60,7 +60,7 @@ void smokeTest(String projectName, String gradleVersion) {
}

@CartesianProductTest(name = "smokeTestRun({arguments})")
@CartesianValueSource(strings = {"test-project", "test-project-kotlin", "test-project-groovy"})
@CartesianValueSource(strings = {"test-project", "test-project-kotlin-pre-1-7", "test-project-kotlin", "test-project-groovy"})
@CartesianValueSource(strings = {"5.1", "5.6", "6.3", "6.4.1", "6.5.1", "6.8.3", "7.0", "7.2"})
void smokeTestRun(String projectName, String gradleVersion) {
LOGGER.lifecycle("Executing smokeTestRun of {} with Gradle {}", projectName, gradleVersion);
Expand Down Expand Up @@ -158,7 +158,7 @@ private static void assertExpectedClassFileFormats(
}

@CartesianProductTest(name = "smokeTestDist({arguments})")
@CartesianValueSource(strings = {"test-project", "test-project-kotlin", "test-project-groovy"})
@CartesianValueSource(strings = {"test-project", "test-project-kotlin-pre-1-7", "test-project-kotlin", "test-project-groovy"})
@CartesianValueSource(strings = {"5.1", "5.6", "6.3", "6.4.1", "6.5.1", "6.8.3", "7.0", "7.2"})
void smokeTestDist(String projectName, String gradleVersion) {
LOGGER.lifecycle("Executing smokeTestDist of {} with Gradle {}", projectName, gradleVersion);
Expand Down Expand Up @@ -199,7 +199,7 @@ void smokeTestDist(String projectName, String gradleVersion) {
}

@CartesianProductTest(name = "smokeTestRunDemo({arguments})")
@CartesianValueSource(strings = {"test-project", "test-project-kotlin", "test-project-groovy"})
@CartesianValueSource(strings = {"test-project", "test-project-kotlin-pre-1-7", "test-project-kotlin", "test-project-groovy"})
@CartesianValueSource(strings = {"5.1", "5.6", "6.3", "6.4.1", "6.5.1", "6.8.3", "7.0", "7.2"})
void smokeTestRunDemo(String projectName, String gradleVersion) {
LOGGER.lifecycle("Executing smokeTestRunDemo of {} with Gradle {}", projectName, gradleVersion);
Expand All @@ -218,7 +218,7 @@ void smokeTestRunDemo(String projectName, String gradleVersion) {
}

@CartesianProductTest(name = "smokeTestRunStartScripts({arguments})")
@CartesianValueSource(strings = {"test-project", "test-project-kotlin", "test-project-groovy"})
@CartesianValueSource(strings = {"test-project", "test-project-kotlin-pre-1-7", "test-project-kotlin", "test-project-groovy"})
@CartesianValueSource(strings = {"5.1", "5.6", "6.3", "6.4.1", "6.5.1", "6.8.3", "7.0", "7.2"})
void smokeTestRunStartScripts(String projectName, String gradleVersion) {
LOGGER.lifecycle("Executing smokeTestRunScripts of {} with Gradle {}", projectName, gradleVersion);
Expand Down Expand Up @@ -248,7 +248,9 @@ private static void assertTasksSuccessful(BuildResult result, String subprojectN
}

private static boolean checkCombination(String projectName, String gradleVersion) {
if(projectName.equals("test-project-kotlin") && gradleVersion.compareTo("6.4") < 0) {
final boolean kotlin_NotSupported = projectName.startsWith("test-project-kotlin") && gradleVersion.compareTo("6.4") < 0;
final boolean kotlin1_7_NotSupported = projectName.equals("test-project-kotlin") && gradleVersion.compareTo("6.6") < 0;
if (kotlin_NotSupported || kotlin1_7_NotSupported) {
LOGGER.lifecycle("Unsupported combination: {} / Gradle {}. Test skipped", projectName, gradleVersion);
return false;
}
Expand Down
40 changes: 40 additions & 0 deletions test-project-kotlin-pre-1-7/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
Introduction
===

This Kotlin 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.

Standalone test product
===
To run this product as a standalone test product use this command (launched from `test-project-kotlin` 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-kotlin` directory.

`../gradlew -c local_maven_settings.gradle clean build`

It will use the 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 for `build.gradle.kts` 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.
59 changes: 59 additions & 0 deletions test-project-kotlin-pre-1-7/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
kotlin("jvm") version "1.3.72" apply false
id("org.javamodularity.moduleplugin") version "1.8.11" apply false
}

subprojects {
apply(plugin = "kotlin")
apply(plugin = "org.javamodularity.moduleplugin")

//region https://docs.gradle.org/current/userguide/kotlin_dsl.html#using_kotlin_delegated_properties
val test by tasks.existing(Test::class)
val build by tasks
val javadoc by tasks

val implementation by configurations
val testImplementation by configurations
val testRuntimeOnly by configurations

val jUnitVersion: String by project
val jUnitPlatformVersion: String by project
//endregion

//region KOTLIN
tasks.withType<KotlinCompile> {
kotlinOptions.jvmTarget = "1.8"
}
dependencies {
implementation(kotlin("stdlib-jdk8"))
}
//endregion

repositories {
mavenCentral()
}

configure<org.javamodularity.moduleplugin.extensions.ModularityExtension> {
improveEclipseClasspathFile()
moduleVersion("1.2.3")
}

test {
useJUnitPlatform()

testLogging {
events("PASSED", "FAILED", "SKIPPED", "STANDARD_OUT")
}
}

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")
testRuntimeOnly("org.junit.platform:junit-platform-launcher:$jUnitPlatformVersion")
}

// build.dependsOn(javadoc) // TODO: No public or protected classes found to document
}
2 changes: 2 additions & 0 deletions test-project-kotlin-pre-1-7/gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
jUnitVersion = 5.6.2
jUnitPlatformVersion = 1.6.2
21 changes: 21 additions & 0 deletions test-project-kotlin-pre-1-7/greeter.api/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import org.javamodularity.moduleplugin.extensions.CompileModuleOptions
import org.javamodularity.moduleplugin.extensions.TestModuleOptions

//region NO-OP (DSL testing)
tasks.compileJava {
extensions.configure<CompileModuleOptions> {
addModules = listOf()
compileModuleInfoSeparately = false
}
}

tasks.test {
extensions.configure<TestModuleOptions> {
addModules = listOf()
runOnClasspath = false
}
}

modularity {
}
//endregion
Loading