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)
+       }
+}

Reply via email to