Some checks failed
Windows Release / windows-x86_64 (push) Has been cancelled
Conan installs generators under build/Release with Ninja; use conan-release presets and that conanbuild.bat instead of build/.
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
|
|
rem Ninja + cmake_layout puts generators under build/Release/generators
|
|
call build-x86_64\build\Release\generators\conanbuild.bat
|
|
if errorlevel 1 exit /b 1
|
|
cmake --preset conan-release -DCONFIG_NEXT_TO_EXECUTABLE=OFF
|
|
if errorlevel 1 exit /b 1
|
|
cmake --build --preset conan-release
|
|
if errorlevel 1 exit /b 1
|
|
call build-x86_64\build\Release\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
|