Skip to content

Add Packaging.producesMainArtifact() API to support module source hierarchy #13396

Open

Description

@gnodet

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:

The Bug Chain

  1. DefaultProjectBuilder.finishProjectInitialization() unconditionally creates a main artifact for every project, regardless of packaging or source hierarchy.
  2. DefaultProject.artifacts() includes the main artifact whenever it's coordinate-wise different from the POM artifact. For jar packaging (extension:"jar" vs "pom"), they always differ → mainArtifact() always returns non-empty.
  3. maven-jar-plugin in module hierarchy mode creates N attached JARs (one per JPMS module with artifactId = moduleName) but never writes to the main artifact slot — the main artifact keeps pointing at target/classes/ (a directory).
  4. maven-install-plugin iterates projectManager.getAllArtifacts(project), finds the main artifact fails Files.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() returns true)
  • Project.mainArtifact() returns Optional.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() returns Optional.empty() when packaging says no main artifact
  • Install/deploy already handle this correctly (proven by pom packaging)

3. Support module source hierarchy

Two options (not mutually exclusive):

Option A — New packaging type (modules):

  • ModulesLifecycleMappingProvider with @Named("modules") — same lifecycle bindings as jar but producesMainArtifact() = false
  • Users write <packaging>modules</packaging>
  • Clean and explicit

Option B — Auto-detection from <sources> model:

  • DefaultPackagingRegistry or DefaultProject detects module source hierarchy via SourceQueries.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 pom packaging, 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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions