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.
246 lines
10 KiB
YAML
246 lines
10 KiB
YAML
name: Windows Release
|
|
|
|
# Continuous Windows portable build on every push to main.
|
|
# Uses GitHub-hosted windows-latest (MSVC) as a *private* builder.
|
|
# Public download: Forgejo release tag windows-continuous.
|
|
# See docs/windows-runner.md
|
|
|
|
on:
|
|
push:
|
|
branches: [main]
|
|
workflow_dispatch:
|
|
|
|
permissions:
|
|
contents: write
|
|
|
|
concurrency:
|
|
group: infinipaint-windows-release
|
|
cancel-in-progress: true
|
|
|
|
jobs:
|
|
windows-x86_64:
|
|
runs-on: windows-latest
|
|
timeout-minutes: 360
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
with:
|
|
submodules: recursive
|
|
|
|
- name: Setup Python
|
|
uses: actions/setup-python@v5
|
|
with:
|
|
python-version: "3.12"
|
|
|
|
- name: Setup Ninja
|
|
uses: seanmiddleditch/gha-setup-ninja@v5
|
|
|
|
- name: Install Conan
|
|
shell: pwsh
|
|
run: |
|
|
python -m pip install --upgrade pip
|
|
python -m pip install "conan>=2,<3"
|
|
conan --version
|
|
# Fresh runners: detect MSVC (windows-latest is currently 195 / VS 18).
|
|
conan profile detect --force
|
|
$default = Join-Path $env:USERPROFILE ".conan2\profiles\default"
|
|
$verLine = Select-String -Path $default -Pattern 'compiler.version=' | Select-Object -First 1
|
|
$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]
|
|
arch=x86_64
|
|
build_type=Release
|
|
compiler=msvc
|
|
compiler.cppstd=23
|
|
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 (generator=Ninja)"
|
|
Get-Content $p
|
|
ninja --version
|
|
|
|
- name: Resolve version
|
|
id: ver
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
ver="$(grep -E 'CURRENT_VERSION_STRING' src/VersionConstants.hpp | head -1 | sed -E 's/.*"([^"]+)".*/\1/')"
|
|
short_sha="$(echo "${GITHUB_SHA}" | cut -c1-7)"
|
|
asset_ver="${ver}+${short_sha}"
|
|
zip_name="infinipaint-${asset_ver}-win-x86_64-portable.zip"
|
|
{
|
|
echo "version=${ver}"
|
|
echo "tag=windows-continuous"
|
|
echo "asset_ver=${asset_ver}"
|
|
echo "zip_name=${zip_name}"
|
|
} >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Build Windows x86_64
|
|
shell: cmd
|
|
run: |
|
|
call .\conan\export_libs.bat
|
|
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
|
|
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
|
|
shell: pwsh
|
|
env:
|
|
ZIP_NAME: ${{ steps.ver.outputs.zip_name }}
|
|
run: |
|
|
$ErrorActionPreference = "Stop"
|
|
$exe = Get-ChildItem -Path . -Recurse -Filter infinipaint.exe |
|
|
Where-Object { $_.FullName -match 'build-x86_64|Release' } |
|
|
Select-Object -First 1
|
|
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
|
|
Copy-Item $exe.FullName (Join-Path $outDir "infinipaint.exe")
|
|
$dllDir = Join-Path (Get-Location) "windowsinstall\dlls-x86_64"
|
|
if (Test-Path $dllDir) {
|
|
Copy-Item (Join-Path $dllDir "*.dll") $outDir -ErrorAction SilentlyContinue
|
|
}
|
|
$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 }
|
|
Compress-Archive -Path (Join-Path $outDir "*") -DestinationPath $zipPath
|
|
"zip_path=$zipPath" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8
|
|
Get-Item $zipPath | Format-List FullName, Length
|
|
|
|
# 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` (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 }}`
|
|
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
|