diff --git a/copyright.txt b/copyright.txt index 2aab0c1f8..2980f611b 100644 --- a/copyright.txt +++ b/copyright.txt @@ -2,6 +2,7 @@ ============ Alexandr Shalugin - https://github.com/shalugin +Amine Touzani - https://github.com/ttzn Andreas Gudian - https://github.com/agudian Andrei Arlou - https://github.com/Captain1653 Andres Jose Sebastian Rincon Gonzalez - https://github.com/stianrincon diff --git a/integrationtest/pom.xml b/integrationtest/pom.xml index 257c32730..8b69ed0a7 100644 --- a/integrationtest/pom.xml +++ b/integrationtest/pom.xml @@ -24,6 +24,19 @@ ${project.version} true + + + + gradle + https://repo.gradle.org/gradle/libs-releases + + true + + + false + + + @@ -42,6 +55,24 @@ maven-verifier test + + org.gradle + gradle-test-kit + 5.6.4 + test + + + org.gradle + gradle-tooling-api + 5.6.4 + test + + + commons-io + commons-io + 2.6 + test + diff --git a/integrationtest/src/test/java/org/mapstruct/itest/tests/GradleIncrementalCompilationTest.java b/integrationtest/src/test/java/org/mapstruct/itest/tests/GradleIncrementalCompilationTest.java new file mode 100644 index 000000000..bb0c0f4ce --- /dev/null +++ b/integrationtest/src/test/java/org/mapstruct/itest/tests/GradleIncrementalCompilationTest.java @@ -0,0 +1,159 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.itest.tests; + +import static org.gradle.testkit.runner.TaskOutcome.SUCCESS; +import static org.gradle.testkit.runner.TaskOutcome.UP_TO_DATE; +import static org.hamcrest.core.StringContains.containsString; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.apache.commons.io.FileUtils; +import org.gradle.testkit.runner.BuildResult; +import org.gradle.testkit.runner.GradleRunner; +import org.gradle.testkit.runner.TaskOutcome; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +/** + *

This is supposed to be run from the mapstruct root project folder. + * Otherwise, use -Dmapstruct_root=path_to_project. + */ +@RunWith( Parameterized.class ) +public class GradleIncrementalCompilationTest { + private static Path rootPath; + private static String projectDir = "integrationtest/src/test/resources/gradleIncrementalCompilationTest"; + private static String compileTaskName = "compileJava"; + + @Rule + public final TemporaryFolder testBuildDir = new TemporaryFolder(); + @Rule + public final TemporaryFolder testProjectDir = new TemporaryFolder(); + + private String gradleVersion; + private GradleRunner runner; + private File sourceDirectory; + private List compileArgs; // Gradle compile task arguments + + public GradleIncrementalCompilationTest(String gradleVersion) { + this.gradleVersion = gradleVersion; + } + + @Parameters( name = "Gradle {0}" ) + public static List gradleVersions() { + return Arrays.asList( "5.0", "6.0" ); + } + + private void replaceInFile(File file, CharSequence target, CharSequence replacement) throws IOException { + String content = FileUtils.readFileToString( file, Charset.defaultCharset() ); + FileUtils.writeStringToFile( file, content.replace( target, replacement ), Charset.defaultCharset() ); + } + + private GradleRunner getRunner(String... additionalArguments) { + List fullArguments = new ArrayList( compileArgs ); + fullArguments.addAll( Arrays.asList( additionalArguments ) ); + return runner.withArguments( fullArguments ); + } + + private void assertCompileOutcome(BuildResult result, TaskOutcome outcome) { + assertEquals( outcome, result.task( ":" + compileTaskName ).getOutcome() ); + } + + private void assertRecompiled(BuildResult result, int recompiledCount) { + assertCompileOutcome( result, recompiledCount > 0 ? SUCCESS : UP_TO_DATE ); + assertThat( + result.getOutput(), + containsString( String.format( "Incremental compilation of %d classes completed", recompiledCount ) ) ); + } + + private List buildCompileArgs() { + // Make Gradle use the temporary build folder by overriding the buildDir property + String buildDirPropertyArg = "-PbuildDir=" + testBuildDir.getRoot().getAbsolutePath(); + + // Inject the path to the folder containing the mapstruct-processor JAR + String jarDirectoryArg = "-PmapstructRootPath=" + rootPath.toString(); + return Arrays.asList( compileTaskName, buildDirPropertyArg, jarDirectoryArg ); + } + + @BeforeClass + public static void setupClass() throws Exception { + rootPath = Paths.get( System.getProperty( "mapstruct_root", "." ) ).toAbsolutePath(); + } + + @Before + public void setup() throws IOException { + // Copy test project files to the temp dir + Path gradleProjectPath = rootPath.resolve( projectDir ); + FileUtils.copyDirectory( gradleProjectPath.toFile(), testProjectDir.getRoot() ); + compileArgs = buildCompileArgs(); + sourceDirectory = new File( testProjectDir.getRoot(), "src/main/java" ); + runner = GradleRunner.create().withGradleVersion( gradleVersion ).withProjectDir( testProjectDir.getRoot() ); + } + + @Test + public void testBuildSucceeds() throws IOException { + // Make sure the test build setup actually compiles + BuildResult buildResult = getRunner().build(); + assertCompileOutcome( buildResult, SUCCESS ); + } + + @Test + public void testUpToDate() throws IOException { + getRunner().build(); + BuildResult secondBuildResult = getRunner().build(); + assertCompileOutcome( secondBuildResult, UP_TO_DATE ); + } + + @Test + public void testChangeConstant() throws IOException { + getRunner().build(); + // Change return value in class Target + File targetFile = new File( sourceDirectory, "org/mapstruct/itest/gradle/model/Target.java" ); + replaceInFile( targetFile, "original", "changed" ); + BuildResult secondBuildResult = getRunner( "--info" ).build(); + + // 3 classes should be recompiled: Target -> TestMapper -> TestMapperImpl + assertRecompiled( secondBuildResult, 3 ); + } + + @Test + public void testChangeTargetField() throws IOException { + getRunner().build(); + // Change target field in mapper interface + File mapperFile = new File( sourceDirectory, "org/mapstruct/itest/gradle/lib/TestMapper.java" ); + replaceInFile( mapperFile, "field", "otherField" ); + BuildResult secondBuildResult = getRunner( "--info" ).build(); + + // 2 classes should be recompiled: TestMapper -> TestMapperImpl + assertRecompiled( secondBuildResult, 2 ); + } + + @Test + public void testChangeUnrelatedFile() throws IOException { + getRunner().build(); + File unrelatedFile = new File( sourceDirectory, "org/mapstruct/itest/gradle/lib/UnrelatedComponent.java" ); + replaceInFile( unrelatedFile, "true", "false" ); + BuildResult secondBuildResult = getRunner( "--info" ).build(); + + // Only the UnrelatedComponent class should be recompiled + assertRecompiled( secondBuildResult, 1 ); + } +} diff --git a/integrationtest/src/test/resources/gradleIncrementalCompilationTest/build.gradle b/integrationtest/src/test/resources/gradleIncrementalCompilationTest/build.gradle new file mode 100644 index 000000000..e62a8d087 --- /dev/null +++ b/integrationtest/src/test/resources/gradleIncrementalCompilationTest/build.gradle @@ -0,0 +1,29 @@ +plugins { + id 'java' +} + +if (!project.hasProperty('mapstructRootPath')) + throw new IllegalArgumentException("Missing required property: mapstructRootPath") + +repositories { + jcenter() + mavenLocal() + flatDir { + dirs "${mapstructRootPath}/processor/target" + dirs "${mapstructRootPath}/core/target" + } +} + +// Extract version and artifactId values +def apPom = new XmlParser().parse(file("${mapstructRootPath}/processor/pom.xml")) +ext.apArtifactId = apPom.artifactId[0].text() +ext.apVersion = apPom.parent[0].version[0].text() + +def libPom = new XmlParser().parse(file("${mapstructRootPath}/core/pom.xml")) +ext.libArtifactId = libPom.artifactId[0].text() +ext.libVersion = libPom.parent[0].version[0].text() + +dependencies { + annotationProcessor name: "${apArtifactId}-${apVersion}" + implementation name: "${libArtifactId}-${libVersion}" +} diff --git a/integrationtest/src/test/resources/gradleIncrementalCompilationTest/settings.gradle b/integrationtest/src/test/resources/gradleIncrementalCompilationTest/settings.gradle new file mode 100644 index 000000000..f62a77ab7 --- /dev/null +++ b/integrationtest/src/test/resources/gradleIncrementalCompilationTest/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'gradle-incremental-compilation-test' \ No newline at end of file diff --git a/integrationtest/src/test/resources/gradleIncrementalCompilationTest/src/main/java/org/mapstruct/itest/gradle/lib/TestMapper.java b/integrationtest/src/test/resources/gradleIncrementalCompilationTest/src/main/java/org/mapstruct/itest/gradle/lib/TestMapper.java new file mode 100644 index 000000000..13ae2ef5f --- /dev/null +++ b/integrationtest/src/test/resources/gradleIncrementalCompilationTest/src/main/java/org/mapstruct/itest/gradle/lib/TestMapper.java @@ -0,0 +1,19 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.itest.gradle.lib; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; + +import org.mapstruct.itest.gradle.model.Target; +import org.mapstruct.itest.gradle.model.Source; + +@Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE) +public interface TestMapper { + @Mapping(source = "value", target = "field") + public Target toTarget(Source source); +} diff --git a/integrationtest/src/test/resources/gradleIncrementalCompilationTest/src/main/java/org/mapstruct/itest/gradle/lib/UnrelatedComponent.java b/integrationtest/src/test/resources/gradleIncrementalCompilationTest/src/main/java/org/mapstruct/itest/gradle/lib/UnrelatedComponent.java new file mode 100644 index 000000000..9f1095850 --- /dev/null +++ b/integrationtest/src/test/resources/gradleIncrementalCompilationTest/src/main/java/org/mapstruct/itest/gradle/lib/UnrelatedComponent.java @@ -0,0 +1,12 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.itest.gradle.lib; + +public class UnrelatedComponent { + public boolean unrelatedMethod() { + return true; + } +} diff --git a/integrationtest/src/test/resources/gradleIncrementalCompilationTest/src/main/java/org/mapstruct/itest/gradle/model/Source.java b/integrationtest/src/test/resources/gradleIncrementalCompilationTest/src/main/java/org/mapstruct/itest/gradle/model/Source.java new file mode 100644 index 000000000..c7103aace --- /dev/null +++ b/integrationtest/src/test/resources/gradleIncrementalCompilationTest/src/main/java/org/mapstruct/itest/gradle/model/Source.java @@ -0,0 +1,18 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.itest.gradle.model; + +public class Source { + private int value; + + public void setValue(int value) { + this.value = value; + } + + public int getValue() { + return value; + } +} diff --git a/integrationtest/src/test/resources/gradleIncrementalCompilationTest/src/main/java/org/mapstruct/itest/gradle/model/Target.java b/integrationtest/src/test/resources/gradleIncrementalCompilationTest/src/main/java/org/mapstruct/itest/gradle/model/Target.java new file mode 100644 index 000000000..4b5a17110 --- /dev/null +++ b/integrationtest/src/test/resources/gradleIncrementalCompilationTest/src/main/java/org/mapstruct/itest/gradle/model/Target.java @@ -0,0 +1,31 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.itest.gradle.model; + +public class Target { + private String field = getDefaultValue(); + private String otherField; + + public void setField(String field) { + this.field = field; + } + + public String getField() { + return field; + } + + public void setOtherField(String otherField) { + this.otherField = otherField; + } + + public String getOtherField() { + return otherField; + } + + public String getDefaultValue() { + return "original"; + } +} diff --git a/processor/src/main/resources/META-INF/gradle/incremental.annotation.processors b/processor/src/main/resources/META-INF/gradle/incremental.annotation.processors new file mode 100644 index 000000000..172d1bb77 --- /dev/null +++ b/processor/src/main/resources/META-INF/gradle/incremental.annotation.processors @@ -0,0 +1 @@ +org.mapstruct.ap.MappingProcessor,isolating