Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: ci
on:
push:
branches: [main, v2]
tags: ['v*']
tags: ['v*', 'cmd/connect-go-v2-migrate/v*']
pull_request:
branches: [main, v2]
schedule:
Expand Down Expand Up @@ -86,9 +86,10 @@ jobs:
with:
fetch-depth: 1
- name: Install Go
uses: actions/setup-go@v6
uses: actions/setup-go@v7
with:
# only the latest
go-version: 1.26.x
go-version: 1.27.x
check-latest: true
- name: Test Migration Tool
run: make testmigrate
11 changes: 9 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ clean: ## Delete intermediate build artifacts
git clean -Xdf

.PHONY: test
test: shorttest slowtest
test: shorttest slowtest testmigrate

.PHONY: shorttest
shorttest: build ## Run unit tests
Expand All @@ -54,7 +54,7 @@ runconformance: build ## Run conformance test suite

.PHONY: testmigrate
testmigrate: ## Run connect-go-v2-migrate test suite
cd cmd/connect-go-v2-migrate && go test ./...
cd ./cmd/connect-go-v2-migrate && go test ./...

.PHONY: bench
bench: BENCH ?= .*
Expand All @@ -64,27 +64,33 @@ bench: build ## Run benchmarks for root package
.PHONY: build
build: generate ## Build all packages
go build ./...
cd ./cmd/connect-go-v2-migrate && go build ./...

.PHONY: install
install: ## Install all binaries
go install ./...
cd ./cmd/connect-go-v2-migrate && go install ./...

.PHONY: lint
lint: $(BIN)/golangci-lint $(BIN)/buf ## Lint Go and protobuf
go vet ./...
golangci-lint run --modules-download-mode=readonly --timeout=3m0s
cd ./cmd/connect-go-v2-migrate && go vet ./...
cd ./cmd/connect-go-v2-migrate && golangci-lint run --modules-download-mode=readonly --timeout=3m0s
buf lint
buf format -d --exit-code

.PHONY: lintfix
lintfix: $(BIN)/golangci-lint $(BIN)/buf ## Automatically fix some lint errors
golangci-lint run --fix --modules-download-mode=readonly --timeout=3m0s
cd ./cmd/connect-go-v2-migrate && golangci-lint run --fix --modules-download-mode=readonly --timeout=3m0s
buf format -w

.PHONY: generate
generate: $(BIN)/buf $(BIN)/protoc-gen-go $(BIN)/protoc-gen-connect-go $(BIN)/license-header ## Regenerate code and licenses
go mod tidy
cd ./internal/conformance && go mod tidy
cd ./cmd/connect-go-v2-migrate && go mod tidy
buf generate
cd internal/conformance && buf generate --template buf.gen.yaml buf.build/connectrpc/conformance
cd ./cmd/protoc-gen-connect-go/internal && \
Expand All @@ -97,6 +103,7 @@ generate: $(BIN)/buf $(BIN)/protoc-gen-go $(BIN)/protoc-gen-connect-go $(BIN)/li
.PHONY: upgrade
upgrade: ## Upgrade dependencies
go get -u -t ./... && go mod tidy -v
cd ./cmd/connect-go-v2-migrate && go get -u -t ./... && go mod tidy -v

.PHONY: checkgenerate
checkgenerate:
Expand Down
24 changes: 19 additions & 5 deletions RELEASE.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Releasing connect-go

This document outlines how to create a release of connect-go.
This document outlines how to create a release of connect-go v2 from the main branch. v1 releases follow the same steps on the `v1` branch.

1. Clone the repo, ensuring you have the latest main.

Expand All @@ -9,8 +9,8 @@ This document outlines how to create a release of connect-go.
* If there are features being released, remove the `-dev` suffix, set the MINOR number to be 1 more than the MINOR number of the [latest release], and set the PATCH number to `0`. In the common case, the diff here will just be to remove the `-dev` suffix.

```patch
-const Version = "1.14.0-dev"
+const Version = "1.14.0"
-const Version = "2.0.0-dev"
+const Version = "2.0.0"
```

3. Check for any changes in [cmd/protoc-gen-connect-go/main.go](cmd/protoc-gen-connect-go/main.go) that require a version restriction. A constant `IsAtLeastVersionX_Y_Z` should be defined in [connect.go](connect.go) if generated code has begun to use a new API. Make sure the generated code references this constant. If a new constant has been added since the last release, ensure that the name of the constant matches the version being released ([Example PR #496](https://github.com/connectrpc/connect-go/pull/496)).
Expand All @@ -35,10 +35,24 @@ This document outlines how to create a release of connect-go.
8. On a new branch, open [connect.go](connect.go) and change the `Version` to increment the minor tag and append the `-dev` suffix. Use the next minor release - we never anticipate bugs and patch releases.

```patch
-const Version = "1.14.0"
+const Version = "1.15.0-dev"
-const Version = "2.0.0"
+const Version = "2.1.0-dev"
```

9. Open a PR titled "Back to development" ([Example PR #662](https://github.com/connectrpc/connect-go/pull/662)). Once it's reviewed and CI passes, merge it.

[latest release]: https://github.com/connectrpc/connect-go/releases/latest

# Releasing connect-go-v2-migrate

`cmd/connect-go-v2-migrate` is its own Go module, `connectrpc.com/connect/v2/cmd/connect-go-v2-migrate`.

1. Using the Github UI, create a new release.
- Under “Choose a tag”, type in “cmd/connect-go-v2-migrate/vX.Y.Z” to create a new tag for the release upon publish. The directory prefix is what keeps it from colliding with the library’s tags in this repository. The version is the module’s own and starts at `v1.0.0`.
- Target the main branch.
- Title the Release “connect-go-v2-migrate vX.Y.Z”.
- Do not click “set as latest release”. That badge is picked by date, so it should stay on the most recent connect-go release.
- Set the last connect-go-v2-migrate release as the “Previous tag”, so the generated notes cover this module rather than the library.
- Click “Generate release notes” to autogenerate release notes.

2. Publish the release.
41 changes: 28 additions & 13 deletions cmd/connect-go-v2-migrate/bufgen.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,19 @@ package main

import (
"path"
"regexp"
"strings"
)

const (
connectV2Module = "connectrpc.com/connect/v2"
connectLocalPlugin = "protoc-gen-connect-go"
connectRemotePlugin = "buf.build/connectrpc/go"
connectRemotePluginV2 = connectRemotePlugin + ":v2.0.0"
connectV2Module = "connectrpc.com/connect/v2"
connectLocalPlugin = "protoc-gen-connect-go"
connectRemotePluginVersion = "v2.0.0"
)

// connectRemotePluginRef matches connect-go remotes plugins.
var connectRemotePluginRef = regexp.MustCompile(`^(` + bsrHostPattern + `)/connectrpc/(go|gosimple)(?::(\S+))?$`)

// Plugin entry kinds returned by connectPluginItem.
const (
kindLocal = "local" // a local binary on $PATH
Expand Down Expand Up @@ -80,7 +83,7 @@ func RewriteBufGen(filename string, src []byte) ([]byte, Report, error) {
case kindGotool:
report.warnAtLinef(filename, index+1, ruleBufgenGoMod, "the plugin runs via go.mod (%s). Update the tool dependency to the v2 module with `go get -tool %s/cmd/%s` then `go mod tidy`. The buf.gen.yaml entry stays the same.", ref, connectV2Module, connectLocalPlugin)
case kindRemote:
pinRemotePluginV2(lines, index, ref, &edits, &report)
pinRemotePluginV2(filename, lines, index, ref, &edits, &report)
}
index = end - 1
}
Expand All @@ -91,14 +94,26 @@ func RewriteBufGen(filename string, src []byte) ([]byte, Report, error) {
return []byte(strings.Join(edits.apply(lines), "\n")), report, nil
}

// pinRemotePluginV2 pins a v1 remote plugin reference (versioned or not) to
// the v2 release. References already at v2 are left alone.
func pinRemotePluginV2(lines []string, index int, ref string, edits *lineEdits, report *Report) {
if strings.HasPrefix(ref, connectRemotePlugin+":v2") {
// pinRemotePluginV2 pins a v1 remote plugin reference (versioned or not) to the
// v2 release. References already at v2 are left alone.
func pinRemotePluginV2(filename string, lines []string, index int, ref string, edits *lineEdits, report *Report) {
match := connectRemotePluginRef.FindStringSubmatch(ref)
if match == nil {
return
}
host, simple, version := match[1], match[2] == "gosimple", match[3]
if !simple && strings.HasPrefix(version, "v2") {
return
}
edits.replace[index] = strings.Replace(lines[index], ref, connectRemotePluginV2, 1)
report.bump("bufgen_pin_remote_v2")
pinned := host + "/connectrpc/go:" + connectRemotePluginVersion
edits.replace[index] = strings.Replace(lines[index], ref, pinned, 1)
if simple {
report.bump("bufgen_replace_gosimple")
} else {
report.bump("bufgen_pin_remote_v2")
}
// TODO: drop once connect-go v2.0.0 ships and the plugin is on the BSR.
report.warnAtLinef(filename, index+1, ruleBufgenRemoteUnpublished, "%s is not published yet. Until connect-go %s is released, generate with the local plugin instead: `go install %s/cmd/%s@latest` and a `local: %s` entry.", pinned, connectRemotePluginVersion, connectV2Module, connectLocalPlugin, connectLocalPlugin)
}

// lineEdits records pending line replacements or deletions keyed by line index.
Expand Down Expand Up @@ -157,10 +172,10 @@ func connectPluginItem(line string) (kind, ref string, ok bool) {
return "", "", false
}

// isConnectRemoteRef reports whether value references the connect-go remote
// isConnectRemoteRef reports whether value references either connect-go remote
// plugin, bare or version-tagged.
func isConnectRemoteRef(value string) bool {
return value == connectRemotePlugin || strings.HasPrefix(value, connectRemotePlugin+":")
return connectRemotePluginRef.MatchString(value)
}

// pathOverride scans a v1 plugin block for a `path:` whose value is a command
Expand Down
110 changes: 110 additions & 0 deletions cmd/connect-go-v2-migrate/bufgen_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,116 @@ plugins:
want: "",
wantChanged: false,
},
{
// v2 folds the simple API into the default generator, so the
// gosimple plugin migrates onto connectrpc/go, not a gosimple v2.
name: "remote_gosimple_replaced_by_default_plugin",
in: `version: v2
plugins:
- remote: buf.build/connectrpc/gosimple:v1.18.1
out: gen
`,
want: `version: v2
plugins:
- remote: buf.build/connectrpc/go:v2.0.0
out: gen
`,
wantChanged: true,
},
{
name: "remote_gosimple_unversioned_replaced",
in: `version: v2
plugins:
- remote: buf.build/connectrpc/gosimple
out: gen
`,
want: `version: v2
plugins:
- remote: buf.build/connectrpc/go:v2.0.0
out: gen
`,
wantChanged: true,
},
{
name: "v1_plugin_gosimple_replaced",
in: `version: v1
plugins:
- plugin: buf.build/connectrpc/gosimple:v1.18.1
out: gen
`,
want: `version: v1
plugins:
- plugin: buf.build/connectrpc/go:v2.0.0
out: gen
`,
wantChanged: true,
},
{
// Private BSR instances use the same plugin path under another host,
// and the rewrite must keep that host rather than jump to buf.build.
name: "remote_private_host_keeps_host",
in: `version: v2
plugins:
- remote: buf.example.com/connectrpc/go:v1.18.1
out: gen
`,
want: `version: v2
plugins:
- remote: buf.example.com/connectrpc/go:v2.0.0
out: gen
`,
wantChanged: true,
},
{
name: "remote_private_host_gosimple_replaced",
in: `version: v2
plugins:
- remote: bsr.internal.acme.dev/connectrpc/gosimple:v1.18.1
out: gen
`,
want: `version: v2
plugins:
- remote: bsr.internal.acme.dev/connectrpc/go:v2.0.0
out: gen
`,
wantChanged: true,
},
{
name: "remote_private_host_already_v2_is_noop",
in: `version: v2
plugins:
- remote: buf.example.com/connectrpc/go:v2.0.0
out: gen
`,
want: "",
wantChanged: false,
},
{
// A hostless reference is not a BSR plugin and must be left alone.
name: "remote_hostless_ref_ignored",
in: `version: v2
plugins:
- remote: connectrpc/go:v1.18.1
out: gen
`,
want: "",
wantChanged: false,
},
{
name: "remote_pin_warns_plugin_not_published",
in: `version: v2
plugins:
- remote: buf.build/connectrpc/go:v1.18.1
out: gen
`,
want: `version: v2
plugins:
- remote: buf.build/connectrpc/go:v2.0.0
out: gen
`,
wantChanged: true,
wantWarn: "is not published yet",
},
{
name: "gotool_warns_about_gomod_and_strips",
in: `version: v2
Expand Down
20 changes: 9 additions & 11 deletions cmd/connect-go-v2-migrate/discover.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"go/types"
"os"
"path/filepath"
"regexp"
"sort"
"strconv"
"strings"
Expand All @@ -29,24 +30,21 @@ import (
)

const (
generatedMarker = "DO NOT EDIT"
// connectStubMarker identifies protoc-gen-connect-go output. Only these
// stubs decide the regenerate-first phase; other connect importers (mocks)
// do not.
generatedMarker = "DO NOT EDIT"
connectStubMarker = "protoc-gen-connect-go"
connectV1Path = "connectrpc.com/connect"
// bsrModulePrefix is the BSR Go SDK import-path prefix:
// buf.build/gen/go/<owner>/<module>/<plugin-owner>/<plugin>.
bsrModulePrefix = "buf.build/gen/go/"
// bsrConnectPlugin is the connect plugin's segment; only its output is
// versioned with connect, so only it takes the @v2 query.
bsrConnectPlugin = "/connectrpc/"
)

// bsrHostPattern matches a registry host, not pinned to buf.build.
const bsrHostPattern = `[^/:]+\.[^/:]+`

// bsrConnectModule matches a connect generated SDK.
var bsrConnectModule = regexp.MustCompile(`^` + bsrHostPattern + `/gen/go/[^/]+/[^/]+/connectrpc/(?:go|gosimple)$`)

// isBSRConnectModule reports whether modulePath is a BSR Go SDK generated by the
// connect-go plugin, so `go get @v2` resolves to the connect-v2 build.
func isBSRConnectModule(modulePath string) bool {
return strings.HasPrefix(modulePath, bsrModulePrefix) && strings.Contains(modulePath, bsrConnectPlugin)
return bsrConnectModule.MatchString(modulePath)
}

// fileContent pairs a path with its bytes. ready is false when the source binds
Expand Down
12 changes: 11 additions & 1 deletion cmd/connect-go-v2-migrate/discover_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,8 @@ func TestIsConnectStubAST(t *testing.T) {

// TestIsBSRConnectModule covers which BSR modules get the @v2 advice: only the
// connect-go plugin's output (.../connectrpc/go), not other plugins under the
// same buf.build/gen/go prefix, and not non-BSR modules.
// same gen/go prefix, and not non-BSR modules. Self-hosted BSR instances use
// the same path layout under a different host, so they match too.
func TestIsBSRConnectModule(t *testing.T) {
t.Parallel()
tests := []struct {
Expand All @@ -95,6 +96,15 @@ func TestIsBSRConnectModule(t *testing.T) {
{module: "buf.build/gen/go/acme/api/grpc/go", want: false}, // a different plugin
{module: "github.com/grafana/pyroscope/api", want: false}, // not a BSR module
{module: "connectrpc.com/connect", want: false},
// Self-hosted BSR instances share the layout under another host.
{module: "buf.example.com/gen/go/acme/api/connectrpc/go", want: true},
{module: "bsr.internal.acme.dev/gen/go/acme/api/connectrpc/go", want: true},
{module: "buf.example.com/gen/go/acme/api/protocolbuffers/go", want: false},
// A hostless or malformed path must not match.
{module: "gen/go/acme/api/connectrpc/go", want: false},
{module: "buf.build/gen/go/acme/api/connectrpc/go/extra", want: false},
{module: "buf.build/gen/go/acme/connectrpc/go", want: false},
{module: "notahost/gen/go/acme/api/connectrpc/go", want: false},
}
for _, test := range tests {
if got := isBSRConnectModule(test.module); got != test.want {
Expand Down
Loading
Loading