blob: 1d17560acabd33b1480ef9f15825d13008572ebf [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* License); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an AS IS BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.beam.gradle
import org.gradle.api.GradleException
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.artifacts.ProjectDependency
import org.gradle.api.file.FileTree
import org.gradle.api.publish.maven.MavenPublication
/**
* Usage:
* <ul>
* <li>The version embedded in the vendored artifactId must match exactly the version of the vendored library with
* using '_' as a replacement for special characters like '.'.
* <li>The package relocation prefix should be 'org.apache.beam.vendor.canonical_library_name.version_identifier.'.
* <li>Upgrading the version of a vendored library should trigger a change in the artifact name.
* <li>Vendored artifact versioning starts at 0.1 and is decoupled from Apache Beam releases and version numbers.
* <li>Increment the vendored artifact version only if we need to release a new version.
* </ul>
*
* <p>Example for com.google.guava:guava:26.0-jre:
* <ul>
* <li>groupId: org.apache.beam
* <li>artifactId: guava-26_0-jre
* <li>namespace: org.apache.beam.vendor.guava.v26_0_jre
* <li>version: 0.1
* </ul>
*
* <p>See <a href="https://lists.apache.org/thread.html/4c12db35b40a6d56e170cd6fc8bb0ac4c43a99aa3cb7dbae54176815@%3Cdev.beam.apache.org%3E">
* original email thread</a> for a discussion of the topic.
*/
class VendorJavaPlugin implements Plugin<Project> {
static class VendorJavaPluginConfig {
List<String> dependencies
List<String> runtimeDependencies
List<String> testDependencies
Map<String, String> relocations
List<String> exclusions
String groupId
String artifactId
String version
}
def isRelease(Project project) {
return project.hasProperty('isRelease')
}
void apply(Project project) {
// Deferred configuration so the extension is available; otherwise it
// needs to happen in a task, but not sure where to attach that
// task
project.ext.vendorJava = {
VendorJavaPluginConfig config = it ? it as VendorJavaPluginConfig : new VendorJavaPluginConfig()
project.apply plugin: 'base'
project.archivesBaseName = config.artifactId
if (!isRelease(project)) {
config.version += '-SNAPSHOT'
}
project.apply plugin: 'com.github.johnrengelman.shadow'
project.apply plugin: 'java'
// Projects should only depend on the shadowJar output
project.jar.enabled = false
project.assemble.dependsOn(project.shadowJar)
// Register all Beam repositories and configuration tweaks
Repositories.register(project)
// Apply a plugin which provides tasks for dependency / property / task reports.
// See https://docs.gradle.org/current/userguide/project_reports_plugin.html
// for further details. This is typically very useful to look at the "htmlDependencyReport"
// when attempting to resolve dependency issues.
project.apply plugin: "project-report"
project.dependencies {
config.dependencies.each { compile it }
config.runtimeDependencies.each { runtimeOnly it }
config.testDependencies.each { compileOnly it}
}
// Create a task which emulates the maven-archiver plugin in generating a
// pom.properties file.
def pomPropertiesFile = "${project.buildDir}/publications/mavenJava/pom.properties"
project.task('generatePomPropertiesFileForMavenJavaPublication') {
outputs.file "${pomPropertiesFile}"
doLast {
new File("${pomPropertiesFile}").text =
"""version=${config.version}
groupId=${project.group}
artifactId=${project.name}
"""
}
}
project.shadowJar {
config.relocations.each { srcNamespace, destNamespace ->
relocate(srcNamespace, destNamespace)
}
config.exclusions.each { exclude it }
classifier = null
mergeServiceFiles()
zip64 true
exclude "META-INF/INDEX.LIST"
exclude "META-INF/*.SF"
exclude "META-INF/*.DSA"
exclude "META-INF/*.RSA"
}
project.task('validateVendoring', dependsOn: 'shadowJar') {
inputs.files project.configurations.shadow.artifacts.files
doLast {
project.configurations.shadow.artifacts.files.each {
FileTree unexpectedlyExposedClasses = project.zipTree(it).matching {
include "**/*.class"
exclude "org/apache/beam/vendor/**"
// BEAM-5919: Exclude paths for Java 9 multi-release jars.
exclude "META-INF/versions/*/module-info.class"
exclude "META-INF/versions/*/org/apache/beam/vendor/**"
}
if (unexpectedlyExposedClasses.files) {
throw new GradleException("$it exposed classes outside of org.apache.beam namespace: ${unexpectedlyExposedClasses.files}")
}
}
}
}
project.check.dependsOn 'validateVendoring'
// Only publish vendored dependencies if specifically requested.
if (project.hasProperty("vendoredDependenciesOnly")) {
project.apply plugin: 'maven-publish'
// Ensure that we validate the contents of the jar before publishing
project.publish.dependsOn project.check
// Have the shaded jar include both the generate pom.xml and its properties file
// emulating the behavior of the maven-archiver plugin.
project.shadowJar {
def pomFile = "${project.buildDir}/publications/mavenJava/pom-default.xml"
// Validate that the artifacts exist before copying them into the jar.
doFirst {
if (!project.file("${pomFile}").exists()) {
throw new GradleException("Expected ${pomFile} to have been generated by the 'generatePomFileForMavenJavaPublication' task.")
}
if (!project.file("${pomPropertiesFile}").exists()) {
throw new GradleException("Expected ${pomPropertiesFile} to have been generated by the 'generatePomPropertiesFileForMavenJavaPublication' task.")
}
}
dependsOn 'generatePomFileForMavenJavaPublication'
into("META-INF/maven/${project.group}/${project.name}") {
from "${pomFile}"
rename('.*', 'pom.xml')
}
dependsOn project.generatePomPropertiesFileForMavenJavaPublication
into("META-INF/maven/${project.group}/${project.name}") { from "${pomPropertiesFile}" }
}
project.publishing {
repositories {
maven {
name "testPublicationLocal"
url "file://${project.rootProject.projectDir}/testPublication/"
}
maven {
url(project.properties['distMgmtSnapshotsUrl'] ?: isRelease(project)
? 'https://repository.apache.org/service/local/staging/deploy/maven2'
: 'https://repository.apache.org/content/repositories/snapshots')
name(project.properties['distMgmtServerId'] ?: isRelease(project)
? 'apache.releases.https' : 'apache.snapshots.https')
// The maven settings plugin will load credentials from ~/.m2/settings.xml file that a user
// has configured with the Apache release and snapshot staging credentials.
// <settings>
// <servers>
// <server>
// <id>apache.releases.https</id>
// <username>USER_TOKEN</username>
// <password>PASS_TOKEN</password>
// </server>
// <server>
// <id>apache.snapshots.https</id>
// <username>USER_TOKEN</username>
// <password>PASS_TOKEN</password>
// </server>
// </servers>
// </settings>
}
}
publications {
mavenJava(MavenPublication) {
groupId = config.groupId
artifactId = config.artifactId
version = config.version
artifact project.shadowJar
pom {
name = project.description
if (project.hasProperty("summary")) {
description = project.summary
}
url = "http://beam.apache.org"
inceptionYear = "2016"
licenses {
license {
name = "Apache License, Version 2.0"
url = "http://www.apache.org/licenses/LICENSE-2.0.txt"
distribution = "repo"
}
}
scm {
connection = "scm:git:https://gitbox.apache.org/repos/asf/beam.git"
developerConnection = "scm:git:https://gitbox.apache.org/repos/asf/beam.git"
url = "https://gitbox.apache.org/repos/asf?p=beam.git;a=summary"
}
issueManagement {
system = "jira"
url = "https://issues.apache.org/jira/browse/BEAM"
}
mailingLists {
mailingList {
name = "Beam Dev"
subscribe = "dev-subscribe@beam.apache.org"
unsubscribe = "dev-unsubscribe@beam.apache.org"
post = "dev@beam.apache.org"
archive = "http://www.mail-archive.com/dev%beam.apache.org"
}
mailingList {
name = "Beam User"
subscribe = "user-subscribe@beam.apache.org"
unsubscribe = "user-unsubscribe@beam.apache.org"
post = "user@beam.apache.org"
archive = "http://www.mail-archive.com/user%beam.apache.org"
}
mailingList {
name = "Beam Commits"
subscribe = "commits-subscribe@beam.apache.org"
unsubscribe = "commits-unsubscribe@beam.apache.org"
post = "commits@beam.apache.org"
archive = "http://www.mail-archive.com/commits%beam.apache.org"
}
}
developers {
developer {
name = "The Apache Beam Team"
email = "dev@beam.apache.org"
url = "http://beam.apache.org"
organization = "Apache Software Foundation"
organizationUrl = "http://www.apache.org"
}
}
}
pom.withXml {
def root = asNode()
def dependenciesNode = root.appendNode('dependencies')
def generateDependenciesFromConfiguration = { param ->
project.configurations."${param.configuration}".allDependencies.each {
def dependencyNode = dependenciesNode.appendNode('dependency')
def appendClassifier = { dep ->
dep.artifacts.each { art ->
if (art.hasProperty('classifier')) {
dependencyNode.appendNode('classifier', art.classifier)
}
}
}
if (it instanceof ProjectDependency) {
dependencyNode.appendNode('groupId', it.getDependencyProject().mavenGroupId)
dependencyNode.appendNode('artifactId', it.getDependencyProject().archivesBaseName)
dependencyNode.appendNode('version', it.version)
dependencyNode.appendNode('scope', param.scope)
appendClassifier(it)
} else {
dependencyNode.appendNode('groupId', it.group)
dependencyNode.appendNode('artifactId', it.name)
dependencyNode.appendNode('version', it.version)
dependencyNode.appendNode('scope', param.scope)
appendClassifier(it)
}
// Start with any exclusions that were added via configuration exclude rules.
// Then add all the exclusions that are specific to the dependency (if any
// were declared). Finally build the node that represents all exclusions.
def exclusions = []
exclusions += project.configurations."${param.configuration}".excludeRules
if (it.hasProperty('excludeRules')) {
exclusions += it.excludeRules
}
if (!exclusions.empty) {
def exclusionsNode = dependencyNode.appendNode('exclusions')
exclusions.each { exclude ->
def exclusionNode = exclusionsNode.appendNode('exclusion')
exclusionNode.appendNode('groupId', exclude.group)
exclusionNode.appendNode('artifactId', exclude.module)
}
}
}
}
generateDependenciesFromConfiguration(configuration: 'runtimeOnly', scope: 'runtime')
generateDependenciesFromConfiguration(configuration: 'compileOnly', scope: 'provided')
// NB: This must come after asNode() logic, as it seems asNode()
// removes XML comments.
// TODO: Load this from file?
def elem = asElement()
def hdr = elem.getOwnerDocument().createComment(
'''
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
''')
elem.insertBefore(hdr, elem.getFirstChild())
}
}
}
}
// Only sign artifacts if we are performing a release
if (isRelease(project) && !project.hasProperty('noSigning')) {
project.apply plugin: "signing"
project.signing {
useGpgCmd()
sign project.publishing.publications
}
}
}
}
}
}