Skip to content

Commit

Permalink
Support buf format (#71)
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewparmet authored May 10, 2022
1 parent a7dcf73 commit 8d25f13
Show file tree
Hide file tree
Showing 72 changed files with 1,585 additions and 28 deletions.
6 changes: 4 additions & 2 deletions src/main/kotlin/com/parmet/buf/gradle/Breaking.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package com.parmet.buf.gradle

import org.gradle.api.Project
import org.gradle.language.base.plugins.LifecycleBasePlugin.CHECK_TASK_NAME
import org.gradle.language.base.plugins.LifecycleBasePlugin.VERIFICATION_GROUP

const val BUF_BREAKING_TASK_NAME = "bufBreaking"
const val BUF_BREAKING_CONFIGURATION_NAME = "bufBreaking"
Expand Down Expand Up @@ -51,9 +52,10 @@ private fun Project.addSchemaDependency(artifactDetails: ArtifactDetails) {

private fun Project.configureBreakingTask() {
tasks.register(BUF_BREAKING_TASK_NAME) {
dependsOn(BUF_BUILD_TASK_NAME)
group = VERIFICATION_GROUP
description = "Checks that Protobuf API definitions are backwards-compatible with previous versions."

group = CHECK_TASK_NAME
dependsOn(BUF_BUILD_TASK_NAME)

execBuf(
"breaking",
Expand Down
6 changes: 6 additions & 0 deletions src/main/kotlin/com/parmet/buf/gradle/BufExtension.kt
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ open class BufExtension {
*/
var checkSchemaAgainstLatestRelease = false

/**
* Enables format checking via `buf format`. Apply formatting suggestions automatically with the `bufFormatApply`
* task.
*/
var enforceFormat = true

/**
* Specify the version of Buf.
*/
Expand Down
1 change: 1 addition & 0 deletions src/main/kotlin/com/parmet/buf/gradle/BufPlugin.kt
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ class BufPlugin : Plugin<Project> {
private fun Project.configureBuf() {
configureBufDependency()
configureLint()
configureFormat()
configureBuild()
configureGenerate()

Expand Down
4 changes: 4 additions & 0 deletions src/main/kotlin/com/parmet/buf/gradle/Build.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import org.gradle.api.publish.PublishingExtension
import org.gradle.api.publish.maven.MavenPublication
import org.gradle.kotlin.dsl.create
import org.gradle.kotlin.dsl.the
import org.gradle.language.base.plugins.LifecycleBasePlugin.BUILD_GROUP
import java.io.File

const val BUF_BUILD_TASK_NAME = "bufBuild"
Expand All @@ -28,6 +29,9 @@ const val BUF_IMAGE_PUBLICATION_NAME = "bufImagePublication"

internal fun Project.configureBuild() {
tasks.register(BUF_BUILD_TASK_NAME) {
group = BUILD_GROUP
description = "Builds a Buf image from a Protobuf schema."

if (hasProtobufGradlePlugin()) {
dependsOn(COPY_BUF_CONFIG_TASK_NAME)
} else {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Copyright (c) 2022 Andrew Parmet
*
* 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.
*/

package com.parmet.buf.gradle

import org.gradle.api.Task
import java.nio.file.Path

internal fun Task.configureDirectorySpecificBufExecution(
vararg bufCommand: String
) {
configureDirectorySpecificBufExecution(bufCommand.asList(), emptyList())
}

internal fun Task.configureDirectorySpecificBufExecution(
bufCommand: String,
extraArgs: Iterable<String>
) {
configureDirectorySpecificBufExecution(listOf(bufCommand), extraArgs)
}

private fun Task.configureDirectorySpecificBufExecution(
bufCommand: Iterable<String>,
extraArgs: Iterable<String>
) {
fun lintWithArgs(path: Path? = null) =
bufCommand + listOfNotNull(path?.let(::mangle)) + extraArgs

when {
project.hasProtobufGradlePlugin() ->
project.srcProtoDirs().forEach { execBuf(lintWithArgs(it)) }
project.hasWorkspace() ->
execBuf(bufCommand)
else ->
execBuf(lintWithArgs())
}
}
49 changes: 49 additions & 0 deletions src/main/kotlin/com/parmet/buf/gradle/Format.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Copyright (c) 2022 Andrew Parmet
*
* 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.
*/

package com.parmet.buf.gradle

import org.gradle.api.Project
import org.gradle.language.base.plugins.LifecycleBasePlugin.CHECK_TASK_NAME
import org.gradle.language.base.plugins.LifecycleBasePlugin.VERIFICATION_GROUP

const val BUF_FORMAT_CHECK_TASK_NAME = "bufFormatCheck"
const val BUF_FORMAT_APPLY_TASK_NAME = "bufFormatApply"

internal fun Project.configureFormat() {
configureBufFormatCheck()
configureBufFormatApply()
}

private fun Project.configureBufFormatCheck() {
tasks.register(BUF_FORMAT_CHECK_TASK_NAME) {
group = VERIFICATION_GROUP
description = "Checks that a Protobuf schema is formatted according to Buf's formatting rules."
enabled = getExtension().enforceFormat

configureDirectorySpecificBufExecution("format", "-d", "--exit-code")
}

tasks.named(CHECK_TASK_NAME).dependsOn(BUF_FORMAT_CHECK_TASK_NAME)
}

private fun Project.configureBufFormatApply() {
tasks.register(BUF_FORMAT_APPLY_TASK_NAME) {
group = VERIFICATION_GROUP
description = "Formats a Protobuf schema according to Buf's formatting rules."

configureDirectorySpecificBufExecution("format", "-w")
}
}
4 changes: 4 additions & 0 deletions src/main/kotlin/com/parmet/buf/gradle/Generate.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package com.parmet.buf.gradle

import org.gradle.api.Project
import org.gradle.language.base.plugins.LifecycleBasePlugin.BUILD_GROUP
import java.io.File

const val BUF_GENERATE_TASK_NAME = "bufGenerate"
Expand All @@ -25,6 +26,9 @@ const val GENERATED_DIR = "generated"
internal fun Project.configureGenerate() {
if (hasGenerate()) {
tasks.register(BUF_GENERATE_TASK_NAME) {
group = BUILD_GROUP
description = "Generates code from a Protobuf schema."

createsOutput()
execBuf("generate", "--output", File(bufbuildDir, GENERATED_DIR))
}
Expand Down
19 changes: 5 additions & 14 deletions src/main/kotlin/com/parmet/buf/gradle/Lint.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,27 +18,18 @@ package com.parmet.buf.gradle
import org.gradle.api.Project
import org.gradle.language.base.plugins.LifecycleBasePlugin.CHECK_TASK_NAME
import org.gradle.language.base.plugins.LifecycleBasePlugin.VERIFICATION_GROUP
import java.nio.file.Path

const val BUF_LINT_TASK_NAME = "bufLint"

internal fun Project.configureLint() {
tasks.register(BUF_LINT_TASK_NAME) {
group = VERIFICATION_GROUP
description = "Checks that Protobuf API definitions are consistent with your chosen best practices."
description = "Checks that a Protobuf schema conforms to the Buf lint configuration."

fun lintWithArgs(path: Path? = null) =
listOfNotNull("lint", path?.let(::mangle)) +
bufConfigFile()?.let { listOf("--config", it.readText()) }.orEmpty()

when {
hasProtobufGradlePlugin() ->
srcProtoDirs().forEach { execBuf(lintWithArgs(it)) }
hasWorkspace() ->
execBuf("lint")
else ->
execBuf(lintWithArgs())
}
configureDirectorySpecificBufExecution(
"lint",
bufConfigFile()?.let { listOf("--config", it.readText()) }.orEmpty()
)
}

tasks.named(CHECK_TASK_NAME).dependsOn(BUF_LINT_TASK_NAME)
Expand Down
5 changes: 0 additions & 5 deletions src/test/kotlin/com/parmet/buf/gradle/AbstractBreakingTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ package com.parmet.buf.gradle
import com.google.common.truth.Truth.assertThat
import org.gradle.testkit.runner.TaskOutcome.FAILED
import org.junit.jupiter.api.Test
import java.io.File
import java.nio.file.Path

abstract class AbstractBreakingTest : AbstractBufIntegrationTest() {
Expand Down Expand Up @@ -68,8 +67,4 @@ abstract class AbstractBreakingTest : AbstractBufIntegrationTest() {
}

protected abstract fun protoFile(): Path

protected fun File.replace(oldValue: String, newValue: String) {
writeText(readText().replace(oldValue, newValue))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ abstract class AbstractBufIntegrationTest : IntegrationTest {

fun publishRunner() =
gradleRunner().withArguments("publish")

fun File.replace(oldValue: String, newValue: String) {
writeText(readText().replace(oldValue, newValue))
}
}

interface IntegrationTest {
Expand Down
64 changes: 64 additions & 0 deletions src/test/kotlin/com/parmet/buf/gradle/AbstractFormatApplyTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* Copyright (c) 2022 Andrew Parmet
*
* 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.
*/

package com.parmet.buf.gradle

import com.google.common.truth.Truth.assertThat
import org.gradle.testkit.runner.TaskOutcome.SUCCESS
import org.junit.jupiter.api.Test
import java.nio.file.Path

abstract class AbstractFormatApplyTest : AbstractBufIntegrationTest() {
@Test
fun `format an incorrect message`() {
assertSuccess(
"""
// Copyright (c) 2022 Andrew Parmet
//
// 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 parmet.buf.test.v1;
message Foo {}
""".trimIndent()
)
}

protected fun assertSuccess(after: String, protoFile: Path = protoFile()) {
assertThat(
gradleRunner()
.withArguments(BUF_FORMAT_APPLY_TASK_NAME)
.build()
.task(":$BUF_FORMAT_APPLY_TASK_NAME")
?.outcome
).isEqualTo(SUCCESS)

assertThat(protoFile.toFile().readText()).isEqualTo(after)
}

protected abstract fun protoFile(): Path
}
64 changes: 64 additions & 0 deletions src/test/kotlin/com/parmet/buf/gradle/AbstractFormatCheckTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* Copyright (c) 2022 Andrew Parmet
*
* 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.
*/

package com.parmet.buf.gradle

import com.google.common.truth.Truth.assertThat
import org.gradle.language.base.plugins.LifecycleBasePlugin.CHECK_TASK_NAME
import org.gradle.testkit.runner.TaskOutcome.FAILED
import org.gradle.testkit.runner.TaskOutcome.SUCCESS
import org.junit.jupiter.api.Test

abstract class AbstractFormatCheckTest : AbstractBufIntegrationTest() {
@Test
fun `format a correct message`() {
assertSuccess()
}

@Test
fun `format an incorrect message`() {
baseAssertBadWhitespace()
}

@Test
fun `do not format an incorrect message when enforcement is disabled`() {
baseAssertBadWhitespace()

buildFile.replace("enforceFormat = true", "enforceFormat = false")

assertSuccess()
}

private fun baseAssertBadWhitespace() {
assertBadWhitespace(
"""
-message Foo {
-
-}
+message Foo {}
""".trimIndent()
)
}

protected fun assertBadWhitespace(diff: String) {
val result = checkRunner().buildAndFail()
assertThat(result.task(":$BUF_FORMAT_CHECK_TASK_NAME")?.outcome).isEqualTo(FAILED)
assertThat(result.output).contains(diff)
}

protected fun assertSuccess() {
assertThat(checkRunner().build().task(":$CHECK_TASK_NAME")?.outcome).isEqualTo(SUCCESS)
}
}
8 changes: 4 additions & 4 deletions src/test/kotlin/com/parmet/buf/gradle/AbstractLintTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,19 @@ abstract class AbstractLintTest : LintTestUtilities, AbstractBufIntegrationTest(
}

@Test
fun `lint a basic incorrect message with wrong location`() {
assertLocationFailure()
fun `lint a basic incorrect message with bad enum`() {
assertBadEnumSuffix()
}

@Test
fun `lint a file with a google dependency`() {
assertSuccess()
}

private fun assertLocationFailure() {
private fun assertBadEnumSuffix() {
val result = checkRunner().buildAndFail()
assertThat(result.task(":$BUF_LINT_TASK_NAME")?.outcome).isEqualTo(FAILED)
assertThat(result.output).contains("must be within a directory \"parmet/buf/test/v1\"")
assertThat(result.output).contains("Enum zero value name \"TEST_FOO\" should be suffixed with \"_UNSPECIFIED\"")
}
}

Expand Down
Loading

0 comments on commit 8d25f13

Please sign in to comment.