Repository navigation
Add Packaging.producesMainArtifact() API to support module source hierarchy #13396
Description
Summary
Projects using JPMS module source hierarchy (a single pom.xml producing multiple JPMS module JARs via <sources><source><module>…) currently fail at install time because Maven unconditionally creates a main artifact for jar packaging, even though no single main JAR is produced. The Packaging API lacks a way to express "this packaging does not produce a main artifact."
Background
Related:
- MNG-8137 — Goal jar unconditionally replace main artifact
- maven-install-plugin PR #466 — workaround: skip main artifact when its file is a directory
- maven-jar-plugin commit
f09e78d— warn and skip JAR creation for packaging without main artifact
The Bug Chain
DefaultProjectBuilder.finishProjectInitialization()unconditionally creates a main artifact for every project, regardless of packaging or source hierarchy.DefaultProject.artifacts()includes the main artifact whenever it's coordinate-wise different from the POM artifact. Forjarpackaging (extension:"jar"vs"pom"), they always differ →mainArtifact()always returns non-empty.maven-jar-pluginin module hierarchy mode creates N attached JARs (one per JPMS module withartifactId = moduleName) but never writes to the main artifact slot — the main artifact keeps pointing attarget/classes/(a directory).maven-install-pluginiteratesprojectManager.getAllArtifacts(project), finds the main artifact failsFiles.isRegularFile()→ BUILD FAILURE:"Is a directory".
Analysis
What works today (the pom packaging precedent)
For pom packaging, the system already handles "no main artifact" correctly:
MavenProject.getArtifact()is non-null (the POM artifact — backward compat)- But
DefaultProject.artifacts()returns a 1-element list because POM artifact and project artifact have the same coordinates (ArtifactIdUtils.equalsVersionlessId()returnstrue) Project.mainArtifact()returnsOptional.empty()- Install/deploy, reactor reader, etc. all cope
What's missing
The "no main artifact" behavior for pom is an accidental side effect of the POM artifact and project artifact sharing the same extension. There is no explicit API for a packaging type to declare "I don't produce a main artifact." This is the missing piece.
Proposed Changes
1. Add producesMainArtifact() to Packaging API
// api/maven-api-core/.../Packaging.java
default boolean producesMainArtifact() {
return true;
}This makes the concept first-class rather than an accidental coordinate-comparison side effect.
2. Wire it into DefaultProject.artifacts()
// impl/maven-core/.../DefaultProject.java — artifacts() method
if (packaging.producesMainArtifact()
&& !ArtifactIdUtils.equalsVersionlessId(pomArtifact, projectArtifact)) {
result.add(session.getArtifact(ProducedArtifact.class, projectArtifact));
}MavenProject.getArtifact()stays non-null (backward compat — no NPEs in legacy code)Project.mainArtifact()returnsOptional.empty()when packaging says no main artifact- Install/deploy already handle this correctly (proven by
pompackaging)
3. Support module source hierarchy
Two options (not mutually exclusive):
Option A — New packaging type (modules):
ModulesLifecycleMappingProviderwith@Named("modules")— same lifecycle bindings asjarbutproducesMainArtifact() = false- Users write
<packaging>modules</packaging> - Clean and explicit
Option B — Auto-detection from <sources> model:
DefaultPackagingRegistryorDefaultProjectdetects module source hierarchy viaSourceQueries.usesModuleSourceHierarchy()(which already exists)- Dynamically sets
producesMainArtifact() = false - Users keep
<packaging>jar</packaging>— it "just works"
4. Fix JAR plugin early-return guard
The recent guard in AbstractJarMojo.execute() (f09e78d) returns early when project.getMainArtifact().isEmpty(). This would prevent module hierarchy JARs from being created if a new packaging makes mainArtifact() empty. Needs:
if (project.getMainArtifact().isEmpty() && !usesModuleSourceHierarchy()) {
// warn and return
}5. (Nice-to-have) Improve ReactorReader
ReactorReader.getProjectArtifacts() hardcodes "pom".equals(project.getPackaging()) to skip the main artifact. Should use the packaging flag instead. The isRegularFile filter at line 408 is already a safety net, so this is non-critical.
Files That Need Changes
| File | Change |
|---|---|
api/maven-api-core/.../Packaging.java |
Add producesMainArtifact() default method |
impl/maven-core/.../DefaultProject.java |
Check producesMainArtifact() in artifacts() |
impl/maven-core/.../DefaultPackagingRegistry.java |
Pass flag to DefaultPackaging record |
impl/maven-core/.../packaging/ |
New lifecycle mapping provider (if Option A) |
impl/maven-core/.../ReactorReader.java |
Replace hardcoded "pom" check (nice-to-have) |
maven-jar-plugin AbstractJarMojo.java |
Don't early-return when module source hierarchy detected |
Why the Install Plugin Workaround is Insufficient
PR #466 filters out the main artifact at install time, but:
- The main artifact still "exists" in
Project.mainArtifact()— confusing for all API consumers - Deploy plugin would need the same workaround
- Every future plugin that inspects project artifacts would need to handle this case
- It advises users to switch to
pompackaging, which is misleading
The fix belongs in Maven core's Packaging API, making the concept explicit and letting all downstream consumers (install, deploy, reactor, etc.) benefit.
Claude Code on behalf of Guillaume Nodet
Activity
Delegate artifact production check to Project instead of wrapping Pac…
Auto-detect module source hierarchy and skip main artifact at install (…