#1420 Add support for Gradle incremental annotation processing (#1971)

* Add relevant file in META-INF
* Add integration test with Gradle test kit dependencies
* Test with Gradle 5 and Gradle 6
This commit is contained in:
ttzn 2019-12-08 19:55:16 +01:00 committed by Filip Hrisafov
parent ee794d042c
commit 779eb1bd47
10 changed files with 302 additions and 0 deletions

View File

@ -2,6 +2,7 @@
============ ============
Alexandr Shalugin - https://github.com/shalugin Alexandr Shalugin - https://github.com/shalugin
Amine Touzani - https://github.com/ttzn
Andreas Gudian - https://github.com/agudian Andreas Gudian - https://github.com/agudian
Andrei Arlou - https://github.com/Captain1653 Andrei Arlou - https://github.com/Captain1653
Andres Jose Sebastian Rincon Gonzalez - https://github.com/stianrincon Andres Jose Sebastian Rincon Gonzalez - https://github.com/stianrincon

View File

@ -24,6 +24,19 @@
<mapstruct.version>${project.version}</mapstruct.version> <mapstruct.version>${project.version}</mapstruct.version>
<maven.deploy.skip>true</maven.deploy.skip> <maven.deploy.skip>true</maven.deploy.skip>
</properties> </properties>
<repositories>
<repository>
<id>gradle</id>
<url>https://repo.gradle.org/gradle/libs-releases</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<dependencies> <dependencies>
<!-- Testing --> <!-- Testing -->
@ -42,6 +55,24 @@
<artifactId>maven-verifier</artifactId> <artifactId>maven-verifier</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.gradle</groupId>
<artifactId>gradle-test-kit</artifactId>
<version>5.6.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.gradle</groupId>
<artifactId>gradle-tooling-api</artifactId>
<version>5.6.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
<scope>test</scope>
</dependency>
</dependencies> </dependencies>
<build> <build>

View File

@ -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;
/**
* <p>This is supposed to be run from the mapstruct root project folder.
* Otherwise, use <code>-Dmapstruct_root=path_to_project</code>.
*/
@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<String> compileArgs; // Gradle compile task arguments
public GradleIncrementalCompilationTest(String gradleVersion) {
this.gradleVersion = gradleVersion;
}
@Parameters( name = "Gradle {0}" )
public static List<String> 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<String> fullArguments = new ArrayList<String>( 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<String> 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 );
}
}

View File

@ -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}"
}

View File

@ -0,0 +1 @@
rootProject.name = 'gradle-incremental-compilation-test'

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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";
}
}

View File

@ -0,0 +1 @@
org.mapstruct.ap.MappingProcessor,isolating