Skip to content

building and packaging

github-actions[bot] edited this page Oct 4, 2026 · 4 revisions

title: Building & Packaging permalink: /building-and-packaging parent: Development nav_order: 19

Building & Packaging

Release artifacts

Every release ships six zip packages - one per supported runtime identifier:

release_<version>_<rid>.zip
     3.1.0     win-x64 | win-arm64 | linux-x64 | linux-arm64 | osx-x64 | osx-arm64
Package Contents
._win-x64.zip SimpleZipDrive.exe, 7za.exe, 7zip-license.txt, winfsp-msil.dll, Avalonia native libraries (libSkiaSharp.dll, libHarfBuzzSharp.dll, av_libglesv2.dll), ReadMe.md, LICENSE.txt, WhatsNew.md
._win-arm64.zip same layout (ARM64 7za.exe)
._linux-x64.zip / ._linux-arm64.zip SimpleZipDrive (apphost), 7zzs, 7zip-license.txt, libSkiaSharp.so, libHarfBuzzSharp.so, docs
._osx-x64.zip / ._osx-arm64.zip SimpleZipDrive, universal 7zz, 7zip-license.txt, libSkiaSharp.dylib, libHarfBuzzSharp.dylib, docs

All are framework-dependent single-file executables: the .NET 10 runtime and the filesystem driver (WinFsp/Dokan on Windows, libfuse3/macFUSE on Linux/macOS) are the only prerequisites.

Automated builds (GitHub Actions)

Three workflows live in .github/workflows:

Workflow Trigger What it does
ci.yml Every push/PR to master Builds and tests the solution on Windows and compiles the app on Ubuntu and macOS
release.yml workflow_dispatch (version input) or a release_* tag push Verifies the csproj version matches, builds + tests on Windows, publishes and packages the six bundles on Windows/Linux/macOS runners, uploads them as release-bundles-* artifacts, then waits for approval in the protected release environment before creating/updating the GitHub release
wiki-sync.yml Push to master touching docs/** (or manually) Mirrors docs/*.md into the repository wiki (index.md → Home.md)

Reviewing a release before it is published

  1. Run Release from the Actions tab (or push a release_x.y.z tag).
  2. When the Build bundles jobs finish, download the release-bundles-* artifacts from the run summary - the six release_<version>_<rid>.zip files. The run summary lists sizes and SHA256 checksums, and nothing is public yet.
  3. Inspect the zips and smoke-test at least one packaged executable per OS.
  4. Approve the waiting Publish release job in the release environment (Settings → Environments → release → required reviewer). The workflow then creates the GitHub release with the six bundles attached and the matching WhatsNew.md section as the release notes.

If the bundles are wrong, do not approve: cancel the run, fix, and re-run.

First-time setup: the release environment needs at least one required reviewer, and the wiki sync needs a WIKI_TOKEN repository secret (a PAT with repository access - GITHUB_TOKEN cannot push to the wiki repository). Both are already configured for this repository.

Local packaging

scripts/package-release.ps1 performs the same publish/package steps locally:

# All targets for the current OS (other OSes are skipped on Windows, see below)
.\scripts\package-release.ps1 -Version 3.1.0

# Only the targets for one OS
.\scripts\package-release.ps1 -Version 3.1.0 -RuntimeIdentifiers win-x64,win-arm64

It runs the test suite first (pass -SkipTests to skip), publishes the requested runtime identifiers, and writes the bundles into SimpleZipDrive\bin\Release next to the historical releases. Existing files in that folder are never deleted; only the bundles for the requested version are written (or overwritten). On Linux/macOS the script uses the zip CLI so the apphost and the 7-Zip binary keep their executable bit; on a Windows host, Linux/macOS runtime identifiers are skipped with a warning because Compress-Archive cannot record Unix permissions. Build those bundles on their own OS (as the release workflow does) - a Windows developer asking for all six targets gets the two Windows bundles plus a clear warning.

Publish commands

Important: the .csproj contains <SelfContained>true</SelfContained>, but releases are built framework-dependent - the publish command must override it with --self-contained false. Publishing without the override produces huge self-contained bundles that also break the packaging assumptions documented below.

dotnet publish SimpleZipDrive\SimpleZipDrive.csproj -c Release -r win-x64    --self-contained false -o out\win-x64
dotnet publish SimpleZipDrive\SimpleZipDrive.csproj -c Release -r win-arm64  --self-contained false -o out\win-arm64
dotnet publish SimpleZipDrive\SimpleZipDrive.csproj -c Release -r linux-x64  --self-contained false -o out\linux-x64
dotnet publish SimpleZipDrive\SimpleZipDrive.csproj -c Release -r linux-arm64 --self-contained false -o out\linux-arm64
dotnet publish SimpleZipDrive\SimpleZipDrive.csproj -c Release -r osx-x64    --self-contained false -o out\osx-x64
dotnet publish SimpleZipDrive\SimpleZipDrive.csproj -c Release -r osx-arm64  --self-contained false -o out\osx-arm64

When packaging by hand, include the single-file executable, every file in the publish root except PDBs, and ReadMe.md/LICENSE.txt/WhatsNew.md. The support files in the publish root (the platform 7-Zip binary and its 7zip-license.txt, Avalonia's Skia/HarfBuzz/ANGLE libraries, and on Windows winfsp-msil.dll) must ship loose beside the executable.

Packaging internals

Three constraints make the file layout non-negotiable:

  1. winfsp-msil.dll must stay a real file beside the exe. Its static initializer (Fsp.Interop.Api.CheckVersion) calls FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location); Assembly.Location is an empty string inside a single-file bundle, so Path.GetFullPath("") throws and every WinFsp mount dies with "The path is empty (Parameter 'path')" before the driver is ever contacted. The csproj contains a target that runs before the bundler computes its file list:

    <Target Name="KeepWinFspInteropOutOfBundle" BeforeTargets="_ComputeFilesToBundle">
        <ItemGroup>
            <ResolvedFileToPublish Update="@(ResolvedFileToPublish)"
                Condition="'%(ResolvedFileToPublish.Filename)%(ResolvedFileToPublish.Extension)' == 'winfsp-msil.dll'"
                ExcludeFromSingleFile="true" />
        </ItemGroup>
    </Target>

    Timing matters: AfterTargets="ComputeFilesToPublish" / BeforeTargets="BundleFiles" do not work - the SDK splits bundled/non-bundled files in _ComputeFilesToBundle.

  2. The 7-Zip fallback binary must stay a real file beside the exe on every platform. SevenZipFallback probes AppContext.BaseDirectory for 7za.exe (Windows), 7zzs (Linux) or 7zz (macOS); the csproj copies exactly the file matching the publish RuntimeIdentifier and links it under its canonical name. Bundling it into the single file would make the fallback silently unavailable. On Unix the app sets the executable bit at runtime, and the release bundles preserve the bit through the zip CLI. The Unix binaries are committed with mode 100755 (git update-index --chmod=+x) so a publish from Linux/macOS keeps them executable.

    7zip-license.txt must ship beside them: it contains the Windows (7za.exe) and Linux/macOS (7zz, 7zzs) license texts, since the Windows and Unix packages carry different notices.

  3. winfsp.net stays at 2.1.x (2.1.25156). Interop 2.2.x rejects the stable native 2.1 driver ("incorrect dll version (need 2.2, have 2.1)"); interop 2.1 accepts both the 2.1 stable driver and 2.2+ betas. Version gates live in WinFspMountService (RequiredWinFspVersion = 2.1).

Release process checklist

  1. Bump <AssemblyVersion>/<FileVersion> to the new version in SimpleZipDrive.csproj and SimpleZipDrive.Tests.csproj (there is no explicit <Version> property; AssemblyVersion drives the published version).
  2. Update WhatsNew.md (user-facing Added/Fixed/Changed/Internal sections) - the matching ## <version> section becomes the GitHub release notes automatically.
  3. Push to master - the CI workflow must be green.
  4. Run Release from the Actions tab with the version, or push a release_<version> tag. The workflow verifies the csproj version, runs the tests again, and builds the six bundles.
  5. Download the release-bundles-* artifacts and review the zips before approving - the run summary lists sizes and SHA256 checksums.
  6. Smoke-test a downloaded bundle on each OS: mount a stored ZIP, a compressed archive, a .zar container, and an Xbox .iso/.cso image through the packaged executable (both a drive letter and a folder; elevated and non-elevated for WinFsp; a folder mount on Linux/macOS).
  7. Approve the Publish release job in the release environment. The workflow creates the GitHub release with the six bundles attached (full release - not draft/prerelease).
  8. Inform issue reporters whose bugs the release fixes.

Bundles can also be produced locally with scripts/package-release.ps1 -Version <version> and uploaded by hand with gh release create release_<version> --title "<version>" --notes-file . *.zip, but the Actions path is the supported one.

Clone this wiki locally