Skip to content

fix: cross-compile cmd/azemu for Windows (v0.3.0 release blocker) - #80

Merged
ZeroDeth merged 1 commit into
mainfrom
fix/windows-cross-compile
Jun 28, 2026
Merged

fix: cross-compile cmd/azemu for Windows (v0.3.0 release blocker)#80
ZeroDeth merged 1 commit into
mainfrom
fix/windows-cross-compile

Conversation

@ZeroDeth

@ZeroDeth ZeroDeth commented Jun 28, 2026

Copy link
Copy Markdown
Owner

Summary

The v0.3.0 release (tag pushed) failed in goreleaser: cmd/azemu/adapter.go used two Unix-only syscalls directly, so the windows/amd64 and windows/arm64 build targets did not compile.

cmd/azemu/adapter.go:115:4: unknown field Setsid in struct literal of type syscall.SysProcAttr

CI only builds for the host (Linux), so it never caught this. No v0.3.0 artifacts were published, goreleaser aborted at the build step before creating the GitHub release or pushing any Docker image.

Fix

Split both Unix-only calls behind build-tagged helpers:

  • adapter_unix.go (//go:build !windows): syscall.Exec; SysProcAttr{Setsid: true}
  • adapter_windows.go (//go:build windows): run-wait-exit exec emulation (Windows has no execve); SysProcAttr{CreationFlags: CREATE_NEW_PROCESS_GROUP}

adapter.go now calls execProcess(...) and detachSysProcAttr() and no longer imports syscall.

Test plan

  • GOOS=windows GOARCH=amd64 go build ./cmd/azemu
  • GOOS=windows GOARCH=arm64 go build ./cmd/azemu
  • GOOS=darwin GOARCH=arm64 go build ./cmd/azemu
  • GOOS=linux GOARCH=amd64 go build ./cmd/azemu
  • go vet ./cmd/azemu/ clean, go test ./cmd/azemu/ passes

After merge

Delete and re-create the v0.3.0 tag at the new main HEAD so goreleaser re-runs against the fixed tree.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Improved process launching so the app can hand off execution more reliably across platforms.
    • Background starts now detach cleanly, helping long-running commands stay active after the terminal closes.
  • Bug Fixes

    • Better alignment of command execution behaviour on Windows and Unix-like systems, with more consistent exit handling.

adapter.go used two Unix-only syscalls directly: syscall.Exec and
syscall.SysProcAttr{Setsid}. Neither exists on Windows, so goreleaser's
windows/amd64 and windows/arm64 build targets failed, aborting the v0.3.0
release. CI only builds for the host (Linux), so it never caught this.

Split both behind build-tagged helpers:
- adapter_unix.go (!windows): syscall.Exec; SysProcAttr{Setsid: true}
- adapter_windows.go (windows): run-wait-exit exec emulation;
  SysProcAttr{CreationFlags: CREATE_NEW_PROCESS_GROUP}

Verified: go build for windows/amd64, windows/arm64, darwin/arm64, and
linux/amd64 all succeed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 28, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

adapter.go removes direct syscall usage, delegating to new execProcess and detachSysProcAttr helpers. These are implemented in two new platform-specific files: adapter_unix.go wraps syscall.Exec and Setsid, while adapter_windows.go emulates exec via a child process with CREATE_NEW_PROCESS_GROUP.

Changes

Platform-specific exec/detach helpers

Layer / File(s) Summary
Callsite refactoring
cmd/azemu/adapter.go
Removes the syscall import; replaces the inline syscall.Exec call in execBinary with execProcess() and the inline SysProcAttr{Setsid: true} in startAzemuBackground with detachSysProcAttr().
Unix and Windows implementations
cmd/azemu/adapter_unix.go, cmd/azemu/adapter_windows.go
Unix file adds execProcess (thin syscall.Exec wrapper) and detachSysProcAttr (returns Setsid: true). Windows file adds execProcess (child-process emulation with exit-code forwarding) and detachSysProcAttr (returns CREATE_NEW_PROCESS_GROUP).

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

A rabbit hops cross platforms wide,
On Unix it Execs with Setsid pride,
On Windows a child does the running instead,
The inline syscall now neatly outsped,
Each helper in its own file resides! 🐇

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: fixing Windows cross-compilation for cmd/azemu and blocking release.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/windows-cross-compile

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbiteu

coderabbiteu Bot commented Jun 28, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Syscall-specific process execution and session-detachment logic is extracted from adapter.go into two new build-tagged files: adapter_unix.go (!windows) and adapter_windows.go. adapter.go now calls execProcess and detachSysProcAttr helpers instead of inlining syscall calls.

Changes

Cross-platform process helper abstraction

Layer / File(s) Summary
Platform helper implementations
cmd/azemu/adapter_unix.go, cmd/azemu/adapter_windows.go
adapter_unix.go defines execProcess (via syscall.Exec) and detachSysProcAttr (Setsid=true). adapter_windows.go defines execProcess (via exec.Command with exit-code forwarding) and detachSysProcAttr (CREATE_NEW_PROCESS_GROUP).
adapter.go delegation
cmd/azemu/adapter.go
Removes syscall import; execBinary calls execProcess(bin, argv, os.Environ()) and startAzemuBackground uses detachSysProcAttr() for os.ProcAttr.Sys.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐇 Two files where one file stood before,
Unix hops left, Windows hops right,
syscall tucked behind a build-tag door,
Each platform dances in its own delight.
The rabbit builds clean, cross-platform and bright!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly reflects the main change: making cmd/azemu cross-compile on Windows and calling out the release blocker.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/windows-cross-compile

Comment @coderabbitai help to get the list of available commands.

@coderabbiteu coderabbiteu Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
cmd/azemu/adapter.go (1)

61-62: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Wrap execProcess failures with command context.

Returning the helper error directly loses which binary failed. Wrap it here so callers keep both the command context and the underlying platform-specific cause.

Suggested change
 	argv := append([]string{name}, args...)
-	return execProcess(bin, argv, os.Environ())
+	if err := execProcess(bin, argv, os.Environ()); err != nil {
+		return fmt.Errorf("exec %s: %w", bin, err)
+	}
+	return nil

As per coding guidelines, **/*.go: Follow the project’s Go style: wrap errors with %w.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/azemu/adapter.go` around lines 61 - 62, The `execProcess` call in
`Adapter` loses the binary context when it fails, so update the return in
`adapter.go` to wrap the error with the command name using Go’s `%w` style. Keep
the context around `bin`/`name` in `execProcess` call handling so callers can
see which command failed while preserving the underlying platform-specific
error.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cmd/azemu/adapter_windows.go`:
- Around line 34-37: The Windows process detachment in detachSysProcAttr only
uses CREATE_NEW_PROCESS_GROUP, which does not fully separate the child from the
parent console. Update the SysProcAttr returned by detachSysProcAttr so
startAzemuBackground() launches azemu serve with a true console detach using
DETACHED_PROCESS or CREATE_NEW_CONSOLE, rather than only changing
console-control handling.

---

Nitpick comments:
In `@cmd/azemu/adapter.go`:
- Around line 61-62: The `execProcess` call in `Adapter` loses the binary
context when it fails, so update the return in `adapter.go` to wrap the error
with the command name using Go’s `%w` style. Keep the context around
`bin`/`name` in `execProcess` call handling so callers can see which command
failed while preserving the underlying platform-specific error.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0e97be2c-3b03-4029-b57e-eb429c1c6b41

📥 Commits

Reviewing files that changed from the base of the PR and between 6af0c49 and b6841c8.

📒 Files selected for processing (3)
  • cmd/azemu/adapter.go
  • cmd/azemu/adapter_unix.go
  • cmd/azemu/adapter_windows.go

Comment on lines +34 to +37
// detachSysProcAttr starts the child in a new process group so it is not killed
// when the parent's console closes. Windows has no setsid equivalent.
func detachSysProcAttr() *syscall.SysProcAttr {
return &syscall.SysProcAttr{CreationFlags: 0x00000200} // CREATE_NEW_PROCESS_GROUP

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

For Go on Windows, does syscall.SysProcAttr{CreationFlags: CREATE_NEW_PROCESS_GROUP} fully detach a child process so it survives parent console close, or are DETACHED_PROCESS / CREATE_NEW_CONSOLE needed?

💡 Result:

No, syscall.SysProcAttr{CreationFlags: CREATE_NEW_PROCESS_GROUP} does not detach a child process from the parent's console [1][2]. To ensure a child process survives the closing of the parent's console, you must use DETACHED_PROCESS or CREATE_NEW_CONSOLE [1][3][2]. Here is a breakdown of why: 1. CREATE_NEW_PROCESS_GROUP: This flag is primarily used for console signal management [1][4]. It makes the new process the root of a new process group, allowing you to send signals (like CTRL+BREAK) to the entire group [1][5][4]. It does not inherently prevent the process from being tied to the parent's console [1][2]. 2. DETACHED_PROCESS: This flag is specifically designed to prevent a console process from inheriting the parent's console [1][2]. If a process is started with this flag, it does not have a console attached, allowing it to survive if the parent console is closed [1][3][2]. 3. CREATE_NEW_CONSOLE: This flag causes the new process to be created with its own, separate console window [1][3]. Like DETACHED_PROCESS, this decouples the child from the parent's console [3]. Note that DETACHED_PROCESS and CREATE_NEW_CONSOLE are mutually exclusive; you cannot use both in the same CreateProcess call [1][2]. Additionally, CREATE_NEW_PROCESS_GROUP is often used alongside these other flags when fine-grained signal control is required, though it is ignored if used with CREATE_NEW_CONSOLE [1][2]. For many Go applications needing to "daemonize" or run a background process on Windows, a common approach is to use: CreationFlags: windows.DETACHED_PROCESS | windows.CREATE_NEW_PROCESS_GROUP This combination ensures the process does not inherit the parent console while also setting up a process group for future signal management [6][1][7].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== cmd/azemu/adapter_windows.go ==\n'
sed -n '1,120p' cmd/azemu/adapter_windows.go

printf '\n== cmd/azemu/adapter.go (background startup references) ==\n'
rg -n "startAzemuBackground|detachSysProcAttr|SysProcAttr|CreationFlags|DETACHED_PROCESS|CREATE_NEW_CONSOLE|CREATE_NEW_PROCESS_GROUP" cmd/azemu/adapter.go cmd/azemu/adapter_windows.go

Repository: ZeroDeth/azemu

Length of output: 1852


CREATE_NEW_PROCESS_GROUP is not enough here. It only changes console-control handling; the child still shares the parent console. If startAzemuBackground() must keep azemu serve alive after the parent console exits, use DETACHED_PROCESS or CREATE_NEW_CONSOLE instead.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/azemu/adapter_windows.go` around lines 34 - 37, The Windows process
detachment in detachSysProcAttr only uses CREATE_NEW_PROCESS_GROUP, which does
not fully separate the child from the parent console. Update the SysProcAttr
returned by detachSysProcAttr so startAzemuBackground() launches azemu serve
with a true console detach using DETACHED_PROCESS or CREATE_NEW_CONSOLE, rather
than only changing console-control handling.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cmd/azemu/adapter_unix.go`:
- Around line 1-16: The build constraint on execProcess and detachSysProcAttr is
too broad because !windows still matches non-Unix targets. Narrow the guard in
adapter_unix.go to unix or an explicit supported GOOS list so this file only
builds where syscall.Exec and SysProcAttr.Setsid are available.

In `@cmd/azemu/adapter_windows.go`:
- Around line 34-37: The Windows child-process setup in detachSysProcAttr still
inherits the parent console, so it is not truly detached. Update the
syscall.SysProcAttr used by detachSysProcAttr in cmd/azemu/adapter_windows.go to
use a real detach behavior (for example, a detached process flag) instead of
only CREATE_NEW_PROCESS_GROUP, so azemu serve can survive the parent console
closing.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: c3f1e84c-2398-457d-aa0b-2cf844bfeda3

📥 Commits

Reviewing files that changed from the base of the PR and between 6af0c49 and b6841c8.

📒 Files selected for processing (3)
  • cmd/azemu/adapter.go
  • cmd/azemu/adapter_unix.go
  • cmd/azemu/adapter_windows.go

Comment thread cmd/azemu/adapter_unix.go
Comment on lines +1 to +16
//go:build !windows

package main

import "syscall"

// execProcess replaces the current process image with the named binary, the
// classic exec(3) behaviour. On success it never returns.
func execProcess(bin string, argv, env []string) error {
return syscall.Exec(bin, argv, env)
}

// detachSysProcAttr starts the child in its own session (setsid) so it
// survives the parent's exit and detaches from the controlling terminal.
func detachSysProcAttr() *syscall.SysProcAttr {
return &syscall.SysProcAttr{Setsid: true}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'Files of interest:\n'
git ls-files 'cmd/azemu/*' | sed -n '1,120p'

printf '\nBuild tag and helper definitions:\n'
cat -n cmd/azemu/adapter_unix.go

printf '\nRelated adapter files:\n'
for f in cmd/azemu/adapter_*.go; do
  [ "$f" = "cmd/azemu/adapter_unix.go" ] && continue
  printf '\n== %s ==\n' "$f"
  cat -n "$f"
done

printf '\nSearch for build tags and syscall.Exec / SysProcAttr usage:\n'
rg -n --hidden --no-messages 'go:build|syscall\.Exec|SysProcAttr|Setsid' cmd/azemu

Repository: ZeroDeth/azemu

Length of output: 3433


🌐 Web query:

Go build constraints unix tag syscall.Exec SysProcAttr.Setsid supported GOOS non-windows

💡 Result:

In Go, the syscall.SysProcAttr.Setsid field and syscall.Exec function are Unix-specific and are not supported on Windows [1][2][3][4]. 1. Setsid: The Setsid field within syscall.SysProcAttr is available on Unix-like operating systems (such as Linux and macOS) to create a new session for a process [5][6]. Because it relies on Unix system primitives that do not exist on Windows, attempting to use SysProcAttr with Setsid set to true on Windows will result in compilation errors if not properly constrained [1][2]. 2. syscall.Exec: The syscall.Exec function performs an execve system call, which replaces the current process with a new one [7][3]. This functionality is fundamentally Unix-specific; on Windows, the function exists in the syscall package but immediately returns an error (EWINDOWS) [3][4]. To handle these differences, Go developers typically use build constraints (also known as build tags) to separate platform-specific code [8]. You can use the //go:build constraint to include files only on specific operating systems [8]. For example, you can create a file for Unix systems: //go:build!windows package mypackage func setAttributes(attr *syscall.SysProcAttr) { attr.Setsid = true } And a corresponding file for Windows: //go:build windows package mypackage func setAttributes(attr *syscall.SysProcAttr) { // Setsid is not supported on Windows } Alternatively, the "unix" build tag can be used to include code on all Unix-like platforms (e.g., //go:build unix) [9]. For general process execution that needs to work across platforms, it is highly recommended to use the os/exec package instead of syscall.Exec, as os/exec provides a cross-platform abstraction [3][10].

Citations:


Narrow the build constraint to Unix targets. !windows still includes non-Unix GOOS values, so this file can be selected where syscall.Exec and SysProcAttr.Setsid are unavailable. Use //go:build unix or an explicit supported GOOS list instead.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/azemu/adapter_unix.go` around lines 1 - 16, The build constraint on
execProcess and detachSysProcAttr is too broad because !windows still matches
non-Unix targets. Narrow the guard in adapter_unix.go to unix or an explicit
supported GOOS list so this file only builds where syscall.Exec and
SysProcAttr.Setsid are available.

Comment on lines +34 to +37
// detachSysProcAttr starts the child in a new process group so it is not killed
// when the parent's console closes. Windows has no setsid equivalent.
func detachSysProcAttr() *syscall.SysProcAttr {
return &syscall.SysProcAttr{CreationFlags: 0x00000200} // CREATE_NEW_PROCESS_GROUP

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== File map ==\n'
git ls-files cmd/azemu | sed -n '1,120p'

printf '\n== adapter_windows.go ==\n'
cat -n cmd/azemu/adapter_windows.go

printf '\n== Search for detachSysProcAttr usage ==\n'
rg -n "detachSysProcAttr|SysProcAttr|CreationFlags|CREATE_NEW_PROCESS_GROUP|CREATE_NO_WINDOW|DETACHED_PROCESS" cmd/azemu -S

printf '\n== Nearby process launch code ==\n'
for f in $(rg -l "detachSysProcAttr|SysProcAttr" cmd/azemu -S); do
  echo "--- $f ---"
  cat -n "$f"
done

Repository: ZeroDeth/azemu

Length of output: 9686


🌐 Web query:

Windows CreateProcess CREATE_NEW_PROCESS_GROUP console close event attached console DETACHED_PROCESS documentation

💡 Result:

When using the CreateProcess function on Windows, the flags CREATE_NEW_PROCESS_GROUP and DETACHED_PROCESS interact with console signaling and inheritance as follows: CREATE_NEW_PROCESS_GROUP When this flag is specified, the new process becomes the root of a new console process group [1][2]. - Console Signals: This flag implicitly calls SetConsoleCtrlHandler(NULL, TRUE) for the new process, which disables CTRL+C signals for that process and its entire process group [3][1][4]. CTRL+BREAK signals remain enabled and can still be used to interrupt the process group [3][4]. - Process Group Management: This flag allows the use of GenerateConsoleCtrlEvent to send signals (specifically CTRL+BREAK) to the entire group [1][5]. - Conflicts: This flag is ignored if used in conjunction with CREATE_NEW_CONSOLE [1][6]. DETACHED_PROCESS When this flag is specified, a console process does not inherit the parent process's console [1][7]. - Console State: The new process begins without an attached console [1][7]. It may call AllocConsole at a later time to create one [1][7]. - Conflicts: This flag cannot be used with CREATE_NEW_CONSOLE [1][6]. Interaction and Limitations - Console Handling: While CREATE_NEW_PROCESS_GROUP affects how a process group responds to signals within a shared console, DETACHED_PROCESS ensures the child does not share the parent's console at all [1][2][7]. - Termination: Console Close events are sent to all processes attached to a console. If a process is detached, it is not attached to the parent's console, and therefore will not receive close events associated with that parent's console window [7]. Processes in a new process group that remain attached to the same console as the parent will continue to receive signals unless explicitly handled otherwise [2][5]. In summary, use CREATE_NEW_PROCESS_GROUP when you need to manage signal propagation (e.g., in a shell) or isolate a group for termination via CTRL+BREAK [3][2]. Use DETACHED_PROCESS when you need to ensure a child process does not interfere with or receive input/output from the parent's console [7][8].

Citations:


This still shares the parent console. CREATE_NEW_PROCESS_GROUP only changes Ctrl+C/Break grouping; it does not detach the child from the inherited console, so closing the parent window can still take azemu serve down. If the backgrounding contract is to survive console closure, this needs a true detach flag such as DETACHED_PROCESS.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/azemu/adapter_windows.go` around lines 34 - 37, The Windows child-process
setup in detachSysProcAttr still inherits the parent console, so it is not truly
detached. Update the syscall.SysProcAttr used by detachSysProcAttr in
cmd/azemu/adapter_windows.go to use a real detach behavior (for example, a
detached process flag) instead of only CREATE_NEW_PROCESS_GROUP, so azemu serve
can survive the parent console closing.

@ZeroDeth
ZeroDeth merged commit f1f0b4d into main Jun 28, 2026
6 checks passed
@ZeroDeth
ZeroDeth deleted the fix/windows-cross-compile branch June 28, 2026 04:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant