Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ This repository includes plugins for configuring packages using Devbox.

## Available Plugins

* Go Monorepo (experimental)
* MongoDB
* RabbitMQ
* NATS Server
Expand Down
5 changes: 5 additions & 0 deletions go-monorepo/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Go Monorepo Plugin

Experimental!

This plugin helps manage go monorepos.
151 changes: 151 additions & 0 deletions go-monorepo/gotidy.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
#!/bin/bash
# shellcheck shell=sh

# Credit to @gcurtis for this script!

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
43 changes: 43 additions & 0 deletions go-monorepo/plugin.json
Original file line number Diff line number Diff line change
@@ -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",
}
}