8000 Ship a Windows build alongside the macOS one · Issue #306 · ynishi/asterism · GitHub
[go: up one dir, main page]

Skip to content

Ship a Windows build alongside the macOS one #306

Open

Description

@ynishi

Problem

Asterism ships one download: a signed, notarized macOS DMG. #165 scoped Windows out ("each is its own toolchain and its own certificate story; v0.1.0's download is the macOS build"), and release.yml repeats that under "What is deliberately absent". That was the right call for getting one platform to open cleanly, but it now leaves out the people the app is for: the users of local AI image generation tooling, many of whom are not on a Mac. A reader who cannot run the app never gets far enough to judge whether it does what it says.

This issue reverses the Windows part of that scope-out. Linux packaging and an updater stay out, for the reasons #165 gives.

Desired outcome

A tagged release carries a Windows installer next to the DMG, and on a clean Windows machine that installer opens the app and gets as far as importing a folder of images.

What the tree assumes today

The work is more than adding a bundle target. These are the places that assume macOS, found by reading the tree:

  • The ffmpeg sidecar's video encoder is Apple-only. scripts/build-ffmpeg-sidecar.sh builds an LGPL ffmpeg whose only H.264 encoder is h264_videotoolbox, and it fails the build if that encoder is missing. preview_ffmpeg.rs tries libx264, then h264_videotoolbox (ENCODERS, line 40). libx264 is GPL and is left out on purpose, so a Windows sidecar needs a different LGPL-compatible H.264 encoder, and FFMPEG-NOTICE.md, which ships inside the app, has to describe that build too.
  • The sidecar build script is written for macOS. It uses Apple-specific configure flags and checks what the binary links against in a macOS-specific way. The Windows build needs its own toolchain (MSVC or MinGW) and its own link check.
  • Thumbnails take a different path. jobs/mod.rs compiles thumb_macos and thumb_video only on macOS. handlers.rs falls back to a single decode slot (THUMB_DECODE_SLOTS) and a JPEG encoder path everywhere else. That fallback compiles, but it has never shipped.
  • Unix-only branches. asterism-exporter-file rejects symlink mode when not on unix. asterism-server/src/core_init.rs has a cfg(not(unix)) branch for file permissions. Keychain signing in disclosure.rs is macOS-only, and core_init.rs already logs an error when it is requested elsewhere. Each one needs a decision about what Windows does, not just whether it compiles.
  • CI never builds for Windows. check.yml and release.yml both run on macos-latest. The Justfile recipes that release.yml relies on (dogfood-build, ffmpeg-sidecar) are bash.

Constraints

  • No GPL in the bundle. The LGPL discipline that the sidecar script and FFMPEG-NOTICE.md hold for macOS holds for Windows too. The release attaches the matching ffmpeg source and build script for each platform it ships.
  • One job per platform, not a matrix. release.yml already says that the file "should grow a job per platform rather than a matrix that pretends they are the same".
  • Releases stay drafts. A tag creates a draft release and a person publishes it, as now.
  • The macOS release path must not regress. That includes the notarization and Gatekeeper checks Sign and notarize the macOS build: the download opens like an app #165 added.

Acceptance criteria

  • A v* tag produces a draft release carrying a Windows installer, the Windows sidecar's ffmpeg source and build script, and the .crate sources the notices promise, next to the existing macOS assets.
  • The Windows bundle contains the ffmpeg sidecar, and the build fails if it does not, the same way dogfood-build's trailing assert does for macOS.
  • On a clean Windows machine, the installed app starts, imports a folder of images, and shows thumbnails. A video in that folder gets a preview rendition.
  • workflow_dispatch runs a rehearsal of the Windows job that produces a workflow artifact and touches no release, as the macOS job does.

Open

Cost these first, by reading release.yml, tauri.conf.json, tauri.bundle.conf.json and the sidecar script, before any job is written:

  1. The Windows H.264 encoder. Candidates are h264_mf (Media Foundation, the Windows system component that plays the role VideoToolbox plays on macOS) and libopenh264. Each has licensing and distribution consequences for the notice.
  2. Code signing. Decide whether the first Windows release is unsigned (so SmartScreen warns on first launch) or Authenticode-signed, and if signed, which certificate or signing service. This is the Windows counterpart of Sign and notarize the macOS build: the download opens like an app #165's Developer ID and notarization work.
  3. Installer format. Tauri v2 bundles NSIS (.exe) and MSI (WiX). Decide whether to ship one or both, and how the installer handles the WebView2 runtime.
  4. Decide whether check.yml gains a Windows build, and whether that is a compile check or a test run, so that cfg(not(target_os = "macos")) code stops being untested until release day.
  5. This issue does not yet cite how the audience splits across operating systems. Add those figures once they can be sourced publicly.

Refs #165

Activity

  1. ynishi commented on Sep 28, 2026

    @ynishi
    OwnerAuthor

    Open 5: how the audience splits across operating systems

    Measured on 2026-09-28 from public GitHub release data. For each tool, the download counts of every release asset were summed across all releases and grouped by operating system using the asset's file name. .dmg or mac in the name counts as macOS, .exe, .msi or win counts as Windows, and .AppImage, .deb, .rpm or linux counts as Linux. Updater manifests (latest*.yml), .blockmap files, checksums and source archives are excluded.

    Tool Kind Releases (span) Windows macOS Linux
    Image MetaHub AI image browser / metadata viewer 60 (2025-09 – 2026-09) 74.4% 11.3% 14.3%
    SD Prompt Reader AI image metadata reader 20 (2023-03 – 2024-05) 87.9% 12.1% — (no Linux build)
    Stability Matrix Stable Diffusion launcher 106 (2023-06 – 2026-09) 81.3% 10.8% 7.8%
    Invoke launcher Stable Diffusion launcher 36 (2024-12 – 2026-09) 73.1% 11.3% 15.6%
    Pinokio AI app launcher 294 (2023-07 – 2026-09) 76.0% 16.0% 8.0%

    The first two are the closest to what Asterism does, since they open and read the images these tools produce. Across all five, macOS accounts for 10.8–16.0% of downloads and Windows for 73–88%. Image MetaHub, the closest of them, ships Windows, macOS and Linux builds.

    Asterism's DMG is built on macos-latest, which is an Apple Silicon runner, with no universal target. So today's download reaches only part of that macOS share.

    Limits of these numbers:

    • Download counts are not users. They include automatic updates, which re-download the installer on each release, so a tool with frequent releases and a built-in updater counts the same user many times. This affects each OS differently, depending on how each tool's updater works.
    • A download counts only when it is served from GitHub Releases. ComfyUI Desktop is left out for this reason: its GitHub assets show 45 downloads in total, so they do not reflect how widely it is used.
    • These are the audiences of neighbouring tools, not measurements of Asterism's own users.

    To reproduce:

    gh api --paginate 'repos/<owner>/<repo>/releases?per_page=100' \
      -q '.[].assets[] | "\(.download_count) \(.name)"'
  2. ynishi commented on Sep 28, 2026

    @ynishi
    OwnerAuthor

    Open 1–3: cost and a recommended choice for each

    Researched on 2026-09-28 against main at e56cbb9, from upstream source and vendor documentation. Claims that could not be confirmed on a primary source are marked unverified.

    Open Recommendation Effort
    1. H.264 encoder h264_mf (Media Foundation), built statically with MSYS2 UCRT64 in the Windows job L
    2. Signing An OV or individual-validation certificate kept in a CA's cloud signing service, called through Tauri's bundle.windows.signCommand M
    3. Installer NSIS only, installMode: currentUser, WebView2 left on the default downloadBootstrapper S

    1. Encoder: h264_mf

    • Licence. FFmpeg's configure lists mediafoundation as an autodetected external library, and it is on none of the GPL, nonfree or version3 lists (configure, release/8.0). The build stays LGPL-2.1 with --enable-mediafoundation --enable-encoder=h264_mf added after --disable-everything. Media Foundation is part of Windows and is loaded at runtime, so it adds nothing to ship and nothing to offer source for.
    • No GPU required. By default h264_mf only looks for software encoders, and it looks for hardware ones only when -hw_encoding 1 is passed (mf_utils.c). So it uses Microsoft's built-in software H.264 encoder, which supports Baseline, Main and High (Microsoft).
    • Where it fails. Windows N editions ship without Media Foundation until the Media Feature Pack is installed (Microsoft). mfenc.c loads mfplat.dll at runtime, so on those editions ffmpeg still starts and thumbnails still work, and only the preview job fails. Its error message should name the Media Feature Pack. The same Microsoft page says the encoder does not support Windows Server editions.
    • The quality flag has to change. preview_ffmpeg.rs passes -q:v 55 to every encoder except libx264. The ffmpeg CLI turns that into a global_quality of 118 × 55 = 6490 (ffmpeg_mux_init.c). mfenc.c hands that value to Media Foundation as a QP, and Microsoft documents a QP range of 16–51. What the encoder does with 6490 is unverified. h264_mf needs its own arguments: -rate_control quality -quality 55.
    • Rejected: libopenh264. Cisco pays the patent licence only for its own binary, which the user downloads separately and can switch on and off (FAQ, binary licence). A copy we bundle or build ourselves has no patent coverage. Using Cisco's binary would mean adding a runtime download and a settings toggle just to get a fallback.
    • Rejected as the only encoder: nvenc / qsv / amf. Each one needs its vendor's GPU and driver.
    • Toolchain. FFmpeg supports MinGW-w64 through MSYS2 (platform notes), and msys2/setup-msys2 provides MSYS2 on GitHub Actions. Cross-compiling from Linux would split the Windows release across two jobs. BtbN's prebuilt "lgpl" binaries add --enable-version3, so they do not match this project's LGPL-2.1 build (variant).
    • Only the first build on the runner can answer these (unverified): whether MinGW's headers let h264_mf build, whether static linking removes libwinpthread-1.dll, and how long the build takes. No source gives a build time.

    2. Signing

    • Unsigned. SmartScreen shows "Windows protected your PC", and the user has to choose "Run anyway". On Windows 11, Smart App Control "will block execution of unsigned files unless the file has a positive reputation", and that check applies to every executable, which includes the ffmpeg sidecar (Microsoft). An unsigned build therefore fails this issue's "clean Windows machine" criterion. Microsoft's wording suggests that a signed file with no reputation yet does pass Smart App Control, but that is unverified.
    • Artifact Signing (the new name of Trusted Signing) is not available here: individual developers must be located in the US or Canada (quickstart). The maintainer is an individual based in Japan.
    • EV brings no benefit. "EV certificates no longer bypass SmartScreen" (the same Microsoft page).
    • Hardware-held keys. Since 2023-06-01 the private key has to live in a hardware module (CA/B Forum), so a .pfx stored as a GitHub secret is no longer possible for a new certificate. The key has to sit in a CA's cloud signing service. Certificates issued from 2026-03-01 are valid for at most 460 days (Ballot CSC-31), so one has to be reissued about every 15 months.
    • Price. Certum Open Source Code Signing costs from €49 with cloud signing (Certum). An SSL.com OV certificate costs $129 per year (SSL.com). Three things are unverified and must be checked before buying: whether Certum issues to an individual based in Japan, whether either CA's cloud signer can sign unattended on a CI runner, and SSL.com's eSigner subscription price.
    • Tauri. bundle.windows.signCommand runs the CA's CLI (Tauri). The bundler signs the external binaries, the main exe and the NSIS installer with it, so one command covers everything Smart App Control checks.
    • Until a certificate exists, the Windows job runs only as the workflow_dispatch rehearsal. Publishing an unsigned release with a note about Smart App Control is the maintainer's decision, not the default.

    3. Installer

    • NSIS, not MSI. The MSI template installs per machine, so it needs admin rights. It also depends on the VBSCRIPT optional feature, which Microsoft is deprecating, and it only accepts numeric pre-release versions (Tauri). NSIS with currentUser installs into %LOCALAPPDATA% without an admin prompt. MSI only helps with enterprise deployment, and that is not who this build is for.
    • WebView2. Leave it on the default downloadBootstrapper. It adds almost nothing to the installer's size. offlineInstaller would add about 127 MB and fixedRuntime about 180 MB (WebviewInstallMode). Windows 11 already includes the runtime, and the bootstrapper installs it on the few Windows 10 machines that lack it (Microsoft).
    • Sidecar name. The existing externalBin entry stays as it is. The script only has to write target/ffmpeg-sidecar/ffmpeg-x86_64-pc-windows-msvc.exe (resources.rs). Unverified: that the installer places it beside the main exe as ffmpeg.exe, as the bundler templates suggest.

    Changes this issue did not list

    Found by reading the tree:

    • The bundled ffmpeg would never be found on Windows. thumb_ffmpeg.rs (sidecar_beside, and the PATH probe) looks for a file named ffmpeg. The tree does not use std::env::consts::EXE_SUFFIX anywhere, and import_launcher.rs joins BINARY_NAME without .exe.
    • A console window at every ffmpeg spawn. None of the three ffmpeg spawn sites sets CREATE_NO_WINDOW, and the release app is built as a GUI app (windows_subsystem = "windows"). Whether a console window actually flashes without the flag is unverified.
    • The third-party notice is macOS-only. about.toml generates THIRD-PARTY-NOTICES.md for aarch64-apple-darwin only. A Windows build links a different set of crates, so the notice has to cover that target too. FFMPEG-NOTICE.md describes a single ffmpeg build, and it would have to describe both.
    • The Justfile cannot run as is. It sets its shell to zsh, and dogfood-build hard-codes the macOS bundle paths.
    • MCP-proxy auto-launch uses macOS open. mcp_proxy.rs starts the app with open, so auto-launch fails on Windows.
    • The two release jobs can race. Both would run gh release view || gh release create, so whichever finishes second fails on "already exists". Either upload after needs: macos, or add a final job that creates the draft release.
    • The build script is macOS-specific. build-ffmpeg-sidecar.sh uses sysctl, shasum and otool -L. The Windows build needs a sibling script that checks linked DLLs with objdump -p and an allow-list of system DLLs.
  3. ynishi commented on Sep 28, 2026

    @ynishi
    OwnerAuthor

    Open 4: decided — check.yml gains a Windows job

    Decision: check.yml gets a Windows job by default. If it runs into problems, those problems get weighed then, and the decision is revisited here with them in hand. It is not argued out in advance.

    The reason is the one Open 4 gave. Today every cfg(not(target_os = "macos")) path compiles for the first time on release day. That covers the single-slot thumbnail decode and JPEG path in jobs/handlers.rs, the non-unix branches in asterism-exporter-file and core_init.rs, and the Windows code this issue adds (the EXE_SUFFIX lookup, CREATE_NO_WINDOW, the h264_mf arm). A Windows job in check.yml makes each pull request answer for that code instead.

    What the job has to deal with. All of this comes from reading check.yml and the Justfile as they are now:

    • The Justfile shell. set shell := ["zsh", "-cu"] has no zsh to run on a Windows runner. Either the recipes the job calls get a Windows shell, or the job calls cargo directly. If it calls cargo directly, it keeps a second copy of what green means, which is what the top of check.yml argues against.
    • ffmpeg for the tests. The macOS job runs brew install ffmpeg because three tests panic without an ffmpeg binary. The Windows job needs its own source, or ASTERISM_FFMPEG pointed at one.
    • docs/aidoc/ stays with the macOS job. Its regeneration step is tied to the macOS target (cargo-aidoc records the target, and two job modules are macOS-only). The Windows job must not regenerate or push it.
    • collation-jsc stays out, for the reason check.yml already gives: it passes as a skip wherever jsc is missing.
    • Scope of the first run. Compile and clippy only, or the tests as well, gets settled when the job is written. The test half is the expensive one: the macOS job's test build was 2 m 25 s of an 11 m 01 s warm run. The first Windows run is also the measurement of what it costs.

    If a problem turns out to be large, the options, in order, are these. Narrow the job to cargo clippy / cargo check for the Windows target. Keep it off pull requests and run it only on main. Drop it. Whichever is chosen gets recorded here, with the run that prompted it.

  4. ynishi commented on Sep 29, 2026

    @ynishi
    OwnerAuthor

    Open 4: the Windows job is narrowed to every gate except the Rust tests

    This records the fallback Open 4 said would be chosen if the job ran into problems, together with the run that prompted it.

    Run 36512441186, on #308 at cc59011, is the second run of the Windows job.

    • cargo clippy --workspace -D warnings passed.
    • Every check-shared gate 5204 passed.
    • The Rust test suite failed in 23 test targets.

    The first run had already found one Windows-only compile error, the symlink arm in asterism-exporter-file, which dce86c5 fixed.

    The test failures are not just tests written against unix. Some come from production code the Windows build would ship:

    • resolve_output_dir refuses any export directory that does not start with /.
    • The disclosure write path and teams-infra's blob store open a directory as a file to fsync it, and Windows refuses that with os error 5.

    Test-only failures (paths joined or compared with /) are part of the same run. #309 lists every failure, grouped by cause.

    Decision: the Windows job runs check-shared and clippy, and leaves the Rust tests to the macOS job. It still compiles and lints every cfg(windows) and cfg(not(target_os = "macos")) path on each pull request, which was the point of Open 4. The tests come back to the Windows job, with an ffmpeg install, once #309 is done.

    This is the first of Open 4's three fallbacks, taken more broadly than it was worded. Open 4 said "cargo clippy / cargo check", but the job keeps every other gate as well, because all of them passed on Windows.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      0