Skip to content

Fix JitPack Multi-Module Artifact Publishing #414

@wax911

Description

@wax911

Problem

JitPack does not currently publish usable artifacts for the modular retrofit-graphql modules. This blocks downstream consumers, especially anitrend-app, from moving away from the deprecated :library facade and onto direct module dependencies.

The issue was reproduced against commit 7d56b74166. The same publishing configuration is still present on develop at the time this issue was written.

Impact

anitrend-app cannot safely begin the import migration or modular dependency swap until retrofit-graphql publishes real module artifacts with correct Maven metadata.

Expected downstream usage should be possible with JitPack coordinates similar to:

implementation("com.github.AniTrend.retrofit-graphql:runtime:<sha-or-tag>")
implementation("com.github.AniTrend.retrofit-graphql:api:<sha-or-tag>")
implementation("com.github.AniTrend.retrofit-graphql:android-assets:<sha-or-tag>")
implementation("com.github.AniTrend.retrofit-graphql:annotations:<sha-or-tag>")
implementation("com.github.AniTrend.retrofit-graphql:serialization-kotlinx:<sha-or-tag>")

Exact casing/path should be verified against JitPack output, but the important requirement is that each intended module resolves as its own artifact instead of returning 404 or relying on the broken facade.

Current Behavior

jitpack.yml runs:

./gradlew build --stacktrace --no-daemon -x lint -x lintVitalRelease
./gradlew publishMavenPublicationToMavenLocal --no-daemon

Source: jitpack.yml.

The repository includes the relevant modules:

  • :annotations
  • :api
  • :android-assets
  • :runtime
  • :codegen-core
  • :serialization-gson
  • :serialization-kotlinx
  • :library

Source: settings.gradle.kts.

However, only the deprecated facade :library currently creates a Maven publication.

In buildSrc/src/main/java/co/anitrend/retrofit/graphql/buildSrc/plugin/components/AndroidOptions.kt, publication creation is guarded behind:

// Only publish from the facade :library module
if (isFacadeModule()) {
    createMavenPublicationUsing(sourcesJar)
}

isFacadeModule() only matches :library.

The Android modules :api, :android-assets, :runtime, :serialization-gson, and :serialization-kotlinx are recognised as library modules and get maven-publish applied, but no publication is registered for them.

The JVM modules :annotations and :codegen-core do not apply the shared Android plugin path and also do not register publications.

Additional Defect: Facade Publication Is Not Component-Backed

The existing publication code uses:

val component = components.findByName("android")
logger.lifecycle("Configuring maven publication options for ${project.path}:maven with component-> ${component?.name}")

The observed JitPack log includes:

Configuring maven publication options for :library:maven with component-> null

That means the current publication is not backed by a real Android software component. The publication then manually attaches:

artifact("${project.layout.buildDirectory.get()}/outputs/aar/${project.name}-release.aar")
from(component)

This is brittle and likely prevents Gradle from emitting correct variant metadata and transitive dependencies into the generated POM/module metadata.

That explains the current symptom: the root/facade artifact behaves like an empty shell and does not bring in the real implementation modules.

Expected Behavior

Every externally consumable module must publish a real Maven artifact:

Android AAR Modules

  • :api
  • :android-assets
  • :runtime
  • :serialization-gson
  • :serialization-kotlinx
  • :library only if keeping the deprecated facade published for backward compatibility

JVM JAR Modules

  • :annotations
  • :codegen-core if codegen internals are intended to be consumed by the Gradle plugin or external tooling

Gradle Plugin Module

  • :gradle-plugin needs an explicit decision.
  • If the plugin is intended to be consumed from JitPack, it must publish plugin marker metadata and the plugin artifact correctly.
  • If not in scope for this issue, document that exclusion clearly so the implementation does not half-publish it.

Implementation Guidelines

Do not patch this by adding copy-pasted publishing {} blocks to each module unless there is a strong reason. The repo already centralizes build behavior in buildSrc; publishing should stay centralized there.

Recommended direction:

  1. Split publishing configuration by module kind:

    • Android library modules publish AARs.
    • JVM modules publish JARs.
    • Gradle plugin module is handled separately or explicitly excluded.
  2. For Android libraries, configure AGP publishing explicitly.

    Use Android Gradle Plugin publishing APIs, for example:

    android {
        publishing {
            singleVariant("release") {
                withSourcesJar()
            }
        }
    }

    Then publish from the real release component:

    publishing {
        publications {
            create<MavenPublication>("release") {
                from(components["release"])
                artifactId = project.name
            }
        }
    }

    Do not use components.findByName("android").
    Do not manually attach build/outputs/aar/<module>-release.aar unless there is a documented reason. A component-backed publication is required so Gradle can generate correct metadata.

  3. For JVM modules, apply maven-publish and publish from the Java component:

    publishing {
        publications {
            create<MavenPublication>("maven") {
                from(components["java"])
                artifactId = project.name
            }
        }
    }

    Add sources JAR support for JVM modules as well.

  4. Use stable artifact IDs:

    • :api -> api
    • :android-assets -> android-assets
    • :runtime -> runtime
    • :annotations -> annotations
    • :serialization-gson -> serialization-gson
    • :serialization-kotlinx -> serialization-kotlinx
    • :codegen-core -> codegen-core, if published
    • :library -> either retrofit-graphql for backward compatibility, or library only if intentionally breaking old coordinates
  5. Keep :library as a backward-compatible facade for now.

    Its POM must declare transitive dependencies on the real module artifacts. Consumers of the old aggregate coordinate should still work, but new consumers must be able to depend directly on individual modules.

  6. Update documentation if coordinates change or become available:

    • README.md
    • MIGRATION.md
    • AGENTS.md
    • .agents/skills/retrofit-graphql-build-dependencies/references/build-map.md

Test Strategy

The fix is not complete until it is verified both locally and via JitPack.

1. Clean Local Maven State

Run from the repository root:

rm -rf ~/.m2/repository/co/anitrend/retrofit-graphql
rm -rf ~/.m2/repository/com/github/AniTrend/retrofit-graphql
rm -rf ~/.m2/repository/com/github/anitrend/retrofit-graphql
./gradlew clean --no-daemon

2. Inspect Available Publish Tasks

Run:

./gradlew tasks --all | grep -i publish

Expected result:

There should be publish tasks for each intended module, not only :library.

Examples of acceptable task names depend on the chosen publication name, but the output should include module-specific tasks for:

  • :api
  • :android-assets
  • :runtime
  • :annotations
  • :serialization-gson
  • :serialization-kotlinx
  • :library
  • :codegen-core, if published

3. Run the Same Task JitPack Runs

Run:

./gradlew build --stacktrace --no-daemon -x lint -x lintVitalRelease
./gradlew publishMavenPublicationToMavenLocal --stacktrace --no-daemon

If the publication names change from maven, either keep a compatible aggregate task or update jitpack.yml accordingly. JitPack must invoke a task that publishes every intended artifact to Maven local.

4. Verify Files Exist in Maven Local

Check local Maven output. Depending on final group ID, inspect the actual generated path under ~/.m2/repository.

At minimum, each published module must have:

  • .pom
  • .module metadata, unless intentionally disabled
  • .aar for Android modules
  • .jar for JVM modules
  • -sources.jar

Suggested verification:

find ~/.m2/repository -path '*retrofit-graphql*' -type f | sort

Then verify the expected module artifacts exist. Example patterns:

find ~/.m2/repository -path '*runtime*' -name '*.pom' -o -path '*runtime*' -name '*.aar'
find ~/.m2/repository -path '*api*' -name '*.pom' -o -path '*api*' -name '*.aar'
find ~/.m2/repository -path '*android-assets*' -name '*.pom' -o -path '*android-assets*' -name '*.aar'
find ~/.m2/repository -path '*annotations*' -name '*.pom' -o -path '*annotations*' -name '*.jar'

Do not rely only on the Gradle task succeeding. The generated files must be inspected.

5. Verify POM Dependencies

Inspect generated POMs directly.

Examples:

cat ~/.m2/repository/**/runtime/**/*.pom
cat ~/.m2/repository/**/library/**/*.pom
cat ~/.m2/repository/**/retrofit-graphql/**/*.pom

Required checks:

  • :runtime POM declares dependencies on api, android-assets, and annotations where appropriate.
  • :android-assets POM declares dependency on annotations.
  • :serialization-gson and :serialization-kotlinx POMs declare dependency on api.
  • :library facade POM declares dependencies on all modules it re-exports.
  • Dependency scopes are sensible. API-facing dependencies should not be accidentally hidden as runtime-only if consumers need them at compile time.

6. Verify Artifact Contents

Inspect archives, not just filenames.

For Android AARs:

unzip -l ~/.m2/repository/**/runtime/**/*.aar | head -100
unzip -l ~/.m2/repository/**/api/**/*.aar | head -100
unzip -l ~/.m2/repository/**/android-assets/**/*.aar | head -100

For JVM JARs:

jar tf ~/.m2/repository/**/annotations/**/*.jar | head -100
jar tf ~/.m2/repository/**/codegen-core/**/*.jar | head -100

Required checks:

  • runtime contains converter implementation classes.
  • api contains public API/model contracts.
  • android-assets contains asset discovery/processor implementation classes.
  • annotations contains GraphQuery annotation classes.
  • library may be thin, but if it remains a facade its metadata must be correct.

7. Add a Throwaway Consumer Verification

Create a temporary consumer project outside the repo, or use a detached Gradle init project, and resolve the local Maven artifacts.

Example:

repositories {
    google()
    mavenCentral()
    mavenLocal()
}

dependencies {
    implementation("<resolved-group>:runtime:<resolved-version>")
    implementation("<resolved-group>:api:<resolved-version>")
    implementation("<resolved-group>:android-assets:<resolved-version>")
    implementation("<resolved-group>:annotations:<resolved-version>")
}

Then run:

./gradlew dependencies --configuration debugRuntimeClasspath
./gradlew compileDebugKotlin

Required checks:

  • Dependencies resolve from mavenLocal().
  • Kotlin imports compile against direct module artifacts.
  • No dependency on the deprecated :library facade is required for modular usage.

8. Verify JitPack After Merge

After merge, trigger or wait for JitPack to build the target commit/tag.

Then verify module URLs resolve with HTTP 200 instead of 404. Example shape:

curl -I https://jitpack.io/com/github/AniTrend/retrofit-graphql/runtime/<sha-or-tag>/runtime-<sha-or-tag>.pom
curl -I https://jitpack.io/com/github/AniTrend/retrofit-graphql/api/<sha-or-tag>/api-<sha-or-tag>.pom
curl -I https://jitpack.io/com/github/AniTrend/retrofit-graphql/android-assets/<sha-or-tag>/android-assets-<sha-or-tag>.pom
curl -I https://jitpack.io/com/github/AniTrend/retrofit-graphql/annotations/<sha-or-tag>/annotations-<sha-or-tag>.pom

If JitPack normalizes owner/repo casing, adjust the URL casing and document the working form in the PR.

Also verify in a real consumer Gradle build using JitPack repository:

repositories {
    google()
    mavenCentral()
    maven("https://jitpack.io")
}

Required checks:

  • Direct modular dependencies resolve.
  • Old facade coordinate still resolves if backward compatibility is retained.
  • No 404 for published modules.
  • No empty-root-only artifact behavior.

Acceptance Criteria

  • JitPack build task publishes all intended modules, not only :library.
  • Android modules publish component-backed release AARs.
  • JVM modules publish JARs with sources.
  • :library remains a valid backward-compatible facade or its removal is explicitly documented as a breaking change.
  • Generated POMs include correct transitive dependencies.
  • Generated artifacts contain implementation classes, not only stubs/facade aliases.
  • Local publishMavenPublicationToMavenLocal output has been inspected and documented in the PR.
  • A throwaway consumer build resolves the locally published artifacts from mavenLocal().
  • JitPack module URLs resolve after merge/tag.
  • README/MIGRATION/agent build docs are updated to reflect the final supported coordinates.

References

  • JitPack multi-module convention: com.github.USER.REPO:MODULE:VERSION, where MODULE is the artifact ID.
  • Android Gradle Plugin publishing API: use android.publishing.singleVariant(...) and publish from the generated component, e.g. components["release"].
  • Repository canonical build guidance: AGENTS.md and .agents/skills/retrofit-graphql-build-dependencies/references/build-map.md.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions