1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# The Windows checks a machine can run: that the tree builds and passes here,
# that the shipped binary imports nothing Windows does not have, that the
# committed icons still match the drawings they were generated from, and, on a
# release, the unsigned Store package. The suite is repeated from the fleet's
# `ci.yml` because that runs on Linux and three things here are compiled only
# for this target — `build.rs`'s manifest arm, the `ico` dependency, and the
# window icon `include_bytes!` reaches. Out of reach: the window, the installer
# scripts, a file association, the certification kit, and the icon as a person
# sees it.
#
# Author: David M. Anderson
# Built with AI assistance (Claude, Anthropic)
name: windows
on:
push:
branches:
pull_request:
branches:
# The release event is what attaches the package. A person's token creates
# the release, so the event fires.
release:
types:
workflow_dispatch:
inputs:
# Optional, because a screenshots run has no tag to give. With one the
# package is attached to that release; without one it is built and kept
# as an artefact.
tag:
description: 'Tag to package for (e.g. v0.1.4)'
required: false
type: string
# A choice rather than the boolean `apple-silicon.yml` has, because this
# lane has a third thing to ask for. Three of the controls the recipes
# name have never been measured on this platform, so `reference` takes
# the one plain frame they get measured off and `full` takes the set.
screenshots:
description: 'Store screenshots'
required: false
default: 'no'
type: choice
options:
permissions:
contents: write
concurrency:
# Keyed by the tag when this is releasing, and by the event and the ref when
# it is not. `github.ref` is refs/heads/main for a workflow_dispatch as much
# as for a push, so keying on it alone lets a push to main cancel a dispatched
# run - and a dispatch asking only for screenshots has no tag to key on.
group: windows-${{ github.event.release.tag_name || inputs.tag || format('{0}-{1}', github.event_name, github.ref) }}
# A superseded check is worth cancelling; a half-built release is not.
cancel-in-progress: ${{ github.event_name == 'push' || github.event_name == 'pull_request' }}
jobs:
windows:
runs-on: windows-latest
steps:
- uses: actions/checkout@v7
with:
ref: ${{ github.event.release.tag_name || inputs.tag || github.ref }}
# The packaging scripts are the only code here that nothing else compiles
# or runs, and there is no PowerShell on the Linux lane. The step is
# excelano/.github's because it is the same one in every repository that
# has them.
- name: The packaging scripts parse
uses: excelano/.github/.github/actions/powershell-parses@main
- name: Install Rust
run: rustup toolchain install stable --profile minimal
- uses: Swatinem/rust-cache@v2
- name: What is installed
run: rustc -V; cargo -V
- name: Build
run: cargo build --workspace --all-targets
- name: Test
run: cargo test --workspace
# `CLAUDE.md` says clippy must be silent, so a warning is an error here.
- name: Clippy
run: cargo clippy --workspace --all-targets -- -D warnings
# The check Slipcase 0.1.1 needed and did not have. It was packaged,
# certified by the Windows App Certification Kit, submitted, and failed
# Store policy 10.2.4.1 because it imported VCRUNTIME140.dll from the
# Visual C++ Redistributable - so on the tester's clean machine it
# installed and then would not start. Nothing else in this file could
# catch it: a GitHub runner has Visual Studio, exactly like every machine
# this project has ever built on.
#
# `.cargo/config.toml` is the fix and this is the guard on it. A
# dependency added on any of the three platforms can pull in a DLL that is
# not part of Windows without anybody here noticing, and the day that
# happens is the day this step has to be the thing that says so.
#
# The release binary rather than the debug one, because the release binary
# is what ships and is the only one whose import table is the artefact's.
- name: Every DLL the shipped binary imports comes with Windows
run: |
cargo build --release -p filebase
powershell -ExecutionPolicy Bypass -File packaging/windows/check-imports.ps1
# The release binary is also the only place two other properties exist. The
# GUI subsystem comes from an attribute that is off under
# `debug_assertions`, and a console-subsystem build puts a console window
# behind the application when a file manager launches it. The DPI
# declaration comes from `build.rs` handing the linker a manifest, and the
# certification kit reads it out of the binary rather than out of the
# running process. `build-msix.ps1` refuses on the first and the kit
# reports the second, and neither of those runs here - so both are read
# straight out of the artefact instead.
- name: The shipped binary is a GUI subsystem one and declares its DPI awareness
shell: pwsh
run: |
$meta = cargo metadata --format-version 1 --no-deps | ConvertFrom-Json
$exe = Join-Path $meta.target_directory 'release\filebase.exe'
$bytes = [System.IO.File]::ReadAllBytes($exe)
$pe = [BitConverter]::ToInt32($bytes, 0x3C)
$subsystem = [BitConverter]::ToUInt16($bytes, $pe + 92)
if ($subsystem -ne 2) {
throw "subsystem $subsystem, and a shipped build must be 2 (Windows GUI)"
}
$text = [System.Text.Encoding]::UTF8.GetString($bytes)
if (-not $text.Contains('PerMonitorV2')) {
throw 'no PerMonitorV2 in the binary - build.rs did not embed the application manifest'
}
Write-Host 'subsystem 2, and the application manifest is embedded'
# **No install check, where every sibling has one.** That step drives
# `install.ps1` and reads the registry back to prove an association goes
# on and comes off cleanly. This application declares no file type and
# ships no install scripts: there is no association to register, because
# it opens a folder and Windows has no way to say that. Nothing was
# dropped here; there is nothing to check.
# The Store takes the package unsigned, and the runner assembles the one that
# is uploaded. The steps are excelano/.github's, because they were the same in
# every repository that has this target; the publisher is the organisation
# secret, being the one value in the identity that is an account's rather than
# this product's.
- name: The Store package
uses: excelano/.github/.github/actions/store-package@main
with:
publisher: ${{ secrets.STORE_PUBLISHER }}
tag: ${{ github.event.release.tag_name || inputs.tag }}
token: ${{ secrets.GITHUB_TOKEN }}
# Off unless asked for. A bug fix should not pay for a set of Store
# screenshots, and a set nobody asked for is a set nobody looks at.
# `shots.ps1` carries the recipes and is the same command that retakes
# them by hand on the Windows machine; `screenshot.ps1` beside it is the
# driver and knows nothing about Filebase.
#
# `shots.ps1` launches the executable directly with a folder as an
# argument, so no association has to be installed first. The siblings
# install one here because the shell is what routes their document.
- name: A folder of containers to ask about
if: inputs.screenshots && inputs.screenshots != 'no'
shell: bash
run: |
cargo install slipcase --quiet
./packaging/demo-corpus.sh
- name: The Store screenshots
if: inputs.screenshots && inputs.screenshots != 'no'
uses: excelano/.github/.github/actions/store-screenshots@main
with:
mode: ${{ inputs.screenshots }}
- name: The committed icons and Store assets still match their generator
run: |
cargo run --manifest-path packaging/make-icons/Cargo.toml
git diff --exit-code -- packaging/icons packaging/windows/filebase.ico packaging/windows/assets