From 3eda79188c003c0495a185e9562f131fe0be5d99 Mon Sep 17 00:00:00 2001 From: Brandur Leach Date: Sun, 18 Aug 2024 12:35:49 -0700 Subject: [PATCH] Move submodule update command to `rivershared` + check for go/toolchain + workspace (#528) This one's a bit of a grab bucket, but do a few refactors and additions: * Bring the project over to use a Go workspace, thereby letting us run tests and programs across module directories and cut out our `replace` statements in `go.mod` all over the place. * Move the River version update program for `go.mod` files over to `rivershared` so that it can be used by other River projects. It's modified so that instead of hardcoding a submodule list, it can now read them straight out of the project's workspace in `go.work`. * Add a new program that's similar to this one whose job it is to check that the `go`/`toolchain` directives across all `go.mod`s in the workspace match, which will help prevent accidental regressions of #522. It can also be used locally to update `go`/`toolchain` directives to new values in case we want to do that in the future. * Do a lot of repaving in `Makefile` so that we can use list of submodules from the workspace to run a series of commands instead of needing to maintain independent lists for each target, a practice that was often unreliable. --- .github/workflows/ci.yaml | 24 +++- Makefile | 88 ++++++++----- cmd/river/go.mod | 12 -- docs/development.md | 2 +- go.mod | 11 -- go.sum | 12 +- go.work | 13 ++ go.work.sum | 38 ++++++ .../update-submodule-versions/main_test.go | 72 ---------- riverdriver/go.mod | 2 - riverdriver/go.sum | 2 + riverdriver/riverdatabasesql/go.mod | 6 - riverdriver/riverdatabasesql/go.sum | 6 + riverdriver/riverpgxv5/go.mod | 6 - riverdriver/riverpgxv5/go.sum | 6 + rivershared/cmd/update-mod-go/main.go | 124 ++++++++++++++++++ rivershared/cmd/update-mod-go/main_test.go | 118 +++++++++++++++++ .../cmd/update-mod-version}/main.go | 46 ++++--- .../cmd/update-mod-version/main_test.go | 82 ++++++++++++ rivershared/go.mod | 1 + rivershared/go.sum | 2 + 21 files changed, 499 insertions(+), 174 deletions(-) create mode 100644 go.work create mode 100644 go.work.sum delete mode 100644 internal/cmd/update-submodule-versions/main_test.go create mode 100644 rivershared/cmd/update-mod-go/main.go create mode 100644 rivershared/cmd/update-mod-go/main_test.go rename {internal/cmd/update-submodule-versions => rivershared/cmd/update-mod-version}/main.go (66%) create mode 100644 rivershared/cmd/update-mod-version/main_test.go diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 8ec6ae9c..a0b01e8e 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -28,9 +28,9 @@ jobs: strategy: matrix: go-version: - - "1.22" - "1.21" - postgres-version: [16, 15, 14] + - "1.22" + postgres-version: [14, 15, 16] fail-fast: false timeout-minutes: 5 @@ -59,10 +59,7 @@ jobs: run: go version - name: Install dependencies - run: | - echo "::group::go get" - go get -t ./... - echo "::endgroup::" + run: go get -t ./... - name: Set up test DBs run: go run ./internal/cmd/testdbman create @@ -265,3 +262,18 @@ jobs: run: | echo "Make sure that all sqlc changes are checked in" make verify/sqlc + + submodule_check: + runs-on: ubuntu-latest + + steps: + - uses: actions/setup-go@v5 + with: + go-version: "stable" + check-latest: true + + - name: Checkout + uses: actions/checkout@v4 + + - name: Check all go/toolchain directives match + run: CHECK=true make update-mod-go diff --git a/Makefile b/Makefile index 091a698f..7088f76e 100644 --- a/Makefile +++ b/Makefile @@ -1,71 +1,87 @@ -.PHONY: generate -generate: -generate: generate/migrations -generate: generate/sqlc +.DEFAULT_GOAL := help .PHONY: db/reset -db/reset: ## drop, create, and migrate dev and test databases +db/reset: ## Drop, create, and migrate dev and test databases db/reset: db/reset/dev db/reset: db/reset/test .PHONY: db/reset/dev -db/reset/dev: ## drop, create, and migrate dev database +db/reset/dev: ## Drop, create, and migrate dev database dropdb river_dev --force --if-exists createdb river_dev cd cmd/river && go run . migrate-up --database-url "postgres://localhost/river_dev" .PHONY: db/reset/test -db/reset/test: ## drop, create, and migrate test databases +db/reset/test: ## Drop, create, and migrate test databases go run ./internal/cmd/testdbman reset +.PHONY: generate +generate: ## Generate generated artifacts +generate: generate/migrations +generate: generate/sqlc + .PHONY: generate/migrations -generate/migrations: ## sync changes of pgxv5 migrations to database/sql +generate/migrations: ## Sync changes of pgxv5 migrations to database/sql rsync -au --delete "riverdriver/riverpgxv5/migration/" "riverdriver/riverdatabasesql/migration/" .PHONY: generate/sqlc -generate/sqlc: +generate/sqlc: ## Generate sqlc cd riverdriver/riverdatabasesql/internal/dbsqlc && sqlc generate cd riverdriver/riverpgxv5/internal/dbsqlc && sqlc generate +# Looks at comments using ## on targets and uses them to produce a help output. +.PHONY: help +help: ALIGN=22 +help: ## Print this message + @awk -F '::? .*## ' -- "/^[^':]+::? .*## /"' { printf "'$$(tput bold)'%-$(ALIGN)s'$$(tput sgr0)' %s\n", $$1, $$2 }' $(MAKEFILE_LIST) + +# Each directory of a submodule in the Go workspace. Go commands provide no +# built-in way to run for all workspace submodules. Add a new submodule to the +# workspace with `go work use ./driver/new`. +submodules := $(shell go list -f '{{.Dir}}' -m) + +# Definitions of following tasks look ugly, but they're done this way because to +# produce the best/most comprehensible output by far (e.g. compared to a shell +# loop). .PHONY: lint -lint: - cd . && golangci-lint run --fix - cd cmd/river && golangci-lint run --fix - cd riverdriver && golangci-lint run --fix - cd riverdriver/riverdatabasesql && golangci-lint run --fix - cd riverdriver/riverpgxv5 && golangci-lint run --fix - cd rivershared && golangci-lint run --fix - cd rivertype && golangci-lint run --fix +lint:: ## Run linter (golangci-lint) for all submodules +define lint-target + lint:: ; cd $1 && golangci-lint run --fix +endef +$(foreach mod,$(submodules),$(eval $(call lint-target,$(mod)))) .PHONY: test -test: - cd . && go test ./... -p 1 - cd cmd/river && go test ./... - cd riverdriver && go test ./... - cd riverdriver/riverdatabasesql && go test ./... - cd riverdriver/riverpgxv5 && go test ./... - cd rivershared && go test ./... - cd rivertype && go test ./... +test:: ## Run test suite for all submodules +define test-target + test:: ; cd $1 && go test ./... -p 1 +endef +$(foreach mod,$(submodules),$(eval $(call test-target,$(mod)))) .PHONY: tidy -tidy: - cd . && go mod tidy - cd cmd/river && go mod tidy - cd riverdriver && go mod tidy - cd riverdriver/riverdatabasesql && go mod tidy - cd riverdriver/riverpgxv5 && go mod tidy - cd rivertype && go mod tidy +tidy:: ## Run `go mod tidy` for all submodules +define tidy-target + tidy:: ; cd $1 && go mod tidy +endef +$(foreach mod,$(submodules),$(eval $(call tidy-target,$(mod)))) + +.PHONY: update-mod-go +update-mod-go: ## Update `go`/`toolchain` directives in all submodules to match `go.work` + go run ./rivershared/cmd/update-mod-go ./go.work + +.PHONY: update-mod-version +update-mod-version: ## Update River packages in all submodules to $VERSION + go run ./rivershared/cmd/update-mod-version ./go.work .PHONY: verify -verify: +verify: ## Verify generated artifacts verify: verify/migrations verify: verify/sqlc .PHONY: verify/migrations -verify/migrations: +verify/migrations: ## Verify synced migrations diff -qr riverdriver/riverpgxv5/migration riverdriver/riverdatabasesql/migration .PHONY: verify/sqlc -verify/sqlc: +verify/sqlc: ## Verify generated sqlc cd riverdriver/riverdatabasesql/internal/dbsqlc && sqlc diff - cd riverdriver/riverpgxv5/internal/dbsqlc && sqlc diff \ No newline at end of file + cd riverdriver/riverpgxv5/internal/dbsqlc && sqlc diff diff --git a/cmd/river/go.mod b/cmd/river/go.mod index e575c895..a5edcad9 100644 --- a/cmd/river/go.mod +++ b/cmd/river/go.mod @@ -4,18 +4,6 @@ go 1.21 toolchain go1.22.5 -// replace github.com/riverqueue/river => ../.. - -// replace github.com/riverqueue/river/riverdriver => ../../riverdriver - -// replace github.com/riverqueue/river/riverdriver/riverdatabasesql => ../../riverdriver/riverdatabasesql - -// replace github.com/riverqueue/river/riverdriver/riverpgxv5 => ../../riverdriver/riverpgxv5 - -// replace github.com/riverqueue/river/rivershared => ../../rivershared - -// replace github.com/riverqueue/river/rivertype => ../../rivertype - require ( github.com/jackc/pgx/v5 v5.6.0 github.com/lmittmann/tint v1.0.4 diff --git a/docs/development.md b/docs/development.md index 06076fcf..aecf1de8 100644 --- a/docs/development.md +++ b/docs/development.md @@ -30,7 +30,7 @@ queries. After changing an sqlc `.sql` file, generate Go with: ```shell git checkout master && git pull --rebase export VERSION=v0.x.y - go run ./internal/cmd/update-submodule-versions/main.go + make update-mod-version git checkout -b $USER-$VERSION ``` diff --git a/go.mod b/go.mod index f9c58e78..826d19c1 100644 --- a/go.mod +++ b/go.mod @@ -4,16 +4,6 @@ go 1.21 toolchain go1.22.5 -replace github.com/riverqueue/river/riverdriver => ./riverdriver - -replace github.com/riverqueue/river/riverdriver/riverpgxv5 => ./riverdriver/riverpgxv5 - -replace github.com/riverqueue/river/riverdriver/riverdatabasesql => ./riverdriver/riverdatabasesql - -replace github.com/riverqueue/river/rivershared => ./rivershared - -replace github.com/riverqueue/river/rivertype => ./rivertype - require ( github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa github.com/jackc/pgx/v5 v5.6.0 @@ -26,7 +16,6 @@ require ( github.com/robfig/cron/v3 v3.0.1 github.com/stretchr/testify v1.9.0 go.uber.org/goleak v1.3.0 - golang.org/x/mod v0.20.0 golang.org/x/sync v0.8.0 golang.org/x/text v0.17.0 ) diff --git a/go.sum b/go.sum index e410aff6..facb6262 100644 --- a/go.sum +++ b/go.sum @@ -19,6 +19,16 @@ github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/riverqueue/river/riverdriver v0.11.2 h1:2xC+R0Y+CFEOSDWKyeFef0wqQLuvhk3PsLkos7MLa1w= +github.com/riverqueue/river/riverdriver v0.11.2/go.mod h1:RhMuAjEtNGexwOFnz445G1iFNZVOnYQ90HDYxHMI+jM= +github.com/riverqueue/river/riverdriver/riverdatabasesql v0.11.2 h1:I4ye1YEa35kqB6Jd3xVPNxbGDL6S1gpSTkZu25qffhc= +github.com/riverqueue/river/riverdriver/riverdatabasesql v0.11.2/go.mod h1:+cOcD4U+8ugUeRZVTGqVhtScy0FS7LPyp+ZsoPIeoMI= +github.com/riverqueue/river/riverdriver/riverpgxv5 v0.11.2 h1:yxFi09ECN02iAr2uO0n7QhFKAyyGZ+Rn9fzKTt2TGhk= +github.com/riverqueue/river/riverdriver/riverpgxv5 v0.11.2/go.mod h1:ajPqIw7OgYBfR24MqH3VGI/SiYVgq0DkvdM7wrs+uDA= +github.com/riverqueue/river/rivershared v0.11.2 h1:VbuLE6zm68R24xBi1elfnerhLBBn6X7DUxR9j4mcTR4= +github.com/riverqueue/river/rivershared v0.11.2/go.mod h1:J4U3qm8MbjHY1o5OlRNiWaminYagec1o8sHYX4ZQ4S4= +github.com/riverqueue/river/rivertype v0.11.2 h1:YREWOGxDMDe1DTdvttwr2DVq/ql65u6e4jkw3VxuNyU= +github.com/riverqueue/river/rivertype v0.11.2/go.mod h1:bm5EMOGAEWhtXKqo27POWnViqSD5nHMZDP/jsrJc530= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= @@ -32,8 +42,6 @@ go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= -golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= -golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= diff --git a/go.work b/go.work new file mode 100644 index 00000000..ae23fdc4 --- /dev/null +++ b/go.work @@ -0,0 +1,13 @@ +go 1.21 + +toolchain go1.22.5 + +use ( + . + ./cmd/river + ./riverdriver + ./riverdriver/riverdatabasesql + ./riverdriver/riverpgxv5 + ./rivershared + ./rivertype +) diff --git a/go.work.sum b/go.work.sum new file mode 100644 index 00000000..6876cb72 --- /dev/null +++ b/go.work.sum @@ -0,0 +1,38 @@ +github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM= +github.com/creack/pty v1.1.9 h1:uDmaGzcdjhF4i/plgjmEsriH11Y0o7RKapEf/LDaM3w= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/kr/pty v1.1.1 h1:VkoXIwSboBpnk99O/KFauAEILuNHv5DVFKZMBN/gUgw= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= +golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2 h1:IRJeR9r1pYWsHKTRe/IInb7lYvbBVIqOgsX/u0mbOWY= +golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= +golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= +golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +gopkg.in/errgo.v2 v2.1.0 h1:0vLT13EuvQ0hNvakwLuFZ/jYrLp5F3kcWHXdRggjCE8= diff --git a/internal/cmd/update-submodule-versions/main_test.go b/internal/cmd/update-submodule-versions/main_test.go deleted file mode 100644 index a1e9a775..00000000 --- a/internal/cmd/update-submodule-versions/main_test.go +++ /dev/null @@ -1,72 +0,0 @@ -package main - -import ( - "os" - "strings" - "testing" - - "github.com/stretchr/testify/require" - "golang.org/x/mod/modfile" - "golang.org/x/mod/module" -) - -const sampleGoMod = `module github.com/riverqueue/river - -go 1.21.0 - -replace github.com/riverqueue/river/riverdriver => ./riverdriver - -replace github.com/riverqueue/river/riverdriver/riverpgxv5 => ./riverdriver/riverpgxv5 - -replace github.com/riverqueue/river/riverdriver/riverdatabasesql => ./riverdriver/riverdatabasesql - -require ( - github.com/riverqueue/river/riverdriver v0.0.0-00010101000000-000000000000 - github.com/riverqueue/river/riverdriver/riverdatabasesql v0.0.0-00010101000000-000000000000 - github.com/riverqueue/river/riverdriver/riverpgxv5 v0.0.12 -)` - -func TestParseAndUpdateGoModFile(t *testing.T) { - t.Parallel() - - file, err := os.CreateTemp("", "go.mod") - require.NoError(t, err) - t.Cleanup(func() { os.Remove(file.Name()) }) - - _, err = file.WriteString(sampleGoMod) - require.NoError(t, err) - require.NoError(t, file.Close()) - - anyChanges, err := parseAndUpdateGoModFile(file.Name(), "v0.0.13") - require.NoError(t, err) - require.True(t, anyChanges) - - // Reread the file that the command above just wrote and make sure the right - // changes were made. - fileData, err := os.ReadFile(file.Name()) - require.NoError(t, err) - - modFile, err := modfile.Parse(file.Name(), fileData, nil) - require.NoError(t, err) - - versions := make([]module.Version, 0, len(modFile.Require)) - for _, require := range modFile.Require { - if require.Indirect || !strings.HasPrefix(require.Mod.Path, "github.com/riverqueue/river") { - continue - } - - versions = append(versions, require.Mod) - } - - require.Equal(t, []module.Version{ - {Path: "github.com/riverqueue/river/riverdriver", Version: "v0.0.13"}, - {Path: "github.com/riverqueue/river/riverdriver/riverdatabasesql", Version: "v0.0.13"}, - {Path: "github.com/riverqueue/river/riverdriver/riverpgxv5", Version: "v0.0.13"}, - }, versions) - - // Running again is allowed and should be idempontent. This time it'll - // return that no changes were made. - anyChanges, err = parseAndUpdateGoModFile(file.Name(), "v0.0.13") - require.NoError(t, err) - require.False(t, anyChanges) -} diff --git a/riverdriver/go.mod b/riverdriver/go.mod index b01459c4..ed56a0f8 100644 --- a/riverdriver/go.mod +++ b/riverdriver/go.mod @@ -4,6 +4,4 @@ go 1.21 toolchain go1.22.5 -replace github.com/riverqueue/river/rivertype => ../rivertype - require github.com/riverqueue/river/rivertype v0.11.2 diff --git a/riverdriver/go.sum b/riverdriver/go.sum index 5496456e..d1cf6bd7 100644 --- a/riverdriver/go.sum +++ b/riverdriver/go.sum @@ -2,6 +2,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/riverqueue/river/rivertype v0.11.2 h1:YREWOGxDMDe1DTdvttwr2DVq/ql65u6e4jkw3VxuNyU= +github.com/riverqueue/river/rivertype v0.11.2/go.mod h1:bm5EMOGAEWhtXKqo27POWnViqSD5nHMZDP/jsrJc530= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/riverdriver/riverdatabasesql/go.mod b/riverdriver/riverdatabasesql/go.mod index f7f4e51f..dafe8090 100644 --- a/riverdriver/riverdatabasesql/go.mod +++ b/riverdriver/riverdatabasesql/go.mod @@ -4,12 +4,6 @@ go 1.21 toolchain go1.22.5 -replace github.com/riverqueue/river/riverdriver => ../ - -replace github.com/riverqueue/river/rivershared => ../../rivershared - -replace github.com/riverqueue/river/rivertype => ../../rivertype - require ( github.com/lib/pq v1.10.9 github.com/riverqueue/river/riverdriver v0.11.2 diff --git a/riverdriver/riverdatabasesql/go.sum b/riverdriver/riverdatabasesql/go.sum index e91f6125..b09415e8 100644 --- a/riverdriver/riverdatabasesql/go.sum +++ b/riverdriver/riverdatabasesql/go.sum @@ -9,6 +9,12 @@ github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/riverqueue/river/riverdriver v0.11.2 h1:2xC+R0Y+CFEOSDWKyeFef0wqQLuvhk3PsLkos7MLa1w= +github.com/riverqueue/river/riverdriver v0.11.2/go.mod h1:RhMuAjEtNGexwOFnz445G1iFNZVOnYQ90HDYxHMI+jM= +github.com/riverqueue/river/rivershared v0.11.2 h1:VbuLE6zm68R24xBi1elfnerhLBBn6X7DUxR9j4mcTR4= +github.com/riverqueue/river/rivershared v0.11.2/go.mod h1:J4U3qm8MbjHY1o5OlRNiWaminYagec1o8sHYX4ZQ4S4= +github.com/riverqueue/river/rivertype v0.11.2 h1:YREWOGxDMDe1DTdvttwr2DVq/ql65u6e4jkw3VxuNyU= +github.com/riverqueue/river/rivertype v0.11.2/go.mod h1:bm5EMOGAEWhtXKqo27POWnViqSD5nHMZDP/jsrJc530= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= diff --git a/riverdriver/riverpgxv5/go.mod b/riverdriver/riverpgxv5/go.mod index 8277c81a..578711b8 100644 --- a/riverdriver/riverpgxv5/go.mod +++ b/riverdriver/riverpgxv5/go.mod @@ -4,12 +4,6 @@ go 1.21 toolchain go1.22.5 -replace github.com/riverqueue/river/riverdriver => ../ - -replace github.com/riverqueue/river/rivershared => ../../rivershared - -replace github.com/riverqueue/river/rivertype => ../../rivertype - require ( github.com/jackc/pgx/v5 v5.5.0 github.com/jackc/puddle/v2 v2.2.1 diff --git a/riverdriver/riverpgxv5/go.sum b/riverdriver/riverpgxv5/go.sum index 1313bbe2..6435158d 100644 --- a/riverdriver/riverpgxv5/go.sum +++ b/riverdriver/riverpgxv5/go.sum @@ -16,6 +16,12 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/riverqueue/river/riverdriver v0.11.2 h1:2xC+R0Y+CFEOSDWKyeFef0wqQLuvhk3PsLkos7MLa1w= +github.com/riverqueue/river/riverdriver v0.11.2/go.mod h1:RhMuAjEtNGexwOFnz445G1iFNZVOnYQ90HDYxHMI+jM= +github.com/riverqueue/river/rivershared v0.11.2 h1:VbuLE6zm68R24xBi1elfnerhLBBn6X7DUxR9j4mcTR4= +github.com/riverqueue/river/rivershared v0.11.2/go.mod h1:J4U3qm8MbjHY1o5OlRNiWaminYagec1o8sHYX4ZQ4S4= +github.com/riverqueue/river/rivertype v0.11.2 h1:YREWOGxDMDe1DTdvttwr2DVq/ql65u6e4jkw3VxuNyU= +github.com/riverqueue/river/rivertype v0.11.2/go.mod h1:bm5EMOGAEWhtXKqo27POWnViqSD5nHMZDP/jsrJc530= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= diff --git a/rivershared/cmd/update-mod-go/main.go b/rivershared/cmd/update-mod-go/main.go new file mode 100644 index 00000000..15618058 --- /dev/null +++ b/rivershared/cmd/update-mod-go/main.go @@ -0,0 +1,124 @@ +// update-mod-go provides a command to help bump the `go/`toolchain` directives +// in the `go.mod`s of River's internal dependencies across the project. It's +// used to check that all directives match in CI, and to give us an easy way of +// updating them all at once during upgrades. +// +// To check that directives match, run with `CHECK`: +// +// CHECK=true make update-mod-go +// +// To upgrade a `go`/`toolchain` directive, change it in the workspace's +// `go.work`, then run the program: +// +// make update-mod-go +package main + +import ( + "errors" + "fmt" + "os" + "path" + + "golang.org/x/mod/modfile" +) + +func main() { + if err := run(); err != nil { + fmt.Fprintf(os.Stderr, "failure: %s\n", err) + os.Exit(1) + } +} + +func run() error { + checkOnly := os.Getenv("CHECK") == "true" + + if len(os.Args) != 2 { + return errors.New("expected exactly one arg, which should be the path to a go.work file") + } + + workFilename := os.Args[1] + + workFileData, err := os.ReadFile(workFilename) + if err != nil { + return fmt.Errorf("error reading file %q: %w", workFilename, err) + } + + workFile, err := modfile.ParseWork(workFilename, workFileData, nil) + if err != nil { + return fmt.Errorf("error parsing file %q: %w", workFilename, err) + } + + var ( + workGoVersion = workFile.Go.Version + workToolchainName = workFile.Toolchain.Name + ) + + for _, workUse := range workFile.Use { + if _, err := parseAndUpdateGoModFile(checkOnly, "./"+path.Join(workUse.Path, "go.mod"), workFilename, workGoVersion, workToolchainName); err != nil { + return err + } + } + + if checkOnly { + fmt.Printf("go/toolchain directives in all go.mod files match workspace\n") + } + + return nil +} + +func parseAndUpdateGoModFile(checkOnly bool, modFilename, workFilename, workGoVersion, workToolchainName string) (bool, error) { + modFileData, err := os.ReadFile(modFilename) + if err != nil { + return false, fmt.Errorf("error reading file %q: %w", modFilename, err) + } + + modFile, err := modfile.Parse(modFilename, modFileData, nil) + if err != nil { + return false, fmt.Errorf("error parsing file %q: %w", modFilename, err) + } + + var anyMismatch bool + + fmt.Printf("%s\n", modFilename) + + if workGoVersion != modFile.Go.Version { + if checkOnly { + return false, fmt.Errorf("go directive of %q (%s) doesn't match %q (%s)", modFilename, modFile.Go.Version, workFilename, workGoVersion) + } + + anyMismatch = true + if err := modFile.AddGoStmt(workGoVersion); err != nil { + return false, fmt.Errorf("error adding go statement: %w", err) + } + fmt.Printf(" set go to %s for %s\n", workGoVersion, modFilename) + } + + if workToolchainName != modFile.Toolchain.Name { + if checkOnly { + return false, fmt.Errorf("toolchain directive of %q (%s) doesn't match %q (%s)", modFilename, modFile.Toolchain.Name, workFilename, workToolchainName) + } + + anyMismatch = true + if err := modFile.AddToolchainStmt(workToolchainName); err != nil { + return false, fmt.Errorf("error adding toolchain statement: %w", err) + } + fmt.Printf(" set toolchain to %s for %s\n", workToolchainName, modFilename) + } + + if !checkOnly { + if anyMismatch { + updatedFileData, err := modFile.Format() + if err != nil { + return false, fmt.Errorf("error formatting file %q after update: %w", modFilename, err) + } + + if err := os.WriteFile(modFilename, updatedFileData, 0o600); err != nil { + return false, fmt.Errorf("error writing file %q after update: %w", modFilename, err) + } + } else { + fmt.Printf(" no changes\n") + } + } + + return anyMismatch, nil +} diff --git a/rivershared/cmd/update-mod-go/main_test.go b/rivershared/cmd/update-mod-go/main_test.go new file mode 100644 index 00000000..7ec6e08b --- /dev/null +++ b/rivershared/cmd/update-mod-go/main_test.go @@ -0,0 +1,118 @@ +package main + +import ( + "fmt" + "os" + "testing" + + "github.com/stretchr/testify/require" + "golang.org/x/mod/modfile" +) + +const sampleGoMod = `module github.com/riverqueue/river + +go 1.21 + +toolchain go1.22.5 + +require ( + github.com/riverqueue/river/riverdriver v0.0.0-00010101000000-000000000000 + github.com/riverqueue/river/riverdriver/riverdatabasesql v0.0.0-00010101000000-000000000000 + github.com/riverqueue/river/riverdriver/riverpgxv5 v0.0.12 +)` + +func TestParseAndUpdateGoModFile(t *testing.T) { + t.Parallel() + + type testBundle struct{} + + setup := func(t *testing.T) (string, *testBundle) { //nolint:unparam + t.Helper() + + file, err := os.CreateTemp("", "go.mod") + require.NoError(t, err) + t.Cleanup(func() { os.Remove(file.Name()) }) + + _, err = file.WriteString(sampleGoMod) + require.NoError(t, err) + require.NoError(t, file.Close()) + + return file.Name(), &testBundle{} + } + + requireDirectives := func(t *testing.T, filename, goVersion, toolchainName string) { + t.Helper() + + fileData, err := os.ReadFile(filename) + require.NoError(t, err) + + modFile, err := modfile.Parse(filename, fileData, nil) + require.NoError(t, err) + + require.Equal(t, goVersion, modFile.Go.Version) + require.Equal(t, toolchainName, modFile.Toolchain.Name) + } + + t.Run("WritesChanges", func(t *testing.T) { + t.Parallel() + + filename, _ := setup(t) + + anyMismatch, err := parseAndUpdateGoModFile(false, filename, "go.work", "1.22", "go1.22.6") + require.NoError(t, err) + require.True(t, anyMismatch) + + // Reread the file that the command above just wrote and make sure the right + // changes were made. + requireDirectives(t, filename, "1.22", "go1.22.6") + + // Running again is allowed and should be idempontent. This time it'll + // return that no changes were made. + anyMismatch, err = parseAndUpdateGoModFile(false, filename, "go.work", "1.22", "go1.22.6") + require.NoError(t, err) + require.False(t, anyMismatch) + }) + + t.Run("NoChanges", func(t *testing.T) { + t.Parallel() + + filename, _ := setup(t) + + anyMismatch, err := parseAndUpdateGoModFile(false, filename, "go.work", "1.21", "go1.22.5") + require.NoError(t, err) + require.False(t, anyMismatch) + + // Expect no changes made in file. + requireDirectives(t, filename, "1.21", "go1.22.5") + }) + + t.Run("CheckOnlyGoMismatch", func(t *testing.T) { + t.Parallel() + + filename, _ := setup(t) + + _, err := parseAndUpdateGoModFile(true, filename, "go.work", "1.22", "go1.22.5") + require.EqualError(t, err, fmt.Sprintf("go directive of %q (%s) doesn't match %q (%s)", filename, "1.21", "go.work", "1.22")) + }) + + t.Run("CheckOnlyToolchainMismatch", func(t *testing.T) { + t.Parallel() + + filename, _ := setup(t) + + _, err := parseAndUpdateGoModFile(true, filename, "go.work", "1.21", "go1.22.6") + require.EqualError(t, err, fmt.Sprintf("toolchain directive of %q (%s) doesn't match %q (%s)", filename, "go1.22.5", "go.work", "go1.22.6")) + }) + + t.Run("CheckOnlyNoChanges", func(t *testing.T) { + t.Parallel() + + filename, _ := setup(t) + + anyMismatch, err := parseAndUpdateGoModFile(true, filename, "go.work", "1.21", "go1.22.5") + require.NoError(t, err) + require.False(t, anyMismatch) + + requireDirectives(t, filename, "1.21", "go1.22.5") + }) +} diff --git a/internal/cmd/update-submodule-versions/main.go b/rivershared/cmd/update-mod-version/main.go similarity index 66% rename from internal/cmd/update-submodule-versions/main.go rename to rivershared/cmd/update-mod-version/main.go index 6ca15c8d..f5d45b31 100644 --- a/internal/cmd/update-submodule-versions/main.go +++ b/rivershared/cmd/update-mod-version/main.go @@ -1,6 +1,10 @@ -// Package main provides a command to help bump the versions of River's internal -// dependencies in the `go.mod` files of submodules across the project. It's -// used to make the release process less error prone and less painful. +// update-mod-version provides a command to help bump the versions of River's +// internal dependencies in the `go.mod` files of submodules across the project. +// It's used to make the release process less error prone and less painful. +// +// Run it with a make target: +// +// VERSION=v0.x.y make update-mod-version package main import ( @@ -14,23 +18,9 @@ import ( "golang.org/x/mod/semver" ) -// Notably, `./cmd/river` is excluded from this list. Unlike the other modules, -// it doesn't use `replace` directives so that it can stay installable with `go -// install ...@latest`. Without `replace`, dependencies need a hard lock in -// `go.sum`, so any changes to its `go.mod` file would require a `go mod tidy` -// be run afterwards. -var allProjectModules = []string{ //nolint:gochecknoglobals - ".", - "./riverdriver", - "./riverdriver/riverdatabasesql", - "./riverdriver/riverpgxv5", - "./rivershared", - "./rivertype", -} - func main() { if err := run(); err != nil { - fmt.Fprintf(os.Stderr, "failure: %s", err) + fmt.Fprintf(os.Stderr, "failure: %s\n", err) os.Exit(1) } } @@ -45,8 +35,24 @@ func run() error { return fmt.Errorf("invalid semver version: %s", version) } - for _, dir := range allProjectModules { - if _, err := parseAndUpdateGoModFile(path.Join(dir, "go.mod"), version); err != nil { + if len(os.Args) != 2 { + return errors.New("expected exactly one arg, which should be the path to a go.work file") + } + + workFilename := os.Args[1] + + workFileData, err := os.ReadFile(workFilename) + if err != nil { + return fmt.Errorf("error reading file %q: %w", workFilename, err) + } + + workFile, err := modfile.ParseWork(workFilename, workFileData, nil) + if err != nil { + return fmt.Errorf("error parsing file %q: %w", workFilename, err) + } + + for _, workUse := range workFile.Use { + if _, err := parseAndUpdateGoModFile("./"+path.Join(workUse.Path, "go.mod"), version); err != nil { return err } } diff --git a/rivershared/cmd/update-mod-version/main_test.go b/rivershared/cmd/update-mod-version/main_test.go new file mode 100644 index 00000000..a54843f1 --- /dev/null +++ b/rivershared/cmd/update-mod-version/main_test.go @@ -0,0 +1,82 @@ +package main + +import ( + "os" + "strings" + "testing" + + "github.com/stretchr/testify/require" + "golang.org/x/mod/modfile" + "golang.org/x/mod/module" +) + +const sampleGoMod = `module github.com/riverqueue/river + +go 1.21 + +toolchain go1.22.5 + +require ( + github.com/riverqueue/river/riverdriver v0.0.0-00010101000000-000000000000 + github.com/riverqueue/river/riverdriver/riverdatabasesql v0.0.0-00010101000000-000000000000 + github.com/riverqueue/river/riverdriver/riverpgxv5 v0.0.12 +)` + +func TestParseAndUpdateGoModFile(t *testing.T) { + t.Parallel() + + type testBundle struct{} + + setup := func(t *testing.T) (string, *testBundle) { + t.Helper() + + file, err := os.CreateTemp("", "go.mod") + require.NoError(t, err) + t.Cleanup(func() { os.Remove(file.Name()) }) + + _, err = file.WriteString(sampleGoMod) + require.NoError(t, err) + require.NoError(t, file.Close()) + + return file.Name(), &testBundle{} + } + + t.Run("WritesChanges", func(t *testing.T) { + t.Parallel() + + filename, _ := setup(t) + + anyChanges, err := parseAndUpdateGoModFile(filename, "v0.0.13") + require.NoError(t, err) + require.True(t, anyChanges) + + // Reread the file that the command above just wrote and make sure the right + // changes were made. + fileData, err := os.ReadFile(filename) + require.NoError(t, err) + + modFile, err := modfile.Parse(filename, fileData, nil) + require.NoError(t, err) + + versions := make([]module.Version, 0, len(modFile.Require)) + for _, require := range modFile.Require { + if require.Indirect || !strings.HasPrefix(require.Mod.Path, "github.com/riverqueue/river") { + continue + } + + versions = append(versions, require.Mod) + } + + require.Equal(t, []module.Version{ + {Path: "github.com/riverqueue/river/riverdriver", Version: "v0.0.13"}, + {Path: "github.com/riverqueue/river/riverdriver/riverdatabasesql", Version: "v0.0.13"}, + {Path: "github.com/riverqueue/river/riverdriver/riverpgxv5", Version: "v0.0.13"}, + }, versions) + + // Running again is allowed and should be idempontent. This time it'll + // return that no changes were made. + anyChanges, err = parseAndUpdateGoModFile(filename, "v0.0.13") + require.NoError(t, err) + require.False(t, anyChanges) + }) +} diff --git a/rivershared/go.mod b/rivershared/go.mod index a9ebe018..86d93d0a 100644 --- a/rivershared/go.mod +++ b/rivershared/go.mod @@ -7,6 +7,7 @@ toolchain go1.22.5 require ( github.com/stretchr/testify v1.9.0 go.uber.org/goleak v1.3.0 + golang.org/x/mod v0.9.0 ) require ( diff --git a/rivershared/go.sum b/rivershared/go.sum index 39549b18..e03f7242 100644 --- a/rivershared/go.sum +++ b/rivershared/go.sum @@ -18,6 +18,8 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs= +golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=