mirror of
https://github.com/gmeligio/flutter-docker-image.git
synced 2026-05-24 12:30:34 +00:00
ci: test windows image (#339)
- **`windows.Dockerfile`** — fixes the `COPY` source path from `./test/Windows.Tests.ps1` to `./test/windows/Windows.Tests.ps1` (root cause of the 12-month "in_progress forever" state); adds `COPY ./config/version.json` to the `test` stage; replaces the commented `CMD` with a real `CMD` so `docker run`/`docker compose run` invokes Pester without arguments. - **`test/windows/Windows.Tests.ps1`** — fixes the `VC.CMake.Project` pattern typo (`,versiona*` → `,version=*`) and standardises all three VS-component patterns to `,version=*`; adds a `Flutter version` test that reads `config/version.json` and asserts `flutter --version` inside the container reports the same version; adds a `Flutter doctor` test with a per-line parser (skip disabled platforms, fail on any non-`[✓]` for Windows toolchain lines, fail on `[✗]` elsewhere). - **`script/RunPester.ps1`** — forces `[Console]::OutputEncoding = UTF8` so the `[✓]`/`[!]`/`[✗]` doctor glyphs survive the `windows-2025` runner's default OEM codepage. - **`test/windows/`** — deletes the dead `ory/dockertest` Go skeleton (`main.go`, `main_test.go`, `go.mod`, `go.sum`) that was never wired into CI and had its only meaningful assertion commented out. - **`.github/workflows/windows.yml`** — deletes three commented-out blocks (`Scan with Docker Scout`, `Push to Docker Hub`, `validate_version` job referencing the deleted `config/version.cue`); drops the now-unused elevated permissions (`packages: write`, `pull-requests: write`, `security-events: write`). --------- Co-authored-by: verified-commit[bot] <180343340+verified-commit[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -25,6 +25,12 @@ version = "v3.6.0"
|
|||||||
[resolutions."docker/metadata-action"."^5"]
|
[resolutions."docker/metadata-action"."^5"]
|
||||||
version = "v5.10.0"
|
version = "v5.10.0"
|
||||||
|
|
||||||
|
[resolutions."docker/metadata-action"."~5.10.0"]
|
||||||
|
version = "v5.10.0"
|
||||||
|
|
||||||
|
[resolutions."docker/metadata-action"."~5.7.0"]
|
||||||
|
version = "v5.7.0"
|
||||||
|
|
||||||
[resolutions."docker/scout-action"."^1"]
|
[resolutions."docker/scout-action"."^1"]
|
||||||
version = "v1.18.2"
|
version = "v1.18.2"
|
||||||
|
|
||||||
@@ -106,6 +112,12 @@ repository = "docker/metadata-action"
|
|||||||
ref_type = "tag"
|
ref_type = "tag"
|
||||||
date = "2025-11-27T12:36:24Z"
|
date = "2025-11-27T12:36:24Z"
|
||||||
|
|
||||||
|
[actions."docker/metadata-action"."v5.7.0"]
|
||||||
|
sha = "902fa8ec7d6ecbf8d84d538b9b233a880e428804"
|
||||||
|
repository = "docker/metadata-action"
|
||||||
|
ref_type = "release"
|
||||||
|
date = "2025-02-26T15:31:35Z"
|
||||||
|
|
||||||
[actions."docker/scout-action"."v1.18.2"]
|
[actions."docker/scout-action"."v1.18.2"]
|
||||||
sha = "f8c776824083494ab0d56b8105ba2ca85c86e4de"
|
sha = "f8c776824083494ab0d56b8105ba2ca85c86e4de"
|
||||||
repository = "docker/scout-action"
|
repository = "docker/scout-action"
|
||||||
|
|||||||
@@ -17,3 +17,6 @@
|
|||||||
"peter-evans/create-pull-request" = "^7"
|
"peter-evans/create-pull-request" = "^7"
|
||||||
"peter-evans/dockerhub-description" = "^5"
|
"peter-evans/dockerhub-description" = "^5"
|
||||||
"plexsystems/container-structure-test-action" = "~0.3.0"
|
"plexsystems/container-structure-test-action" = "~0.3.0"
|
||||||
|
|
||||||
|
[actions.overrides]
|
||||||
|
"docker/metadata-action" = [{ workflow = ".github/workflows/build.yml", job = "test_image", step = 5, version = "~5.10.0" }, { workflow = ".github/workflows/ci.yml", job = "test_image", step = 5, version = "~5.10.0" }, { workflow = ".github/workflows/release.yml", job = "release_android", step = 2, version = "~5.10.0" }, { workflow = ".github/workflows/windows.yml", job = "test_windows", step = 3, version = "~5.7.0" }]
|
||||||
|
|||||||
@@ -49,6 +49,8 @@ jobs:
|
|||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
|
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
|
||||||
|
with:
|
||||||
|
buildkitd-flags: --debug
|
||||||
|
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
||||||
|
|||||||
@@ -12,16 +12,9 @@ concurrency:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test_windows:
|
test_windows:
|
||||||
permissions:
|
|
||||||
# Allow to write packages for the docker/scout-action to write a comment
|
|
||||||
packages: write
|
|
||||||
# Allow to write pull requests for the docker/scout-action to write a comment
|
|
||||||
pull-requests: write
|
|
||||||
# Allow to write security events for github/codeql-action/upload-sarif to upload SARIF results
|
|
||||||
security-events: write
|
|
||||||
runs-on: windows-2025
|
runs-on: windows-2025
|
||||||
env:
|
env:
|
||||||
IMAGE_REPOSITORY_NAME: flutter-android
|
IMAGE_REPOSITORY_NAME: flutter-windows
|
||||||
VERSION_MANIFEST: config/version.json
|
VERSION_MANIFEST: config/version.json
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
@@ -44,56 +37,21 @@ jobs:
|
|||||||
const script = require('./script/setEnvironmentVariables.js')
|
const script = require('./script/setEnvironmentVariables.js')
|
||||||
return await script({ core })
|
return await script({ core })
|
||||||
|
|
||||||
# - name: Load image metadata
|
- name: Load image metadata
|
||||||
# uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0
|
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0
|
||||||
# id: metadata
|
id: metadata
|
||||||
# with:
|
with:
|
||||||
# images: |
|
images: |
|
||||||
# ${{ env.IMAGE_REPOSITORY_PATH }}
|
${{ env.IMAGE_REPOSITORY_PATH }}
|
||||||
# tags: |
|
tags: |
|
||||||
# type=raw,value=${{ env.FLUTTER_VERSION }}
|
type=raw,value=${{ env.FLUTTER_VERSION }}
|
||||||
|
|
||||||
# - name: Set up Docker Buildx
|
# Docker Buildx is not supported for Windows containers
|
||||||
# uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
|
# so we'll use direct docker build commands
|
||||||
|
- name: Test image and push to local Docker daemon
|
||||||
- name: Build image and push to local Docker daemon
|
|
||||||
shell: powershell
|
shell: powershell
|
||||||
run: |
|
run: |
|
||||||
docker build . -f windows.Dockerfile --build-arg flutter_version=${{ env.FLUTTER_VERSION }} -t ${{ env.IMAGE_REPOSITORY_PATH }}
|
docker build . -f windows.Dockerfile --build-arg flutter_version=${{ env.FLUTTER_VERSION }} -t ${{ fromJson(steps.metadata.outputs.json).tags[0] }} --target test
|
||||||
|
|
||||||
# - name: Build image and push to local Docker daemon
|
docker run --rm ${{ fromJson(steps.metadata.outputs.json).tags[0] }}
|
||||||
# uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6.15.0
|
|
||||||
# with:
|
|
||||||
# file: windows.Dockerfile
|
|
||||||
# load: true
|
|
||||||
# cache-from: type=gha
|
|
||||||
# cache-to: type=gha,mode=max
|
|
||||||
# labels: ${{ steps.metadata.outputs.labels }}
|
|
||||||
# tags: ${{ steps.metadata.outputs.tags }}
|
|
||||||
# target: android
|
|
||||||
# build-args: |
|
|
||||||
# flutter_version=${{ env.FLUTTER_VERSION }}
|
|
||||||
|
|
||||||
# - name: Test image
|
|
||||||
# uses: plexsystems/container-structure-test-action@c0a028aa96e8e82ae35be556040340cbb3e280ca # v0.3.0
|
|
||||||
# with:
|
|
||||||
# image: ${{ fromJSON(steps.metadata.outputs.json).tags[0] }}
|
|
||||||
# config: test/android.yml
|
|
||||||
|
|
||||||
# # TODO: Parallelize testing and vulnerability scanning
|
|
||||||
# - name: Scan with Docker Scout
|
|
||||||
# id: docker-scout
|
|
||||||
# uses: docker/scout-action@0133ff88fe16d4a412dc4827a8fccbccb6b583e0 # v1.16.3
|
|
||||||
# with:
|
|
||||||
# command: compare, recommendations
|
|
||||||
# # Use the Docker Hub image that is the first tag in the metadata
|
|
||||||
# image: local://${{ fromJson(steps.metadata.outputs.json).tags[0] }}
|
|
||||||
# # github-token is needed to be able to write the PR comment
|
|
||||||
# github-token: ${{ github.token }}
|
|
||||||
# only-fixed: true
|
|
||||||
# organization: ${{ secrets.DOCKER_HUB_USERNAME }}
|
|
||||||
# # sarif-file: output.sarif.json
|
|
||||||
# to-env: prod
|
|
||||||
# # Enable debug logging when needed
|
|
||||||
# # debug: true
|
|
||||||
# # verbose-debug: true
|
|
||||||
|
|||||||
@@ -34,5 +34,13 @@ services:
|
|||||||
windows:
|
windows:
|
||||||
build:
|
build:
|
||||||
dockerfile: ./windows.Dockerfile
|
dockerfile: ./windows.Dockerfile
|
||||||
|
target: flutter
|
||||||
args:
|
args:
|
||||||
flutter_version: $FLUTTER_VERSION
|
flutter_version: $FLUTTER_VERSION
|
||||||
|
|
||||||
|
windows-test:
|
||||||
|
build:
|
||||||
|
dockerfile: ./windows.Dockerfile
|
||||||
|
target: test
|
||||||
|
args:
|
||||||
|
flutter_version: $FLUTTER_VERSION
|
||||||
@@ -5,8 +5,9 @@
|
|||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "npm run readme && npm run contributing && npm run license",
|
"build": "npm run readme && npm run windows && npm run contributing && npm run license",
|
||||||
"readme": "cross-env NODE_ENV=production node compile.js readme.mdx ../../readme.md",
|
"readme": "cross-env NODE_ENV=production node compile.js readme.mdx ../../readme.md",
|
||||||
|
"windows": "cross-env NODE_ENV=production node compile.js windows.mdx ../windows.md",
|
||||||
"license": "cross-env NODE_ENV=production node compile.js license.mdx ../../LICENSE.md",
|
"license": "cross-env NODE_ENV=production node compile.js license.mdx ../../LICENSE.md",
|
||||||
"contributing": "cross-env NODE_ENV=production node compile.js contributing.mdx ../contributing.md",
|
"contributing": "cross-env NODE_ENV=production node compile.js contributing.mdx ../contributing.md",
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
1. Install tools
|
1. Install tools
|
||||||
|
|
||||||
```powershell`
|
```powershell
|
||||||
# # needed? No
|
# # needed? No
|
||||||
# --add Microsoft.Component.MSBuild' `
|
# --add Microsoft.Component.MSBuild' `
|
||||||
# # needed? No
|
# # needed? No
|
||||||
@@ -29,6 +29,7 @@ RUN Invoke-WebRequest -Uri https://aka.ms/vs/17/release/vs_buildtools.exe -OutFi
|
|||||||
Remove-Item vs_BuildTools.exe;
|
Remove-Item vs_BuildTools.exe;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
1. Read dependencies from [flutter_tools](https://github.com/flutter/flutter/blob/master/packages/flutter_tools/lib/src/windows/visual_studio.dart).
|
||||||
1. Check how it can be run in Github actions.
|
1. Check how it can be run in Github actions.
|
||||||
1. Check how it can be run in Gitlab CI/CD.
|
1. Check how it can be run in Gitlab CI/CD.
|
||||||
1. Test where is installed.
|
1. Test where is installed.
|
||||||
@@ -73,13 +74,12 @@ Debug with `curl -A github165 -v https://mcr.microsoft.com/v2/powershell/manifes
|
|||||||
|
|
||||||
1. Enable Windows Developer Settings to solve error:
|
1. Enable Windows Developer Settings to solve error:
|
||||||
|
|
||||||
>Building with plugins requires symlink support.
|
|
||||||
>
|
|
||||||
>Please enable Developer Mode in your system settings. Run
|
|
||||||
> start ms-settings:developers
|
|
||||||
>to open settings.
|
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
|
# >Building with plugins requires symlink support.
|
||||||
|
# >
|
||||||
|
# >Please enable Developer Mode in your system settings. Run
|
||||||
|
# > start ms-settings:developers
|
||||||
|
# >to open settings.
|
||||||
reg add "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock" /t REG_DWORD /f /v "AllowDevelopmentWithoutDevLicense" /d "1"
|
reg add "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock" /t REG_DWORD /f /v "AllowDevelopmentWithoutDevLicense" /d "1"
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -87,7 +87,7 @@ Debug with `curl -A github165 -v https://mcr.microsoft.com/v2/powershell/manifes
|
|||||||
|
|
||||||
1. Docker version must be pinned in Github workflow to avoid breaking changes: with escaping `\"` syntax inside RUN directive, etc.
|
1. Docker version must be pinned in Github workflow to avoid breaking changes: with escaping `\"` syntax inside RUN directive, etc.
|
||||||
|
|
||||||
1. Packaging tool in Windows: <https://pub.dev/packages/msix> . It uses the executables:
|
1. Packaging tool in Windows: [msix](https://pub.dev/packages/msix) . It uses the executables:
|
||||||
|
|
||||||
- [makeappx.exe](https://learn.microsoft.com/en-us/windows/win32/appxpkg/make-appx-package--makeappx-exe-)
|
- [makeappx.exe](https://learn.microsoft.com/en-us/windows/win32/appxpkg/make-appx-package--makeappx-exe-)
|
||||||
- [makepri.exe](https://learn.microsoft.com/en-us/windows/uwp/app-resources/makepri-exe-command-options)
|
- [makepri.exe](https://learn.microsoft.com/en-us/windows/uwp/app-resources/makepri-exe-command-options)
|
||||||
@@ -108,7 +108,7 @@ Debug with `curl -A github165 -v https://mcr.microsoft.com/v2/powershell/manifes
|
|||||||
|
|
||||||
- According to the [msstore guide](https://learn.microsoft.com/en-us/windows/apps/publish/msstore-dev-cli/commands?pivots=msstoredevcli-installer-linux#installation), It will be needed to install Microsoft.NetCore.Component.Runtime.8.0 with vs_BuildTools
|
- According to the [msstore guide](https://learn.microsoft.com/en-us/windows/apps/publish/msstore-dev-cli/commands?pivots=msstoredevcli-installer-linux#installation), It will be needed to install Microsoft.NetCore.Component.Runtime.8.0 with vs_BuildTools
|
||||||
|
|
||||||
1. From <https://github.com/tauu/flutter-windows-builder/blob/main/Dockerfile> => install <https://github.com/microsoft/StoreBroker> This is currently the primary tool to publish to Microsoft Store
|
1. From [github.com/tauu/flutter-windows-builder/Dockerfile](https://github.com/tauu/flutter-windows-builder/blob/main/Dockerfile) => install [github.com/microsoft/StoreBroker](https://github.com/microsoft/StoreBroker) This is currently the primary tool to publish to Microsoft Store
|
||||||
|
|
||||||
- Not installed right now
|
- Not installed right now
|
||||||
|
|
||||||
+109
@@ -0,0 +1,109 @@
|
|||||||
|
<!--- This markdown file was auto-generated from "windows.mdx" -->
|
||||||
|
|
||||||
|
# Windows
|
||||||
|
|
||||||
|
## Swich between Linux and Windows containers
|
||||||
|
|
||||||
|
& $Env:ProgramFiles\\Docker\\Docker\\DockerCli.exe -SwitchDaemon
|
||||||
|
|
||||||
|
## TODO
|
||||||
|
|
||||||
|
1. Install tools
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# # needed? No
|
||||||
|
# --add Microsoft.Component.MSBuild' `
|
||||||
|
# # needed? No
|
||||||
|
# --add Microsoft.VisualStudio.Component.TestTools.BuildTools `
|
||||||
|
# # needed? No
|
||||||
|
# --add Microsoft.VisualStudio.Component.VC.ASAN `
|
||||||
|
# # needed? no
|
||||||
|
# # --add Microsoft.VisualStudio.Component.VC.Tools.x86.x64 `
|
||||||
|
RUN Invoke-WebRequest -Uri https://aka.ms/vs/17/release/vs_buildtools.exe -OutFile vs_BuildTools.exe; `
|
||||||
|
Start-Process vs_BuildTools.exe -ArgumentList '--quiet --wait --norestart --nocache `
|
||||||
|
# # needed? yes
|
||||||
|
# --add Microsoft.VisualStudio.Component.VC.CMake.Project `
|
||||||
|
# # needed? Yes
|
||||||
|
# --add Microsoft.VisualStudio.Component.Windows11SDK.22621 `
|
||||||
|
# # needed?
|
||||||
|
# --add Microsoft.VisualStudio.Workload.VCTools' `
|
||||||
|
-Wait; `
|
||||||
|
Remove-Item vs_BuildTools.exe;
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Read dependencies from [flutter\_tools](https://github.com/flutter/flutter/blob/master/packages/flutter%5Ftools/lib/src/windows/visual%5Fstudio.dart).
|
||||||
|
2. Check how it can be run in Github actions.
|
||||||
|
3. Check how it can be run in Gitlab CI/CD.
|
||||||
|
4. Test where is installed.
|
||||||
|
5. Test that path to powershell.exe exists.
|
||||||
|
6. Test with a snapshot of flutter config to determine if new feature flags should be enabled or disabled.
|
||||||
|
7. Test that Build Tools were installed in C:\\Program Files (x86)\\Microsoft Visual Studio\\2022\\BuildTools\\msbuild\\current\\bin
|
||||||
|
8. Check [Windows installation requirements for Flutter](https://docs.flutter.dev/get-started/install/windows/desktop)
|
||||||
|
9. Add docs explaining to use `$VerbosePreference = 'Continue';` in the SHELL to debug unexpected pwsh problems.
|
||||||
|
|
||||||
|
## Open issue in windows Docker images repo
|
||||||
|
|
||||||
|
1. Some images can be pulled while others give error:
|
||||||
|
```text
|
||||||
|
Error response from daemon: Get "https://mcr.microsoft.com/v2/": read tcp [2a0c:5a84:e100:e501::a97c]:58039->[2603:1061:f:101::10]:443: wsarecv: An existing connection was forcibly closed by the remote host.
|
||||||
|
```
|
||||||
|
|
||||||
|
Debug with `curl -A github165 -v https://mcr.microsoft.com/v2/powershell/manifests/lts-nanoserver-ltsc2022`
|
||||||
|
|
||||||
|
## Contribute flutter upstream
|
||||||
|
|
||||||
|
1. Remove `WHERE` in bin\\internal\\shared.bat and use instead:
|
||||||
|
```batch
|
||||||
|
pwsh.exe -Command "exit" >nul 2>&1 && (
|
||||||
|
SET powershell_executable=pwsh.exe
|
||||||
|
) || powershell.exe -Command "exit" >nul 2>&1 && (
|
||||||
|
SET powershell_executable=PowerShell.exe
|
||||||
|
) || (
|
||||||
|
ECHO Error: PowerShell executable not found. 1>&2
|
||||||
|
ECHO Either pwsh.exe or PowerShell.exe must be in your PATH. 1>&2
|
||||||
|
EXIT 1
|
||||||
|
)
|
||||||
|
```
|
||||||
|
2. Find if the executable should be pwsh or powershell and put it in a service to remove the hardcoded "powershell" in multiple places, like in:
|
||||||
|
* dev\\devicelab\\lib\\framework\\running\_processes.dart
|
||||||
|
* packages\\flutter\_tools\\lib\\src\\windows\\windows\_version\_validator.dart
|
||||||
|
|
||||||
|
## Steps to reproduce in Docker
|
||||||
|
|
||||||
|
1. Enable Windows Developer Settings to solve error:
|
||||||
|
```powershell
|
||||||
|
# >Building with plugins requires symlink support.
|
||||||
|
# >
|
||||||
|
# >Please enable Developer Mode in your system settings. Run
|
||||||
|
# > start ms-settings:developers
|
||||||
|
# >to open settings.
|
||||||
|
reg add "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock" /t REG_DWORD /f /v "AllowDevelopmentWithoutDevLicense" /d "1"
|
||||||
|
```
|
||||||
|
2. For CI/CD
|
||||||
|
1. Docker version must be pinned in Github workflow to avoid breaking changes: with escaping `\"` syntax inside RUN directive, etc.
|
||||||
|
2. Packaging tool in Windows: [msix](https://pub.dev/packages/msix) . It uses the executables:
|
||||||
|
* [makeappx.exe](https://learn.microsoft.com/en-us/windows/win32/appxpkg/make-appx-package--makeappx-exe-)
|
||||||
|
* [makepri.exe](https://learn.microsoft.com/en-us/windows/uwp/app-resources/makepri-exe-command-options)
|
||||||
|
* [signtool.exe](https://learn.microsoft.com/en-us/dotnet/framework/tools/signtool-exe)
|
||||||
|
* certificate
|
||||||
|
* Make a note that --install-certificate should be "false" or configured because the certificate can't be installed as ContainerUser.
|
||||||
|
```powershell
|
||||||
|
# OK
|
||||||
|
Import-PfxCertificate -FilePath "C:\Users\ContainerUser\AppData\Local\Pub\Cache\hosted\pub.dev\msix-3.16.8\lib\assets\test_certificate.pfx" -Password (ConvertTo-SecureString -AsPlainText -Force "1234") -CertStoreLocation Cert:\LocalMachine\Root
|
||||||
|
# Doesn't work
|
||||||
|
Import-PfxCertificate -FilePath "C:\Users\ContainerUser\AppData\Local\Pub\Cache\hosted\pub.dev\msix-3.16.8\lib\assets\test_certificate.pfx" -Password (ConvertTo-SecureString -AsPlainText -Force "1234")
|
||||||
|
```
|
||||||
|
3. Install msstore CLI <https://github.com/microsoft/msstore-cli> It seems behind StoreBroker but it looks that it's going to be the primary and recommended way to publish to Microsoft Store
|
||||||
|
* According to the [msstore guide](https://learn.microsoft.com/en-us/windows/apps/publish/msstore-dev-cli/commands?pivots=msstoredevcli-installer-linux#installation), It will be needed to install Microsoft.NetCore.Component.Runtime.8.0 with vs\_BuildTools
|
||||||
|
4. From [github.com/tauu/flutter-windows-builder/Dockerfile](https://github.com/tauu/flutter-windows-builder/blob/main/Dockerfile) \=> install [github.com/microsoft/StoreBroker](https://github.com/microsoft/StoreBroker) This is currently the primary tool to publish to Microsoft Store
|
||||||
|
* Not installed right now
|
||||||
|
5. Install the [Windows App Certification Kit](https://learn.microsoft.com/en-us/windows/uwp/debug-test-perf/windows-app-certification-kit) or the [Windows SDK that already includes it](https://developer.microsoft.com/en-us/windows/downloads/windows-sdk/)
|
||||||
|
* Installed currently by one of the workloads in vs\_BuildTools
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
* [How environment variables work on Windows containers?](https://blog.sixeyed.com/windows-weekly-dockerfile-14-environment-variables/)
|
||||||
|
* [Windows deployment in Flutter](https://docs.flutter.dev/deployment/windows)
|
||||||
|
* [vs\_BuildTools workloads](https://learn.microsoft.com/en-us/visualstudio/install/workload-component-id-vs-build-tools?view=vs-2022&preserve-view=true)
|
||||||
|
* Useful Dockerfile <https://git.openprivacy.ca/openprivacy/flutter-desktop/src/branch/main/windows/Dockerfile>
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
schema: spec-driven
|
||||||
|
created: 2026-05-09
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
## Context
|
||||||
|
|
||||||
|
PR #339 has 11 commits over ~12 months and is currently in a state where the Windows CI job either fails the `COPY` step or never produces meaningful signal. The accumulated changes overlap three concerns: (1) fixing the test pipeline, (2) adding a Go-based dockertest harness, (3) renaming `script/test.sh` files. This change keeps only (1). It treats Pester running *inside* the test-target container as the single verification mechanism for the Windows image, mirroring how the Android image uses `container-structure-test`.
|
||||||
|
|
||||||
|
Constraints:
|
||||||
|
|
||||||
|
- `windows-2025` is the only viable runner. There is no Windows-container support in `docker/build-push-action`, no Buildx cache, and the full image build (Flutter clone + VS BuildTools install) takes 30–60 minutes per run.
|
||||||
|
- The `gx`-managed action pinning regime (commit `846ffd6`, spec `actions-version-tracking`) requires every `uses:` to be SHA-pinned with a `# vX.Y.Z` comment. New actions added to `windows.yml` must go through `.github/gx.toml`.
|
||||||
|
- `config/version.json` is the single source of truth for `flutter.version` (spec `flutter-version-update`). The Pester suite must read it, not hardcode.
|
||||||
|
|
||||||
|
## Goals / Non-Goals
|
||||||
|
|
||||||
|
**Goals:**
|
||||||
|
|
||||||
|
- The `test_windows` PR check goes from "in_progress forever / red on COPY" to "green on a healthy image, red on a regression."
|
||||||
|
- The Pester suite has at least one positive assertion on Flutter behavior (version, doctor) rather than only inspecting the on-disk VS package directories.
|
||||||
|
- `test/windows/` contains exactly one form of test (Pester). No dead skeletons.
|
||||||
|
- Everything that PR #339 added but did not finish is either finished or removed; nothing is left in a half-implemented state.
|
||||||
|
|
||||||
|
**Non-Goals:**
|
||||||
|
|
||||||
|
- Publishing the `flutter-windows` image on tag (covered by `p2-release-windows-image`).
|
||||||
|
- Tracking the VS BuildTools / Win11 SDK / CMake versions in `config/version.json` and through Renovate (covered by `p3-windows-version-schema`).
|
||||||
|
- Reducing the Windows CI run time. The job will remain slow; this change accepts that.
|
||||||
|
- Adding Docker Scout vulnerability scanning for the Windows image. The commented-out block is deleted; reintroducing it is left to a separate change if/when Scout becomes valuable for the Windows base image.
|
||||||
|
- Adding the `validate_version` job to `windows.yml`. The same CUE validation runs in `build.yml`'s `validate_version_files` job already; duplicating it on `windows-2025` adds runner cost with no new signal.
|
||||||
|
|
||||||
|
## Decisions
|
||||||
|
|
||||||
|
### Decision: Pester is the only verification harness; the Go/dockertest skeleton is deleted
|
||||||
|
|
||||||
|
The Go module under `test/windows/` (commit `df7666e`) is removed in this change. Reasons:
|
||||||
|
|
||||||
|
- It is not invoked by any CI workflow.
|
||||||
|
- Its only useful assertion (the Pester `Exec` block in `main_test.go:38-49`) is commented out.
|
||||||
|
- It runs the test image as `flutter-docker-image-windows-test:latest` without ever building it, so even uncommented it would fail.
|
||||||
|
- Pester running *inside* the container is the natural fit: the assertions are about the file system and toolchain *of the container*, which is awkward to express through `dockertest.Exec` from a Linux Go process.
|
||||||
|
|
||||||
|
Alternatives considered:
|
||||||
|
|
||||||
|
- **Wire the Go harness into CI.** Rejected: doubles the test infrastructure for no new signal, and the harness would still need a Windows host to run Windows containers — the same `windows-2025` runner constraint.
|
||||||
|
- **Keep the harness as a placeholder.** Rejected: dead code rots; unmaintained `go.mod` will collect `govulncheck` noise from Renovate.
|
||||||
|
|
||||||
|
### Decision: The Flutter version assertion reads `config/version.json` at test time, not via a build arg
|
||||||
|
|
||||||
|
The Pester test computes the expected version by parsing `config/version.json` (already `COPY`'d into the test stage as part of the `flutter` stage's checkout, or freshly copied in the `test` stage). Alternatives:
|
||||||
|
|
||||||
|
- **Hardcode the version in the test.** Rejected: drifts on every Flutter upgrade; defeats the point of `flutter-version-update`.
|
||||||
|
- **Pass via `--build-arg expected_flutter_version` and bake into env.** Rejected: extra plumbing; the `flutter_version` build arg is already the source of truth fed to `git clone --branch`. Reading the manifest directly catches the case where someone passes a build arg that doesn't match the manifest.
|
||||||
|
|
||||||
|
### Decision: `flutter doctor` failure mode
|
||||||
|
|
||||||
|
`flutter doctor` produces lines like `[✓]`, `[!]`, `[✗]` (mapped from `ValidationType.success/partial/missing` in `packages/flutter_tools/lib/src/doctor_validator.dart`). The test applies a per-line rule based on the platform header:
|
||||||
|
|
||||||
|
- **Disabled platforms** (`Android`, `iOS`, `macOS`, `Linux`, `Web`, `Chrome`): skipped entirely. These are explicitly turned off by `flutter config --no-enable-*` so any marker on them is irrelevant.
|
||||||
|
- **Owned-toolchain lines** (`Windows Version`, `Visual Studio - develop Windows apps`): fail unless the marker is `[✓]`. Both `[!]` and `[✗]` fail here. This is intentional: `WindowsVersionValidator` emits `[!]` when the Topaz OFD security module is detected (real build interference), and `VisualStudioValidator` emits `[!]` when VS is too old, needs reboot, has an incomplete install, is not launchable, is missing required components, or is missing the Windows 10 SDK — every one of which is a regression class this image must not ship. Sources: `packages/flutter_tools/lib/src/windows/{windows_version_validator,visual_studio_validator}.dart` in `flutter/flutter`.
|
||||||
|
- **Other lines** (`Flutter`, `Connected device`, `Network resources`, etc.): fail only on `[✗]`. `[!]` here is informational (e.g., no devices connected), expected in a CI container.
|
||||||
|
|
||||||
|
The leaner "fail on `[✗]` only" rule was rejected: it would let a PR that drops `Microsoft.VisualStudio.Workload.VCTools` or `Windows11SDK.22621` from the Dockerfile pass with a `[!] Visual Studio` line, defeating the point of the smoke test.
|
||||||
|
|
||||||
|
### Decision: VS component pattern fix uses `,version=*`
|
||||||
|
|
||||||
|
The on-disk format for VS package directories is `<ComponentId>,version=<X.Y.Z.W>`. The current pattern `,versiona*` is a typo. The pattern `,version=*` is the minimum specific match that distinguishes a real install directory from any other coincident directory. Using `*` alone (no `,version=` anchor) would accept directories like `Microsoft.VisualStudio.Component.VC.CMake.Project_alt,…` which is too loose.
|
||||||
|
|
||||||
|
### Decision: `ENTRYPOINT` and `CMD` in the test stage both target `RunPester.ps1`
|
||||||
|
|
||||||
|
The `test` stage **resets** `ENTRYPOINT` to exec-form `["powershell", "-NoLogo", "-NoProfile", "-File"]` and sets `CMD` to `[".\\script\\RunPester.ps1"]`. This is required because the parent `flutter` stage uses a **shell-form** `ENTRYPOINT "C:\Users\ContainerUser\docker_entrypoint.ps1"` (the analytics-toggle script). Per Docker's documented `ENTRYPOINT`/`CMD` interaction, a shell-form `ENTRYPOINT` runs under PowerShell `-Command` and does **not** append `CMD` args — Docker emits the warning "Shell-form ENTRYPOINT and exec-form CMD may have unexpected results", and `docker run <image> .\script\RunPester.ps1` fails with `hcs::System::CreateProcess … 0x2 file not found` because the workflow's argument is treated as a separate executable.
|
||||||
|
|
||||||
|
With exec-form `ENTRYPOINT` in the test stage:
|
||||||
|
|
||||||
|
- `docker run <test-image>` invokes `powershell -NoLogo -NoProfile -File .\script\RunPester.ps1` (uses `CMD`).
|
||||||
|
- `docker run <test-image> .\test\OtherTest.ps1` swaps in a different script (overrides `CMD`).
|
||||||
|
- The CI workflow runs `docker run --rm <image>` with no explicit script argument; the `CMD` is the source of truth.
|
||||||
|
|
||||||
|
The analytics-toggle entrypoint inherited from the `flutter` stage is intentionally not preserved here — the test image doesn't need runtime analytics control, and the inherited shell-form is the bug source.
|
||||||
|
|
||||||
|
An earlier draft of this proposal kept the workflow's explicit `.\script\RunPester.ps1` arg "as redundant but harmless." That was wrong: it was the failure trigger when combined with the inherited shell-form `ENTRYPOINT`. The arg has been removed from the workflow.
|
||||||
|
|
||||||
|
## Risks / Trade-offs
|
||||||
|
|
||||||
|
- **[Risk] Build duration on `windows-2025` may exceed the GitHub Actions timeout for free-tier runners.** → Mitigation: this repo is not free-tier-constrained (see `release_android` already running multi-job pipelines). The `concurrency` block in `windows.yml` cancels stale runs so a force-push doesn't queue multiple builds. No further mitigation in this change; if duration becomes a blocker, layer caching in a follow-up.
|
||||||
|
- **[Risk] `flutter doctor` output format is not a stable contract; Flutter could change `[✗]` to a different marker.** → Mitigation: the doctor parser is small and lives in the Pester test, so a Flutter upgrade that breaks it produces a single, localized red test rather than silent passes. The `flutter-version-update` spec already requires a passing CI before merge, so any format break is caught at upgrade time, not in production.
|
||||||
|
- **[Trade-off] Removing the Go harness is a one-way door** for any future contributor who wants to add Linux-host-driven dockertest assertions. → Acceptable: such a future contributor can re-add the module deliberately, against a real requirement, instead of the current orphaned skeleton.
|
||||||
|
- **[Trade-off] The `validate_version` and `scout-action` blocks are deleted rather than left commented.** → Acceptable: commented code that references a deleted file (`config/version.cue`) is misleading. Deletion forces the next iteration to think through what they actually need rather than uncomment dead code.
|
||||||
|
|
||||||
|
## Automated Test Strategy
|
||||||
|
|
||||||
|
This change is itself a test infrastructure change. Verification of the change works on two levels:
|
||||||
|
|
||||||
|
- **Self-test (the only level that matters for shipping)**: the `test_windows` job on PR #339 (or its replacement PR) goes green. That single check is the success criterion. It exercises every change in this proposal end-to-end: the `COPY` path is correct (build succeeds), the `versiona*` typo is fixed (VS-component test passes), the manifest-driven version test passes (Flutter version read from `config/version.json` matches what `flutter --version` reports), the doctor smoke test passes, the `CMD` is set (the workflow runs Pester and gets a non-zero exit on failure).
|
||||||
|
- **No new test infrastructure**: Pester is already installed via `script/InstallPester.ps1`; no new tooling is added. The change is a *reduction* in tooling (Go module removed).
|
||||||
|
|
||||||
|
There is no unit-test layer below the Pester suite because the assertions are inherently integration-level — they require the real image to run. Local verification by contributors uses `docker compose run --rm windows-test` (which starts to work as part of this change).
|
||||||
|
|
||||||
|
## Observability
|
||||||
|
|
||||||
|
- **Failure surface**: every assertion is a Pester test. Pester emits per-test pass/fail with file:line in the workflow log. `Invoke-Pester -Configuration @{Output=@{Verbosity='Detailed'}}` (already configured in `script/RunPester.ps1`) shows the failing assertion's expected vs. actual.
|
||||||
|
- **No silent failures possible**: `RunPester.ps1` ends with `Exit $LASTEXITCODE`, so any Pester test failure propagates to a non-zero `docker run` exit, which fails the workflow step. The `set -e`-equivalent for PowerShell (`$ErrorActionPreference = 'Stop'`) is already configured in the test stage's `SHELL` directive.
|
||||||
|
- **Build-stage failures** (e.g., a future bad `COPY` path) surface as standard `docker build` errors with the failing instruction in the workflow log. There is no need for additional logging because the failing layer is named in the error.
|
||||||
|
- **No telemetry sent off-platform**: GitHub Actions logs are the entire observability surface. Maintainers monitor `gh run list --workflow=windows.yml --limit 5` (or the PR check UI).
|
||||||
|
|
||||||
|
## Migration Plan
|
||||||
|
|
||||||
|
1. Land this change on PR #339 (or replace #339 with a fresh PR built off the current `windows` branch).
|
||||||
|
2. Force-push the branch after fixing the `COPY` and pattern, and confirm `test_windows` goes from "in_progress forever" to a green check.
|
||||||
|
3. Delete the Go module files in the same commit as the Dockerfile fix; rerun the workflow to confirm no path now references `test/windows/main*.go`.
|
||||||
|
4. Squash-merge PR #339 with a non-empty body referencing this proposal.
|
||||||
|
5. No rollback is needed because every change is additive to the test surface or is a deletion of unused code; if the new Pester tests are wrong, they fail loudly and a follow-up fix applies — there is no production behavior to revert.
|
||||||
|
|
||||||
|
## Resolved Questions
|
||||||
|
|
||||||
|
- **Doctor `[!]` semantics on Windows-toolchain lines.** *Resolved 2026-05-10:* `[!]` on `Windows Version` and `Visual Studio - develop Windows apps` fails the test, same as `[✗]`. Captured in the "`flutter doctor` failure mode" decision above. Source: `WindowsVersionValidator` and `VisualStudioValidator` in `flutter/flutter`.
|
||||||
|
- **`dart-flutter-telemetry.config` path resolution under `ContainerUser`.** *Resolved 2026-05-10:* The existing assertion path `$env:APPDATA\.dart-tool\dart-flutter-telemetry.config` is correct. `package:unified_analytics` (used by both `flutter` and `dart` CLIs) reads `Platform.environment['AppData']` on Windows and joins `.dart-tool/dart-flutter-telemetry.config`. The Dockerfile runs the disable-analytics commands as `ContainerUser` and the test runs as `ContainerUser`, so `$env:APPDATA` resolves to the same path in both phases. Sources: `pkgs/unified_analytics/lib/src/{utils,initializer,constants}.dart` in `dart-lang/tools`. No change needed.
|
||||||
|
|
||||||
|
## Open Questions
|
||||||
|
|
||||||
|
- Should `windows.yml` upload Pester output as a workflow artifact (e.g., NUnit XML) for easier triage? *Tentatively no for this change* — the inline log is sufficient and adds no maintenance. Reopen if maintainers find themselves repeatedly digging through long Detailed-verbosity logs.
|
||||||
|
- Should there be a smoke test that runs `flutter create` + `flutter build windows` end-to-end in the test stage? *Out of scope here* — the build is already exercised in the `flutter` stage via `flutter create build_app; flutter build windows;` (Dockerfile lines 64, 81). Re-running it inside the `test` stage would multiply build time without new signal. Revisit if a regression slips past the existing build step.
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
## Why
|
||||||
|
|
||||||
|
PR #339 ("ci: test windows image") has been open for ~12 months and still cannot turn green: `windows.Dockerfile` copies `./test/Windows.Tests.ps1` from a path that no longer exists (the file was moved to `./test/windows/` when the dockertest skeleton was added), and the Pester pattern for the `VC.CMake.Project` package has a typo (`,versiona*`) that would never match a real Visual Studio package directory. As a result the `flutter-windows` image has *zero* automated verification on every PR, while the `flutter-android` image runs `container-structure-test` and Docker Scout. This change is what's needed to actually land PR #339 and start producing a meaningful CI signal for the Windows image.
|
||||||
|
|
||||||
|
## What Changes
|
||||||
|
|
||||||
|
- Fix `windows.Dockerfile` `COPY` to source `./test/windows/Windows.Tests.ps1` (the real path).
|
||||||
|
- Fix `test/windows/Windows.Tests.ps1` `BeLikeExactly` pattern: `,versiona*` → `,version=*` to match the pattern actually written by `vs_BuildTools.exe`.
|
||||||
|
- Add a Flutter version assertion that reads `config/version.json` and asserts `flutter --version` inside the container reports the same `flutter.version`. This converts the test job from "image builds" to "image is the version we shipped."
|
||||||
|
- Add a `flutter doctor` smoke assertion that fails the test when doctor reports any error (warnings on platform-specific tooling are tolerated).
|
||||||
|
- Set a default `CMD` in the `test` stage of `windows.Dockerfile` so `docker compose run windows-test` (and equivalent local invocations) actually runs Pester instead of exiting silently. The CI workflow continues to invoke `RunPester.ps1` explicitly.
|
||||||
|
- Remove the dead `test/windows/main.go`, `test/windows/main_test.go`, `test/windows/go.mod`, `test/windows/go.sum`. The `ory/dockertest` harness was scaffolded in commit `df7666e` but never wired into CI, never builds the image it tries to run, and has its only useful assertion (the Pester `Exec`) commented out. Pester running inside the container is the chosen verification mechanism.
|
||||||
|
- Either delete or wire up the two commented-out blocks in `.github/workflows/windows.yml`: the `docker/scout-action` step and the `validate_version` job (which still references the deleted `config/version.cue`). This change deletes them because Scout/version-validation parity is out of scope; the follow-up changes (`p2`, `p3`) reintroduce them deliberately.
|
||||||
|
- Set a non-empty body on PR #339 describing the test surface.
|
||||||
|
|
||||||
|
## Capabilities
|
||||||
|
|
||||||
|
### New Capabilities
|
||||||
|
|
||||||
|
- `windows-image-testing`: defines what `.github/workflows/windows.yml` and `test/windows/Windows.Tests.ps1` are required to verify about the `flutter-windows` Docker image on every pull request — Flutter version match, doctor health, presence of pinned Visual Studio components, and analytics-disabled telemetry config.
|
||||||
|
|
||||||
|
### Modified Capabilities
|
||||||
|
|
||||||
|
_None._ The Windows image previously had no spec; the existing `flutter-version-update` and `actions-version-tracking` specs are not touched.
|
||||||
|
|
||||||
|
## Impact
|
||||||
|
|
||||||
|
- Affected files: `windows.Dockerfile`, `test/windows/Windows.Tests.ps1`, `.github/workflows/windows.yml`, `script/RunPester.ps1` (no change expected, but inputs change), `docker-compose.yml` (windows-test service still works).
|
||||||
|
- Removed files: `test/windows/main.go`, `test/windows/main_test.go`, `test/windows/go.mod`, `test/windows/go.sum`.
|
||||||
|
- No release/publish behavior changes — `release.yml` is untouched. Distribution of the Windows image is the explicit subject of `p2-release-windows-image`.
|
||||||
|
- No version manifest changes — `config/schema.cue` is untouched. Tracking VS BuildTools / Win11 SDK / CMake versions in `config/version.json` is the explicit subject of `p3-windows-version-schema`.
|
||||||
|
- Risk: the only CI signal here is a slow (`windows-2025`, multi-hour) Windows container build. This change does not address build duration; a green check is the success criterion, not a fast green check.
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
## ADDED Requirements
|
||||||
|
|
||||||
|
### Requirement: Pull request CI verifies the Windows image on every PR
|
||||||
|
|
||||||
|
The `.github/workflows/windows.yml` workflow SHALL run on every `pull_request` event, build `windows.Dockerfile` with `--target test`, and run the Pester suite at `test/windows/Windows.Tests.ps1` inside that image. The workflow SHALL fail the PR check if the image build fails, if any Pester test fails, or if Pester exits non-zero.
|
||||||
|
|
||||||
|
The experience context is the maintainer reviewing a PR that touches `windows.Dockerfile`, `script/InstallPester.ps1`, `script/RunPester.ps1`, or `test/windows/**` — they get a single red/green check rather than having to build the multi-hour Windows image locally.
|
||||||
|
|
||||||
|
#### Scenario: PR check is green when the image is healthy
|
||||||
|
|
||||||
|
- **GIVEN** a PR whose `windows.Dockerfile` builds successfully on `windows-2025`
|
||||||
|
- **AND** every Pester test in `test/windows/Windows.Tests.ps1` passes inside the resulting `test`-target image
|
||||||
|
- **WHEN** the `test_windows` job runs
|
||||||
|
- **THEN** the job exits 0
|
||||||
|
- **AND** the `test_windows` check on the PR is reported as success
|
||||||
|
|
||||||
|
#### Scenario: PR check is red when a Pester test fails
|
||||||
|
|
||||||
|
- **GIVEN** a PR whose `test`-target image builds successfully
|
||||||
|
- **AND** at least one Pester test fails (e.g., the Flutter version inside the image does not match `config/version.json`)
|
||||||
|
- **WHEN** `script/RunPester.ps1` runs
|
||||||
|
- **THEN** the script exits non-zero (it propagates `$LASTEXITCODE` from `Invoke-Pester`)
|
||||||
|
- **AND** the `test_windows` job is reported as failed on the PR
|
||||||
|
|
||||||
|
#### Scenario: PR check is red when the Dockerfile cannot be built
|
||||||
|
|
||||||
|
- **GIVEN** a PR that breaks `windows.Dockerfile` (for example, by referencing a `COPY` source path that does not exist)
|
||||||
|
- **WHEN** the `test_windows` job runs `docker build ... --target test`
|
||||||
|
- **THEN** the build exits non-zero
|
||||||
|
- **AND** the `test_windows` job is reported as failed on the PR
|
||||||
|
|
||||||
|
### Requirement: Tests assert the Flutter version inside the image matches `config/version.json`
|
||||||
|
|
||||||
|
The Pester suite SHALL include a test that runs `flutter --version` inside the running container and asserts the reported semver equals `flutter.version` from `config/version.json` at the commit being tested. The version SHALL be read from the manifest, not hardcoded in the test file.
|
||||||
|
|
||||||
|
The experience context is the CI engineer pulling `flutter-windows:<tag>` and expecting the in-container Flutter to match the tag — a silent drift between manifest and image is the failure mode this requirement prevents.
|
||||||
|
|
||||||
|
#### Scenario: Manifest and image agree
|
||||||
|
|
||||||
|
- **GIVEN** `config/version.json` declares `flutter.version == "X.Y.Z"`
|
||||||
|
- **AND** the image was built with `--build-arg flutter_version=X.Y.Z`
|
||||||
|
- **WHEN** the Flutter version Pester test runs
|
||||||
|
- **THEN** `flutter --version` inside the container reports `Flutter X.Y.Z`
|
||||||
|
- **AND** the test passes
|
||||||
|
|
||||||
|
#### Scenario: Manifest and image disagree
|
||||||
|
|
||||||
|
- **GIVEN** `config/version.json` declares `flutter.version == "X.Y.Z"`
|
||||||
|
- **AND** the image was built with `--build-arg flutter_version=X.Y.W` (any other version)
|
||||||
|
- **WHEN** the Flutter version Pester test runs
|
||||||
|
- **THEN** the test fails with a message naming both versions
|
||||||
|
|
||||||
|
### Requirement: Tests assert `flutter doctor` reports no errors
|
||||||
|
|
||||||
|
The Pester suite SHALL include a test that runs `flutter doctor` inside the container and fails when doctor reports any line classified as an error. Warnings on platform-specific tooling that is intentionally not installed (e.g., Android, iOS, macOS, Linux desktop, Chrome) SHALL NOT fail the test.
|
||||||
|
|
||||||
|
The experience context is the developer who runs `docker run flutter-windows flutter doctor` after pulling the image and expects a clean report for the Windows desktop toolchain — Pester catches regressions before the image is published.
|
||||||
|
|
||||||
|
#### Scenario: Doctor reports a clean Windows toolchain
|
||||||
|
|
||||||
|
- **GIVEN** the image was built successfully with VS BuildTools (CMake, Win11SDK, VCTools workload) installed
|
||||||
|
- **WHEN** the doctor Pester test runs
|
||||||
|
- **THEN** `flutter doctor` reports `[✓] Windows Version` and `[✓] Visual Studio - develop Windows apps`
|
||||||
|
- **AND** the test passes
|
||||||
|
|
||||||
|
#### Scenario: Doctor reports a Windows-toolchain error
|
||||||
|
|
||||||
|
- **GIVEN** a PR that removes the `Microsoft.VisualStudio.Workload.VCTools` line from `windows.Dockerfile`
|
||||||
|
- **AND** the image still builds (the workload removal does not break the build itself)
|
||||||
|
- **WHEN** the doctor Pester test runs
|
||||||
|
- **THEN** `flutter doctor` reports `[✗] Visual Studio` (or equivalent error marker)
|
||||||
|
- **AND** the test fails
|
||||||
|
|
||||||
|
### Requirement: Tests assert presence of pinned Visual Studio components
|
||||||
|
|
||||||
|
The Pester suite SHALL assert that the directories at `$env:ProgramData\Microsoft\VisualStudio\Packages\` contain entries matching the components installed by `windows.Dockerfile`: `Microsoft.VisualStudio.Component.VC.CMake.Project`, `Microsoft.VisualStudio.Component.Windows11SDK.22621`, and `Microsoft.VisualStudio.Workload.VCTools`. The match pattern SHALL accept any installed `version=...` suffix.
|
||||||
|
|
||||||
|
The experience context is detecting silent removal or rename of a VS component in the Dockerfile — the package directory is the on-disk evidence that the component installed.
|
||||||
|
|
||||||
|
#### Scenario: All three components match
|
||||||
|
|
||||||
|
- **GIVEN** the image was built from the current `windows.Dockerfile`
|
||||||
|
- **WHEN** the VS-component Pester tests run
|
||||||
|
- **THEN** each of `VC.CMake.Project`, `Windows11SDK.22621`, and `Workload.VCTools` matches `*,version=*`
|
||||||
|
- **AND** all three tests pass
|
||||||
|
|
||||||
|
#### Scenario: Pattern correctly accepts the on-disk format
|
||||||
|
|
||||||
|
- **GIVEN** a real package directory `Microsoft.VisualStudio.Component.VC.CMake.Project,version=17.13.35919.96`
|
||||||
|
- **WHEN** the `BeLikeExactly` assertion runs against pattern `Microsoft.VisualStudio.Component.VC.CMake.Project,version=*`
|
||||||
|
- **THEN** the assertion passes
|
||||||
|
|
||||||
|
### Requirement: Tests assert Flutter and Dart telemetry are disabled
|
||||||
|
|
||||||
|
The Pester suite SHALL assert that the dart-flutter telemetry config at `$env:APPDATA\.dart-tool\dart-flutter-telemetry.config` exists and contains `reporting=0`.
|
||||||
|
|
||||||
|
The experience context is the privacy-conscious user pulling the image and expecting analytics to be off by default — the test prevents a Dockerfile change from silently re-enabling telemetry.
|
||||||
|
|
||||||
|
#### Scenario: Telemetry is disabled
|
||||||
|
|
||||||
|
- **GIVEN** the image was built with `flutter config --no-analytics; dart --disable-analytics;` as currently in `windows.Dockerfile`
|
||||||
|
- **WHEN** the telemetry Pester test runs
|
||||||
|
- **THEN** `dart-flutter-telemetry.config` contains `reporting=0`
|
||||||
|
- **AND** the test passes
|
||||||
|
|
||||||
|
### Requirement: The `test` Dockerfile stage is self-running by default
|
||||||
|
|
||||||
|
The `test` stage of `windows.Dockerfile` SHALL declare a `CMD` (or equivalent) that invokes `script/RunPester.ps1`, so that `docker run <test-image>` (and `docker compose run windows-test`) executes the Pester suite without requiring the caller to pass a command.
|
||||||
|
|
||||||
|
The experience context is the contributor who runs the test image locally — they should not need to know the exact PowerShell incantation to invoke Pester.
|
||||||
|
|
||||||
|
#### Scenario: Local invocation runs the suite
|
||||||
|
|
||||||
|
- **GIVEN** a test image built with `docker compose build windows-test`
|
||||||
|
- **WHEN** the contributor runs `docker compose run --rm windows-test`
|
||||||
|
- **THEN** Pester executes against `.\test`
|
||||||
|
- **AND** the container exits with the Pester exit code
|
||||||
|
|
||||||
|
### Requirement: No dead Go/dockertest harness in `test/windows/`
|
||||||
|
|
||||||
|
The repository SHALL NOT contain a Go module under `test/windows/` unless that module is invoked by at least one CI job. The `ory/dockertest` skeleton (`main.go`, `main_test.go`, `go.mod`, `go.sum`) introduced in commit `df7666e` SHALL be removed because Pester running inside the container is the chosen verification mechanism.
|
||||||
|
|
||||||
|
The experience context is the contributor reading `test/windows/` and trying to determine which file is the source of truth — a dead harness alongside live Pester tests is a confusion hazard.
|
||||||
|
|
||||||
|
#### Scenario: Repository contains no orphan Go test files for Windows
|
||||||
|
|
||||||
|
- **WHEN** a contributor lists `test/windows/`
|
||||||
|
- **THEN** the listing contains `Windows.Tests.ps1` (and any newly added Pester files)
|
||||||
|
- **AND** the listing does not contain `main.go`, `main_test.go`, `go.mod`, or `go.sum`
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
## 1. Fix the broken Dockerfile copy and Pester typo
|
||||||
|
|
||||||
|
- [x] 1.1 In `windows.Dockerfile`, change the `COPY ./test/Windows.Tests.ps1` line to source `./test/windows/Windows.Tests.ps1`; keep the destination `.\test\Windows.Tests.ps1`.
|
||||||
|
- [x] 1.2 In `test/windows/Windows.Tests.ps1`, change the CMake assertion pattern from `,versiona*` to `,version=*`. Apply the same `,version=*` form to the Win11SDK and VCTools assertions for consistency.
|
||||||
|
|
||||||
|
## 2. Make the test stage self-running
|
||||||
|
|
||||||
|
- [x] 2.1 In `windows.Dockerfile`, replace the trailing `# CMD Invoke-Pester ...` comment in the `test` stage with `CMD ["powershell", "-NoLogo", "-NoProfile", "-File", ".\\script\\RunPester.ps1"]` (or equivalent that invokes `RunPester.ps1`).
|
||||||
|
- [x] 2.2 Verify locally that `docker compose run --rm windows-test` runs Pester and exits with the Pester exit code. (Skip if no Windows host available; rely on the CI run for confirmation.)
|
||||||
|
|
||||||
|
## 3. Add the Flutter version Pester test
|
||||||
|
|
||||||
|
- [x] 3.1 In `windows.Dockerfile`'s `test` stage, add `COPY ./config/version.json .\config\version.json` so the manifest is available at test time.
|
||||||
|
- [x] 3.2 In `test/windows/Windows.Tests.ps1`, add a new `Describe "Flutter version"` block with a test that:
|
||||||
|
- reads `config\version.json` via `Get-Content | ConvertFrom-Json`;
|
||||||
|
- extracts `flutter.version`;
|
||||||
|
- runs `flutter --version` and parses the first line into a semver string;
|
||||||
|
- asserts the parsed version equals the manifest version, with a failure message naming both values.
|
||||||
|
|
||||||
|
## 4. Add the `flutter doctor` smoke test
|
||||||
|
|
||||||
|
- [x] 4.1 In `test/windows/Windows.Tests.ps1`, add a `Describe "Flutter doctor"` block that runs `flutter doctor` and captures stdout. At the top of `script/RunPester.ps1` (or in a `BeforeAll` for this Describe), force `[Console]::OutputEncoding = [System.Text.Encoding]::UTF8` so the `[✓]`/`[!]`/`[✗]` glyphs survive PowerShell's default OEM encoding on `windows-2025`.
|
||||||
|
- [x] 4.2 Implement a parser that classifies each line by its platform header and applies the per-line rule from the `flutter doctor` failure-mode decision in `design.md`:
|
||||||
|
- **Skip** lines whose header is one of `Android`, `iOS`, `macOS`, `Linux`, `Web`, `Chrome` (intentionally disabled via `flutter config --no-enable-*`).
|
||||||
|
- **Fail unless `[✓]`** for headers that start with `Windows Version` or `Visual Studio` (any `[!]` or `[✗]` here is a real toolchain regression — see the Flutter validator sources cited in `design.md`).
|
||||||
|
- **Fail only on `[✗]`** for any other header (e.g., `Flutter`, `Connected device`, `Network resources`); `[!]` on these is informational in a CI container.
|
||||||
|
- To survive encoding edge cases, match markers by character class (e.g., `^\[(✓|✔|!|✗|✘|x|X)\]`) rather than literal codepoints, mapping `✓/✔` → pass, `!` → partial, `✗/✘/x/X` → fail.
|
||||||
|
- [x] 4.3 The test passes when at least the `Windows Version` and `Visual Studio - develop Windows apps` lines are tagged `[✓]` and no other non-skipped line is tagged `[✗]`.
|
||||||
|
|
||||||
|
## 5. Delete the dead Go/dockertest harness
|
||||||
|
|
||||||
|
- [x] 5.1 Delete `test/windows/main.go`, `test/windows/main_test.go`, `test/windows/go.mod`, `test/windows/go.sum`.
|
||||||
|
- [x] 5.2 Confirm that no workflow under `.github/workflows/` still references Go or `dockertest` (`grep -r "dockertest\|go test\|go mod" .github/workflows/`).
|
||||||
|
|
||||||
|
## 6. Clean up commented-out workflow blocks
|
||||||
|
|
||||||
|
- [x] 6.1 In `.github/workflows/windows.yml`, delete the commented-out `Scan with Docker Scout` step block.
|
||||||
|
- [x] 6.2 In `.github/workflows/windows.yml`, delete the commented-out `Push to Docker Hub` step block (release path is the subject of `p2-release-windows-image`).
|
||||||
|
- [x] 6.3 In `.github/workflows/windows.yml`, delete the commented-out `validate_version` job block (it references the deleted `config/version.cue`).
|
||||||
|
|
||||||
|
## 7. Verify and ship
|
||||||
|
|
||||||
|
- [x] 7.1 Push the branch; wait for the `test_windows` job in `.github/workflows/windows.yml` to complete on `windows-2025`.
|
||||||
|
- [ ] 7.2 Confirm the job exits 0 with all Pester tests reporting `Passed`.
|
||||||
|
- [x] 7.3 Update PR #339 (or open a replacement) with a non-empty body referencing this proposal: link to `openspec/changes/p1-fix-windows-ci-tests/proposal.md` and list the assertions now enforced.
|
||||||
|
- [ ] 7.4 Merge.
|
||||||
|
|
||||||
|
## 8. Archive
|
||||||
|
|
||||||
|
- [ ] 8.1 After merge, archive this change by running the `openspec-archive-change` flow so the `windows-image-testing` spec is promoted to `openspec/specs/windows-image-testing/spec.md`.
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
schema: spec-driven
|
||||||
|
created: 2026-05-09
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
## Context
|
||||||
|
|
||||||
|
`release.yml` is the publishing workflow for tagged releases. It currently has five jobs, all Android-scoped: `release_android`, `update_description`, `record_image`, `set_bootstrap_image`, `create_github_release`. The Windows Dockerfile and the `windows.yml` PR-test workflow exist but are not connected to release. PR #339's `windows.yml` even has a commented-out `Push to Docker Hub` step (lines 84-88) hinting at this gap.
|
||||||
|
|
||||||
|
Constraints:
|
||||||
|
|
||||||
|
- Docker Buildx and `docker/build-push-action` cache features do not work for Windows containers (already noted in `windows.yml`'s comment "Docker Buildx is not supported for Windows containers"). The release job must use `docker build` + `docker push` directly, like `windows.yml` does.
|
||||||
|
- The `gx`-managed pinning regime (spec `actions-version-tracking`) requires every new `uses:` to be SHA-pinned via `.github/gx.toml`. This change reuses actions already pinned by `release_android` (`actions/checkout`, `docker/metadata-action`, `docker/login-action`, `actions/github-script`), so no `gx.toml` edit is needed.
|
||||||
|
- The three-registry fan-out (Docker Hub + GHCR + Quay) is not negotiable — it's the pre-existing distribution promise from `release_android`.
|
||||||
|
- `windows-2025` runner cost: each release run adds 30–60 minutes of `windows-2025` minutes. The repository already pays for this in the PR-test workflow per `p1`, so this is incremental cost on tag pushes only.
|
||||||
|
|
||||||
|
## Goals / Non-Goals
|
||||||
|
|
||||||
|
**Goals:**
|
||||||
|
|
||||||
|
- A single tag push publishes both `flutter-android:X.Y.Z` and `flutter-windows:X.Y.Z` to Docker Hub, GHCR, and Quay.
|
||||||
|
- The Windows release path is *operationally identical* to the Android release path from a user's perspective: same registries, same tag scheme, same OCI labels.
|
||||||
|
- Failure isolation between architectures: Windows runner flake does not block Android publishing.
|
||||||
|
- `workflow_dispatch` continues to allow manual re-runs without re-tagging.
|
||||||
|
|
||||||
|
**Non-Goals:**
|
||||||
|
|
||||||
|
- Updating Docker Hub description for the Windows image (`peter-evans/dockerhub-description`). The current `update_description` job is Android-scoped against `readme.md`. Adding a Windows variant requires a separate readme and a separate Docker Hub repo description; out of scope.
|
||||||
|
- Recording Windows image vulnerabilities in Docker Scout (`record_image` job). Scout's Windows-base-image coverage is limited and the existing `windows.yml` already commented out the Scout block (line 65 onwards) for that reason. If/when Scout becomes useful for Windows, add it in a follow-up.
|
||||||
|
- Updating the `FLUTTER_VERSION` repo variable from a Windows job (`set_bootstrap_image`). That variable bootstraps `test_gradle` against the Android image; Windows has no analogous bootstrap need today.
|
||||||
|
- Including the Windows image in the GitHub Release notes generated by `create_github_release`. The release notes are tag-scoped, not image-scoped, so they continue to apply.
|
||||||
|
- Multi-arch manifests (`linux/amd64` + `windows/amd64` under one tag). Windows containers cannot share a manifest list with Linux containers in practice; users pull the right image for their host. Status quo.
|
||||||
|
|
||||||
|
## Decisions
|
||||||
|
|
||||||
|
### Decision: Add a single `release_windows` job, not a matrix on `release_android`
|
||||||
|
|
||||||
|
A `strategy.matrix` over `[android, windows]` would conflict on `runs-on` (`ubuntu-24.04` vs `windows-2025`), `dockerfile` (`android.Dockerfile` vs `windows.Dockerfile`), `target` (`android` vs `flutter`), and the `clean-runner-disk` step (Linux-only). The result would be a matrix mostly composed of conditionals — less readable than two parallel jobs sharing the metadata pattern. Two-job design wins for clarity.
|
||||||
|
|
||||||
|
Alternatives considered:
|
||||||
|
|
||||||
|
- **Reusable workflow** with the registry login + metadata + push pattern factored out. Rejected: only two callers (Android and Windows), and the steps differ enough (Buildx vs. plain docker build) that the reusable surface would have ~5 inputs to model two callers. Not worth it.
|
||||||
|
|
||||||
|
### Decision: No Buildx on the Windows job; use plain `docker build` + per-registry `docker push`
|
||||||
|
|
||||||
|
`docker/build-push-action` does support Windows containers in some configurations, but the `cache-from: type=gha` / `cache-to: type=gha,mode=max` pattern used by `release_android` is Linux-only. Trying to use the action without GHA cache offers no benefit over plain `docker build`. The Windows job builds with `docker build` and pushes to each registry with three explicit `docker push` calls (or one `docker push --all-tags`).
|
||||||
|
|
||||||
|
Tags are produced by `docker/metadata-action` exactly as in the Android job; the script then `docker tag`s the local build to each registry-prefixed name and pushes.
|
||||||
|
|
||||||
|
### Decision: Three registry logins, all reusing existing secrets
|
||||||
|
|
||||||
|
The job runs `docker/login-action` three times (Docker Hub, GHCR, Quay) using the same secrets as `release_android`: `DOCKER_HUB_USERNAME` / `DOCKER_HUB_TOKEN` for Docker Hub, `github.actor` / `github.token` for GHCR, `QUAY_USERNAME` / `QUAY_ROBOT_TOKEN` for Quay. No new secret rotation is needed.
|
||||||
|
|
||||||
|
### Decision: No `needs:` dependency between `release_android` and `release_windows`
|
||||||
|
|
||||||
|
Independent jobs run in parallel. If Windows build fails, Android still publishes. The workflow run as a whole reports failure, but the Android image is live. This matches how multi-arch open-source distributions usually behave: don't hold back one platform on another's flake.
|
||||||
|
|
||||||
|
Alternative considered:
|
||||||
|
|
||||||
|
- **`release_windows: needs: release_android`** so Android validates first. Rejected: if Android fails, the tag is half-cut anyway and Windows would only matter as a postmortem; in the common case (both pass) it just adds 30–60 minutes to the wall-clock of the Windows publish.
|
||||||
|
|
||||||
|
### Decision: Image build target is `flutter`, not a new release-only target
|
||||||
|
|
||||||
|
`windows.Dockerfile` already has the `flutter` stage as its production stage and `test` as the test stage. The release job builds `--target flutter`. `windows.yml` (PR test) builds `--target test`. This split is correct and matches `android.Dockerfile`.
|
||||||
|
|
||||||
|
## Risks / Trade-offs
|
||||||
|
|
||||||
|
- **[Risk] Windows runner is flaky and tags ship with no Windows image.** → Mitigation: `workflow_dispatch` re-run lets a maintainer recover without re-tagging. The release notes are tag-scoped (Android already publishes), so a delayed Windows publish is observable but not a failed release.
|
||||||
|
- **[Risk] Quay or GHCR push fails after Docker Hub push succeeds.** → Mitigation: `docker push` per-registry is idempotent; a re-run of the `release_windows` job pushes any missing tags. Document that the job is safe to re-run.
|
||||||
|
- **[Risk] `windows-2025` minutes cost grows by 30–60 min per tag.** → Acceptable: tag cadence is roughly monthly (`flutter-version-update` PRs land on stable bumps). Annualized cost is bounded.
|
||||||
|
- **[Trade-off] No Docker Hub description update for the Windows image.** → Acceptable: users discover the Windows variant via the GitHub README, which already documents both. A separate Docker Hub repo (`flutter-windows`) will appear bare for now.
|
||||||
|
- **[Trade-off] No Scout scan / SARIF upload for Windows.** → Acceptable: the Scout coverage gap on Windows base images is well-known. Code-scanning dashboard remains Android-only until Scout matures.
|
||||||
|
|
||||||
|
## Automated Test Strategy
|
||||||
|
|
||||||
|
- **Pre-merge verification (the only level that matters here):** the `release.yml` workflow itself does not run on `pull_request`; it runs on `push: tags: *` and `workflow_dispatch`. Therefore this change cannot be verified by a normal PR check. The verification path is:
|
||||||
|
1. Land the PR with the new `release_windows` job.
|
||||||
|
2. Use `workflow_dispatch` against an existing tag (e.g., the most recent stable Flutter tag) to trigger a one-shot run before the next stable bump.
|
||||||
|
3. Confirm the three pushed images exist via `docker manifest inspect`.
|
||||||
|
- **Post-merge ongoing verification:** every monthly tag exercises the path. The `flutter-version-update` PR pipeline (spec: `flutter-version-update`) already gates that the Android image is healthy before tagging; once `p1-fix-windows-ci-tests` is in, the Windows image is also gated by its `test_windows` PR check on the upgrade PR. So the release-time signal is "PR was green and merged → tag was cut → release builds against a verified image."
|
||||||
|
- **No new test infrastructure**: the change is GitHub Actions YAML. The `metadata-action` and `login-action` are battle-tested in `release_android`.
|
||||||
|
|
||||||
|
## Observability
|
||||||
|
|
||||||
|
- **Failure surface**: the `release_windows` job appears as its own check on the workflow run. Failures show in the GitHub Actions UI exactly like `release_android` failures do today.
|
||||||
|
- **Per-registry push errors**: `docker push` errors are emitted to stdout by the Docker daemon and end up in the workflow log under the push step. No additional logging is needed.
|
||||||
|
- **Image digest visible after push**: each `docker push` prints the resulting digest. Copying that digest into the run summary is a nice-to-have but not required; `docker manifest inspect` from any host gives the same answer post-hoc.
|
||||||
|
- **No silent failures possible**: each `docker push` is its own step (or sub-command) and propagates its exit code. The job step fails on any non-zero exit.
|
||||||
|
- **Maintainer dashboard**: `gh run list --workflow=release.yml --limit 5` shows the most recent releases with per-job status; this is the existing observability surface and continues to work.
|
||||||
|
|
||||||
|
## Migration Plan
|
||||||
|
|
||||||
|
1. Land `p1-fix-windows-ci-tests` first so the Windows image is actually verified per-PR.
|
||||||
|
2. Open a PR adding the `release_windows` job to `release.yml`. The PR's `pull_request` checks do not exercise `release.yml` (it runs on `push: tags`); land it on the strength of YAML review + pinned-action diff.
|
||||||
|
3. After merge, manually run `release.yml` via `workflow_dispatch` against the most recent stable tag to validate the new job end-to-end *before* the next monthly upgrade PR cycle. If something is wrong, hotfix in a follow-up PR rather than waiting for a real release.
|
||||||
|
4. The next `flutter-version-update` PR that lands and is tagged exercises the path automatically.
|
||||||
|
5. Rollback strategy: if a regression appears (e.g., Windows registry credentials are wrong), revert the PR. Tags already pushed continue to publish only Android until the revert is itself reverted.
|
||||||
|
|
||||||
|
## Open Questions
|
||||||
|
|
||||||
|
- Should `release_windows` `needs: release_android` after all, to gate Windows publication on Android publication and reduce the chance of a half-released tag visible to users? *Tentative answer: no* (per the parallel-jobs decision above). Reopen if maintainers see frequent partial releases in practice.
|
||||||
|
- Should the `update_description` job on Docker Hub gain a parallel `update_description_windows` step pointing at a `readme-windows.md`? *Out of scope here* — split if/when there's a Windows-specific readme worth maintaining.
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
## Why
|
||||||
|
|
||||||
|
`release.yml` only builds and publishes the `flutter-android` image on tag. The Windows Dockerfile, the `windows-2025` test workflow, and the `IMAGE_REPOSITORY_NAME: flutter-windows` env var in `windows.yml` all imply a `flutter-windows` image is shipped — but no release path actually pushes it. Once `p1-fix-windows-ci-tests` lands and CI verifies the image, users still cannot `docker pull <org>/flutter-windows:<flutter-version>`. This change adds a `release_windows` job to `release.yml` that mirrors `release_android` for the Windows artifact, so cutting a tag publishes both images.
|
||||||
|
|
||||||
|
## What Changes
|
||||||
|
|
||||||
|
- Add a new `release_windows` job to `.github/workflows/release.yml` that runs on `windows-2025`, builds `windows.Dockerfile` with `--target flutter`, and pushes the resulting image to Docker Hub, GitHub Container Registry, and Quay.io with the `<flutter-version>` tag (matching the existing Android tagging convention).
|
||||||
|
- Reuse `script/setEnvironmentVariables.js` and `docker/metadata-action` exactly as `release_android` does, so the tag/label conventions stay identical across architectures.
|
||||||
|
- Login steps reuse the existing `DOCKER_HUB_*`, `QUAY_*`, and `GHCR` credentials. No new secrets are introduced.
|
||||||
|
- The new job runs in parallel with `release_android` (no `needs:` dependency between them) so a Windows build failure does not block Android publishing and vice versa.
|
||||||
|
- The downstream `update_description`, `record_image`, `set_bootstrap_image`, and `create_github_release` jobs that currently `needs: release_android` are NOT changed: they remain Android-scoped because the Docker Hub description, Scout environment, bootstrap-image variable, and changelog all currently reference Android only. Generalizing them is out of scope.
|
||||||
|
- The `test_windows` PR check from `p1` SHALL be a required check before tags can be cut, but the actual gating is repository-settings-only and not in this PR's diff.
|
||||||
|
|
||||||
|
## Capabilities
|
||||||
|
|
||||||
|
### New Capabilities
|
||||||
|
|
||||||
|
- `windows-image-release`: defines what `release.yml` must do on a tag push so that a `flutter-windows:<flutter-version>` image is published to the same set of registries as the Android image.
|
||||||
|
|
||||||
|
### Modified Capabilities
|
||||||
|
|
||||||
|
_None._ The existing `flutter-version-update` and `actions-version-tracking` specs are unaffected. `release_android` is unchanged.
|
||||||
|
|
||||||
|
## Impact
|
||||||
|
|
||||||
|
- Affected files: `.github/workflows/release.yml` (one new job added), `.github/gx.toml` and `.github/gx.lock` (new entries if any new actions are introduced — none expected; the new job uses actions already pinned via the Android job).
|
||||||
|
- Depends on: `p1-fix-windows-ci-tests` landed (so the image is verified before publishing), but does not depend on `p3-windows-version-schema` (versioning of the Windows artifact follows the existing `flutter.version` convention).
|
||||||
|
- Operational impact: every tag push triggers an additional `windows-2025` run, which currently takes 30–60 minutes. Tag-push to first-Windows-image-published wall-clock time grows by that amount.
|
||||||
|
- Cost: `windows-2025` runner minutes are billed; the budget impact should be reviewed before this lands.
|
||||||
|
- Risk: a flaky Windows build will block release tags. Mitigation is captured in the design (manual workflow_dispatch fallback already exists in `release.yml` and continues to work for Android).
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
## ADDED Requirements
|
||||||
|
|
||||||
|
### Requirement: Tag push publishes a `flutter-windows` image to all release registries
|
||||||
|
|
||||||
|
When a tag matching `*` is pushed to the repository, the `release_windows` job in `.github/workflows/release.yml` SHALL build `windows.Dockerfile` with `--target flutter` and `--build-arg flutter_version=<tag>`, and SHALL push the resulting image to Docker Hub, GitHub Container Registry, and Quay.io under the repository name `flutter-windows` with the tag equal to the Flutter version.
|
||||||
|
|
||||||
|
The experience context is the CI engineer who, on the day a new Flutter stable lands, expects to run `docker pull docker.io/<org>/flutter-windows:<version>` and find the image at the same tag they already use for `flutter-android`.
|
||||||
|
|
||||||
|
#### Scenario: Tag push fans out to all three registries
|
||||||
|
|
||||||
|
- **GIVEN** a tag `X.Y.Z` is pushed to the repository
|
||||||
|
- **WHEN** the `release_windows` job completes successfully
|
||||||
|
- **THEN** `docker.io/<org>/flutter-windows:X.Y.Z` exists
|
||||||
|
- **AND** `ghcr.io/<org>/flutter-windows:X.Y.Z` exists
|
||||||
|
- **AND** `quay.io/<org>/flutter-windows:X.Y.Z` exists
|
||||||
|
|
||||||
|
#### Scenario: Tag-image consistency
|
||||||
|
|
||||||
|
- **WHEN** any of the three published `flutter-windows:X.Y.Z` images is pulled and `flutter --version` is invoked inside it
|
||||||
|
- **THEN** the reported Flutter version is exactly `X.Y.Z`
|
||||||
|
|
||||||
|
### Requirement: Windows release runs in parallel with Android release
|
||||||
|
|
||||||
|
The `release_windows` job SHALL NOT declare a `needs:` dependency on `release_android`, and `release_android` SHALL NOT declare a `needs:` dependency on `release_windows`. A failure in one SHALL NOT cancel the other.
|
||||||
|
|
||||||
|
The experience context is the maintainer cutting a release: they accept that one architecture may publish while the other fails, and prefer fixing the failed one in a follow-up tag rather than blocking both.
|
||||||
|
|
||||||
|
#### Scenario: Android publishes when Windows build fails
|
||||||
|
|
||||||
|
- **GIVEN** a tag is pushed
|
||||||
|
- **AND** the `release_windows` job fails (e.g., transient `windows-2025` runner issue)
|
||||||
|
- **AND** the `release_android` job succeeds
|
||||||
|
- **WHEN** the workflow run completes
|
||||||
|
- **THEN** Android images are published at all three registries
|
||||||
|
- **AND** the workflow run is reported as failed (because at least one job failed)
|
||||||
|
- **AND** the failure surface is the `release_windows` job specifically, not `release_android`
|
||||||
|
|
||||||
|
### Requirement: Windows release uses the same metadata conventions as Android release
|
||||||
|
|
||||||
|
The `release_windows` job SHALL use `docker/metadata-action` with the `images` input set to the same three registry namespaces and the `tags` input set to `type=raw,value=${{ env.FLUTTER_VERSION }}`, mirroring the Android job. The image labels (`org.opencontainers.image.*`) produced by `metadata-action` SHALL be applied to the built image via `docker/build-push-action`'s `labels` input.
|
||||||
|
|
||||||
|
The experience context is the operator inspecting `docker inspect <org>/flutter-windows:X.Y.Z` and `docker inspect <org>/flutter-android:X.Y.Z` and finding the same set of OCI labels (description, source, revision, version) populated with the same values.
|
||||||
|
|
||||||
|
#### Scenario: Labels match Android conventions
|
||||||
|
|
||||||
|
- **GIVEN** a successful `release_windows` run for tag `X.Y.Z`
|
||||||
|
- **WHEN** an operator runs `docker inspect docker.io/<org>/flutter-windows:X.Y.Z` and inspects the `Labels` map
|
||||||
|
- **THEN** the keys `org.opencontainers.image.source`, `org.opencontainers.image.revision`, `org.opencontainers.image.version`, and `org.opencontainers.image.title` are all present
|
||||||
|
- **AND** `org.opencontainers.image.version` equals `X.Y.Z`
|
||||||
|
- **AND** `org.opencontainers.image.revision` equals the commit SHA of the tag
|
||||||
|
|
||||||
|
### Requirement: Manual `workflow_dispatch` rebuild remains available for Windows
|
||||||
|
|
||||||
|
The `release.yml` workflow SHALL continue to declare `workflow_dispatch:`, and the `release_windows` job SHALL be runnable via `workflow_dispatch` with the `FLUTTER_VERSION` env var set from `github.ref_name`, so that a maintainer can rebuild a single tag's Windows image without re-cutting the Git tag.
|
||||||
|
|
||||||
|
The experience context is the maintainer recovering from a transient Windows runner failure: they re-run the workflow on the existing tag instead of force-pushing a new one.
|
||||||
|
|
||||||
|
#### Scenario: Manual rebuild produces a fresh image
|
||||||
|
|
||||||
|
- **GIVEN** a tag `X.Y.Z` exists in the repository
|
||||||
|
- **AND** the prior `release_windows` run for that tag failed
|
||||||
|
- **WHEN** a maintainer triggers `release.yml` via `workflow_dispatch` selecting ref `X.Y.Z`
|
||||||
|
- **THEN** `release_windows` builds and pushes `flutter-windows:X.Y.Z` to all three registries
|
||||||
|
- **AND** the existing image digests at those tags are overwritten by the new digests
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
## 1. Add the `release_windows` job to `release.yml`
|
||||||
|
|
||||||
|
- [ ] 1.1 Open `.github/workflows/release.yml` and add a new job `release_windows` after `release_android`. Set `runs-on: windows-2025`, `permissions.packages: write`, `env.IMAGE_REPOSITORY_NAME: flutter-windows`, `env.VERSION_MANIFEST: config/version.json`.
|
||||||
|
- [ ] 1.2 Add a `Checkout repository` step using the same SHA-pinned `actions/checkout` already in use elsewhere in the file.
|
||||||
|
- [ ] 1.3 Add a `Read environment variables from the version manifest` step using `actions/github-script` and `script/setEnvironmentVariables.js`, identical to `release_android`.
|
||||||
|
- [ ] 1.4 Add a `Load image metadata` step using `docker/metadata-action` with `images:` set to `${{ env.IMAGE_REPOSITORY_PATH }}`, `ghcr.io/${{ env.IMAGE_REPOSITORY_PATH }}`, `quay.io/${{ env.IMAGE_REPOSITORY_PATH }}` and `tags: type=raw,value=${{ env.FLUTTER_VERSION }}`.
|
||||||
|
|
||||||
|
## 2. Wire registry logins
|
||||||
|
|
||||||
|
- [ ] 2.1 Add `Login to Docker Hub` step using `docker/login-action` with `${{ secrets.DOCKER_HUB_USERNAME }}` / `${{ secrets.DOCKER_HUB_TOKEN }}`.
|
||||||
|
- [ ] 2.2 Add `Login to GitHub Container Registry` step with `registry: ghcr.io`, `${{ github.actor }}` / `${{ github.token }}`.
|
||||||
|
- [ ] 2.3 Add `Login to Quay.io` step with `registry: quay.io`, `${{ secrets.QUAY_USERNAME }}` / `${{ secrets.QUAY_ROBOT_TOKEN }}`.
|
||||||
|
|
||||||
|
## 3. Build and push the Windows image
|
||||||
|
|
||||||
|
- [ ] 3.1 Add a `Build image` step running `docker build . -f windows.Dockerfile --target flutter --build-arg flutter_version=${{ env.FLUTTER_VERSION }}` followed by `docker tag` calls that apply each metadata-action tag to the local image.
|
||||||
|
- [ ] 3.2 Add the OCI labels emitted by `metadata-action` to the build using `--label` arguments (or pipe the labels via a script step that iterates `${{ steps.metadata.outputs.labels }}`).
|
||||||
|
- [ ] 3.3 Add a `Push to registries` step that runs `docker push` for each tag in `${{ steps.metadata.outputs.tags }}` (one push per registry-prefixed tag).
|
||||||
|
|
||||||
|
## 4. Confirm parallelism and isolation from `release_android`
|
||||||
|
|
||||||
|
- [ ] 4.1 Verify the new job has no `needs:` line and no `if:` line keying on `release_android` outcome — it must run in parallel.
|
||||||
|
- [ ] 4.2 Verify the existing `update_description`, `record_image`, `set_bootstrap_image`, and `create_github_release` jobs still `needs: release_android` only, not `release_windows`.
|
||||||
|
|
||||||
|
## 5. Confirm gx pinning compliance
|
||||||
|
|
||||||
|
- [ ] 5.1 Confirm every `uses:` action in the new job is already entered in `.github/gx.toml` (it should be, since they all appear in `release_android`).
|
||||||
|
- [ ] 5.2 Run `gx tidy` locally; the diff should be empty. If it isn't, commit the gx-managed updates with the change.
|
||||||
|
- [ ] 5.3 Run `gx lint` locally to confirm SHA pinning is correct.
|
||||||
|
|
||||||
|
## 6. Pre-merge dry run
|
||||||
|
|
||||||
|
- [ ] 6.1 Push the branch and open a PR. The `pull_request` checks do not exercise `release.yml`, so the PR is evaluated on YAML review only.
|
||||||
|
- [ ] 6.2 After merge, use `workflow_dispatch` to trigger `release.yml` against the most recent stable Flutter tag.
|
||||||
|
- [ ] 6.3 Confirm `release_windows` exits 0 and the three published manifests exist:
|
||||||
|
- `docker manifest inspect docker.io/<org>/flutter-windows:<version>`
|
||||||
|
- `docker manifest inspect ghcr.io/<org>/flutter-windows:<version>`
|
||||||
|
- `docker manifest inspect quay.io/<org>/flutter-windows:<version>`
|
||||||
|
- [ ] 6.4 Confirm `docker.io/<org>/flutter-android:<version>` is unaffected by the workflow_dispatch run (its digest matches what was published at the original tag time).
|
||||||
|
|
||||||
|
## 7. Confirm OCI labels and version match
|
||||||
|
|
||||||
|
- [ ] 7.1 Run `docker pull docker.io/<org>/flutter-windows:<version>` and `docker inspect` it.
|
||||||
|
- [ ] 7.2 Confirm `Labels["org.opencontainers.image.version"]` equals `<version>` and `Labels["org.opencontainers.image.revision"]` equals the tag's commit SHA.
|
||||||
|
- [ ] 7.3 Run the image and confirm `flutter --version` reports `<version>`.
|
||||||
|
|
||||||
|
## 8. Archive
|
||||||
|
|
||||||
|
- [ ] 8.1 After merge and successful first real (non-dispatch) release, archive this change so the `windows-image-release` spec is promoted to `openspec/specs/`.
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
schema: spec-driven
|
||||||
|
created: 2026-05-09
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
## Context
|
||||||
|
|
||||||
|
The repository already has a strong "manifest is source of truth" discipline for Linux/Android: `config/version.json` declares versions, `config/schema.cue` validates them, `setEnvironmentVariables.js` exports them, `update_version.yml` refreshes them, and `flutter-version-update` is the spec that ties it together. The Windows side has none of this — `windows.Dockerfile` carries hardcoded `git_version=2.46.0`, hardcoded `Windows11SDK.22621`, and no version metadata for the CMake or VCTools components beyond their *names*. After `p1` lands, the Pester suite asserts presence; this change extends to asserting *exact versions*, so the manifest becomes the single point of truth for "which Windows toolchain produced this image."
|
||||||
|
|
||||||
|
Constraints:
|
||||||
|
|
||||||
|
- Microsoft does not publish a clean, programmatic, monotonic API for VS BuildTools component versions. The closest source is the channel manifest at `https://aka.ms/vs/17/release/channel` (JSON), which is used by Microsoft's own VS installers but has nondeterministic ordering and changes structure occasionally.
|
||||||
|
- The Windows 11 SDK build number (`22621`) is essentially a Microsoft branding choice; it changes infrequently (Win11 22H2, 23H2 etc.) and is not strictly tied to VS BuildTools versions.
|
||||||
|
- Git for Windows publishes clean GitHub releases at `git-for-windows/git`, but the tag naming is `vM.m.p.windows.N` — the `.windows.N` suffix needs to be stripped before storing as a clean semver.
|
||||||
|
- `cue` already vendors well in this repo; adding `#SemverQuad` is a one-line addition.
|
||||||
|
- `setEnvironmentVariables.js` is the chokepoint — every workflow consumes it. Adding fields there ripples to every downstream job for free.
|
||||||
|
|
||||||
|
## Goals / Non-Goals
|
||||||
|
|
||||||
|
**Goals:**
|
||||||
|
|
||||||
|
- `config/version.json` carries every version that the Windows image embeds.
|
||||||
|
- `windows.Dockerfile` has no version literals — every version is a build arg.
|
||||||
|
- The Pester suite asserts exact versions, not "any version is fine."
|
||||||
|
- The monthly upgrade PR carries Windows updates alongside Flutter and Android.
|
||||||
|
- A maintainer reading `git log -p config/version.json` sees Windows toolchain changes at the same fidelity as Android toolchain changes.
|
||||||
|
|
||||||
|
**Non-Goals:**
|
||||||
|
|
||||||
|
- Auto-detecting the latest VS BuildTools component versions from Microsoft. The channel manifest is too unstable to drive blind; this change reads it but pins the parsed output, gated behind a manual review step (see decisions).
|
||||||
|
- Tracking the Microsoft Windows Server Core base image (`mcr.microsoft.com/windows/servercore:ltsc2022@sha256:…`) under the manifest. That digest is already SHA-pinned in the Dockerfile FROM line; Renovate's Docker manager handles it. The `windows` block in `version.json` covers tools installed *on top of* the base, not the base itself.
|
||||||
|
- Running the Windows update job on `windows-2025`. The job that *fetches* the new versions is a Linux job (it just reads URLs and edits JSON). The image is rebuilt by `windows.yml` and `release.yml` on `windows-2025`.
|
||||||
|
- Renaming Android-side fields for consistency. `android.buildTools.version` and `windows.vsBuildTools.cmakeProject.version` look asymmetric but match each ecosystem's idioms.
|
||||||
|
|
||||||
|
## Decisions
|
||||||
|
|
||||||
|
### Decision: VS BuildTools component versions are pinned in `config/version.json`, refreshed manually from the channel manifest
|
||||||
|
|
||||||
|
The `update_windows_version` job reads `https://aka.ms/vs/17/release/channel` and writes the resolved component versions into `config/version.json`. However, the channel manifest occasionally drops or renames components, so this is treated as a *suggestion* not a *truth*: the PR is opened with the new values, but the Pester suite then verifies that those values actually install. If they don't, the PR fails and a human pins by hand.
|
||||||
|
|
||||||
|
Alternatives considered:
|
||||||
|
|
||||||
|
- **Pin the four versions in the schema (in `schema.cue`) and require a human edit to bump.** Rejected: same review burden, but no automation. The channel-manifest read is cheap and 99% of the time correct.
|
||||||
|
- **Use Renovate's `vsBuildTools` datasource.** Rejected: no such datasource exists. Adding one is out of scope.
|
||||||
|
|
||||||
|
### Decision: Schema additions are `#SemverPatch` (Git), `#SemverQuad` (CMake/VCTools), `int` (Win11SDK build)
|
||||||
|
|
||||||
|
Git for Windows publishes three-part versions (e.g., `2.46.0`); `#SemverPatch` matches.
|
||||||
|
|
||||||
|
VS components publish four-part versions (e.g., `17.13.35919.96`); a new `#SemverQuad: { version!: =~ "^\\d+\\.\\d+\\.\\d+\\.\\d+$" }` is added to `schema.cue`.
|
||||||
|
|
||||||
|
Win11 SDK is identified by a build id, not a semver; modeled as a bare integer. This matches Microsoft's documentation conventions.
|
||||||
|
|
||||||
|
### Decision: Build args have no defaults
|
||||||
|
|
||||||
|
`windows.Dockerfile` ARG declarations remove all default values. This makes the build fail loudly if a workflow forgets to pass a build arg. The alternative (keep defaults as a fallback) was rejected because a default value is a second source of truth that drifts from `version.json`.
|
||||||
|
|
||||||
|
### Decision: Pester reads `config/version.json` once at the top of `Describe`, not in every test
|
||||||
|
|
||||||
|
A `BeforeAll` block parses the manifest into PowerShell variables. This keeps each test focused on the assertion, not the parsing. PowerShell's `ConvertFrom-Json` returns nested PSObjects; `$manifest.windows.git.version` is the access pattern.
|
||||||
|
|
||||||
|
### Decision: `update_windows_version` runs in parallel with `update_android_version`
|
||||||
|
|
||||||
|
Both `needs: update_flutter_version` and gate on `result == 'true'`. They emit separate artifacts that `update_docs_and_create_pr` merges. This mirrors the parallelism decision in `p2-release-windows-image`.
|
||||||
|
|
||||||
|
Alternative considered:
|
||||||
|
|
||||||
|
- **Sequence Windows after Android.** Rejected: no shared state. The Android job needs `flutter_version.json` (downloaded from the artifact), and the Windows job needs the same — the dependency is on the Flutter job, not on each other.
|
||||||
|
|
||||||
|
### Decision: Stripping `.windows.N` suffix from Git for Windows tags
|
||||||
|
|
||||||
|
Tag `v2.46.0.windows.1` → store `2.46.0`. The `.windows.1` revision number changes when Git for Windows publishes a fix without a Git upstream change; the underlying Git binary is identical from Flutter's perspective. Storing the upstream Git version aligns with what users see when they run `git --version` (which reports `git version 2.46.0.windows.1` — but the test parses `2.46.0` from the leading three parts).
|
||||||
|
|
||||||
|
This means the test compares the leading three parts of `git --version` output to `windows.git.version`. The trailing `.windows.N` is informational, not asserted.
|
||||||
|
|
||||||
|
## Risks / Trade-offs
|
||||||
|
|
||||||
|
- **[Risk] The VS channel manifest changes structure and the update job starts producing nonsense versions.** → Mitigation: `cue vet` rejects values that don't match `#SemverQuad`. The PR fails its own validation. A human manually pins the right values until the channel-manifest reader is fixed.
|
||||||
|
- **[Risk] Microsoft yanks a VS component version (it has happened) and the image cannot be rebuilt for a tag that pinned the yanked version.** → Mitigation: the existing tag's image is already pushed and immutable. Future tags use new versions. Old-tag rebuilds (`workflow_dispatch` per `p2`) might fail until the manifest is updated; document this as a known limitation.
|
||||||
|
- **[Risk] Tightening Pester to exact versions makes the test brittle.** → Acceptable: that's the point. Drift detection is a feature.
|
||||||
|
- **[Trade-off] More fields in `version.json` mean more review surface for upgrade PRs.** → Acceptable: the alternative is hidden state in the Dockerfile, which has no review surface at all.
|
||||||
|
- **[Trade-off] `update_windows_version` adds a runtime dependency on `aka.ms/vs/17/release/channel` and `api.github.com/repos/git-for-windows/git`.** → Acceptable: the workflow already depends on `storage.googleapis.com/flutter_infra_release` and `raw.githubusercontent.com/flutter/flutter`. Two more upstreams is incremental.
|
||||||
|
|
||||||
|
## Automated Test Strategy
|
||||||
|
|
||||||
|
- **Schema validation (existing path):** `validate_version_files` job in `build.yml` and `validate_config_version` job in `update_version.yml` already run `cue vet`. Once the `windows` block is required by `#Version`, any `version.json` missing the block fails these jobs. No new test infrastructure.
|
||||||
|
- **Unit-level test for `setEnvironmentVariables.js`:** none added. The script is small and has no test today; this change preserves the status quo. A regression in env-var emission would surface as a build-arg-not-set failure in `windows.yml` (the build fails loudly because no defaults are kept).
|
||||||
|
- **Integration test (the load-bearing layer):** the Pester suite running on `windows-2025`. After this change, the suite reads `config/version.json` and asserts toolchain versions match. A CUE-valid manifest that doesn't match the actually-installed image fails the test.
|
||||||
|
- **End-to-end (monthly):** `update_version.yml` runs on schedule; the PR it opens carries the new `windows` block; `windows.yml` runs against that PR; the Pester suite gates the merge. If Microsoft changed something incompatibly, the PR fails CI and a maintainer intervenes.
|
||||||
|
- **No new test files** — the Pester suite is extended, not duplicated. The schema test surface is the existing `cue vet` step.
|
||||||
|
|
||||||
|
## Observability
|
||||||
|
|
||||||
|
- **CUE failures**: `cue vet` prints field-level errors with paths like `windows.vsBuildTools.cmakeProject.version: invalid value …`. The workflow log is the surface.
|
||||||
|
- **Pester version-mismatch failures**: the test failure message names both the manifest value and the in-image value (e.g., `Expected git --version to report 2.46.0; got 2.45.2`). This is how a maintainer diagnoses why a PR is red.
|
||||||
|
- **Update-job failures**: if `update_windows_version` cannot reach the VS channel manifest or the GitHub API, the step fails with a 4xx/5xx in the curl/jq pipeline. The job logs the URL it tried.
|
||||||
|
- **No silent partial updates**: `update_docs_and_create_pr` consumes the artifacts of every prior job. If `update_windows_version` did not upload an artifact, the PR-creation job fails on the missing artifact rather than opening a half-baked PR.
|
||||||
|
- **Dashboard surface**: existing — `gh run list --workflow=update_version.yml`. No new dashboards needed.
|
||||||
|
|
||||||
|
## Migration Plan
|
||||||
|
|
||||||
|
1. Land `p1-fix-windows-ci-tests` first (the Pester suite must exist to be tightened).
|
||||||
|
2. Land `p2-release-windows-image` ideally before this, so the Windows image is published; then this change extends what's published with version metadata.
|
||||||
|
3. Open a PR with:
|
||||||
|
- `config/schema.cue` adding `#SemverQuad` and the `#Version.windows` field.
|
||||||
|
- `config/version.json` backfilled with current values from `windows.Dockerfile`.
|
||||||
|
- `windows.Dockerfile` ARGs without defaults.
|
||||||
|
- `script/setEnvironmentVariables.js` exporting the new env vars.
|
||||||
|
- `windows.yml` (and `release.yml` if `p2` landed) passing the new env vars as `--build-arg`.
|
||||||
|
- `test/windows/Windows.Tests.ps1` reading the manifest and tightening assertions.
|
||||||
|
- `update_version.yml` adding the `update_windows_version` job and merging its artifact in `update_docs_and_create_pr`.
|
||||||
|
4. PR's own `windows.yml` run is the verification. Green = the manifest values match the image; red = somebody mistyped one of the four values during backfill.
|
||||||
|
5. Merge.
|
||||||
|
6. Wait for the next scheduled `update_version.yml` run; it should produce a PR that includes a `windows` block diff. Review and merge as usual.
|
||||||
|
7. Rollback strategy: if `update_windows_version` produces consistently-bad PRs, mark it `if: false` in a follow-up PR. The schema and Dockerfile changes do not need rolling back; they are stable.
|
||||||
|
|
||||||
|
## Open Questions
|
||||||
|
|
||||||
|
- Should `#SemverQuad` live alongside `#SemverPatch` in `schema.cue` or in a separate `windows.cue`? *Tentative: same file.* `schema.cue` is small and the cohesion is high.
|
||||||
|
- Should the `windows.Dockerfile`'s `vs_BuildTools.exe` URL be a build arg too? *Tentative: no.* The URL is `https://aka.ms/vs/17/release/vs_buildtools.exe` which Microsoft promises to keep stable per major VS version. If that promise breaks, this is a separate change.
|
||||||
|
- Should the channel manifest be cached in the repo (committed) so the update job is reproducible offline? *Tentative: no.* That would mean tracking churn that doesn't affect us. The runtime dependency is acceptable.
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
## Why
|
||||||
|
|
||||||
|
`config/version.json` is the single source of truth for Android tooling versions (`buildTools`, `cmake`, `ndk`, `gradle`, `cmdlineTools`, `platforms`) and Flutter itself, and `config/schema.cue` validates it. The Windows build, by contrast, has hardcoded version-bearing strings scattered across `windows.Dockerfile` (Git for Windows `2.46.0`, `Microsoft.VisualStudio.Component.VC.CMake.Project`, `Microsoft.VisualStudio.Component.Windows11SDK.22621`, `Microsoft.VisualStudio.Workload.VCTools`) with no manifest entry, no CUE constraint, and no Renovate-driven update path. After `p1-fix-windows-ci-tests` lands, the Pester suite asserts these components are *present*, but cannot assert they are at any particular version because there is no source of truth to compare against. This change extends `config/schema.cue` and `config/version.json` with a `windows` block, plumbs the values into `windows.Dockerfile` build args, and adds a corresponding `update_windows_version` job to `update_version.yml` so monthly upgrade PRs cover the Windows toolchain alongside Flutter and Android.
|
||||||
|
|
||||||
|
## What Changes
|
||||||
|
|
||||||
|
- Add a `#WindowsToolchain` definition to `config/schema.cue` and constrain `#Version` to include a top-level `windows: #WindowsToolchain` field. Initial fields:
|
||||||
|
- `git: #SemverPatch` (Git for Windows version, currently hardcoded as `2.46.0`),
|
||||||
|
- `vsBuildTools.cmakeProject: #SemverPatch` (the four-part version VS reports as `version=A.B.C.D`; we model it as a `#SemverQuad` introduced alongside),
|
||||||
|
- `vsBuildTools.windows11Sdk: { build!: int }` (the `22621` from `Windows11SDK.22621` — already a numeric build id),
|
||||||
|
- `vsBuildTools.vcTools: #SemverQuad` (workload version reported by VS).
|
||||||
|
- Add `#SemverQuad: { version!: =~ "^\\d+\\.\\d+\\.\\d+\\.\\d+$" }` to `schema.cue` (VS components publish four-part versions; the existing `#SemverPatch` rejects them).
|
||||||
|
- Populate the new `windows` block in `config/version.json` with the values currently hardcoded in `windows.Dockerfile`. This is a one-time backfill; the values do not change.
|
||||||
|
- In `windows.Dockerfile`, replace the hardcoded `git_version=2.46.0` ARG default with no default (force the build arg), and add `vs_cmake_version`, `vs_win11sdk_build`, `vs_vctools_version` build args. The `--add Microsoft.VisualStudio.Component.Windows11SDK.<build>` argument is composed from the build arg.
|
||||||
|
- In `script/setEnvironmentVariables.js`, surface the new fields as env vars (`GIT_VERSION`, `VS_CMAKE_VERSION`, `VS_WIN11SDK_BUILD`, `VS_VCTOOLS_VERSION`) so `windows.yml` and `release.yml` (post-`p2`) can pass them to the build.
|
||||||
|
- In `windows.yml` and (after `p2`) `release.yml`'s `release_windows`, pass these env vars as `--build-arg` values to `docker build`.
|
||||||
|
- In the Pester suite (added by `p1`), tighten the VS component assertions from `*,version=*` to `*,version=<exact-version>*`, and assert Git's reported `git --version` matches `windows.git.version`.
|
||||||
|
- Add a new `update_windows_version` job to `update_version.yml`, parallel to `update_android_version` (both gated by `update_flutter_version`'s `result == 'true'` output). The job:
|
||||||
|
- reads upstream Git for Windows latest release from `https://api.github.com/repos/git-for-windows/git/releases/latest`,
|
||||||
|
- reads VS BuildTools component versions from the channel manifest (or pins them; see design),
|
||||||
|
- writes the new fields into `config/version.json` and uploads the artifact for the `validate_config_version` and `update_docs_and_create_pr` jobs to consume.
|
||||||
|
- The Windows-relevant fields fall under the existing `flutter-version-update` PR cadence — same upgrade PR carries both Android and Windows updates.
|
||||||
|
|
||||||
|
## Capabilities
|
||||||
|
|
||||||
|
### New Capabilities
|
||||||
|
|
||||||
|
- `windows-version-tracking`: defines what `config/version.json`, `config/schema.cue`, and `update_version.yml` must guarantee about Windows toolchain versions — that they live in the manifest, are CUE-validated, are passed into the Dockerfile build, are asserted by the Pester suite at the manifest's pinned values, and are refreshed monthly by the upgrade PR.
|
||||||
|
|
||||||
|
### Modified Capabilities
|
||||||
|
|
||||||
|
- `flutter-version-update`: the requirement "Upgrade PR contains a coherent, validated `version.json`" is extended so the PR's `version.json` also passes `cue vet` against the new `windows` block. The existing scenario that asserts Android `buildTools.version` matches Flutter's `packages.txt` is unchanged; a new scenario asserts Git for Windows tracks the latest GitHub-released Git for Windows tag.
|
||||||
|
- `windows-image-testing` (introduced by `p1`): the VS component requirements are tightened from `*,version=*` to exact-version match against `config/version.json`'s `windows` block; a new requirement asserts Git's reported version matches the manifest.
|
||||||
|
|
||||||
|
## Impact
|
||||||
|
|
||||||
|
- Affected files: `config/schema.cue`, `config/version.json`, `config/flutter_version.json` (unchanged — this is `#Version` not `#FlutterVersion`), `windows.Dockerfile`, `script/setEnvironmentVariables.js`, `.github/workflows/windows.yml`, `.github/workflows/update_version.yml`, `.github/workflows/release.yml` (after `p2` lands), `test/windows/Windows.Tests.ps1`.
|
||||||
|
- Cross-cutting: this is the largest of the three changes. Touches schema, manifest, dockerfile, two workflows, the version-update node script, and the test suite.
|
||||||
|
- Depends on: `p1-fix-windows-ci-tests` landed (Pester tests exist to tighten); `p2-release-windows-image` ideally landed (so the build args also flow through release).
|
||||||
|
- Does not depend on the Renovate-via-`gx` integration (`actions-version-tracking`) since Windows toolchain versions are tracked in `config/version.json` like Android, not in `gx.toml`. Renovate is unaffected.
|
||||||
|
- Risk: VS BuildTools component versioning is provided by Microsoft via the channel manifest; the source of truth and the update API have less stability than Flutter's `releases_linux.json`. Mitigation in design.
|
||||||
|
- Risk: tightening Pester assertions to exact versions means a Microsoft-side patch bump to a VS component will fail CI even though the image still works. Mitigation: track at the build-id level for Win11SDK (already coarse), at the publisher's `version=` for the others, accept that an upgrade PR is required when Microsoft ships a patch.
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
## MODIFIED Requirements
|
||||||
|
|
||||||
|
### Requirement: Upgrade PR contains a coherent, validated `version.json`
|
||||||
|
|
||||||
|
When the workflow opens an upgrade PR, the included `config/version.json` SHALL satisfy `cue vet config/schema.cue -d '#Version'` and SHALL contain the Android `buildTools.version` listed for that exact Flutter tag in `engine/src/flutter/tools/android_sdk/packages.txt` upstream. The same `version.json` SHALL also contain a `windows.git.version` equal to the latest non-prerelease tag at `https://api.github.com/repos/git-for-windows/git/releases/latest` (with any `.windows.N` suffix stripped) and the VS BuildTools component versions sourced from the deterministic source documented in `p3-windows-version-schema`'s design.
|
||||||
|
|
||||||
|
The experience context is the CI engineer reviewing or merging the upgrade PR — they observe that downstream image builds will not silently regress on Android tooling *or* on Windows tooling.
|
||||||
|
|
||||||
|
#### Scenario: Build-tools version tracks the new Flutter tag
|
||||||
|
|
||||||
|
- **GIVEN** the workflow is opening an upgrade PR for Flutter `X.Y.Z`
|
||||||
|
- **AND** Flutter's `engine/src/flutter/tools/android_sdk/packages.txt` at tag `X.Y.Z` lists `build-tools;A.B.C`
|
||||||
|
- **WHEN** the PR is created
|
||||||
|
- **THEN** `config/version.json` in the PR contains `android.buildTools.version == "A.B.C"`
|
||||||
|
|
||||||
|
#### Scenario: Generated config is schema-valid
|
||||||
|
|
||||||
|
- **GIVEN** the workflow has produced a candidate `config/version.json`
|
||||||
|
- **WHEN** the `validate_config_version` job runs
|
||||||
|
- **THEN** `cue vet config/schema.cue -d '#Version' config/version.json` exits 0
|
||||||
|
- **AND** the workflow only proceeds to open the PR if validation passes
|
||||||
|
|
||||||
|
#### Scenario: Git for Windows tracks the latest published tag
|
||||||
|
|
||||||
|
- **GIVEN** `https://api.github.com/repos/git-for-windows/git/releases/latest` returns an asset whose underlying Git semver is `M.m.p`
|
||||||
|
- **WHEN** the upgrade PR is created
|
||||||
|
- **THEN** `config/version.json` in the PR contains `windows.git.version == "M.m.p"`
|
||||||
|
|
||||||
|
#### Scenario: Windows toolchain block is schema-valid
|
||||||
|
|
||||||
|
- **GIVEN** the workflow has produced a candidate `config/version.json` containing the new `windows` block
|
||||||
|
- **WHEN** the `validate_config_version` job runs
|
||||||
|
- **THEN** `cue vet` passes against the `windows` block as well as the existing `flutter` and `android` blocks
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
## ADDED Requirements
|
||||||
|
|
||||||
|
### Requirement: `config/version.json` declares the Windows toolchain versions
|
||||||
|
|
||||||
|
`config/version.json` SHALL contain a top-level `windows` object with the following fields, each validated by `config/schema.cue` `#Version`:
|
||||||
|
|
||||||
|
- `windows.git.version` — Git for Windows release version, three-part semver (e.g., `2.46.0`).
|
||||||
|
- `windows.vsBuildTools.cmakeProject.version` — Visual Studio CMake component version, four-part (e.g., `17.13.35919.96`).
|
||||||
|
- `windows.vsBuildTools.windows11Sdk.build` — Windows 11 SDK build number as integer (e.g., `22621`).
|
||||||
|
- `windows.vsBuildTools.vcTools.version` — Visual Studio VCTools workload version, four-part.
|
||||||
|
|
||||||
|
The experience context is the maintainer or CI engineer reading `config/version.json` to know exactly which Windows toolchain a given image tag was built against — without having to grep Dockerfiles.
|
||||||
|
|
||||||
|
#### Scenario: Manifest validates against schema
|
||||||
|
|
||||||
|
- **GIVEN** the current `config/version.json` and `config/schema.cue`
|
||||||
|
- **WHEN** `cue vet config/schema.cue -d '#Version' config/version.json` runs
|
||||||
|
- **THEN** the command exits 0
|
||||||
|
|
||||||
|
#### Scenario: Missing `windows` block fails validation
|
||||||
|
|
||||||
|
- **GIVEN** a candidate `config/version.json` with no `windows` field
|
||||||
|
- **WHEN** `cue vet config/schema.cue -d '#Version' config/version.json` runs
|
||||||
|
- **THEN** the command exits non-zero with an error naming the missing `windows` field
|
||||||
|
|
||||||
|
### Requirement: `windows.Dockerfile` builds from manifest values, not hardcoded constants
|
||||||
|
|
||||||
|
`windows.Dockerfile` SHALL accept the build args `flutter_version`, `git_version`, `vs_cmake_version`, `vs_win11sdk_build`, and `vs_vctools_version`, with no default values. The `--add Microsoft.VisualStudio.Component.Windows11SDK.${vs_win11sdk_build}` invocation in the Dockerfile SHALL substitute the build-arg value, and the Git installer download SHALL use the `git_version` build arg in the URL and filename.
|
||||||
|
|
||||||
|
The experience context is the contributor changing the Windows toolchain: they edit one place (`config/version.json`), regenerate, and the build picks up the change.
|
||||||
|
|
||||||
|
#### Scenario: Build with manifest values succeeds
|
||||||
|
|
||||||
|
- **GIVEN** `config/version.json` declares the four windows version fields
|
||||||
|
- **AND** `windows.yml` passes them as `--build-arg` from the env vars exported by `setEnvironmentVariables.js`
|
||||||
|
- **WHEN** the test_windows job runs `docker build`
|
||||||
|
- **THEN** the build completes and the resulting image has VS components installed at the manifest-declared versions
|
||||||
|
|
||||||
|
#### Scenario: Build without manifest values fails fast
|
||||||
|
|
||||||
|
- **WHEN** a developer runs `docker build -f windows.Dockerfile .` without any `--build-arg`
|
||||||
|
- **THEN** the build fails on the first ARG-using step
|
||||||
|
- **AND** the error message names the missing build argument
|
||||||
|
|
||||||
|
### Requirement: `setEnvironmentVariables.js` exports Windows fields as env vars
|
||||||
|
|
||||||
|
`script/setEnvironmentVariables.js` SHALL read `windows.git.version`, `windows.vsBuildTools.cmakeProject.version`, `windows.vsBuildTools.windows11Sdk.build`, and `windows.vsBuildTools.vcTools.version` from `config/version.json` and export them as `GIT_VERSION`, `VS_CMAKE_VERSION`, `VS_WIN11SDK_BUILD`, and `VS_VCTOOLS_VERSION` to the GitHub Actions environment.
|
||||||
|
|
||||||
|
The experience context is `windows.yml` and `release.yml` reading exactly the same env vars to feed `docker build` — a single point of plumbing.
|
||||||
|
|
||||||
|
#### Scenario: Workflow env contains the windows fields
|
||||||
|
|
||||||
|
- **WHEN** the `Read environment variables from the version manifest` step runs in any workflow
|
||||||
|
- **THEN** the workflow's environment contains `GIT_VERSION`, `VS_CMAKE_VERSION`, `VS_WIN11SDK_BUILD`, `VS_VCTOOLS_VERSION` with values matching `config/version.json`
|
||||||
|
|
||||||
|
### Requirement: Pester suite asserts exact toolchain versions
|
||||||
|
|
||||||
|
The Pester suite at `test/windows/Windows.Tests.ps1` SHALL read `config/version.json` (already copied into the test stage by `p1-fix-windows-ci-tests`) and assert that:
|
||||||
|
|
||||||
|
- `git --version` reports a version equal to `windows.git.version`,
|
||||||
|
- the `Microsoft.VisualStudio.Component.VC.CMake.Project,version=<x>` directory's `<x>` equals `windows.vsBuildTools.cmakeProject.version`,
|
||||||
|
- the `Microsoft.VisualStudio.Component.Windows11SDK.<build>` directory's `<build>` equals `windows.vsBuildTools.windows11Sdk.build`,
|
||||||
|
- the `Microsoft.VisualStudio.Workload.VCTools,version=<x>` directory's `<x>` equals `windows.vsBuildTools.vcTools.version`.
|
||||||
|
|
||||||
|
The experience context is the reviewer of an upgrade PR: any drift between the manifest the PR proposes and the image actually produced is caught as a hard test failure, not silent semantics drift.
|
||||||
|
|
||||||
|
#### Scenario: Manifest and image agree on every Windows version
|
||||||
|
|
||||||
|
- **GIVEN** the test image was built with the build args derived from the current `config/version.json`
|
||||||
|
- **WHEN** the Pester suite runs
|
||||||
|
- **THEN** all four version assertions pass
|
||||||
|
|
||||||
|
#### Scenario: Manifest claims a version the image does not have
|
||||||
|
|
||||||
|
- **GIVEN** a PR that bumps `windows.git.version` to a version different from the build arg actually passed to the image
|
||||||
|
- **WHEN** the Pester suite runs
|
||||||
|
- **THEN** the Git version test fails with a message naming both the manifest value and the in-image value
|
||||||
|
|
||||||
|
### Requirement: Monthly upgrade PR includes Windows toolchain updates
|
||||||
|
|
||||||
|
The `update_version.yml` workflow SHALL include a job that updates the `windows` block in `config/version.json` whenever it runs. The job SHALL:
|
||||||
|
|
||||||
|
- read the latest Git for Windows release from `https://api.github.com/repos/git-for-windows/git/releases/latest` and write the resolved version to `windows.git.version`,
|
||||||
|
- write the VS BuildTools component versions from a deterministic source (see design — channel manifest or pinned config),
|
||||||
|
- emit a CUE-validated `config/version.json` that is consumed by `validate_config_version` and `update_docs_and_create_pr`.
|
||||||
|
|
||||||
|
The experience context is the maintainer who merges the monthly upgrade PR and expects both Android and Windows toolchains to be bumped in the same PR — a single review surface.
|
||||||
|
|
||||||
|
#### Scenario: Monthly run produces a Windows-aware upgrade PR
|
||||||
|
|
||||||
|
- **GIVEN** a scheduled run of `update_version.yml` where Flutter has a new stable
|
||||||
|
- **AND** Git for Windows has a new release since the last run
|
||||||
|
- **WHEN** the workflow opens its upgrade PR
|
||||||
|
- **THEN** the PR's `config/version.json` has a bumped `windows.git.version`
|
||||||
|
- **AND** the PR's `config/version.json` passes `cue vet` against `#Version`
|
||||||
|
|
||||||
|
#### Scenario: No Windows update needed in this cycle
|
||||||
|
|
||||||
|
- **GIVEN** Git for Windows and VS BuildTools component versions match what is already in `config/version.json`
|
||||||
|
- **WHEN** the upgrade PR is composed
|
||||||
|
- **THEN** the PR still opens (because Flutter changed) but the `windows` block is unchanged byte-for-byte
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
## 1. Extend the CUE schema
|
||||||
|
|
||||||
|
- [ ] 1.1 Add `#SemverQuad: { version!: =~ "^\\d+\\.\\d+\\.\\d+\\.\\d+$" }` to `config/schema.cue` alongside the existing `#SemverPatch`.
|
||||||
|
- [ ] 1.2 Add a `#WindowsToolchain` definition with sub-fields `git: #SemverPatch`, `vsBuildTools.cmakeProject: #SemverQuad`, `vsBuildTools.windows11Sdk.build!: int`, `vsBuildTools.vcTools: #SemverQuad`.
|
||||||
|
- [ ] 1.3 Extend `#Version` to require `windows: #WindowsToolchain`.
|
||||||
|
- [ ] 1.4 Run `cue vet config/schema.cue -d '#Version' config/version.json` and confirm it fails (because `version.json` doesn't yet have the `windows` block — the next group fixes this).
|
||||||
|
|
||||||
|
## 2. Backfill `config/version.json`
|
||||||
|
|
||||||
|
- [ ] 2.1 Read the current values from `windows.Dockerfile`: Git `2.46.0`, Win11SDK build `22621`. Run `vs_BuildTools.exe` introspection (or look at a successful `windows-2025` build's package directory listing in a recent CI run) to find the four-part versions actually installed for `Microsoft.VisualStudio.Component.VC.CMake.Project` and `Microsoft.VisualStudio.Workload.VCTools`.
|
||||||
|
- [ ] 2.2 Add the `windows` block to `config/version.json` with those four values.
|
||||||
|
- [ ] 2.3 Run `cue vet config/schema.cue -d '#Version' config/version.json`; it should now exit 0.
|
||||||
|
|
||||||
|
## 3. Remove version literals from `windows.Dockerfile`
|
||||||
|
|
||||||
|
- [ ] 3.1 Replace `ARG git_version=2.46.0` with `ARG git_version` (no default).
|
||||||
|
- [ ] 3.2 Add `ARG vs_cmake_version`, `ARG vs_win11sdk_build`, `ARG vs_vctools_version` (no defaults) before the `vs_BuildTools.exe` invocation.
|
||||||
|
- [ ] 3.3 Replace the literal `--add Microsoft.VisualStudio.Component.Windows11SDK.22621` with `--add Microsoft.VisualStudio.Component.Windows11SDK.${env:vs_win11sdk_build}` (or the correct PowerShell expansion syntax inside the RUN command).
|
||||||
|
- [ ] 3.4 The CMake and VCTools `--add` lines do not embed versions in the component id — versions are picked up from the channel and asserted later by Pester. Leave the `--add` lines alone for these.
|
||||||
|
|
||||||
|
## 4. Surface the new fields via `setEnvironmentVariables.js`
|
||||||
|
|
||||||
|
- [ ] 4.1 Open `script/setEnvironmentVariables.js`. Add reads for `windows.git.version`, `windows.vsBuildTools.cmakeProject.version`, `windows.vsBuildTools.windows11Sdk.build`, `windows.vsBuildTools.vcTools.version`.
|
||||||
|
- [ ] 4.2 Export each via `core.exportVariable` as `GIT_VERSION`, `VS_CMAKE_VERSION`, `VS_WIN11SDK_BUILD`, `VS_VCTOOLS_VERSION`.
|
||||||
|
- [ ] 4.3 Confirm existing exported variables are unchanged.
|
||||||
|
|
||||||
|
## 5. Wire build args into the workflows
|
||||||
|
|
||||||
|
- [ ] 5.1 In `.github/workflows/windows.yml`, update the `docker build` invocation in the `Test image and push to local Docker daemon` step to pass `--build-arg git_version=${{ env.GIT_VERSION }}`, `--build-arg vs_cmake_version=${{ env.VS_CMAKE_VERSION }}`, `--build-arg vs_win11sdk_build=${{ env.VS_WIN11SDK_BUILD }}`, `--build-arg vs_vctools_version=${{ env.VS_VCTOOLS_VERSION }}`.
|
||||||
|
- [ ] 5.2 If `p2-release-windows-image` is already merged, apply the same `--build-arg` changes to the `release_windows` job in `.github/workflows/release.yml`.
|
||||||
|
|
||||||
|
## 6. Tighten the Pester assertions
|
||||||
|
|
||||||
|
- [ ] 6.1 In `test/windows/Windows.Tests.ps1`, add a `BeforeAll` block that reads `config\version.json` via `Get-Content -Raw | ConvertFrom-Json` into `$manifest`.
|
||||||
|
- [ ] 6.2 Add a Git version test that runs `git --version`, parses the leading three-part semver (`X.Y.Z` from `git version X.Y.Z.windows.N`), and asserts equality with `$manifest.windows.git.version`.
|
||||||
|
- [ ] 6.3 Tighten the existing CMake assertion: change pattern from `,version=*` to `,version=$($manifest.windows.vsBuildTools.cmakeProject.version)*`.
|
||||||
|
- [ ] 6.4 Tighten the existing Win11SDK assertion: change to match `Microsoft.VisualStudio.Component.Windows11SDK.$($manifest.windows.vsBuildTools.windows11Sdk.build),version*`.
|
||||||
|
- [ ] 6.5 Tighten the existing VCTools assertion: change pattern from `,version=*` to `,version=$($manifest.windows.vsBuildTools.vcTools.version)*`.
|
||||||
|
|
||||||
|
## 7. Add `update_windows_version` to `update_version.yml`
|
||||||
|
|
||||||
|
- [ ] 7.1 Add a job `update_windows_version` after `update_flutter_version`. Set `runs-on: ubuntu-24.04`, `needs: update_flutter_version`, `if: ${{ needs.update_flutter_version.outputs.new_version == 'true' }}`. Note: this job runs in parallel with `update_android_version`.
|
||||||
|
- [ ] 7.2 Job step: download the `flutter_version.json` artifact (parallel pattern with `update_android_version`).
|
||||||
|
- [ ] 7.3 Job step: `curl -fsSL https://api.github.com/repos/git-for-windows/git/releases/latest | jq -r '.tag_name'`; strip leading `v` and trailing `.windows.N`; export as `GIT_VERSION_NEW`.
|
||||||
|
- [ ] 7.4 Job step: `curl -fsSL https://aka.ms/vs/17/release/channel | jq …` to extract the `Microsoft.VisualStudio.Component.VC.CMake.Project` and `Microsoft.VisualStudio.Workload.VCTools` component versions. Document the jq path in a comment so future maintainers can update it if the channel manifest restructures.
|
||||||
|
- [ ] 7.5 Job step: write the four resolved values into `config/version.json` using `jq` (preserving existing keys).
|
||||||
|
- [ ] 7.6 Job step: validate with `cue vet config/schema.cue -d '#Version' config/version.json`. Fail the job on non-zero exit.
|
||||||
|
- [ ] 7.7 Job step: upload `config/version.json` as an artifact named `version.json.windows` (so it doesn't collide with the Android artifact).
|
||||||
|
- [ ] 7.8 In `update_docs_and_create_pr`, add a `download` step for the new artifact and a merge step that combines the Android-emitted `version.json` and the Windows-emitted `version.json` into one. Document the merge order: Android artifact wins for `flutter`/`android` keys, Windows artifact wins for `windows` keys.
|
||||||
|
|
||||||
|
## 8. Validate end-to-end
|
||||||
|
|
||||||
|
- [ ] 8.1 Push the branch; the `windows.yml` PR check builds the image with the new build args and runs the tightened Pester suite. Confirm green.
|
||||||
|
- [ ] 8.2 Run `update_version.yml` via `workflow_dispatch` (which runs unconditionally on the dispatch branch); confirm a draft PR is opened with a non-empty `windows` block diff. (If Flutter is not changing, the dispatch will be a no-op; force a dispatch on a test branch where `flutter_version.json` has been hand-edited.)
|
||||||
|
|
||||||
|
## 9. Documentation
|
||||||
|
|
||||||
|
- [ ] 9.1 Update `docs/src/windows.mdx` (and the auto-generated `docs/windows.md`) to note that the Windows toolchain versions are pinned in `config/version.json` and refreshed by the monthly upgrade PR.
|
||||||
|
- [ ] 9.2 Remove now-completed TODO items in `docs/windows.md` referring to "test where is installed" and "snapshot of flutter config" — those are covered by the Pester suite now.
|
||||||
|
|
||||||
|
## 10. Archive
|
||||||
|
|
||||||
|
- [ ] 10.1 After merge and confirmation that the next scheduled `update_version.yml` produces a sensible Windows-bumping PR, archive this change so the `windows-version-tracking` spec is promoted to `openspec/specs/` and the `flutter-version-update` delta is applied to the existing spec there.
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
Install-PackageProvider -Name NuGet -Force
|
||||||
|
Install-Module -Name Pester -Force -SkipPublisherCheck
|
||||||
|
|
||||||
|
Write-Host "Pester installation completed successfully"
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
||||||
|
|
||||||
|
$pesterConfig = New-PesterConfiguration;
|
||||||
|
$pesterConfig.Output.Verbosity = 'Detailed';
|
||||||
|
$pesterConfig.Run.Exit = $true;
|
||||||
|
$pesterConfig.Run.Path = ".\test";
|
||||||
|
|
||||||
|
Invoke-Pester -Configuration $pesterConfig;
|
||||||
|
Exit $LASTEXITCODE;
|
||||||
Executable → Regular
Executable → Regular
@@ -0,0 +1,83 @@
|
|||||||
|
Describe "Flutter version" {
|
||||||
|
It "Should match the version in config/version.json" {
|
||||||
|
$manifest = Get-Content "config\version.json" | ConvertFrom-Json
|
||||||
|
$expectedVersion = $manifest.flutter.version
|
||||||
|
|
||||||
|
$firstLine = flutter --version 2>&1 | Select-Object -First 1
|
||||||
|
$firstLine -match 'Flutter (\S+)' | Out-Null
|
||||||
|
$actualVersion = $Matches[1]
|
||||||
|
|
||||||
|
$actualVersion | Should -Be $expectedVersion -Because "flutter --version reported '$actualVersion' but config/version.json specifies '$expectedVersion'"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Describe "Flutter doctor" {
|
||||||
|
BeforeAll {
|
||||||
|
# Flutter doctor on Windows uses U+221A (SQUARE ROOT) for pass and ASCII X for fail.
|
||||||
|
# On other platforms it uses U+2713/U+2717. Keep this source pure ASCII so Windows
|
||||||
|
# PowerShell 5.x does not choke on the file encoding.
|
||||||
|
$script:passMarkers = @([char]0x221A, [char]0x2713, [char]0x2714)
|
||||||
|
$script:failMarkers = @('X', 'x', [char]0x2717, [char]0x2718)
|
||||||
|
$script:doctorOutput = flutter doctor 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Should report a healthy Windows toolchain with no unexpected errors" {
|
||||||
|
$skippedPlatforms = @('Android', 'iOS', 'macOS', 'Linux', 'Web', 'Chrome')
|
||||||
|
$failures = @()
|
||||||
|
$expectedMark = "[" + [char]0x221A + "]"
|
||||||
|
|
||||||
|
foreach ($line in $script:doctorOutput) {
|
||||||
|
if ($line -match '^\[(.)\] (.+)$') {
|
||||||
|
$marker = $Matches[1]
|
||||||
|
$header = $Matches[2]
|
||||||
|
|
||||||
|
$skip = $false
|
||||||
|
foreach ($platform in $skippedPlatforms) {
|
||||||
|
if ($header -like "$platform*") { $skip = $true; break }
|
||||||
|
}
|
||||||
|
if ($skip) { continue }
|
||||||
|
|
||||||
|
$isPass = $script:passMarkers -contains $marker
|
||||||
|
$isFail = $script:failMarkers -contains $marker
|
||||||
|
|
||||||
|
if ($header -like 'Windows Version*' -or $header -like 'Visual Studio*') {
|
||||||
|
if (-not $isPass) {
|
||||||
|
$failures += "[$marker] $header (expected $expectedMark)"
|
||||||
|
}
|
||||||
|
} elseif ($isFail) {
|
||||||
|
$failures += "[$marker] $header"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$failures | Should -BeNullOrEmpty -Because ($failures -join '; ')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Describe "Windows file structure tests" {
|
||||||
|
It "Should have specific file content in dart telemetry config" {
|
||||||
|
"$env:APPDATA\.dart-tool\dart-flutter-telemetry.config" | Should -FileContentMatchExactly "reporting=0"
|
||||||
|
}
|
||||||
|
|
||||||
|
Context "VisualStudio components" {
|
||||||
|
BeforeAll {
|
||||||
|
$visualStudioPackages = (Get-ChildItem $env:ProgramData\Microsoft\VisualStudio\Packages).Name
|
||||||
|
}
|
||||||
|
|
||||||
|
It "CMake version matches" {
|
||||||
|
$directoryName = $visualStudioPackages | Select-String -CaseSensitive Microsoft.VisualStudio.Component.VC.CMake.Project
|
||||||
|
$directoryName | Should -BeLikeExactly "Microsoft.VisualStudio.Component.VC.CMake.Project,version=*"
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Windows11SDK version matches" {
|
||||||
|
$directoryName = $visualStudioPackages | Select-String -CaseSensitive Microsoft.VisualStudio.Component.Windows11SDK
|
||||||
|
$directoryName | Should -BeLikeExactly "Microsoft.VisualStudio.Component.Windows11SDK.22621,version=*"
|
||||||
|
}
|
||||||
|
|
||||||
|
It "VCTools version matches" {
|
||||||
|
$directoryName = $visualStudioPackages | Select-String -CaseSensitive Microsoft.VisualStudio.Workload.VCTools
|
||||||
|
$directoryName | Should -BeLikeExactly "Microsoft.VisualStudio.Workload.VCTools,version=*"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -83,6 +83,42 @@ RUN flutter build windows;
|
|||||||
WORKDIR "$USERPROFILE"
|
WORKDIR "$USERPROFILE"
|
||||||
COPY ./script/docker_windows_entrypoint.ps1 "docker_entrypoint.ps1"
|
COPY ./script/docker_windows_entrypoint.ps1 "docker_entrypoint.ps1"
|
||||||
|
|
||||||
|
# hadolint ignore=DL3025
|
||||||
ENTRYPOINT "C:\Users\ContainerUser\docker_entrypoint.ps1"
|
ENTRYPOINT "C:\Users\ContainerUser\docker_entrypoint.ps1"
|
||||||
|
|
||||||
RUN Remove-Item -Recurse build_app;
|
RUN Remove-Item -Recurse build_app;
|
||||||
|
|
||||||
|
#-----------------------------------------------
|
||||||
|
#-----------------------------------------------
|
||||||
|
#-----------------------------------------------
|
||||||
|
|
||||||
|
FROM flutter as test
|
||||||
|
|
||||||
|
SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"]
|
||||||
|
|
||||||
|
# TODO: Find a way to pass $env:USERPROFILE instead of hardcoding C:\Users\ContainerUser. It's hardcoded because environment variables in Windows container works by setting for the Machine scope and that will have $env:USERPROFILE as C:\Users\ContainerAdministrator instead.
|
||||||
|
ENV USERPROFILE="C:\Users\ContainerUser"
|
||||||
|
|
||||||
|
WORKDIR "$USERPROFILE"
|
||||||
|
|
||||||
|
# Install Pester
|
||||||
|
COPY ./script/InstallPester.ps1 ".\InstallPester.ps1"
|
||||||
|
|
||||||
|
# Administrator rights are required to install modules in 'C:\Program Files\WindowsPowerShell\Modules'
|
||||||
|
USER ContainerAdministrator
|
||||||
|
RUN ".\InstallPester.ps1"; `
|
||||||
|
Remove-Item ".\InstallPester.ps1"; `
|
||||||
|
Import-Module Pester;
|
||||||
|
USER ContainerUser
|
||||||
|
|
||||||
|
# Run the tests
|
||||||
|
COPY ./config/version.json ".\config\version.json"
|
||||||
|
COPY ./test/windows/Windows.Tests.ps1 ".\test\Windows.Tests.ps1"
|
||||||
|
COPY ./script/RunPester.ps1 ".\script\RunPester.ps1"
|
||||||
|
|
||||||
|
# Reset the inherited shell-form ENTRYPOINT from the flutter stage. The test image runs Pester,
|
||||||
|
# not the analytics-toggle entrypoint, and shell-form ENTRYPOINT prevents CMD args from being
|
||||||
|
# appended cleanly (Docker emits "Shell-form ENTRYPOINT and exec-form CMD may have unexpected
|
||||||
|
# results" otherwise).
|
||||||
|
ENTRYPOINT ["powershell", "-NoLogo", "-NoProfile", "-File"]
|
||||||
|
CMD [".\\script\\RunPester.ps1"]
|
||||||
|
|||||||
Reference in New Issue
Block a user