22 Maven-Publish: Konfiguration von Artefakten aus Unterprojekten

22.1 Grundlagen der Maven-Publication

Das Maven-Publish-Plugin transformiert Gradle-Projekte in publizierbare Maven-Artefakte. In Multi-Projekt-Umgebungen entstehen dabei mehrere zusammenhängende Artefakte, die koordiniert versioniert und publiziert werden müssen. Jedes Subprojekt kann eigene Artefakte generieren, die als separate Maven-Module in Repositories landen. Diese Artefakte folgen Maven-Konventionen mit Group-ID, Artifact-ID und Version, wodurch sie von anderen Build-Tools konsumierbar werden.

Die Publication-Konfiguration definiert, welche Artefakte publiziert werden und welche Metadaten sie enthalten. Ein typisches Java-Library-Projekt publiziert JAR-Dateien mit kompiliertem Code, Source-JARs für IDE-Integration und JavaDoc-JARs für Dokumentation. Das generierte POM-File beschreibt das Modul und seine Abhängigkeiten im Maven-Format. Diese Metadaten ermöglichen transitives Dependency-Management für Konsumenten der Artefakte.

Multi-Projekt-Builds erfordern besondere Überlegungen bei der Publication-Strategie. Die Artefakte können einzeln publiziert werden, was Flexibilität bietet aber Versions-Inkonsistenzen riskiert. Alternativ erfolgt eine koordinierte Publication aller Module mit derselben Version, was Konsistenz garantiert aber weniger granulare Updates ermöglicht. Die Wahl hängt von der Release-Strategie und der Coupling-Stärke zwischen Modulen ab.

22.2 Konfiguration von Publications in Subprojekten

Die Publication-Konfiguration erfolgt in jedem Subprojekt, das Artefakte publizieren soll. Das publishing-Block definiert Publications und Repositories. Eine Publication spezifiziert die zu publizierenden Artefakte und deren Metadaten. Die from-Methode verbindet die Publication mit einer Software-Component, typischerweise components.java für Java-Libraries.

// In jedem publizierenden Subprojekt
plugins {
    `java-library`
    `maven-publish`
}

publishing {
    publications {
        create<MavenPublication>("maven") {
            from(components["java"])
            
            // Artifact-Metadaten
            artifactId = project.name
            
            // Zusätzliche Artefakte
            artifact(tasks.register<Jar>("sourcesJar") {
                from(sourceSets.main.get().allJava)
                archiveClassifier.set("sources")
            })
            
            // POM-Anpassungen
            pom {
                name.set("${project.group}:${project.name}")
                description.set("Komponente ${project.name} des Systems")
                url.set("https://github.com/company/project")
                
                licenses {
                    license {
                        name.set("Apache License 2.0")
                        url.set("http://www.apache.org/licenses/LICENSE-2.0")
                    }
                }
                
                developers {
                    developer {
                        id.set("team")
                        name.set("Development Team")
                        email.set("dev@company.com")
                    }
                }
            }
        }
    }
}

Die Zentralisierung gemeinsamer Publication-Konfiguration reduziert Redundanz. Ein Convention-Plugin im buildSrc definiert Standard-Publications, die von allen Subprojekten verwendet werden. Projekt-spezifische Anpassungen überschreiben oder erweitern diese Standards. Diese Struktur garantiert konsistente Metadaten über alle publizierten Artefakte.

22.3 Dependency-Management zwischen publizierten Modulen

Inter-Modul-Dependencies in Multi-Projekt-Builds werden zu Maven-Dependencies in publizierten POMs transformiert. Gradle mappt project()-Dependencies automatisch zu Maven-Koordinaten basierend auf Group und Artifact-ID der referenzierten Module. Diese Transformation preserviert die Dependency-Struktur für externe Konsumenten.

Die Dependency-Scope-Zuordnung folgt klaren Regeln. Gradle’s implementation-Dependencies werden zu Maven’s runtime-Scope, während api-Dependencies zu compile-Scope werden. Test-Dependencies landen im test-Scope. Diese Mappings können durch explizite Konfiguration überschrieben werden, wenn Maven-Konsumenten spezielle Scope-Requirements haben.

dependencies {
    api(project(":core"))                    // -> compile scope
    implementation(project(":commons"))      // -> runtime scope
    testImplementation(project(":test-utils")) // -> test scope
}

// Explizite Scope-Kontrolle
publishing {
    publications {
        create<MavenPublication>("maven") {
            from(components["java"])
            
            pom.withXml {
                val dependenciesNode = asNode().appendNode("dependencies")
                
                configurations["implementation"].allDependencies.forEach {
                    if (it.group != null) {
                        val dependencyNode = dependenciesNode.appendNode("dependency")
                        dependencyNode.appendNode("groupId", it.group)
                        dependencyNode.appendNode("artifactId", it.name)
                        dependencyNode.appendNode("version", it.version)
                        dependencyNode.appendNode("scope", "provided")  // Custom scope
                    }
                }
            }
        }
    }
}

Platform- und BOM-Publications koordinieren Versions-Management über Module. Ein dediziertes Platform-Modul definiert Dependency-Constraints für alle Module des Multi-Projekt-Builds. Konsumenten können diese Platform importieren und erhalten automatisch kompatible Versionen aller Module. Diese Strategie entspricht dem Spring-Boot-BOM-Pattern und vereinfacht Dependency-Management für Nutzer.

22.4 Version-Koordination und Release-Management

Die Versionierung in Multi-Projekt-Builds folgt verschiedenen Strategien. Monolithische Versionierung verwendet dieselbe Version für alle Module, vereinfacht durch eine zentrale Definition im Root-Projekt. Diese Strategie garantiert Kompatibilität zwischen Modulen und vereinfacht Release-Management. Der Nachteil liegt in unnötigen Versions-Updates für unveränderte Module.

// Root build.gradle.kts
allprojects {
    group = "com.company.system"
    version = "2.3.0"
}

// Alternativ: Aus external source
val systemVersion: String by project  // from gradle.properties
allprojects {
    version = systemVersion
}

Independent Module Versioning erlaubt unterschiedliche Versions-Nummern pro Modul. Diese Strategie ermöglicht granulare Updates und semantic versioning pro Modul. Die Komplexität liegt in der Verwaltung kompatibler Versions-Kombinationen. Eine Compatibility-Matrix dokumentiert, welche Modul-Versionen zusammen funktionieren. Automated Testing verifiziert diese Kombinationen.

Snapshot- und Release-Versionen erfordern unterschiedliche Publication-Strategien. Snapshot-Versionen werden zu Snapshot-Repositories publiziert und bei jedem Build überschrieben. Release-Versionen sind immutable und werden zu Release-Repositories publiziert. Das Build-Skript unterscheidet basierend auf der Version und wählt das entsprechende Repository.

22.5 Repository-Konfiguration und Credential-Management

Die Repository-Konfiguration definiert, wohin Artefakte publiziert werden. Typische Setups unterscheiden zwischen lokalen Repositories für Development, unternehmensinternen Repositories für Team-Sharing und öffentlichen Repositories für Open-Source-Projekte. Jedes Repository hat eigene URL-, Authentication- und Policy-Requirements.

publishing {
    repositories {
        maven {
            name = "corporate"
            url = uri(
                if (version.toString().endsWith("SNAPSHOT"))
                    "https://nexus.company.com/repository/maven-snapshots"
                else
                    "https://nexus.company.com/repository/maven-releases"
            )
            
            credentials {
                username = project.findProperty("nexusUsername") as String? 
                    ?: System.getenv("NEXUS_USERNAME")
                password = project.findProperty("nexusPassword") as String?
                    ?: System.getenv("NEXUS_PASSWORD")
            }
            
            // Repository-spezifische Konfiguration
            isAllowInsecureProtocol = false
            authentication {
                create<BasicAuthentication>("basic")
            }
        }
        
        // Lokales Repository für Testing
        maven {
            name = "local"
            url = uri("${buildDir}/repo")
        }
    }
}

Credential-Management erfolgt außerhalb der Build-Skripte. Sensitive Daten wie Passwörter oder API-Keys werden über Gradle-Properties, Umgebungsvariablen oder Secret-Management-Systeme bereitgestellt. In CI/CD-Pipelines kommen verschlüsselte Secrets oder temporäre Credentials zum Einsatz. Die Build-Skripte referenzieren diese Credentials ohne sie zu exponieren.

Multi-Repository-Strategien unterstützen verschiedene Deployment-Szenarien. Development-Builds publizieren zu internen Repositories, während Releases zu öffentlichen Repositories gehen. Geographic Distribution nutzt regionale Repositories für bessere Performance. Repository-Gruppen in Nexus oder Artifactory aggregieren mehrere Repositories zu einer einheitlichen Sicht.

22.6 Aggregierte Publications und Release-Bundles

Aggregierte Publications kombinieren mehrere Module zu einem Release-Bundle. Diese Bundles enthalten alle Artefakte einer Version mit koordinierten Metadaten. Ein dediziertes Distribution-Modul sammelt die Artefakte aller Subprojekte und erstellt Archive oder Container-Images für Deployment.

// distribution/build.gradle.kts
plugins {
    base
    `maven-publish`
}

val collectArtifacts by configurations.creating {
    isCanBeConsumed = false
    isCanBeResolved = true
}

dependencies {
    subprojects.filter { it.plugins.hasPlugin("maven-publish") }.forEach {
        collectArtifacts(project(it.path))
    }
}

val distributionZip = tasks.register<Zip>("distributionZip") {
    from(collectArtifacts)
    archiveBaseName.set("system-distribution")
    destinationDirectory.set(layout.buildDirectory.dir("distributions"))
}

publishing {
    publications {
        create<MavenPublication>("distribution") {
            artifact(distributionZip)
            artifactId = "system-distribution"
            
            pom {
                packaging = "zip"
                name.set("Complete System Distribution")
                
                // Referenziert alle enthaltenen Module
                withXml {
                    val deps = asNode().appendNode("dependencies")
                    subprojects.filter { it.plugins.hasPlugin("maven-publish") }.forEach {
                        val dep = deps.appendNode("dependency")
                        dep.appendNode("groupId", it.group)
                        dep.appendNode("artifactId", it.name)
                        dep.appendNode("version", it.version)
                    }
                }
            }
        }
    }
}

Bill of Materials (BOM) Publications definieren Dependency-Management ohne eigene Artefakte. Das BOM-POM enthält nur dependencyManagement-Sections mit Versions-Constraints für alle Module. Konsumenten importieren das BOM und erhalten konsistente Versionen ohne explizite Versions-Angaben. Diese Strategie reduziert Versions-Konflikte und vereinfacht Updates.

Signing und Verification sichern die Integrität publizierter Artefakte. Das Signing-Plugin generiert GPG-Signaturen für alle Artefakte. Repository-Manager verifizieren diese Signaturen und rejekten unsignierte oder falsch signierte Artefakte. Die Signing-Konfiguration unterscheidet zwischen Development-Keys für Snapshots und Production-Keys für Releases. Automated Signing in CI/CD nutzt Hardware Security Modules oder Key Management Services.