Skip to content

Architecture: establish shared Native Image planning and harden plugin boundaries #980

Description

@vjovanov

Motivation

Native Build Tools already has the right high-level dependency direction: the Gradle and Maven plugins depend on build-tool-neutral common libraries. However, important shared Native Image semantics are still assembled independently in the two adapters.

In particular:

  • Gradle's NativeImageCommandLineProvider and Maven's AbstractNativeImageMojo independently construct the classpath, standard flags, output path, system/JVM properties, configuration directories, argument file, and trailing main class.
  • NativeImagePlugin has grown into a large composition root containing registration, native tests, agents, metadata, toolchain, layer, and compatibility-mode wiring.
  • Maven-specific helpers are packaged under org.graalvm.buildtools.utils, creating a split package with the published common-utils dependency.
  • MissingMetadataCommandSupport combines domain analysis, rendering, GitHub/Maven Central HTTP integration, authentication, and repository discovery.
  • Parity is specified and covered by separate functional suites, but there is no shared contract suite that proves equivalent requests produce equivalent Native Image plans.

This makes Gradle/Maven behavior easier to drift, raises the cost of adding shared features, and pushes too much verification into expensive end-to-end tests.

Proposed target architecture

Introduce a build-tool-neutral planning boundary:

Gradle model ----\
                  -> NativeBuildRequest -> NativeImagePlanner -> InvocationPlan
Maven model -----/                                      |
                                      adapter-specific process execution

The shared planner should operate only on plain Java values. Gradle remains responsible for providers, task inputs/outputs, services, and ExecOperations; Maven remains responsible for project/lifecycle/toolchain integration and Mojo error translation.

This is an umbrella architecture issue. Large implementation phases may be tracked as linked follow-up issues.

Scope

1. Specify and introduce shared planning types

  • Update the most-specific grund functional and architecture points before changing code.
  • Add immutable build-tool-neutral request/plan types for common Native Image invocation semantics.
  • Centralize standard flag, classpath, output, property, configuration-directory, argument-file, and main-class ordering semantics in a pure planner.
  • Keep build-tool-specific option collection and process execution in the adapters.

2. Adapt Gradle and Maven incrementally

  • Translate finalized Gradle provider values into the shared request.
  • Translate Maven Mojo/project state into the shared request.
  • Migrate one semantic slice at a time while preserving existing task, DSL, goal, parameter, and diagnostic compatibility.

3. Harden ownership boundaries

  • Move Maven-only helpers from org.graalvm.buildtools.utils to org.graalvm.buildtools.maven.internal (with compatibility bridges if required).
  • Decompose NativeImagePlugin into focused internal feature/configuration components while keeping the public plugin class as the composition root.
  • Make Maven Mojos thin parameter/descriptor adapters that delegate execution behavior to focused services.
  • Split missing-metadata analysis/reporting from GitHub, Maven Central, and repository-discovery adapters.

4. Enforce the architecture

  • Add shared planner/contract tests covering parity-sensitive scenarios.
  • Retain real Gradle TestKit and Maven functional tests for adapter and end-to-end behavior.
  • Add automated dependency/package checks: common code must not import Gradle or Maven APIs, product plugins must not depend on each other, and published artifacts must not create split packages.
  • Add an API compatibility baseline for public common/Gradle APIs and the Maven plugin descriptor.

Non-goals

  • Creating a shared Gradle/Maven user-facing DSL.
  • Making common code depend on Gradle or Maven APIs.
  • Replacing idiomatic Gradle provider/task wiring or Maven lifecycle/toolchain integration.
  • Changing existing user-visible behavior as part of the structural migration.
  • Splitting every package into a separately published artifact upfront.

Acceptance criteria

  • The applicable grund specs describe the request/planner/plan boundary and pass grund check.
  • A pure shared planner owns the parity-sensitive Native Image invocation semantics listed above.
  • Both plugins consume that planner without exposing build-tool APIs to common code.
  • Existing public Gradle DSL/task names and Maven goal/parameter names remain compatible.
  • Maven-specific implementation classes no longer contribute to the common-utils package.
  • The Gradle entry point and Maven Mojo hierarchy delegate major workflows to focused internal components.
  • Missing-metadata domain analysis can be tested without network clients.
  • Shared contract tests cover core Gradle/Maven parity scenarios, with product-specific differences documented in their focused specs.
  • Automated architecture/API checks prevent dependency-direction, split-package, and compatibility regressions.
  • Existing unit, functional, and end-to-end suites continue to pass.

Suggested implementation order

  1. Architecture/spec update and shared request/plan skeleton.
  2. Basic command-line planning and pure unit tests.
  3. Gradle and Maven adapters plus parity contract tests.
  4. Split-package removal and entry-point/Mojo decomposition.
  5. Missing-metadata ports/adapters and API/architecture enforcement.

Activity

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

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions