IKE Tooling
IKE Maven Plugin 186
-
Home
- Overview
Self-host bootstrap pattern
How a Maven reactor that contains ike-maven-plugin (or any other plugin) can also use that plugin against itself, without falling into a reactor cycle. The canonical example is ike-tooling’s own reactor root: it ships the plugin, and it wants to bind the plugin’s `built-with and render-sbom-viewer goals so its own published site has /ike-tooling/built-with.html and /ike-tooling/dependencies.html on every release.
This is a niche concern — only relevant if your reactor builds the plugin you want to bind. Most consumers inherit ike-maven-plugin via pluginManagement and never hit this.
The cycle
An unconditional reactor-root <plugins> reference to a plugin that this reactor builds creates a Maven reactor cycle. For ike-tooling, the edge is:
root → ike-maven-plugin → ike-build-standards → root
The middle module exists because ike-maven-plugin depends on ike-build-standards for shared compile-time machinery, and ike-build-standards inherits from root. Maven flags this at reactor evaluation time, before any phase runs — so even mvn install fails with a cycle error.
Important subtlety: pluginManagement does not create such an edge. Only the live <plugins> binding does. The rest of the pom can safely list the plugin in <pluginManagement> — the cycle problem is specific to wanting to actually execute goals from the plugin at reactor-root build time.
The fix: X-SNAPSHOT bootstrap
Indirect through a property whose value is a different GAV than the reactor submodules, and put the plugin binding inside a profile that activates only when the property is set:
<profile>
<id>releaseSelfSite</id>
<activation>
<property>
<name>release.bootstrap.version</name>
</property>
</activation>
<build>
<plugins>
<plugin>
<groupId>network.ike.tooling</groupId>
<artifactId>ike-maven-plugin</artifactId>
<version>${release.bootstrap.version}</version>
<executions>
<execution>
<id>built-with</id>
<phase>pre-site</phase>
<goals><goal>built-with</goal></goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
And invoke mvn site with:
mvn site -Drelease.bootstrap.version=X-SNAPSHOT
where X is the version we’re about to release.
Why this breaks the cycle
During release flow, submodules are set to X (the release version). A <plugins> reference to the plugin at X-SNAPSHOT is a reference to a different GAV than any submodule — no graph edge to a submodule — no reactor cycle.
If we used 186 here, we’d be back to X, and the cycle would return. The whole point is that X-SNAPSHOT and X are different artifacts as far as Maven’s reactor graph is concerned.
Why X-SNAPSHOT is guaranteed in ~/.m2
The release flow runs mvn clean install on the SNAPSHOT pom before bumping to X. That install puts X-SNAPSHOT in the local Maven repository. By the time the site invocation needs to resolve the plugin descriptor, X-SNAPSHOT is already there. And X-SNAPSHOT carries the latest code being released — not a stale previously- released version.
If there is nothing to install, there is nothing to release — so the guarantee holds by induction on the release contract.
Implementation, by file
The pattern is implemented in two cooperating places. Both carry cross-references in their comments so a reader landing at either side can follow the link to the other.
The pom that consumes the property
ike-tooling/pom.xml[1] declares the releaseSelfSite profile (search for "X-SNAPSHOT bootstrap (1 of 2)"). The profile:
- Is property-activated, not always-on.
- Binds
ike:built-withandike:render-sbom-viewerat thepre-sitephase. - References
ike-maven-pluginat${release.bootstrap.version}— the literal value supplied by the caller.
The mojo that supplies the property
ReleaseDraftMojo.java[2] captures oldVersion (the pre-release pom version, = X-SNAPSHOT) at the top of the release flow, then passes -Drelease.bootstrap.version= ${oldVersion} on three site invocations: pre-flight mvn site, external-phase mvn site, external-phase mvn site:stage. Search for "X-SNAPSHOT bootstrap (2 of 2)".
Note that other release-flow mvn calls (verify, clean deploy, ike:site-publish, etc.) do not pass the property. They stay outside the profile and resolve plugin coordinates through ordinary pluginManagement — which is fine, because pluginManagement does not create reactor edges the way live <plugins> does.
When does another reactor need this?
You hit the cycle if all three are true:
- Your reactor builds the plugin you want to bind.
- You want to execute (not just manage) that plugin’s goals at reactor-root build time — typically for site-level reports.
- The plugin has a dependency that transitively pulls in your reactor root (e.g., a sibling module that inherits from it).
If you only have #1, pluginManagement is enough. If you have #1 and #3 but only want pluginManagement, you’re also fine. The cycle is specifically triggered by adding a live <plugins> binding.
When you do hit it, the pattern transplants directly: pick a property name, wrap the plugin binding in a property-activated profile, and pass -D<your-property>=<your-snapshot> from whatever drives the build (release tool, CI script, operator-typed command).
See also
- ike-maven-plugin home[3]
- ike-issues#370[4] — the umbrella issue for the
ike-toolinginstance of this pattern.