@@ -11,6 +11,160 @@ source "$(dirname "$SCRIPT")/../../setup/packages.sh"
1111
1212check_dependencies
1313
14+ # quickstyle_repo_key returns a stable, filesystem-safe key for the current git repo root.
15+ # It is used for naming repo-scoped cache directories under the workflow workdir.
16+ function quickstyle_repo_key() {
17+ echo " ${gitroot} " | tr ' /' ' _'
18+ }
19+
20+ # quickstyle_tools_cache_dir returns (and ensures) the directory where quickstyle may place
21+ # repo-scoped helper binaries (e.g. goimports) managed by workflow, not by the target repo.
22+ function quickstyle_tools_cache_dir() {
23+ local repo_key
24+ repo_key=" $( quickstyle_repo_key) "
25+ local tools_dir=" ${ROX_WORKFLOW_WORKDIR} /quickstyle/tools/${repo_key} "
26+ mkdir -p " ${tools_dir} "
27+ echo " ${tools_dir} "
28+ }
29+
30+ # Try to resolve a tool path via the repo's Makefile gotools support (make/gotools.mk).
31+ # Many StackRox repos expose `which-<tool>` targets that print the tool's canonical binary; use those
32+ # to avoid relying on whatever happens to be in PATH/GOPATH.
33+ # Args:
34+ # - $1: tool basename (e.g. "roxvet", "golangci-lint", "goimports")
35+ # Output: prints an absolute path to stdout if found.
36+ # Returns: 0 if resolved; non-zero otherwise.
37+ function resolve_tool_via_make() {
38+ local tool=" $1 "
39+ [[ -n " ${tool} " ]] || return 1
40+ [[ -f " ${gitroot} /Makefile" ]] || return 1
41+ [[ -x " $( command -v make) " ]] || return 1
42+ local resolved
43+ resolved=" $( make -C " ${gitroot} " --quiet --no-print-directory " which-${tool} " 2> /dev/null) " || return 1
44+ [[ -n " ${resolved} " ]] || return 1
45+ [[ -x " ${resolved} " ]] || return 1
46+ printf ' %s\n' " ${resolved} "
47+ }
48+
49+ # resolve_tool_path attempts to find an executable tool by:
50+ # 1) repo-local Makefile ("make which-<tool>"), then
51+ # 2) PATH lookup.
52+ # Args:
53+ # - $1: tool basename
54+ # Output: prints resolved executable path to stdout.
55+ # Returns: 0 if resolved; non-zero otherwise.
56+ function resolve_tool_path() {
57+ local tool=" $1 "
58+ local resolved=" "
59+
60+ resolved=" $( resolve_tool_via_make " ${tool} " ) " && { printf ' %s\n' " ${resolved} " ; return 0; }
61+
62+ resolved=" $( command -v " ${tool} " 2> /dev/null) " || true
63+ [[ -n " ${resolved} " && -x " ${resolved} " ]] || return 1
64+ printf ' %s\n' " ${resolved} "
65+ }
66+
67+ # go_major_minor extracts "<major>.<minor>" from a Go version string.
68+ # Example: "go1.24.9" -> "1.24"
69+ # Args:
70+ # - $1: value like "$(go env GOVERSION)" or "go1.24.9"
71+ # Output: prints "<major>.<minor>" to stdout.
72+ function go_major_minor() {
73+ local goversion=" $1 "
74+ # Examples: go1.24.9 -> 1.24 ; go1.22.7 -> 1.22
75+ echo " ${goversion} " | sed -E ' s/^go([0-9]+\.[0-9]+).*/\1/'
76+ }
77+
78+ # tool_built_with_go_major_minor returns the Go "<major>.<minor>" used to build a given binary.
79+ # This uses `go version -m`, so it requires the binary to contain build info.
80+ # Args:
81+ # - $1: path to an executable binary
82+ # Output: prints "<major>.<minor>" to stdout.
83+ # Returns: 0 if build info is available; non-zero otherwise.
84+ function tool_built_with_go_major_minor() {
85+ local tool_path=" $1 "
86+ [[ -n " ${tool_path} " && -x " ${tool_path} " ]] || return 1
87+ local first_line
88+ first_line=" $( go version -m " ${tool_path} " 2> /dev/null | head -n 1) " || return 1
89+ # Format: /path/to/tool: go1.22.7
90+ local tool_go_version
91+ tool_go_version=" $( echo " ${first_line} " | awk ' {print $2}' ) "
92+ [[ -n " ${tool_go_version} " ]] || return 1
93+ go_major_minor " ${tool_go_version} "
94+ }
95+
96+ # ensure_goimports resolves a usable goimports binary.
97+ # Resolution order:
98+ # 1) repo-local Makefile ("make which-goimports"), then
99+ # 2) PATH, then
100+ # 3) workflow-managed install into a repo-scoped cache dir under ${ROX_WORKFLOW_WORKDIR}.
101+ # Notes:
102+ # - When installing, we try to pin to the repo's `golang.org/x/tools` module version if present;
103+ # otherwise we fall back to @latest.
104+ # Output: prints the goimports executable path to stdout.
105+ # Returns: 0 if available; non-zero otherwise.
106+ function ensure_goimports() {
107+ local goimports_bin
108+ goimports_bin=" $( resolve_tool_path goimports) " && { printf ' %s\n' " ${goimports_bin} " ; return 0; }
109+
110+ local tools_dir
111+ tools_dir=" $( quickstyle_tools_cache_dir) "
112+ goimports_bin=" ${tools_dir} /goimports"
113+ [[ -x " ${goimports_bin} " ]] && { printf ' %s\n' " ${goimports_bin} " ; return 0; }
114+
115+ local x_tools_version
116+ x_tools_version=" $( cd " ${gitroot} " && go list -m -f ' {{.Version}}' golang.org/x/tools 2> /dev/null) " || true
117+ local goimports_pkg=" golang.org/x/tools/cmd/goimports@latest"
118+ if [[ -n " ${x_tools_version} " ]]; then
119+ goimports_pkg=" golang.org/x/tools/cmd/goimports@${x_tools_version} "
120+ fi
121+
122+ einfo " goimports not found; installing ${goimports_pkg} into ${tools_dir} ..."
123+ if ! env GOBIN=" ${tools_dir} " go install " ${goimports_pkg} " ; then
124+ efatal " Failed to install goimports (${goimports_pkg} )."
125+ efatal " Either install goimports on PATH, or ensure the repo exposes a 'which-goimports' Make target."
126+ return 1
127+ fi
128+
129+ [[ -x " ${goimports_bin} " ]] || return 1
130+ printf ' %s\n' " ${goimports_bin} "
131+ }
132+
133+ # ensure_roxvet resolves a usable roxvet binary.
134+ # Resolution order:
135+ # 1) repo-local Makefile ("make which-roxvet"), then
136+ # 2) GOPATH/bin/roxvet.
137+ # If GOPATH/bin/roxvet exists but was built with a different Go major/minor than the current toolchain,
138+ # we attempt to rebuild it from ${gitroot}/tools/roxvet (when present) to avoid export-data panics.
139+ # Output: prints the roxvet executable path to stdout.
140+ # Returns: 0 if available; non-zero otherwise.
141+ function ensure_roxvet() {
142+ local roxvet_bin=" "
143+ roxvet_bin=" $( resolve_tool_via_make roxvet) " && { printf ' %s\n' " ${roxvet_bin} " ; return 0; }
144+
145+ roxvet_bin=" $( go env GOPATH) /bin/roxvet"
146+
147+ if [[ -x " ${roxvet_bin} " ]]; then
148+ local tool_go_mm
149+ tool_go_mm=" $( tool_built_with_go_major_minor " ${roxvet_bin} " ) " || tool_go_mm=" "
150+ local cur_go_mm
151+ cur_go_mm=" $( go_major_minor " $( go env GOVERSION) " ) "
152+
153+ # If roxvet is stale (built with different Go major.minor), it can panic on newer export data.
154+ if [[ -n " ${tool_go_mm} " && " ${tool_go_mm} " != " ${cur_go_mm} " ]]; then
155+ ewarn " roxvet (${roxvet_bin} ) was built with Go ${tool_go_mm} , but current Go is ${cur_go_mm} ; rebuilding..."
156+ if [[ -d " ${gitroot} /tools/roxvet" ]]; then
157+ if ! (cd " ${gitroot} " && env GOBIN=" $( dirname " ${roxvet_bin} " ) " go install ./tools/roxvet); then
158+ ewarn " Failed to rebuild roxvet; continuing with existing binary (may fail)."
159+ fi
160+ fi
161+ fi
162+ fi
163+
164+ [[ -x " ${roxvet_bin} " ]] || return 1
165+ printf ' %s\n' " ${roxvet_bin} "
166+ }
167+
14168function newlinecheck() {
15169 local check_newlines
16170 check_newlines=" $(
@@ -59,8 +213,10 @@ function gostyle() {
59213 einfo " Running go style checks..."
60214 if [[ -f " ${gitroot} /.golangci.yml" ]]; then
61215 einfo " golangci-lint"
62- if [[ -x " $( which golangci-lint) " ]]; then
63- golangci-lint run --allow-parallel-runners " ${godirs[@]} " --fix && (( status == 0 ))
216+ local golangci_lint_bin
217+ golangci_lint_bin=" $( resolve_tool_path golangci-lint) " || golangci_lint_bin=" "
218+ if [[ -n " ${golangci_lint_bin} " && -x " ${golangci_lint_bin} " ]]; then
219+ " ${golangci_lint_bin} " run --allow-parallel-runners " ${godirs[@]} " --fix && (( status == 0 ))
64220 status=$?
65221 else
66222 ewarn " No golangci-lint binary found, but the repo has a config file. Skipping..."
@@ -69,7 +225,9 @@ function gostyle() {
69225
70226 if ! golangci_linter_enabled ' goimports' ; then
71227 einfo " imports"
72- goimports -l -w " ${gofiles[@]} " && (( status == 0 ))
228+ local goimports_bin
229+ goimports_bin=" $( ensure_goimports) " || { status=1; return " ${status} " ; }
230+ " ${goimports_bin} " -l -w " ${gofiles[@]} " && (( status == 0 ))
73231 status=$?
74232 fi
75233
@@ -119,11 +277,9 @@ function gostyle() {
119277 roxvet_includes_validateimports=0
120278
121279 einfo " roxvet"
122- local rox_vet=" $( go env GOPATH) /bin/roxvet"
123- if [[ ! -x " ${rox_vet} " ]] && [[ -d " ${gitroot} /tools/roxvet" ]]; then
124- go install " ${gitroot} /tools/roxvet"
125- fi
126- if [[ -x " ${rox_vet} " ]]; then
280+ local rox_vet
281+ rox_vet=" $( ensure_roxvet) " || rox_vet=" "
282+ if [[ -n " ${rox_vet} " && -x " ${rox_vet} " ]]; then
127283 go vet -vettool " ${rox_vet} " " ${godirs[@]} " && (( status == 0 ))
128284 status=$?
129285 if " ${rox_vet} " help | grep -q validateimports; then
@@ -152,10 +308,6 @@ function gostyle() {
152308}
153309
154310function golangci_linter_enabled() {
155- if [[ ! -x " $( command -v " golangci-lint" ) " ]]; then
156- return 1
157- fi
158-
159311 local linter
160312 local enabled_linters
161313 linter=" ${1} "
0 commit comments