diff --git a/src/main/groovy/io/repaint/maven/tiles/TilesMavenLifecycleParticipant.groovy b/src/main/groovy/io/repaint/maven/tiles/TilesMavenLifecycleParticipant.groovy index d662de2..b436c7c 100644 --- a/src/main/groovy/io/repaint/maven/tiles/TilesMavenLifecycleParticipant.groovy +++ b/src/main/groovy/io/repaint/maven/tiles/TilesMavenLifecycleParticipant.groovy @@ -40,6 +40,7 @@ import org.apache.maven.model.DistributionManagement import org.apache.maven.model.Model import org.apache.maven.model.Parent import org.apache.maven.model.Plugin +import org.apache.maven.model.PluginExecution import org.apache.maven.model.PluginManagement import org.apache.maven.model.Repository import org.apache.maven.model.building.DefaultModelBuilder @@ -68,6 +69,7 @@ import org.apache.maven.shared.filtering.MavenResourcesFiltering import org.codehaus.plexus.component.annotations.Component import org.codehaus.plexus.component.annotations.Requirement import org.codehaus.plexus.logging.Logger +import org.codehaus.plexus.util.xml.Xpp3Dom import org.codehaus.plexus.util.xml.pull.XmlPullParserException import org.eclipse.aether.impl.VersionRangeResolver import org.eclipse.aether.resolution.VersionRangeRequest @@ -108,7 +110,7 @@ public class TilesMavenLifecycleParticipant extends AbstractMavenLifecyclePartic @Requirement ProjectBuilder projectBuilder - + @Requirement ModelBuilder modelBuilder @@ -154,6 +156,8 @@ public class TilesMavenLifecycleParticipant extends AbstractMavenLifecyclePartic Map processedTiles = [:] List tileDiscoveryOrder = [] Map unprocessedTiles = [:] + Map tilesByExecution = [:]; + String applyBeforeParent; /** @@ -359,9 +363,10 @@ public class TilesMavenLifecycleParticipant extends AbstractMavenLifecyclePartic */ protected void orchestrateMerge(MavenSession mavenSession, MavenProject project) throws MavenExecutionException { // Clear collected tiles from previous project in reactor - processedTiles.clear(); - tileDiscoveryOrder.clear(); - unprocessedTiles.clear(); + processedTiles.clear() + tileDiscoveryOrder.clear() + unprocessedTiles.clear() + tilesByExecution.clear() // collect the first set of tiles parseConfiguration(project.model, project.file) @@ -467,7 +472,7 @@ public class TilesMavenLifecycleParticipant extends AbstractMavenLifecyclePartic try { ModelBuildingResult interimBuild = modelBuilder.build(request) - // this will revert the tile dependencies inserted by TilesProjectBuilder, which is fine since by now they + // this will revert the tile dependencies inserted by TilesProjectBuilder, which is fine since by now they // served their purpose of correctly ordering projects, so we can now do without them ModelBuildingResult finalModel = modelBuilder.build(request, interimBuild) if (!tilesInjected && applyBeforeParent) { @@ -687,6 +692,8 @@ public class TilesMavenLifecycleParticipant extends AbstractMavenLifecyclePartic } protected void loadAllDiscoveredTiles(MavenSession mavenSession, MavenProject project) throws MavenExecutionException { + + List mergeSourceTiles = [] while (unprocessedTiles.size() > 0) { String unresolvedTile = unprocessedTiles.keySet().iterator().next() @@ -696,14 +703,102 @@ public class TilesMavenLifecycleParticipant extends AbstractMavenLifecyclePartic // ensure we have resolved the tile (it could come from a non-tile model) if (tileModel) { - processedTiles.put(artifactName(resolvedTile), new ArtifactModel(resolvedTile, tileModel)) - parseForExtendedSyntax(tileModel, resolvedTile.getFile()) + if (hasProperty(tileModel, 'tile-merge-source')) { + // hold and merge into target later + mergeSourceTiles.add(tileModel) + } else { + if (hasProperty(tileModel, 'tile-merge-target')) { + registerTargetTile(tileModel) + } + String tileName = artifactName(resolvedTile) + processedTiles.put(tileName, new ArtifactModel(resolvedTile, tileModel)) + parseForExtendedSyntax(tileModel, resolvedTile.getFile()) + } } } + // merge all the source tiles last + for (TileModel mergeTile : mergeSourceTiles) { + mergeTileIntoTarget(mergeTile) + } + ensureAllTilesDiscoveredAreAccountedFor() } + private static boolean hasProperty(TileModel tileModel, String propertyKey) { + // remove these properties, we don't want them in the merged result + return 'true' == tileModel.model?.properties?.remove(propertyKey) + } + + private List registerTargetTile(TileModel targetTile) { + return mergeTile(targetTile, false) + } + + private List mergeTileIntoTarget(TileModel fragmentTile) { + return mergeTile(fragmentTile, true) + } + + private List mergeTile(TileModel tileModel, boolean mergeIntoTarget) { + + tileModel.model?.build?.plugins?.each { plugin -> + plugin.executions.each { execution -> + String eid = "$plugin.groupId:$plugin.artifactId:$execution.id" + if (!mergeIntoTarget) { + tilesByExecution.put(eid, tileModel) + } else { + String fragmentId = "$tileModel.model.groupId:$tileModel.model.artifactId" + TileModel targetTile = tilesByExecution.get(eid) + if (targetTile) { + String targetId = "$targetTile.model.groupId:$targetTile.model.artifactId" + logger.info("Merged tile $fragmentId into $targetId plugin:$eid") + mergeProperties(targetTile, tileModel) + mergeExecutionConfiguration(targetTile, execution, eid) + } else { + String missingTileId = tileModel.model?.properties?.getProperty('tile-merge-expected-target') + if (missingTileId) { + throw new MavenExecutionException("Please add missing tile $missingTileId. This is required for tile $fragmentId, plugin:$eid", (Throwable)null) + } else { + throw new MavenExecutionException("Error with tile $fragmentId - Missing target tile required with plugin:$eid. Please check the documentation for this tile.", (Throwable)null) + } + } + } + } + } + } + + /** + * Merge the properties from the mergeTile into targetTile. + */ + private static void mergeProperties(TileModel targetTile, TileModel mergeTile) { + if (mergeTile.model.properties) { + targetTile.model.properties.putAll(mergeTile.model.properties) + } + } + + /** + * Merge the execution configuration from mergeExecution into the target tile. + */ + private void mergeExecutionConfiguration(TileModel targetTile, PluginExecution mergeExecution, String eid) { + + targetTile.model?.build?.plugins?.each { plugin -> + plugin.executions.each { execution -> + String targetEid = "$plugin.groupId:$plugin.artifactId:$execution.id" + if (targetEid.equals(eid)) { + Xpp3Dom configuration = (Xpp3Dom)execution.configuration + String appendElementName = configuration.getAttribute('tiles-append') + if (appendElementName) { + Xpp3Dom target = configuration.getChild(appendElementName) + Xpp3Dom source = ((Xpp3Dom)mergeExecution.configuration).getChild(appendElementName) + // append from source into target + Xpp3Dom.mergeXpp3Dom(target, source, false) + + logger.debug("merged execution configuration - $eid") + } + } + } + } + } + /** * removes all invalid tiles from the discovery order */ @@ -787,8 +882,7 @@ public class TilesMavenLifecycleParticipant extends AbstractMavenLifecyclePartic void resolveVersionRange(MavenProject project, Artifact tileArtifact) { def versionRangeRequest = new VersionRangeRequest(RepositoryUtils.toArtifact(tileArtifact), - RepositoryUtils.toRepos(project?.remoteArtifactRepositories), - null) + RepositoryUtils.toRepos(project?.remoteArtifactRepositories), null) def versionRangeResult = versionRangeResolver.resolveVersionRange(mavenSession?.repositorySession, versionRangeRequest) diff --git a/src/test/groovy/io/repaint/maven/tiles/TilesMavenLifecycleParticipantTest.groovy b/src/test/groovy/io/repaint/maven/tiles/TilesMavenLifecycleParticipantTest.groovy index 00da1ff..25fead6 100644 --- a/src/test/groovy/io/repaint/maven/tiles/TilesMavenLifecycleParticipantTest.groovy +++ b/src/test/groovy/io/repaint/maven/tiles/TilesMavenLifecycleParticipantTest.groovy @@ -30,6 +30,7 @@ import org.apache.maven.model.Build import org.apache.maven.model.Model import org.apache.maven.model.Parent import org.apache.maven.model.Plugin +import org.apache.maven.model.PluginExecution import org.apache.maven.model.building.ModelBuildingRequest import org.apache.maven.model.io.xpp3.MavenXpp3Reader import org.apache.maven.project.MavenProject @@ -37,6 +38,7 @@ import org.apache.maven.shared.filtering.DefaultMavenFileFilter import org.apache.maven.shared.filtering.DefaultMavenReaderFilter import org.apache.maven.shared.filtering.DefaultMavenResourcesFiltering import org.codehaus.plexus.logging.Logger +import org.codehaus.plexus.util.xml.Xpp3Dom import org.codehaus.plexus.util.xml.Xpp3DomBuilder import org.eclipse.aether.impl.VersionRangeResolver import org.junit.AfterClass @@ -48,6 +50,8 @@ import org.sonatype.plexus.build.incremental.DefaultBuildContext import static groovy.test.GroovyAssert.shouldFail import static io.repaint.maven.tiles.Constants.TILEPLUGIN_ARTIFACT import static io.repaint.maven.tiles.Constants.TILEPLUGIN_GROUP +import static io.repaint.maven.tiles.GavUtil.artifactName +import static org.junit.Assert.assertEquals import static org.mockito.Mockito.mock import static org.mockito.Mockito.when @@ -149,6 +153,69 @@ public class TilesMavenLifecycleParticipantTest { } } + @Test + void testTileMerge() { + + Model model = new Model() + model.setGroupId("io.repaint.tiles") + model.setArtifactId("test-merge-tile") + model.setVersion("1.1-SNAPSHOT") + + model.build = new Build() + model.build.directory = "target/test-merge-tile" + + MavenProject project = new MavenProject(model) + project.setFile(new File("src/test/resources/test-merge-tile/pom.xml")) + + MavenExecutionRequest req = mock(MavenExecutionRequest.class) + when(req.getUserProperties()).thenReturn(new Properties()) + when(req.getSystemProperties()).thenReturn(new Properties()) + + MavenSession session = new MavenSession(null, req, mock(MavenExecutionResult.class), Arrays.asList(project)) + + addUnprocessedTile('test-merge-tile/kapt-tile.xml', 'kapt-tile') + addUnprocessedTile('test-merge-tile/kapt-dinject-tile.xml', 'kapt-dinject-tile') + addUnprocessedTile('test-merge-tile/kapt-javalin-tile.xml', 'kapt-javalin-tile') + + // act + participant.loadAllDiscoveredTiles(session, project) + + + Model tileModel = participant.processedTiles['io.repaint.tiles:kapt-tile'].tileModel.model + PluginExecution pluginExecution = tileModel.build.plugins[0].executions[0] + assert pluginExecution.id == 'kapt' + + // assert properties have been merged + assert tileModel.properties['dinject-generator.version'] == '1.8' + assert tileModel.properties['kotlin.version'] == '1.3.31' + + String expectedAnnotationProcessorPaths = ''' + + + + io.dinject + javalin-generator + 1.6 + + + io.dinject + dinject-generator + ${dinject-generator.version} + + +'''.trim() + + // assert the annotationProcessorPaths have been appended + Xpp3Dom paths = ((Xpp3Dom)pluginExecution.configuration).getChild('annotationProcessorPaths') + assertEquals(paths.toString().trim(), expectedAnnotationProcessorPaths) + } + + def addUnprocessedTile(String testResourceName, String tileName) { + Artifact kaptTile = participant.turnPropertyIntoUnprocessedTile("io.repaint.tiles:$tileName:1.1", null) + kaptTile.file = new File("src/test/resources/$testResourceName") + participant.unprocessedTiles.put(artifactName(kaptTile), kaptTile) + } + @Test public void testFiltering() { final def context = new DefaultBuildContext() diff --git a/src/test/resources/test-merge-tile/kapt-dinject-tile.xml b/src/test/resources/test-merge-tile/kapt-dinject-tile.xml new file mode 100644 index 0000000..b6dfe48 --- /dev/null +++ b/src/test/resources/test-merge-tile/kapt-dinject-tile.xml @@ -0,0 +1,41 @@ + + + + + Add KAPT annotation processor to generate DInject Dependency injection (as java source code). + + + + 1.8 + true + + + + + + + + org.jetbrains.kotlin + kotlin-maven-plugin + + + + kapt + + + + io.dinject + dinject-generator + ${dinject-generator.version} + + + + + + + + + + + + diff --git a/src/test/resources/test-merge-tile/kapt-javalin-tile.xml b/src/test/resources/test-merge-tile/kapt-javalin-tile.xml new file mode 100644 index 0000000..ef42c95 --- /dev/null +++ b/src/test/resources/test-merge-tile/kapt-javalin-tile.xml @@ -0,0 +1,41 @@ + + + + + Add KAPT annotation processor to generate javalin controllers (as java source code). + + + + true + + + + + + + org.jetbrains.kotlin + kotlin-maven-plugin + + + + kapt + + + + + io.dinject + javalin-generator + 1.6 + + + + + + + + + + + + + diff --git a/src/test/resources/test-merge-tile/kapt-tile.xml b/src/test/resources/test-merge-tile/kapt-tile.xml new file mode 100644 index 0000000..9d6ed92 --- /dev/null +++ b/src/test/resources/test-merge-tile/kapt-tile.xml @@ -0,0 +1,114 @@ + + + + + Kotlin compiler with KAPT annotation processing support. + + + + true + 1.8 + 1.3.31 + 1.3 + 1.8 + UTF-8 + + + + src/main/kotlin + src/test/kotlin + + + + + org.jetbrains.kotlin + kotlin-maven-plugin + ${kotlin.version} + + ${kotlin.apiVersion} + ${kotlin.jvmTarget} + + + + + kapt + + kapt + + + + src/main/kotlin + src/main/java + + + + + + + + + + + + compile + compile + + compile + + + + + test-compile + test-compile + + test-compile + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.0 + + ${java.version} + ${java.version} + + + + + default-compile + none + + + + + default-testCompile + none + + + + + java-compile + compile + + compile + + + + + java-test-compile + test-compile + + testCompile + + + + + + + + + + diff --git a/src/test/resources/test-merge-tile/pom.xml b/src/test/resources/test-merge-tile/pom.xml new file mode 100644 index 0000000..6226b5b --- /dev/null +++ b/src/test/resources/test-merge-tile/pom.xml @@ -0,0 +1,33 @@ + + + + 4.0.0 + io.repaint.tiles + test-tile-merge + 1.1-SNAPSHOT + tile + + + + io.repaint.maven + tiles-maven-plugin + 1.1-SNAPSHOT + true + + + +