diff --git a/AGENTS.md b/AGENTS.md index 8120ecc36..0e93a89e5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -8,6 +8,8 @@ This file is a **map**, not a manual. Follow links for details. |---------|---------| | `make build` | Compile the project | | `make lint` | Run linter (must pass before committing) | +| `make lint-changed-local` | Run native local linter on changes vs base | +| `make install-custom-gcl` | Build native `custom-gcl` for this host | | `make fmt` | Format all Go source files | | `make unit pkg= case=` | Run unit tests | | `make unit log="stdlog trace" pkg= case=` | Unit tests with debug logs | @@ -49,6 +51,8 @@ Body wrapped at 72 characters. Explain WHY, not just WHAT. 1. **Never edit generated code** — regenerate via `make rpc` or `make sqlc`. 2. **Never write raw SQL in Go** — add queries to `db/queries/`, use sqlc. 3. **Run `make lint` before every commit.** + Install native `custom-gcl` with `make install-custom-gcl` first if you + want the local no-Docker path to load the real `ll` plugin. 4. **Run tests before every commit** — see [`docs/testing-guide.md`](docs/testing-guide.md). 5. **No underscores in Go test names** — `TestFoo` not `Test_Foo`. 6. Use early returns; do not nest error handling. diff --git a/CLAUDE.md b/CLAUDE.md index 8120ecc36..0e93a89e5 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -8,6 +8,8 @@ This file is a **map**, not a manual. Follow links for details. |---------|---------| | `make build` | Compile the project | | `make lint` | Run linter (must pass before committing) | +| `make lint-changed-local` | Run native local linter on changes vs base | +| `make install-custom-gcl` | Build native `custom-gcl` for this host | | `make fmt` | Format all Go source files | | `make unit pkg= case=` | Run unit tests | | `make unit log="stdlog trace" pkg= case=` | Unit tests with debug logs | @@ -49,6 +51,8 @@ Body wrapped at 72 characters. Explain WHY, not just WHAT. 1. **Never edit generated code** — regenerate via `make rpc` or `make sqlc`. 2. **Never write raw SQL in Go** — add queries to `db/queries/`, use sqlc. 3. **Run `make lint` before every commit.** + Install native `custom-gcl` with `make install-custom-gcl` first if you + want the local no-Docker path to load the real `ll` plugin. 4. **Run tests before every commit** — see [`docs/testing-guide.md`](docs/testing-guide.md). 5. **No underscores in Go test names** — `TestFoo` not `Test_Foo`. 6. Use early returns; do not nest error handling. diff --git a/Makefile b/Makefile index 28d070c71..b6340534a 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ .PHONY: sqlc sqlc-check migrate-create migrate-up migrate-down gen -.PHONY: lint lint-source lint-local lint-source-local lint-changed-local local-custom-gcl docker-tools fmt fmt-check tidy-module tidy-module-check schema-check doc-check +.PHONY: lint lint-source lint-local lint-source-local lint-changed-local local-custom-gcl install-custom-gcl docker-tools fmt fmt-check tidy-module tidy-module-check schema-check doc-check .PHONY: ast-lint ast-grep-fix .PHONY: unit unit-cover unit-race check-go-version build install clean release .PHONY: build rpc install help clean-networks @@ -212,45 +212,10 @@ docker-tools: docker build -q -t darepo-tools $(TOOLS_DIR) local-custom-gcl: - @mkdir -p $(TOOLS_DIR) - @if command -v custom-gcl >/dev/null 2>&1; then \ - ln -sf "$$(command -v custom-gcl)" "$(LOCAL_CUSTOM_GCL)"; \ - echo "Using custom-gcl from PATH."; \ - elif command -v go >/dev/null 2>&1; then \ - printf '%s\n' \ - '#!/bin/sh' \ - 'set -e' \ - 'run_golangci() {' \ - ' exec go run github.com/golangci/golangci-lint/cmd/golangci-lint@v1.64.5 "$$@"' \ - '}' \ - 'if [ "$$1" = "run" ]; then' \ - ' shift' \ - ' tmp="$$(mktemp)"' \ - ' cfg="$$tmp.yml"' \ - ' mv "$$tmp" "$$cfg"' \ - ' trap '\''rm -f "$$cfg"'\'' EXIT' \ - ' awk '\''{' \ - ' if ($$0 ~ /^linters-settings:[[:space:]]*$$/) {in_ls=1; print; next}' \ - ' if (in_ls && $$0 ~ /^ custom:[[:space:]]*$$/) {skip_custom=1; next}' \ - ' if (skip_custom && $$0 ~ /^ [A-Za-z0-9_-]+:[[:space:]]*$$/) {skip_custom=0}' \ - ' if (in_ls && $$0 ~ /^[^[:space:]]/) {in_ls=0}' \ - ' if (skip_custom) {next}' \ - ' if ($$0 ~ /^[[:space:]]*-[[:space:]]*ll[[:space:]]*$$/) {sub(/ll/, "lll"); print; next}' \ - ' print $$0' \ - ' }'\'' .golangci.yml > "$$cfg"' \ - ' run_golangci run --config "$$cfg" "$$@"' \ - 'fi' \ - 'run_golangci "$$@"' \ - > "$(LOCAL_CUSTOM_GCL)"; \ - chmod +x "$(LOCAL_CUSTOM_GCL)"; \ - echo "custom-gcl not found; using golangci-lint v1.64.5 fallback"; \ - echo "(custom linter plugin 'll' is disabled in local mode)."; \ - elif [ -x "$(LOCAL_CUSTOM_GCL)" ]; then \ - echo "Using local linter binary: $(LOCAL_CUSTOM_GCL)"; \ - else \ - echo "error: install go or custom-gcl"; \ - exit 1; \ - fi + @./scripts/local-custom-gcl.sh "$(LOCAL_CUSTOM_GCL)" + +install-custom-gcl: #? Build and install a native custom-gcl binary to dest= (default: ./tools/custom-gcl) + @./scripts/install-custom-gcl.sh "$(if $(dest),$(dest),$(LOCAL_CUSTOM_GCL))" lint-source: docker-tools @$(call print, "Linting source.") diff --git a/scripts/install-custom-gcl.sh b/scripts/install-custom-gcl.sh new file mode 100755 index 000000000..7a5dd5592 --- /dev/null +++ b/scripts/install-custom-gcl.sh @@ -0,0 +1,118 @@ +#!/bin/sh + +set -eu + +usage() { + cat <<'EOF2' +usage: install-custom-gcl.sh + +Build a native custom-gcl binary for the current host OS/ARCH using the +repo-local ll linter plugin and install it at . +EOF2 +} + +if [ "${1:-}" = "" ] || [ "${1:-}" = "-h" ] || [ "${1:-}" = "--help" ]; then + usage + exit 1 +fi + +dest="$1" + +if [ -d "$dest" ]; then + echo "error: destination cannot be a directory: $dest" >&2 + exit 1 +fi + +script_dir=$(CDPATH='' cd -- "$(dirname "$0")" && pwd) +repo_root=$(CDPATH='' cd -- "$script_dir/.." && pwd) +tools_dir="$repo_root/tools" +config_file="$tools_dir/.custom-gcl.yml" +plugin_dir="$tools_dir/linters" + +if ! command -v go >/dev/null 2>&1; then + echo "error: go is required to build custom-gcl" >&2 + exit 1 +fi + +if ! command -v git >/dev/null 2>&1; then + echo "error: git is required to build custom-gcl" >&2 + exit 1 +fi + +if [ ! -f "$config_file" ]; then + echo "error: missing config file: $config_file" >&2 + exit 1 +fi + +if [ ! -d "$plugin_dir" ]; then + echo "error: missing plugin module directory: $plugin_dir" >&2 + exit 1 +fi + +version=$(sed -n 's/^version:[[:space:]]*//p' "$config_file" | head -n 1) +plugin_module=$( + sed -n 's/^[[:space:]]*-[[:space:]]*module:[[:space:]]*//p' \ + "$config_file" | head -n 1 | tr -d "'\"" +) + +if [ -z "$version" ]; then + echo "error: unable to determine golangci-lint version" >&2 + exit 1 +fi + +if [ -z "$plugin_module" ]; then + echo "error: unable to determine plugin module path" >&2 + exit 1 +fi + +tmpdir=$(mktemp -d "${TMPDIR:-/tmp}/custom-gcl.XXXXXX") +trap 'rm -rf "$tmpdir"' EXIT HUP INT TERM + +repo_dir="$tmpdir/golangci-lint" +tmp_bin="$tmpdir/custom-gcl" + +echo "Building native custom-gcl ${version} for $(go env GOOS)/$(go env GOARCH)" +echo "Using plugin module: ${plugin_module}" + +GIT_CONFIG_GLOBAL=/dev/null \ +GIT_TERMINAL_PROMPT=0 \ +git clone \ + --branch "$version" \ + --single-branch \ + --depth 1 \ + -c advice.detachedHead=false \ + -q \ + https://github.com/golangci/golangci-lint.git \ + "$repo_dir" + +cat >"$repo_dir/cmd/golangci-lint/plugins.go" </dev/null 2>&1; then + ln -sf "$(command -v custom-gcl)" "$dest" + echo "Using custom-gcl from PATH." + exit 0 +fi + +if [ -x "$dest" ]; then + echo "Using local linter binary: $dest" + exit 0 +fi + +if command -v go >/dev/null 2>&1; then + if "$script_dir/install-custom-gcl.sh" "$dest"; then + echo "Built native custom-gcl: $dest" + exit 0 + fi + + cat >"$dest" <<'EOF2' +#!/bin/sh + +set -eu + +repo_root=$(CDPATH='' cd -- "$(dirname "$0")/.." && pwd) +config_file="$repo_root/tools/.custom-gcl.yml" +gcl_version="" + +if [ -f "$config_file" ]; then + gcl_version=$(sed -n 's/^version:[[:space:]]*//p' "$config_file" | head -n 1) +fi + +gcl_version=${gcl_version:-v1.64.5} + +run_golangci() { + exec go run "github.com/golangci/golangci-lint/cmd/golangci-lint@${gcl_version}" "$@" +} + +if [ "${1:-}" = "run" ]; then + shift + + tmpdir="$(mktemp -d "${TMPDIR:-/tmp}/custom-gcl.XXXXXX")" + cfg="$tmpdir/custom-gcl.yml" + trap 'rm -rf "$tmpdir"' EXIT + + awk ' + $0 ~ /^linters-settings:[[:space:]]*$/ { + in_ls = 1 + print + next + } + in_ls && $0 ~ /^ custom:[[:space:]]*$/ { + skip_custom = 1 + next + } + skip_custom && $0 ~ /^ [A-Za-z0-9_-]+:[[:space:]]*$/ { + skip_custom = 0 + } + in_ls && $0 ~ /^[^[:space:]]/ { + in_ls = 0 + } + skip_custom { + next + } + $0 ~ /^[[:space:]]*-[[:space:]]*ll[[:space:]]*$/ { + sub(/ll/, "lll") + print + next + } + { + print + } + ' .golangci.yml >"$cfg" + + run_golangci run --config "$cfg" "$@" +fi + +run_golangci "$@" +EOF2 + + chmod +x "$dest" + echo "custom-gcl not found; using golangci-lint v1.64.5 fallback" + echo "(custom linter plugin 'll' is disabled in local mode)." + exit 0 +fi + +echo "error: install go or custom-gcl" >&2 +exit 1 diff --git a/tools/AGENTS.md b/tools/AGENTS.md index 8de531e7f..c25df3441 100644 --- a/tools/AGENTS.md +++ b/tools/AGENTS.md @@ -8,3 +8,11 @@ Development tool dependencies (`tools.go` for protoc plugins, sqlc, linters). - **Depends on**: nothing (Go module tool dependencies). - **Depended on by**: `make rpc`, `make sqlc`, `make lint`. + +## Local Linting + +- Run `make install-custom-gcl` from the repo root to build a native + `custom-gcl` binary for the current macOS/Linux host. +- After installation, `make lint-local` and `make lint-changed-local` + reuse that native binary and load the real `ll` plugin instead of the + fallback `lll` approximation. diff --git a/tools/CLAUDE.md b/tools/CLAUDE.md index 8de531e7f..c25df3441 100644 --- a/tools/CLAUDE.md +++ b/tools/CLAUDE.md @@ -8,3 +8,11 @@ Development tool dependencies (`tools.go` for protoc plugins, sqlc, linters). - **Depends on**: nothing (Go module tool dependencies). - **Depended on by**: `make rpc`, `make sqlc`, `make lint`. + +## Local Linting + +- Run `make install-custom-gcl` from the repo root to build a native + `custom-gcl` binary for the current macOS/Linux host. +- After installation, `make lint-local` and `make lint-changed-local` + reuse that native binary and load the real `ll` plugin instead of the + fallback `lll` approximation.