この記事は Applibot Advent Calendar 2024 22日目の記事です。前回の記事は コチラ です。
本記事の目的
Nx を用いて非 Javascript プロジェクトのモノレポを構築する際に、公式ドキュメントにはチュートリアルなどでまとまっておらず少し困ったので、自分がドキュメントを読みながら試行錯誤して行った手順をまとめる。
構築するもの
- 複数の
go.mod
をもつ Go のモノレポに Nx を導入する - Go 以外の言語を増やした場合でも使える手順・ディレクトリ構成にする
- main ブランチとの差分を検知して、変更があるモジュールのみフォーマット・リント・テスト・ビルドできるようにする
- どのようにフォーマットやリントするかなどを柔軟に設定できる
最終ディレクトリ構成
.
├── .gitignore
├── .nx
│ ├── cache
│ │ ├── run.json
│ │ └── terminalOutputs
│ ├── installation
│ │ ├── node_modules
│ │ ├── package-lock.json
│ │ └── package.json
│ ├── nxw.js
│ └── workspace-data
├── Makefile
├── go
│ ├── .golangci.yaml
│ ├── app
│ │ └── bff
│ │ ├── Makefile
│ │ ├── cmd
│ │ │ └── api
│ │ │ └── main.go
│ │ ├── go.mod
│ │ └── project.json
│ ├── pkg
│ │ └── logger
│ │ ├── Makefile
│ │ ├── go.mod
│ │ ├── go.sum
│ │ ├── logger.go
│ │ └── project.json
│ └── tools
├── nx
├── nx.bat
└── nx.json
前提
- デバイスは Macbook
- Node.js がインストールされている
- Go がインストールされている
- Github をレポジトリに使っている(手順3)
初期ディレクトリ構成
.
└── go
├── .golangci.yaml
├── app
│ └── bff
│ ├── cmd
│ │ └── api
│ │ └── main.go
│ └── go.mod
├── pkg
│ └── logger
│ ├── go.mod
│ ├── go.sum
│ └── logger.go
└── tools
補足
なお、サードパーティのプラグインとして @nx-go/nx-go が存在するが、フォーマットに goimports を使ったり、リントに golangci-lint を使ったりすることができなさそうだったため、今回は使わないものとする。
手順
1. Nx のセットアップ
公式ドキュメントの「Install Nx in a Non-Javascript Repo」のように init コマンドをルートディレクトリで実行する。(要 Node.js)
$ npx nx init
Setting Nx up installation in `.nx`. You can run Nx commands like: `./nx --help`
CREATE nx.json
CREATE .gitignore
CREATE .nx/nxw.js
CREATE nx.bat
CREATE nx
生成される nx
ファイルがコマンドを実行するためのファイル、nx.json
が設定ファイル、.nx/
配下にプラグインやキャッシュなどが格納される。
nx.json
に下記の修正をいれる。(JSON Schema の設定と、変更差分を検知するためのベースブランチ設定)
{
+ "$schema": ".nx/installation/node_modules/nx/schemas/nx-schema.json",
"installation": {
"version": "20.3.0"
- }
+ },
+ "defaultBase": "main"
}
2. 既存の Go のモジュールを Nx プロジェクト化する
go.mod
があるディレクトリに、project.json
を作成する。(下記は go/app/bff
の例)
project.json
を作成することで、Nx にプロジェクトとして認識されるようになる。
{
"name": "go/app/bff",
"$schema": "../../../.nx/installation/node_modules/nx/schemas/project-schema.json",
"projectType": "application",
"sourceRoot": "go/app/bff",
"tags": [],
"targets": {
"build": {
"command": "go build -o bin/bff-api ./cmd/api",
"options": {
"cwd": "go/app/bff"
}
},
"serve": {
"command": "go run cmd/api/main.go",
"options": {
"cwd": "go/app/bff"
}
},
"fmt": {
"command": "go fmt ./...",
"options": {
"cwd": "go/app/bff"
}
},
"lint": {
"command": "go vet ./...",
"options": {
"cwd": "go/app/bff"
}
},
"test": {
"command": "go test -v ./...",
"options": {
"cwd": "go/app/bff"
}
}
}
}
上記の targets
に列挙されているものがそれぞれ Nx タスクの識別子となる。
JSON Schema を参照し、go/pkg/logger
のものも作成する。(上記例で、go/app/bff
を go/pkg/logger
に置き換え、projectType
を library
にする)
タスク実行方法
例えばルートディレクトリで下記コマンドを実行することで、指定プロジェクトの test タスクを実行できる。
$ ./nx test go/app/bff
全てのプロジェクトの指定タスクを実行する場合は、下記コマンドとなる。
$ ./nx run-many -t test
main ブランチとの変更差分があるプロジェクトのみで指定タスクを実行する場合は、下記コマンドとなる。
$ ./nx affected -t test
また、エディタ拡張の Nx Console を使うと以下のように GUI で各コマンドを実行できるようになる。(下記は Jetbrains の例)
3. コマンドをカスタマイズする(こちらは一例)
今回は、コマンドのカスタマイズとして下記を一元的に記述して実行するために Makefile
を用いる。
- fmt は、goimports・gofumpt・go mod tidy が全て実行されるようにする
- lint は、 golangci-lint を使用し、設定ファイルは共通のものを使う。また、govulncheck も実行する
- Go のプライベートレポジトリ参照も考慮したコマンドを実行できるようにする
プロジェクトのルートディレクトリに下記 Makefile
を作成する。
<your-github-org>
と<your-github-repository>
は適切に変えてください。
### General
SHELL := /bin/bash
ROOT := $(abspath $(dir $(lastword $(MAKEFILE_LIST))))
BIN := $(ROOT)/bin
### GitHub
GITHUB_ORG := <your-github-org>
REPOSITORY_NAME := <your-github-repository>
### Go
GO ?= go
GO_ENV ?= CGO_ENABLED=0 GOPRIVATE=github.com/${GITHUB_ORG} GOBIN=$(BIN)
GO_MODULE ?= github.com/${GITHUB_ORG}/${REPOSITORY_NAME}
### Tools
$(shell mkdir -p $(BIN))
GOIMPORTS_VERSION := 0.28.0
$(BIN)/goimports-$(GOIMPORTS_VERSION):
unlink $(BIN)/goimports || true
$(GO_ENV) ${GO} install golang.org/x/tools/cmd/goimports@v$(GOIMPORTS_VERSION)
mv $(BIN)/goimports $(BIN)/goimports-$(GOIMPORTS_VERSION)
ln -s $(BIN)/goimports-$(GOIMPORTS_VERSION) $(BIN)/goimports
GOFUMPT_VERSION := 0.7.0
$(BIN)/gofumpt-$(GOFUMPT_VERSION):
unlink $(BIN)/gofumpt || true
$(GO_ENV) ${GO} install mvdan.cc/gofumpt@v$(GOFUMPT_VERSION)
mv $(BIN)/gofumpt $(BIN)/gofumpt-$(GOFUMPT_VERSION)
ln -s $(BIN)/gofumpt-$(GOFUMPT_VERSION) $(BIN)/gofumpt
GOLANGCI_LINT_VERSION := 1.62.2
$(BIN)/golangci-lint-$(GOLANGCI_LINT_VERSION):
unlink $(BIN)/golangci-lint || true
$(GO_ENV) ${GO} install github.com/golangci/golangci-lint/cmd/golangci-lint@v$(GOLANGCI_LINT_VERSION)
mv $(BIN)/golangci-lint $(BIN)/golangci-lint-$(GOLANGCI_LINT_VERSION)
ln -s $(BIN)/golangci-lint-$(GOLANGCI_LINT_VERSION) $(BIN)/golangci-lint
GOVULNCHECK_VERSION := 1.1.3
$(BIN)/govulncheck-$(GOVULNCHECK_VERSION):
unlink $(BIN)/govulncheck || true
$(GO_ENV) ${GO} install golang.org/x/vuln/cmd/govulncheck@v$(GOVULNCHECK_VERSION)
mv $(BIN)/govulncheck $(BIN)/govulncheck-$(GOVULNCHECK_VERSION)
ln -s $(BIN)/govulncheck-$(GOVULNCHECK_VERSION) $(BIN)/govulncheck
### Commands
.PHONY: initialize
initialize: initialize-once initialize-once-go
.PHONY: initialize-once
initialize-once:
git config --global url.git@github.com:${GITHUB_ORG}/.insteadOf https://github.com${GITHUB_ORG}/ # NOTE: go でマルチモジュール構成を取っており、プライベートレポジトリを参照するための設定
.PHONY: initialize-once-go
initialize-once-go: $(BIN)/goimports-$(GOIMPORTS_VERSION) $(BIN)/gofumpt-$(GOFUMPT_VERSION) $(BIN)/golangci-lint-$(GOLANGCI_LINT_VERSION) $(BIN)/govulncheck-$(GOVULNCHECK_VERSION)
### Development Commands
.PHONY: fmt
fmt:
./nx affected -t fmt
.PHONY: lint
lint:
./nx affected -t lint
.PHONY: test
test:
./nx affected -t test
.PHONY: graph
graph:
./nx graph
### Go Commands
.PHONY: fmt-go
fmt-go: $(BIN)/goimports-$(GOIMPORTS_VERSION) $(BIN)/gofumpt-$(GOFUMPT_VERSION)
$(GO_ENV) $(GO) mod tidy
$(BIN)/goimports -local $(GO_MODULE) -w ./
$(BIN)/gofumpt -l -w ./
.PHONY: lint-go
lint-go: $(BIN)/golangci-lint-$(GOLANGCI_LINT_VERSION) $(BIN)/govulncheck-$(GOVULNCHECK_VERSION)
$(BIN)/golangci-lint run ./... -v -c $(ROOT)/go/.golangci.yaml ./...
$(BIN)/govulncheck ./...
.PHONY: test-go
test-go:
$(GO_ENV) $(GO) test -v ./...
Makefile
により、make コマンドでカスタマイズしたフォーマット・リント・テストが実行できるようになる。
上記 Makefile
を include した Makefile
を各プロジェクトに作成し、project.json
のコマンドを下記のように修正する。
include ../../../Makefile
diff --git a/go/app/bff/project.json b/go/app/bff/project.json
index 7a9d997..a84c147 100644
--- a/go/app/bff/project.json
+++ b/go/app/bff/project.json
@@ -18,19 +18,19 @@
}
},
"fmt": {
- "command": "go fmt ./...",
+ "command": "make fmt-go",
"options": {
"cwd": "go/app/bff"
}
},
"lint": {
- "command": "go vet ./...",
+ "command": "make lint-go",
"options": {
"cwd": "go/app/bff"
}
},
"test": {
- "command": "go test -v ./...",
+ "command": "make test-go",
"options": {
"cwd": "go/app/bff"
}
go/pkg/logger
も同様のファイル作成と変更を加える。
おわりに
以上で、Makefile
によりどのようにフォーマットやリントするかなどを柔軟に設定できる Nx を用いた Go のモノレポを作成することができる。
ルートディレクトリで、make fmt
などと実行することで main ブランチとの差分を検知して、変更があるモジュールのみにフォーマットなどをかけることができる。
2 の手順までは他言語でも同様であり、コマンドをカスタマイズすることで柔軟にフォーマットなどをカスタマイズすることができる。
ネクストアクションとして、キャッシュの最適化に関するドキュメントを記載しておく。