From 64f113a9faf4a226ce4b628a30e9e94c5913391e Mon Sep 17 00:00:00 2001 From: Mike Landau Date: Fri, 29 Mar 2024 13:01:27 -0700 Subject: [PATCH 1/2] [go] Add go monorepo plugin --- README.md | 1 + go-monorepo/README.md | 5 ++ go-monorepo/gotidy.sh | 149 ++++++++++++++++++++++++++++++++++++++++ go-monorepo/plugin.json | 43 ++++++++++++ 4 files changed, 198 insertions(+) create mode 100644 go-monorepo/README.md create mode 100755 go-monorepo/gotidy.sh create mode 100644 go-monorepo/plugin.json diff --git a/README.md b/README.md index 7b765cc..d59ee12 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@ This repository includes plugins for configuring packages using Devbox. ## Available Plugins +* Go Monorepo (experimental) * MongoDB * RabbitMQ * NATS Server diff --git a/go-monorepo/README.md b/go-monorepo/README.md new file mode 100644 index 0000000..c6ed501 --- /dev/null +++ b/go-monorepo/README.md @@ -0,0 +1,5 @@ +# Go Monorepo Plugin + +Experimental! + +This plugin helps manage go monorepos. diff --git a/go-monorepo/gotidy.sh b/go-monorepo/gotidy.sh new file mode 100755 index 0000000..e4d398d --- /dev/null +++ b/go-monorepo/gotidy.sh @@ -0,0 +1,149 @@ +#!/bin/bash +# shellcheck shell=sh + +set -e + +if [ "$1" = "-h" ] || [ "$1" = "-help" ] || [ "$1" = "--help" ]; then + echo "usage: $0 [-u | -u direct | -u go]" + echo + echo "gotidy tidies the go.mod files in the current repo and checks that each module" + echo "compiles independently of the workspace. If -u is set, it also updates the" + echo "dependencies of each module. If -u is set to \"go\" then it only updates the Go" + echo "version. If -u is set to \"direct\" then it only updates the direct dependencies" + echo "of each module. Restricting updates to direct dependencies is useful when there" + echo "are prerelease packages with updates that aren't backwards compatible." + exit 2 +fi + +if [ "$1" = "-u" ]; then + case "$2" in + "") + update_deps=1 + ;; + "direct") + update_deps=1 + direct_deps_only=1 + ;; + "go") + update_go=1 + ;; + *) + echo "invalid value \"$2\" for -u flag (must be empty, \"direct\", or \"go\")" + exit 1 + ;; + esac +elif [ "$1" != "" ]; then + echo "invalid flag \"$1\"" + exit 1 +fi + +# Restore the current directory before exiting. +pwd="$(pwd)" +trap 'cd "${pwd}"' EXIT INT TERM HUP + +# Start at the repo root to get a list of all Go modules in the workspace. +repo="$(git rev-parse --show-toplevel)" +cd "${repo}" + +# go work use to make sure the workspace Go version is compatible with the +# modules' Go versions. +go work use +mods="$(go list -m -f "{{`{{.Dir}}`}}")" + +# Disable workspaces to ensure that each module builds successfully outside of +# the workspace. This is important for Docker containers or mirrored open +# source repos where the workspace won't exist. +export GOWORK=off + +# Update the Go version first, since that will influence how Go updates +# dependencies and tidies the modules. +if [ "${update_go}" = 1 ]; then + for dir in ${mods}; do + if ! cd "${dir}"; then + echo "$0: ${dir}: skipping directory" + continue + fi + + echo "$0: ${dir}: checking for newer Go version" + go get go@latest + done +fi + +if [ "${update_deps}" = 1 ]; then + for dir in ${mods}; do + if ! cd "${dir}"; then + echo "$0: ${dir}: skipping directory" + continue + fi + + # Tidy before the update to make sure all the necessary + # dependencies are in go.mod. + echo "$0: ${dir}: tidying go.mod" + go mod tidy -e + + # Manually list out the dependencies instead of go get -u ./... + # so we can filter them. + echo "$0: ${dir}: checking for dependency updates" + deps="$(go mod edit -json | jq -c ".Require[]")" + for dep in ${deps}; do + dep_path="$(echo "${dep}" | jq -r ".Path")" + if [ "${direct_deps_only}" = 1 ] && echo "${dep}" | jq -e ".Indirect" >/dev/null; then + echo "$0: ${dir}: skipping indirect dependency ${dep_path}" + continue + fi + + # To temporarily skip a problematic update: + # + # if [ "${dep_path}" = "bad_dependency" ]; then + # continue + # fi + echo "$0: ${dir}: checking for updates to ${dep_path}" + go get -u -t "${dep_path}" + done + done +fi + +# Final tidy and ensure all packages build without the workspace. +for dir in ${mods}; do + if ! cd "${dir}"; then + echo "$0: ${dir}: skipping directory" + continue + fi + + echo "gotidy ${dir}: tidying go.mod" + go mod tidy + + echo "gotidy ${dir}: downloading dependencies" + go mod download + + echo "gotidy ${dir}: formatting module" + go fmt ./... + + echo "gotidy ${dir}: building module" + go build ./... + + echo "gotidy ${dir}: building module tests" + go test -c -o /dev/null ./... +done + +# Reenable the workspace to sync all of the modules' transitive +# dependencies and ensure everything still builds. +export GOWORK=auto +cd "${repo}" +go work sync +go mod download + +for dir in ${mods}; do + if ! cd "${dir}"; then + echo "$0: ${dir}: skipping directory" + continue + fi + + echo "gotidy ${dir}: building module" + go build ./... + + echo "gotidy ${dir}: building module tests" + go test -c -o /dev/null ./... +done + +git --no-pager diff --stat diff --git a/go-monorepo/plugin.json b/go-monorepo/plugin.json new file mode 100644 index 0000000..d0d2c33 --- /dev/null +++ b/go-monorepo/plugin.json @@ -0,0 +1,43 @@ +{ + "name": "go-monorepo", + "description": "Go monorepo plugin", + "packages": { + "go": "latest", + "golangci-lint": "latest", + }, + "env": { + "GOENV": "off", + }, + "shell": { + "init_hook": [ + // Remove Go environment variables that might've been inherited from the + // user's environment and could affect the build. + "test -z $FISH_VERSION && \\", + "unset CGO_ENABLED GO111MODULE GOARCH GOFLAGS GOMOD GOOS GOROOT GOTOOLCHAIN GOWORK || \\", + "set --erase CGO_ENABLED GO111MODULE GOARCH GOFLAGS GOMOD GOOS GOROOT GOTOOLCHAIN GOWORK", + // Shell function that evals its arguments inside each Go module in the + // workspace. For example, `for_each_gomod go test ./...` runs tests for + // every module. + "for_each_gomod() {", + " pwd=\"$(pwd)\"", + " for dir in $(go list -m -f '{{`{{.Dir}}`}}'); do", + " echo \"$dir: $*\" && cd \"$dir\" && \"$@\"", + " done", + " cd \"$pwd\" || return", + "}" + ], + "scripts": { + "tidy": "bash {{.Virtenv}}/gotidy.sh", + "update": "bash {{.Virtenv}}/gotidy.sh -u", + "build": "for_each_gomod go build -v ./...", + // TODO: fmt and lint is not correctly running on all projects. + // Some projects need "devbox run fmt" and "devbox run lint". + "fmt": "for_each_gomod go fmt ./...", + "lint": "for_each_gomod golangci-lint run --timeout 300s", + "test": "for_each_gomod go test -race -cover -v ./...", + } + }, + "create_files": { + "{{.Virtenv}}/gotidy.sh": "gotidy.sh", + } +} From 3e334833e7e5113d0d8b2fb706d3f53708f04b0f Mon Sep 17 00:00:00 2001 From: Mike Landau Date: Mon, 1 Apr 2024 12:36:47 -0700 Subject: [PATCH 2/2] added credit for gotidy --- go-monorepo/gotidy.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/go-monorepo/gotidy.sh b/go-monorepo/gotidy.sh index e4d398d..2624c75 100755 --- a/go-monorepo/gotidy.sh +++ b/go-monorepo/gotidy.sh @@ -1,6 +1,8 @@ #!/bin/bash # shellcheck shell=sh +# Credit to @gcurtis for this script! + set -e if [ "$1" = "-h" ] || [ "$1" = "-help" ] || [ "$1" = "--help" ]; then