Add Windows build support and release artifacts #80

Closed
opened 2026-04-03 18:26:41 +02:00 by kfickel · 2 comments
Owner

Summary

Add Windows cross-compilation support using mingw-w64 on NixOS and include Windows binaries in release artifacts.

Requirements

  1. Cross-compilation from NixOS - Build Windows binaries using mingw-w64 toolchain via Nix
  2. Release integration - Include Windows .exe in release artifacts alongside Linux .deb and musl binary
  3. Application compatibility - Fix code that doesn't work on Windows

Implementation Plan

1. Nix Cross-Compilation (flake.nix)

Add mkWindowsCraneLib and mkStreamdWindows functions following the existing musl cross-compilation pattern:

  • Target: x86_64-pc-windows-gnu
  • Toolchain: pkgs.pkgsCross.mingwW64
  • Export as streamd-windows package

2. Code Compatibility Fixes

File Issue Fix
src/config.rs:41 Hardcoded ~/.config/streamd/config.toml fallback Use directories::BaseDirs for cross-platform fallback
src/cli/commands/new.rs:27 EDITOR defaults to vi Use cfg!(windows) to default to notepad on Windows
src/cli/commands/edit.rs:68 EDITOR defaults to vi Same as above
src/cli/commands/todo.rs:93-98 EDITOR defaults to vi + uses +N line arg Default to notepad, skip line arg for notepad

3. Release Workflow (.forgejo/workflows/release.yml)

  • Add nix build .#streamd-windows step
  • Copy streamd-VERSION-windows-x86_64.exe to release artifacts

Verification

  1. nix build .#streamd-windows - Build Windows binary locally
  2. file result/bin/streamd.exe - Verify PE executable format
  3. wine result/bin/streamd.exe --version - Optional smoke test
  4. cargo test - Ensure code changes don't break Linux
## Summary Add Windows cross-compilation support using mingw-w64 on NixOS and include Windows binaries in release artifacts. ## Requirements 1. **Cross-compilation from NixOS** - Build Windows binaries using mingw-w64 toolchain via Nix 2. **Release integration** - Include Windows `.exe` in release artifacts alongside Linux `.deb` and musl binary 3. **Application compatibility** - Fix code that doesn't work on Windows ## Implementation Plan ### 1. Nix Cross-Compilation (`flake.nix`) Add `mkWindowsCraneLib` and `mkStreamdWindows` functions following the existing musl cross-compilation pattern: - Target: `x86_64-pc-windows-gnu` - Toolchain: `pkgs.pkgsCross.mingwW64` - Export as `streamd-windows` package ### 2. Code Compatibility Fixes | File | Issue | Fix | |------|-------|-----| | `src/config.rs:41` | Hardcoded `~/.config/streamd/config.toml` fallback | Use `directories::BaseDirs` for cross-platform fallback | | `src/cli/commands/new.rs:27` | EDITOR defaults to `vi` | Use `cfg!(windows)` to default to `notepad` on Windows | | `src/cli/commands/edit.rs:68` | EDITOR defaults to `vi` | Same as above | | `src/cli/commands/todo.rs:93-98` | EDITOR defaults to `vi` + uses `+N` line arg | Default to `notepad`, skip line arg for notepad | ### 3. Release Workflow (`.forgejo/workflows/release.yml`) - Add `nix build .#streamd-windows` step - Copy `streamd-VERSION-windows-x86_64.exe` to release artifacts ## Verification 1. `nix build .#streamd-windows` - Build Windows binary locally 2. `file result/bin/streamd.exe` - Verify PE executable format 3. `wine result/bin/streamd.exe --version` - Optional smoke test 4. `cargo test` - Ensure code changes don't break Linux
Author
Owner

Implementation Plan

1. Nix Cross-Compilation (flake.nix)

Add mkWindowsCraneLib (after mkMuslCraneLib around line 123):

mkWindowsCraneLib =
  system:
  let
    pkgs = mkPkgs system;
    toolchain = pkgs.rust-bin.stable.latest.default.override {
      targets = [ "x86_64-pc-windows-gnu" ];
    };
  in
  (crane.mkLib pkgs).overrideToolchain toolchain;

Add mkStreamdWindows (after mkStreamdMusl around line 139):

mkStreamdWindows =
  system:
  let
    pkgs = mkPkgs system;
    pkgsCross = pkgs.pkgsCross.mingwW64;
    craneLib = mkWindowsCraneLib system;
    commonArgs = {
      src = craneLib.path ./.;
      pname = "streamd";
      inherit version;
      strictDeps = true;
      CARGO_BUILD_TARGET = "x86_64-pc-windows-gnu";
      CC_x86_64_pc_windows_gnu = "${pkgsCross.stdenv.cc}/bin/x86_64-w64-mingw32-gcc";
      CARGO_TARGET_X86_64_PC_WINDOWS_GNU_LINKER = "${pkgsCross.stdenv.cc}/bin/x86_64-w64-mingw32-gcc";
      nativeBuildInputs = [ pkgsCross.stdenv.cc pkgsCross.windows.pthreads ];
    };
    cargoArtifacts = craneLib.buildDepsOnly commonArgs;
  in
  craneLib.buildPackage (commonArgs // { inherit cargoArtifacts; });

Export package (update packages output around line 191):

streamd-windows = mkStreamdWindows system;
# In the inherit line:
inherit streamd streamd-musl streamd-deb streamd-windows;

2. Code Compatibility Fixes

2.1 Config path fallback (src/config.rs:41)

Replace hardcoded Unix path with cross-platform fallback:

fn config_path() -> PathBuf {
    if let Some(proj_dirs) = ProjectDirs::from("", "", "streamd") {
        proj_dirs.config_dir().join("config.toml")
    } else if let Some(base_dirs) = directories::BaseDirs::new() {
        base_dirs.config_dir().join("streamd").join("config.toml")
    } else {
        PathBuf::from("streamd_config.toml")
    }
}

2.2 Editor fallback (src/cli/commands/new.rs:27, edit.rs:68, todo.rs:93)

Replace in all three locations:

let editor = std::env::var("EDITOR").unwrap_or_else(|_| {
    if cfg!(windows) { "notepad".to_string() } else { "vi".to_string() }
});

2.3 Line number argument (src/cli/commands/todo.rs:94-98)

Handle notepad not supporting +N syntax:

let mut cmd = Command::new(&editor);
let editor_lower = editor.to_lowercase();
if !editor_lower.contains("notepad") {
    cmd.arg(format!("+{}", task.start_line));
}
let status = cmd.arg(file_path).status()?;

3. Release Workflow (.forgejo/workflows/release.yml)

Add Windows build step after musl build:

- name: Build Windows binary
  if: steps.version.outputs.SKIP != 'true'
  run: nix build .#streamd-windows -o result-windows

Update artifact preparation step:

- name: Prepare release artifacts
  if: steps.version.outputs.SKIP != 'true'
  run: |
    mkdir -p release
    cp result-deb release/streamd_${{ steps.version.outputs.VERSION }}_amd64.deb
    cp result-musl/bin/streamd release/streamd-${{ steps.version.outputs.VERSION }}-linux-x86_64
    cp result-windows/bin/streamd.exe release/streamd-${{ steps.version.outputs.VERSION }}-windows-x86_64.exe

Files to Modify

File Change
flake.nix Add mkWindowsCraneLib, mkStreamdWindows, export package
src/config.rs Fix fallback path (line 41)
src/cli/commands/new.rs Windows editor fallback (line 27)
src/cli/commands/edit.rs Windows editor fallback (line 68)
src/cli/commands/todo.rs Windows editor fallback + line arg handling (lines 93-98)
.forgejo/workflows/release.yml Add Windows build step and artifact

Verification

  1. Build locally: nix build .#streamd-windows
  2. Check binary: file result/bin/streamd.exe
  3. Smoke test with Wine (optional): wine result/bin/streamd.exe --version
  4. Run existing tests: cargo test (code changes use compile-time cfg)
  5. Test release workflow: Push version bump to trigger CI
## Implementation Plan ### 1. Nix Cross-Compilation (`flake.nix`) #### Add `mkWindowsCraneLib` (after `mkMuslCraneLib` around line 123): ```nix mkWindowsCraneLib = system: let pkgs = mkPkgs system; toolchain = pkgs.rust-bin.stable.latest.default.override { targets = [ "x86_64-pc-windows-gnu" ]; }; in (crane.mkLib pkgs).overrideToolchain toolchain; ``` #### Add `mkStreamdWindows` (after `mkStreamdMusl` around line 139): ```nix mkStreamdWindows = system: let pkgs = mkPkgs system; pkgsCross = pkgs.pkgsCross.mingwW64; craneLib = mkWindowsCraneLib system; commonArgs = { src = craneLib.path ./.; pname = "streamd"; inherit version; strictDeps = true; CARGO_BUILD_TARGET = "x86_64-pc-windows-gnu"; CC_x86_64_pc_windows_gnu = "${pkgsCross.stdenv.cc}/bin/x86_64-w64-mingw32-gcc"; CARGO_TARGET_X86_64_PC_WINDOWS_GNU_LINKER = "${pkgsCross.stdenv.cc}/bin/x86_64-w64-mingw32-gcc"; nativeBuildInputs = [ pkgsCross.stdenv.cc pkgsCross.windows.pthreads ]; }; cargoArtifacts = craneLib.buildDepsOnly commonArgs; in craneLib.buildPackage (commonArgs // { inherit cargoArtifacts; }); ``` #### Export package (update packages output around line 191): ```nix streamd-windows = mkStreamdWindows system; # In the inherit line: inherit streamd streamd-musl streamd-deb streamd-windows; ``` --- ### 2. Code Compatibility Fixes #### 2.1 Config path fallback (`src/config.rs:41`) Replace hardcoded Unix path with cross-platform fallback: ```rust fn config_path() -> PathBuf { if let Some(proj_dirs) = ProjectDirs::from("", "", "streamd") { proj_dirs.config_dir().join("config.toml") } else if let Some(base_dirs) = directories::BaseDirs::new() { base_dirs.config_dir().join("streamd").join("config.toml") } else { PathBuf::from("streamd_config.toml") } } ``` #### 2.2 Editor fallback (`src/cli/commands/new.rs:27`, `edit.rs:68`, `todo.rs:93`) Replace in all three locations: ```rust let editor = std::env::var("EDITOR").unwrap_or_else(|_| { if cfg!(windows) { "notepad".to_string() } else { "vi".to_string() } }); ``` #### 2.3 Line number argument (`src/cli/commands/todo.rs:94-98`) Handle notepad not supporting `+N` syntax: ```rust let mut cmd = Command::new(&editor); let editor_lower = editor.to_lowercase(); if !editor_lower.contains("notepad") { cmd.arg(format!("+{}", task.start_line)); } let status = cmd.arg(file_path).status()?; ``` --- ### 3. Release Workflow (`.forgejo/workflows/release.yml`) #### Add Windows build step after musl build: ```yaml - name: Build Windows binary if: steps.version.outputs.SKIP != 'true' run: nix build .#streamd-windows -o result-windows ``` #### Update artifact preparation step: ```yaml - name: Prepare release artifacts if: steps.version.outputs.SKIP != 'true' run: | mkdir -p release cp result-deb release/streamd_${{ steps.version.outputs.VERSION }}_amd64.deb cp result-musl/bin/streamd release/streamd-${{ steps.version.outputs.VERSION }}-linux-x86_64 cp result-windows/bin/streamd.exe release/streamd-${{ steps.version.outputs.VERSION }}-windows-x86_64.exe ``` --- ### Files to Modify | File | Change | |------|--------| | `flake.nix` | Add `mkWindowsCraneLib`, `mkStreamdWindows`, export package | | `src/config.rs` | Fix fallback path (line 41) | | `src/cli/commands/new.rs` | Windows editor fallback (line 27) | | `src/cli/commands/edit.rs` | Windows editor fallback (line 68) | | `src/cli/commands/todo.rs` | Windows editor fallback + line arg handling (lines 93-98) | | `.forgejo/workflows/release.yml` | Add Windows build step and artifact | --- ### Verification 1. **Build locally**: `nix build .#streamd-windows` 2. **Check binary**: `file result/bin/streamd.exe` 3. **Smoke test with Wine** (optional): `wine result/bin/streamd.exe --version` 4. **Run existing tests**: `cargo test` (code changes use compile-time cfg) 5. **Test release workflow**: Push version bump to trigger CI
kfickel added the
planned
label 2026-04-03 18:28:16 +02:00
Author
Owner

Implementation Summary

PR: #83
Duration: ~15 minutes
Tokens used: ~18k input / ~3k output

Findings

  • The implementation plan was accurate and complete. All changes landed as specified.
  • One deviation from the plan: pkgsCross.windows.pthreads had to go in buildInputs (linked into the target binary) rather than nativeBuildInputs (tools running on the build host). Putting it in nativeBuildInputs caused a Nix evaluation error because it's a Windows-platform package.
  • Cross-compilation itself succeeded on the first attempt after fixing the above — the binary compiled to a valid PE32+ x86-64 Windows executable.
  • The check phase in mkStreamdWindows had to be disabled (doCheck = false) because Nix tries to run the test binary directly, which fails with "Exec format error" when the target is Windows and Wine is not available in the sandbox.
  • All 167 existing tests continue to pass on Linux, and nix flake check passes cleanly.
## Implementation Summary **PR:** #83 **Duration:** ~15 minutes **Tokens used:** ~18k input / ~3k output ### Findings - The implementation plan was accurate and complete. All changes landed as specified. - One deviation from the plan: `pkgsCross.windows.pthreads` had to go in `buildInputs` (linked into the target binary) rather than `nativeBuildInputs` (tools running on the build host). Putting it in `nativeBuildInputs` caused a Nix evaluation error because it's a Windows-platform package. - Cross-compilation itself succeeded on the first attempt after fixing the above — the binary compiled to a valid `PE32+ x86-64` Windows executable. - The check phase in `mkStreamdWindows` had to be disabled (`doCheck = false`) because Nix tries to run the test binary directly, which fails with "Exec format error" when the target is Windows and Wine is not available in the sandbox. - All 167 existing tests continue to pass on Linux, and `nix flake check` passes cleanly.
Sign in to join this conversation.
No labels
planned
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Reference: kfickel/streamd#80
No description provided.