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
4 changes: 4 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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=<pkg> case=<test>` | Run unit tests |
| `make unit log="stdlog trace" pkg=<pkg> case=<test>` | Unit tests with debug logs |
Expand Down Expand Up @@ -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.
Expand Down
4 changes: 4 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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=<pkg> case=<test>` | Run unit tests |
| `make unit log="stdlog trace" pkg=<pkg> case=<test>` | Unit tests with debug logs |
Expand Down Expand Up @@ -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.
Expand Down
45 changes: 5 additions & 40 deletions Makefile
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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=<path> (default: ./tools/custom-gcl)
@./scripts/install-custom-gcl.sh "$(if $(dest),$(dest),$(LOCAL_CUSTOM_GCL))"

lint-source: docker-tools
@$(call print, "Linting source.")
Expand Down
118 changes: 118 additions & 0 deletions scripts/install-custom-gcl.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
#!/bin/sh

set -eu

usage() {
cat <<'EOF2'
usage: install-custom-gcl.sh <destination>

Build a native custom-gcl binary for the current host OS/ARCH using the
repo-local ll linter plugin and install it at <destination>.
EOF2
}

if [ "${1:-}" = "" ] || [ "${1:-}" = "-h" ] || [ "${1:-}" = "--help" ]; then
usage
exit 1
fi

dest="$1"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The script doesn't handle the case where the provided destination is an existing directory. If <destination> is a directory, mv will place the binary inside it, but the success message will point to the directory itself, which is misleading. Furthermore, subsequent scripts attempting to execute the destination path will fail. You should add a check to ensure the destination is not a directory.

Suggested change
dest="$1"
dest="$1"
if [ -d "$dest" ]; then
echo "error: destination cannot be a directory: $dest" >&2
exit 1
fi


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" <<EOF2
package main

import (
_ "${plugin_module}"
)
EOF2

(
cd "$repo_dir"

go mod edit -replace "${plugin_module}=${plugin_dir}"
go mod tidy

build_date=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
ldflags="-s -w -X main.version=${version}-custom-gcl -X main.date=${build_date}"

export GOFLAGS="${GOFLAGS-}${GOFLAGS:+ }-buildvcs=false"

CGO_ENABLED=0 go build \
-trimpath \
-ldflags "$ldflags" \
-o "$tmp_bin" \
./cmd/golangci-lint
)

mkdir -p "$(dirname "$dest")"
mv "$tmp_bin" "$dest"
chmod +x "$dest"

echo "Installed native custom-gcl to: $dest"
95 changes: 95 additions & 0 deletions scripts/local-custom-gcl.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
#!/bin/sh

set -eu

dest="${1:?usage: local-custom-gcl.sh <dest>}"
script_dir=$(CDPATH='' cd -- "$(dirname "$0")" && pwd)

mkdir -p "$(dirname "$dest")"

if command -v custom-gcl >/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
Comment on lines +27 to +86

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The fallback script hardcodes the golangci-lint version (v1.64.5 on line 33). This could become out of sync if the version in tools/.custom-gcl.yml is updated, leading to inconsistencies. The generated script should dynamically determine the version from tools/.custom-gcl.yml at runtime. This would make the fallback mechanism more robust and easier to maintain.

cat >"$dest" <<'EOF2'
#!/bin/sh

set -eu

# This script is a fallback. It assumes it is located in tools/ and that
# the repo root is one level up.
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

# Fallback to a known-good version if parsing fails.
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
8 changes: 8 additions & 0 deletions tools/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
8 changes: 8 additions & 0 deletions tools/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Loading