Ship 0.8 fog/freistellen and fix Windows CI for friends
Some checks are pending
Windows Release / windows-x86_64 (push) Waiting to run

Publish the portable zip to public Forgejo from the private GitHub
builder, and force Ninja so MSVC 195 deps build without CMake 4.2.
This commit is contained in:
copilot 2026-10-25 18:07:47 +01:00
parent 97b136de3b
commit 1c1d63f376
46 changed files with 1748 additions and 156 deletions

View file

@ -1,7 +1,8 @@
name: Windows Release
# Continuous Windows portable build on every push to main.
# Uses GitHub-hosted windows-latest (MSVC preinstalled).
# Uses GitHub-hosted windows-latest (MSVC) as a *private* builder.
# Public download: Forgejo release tag windows-continuous.
# See docs/windows-runner.md
on:
@ -31,6 +32,9 @@ jobs:
with:
python-version: "3.12"
- name: Setup Ninja
uses: seanmiddleditch/gha-setup-ninja@v5
- name: Install Conan
shell: pwsh
run: |
@ -44,6 +48,8 @@ jobs:
$msvcVer = ($verLine.Line -split '=')[1].Trim()
if (-not $msvcVer) { throw "could not read compiler.version from default profile" }
# Host profile matching the runner (repo win-x86_64 still pins 194 for local Tower).
# Force Ninja: recipes that tool_require cmake<4 cannot use "Visual Studio 18 2026"
# (needs CMake >= 4.2). Ninja works with older CMake + MSVC 195.
$p = Join-Path $env:USERPROFILE ".conan2\profiles\win-ci"
@"
[settings]
@ -54,9 +60,13 @@ jobs:
compiler.runtime=dynamic
compiler.version=$msvcVer
os=Windows
[conf]
tools.cmake.cmaketoolchain:generator=Ninja
"@.Trim() | Set-Content -Path $p -Encoding ascii
Write-Host "win-ci MSVC version=$msvcVer"
Write-Host "win-ci MSVC version=$msvcVer (generator=Ninja)"
Get-Content $p
ninja --version
- name: Resolve version
id: ver
@ -81,8 +91,14 @@ jobs:
if errorlevel 1 exit /b 1
conan install . -of=build-x86_64 --build=missing -pr:h=win-ci -pr:b=default
if errorlevel 1 exit /b 1
call windowsinstall\build_x86_64.bat
if not exist build-x86_64\build mkdir build-x86_64\build
cd build-x86_64\build
call .\generators\conanbuild.bat
cmake ..\.. -G Ninja -DCMAKE_TOOLCHAIN_FILE="generators\conan_toolchain.cmake" -DCMAKE_BUILD_TYPE=Release -DCONFIG_NEXT_TO_EXECUTABLE=OFF
if errorlevel 1 exit /b 1
cmake --build . --config Release
if errorlevel 1 exit /b 1
call .\generators\deactivate_conanbuild.bat
- name: Package portable zip
id: pkg
@ -92,9 +108,9 @@ jobs:
run: |
$ErrorActionPreference = "Stop"
$exe = Get-ChildItem -Path . -Recurse -Filter infinipaint.exe |
Where-Object { $_.FullName -match 'Release' } |
Where-Object { $_.FullName -match 'build-x86_64|Release' } |
Select-Object -First 1
if (-not $exe) { throw "infinipaint.exe not found under Release" }
if (-not $exe) { throw "infinipaint.exe not found under build-x86_64/Release" }
$outDir = Join-Path (Get-Location) "dist\portable"
Remove-Item -Recurse -Force $outDir -ErrorAction SilentlyContinue
New-Item -ItemType Directory -Force -Path $outDir | Out-Null
@ -106,6 +122,14 @@ jobs:
$dataSrc = Join-Path (Get-Location) "assets\data"
if (-not (Test-Path $dataSrc)) { $dataSrc = Join-Path (Get-Location) "data" }
Copy-Item -Recurse $dataSrc (Join-Path $outDir "data")
$rembgSrc = Join-Path (Get-Location) "third_party\rembg"
if (Test-Path $rembgSrc) {
Copy-Item -Recurse $rembgSrc (Join-Path $outDir "rembg")
}
$modelsSrc = Join-Path (Get-Location) "third_party\rembg-home"
if (Test-Path $modelsSrc) {
Copy-Item -Recurse $modelsSrc (Join-Path $outDir "rembg-home")
}
New-Item -ItemType Directory -Force -Path "dist" | Out-Null
$zipPath = Join-Path (Get-Location) "dist\$env:ZIP_NAME"
if (Test-Path $zipPath) { Remove-Item $zipPath }
@ -113,19 +137,110 @@ jobs:
"zip_path=$zipPath" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8
Get-Item $zipPath | Format-List FullName, Length
- name: Publish GitHub release
# Optional private artifact on GitHub (repo is private builder-only).
- name: Publish private GitHub release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ steps.ver.outputs.tag }}
name: Windows continuous (${{ steps.ver.outputs.asset_ver }})
prerelease: true
body: |
Continuous Windows build from `main`.
Continuous Windows build from `main` (private GitHub builder).
**Public download:** Forgejo release `windows-continuous`
https://forgejo.fsociety00.cc/copilot/infinipaint/releases/tag/windows-continuous
- Version: **${{ steps.ver.outputs.version }}**
- Commit: `${{ github.sha }}`
- Asset: `${{ steps.ver.outputs.zip_name }}`
Updated on every push to `main`. Built on GitHub-hosted `windows-latest`.
files: dist/${{ steps.ver.outputs.zip_name }}
fail_on_unmatched_files: true
- name: Publish Forgejo release (public)
shell: bash
env:
FORGEJO_TOKEN: ${{ secrets.FORGEJO_TOKEN }}
VERSION_TAG: ${{ steps.ver.outputs.tag }}
ASSET_VER: ${{ steps.ver.outputs.asset_ver }}
VERSION: ${{ steps.ver.outputs.version }}
ZIP_NAME: ${{ steps.ver.outputs.zip_name }}
FORGEJO_API: https://forgejo.fsociety00.cc/api/v1
FORGEJO_REPO: copilot/infinipaint
run: |
set -euo pipefail
if [ -z "${FORGEJO_TOKEN:-}" ]; then
echo "FORGEJO_TOKEN secret missing — cannot publish public Forgejo release" >&2
exit 1
fi
zip_path="dist/${ZIP_NAME}"
test -f "$zip_path"
python3 - <<'PY'
import json, os, ssl, urllib.error, urllib.parse, urllib.request
api = os.environ["FORGEJO_API"].rstrip("/")
repo = os.environ["FORGEJO_REPO"]
token = os.environ["FORGEJO_TOKEN"]
tag = os.environ["VERSION_TAG"]
name = f"Windows continuous ({os.environ['ASSET_VER']})"
zip_name = os.environ["ZIP_NAME"]
zip_path = f"dist/{zip_name}"
body = "\n".join([
"Continuous Windows build from `main` (built on private GitHub Actions).",
"",
f"- Version: **{os.environ['VERSION']}**",
f"- Commit: `{os.environ['GITHUB_SHA']}`",
f"- Asset: `{zip_name}`",
"",
"Updated on every push to `main`.",
])
ctx = ssl.create_default_context()
if os.environ.get("FORGEJO_CURL_INSECURE", "").lower() in ("1", "true"):
ctx = ssl._create_unverified_context()
def req(method, url, data=None, headers=None, raw=False, allow_404=False):
h = {"Authorization": f"token {token}"}
if headers:
h.update(headers)
body_data = data
if data is not None and not raw:
body_data = json.dumps(data).encode()
h.setdefault("Content-Type", "application/json")
r = urllib.request.Request(url, data=body_data, headers=h, method=method)
try:
with urllib.request.urlopen(r, context=ctx) as resp:
raw_body = resp.read()
return resp.status, (json.loads(raw_body) if raw_body else {})
except urllib.error.HTTPError as e:
err = e.read().decode(errors="replace")
if allow_404 and e.code == 404:
return 404, {}
raise SystemExit(f"{method} {url} -> {e.code}: {err}") from e
base = f"{api}/repos/{repo}/releases"
status, release = req("GET", f"{base}/tags/{urllib.parse.quote(tag)}", allow_404=True)
payload = {
"tag_name": tag,
"name": name,
"body": body,
"draft": False,
"prerelease": True,
}
if status == 404:
_, release = req("POST", base, payload)
else:
release_id = release["id"]
_, release = req("PATCH", f"{base}/{release_id}", payload)
for asset in release.get("assets") or []:
if asset.get("name") == zip_name:
req("DELETE", f"{base}/{release_id}/assets/{asset['id']}")
release_id = release["id"]
with open(zip_path, "rb") as f:
data = f.read()
upload_url = f"{base}/{release_id}/assets?name={urllib.parse.quote(zip_name)}"
_, asset = req(
"POST", upload_url, data=data, raw=True,
headers={"Content-Type": "application/octet-stream"},
)
print(asset.get("browser_download_url") or asset.get("name") or asset)
PY