Skip to content

Commit

Permalink
Gradle 8 support (#28756)
Browse files Browse the repository at this point in the history
* Gradle 8 support

* Make gradle help --scan green

* Replicate defunct com.palantir.docker and docker-run plugins

* Fixes

* bump errorprone plugin

* Fix copySdkHarnessLauncher dependency

* Link to original plugin src
  • Loading branch information
Abacn committed Oct 5, 2023
1 parent 6f4e285 commit 161cd6b
Show file tree
Hide file tree
Showing 11 changed files with 511 additions and 37 deletions.
17 changes: 8 additions & 9 deletions buildSrc/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -44,20 +44,19 @@ dependencies {
implementation("gradle.plugin.com.github.johnrengelman:shadow:7.1.1")
implementation("com.github.spotbugs.snom:spotbugs-gradle-plugin:5.0.14")

runtimeOnly("com.google.protobuf:protobuf-gradle-plugin:0.8.13") // Enable proto code generation
runtimeOnly("com.github.davidmc24.gradle-avro-plugin:gradle-avro-plugin:0.16.0") // Enable Avro code generation
runtimeOnly("com.diffplug.spotless:spotless-plugin-gradle:5.6.1") // Enable a code formatting plugin
runtimeOnly("com.palantir.gradle.docker:gradle-docker:0.34.0") // Enable building Docker containers
runtimeOnly("gradle.plugin.com.dorongold.plugins:task-tree:1.5") // Adds a 'taskTree' task to print task dependency tree
runtimeOnly("gradle.plugin.com.github.johnrengelman:shadow:7.1.1") // Enable shading Java dependencies
runtimeOnly("com.google.protobuf:protobuf-gradle-plugin:0.8.13") // Enable proto code generation
runtimeOnly("com.github.davidmc24.gradle-avro-plugin:gradle-avro-plugin:0.16.0") // Enable Avro code generation
runtimeOnly("com.diffplug.spotless:spotless-plugin-gradle:5.6.1") // Enable a code formatting plugin
runtimeOnly("gradle.plugin.com.dorongold.plugins:task-tree:1.5") // Adds a 'taskTree' task to print task dependency tree
runtimeOnly("gradle.plugin.com.github.johnrengelman:shadow:7.1.1") // Enable shading Java dependencies
runtimeOnly("net.linguica.gradle:maven-settings-plugin:0.5")
runtimeOnly("gradle.plugin.io.pry.gradle.offline_dependencies:gradle-offline-dependencies-plugin:0.5.0") // Enable creating an offline repository
runtimeOnly("net.ltgt.gradle:gradle-errorprone-plugin:1.2.1") // Enable errorprone Java static analysis
runtimeOnly("net.ltgt.gradle:gradle-errorprone-plugin:3.1.0") // Enable errorprone Java static analysis
runtimeOnly("org.ajoberstar.grgit:grgit-gradle:4.1.1") // Enable website git publish to asf-site branch
runtimeOnly("com.avast.gradle:gradle-docker-compose-plugin:0.16.12") // Enable docker compose tasks
runtimeOnly("com.avast.gradle:gradle-docker-compose-plugin:0.16.12") // Enable docker compose tasks
runtimeOnly("ca.cutterslade.gradle:gradle-dependency-analyze:1.8.3") // Enable dep analysis
runtimeOnly("gradle.plugin.net.ossindex:ossindex-gradle-plugin:0.4.11") // Enable dep vulnerability analysis
runtimeOnly("org.checkerframework:checkerframework-gradle-plugin:0.6.33") // Enable enhanced static checking plugin
runtimeOnly("org.checkerframework:checkerframework-gradle-plugin:0.6.33") // Enable enhanced static checking plugin
}

// Because buildSrc is built and tested automatically _before_ gradle
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,325 @@
/*
* 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 java.util.regex.Pattern
import org.gradle.api.GradleException
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.file.CopySpec
import org.gradle.api.logging.LogLevel
import org.gradle.api.logging.Logger
import org.gradle.api.logging.Logging
import org.gradle.api.tasks.Copy
import org.gradle.api.tasks.Delete
import org.gradle.api.tasks.Exec

/**
* A gradle plug-in interacting with docker. Originally replicated from
* <a href="https://github.com/palantir/gradle-docker">com.palantir.docker</a> plugin.
*/
class BeamDockerPlugin implements Plugin<Project> {
private static final Logger logger = Logging.getLogger(BeamDockerPlugin.class)
private static final Pattern LABEL_KEY_PATTERN = Pattern.compile('^[a-z0-9.-]*$')

static class DockerExtension {
Project project

private static final String DEFAULT_DOCKERFILE_PATH = 'Dockerfile'
String name = null
File dockerfile = null
String dockerComposeTemplate = 'docker-compose.yml.template'
String dockerComposeFile = 'docker-compose.yml'
Set<Task> dependencies = [] as Set
Set<String> tags = [] as Set
Map<String, String> namedTags = [:]
Map<String, String> labels = [:]
Map<String, String> buildArgs = [:]
boolean pull = false
boolean noCache = false
String network = null
boolean buildx = false
Set<String> platform = [] as Set
boolean load = false
boolean push = false
String builder = null

File resolvedDockerfile = null
File resolvedDockerComposeTemplate = null
File resolvedDockerComposeFile = null

// The CopySpec defining the Docker Build Context files
final CopySpec copySpec

DockerExtension(Project project) {
this.project = project
this.copySpec = project.copySpec()
}

void resolvePathsAndValidate() {
if (dockerfile != null) {
resolvedDockerfile = dockerfile
} else {
resolvedDockerfile = project.file(DEFAULT_DOCKERFILE_PATH)
}
resolvedDockerComposeFile = project.file(dockerComposeFile)
resolvedDockerComposeTemplate = project.file(dockerComposeTemplate)
}

void dependsOn(Task... args) {
this.dependencies = args as Set
}

Set<Task> getDependencies() {
return dependencies
}

void files(Object... files) {
copySpec.from(files)
}

void tags(String... args) {
this.tags = args as Set
}

Set<String> getTags() {
return this.tags + project.getVersion().toString()
}

Set<String> getPlatform() {
return platform
}

void platform(String... args) {
this.platform = args as Set
}
}

@Override
void apply(Project project) {
DockerExtension ext = project.extensions.create('docker', DockerExtension, project)

Delete clean = project.tasks.create('dockerClean', Delete, {
group = 'Docker'
description = 'Cleans Docker build directory.'
})

Copy prepare = project.tasks.create('dockerPrepare', Copy, {
group = 'Docker'
description = 'Prepares Docker build directory.'
dependsOn clean
})

Exec exec = project.tasks.create('docker', Exec, {
group = 'Docker'
description = 'Builds Docker image.'
dependsOn prepare
})

Task tag = project.tasks.create('dockerTag', {
group = 'Docker'
description = 'Applies all tags to the Docker image.'
dependsOn exec
})

Task pushAllTags = project.tasks.create('dockerTagsPush', {
group = 'Docker'
description = 'Pushes all tagged Docker images to configured Docker Hub.'
})

project.tasks.create('dockerPush', {
group = 'Docker'
description = 'Pushes named Docker image to configured Docker Hub.'
dependsOn pushAllTags
})

project.afterEvaluate {
ext.resolvePathsAndValidate()
String dockerDir = "${project.buildDir}/docker"
clean.delete dockerDir

prepare.with {
with ext.copySpec
from(ext.resolvedDockerfile) {
rename { fileName ->
fileName.replace(ext.resolvedDockerfile.getName(), 'Dockerfile')
}
}
into dockerDir
}

exec.with {
workingDir dockerDir
commandLine buildCommandLine(ext)
dependsOn ext.getDependencies()
logging.captureStandardOutput LogLevel.INFO
logging.captureStandardError LogLevel.ERROR
}

Map<String, Object> tags = ext.namedTags.collectEntries { taskName, tagName ->
[
generateTagTaskName(taskName),
[
tagName: tagName,
tagTask: {
-> tagName }
]
]
}

if (!ext.tags.isEmpty()) {
ext.tags.each { unresolvedTagName ->
String taskName = generateTagTaskName(unresolvedTagName)

if (tags.containsKey(taskName)) {
throw new IllegalArgumentException("Task name '${taskName}' is existed.")
}

tags[taskName] = [
tagName: unresolvedTagName,
tagTask: {
-> computeName(ext.name, unresolvedTagName) }
]
}
}

tags.each { taskName, tagConfig ->
Exec tagSubTask = project.tasks.create('dockerTag' + taskName, Exec, {
group = 'Docker'
description = "Tags Docker image with tag '${tagConfig.tagName}'"
workingDir dockerDir
commandLine 'docker', 'tag', "${-> ext.name}", "${-> tagConfig.tagTask()}"
dependsOn exec
})
tag.dependsOn tagSubTask

Exec pushSubTask = project.tasks.create('dockerPush' + taskName, Exec, {
group = 'Docker'
description = "Pushes the Docker image with tag '${tagConfig.tagName}' to configured Docker Hub"
workingDir dockerDir
commandLine 'docker', 'push', "${-> tagConfig.tagTask()}"
dependsOn tagSubTask
})
pushAllTags.dependsOn pushSubTask
}
}
}

private List<String> buildCommandLine(DockerExtension ext) {
List<String> buildCommandLine = ['docker']
if (ext.buildx) {
buildCommandLine.addAll(['buildx', 'build'])
if (!ext.platform.isEmpty()) {
buildCommandLine.addAll('--platform', String.join(',', ext.platform))
}
if (ext.load) {
buildCommandLine.add '--load'
}
if (ext.push) {
buildCommandLine.add '--push'
if (ext.load) {
throw new Exception("cannot combine 'push' and 'load' options")
}
}
if (ext.builder != null) {
buildCommandLine.addAll('--builder', ext.builder)
}
} else {
buildCommandLine.add 'build'
}
if (ext.noCache) {
buildCommandLine.add '--no-cache'
}
if (ext.getNetwork() != null) {
buildCommandLine.addAll('--network', ext.network)
}
if (!ext.buildArgs.isEmpty()) {
for (Map.Entry<String, String> buildArg : ext.buildArgs.entrySet()) {
buildCommandLine.addAll('--build-arg', "${buildArg.getKey()}=${buildArg.getValue()}" as String)
}
}
if (!ext.labels.isEmpty()) {
for (Map.Entry<String, String> label : ext.labels.entrySet()) {
if (!label.getKey().matches(LABEL_KEY_PATTERN)) {
throw new GradleException(String.format("Docker label '%s' contains illegal characters. " +
"Label keys must only contain lowercase alphanumberic, `.`, or `-` characters (must match %s).",
label.getKey(), LABEL_KEY_PATTERN.pattern()))
}
buildCommandLine.addAll('--label', "${label.getKey()}=${label.getValue()}" as String)
}
}
if (ext.pull) {
buildCommandLine.add '--pull'
}
buildCommandLine.addAll(['-t', "${-> ext.name}", '.'])
logger.debug("${buildCommandLine}" as String)
return buildCommandLine
}

private static String computeName(String name, String tag) {
int firstAt = tag.indexOf("@")

String tagValue
if (firstAt > 0) {
tagValue = tag.substring(firstAt + 1, tag.length())
} else {
tagValue = tag
}

if (tagValue.contains(':') || tagValue.contains('/')) {
// tag with ':' or '/' -> force use the tag value
return tagValue
} else {
// tag without ':' and '/' -> replace the tag part of original name
int lastColon = name.lastIndexOf(':')
int lastSlash = name.lastIndexOf('/')

int endIndex;

// image_name -> this should remain
// host:port/image_name -> this should remain.
// host:port/image_name:v1 -> v1 should be replaced
if (lastColon > lastSlash) endIndex = lastColon
else endIndex = name.length()

return name.substring(0, endIndex) + ":" + tagValue
}
}

private static String generateTagTaskName(String name) {
String tagTaskName = name
int firstAt = name.indexOf("@")

if (firstAt > 0) {
// Get substring of task name
tagTaskName = name.substring(0, firstAt)
} else if (firstAt == 0) {
// Task name must not be empty
throw new GradleException("Task name of docker tag '${name}' must not be empty.")
} else if (name.contains(':') || name.contains('/')) {
// Tags which with repo or name must have a task name
throw new GradleException("Docker tag '${name}' must have a task name.")
}

StringBuffer sb = new StringBuffer(tagTaskName)
// Uppercase the first letter of task name
sb.replace(0, 1, tagTaskName.substring(0, 1).toUpperCase());
return sb.toString()
}
}
Loading

0 comments on commit 161cd6b

Please sign in to comment.