This is an automated email from the ASF dual-hosted git repository.
lizhimins pushed a commit to branch rocketmq-studio
in repository https://gitbox.apache.org/repos/asf/rocketmq-dashboard.git
The following commit(s) were added to refs/heads/rocketmq-studio by this push:
new 355062386 fix(rmqctl): select the host target for native Windows
builds (#6461)
355062386 is described below
commit 355062386e40081c48610291fae2013543cedb2c
Author: Loyal-Young <[email protected]>
AuthorDate: Thu Oct 15 20:29:13 2026 +0800
fix(rmqctl): select the host target for native Windows builds (#6461)
* fix(rmqctl): select the host target for native Windows builds
* test(rmqctl): guard the Windows host-target binding on Linux CI
Complete the abbreviated ASF header in build_windows_test.go, matching
license-binary_test.go in the same directory.
The executable regression in that file is skipped everywhere except Windows,
and CI only runs ubuntu-latest, so the script it protects was uncovered. Add
build_script_test.go -- deliberately without a `_windows` filename suffix,
which Go turns into an implicit build constraint -- asserting on the script
text that GOOS/GOARCH are bound to GOHOSTOS/GOHOSTARCH, that the lookup
fails
closed, and that both happen before `go build` and before the optional
`go test`.
---------
Co-authored-by: lizhimins <[email protected]>
---
rmqctl/scripts/build-windows.ps1 | 9 ++++
rmqctl/scripts/build_script_test.go | 67 ++++++++++++++++++++++++++++++
rmqctl/scripts/build_windows_test.go | 79 ++++++++++++++++++++++++++++++++++++
3 files changed, 155 insertions(+)
diff --git a/rmqctl/scripts/build-windows.ps1 b/rmqctl/scripts/build-windows.ps1
index af67aa4ed..46cc8ca35 100644
--- a/rmqctl/scripts/build-windows.ps1
+++ b/rmqctl/scripts/build-windows.ps1
@@ -67,6 +67,15 @@ if (-not (Get-Command go -ErrorAction SilentlyContinue)) {
Write-Host ">> $(go version)"
Write-Host ">> module root: $moduleRoot"
+# This entry point builds and optionally tests a native executable. Do not
+# inherit GOOS/GOARCH left over from a previous cross-compilation command.
+$nativeTarget = go env -json GOHOSTOS GOHOSTARCH | ConvertFrom-Json
+if ($LASTEXITCODE -ne 0 -or -not $nativeTarget.GOHOSTOS -or -not
$nativeTarget.GOHOSTARCH) {
+ throw "Could not determine the native Go build target"
+}
+$env:GOOS = $nativeTarget.GOHOSTOS
+$env:GOARCH = $nativeTarget.GOHOSTARCH
+
# Version metadata, mirroring the Makefile LDFLAGS.
$gitCommit = "unknown"
try {
diff --git a/rmqctl/scripts/build_script_test.go
b/rmqctl/scripts/build_script_test.go
new file mode 100644
index 000000000..b24e72e28
--- /dev/null
+++ b/rmqctl/scripts/build_script_test.go
@@ -0,0 +1,67 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import (
+ "os"
+ "regexp"
+ "testing"
+)
+
+// The executable regression in build_windows_test.go only runs on Windows,
and CI has a single
+// ubuntu-latest job, so it is always skipped there and the script itself
would stay unprotected.
+// This file carries no `_windows` suffix on purpose: it has to run on Linux.
It pins the script
+// text instead -- the native target must be resolved from
GOHOSTOS/GOHOSTARCH, the resolution must
+// fail closed, and both must happen before `go build` and before the optional
`go test`. Otherwise
+// a GOOS/GOARCH left over from a previous `make build-all` cross-compilation
silently produces a
+// foreign-architecture rmqctl.exe that cannot run on the build host.
+func TestWindowsBuildScriptPinsHostTargetBeforeCompiling(t *testing.T) {
+ content, err := os.ReadFile("build-windows.ps1")
+ if err != nil {
+ t.Fatal(err)
+ }
+ script := string(content)
+
+ buildAt := regexp.MustCompile(`(?m)^go build `).FindStringIndex(script)
+ if buildAt == nil {
+ t.Fatal("build-windows.ps1 no longer invokes `go build`")
+ }
+ testAt := regexp.MustCompile(`(?m)^\s*go test -count=1
\./\.\.\.$`).FindStringIndex(script)
+ if testAt == nil {
+ t.Fatal("build-windows.ps1 no longer invokes `go test`")
+ }
+
+ for _, pin := range []struct{ what, pattern string }{
+ {"host target lookup", `(?m)^.*go env -json GOHOSTOS
GOHOSTARCH.*$`},
+ {"fail-closed guard", `(?m)^\s*throw "Could not determine the
native Go build target"$`},
+ {"GOOS binding", `(?m)^\$env:GOOS = \$nativeTarget\.GOHOSTOS$`},
+ {"GOARCH binding", `(?m)^\$env:GOARCH =
\$nativeTarget\.GOHOSTARCH$`},
+ } {
+ at := regexp.MustCompile(pin.pattern).FindStringIndex(script)
+ if at == nil {
+ t.Errorf("build-windows.ps1 lost the %s matching %s",
pin.what, pin.pattern)
+ continue
+ }
+ if at[0] > buildAt[0] {
+ t.Errorf("the %s at offset %d must precede `go build`
at offset %d",
+ pin.what, at[0], buildAt[0])
+ }
+ if at[0] > testAt[0] {
+ t.Errorf("the %s at offset %d must precede `go test` at
offset %d",
+ pin.what, at[0], testAt[0])
+ }
+ }
+}
diff --git a/rmqctl/scripts/build_windows_test.go
b/rmqctl/scripts/build_windows_test.go
new file mode 100644
index 000000000..18f1d5338
--- /dev/null
+++ b/rmqctl/scripts/build_windows_test.go
@@ -0,0 +1,79 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import (
+ "os"
+ "os/exec"
+ "path/filepath"
+ "runtime"
+ "strings"
+ "testing"
+)
+
+func TestWindowsNativeBuildIgnoresCrossCompilationTarget(t *testing.T) {
+ if runtime.GOOS != "windows" {
+ t.Skip("native Windows build script")
+ }
+ shell, err := exec.LookPath("pwsh")
+ if err != nil {
+ shell, err = exec.LookPath("powershell")
+ }
+ if err != nil {
+ t.Skip("PowerShell is unavailable")
+ }
+ goTool, err := exec.LookPath("go")
+ if err != nil {
+ t.Fatal(err)
+ }
+ script, err := os.ReadFile("build-windows.ps1")
+ if err != nil {
+ t.Fatal(err)
+ }
+ fixture := filepath.Join(t.TempDir(), "构建 files")
+ if err := os.MkdirAll(filepath.Join(fixture, "scripts"), 0o700); err !=
nil {
+ t.Fatal(err)
+ }
+ files := map[string]string{
+ "go.mod": "module buildfixture\n\ngo 1.20\n",
+ "main.go": "package main\nimport (\"fmt\";
\"runtime\")\nfunc main() { fmt.Print(runtime.GOOS + \"/\" + runtime.GOARCH +
\" 构建完成\") }\n",
+ "main_test.go": "package main\nimport (\"os\";
\"testing\")\nfunc TestNativeExecution(t *testing.T) { if err :=
os.WriteFile(\"tests-ran\", []byte(\"yes\"), 0600); err != nil { t.Fatal(err) }
}\n",
+ filepath.Join("scripts", "build-windows.ps1"): string(script),
+ }
+ for name, content := range files {
+ if err := os.WriteFile(filepath.Join(fixture, name),
[]byte(content), 0o600); err != nil {
+ t.Fatal(err)
+ }
+ }
+ t.Setenv("PATH",
filepath.Dir(goTool)+string(os.PathListSeparator)+os.Getenv("PATH"))
+ t.Setenv("GOOS", "linux")
+ t.Setenv("GOARCH", "arm64")
+ command := exec.Command(shell, "-NoProfile", "-File",
filepath.Join(fixture, "scripts", "build-windows.ps1"),
+ "-Output", filepath.Join("bin", "本机构建.exe"), "-RunTests")
+ if output, err := command.CombinedOutput(); err != nil {
+ t.Fatalf("native build failed: %v\n%s", err, output)
+ }
+ output, err := exec.Command(filepath.Join(fixture, "bin",
"本机构建.exe")).CombinedOutput()
+ if err != nil {
+ t.Fatalf("built executable cannot run: %v\n%s", err, output)
+ }
+ if got, want := strings.TrimSpace(string(output)),
runtime.GOOS+"/"+runtime.GOARCH+" 构建完成"; got != want {
+ t.Fatalf("built executable output = %q, want %q", got, want)
+ }
+ if _, err := os.Stat(filepath.Join(fixture, "tests-ran")); err != nil {
+ t.Fatalf("optional tests did not run: %v", err)
+ }
+}