Skip to content

Commit 8da0e3a

Browse files
Support other formats for published image (#170)
Co-authored-by: Andrew Parmet <[email protected]>
1 parent 7fc4efd commit 8da0e3a

File tree

20 files changed

+445
-8
lines changed

20 files changed

+445
-8
lines changed

README.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ When applied, the plugin creates the following tasks:
4545
- `bufFormatApply` applies [`buf format`](https://buf.build/docs/format/style/)
4646
- `bufFormatCheck` validates [`buf format`](https://buf.build/docs/format/style/)
4747
- `bufLint` validates [`buf lint`](https://buf.build/docs/breaking/overview/)
48+
- `bufBuild` builds an image with [`buf build`](https://buf.build/docs/reference/cli/buf/build)
4849
- `bufBreaking` checks Protobuf schemas against a previous version for backwards-incompatible changes through [`buf breaking`](https://buf.build/docs/breaking/overview/)
4950
- `bufGenerate` generates Protobuf code with [`buf generate`](https://buf.build/docs/generate/overview/)
5051

@@ -203,6 +204,34 @@ buf {
203204

204205
`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.
205206

207+
### `bufBuild`
208+
209+
`bufBuild` is configured with the `build` closure:
210+
211+
```kotlin
212+
buf {
213+
build {
214+
imageFormat = ImageFormat.JSON // JSON by default
215+
compressionFormat = CompressionFormat.GZ // null by default (no compression)
216+
}
217+
}
218+
```
219+
220+
Available image formats are:
221+
222+
- `binpb`
223+
- `bin`
224+
- `json`
225+
- `txtpb`
226+
227+
Available compression formats are:
228+
229+
- `gz`
230+
- `zst`
231+
232+
The file is built in the `bufbuild` directory in the project's build directory and has the name `image` followed by
233+
the image format and optionally the compression format, e.g. `build/bufbuild/image.bin.zst`.
234+
206235
### `bufBreaking`
207236

208237
`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.

src/main/kotlin/build/buf/gradle/BufExtension.kt

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,15 @@ open class BufExtension {
5151
*/
5252
var toolVersion = "1.27.1"
5353

54+
internal var buildDetails: BuildDetails? = null
55+
56+
/**
57+
* Specify the build details for image generation.
58+
*/
59+
fun build(configure: Action<BuildDetails>) {
60+
buildDetails = (buildDetails ?: BuildDetails()).apply(configure::execute)
61+
}
62+
5463
internal var imageArtifactDetails: ArtifactDetails? = null
5564

5665
/**
@@ -70,6 +79,33 @@ open class BufExtension {
7079
}
7180
}
7281

82+
enum class ImageFormat(
83+
internal val formatName: String,
84+
) {
85+
BINPB("binpb"),
86+
BIN("bin"),
87+
JSON("json"),
88+
TXTPB("txtpb"),
89+
}
90+
91+
enum class CompressionFormat(
92+
internal val ext: String,
93+
) {
94+
GZ("gz"),
95+
ZST("zst"),
96+
}
97+
98+
class BuildDetails(
99+
/**
100+
* The format of the built image.
101+
*/
102+
var imageFormat: ImageFormat = ImageFormat.JSON,
103+
/**
104+
* The compression, if any, of the built image.
105+
*/
106+
var compressionFormat: CompressionFormat? = null,
107+
)
108+
73109
class ArtifactDetails(
74110
var groupId: String? = null,
75111
var artifactId: String? = null,

src/main/kotlin/build/buf/gradle/BuildConfiguration.kt

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import org.gradle.language.base.plugins.LifecycleBasePlugin.BUILD_GROUP
2424
import java.io.File
2525

2626
const val BUF_BUILD_TASK_NAME = "bufBuild"
27-
const val BUF_BUILD_PUBLICATION_FILE_NAME = "image.json"
27+
private const val BUF_BUILD_PUBLICATION_FILE_BASE_NAME = "image"
2828
const val BUF_IMAGE_PUBLICATION_NAME = "bufImagePublication"
2929

3030
internal fun Project.configureBuild() {
@@ -52,13 +52,20 @@ internal fun Project.configureImagePublication(artifactDetails: ArtifactDetails)
5252

5353
artifact(bufBuildPublicationFile) {
5454
builtBy(tasks.named(BUF_BUILD_TASK_NAME))
55+
extension = bufBuildPublicationFileExtension
5556
}
5657
}
5758
}
5859
}
5960

60-
internal val Project.bufBuildPublicationFile
61-
get() = File(bufbuildDir, BUF_BUILD_PUBLICATION_FILE_NAME)
61+
private val Project.bufBuildPublicationFileExtension
62+
get() =
63+
(getExtension().buildDetails ?: BuildDetails()).let { deets ->
64+
deets.imageFormat.formatName + deets.compressionFormat?.let { ".${it.ext}" }.orEmpty()
65+
}
66+
67+
private val Project.bufBuildPublicationFile
68+
get() = File(bufbuildDir, "$BUF_BUILD_PUBLICATION_FILE_BASE_NAME.$bufBuildPublicationFileExtension")
6269

6370
internal val Task.bufBuildPublicationFile
6471
get() = project.bufBuildPublicationFile

src/test/kotlin/build/buf/gradle/AbstractBreakingTest.kt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,12 @@
1414

1515
package build.buf.gradle
1616

17+
import build.buf.gradle.ImageGenerationSupport.replaceBuildDetails
1718
import com.google.common.truth.Truth.assertThat
1819
import org.gradle.testkit.runner.TaskOutcome.FAILED
1920
import org.junit.jupiter.api.Test
21+
import org.junit.jupiter.params.ParameterizedTest
22+
import org.junit.jupiter.params.provider.MethodSource
2023
import java.nio.file.Path
2124

2225
abstract class AbstractBreakingTest : AbstractBufIntegrationTest() {
@@ -32,6 +35,17 @@ abstract class AbstractBreakingTest : AbstractBufIntegrationTest() {
3235
checkBreaking()
3336
}
3437

38+
@ParameterizedTest
39+
@MethodSource("build.buf.gradle.ImageGenerationSupport#publicationFileExtensionTestCase")
40+
fun `breaking schema with specified publication file extension`(
41+
format: String,
42+
compression: String?,
43+
) {
44+
replaceBuildDetails(format, compression)
45+
publishRunner().build()
46+
checkBreaking()
47+
}
48+
3549
@Test
3650
fun `normally breaking schema with an ignore`() {
3751
publishRunner().build()

src/test/kotlin/build/buf/gradle/AbstractBuildTest.kt

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,21 +14,24 @@
1414

1515
package build.buf.gradle
1616

17+
import build.buf.gradle.ImageGenerationSupport.replaceBuildDetails
1718
import com.google.common.truth.Truth.assertThat
1819
import org.gradle.testkit.runner.TaskOutcome.SUCCESS
1920
import org.junit.jupiter.api.Test
21+
import org.junit.jupiter.params.ParameterizedTest
22+
import org.junit.jupiter.params.provider.MethodSource
2023
import java.io.File
2124
import java.nio.file.Paths
2225

2326
abstract class AbstractBuildTest : AbstractBufIntegrationTest() {
2427
@Test
2528
fun `build image with explicit artifact details`() {
26-
assertImageGeneration()
29+
assertImageGeneration("image.json")
2730
}
2831

2932
@Test
3033
fun `build image with inferred artifact details`() {
31-
assertImageGeneration()
34+
assertImageGeneration("image.json")
3235
}
3336

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

44+
@ParameterizedTest
45+
@MethodSource("build.buf.gradle.ImageGenerationSupport#publicationFileExtensionTestCase")
46+
fun `build image with specified publication file extension`(
47+
format: String,
48+
compression: String?,
49+
) {
50+
replaceBuildDetails(format, compression)
51+
val extension = format + (compression?.let { ".$compression" } ?: "")
52+
assertImageGeneration("image.$extension")
53+
}
54+
4155
@Test
4256
fun `build image with two publications should fail`() {
4357
val result = buildRunner().buildAndFail()
@@ -47,7 +61,7 @@ abstract class AbstractBuildTest : AbstractBufIntegrationTest() {
4761

4862
@Test
4963
fun `build image with two publications should succeed if details are provided explicitly`() {
50-
assertImageGeneration()
64+
assertImageGeneration("image.json")
5165
}
5266

5367
@Test
@@ -60,9 +74,9 @@ abstract class AbstractBuildTest : AbstractBufIntegrationTest() {
6074
)
6175
}
6276

63-
private fun assertImageGeneration() {
77+
private fun assertImageGeneration(publicationFileName: String) {
6478
assertThat(buildRunner().build().task(":$BUF_BUILD_TASK_NAME")?.outcome).isEqualTo(SUCCESS)
65-
val image = Paths.get(projectDir.path, "build", "bufbuild", "image.json").toFile().readText()
79+
val image = Paths.get(projectDir.path, "build", "bufbuild", publicationFileName).toFile().readText()
6680
assertThat(image).isNotEmpty()
6781
}
6882

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package build.buf.gradle
2+
3+
import com.google.common.collect.Lists
4+
import org.junit.jupiter.params.provider.Arguments
5+
6+
private val NULL_SENTINEL = Any()
7+
8+
object ImageGenerationSupport {
9+
@JvmStatic
10+
fun publicationFileExtensionTestCase() =
11+
Lists.cartesianProduct(
12+
ImageFormat.values().map { it.formatName },
13+
CompressionFormat.values().map { it.ext } + NULL_SENTINEL,
14+
).map { imageAndCompression ->
15+
Arguments.of(imageAndCompression[0], imageAndCompression[1].takeIf { it != NULL_SENTINEL })
16+
}
17+
18+
fun AbstractBufIntegrationTest.replaceBuildDetails(
19+
format: String,
20+
compression: String?,
21+
) {
22+
buildFile.replace(
23+
"imageFormat = REPLACEME",
24+
"imageFormat = '$format'",
25+
)
26+
buildFile.replace(
27+
"compressionFormat = REPLACEME",
28+
"compressionFormat = ${compression?.let { "'$it'" }}",
29+
)
30+
}
31+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Copyright 2023 Buf Technologies, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
syntax = "proto3";
16+
17+
package buf.test.v1;
18+
19+
message BasicMessage {}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
plugins {
2+
id 'java'
3+
id 'build.buf'
4+
id 'maven-publish'
5+
}
6+
7+
repositories {
8+
mavenCentral()
9+
maven { url 'build/repos/test' }
10+
}
11+
12+
publishing {
13+
repositories {
14+
maven { url 'build/repos/test' }
15+
}
16+
}
17+
18+
buf {
19+
publishSchema = true
20+
//previousVersion = '2319'
21+
22+
build {
23+
imageFormat = REPLACEME
24+
compressionFormat = REPLACEME
25+
}
26+
27+
imageArtifact {
28+
groupId = 'foo'
29+
artifactId = 'bar'
30+
version = '2319'
31+
}
32+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
plugins {
2+
id 'java'
3+
id 'com.google.protobuf' version "$protobufGradleVersion"
4+
id 'build.buf'
5+
id 'maven-publish'
6+
}
7+
8+
repositories {
9+
mavenCentral()
10+
maven { url 'build/repos/test' }
11+
}
12+
13+
protobuf {
14+
protoc {
15+
artifact = "com.google.protobuf:protoc:$protobufVersion"
16+
}
17+
}
18+
19+
compileJava.enabled = false
20+
21+
publishing {
22+
repositories {
23+
maven { url 'build/repos/test' }
24+
}
25+
}
26+
27+
buf {
28+
publishSchema = true
29+
//previousVersion = '2319'
30+
31+
build {
32+
imageFormat = REPLACEME
33+
compressionFormat = REPLACEME
34+
}
35+
36+
imageArtifact {
37+
groupId = 'foo'
38+
artifactId = 'bar'
39+
version = '2319'
40+
}
41+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Copyright 2023 Buf Technologies, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
syntax = "proto3";
16+
17+
package buf.test.v1;
18+
19+
message BasicMessage {}

0 commit comments

Comments
 (0)