Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support other extensions for published image #170

Merged
merged 13 commits into from
Jan 16, 2024
Merged
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ When applied, the plugin creates the following tasks:
- `bufFormatApply` applies [`buf format`](https://buf.build/docs/format/style/)
- `bufFormatCheck` validates [`buf format`](https://buf.build/docs/format/style/)
- `bufLint` validates [`buf lint`](https://buf.build/docs/breaking/overview/)
- `bufBuild` builds an image with [`buf build`](https://buf.build/docs/reference/cli/buf/build)
- `bufBreaking` checks Protobuf schemas against a previous version for backwards-incompatible changes through [`buf breaking`](https://buf.build/docs/breaking/overview/)
- `bufGenerate` generates Protobuf code with [`buf generate`](https://buf.build/docs/generate/overview/)

Expand Down Expand Up @@ -203,6 +204,34 @@ buf {

`bufLint` is configured by creating `buf.yaml` in basic projects or projects using the `protobuf-gradle-plugin`. It is run automatically during the `check` task. Specification of `buf.yaml` is not supported for projects using a workspace.

### `bufBuild`

`bufBuild` is configured with the `build` closure:

```kotlin
buf {
build {
imageFormat = ImageFormat.JSON // JSON by default
compressionFormat = CompressionFormat.GZ // null by default (no compression)
}
}
```

Available image formats are:

- `binpb`
- `bin`
- `json`
- `txtpb`

Available compression formats are:

- `gz`
- `zst`

The file is built in the `bufbuild` directory in the project's build directory and has the name `image` followed by
the image format and optionally the compression format, e.g. `build/bufbuild/image.bin.zst`.

### `bufBreaking`

`bufBreaking` is more complicated since it requires a previous version of the Protobuf schema to validate the current version. Buf's built-in git integration isn't quite enough since it requires a buildable Protobuf source set, and the `protobuf-gradle-plugin`'s extraction step typically targets the project build directory which is ephemeral and is not committed.
Expand Down
36 changes: 36 additions & 0 deletions src/main/kotlin/build/buf/gradle/BufExtension.kt
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,15 @@ open class BufExtension {
*/
var toolVersion = "1.27.1"

internal var buildDetails: BuildDetails? = null

/**
* Specify the build details for image generation.
*/
fun build(configure: Action<BuildDetails>) {
buildDetails = (buildDetails ?: BuildDetails()).apply(configure::execute)
}

internal var imageArtifactDetails: ArtifactDetails? = null

/**
Expand All @@ -70,6 +79,33 @@ open class BufExtension {
}
}

enum class ImageFormat(
internal val formatName: String,
) {
BINPB("binpb"),
BIN("bin"),
JSON("json"),
TXTPB("txtpb"),
}

enum class CompressionFormat(
internal val ext: String,
) {
GZ("gz"),
ZST("zst"),
}

class BuildDetails(
/**
* The format of the built image.
*/
var imageFormat: ImageFormat = ImageFormat.JSON,
/**
* The compression, if any, of the built image.
*/
var compressionFormat: CompressionFormat? = null,
)

class ArtifactDetails(
var groupId: String? = null,
var artifactId: String? = null,
Expand Down
13 changes: 10 additions & 3 deletions src/main/kotlin/build/buf/gradle/BuildConfiguration.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import org.gradle.language.base.plugins.LifecycleBasePlugin.BUILD_GROUP
import java.io.File

const val BUF_BUILD_TASK_NAME = "bufBuild"
const val BUF_BUILD_PUBLICATION_FILE_NAME = "image.json"
private const val BUF_BUILD_PUBLICATION_FILE_BASE_NAME = "image"
const val BUF_IMAGE_PUBLICATION_NAME = "bufImagePublication"

internal fun Project.configureBuild() {
Expand Down Expand Up @@ -52,13 +52,20 @@ internal fun Project.configureImagePublication(artifactDetails: ArtifactDetails)

artifact(bufBuildPublicationFile) {
builtBy(tasks.named(BUF_BUILD_TASK_NAME))
extension = bufBuildPublicationFileExtension
}
}
}
}

internal val Project.bufBuildPublicationFile
get() = File(bufbuildDir, BUF_BUILD_PUBLICATION_FILE_NAME)
private val Project.bufBuildPublicationFileExtension
get() =
(getExtension().buildDetails ?: BuildDetails()).let { deets ->
deets.imageFormat.formatName + deets.compressionFormat?.let { ".${it.ext}" }.orEmpty()
}

private val Project.bufBuildPublicationFile
get() = File(bufbuildDir, "$BUF_BUILD_PUBLICATION_FILE_BASE_NAME.$bufBuildPublicationFileExtension")

internal val Task.bufBuildPublicationFile
get() = project.bufBuildPublicationFile
14 changes: 14 additions & 0 deletions src/test/kotlin/build/buf/gradle/AbstractBreakingTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,12 @@

package build.buf.gradle

import build.buf.gradle.ImageGenerationSupport.replaceBuildDetails
import com.google.common.truth.Truth.assertThat
import org.gradle.testkit.runner.TaskOutcome.FAILED
import org.junit.jupiter.api.Test
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.MethodSource
import java.nio.file.Path

abstract class AbstractBreakingTest : AbstractBufIntegrationTest() {
Expand All @@ -32,6 +35,17 @@ abstract class AbstractBreakingTest : AbstractBufIntegrationTest() {
checkBreaking()
}

@ParameterizedTest
@MethodSource("build.buf.gradle.ImageGenerationSupport#publicationFileExtensionTestCase")
fun `breaking schema with specified publication file extension`(
format: String,
compression: String?,
) {
replaceBuildDetails(format, compression)
publishRunner().build()
checkBreaking()
}

@Test
fun `normally breaking schema with an ignore`() {
publishRunner().build()
Expand Down
24 changes: 19 additions & 5 deletions src/test/kotlin/build/buf/gradle/AbstractBuildTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,24 @@

package build.buf.gradle

import build.buf.gradle.ImageGenerationSupport.replaceBuildDetails
import com.google.common.truth.Truth.assertThat
import org.gradle.testkit.runner.TaskOutcome.SUCCESS
import org.junit.jupiter.api.Test
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.MethodSource
import java.io.File
import java.nio.file.Paths

abstract class AbstractBuildTest : AbstractBufIntegrationTest() {
@Test
fun `build image with explicit artifact details`() {
assertImageGeneration()
assertImageGeneration("image.json")
}

@Test
fun `build image with inferred artifact details`() {
assertImageGeneration()
assertImageGeneration("image.json")
}

@Test
Expand All @@ -38,6 +41,17 @@ abstract class AbstractBuildTest : AbstractBufIntegrationTest() {
assertThat(result.output).contains("found 0")
}

@ParameterizedTest
@MethodSource("build.buf.gradle.ImageGenerationSupport#publicationFileExtensionTestCase")
fun `build image with specified publication file extension`(
format: String,
compression: String?,
) {
replaceBuildDetails(format, compression)
val extension = format + (compression?.let { ".$compression" } ?: "")
assertImageGeneration("image.$extension")
}

@Test
fun `build image with two publications should fail`() {
val result = buildRunner().buildAndFail()
Expand All @@ -47,7 +61,7 @@ abstract class AbstractBuildTest : AbstractBufIntegrationTest() {

@Test
fun `build image with two publications should succeed if details are provided explicitly`() {
assertImageGeneration()
assertImageGeneration("image.json")
}

@Test
Expand All @@ -60,9 +74,9 @@ abstract class AbstractBuildTest : AbstractBufIntegrationTest() {
)
}

private fun assertImageGeneration() {
private fun assertImageGeneration(publicationFileName: String) {
assertThat(buildRunner().build().task(":$BUF_BUILD_TASK_NAME")?.outcome).isEqualTo(SUCCESS)
val image = Paths.get(projectDir.path, "build", "bufbuild", "image.json").toFile().readText()
val image = Paths.get(projectDir.path, "build", "bufbuild", publicationFileName).toFile().readText()
assertThat(image).isNotEmpty()
}

Expand Down
31 changes: 31 additions & 0 deletions src/test/kotlin/build/buf/gradle/ImageGenerationSupport.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package build.buf.gradle

import com.google.common.collect.Lists
import org.junit.jupiter.params.provider.Arguments

private val NULL_SENTINEL = Any()

object ImageGenerationSupport {
@JvmStatic
fun publicationFileExtensionTestCase() =
Lists.cartesianProduct(
ImageFormat.values().map { it.formatName },
CompressionFormat.values().map { it.ext } + NULL_SENTINEL,
).map { imageAndCompression ->
Arguments.of(imageAndCompression[0], imageAndCompression[1].takeIf { it != NULL_SENTINEL })
}

fun AbstractBufIntegrationTest.replaceBuildDetails(
format: String,
compression: String?,
) {
buildFile.replace(
"imageFormat = REPLACEME",
"imageFormat = '$format'",
)
buildFile.replace(
"compressionFormat = REPLACEME",
"compressionFormat = ${compression?.let { "'$it'" }}",
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Copyright 2023 Buf Technologies, Inc.
//
// Licensed 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.

syntax = "proto3";

package buf.test.v1;

message BasicMessage {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
plugins {
id 'java'
id 'build.buf'
id 'maven-publish'
}

repositories {
mavenCentral()
maven { url 'build/repos/test' }
}

publishing {
repositories {
maven { url 'build/repos/test' }
}
}

buf {
publishSchema = true
//previousVersion = '2319'

build {
imageFormat = REPLACEME
compressionFormat = REPLACEME
}

imageArtifact {
groupId = 'foo'
artifactId = 'bar'
version = '2319'
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
plugins {
id 'java'
id 'com.google.protobuf' version "$protobufGradleVersion"
id 'build.buf'
id 'maven-publish'
}

repositories {
mavenCentral()
maven { url 'build/repos/test' }
}

protobuf {
protoc {
artifact = "com.google.protobuf:protoc:$protobufVersion"
}
}

compileJava.enabled = false

publishing {
repositories {
maven { url 'build/repos/test' }
}
}

buf {
publishSchema = true
//previousVersion = '2319'

build {
imageFormat = REPLACEME
compressionFormat = REPLACEME
}

imageArtifact {
groupId = 'foo'
artifactId = 'bar'
version = '2319'
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Copyright 2023 Buf Technologies, Inc.
//
// Licensed 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.

syntax = "proto3";

package buf.test.v1;

message BasicMessage {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
version: v1
directories:
- workspace
Loading