Files
LorenEteval_Furious/.github/workflows/daily-matrix-build.yml
T
2026-09-10 15:09:45 +08:00

833 lines
30 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Copyright (C) 2024present Loren Eteval & contributors <loren.eteval@proton.me>
#
# This file is part of Furious.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
name: Daily matrix build
on:
schedule:
# Daily at 02:17 UTC (10:17 Asia/Shanghai).
- cron: '17 2 * * *'
workflow_dispatch:
workflow_call:
permissions:
contents: read
defaults:
run:
shell: bash
jobs:
deploy-binaries:
name: Deploy ${{ matrix.id }} on ${{ matrix.os }} Python ${{ matrix.python-version }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- id: linux-amd64
os: ubuntu-22.04
python-version: "3.13"
python-architecture: x64
pyside6-version: "6.8.3"
windows-compatibility: ""
windows-architecture: ""
- id: linux-arm64
os: ubuntu-22.04-arm
python-version: "3.11"
python-architecture: arm64
pyside6-version: "6.5.3"
windows-compatibility: ""
windows-architecture: ""
- id: windows7-amd64
os: windows-2025
python-version: "3.13"
python-architecture: x64
pyside6-version: "6.8.3"
windows-compatibility: windows7
windows-architecture: amd64
- id: windows10-amd64
os: windows-2025
python-version: "3.13"
python-architecture: x64
pyside6-version: "6.8.3"
windows-compatibility: windows10
windows-architecture: amd64
- id: windows10-arm64
os: windows-11-vs2026-arm
python-version: "3.13"
python-architecture: arm64
pyside6-version: "6.11.2"
windows-compatibility: windows10
windows-architecture: arm64
- id: macos-intel
os: macos-15-intel
python-version: "3.13"
python-architecture: x64
pyside6-version: "6.8.3"
windows-compatibility: ""
windows-architecture: ""
- id: macos-arm64
os: macos-14
python-version: "3.13"
python-architecture: arm64
pyside6-version: "6.8.3"
windows-compatibility: ""
windows-architecture: ""
env:
GO_WIN7_VERSION: "1.27.1"
WIX_VERSION: "6.0.2"
WIX_INSTALLER_SHA256: "A8A5CC7443353CEF3AB900C60CD7A3A5EE601746319D104AC7B12AD0CED2345C"
steps:
- uses: actions/checkout@v7
- name: Install macOS dependencies
run: |
brew install create-dmg
if: runner.os == 'macOS'
# Remove problematic brew libs if Intel Mac
# Credits: https://github.com/RimSort/RimSort
- name: Remove problematic brew libs on Intel Mac
run: |
brew remove --force --ignore-dependencies openssl@3
brew cleanup openssl@3
if: runner.os == 'macOS' && runner.arch == 'X64'
- name: Install Linux dependencies
run: |
sudo apt update
# if [ "$RUNNER_ARCH" == "ARM64" ]; then
# sudo apt install -y \
# libevent-2.1-7 \
# libwebp7 \
# libminizip1
# fi
sudo apt install -y \
libxcb-cursor0 \
qt6-base-dev \
patchelf \
ccache \
zlib1g-dev
sudo apt install -y flatpak flatpak-builder elfutils patchelf
sudo apt install -y rpm
flatpak remote-add --if-not-exists --user flathub https://flathub.org/repo/flathub.flatpakrepo
flatpak update
flatpak install -y flathub org.kde.Platform//6.8 org.kde.Sdk//6.8
if [ "$RUNNER_ARCH" == "X64" ]; then
appimage_arch="x86_64"
elif [ "$RUNNER_ARCH" == "ARM64" ]; then
appimage_arch="aarch64"
else
echo "Unknown runner architecture: $RUNNER_ARCH"
exit 1
fi
appimage_tools=".appimage-tools"
mkdir "$appimage_tools"
echo "${PWD}/${appimage_tools}" >> "$GITHUB_PATH"
wget -P "$appimage_tools" \
"https://github.com/AppImage/appimagetool/releases/latest/download/appimagetool-${appimage_arch}.AppImage"
chmod +x "$appimage_tools"/*
if: runner.os == 'Linux'
- name: Install Windows dependencies
shell: pwsh
run: |
$toolsetArchitecture = switch ($env:RUNNER_ARCH) {
'X64' { 'x64' }
'ARM64' { 'arm64' }
default { throw "Unknown runner architecture: $env:RUNNER_ARCH" }
}
$installer = Join-Path $env:RUNNER_TEMP 'wix-cli-x64.msi'
$installerUrl = "https://github.com/wixtoolset/wix/releases/download/v$env:WIX_VERSION/wix-cli-x64.msi"
Invoke-WebRequest -Uri $installerUrl -OutFile $installer
$installerHash = (Get-FileHash -Algorithm SHA256 -LiteralPath $installer).Hash
if ($installerHash -ne $env:WIX_INSTALLER_SHA256) {
throw "Unexpected WiX installer SHA-256: $installerHash"
}
$msi = Start-Process msiexec.exe -ArgumentList @(
'/i',
$installer,
'/quiet',
'/norestart',
'/log',
(Join-Path $env:RUNNER_TEMP 'wix-install.log')
) -WindowStyle Hidden -Wait -PassThru
if ($msi.ExitCode -notin @(0, 3010)) {
throw "WiX installation failed with exit code $($msi.ExitCode)"
}
$toolsetRoot = 'C:\Program Files\WiX Toolset v6.0\bin'
$toolsetRoot | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
(Join-Path $toolsetRoot $toolsetArchitecture) | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
if: runner.os == 'Windows'
- name: Check Windows dependencies
run: |
wix --version
wix extension add -g "WixToolset.UI.wixext/${WIX_VERSION}"
wix extension list -g
if: runner.os == 'Windows'
- name: Set up Python for Windows7
uses: LorenEteval/setup-python-win7@v1
with:
python-version: ${{ matrix.python-version }}
check-latest: true
if: matrix.id == 'windows7-amd64'
- name: Set up Python
uses: actions/setup-python@v7
with:
python-version: ${{ matrix.python-version }}
architecture: ${{ matrix.python-architecture }}
check-latest: true
if: matrix.id != 'windows7-amd64'
- name: Verify runner and Python architecture
env:
EXPECTED_ARCHITECTURE: ${{ matrix.python-architecture }}
run: |
python - <<'PY'
import os
import platform
import struct
import sys
aliases = {
'x64': {'amd64', 'x86_64'},
'arm64': {'arm64', 'aarch64'},
}
expected = os.environ['EXPECTED_ARCHITECTURE']
runner_architecture = os.environ.get('RUNNER_ARCH', '').casefold()
machine = platform.machine().casefold()
pointer_width = struct.calcsize('P') * 8
print(f'RUNNER_OS={os.environ.get("RUNNER_OS")}')
print(f'RUNNER_ARCH={os.environ.get("RUNNER_ARCH")}')
print(f'Python={sys.version}')
print(f'platform.machine()={platform.machine()}')
print(f'pointer width={pointer_width}')
if runner_architecture != expected:
raise SystemExit(
f'expected {expected} runner, got {runner_architecture or "unknown"}'
)
if machine not in aliases[expected] or pointer_width != 64:
raise SystemExit(
f'expected native {expected} Python, got {machine}/{pointer_width}-bit'
)
PY
- name: Set up PySide6 for Windows7
uses: LorenEteval/setup-pyside6-win7@v1
with:
pyside6-version: ${{ matrix.pyside6-version }}
if: matrix.id == 'windows7-amd64'
- name: Install Python build and runtime dependencies
run: |
python -c "import sys; print(sys.version)"
python -m pip install --upgrade pip
python -m pip install setuptools wheel
python - <<'PY'
import re
from pathlib import Path
excluded = {
'pyside6',
'pyside6-addons',
'pyside6-essentials',
'xray-core',
'hysteria',
'hysteria2',
'tun2socks',
}
requirements = []
for line in Path('requirements.txt').read_text(encoding='utf-8').splitlines():
match = re.match(r'\s*([A-Za-z0-9_.-]+)', line)
if match and match.group(1).casefold().replace('_', '-') in excluded:
continue
requirements.append(line)
Path('.binary-requirements.txt').write_text(
'\n'.join(requirements) + '\n',
encoding='utf-8',
)
PY
if [[ "${{ matrix.id }}" != "windows7-amd64" ]]; then
pyside_packages=(
"PySide6-Essentials==${{ matrix.pyside6-version }}"
)
if [[ "$RUNNER_OS" != "Linux" ]]; then
pyside_packages+=(
"PySide6-Addons==${{ matrix.pyside6-version }}"
)
fi
if [[ "$RUNNER_OS" == "Windows" ]]; then
pyside_binary_policy=(
"--only-binary=PySide6-Essentials,PySide6-Addons"
)
else
pyside_binary_policy=()
fi
python -m pip install \
"${pyside_binary_policy[@]}" \
"${pyside_packages[@]}"
fi
if [ "$RUNNER_OS" == "Linux" ] && [ "$RUNNER_ARCH" == "ARM64" ]; then
python -m pip install "numpy<2"
fi
python -m pip install -r .binary-requirements.txt
python -m pip install nuitka imageio requests
python -c "import importlib.metadata; print('zxing-cpp', importlib.metadata.version('zxing-cpp'))"
python -c "from PySide6 import QtCore; print(f'PySide6/Qt {QtCore.__version__}')"
- name: Verify Linux Essentials-only Qt environment
if: runner.os == 'Linux'
env:
EXPECTED_PYSIDE6_VERSION: ${{ matrix.pyside6-version }}
run: |
python - <<'PY'
import importlib.metadata
import importlib.util
import os
import platform
import sys
from PySide6 import QtCore
expected = os.environ['EXPECTED_PYSIDE6_VERSION']
essentials = importlib.metadata.version('PySide6-Essentials')
try:
addons = importlib.metadata.version('PySide6-Addons')
except importlib.metadata.PackageNotFoundError:
addons = None
print(f'Python={sys.version}')
print(f'platform.machine()={platform.machine()}')
print(f'PySide6-Essentials={essentials}')
print(f'PySide6-Addons={addons or "absent"}')
print(f'Qt={QtCore.qVersion()}')
if essentials != expected:
raise SystemExit(
f'expected PySide6-Essentials {expected}, got {essentials}'
)
if QtCore.__version__ != expected:
raise SystemExit(
f'expected PySide6/Qt {expected}, got {QtCore.__version__}'
)
if addons is not None:
raise SystemExit(f'PySide6-Addons must be absent, got {addons}')
for name in (
'PySide6.QtWebChannel',
'PySide6.QtWebEngineCore',
'PySide6.QtWebEngineWidgets',
):
if importlib.util.find_spec(name) is not None:
raise SystemExit(f'{name} must be absent from the Linux environment')
PY
- name: Install patched Go for Windows7
run: |
go_name="go-for-win7-windows-amd64"
go_file="${go_name}.zip"
curl --fail --location --output "${go_file}" \
"https://github.com/XTLS/go-win7/releases/download/patched-${GO_WIN7_VERSION}/${go_file}"
7z x "${go_file}" "-o${go_name}" -y
go_root="$(cygpath -w "$PWD/${go_name}")"
echo "GOROOT=${go_root}" >> $GITHUB_ENV
echo "CGO_ENABLED=1" >> $GITHUB_ENV
echo "${go_root}\\bin" >> $GITHUB_PATH
if: matrix.id == 'windows7-amd64'
- name: Set up Go for Windows10
uses: actions/setup-go@v7
with:
go-version: "1.27"
check-latest: true
cache: false
if: matrix.id == 'windows10-amd64'
- name: Build Windows AMD64 bindings from source
shell: pwsh
env:
CC: gcc
CXX: g++
CGO_ENABLED: "1"
CMAKE_BUILD_PARALLEL_LEVEL: "2"
GOPROXY: https://proxy.golang.org,direct
run: |
$ErrorActionPreference = 'Stop'
$PSNativeCommandUseErrorActionPreference = $true
Write-Output "Go executable: $((Get-Command go -ErrorAction Stop).Source)"
go version
go env GOROOT GOOS GOARCH CGO_ENABLED
Write-Output "GCC executable: $((Get-Command gcc -ErrorAction Stop).Source)"
gcc -dumpmachine
$temporaryDirectory = Join-Path $env:RUNNER_TEMP 'bindings'
New-Item -ItemType Directory -Force -Path $temporaryDirectory | Out-Null
foreach ($name in 'TMPDIR', 'TEMP', 'TMP') {
Set-Item -Path "Env:$name" -Value $temporaryDirectory
}
Write-Output "Configured temporary directory: $temporaryDirectory"
python -c "import tempfile; print(f'Python temporary directory: {tempfile.gettempdir()}')"
python -m pip install `
"setuptools>=68" `
wheel `
"cmake>=3.15" `
"pybind11>=3.0.1,<3.1"
$wheelDirectory = Join-Path $temporaryDirectory 'wheels'
New-Item -ItemType Directory -Force -Path $wheelDirectory | Out-Null
$bindings = @(
'Xray-core'
'hysteria2'
'tun2socks'
)
foreach ($binding in $bindings) {
python -m pip wheel `
--no-cache-dir `
--no-build-isolation `
--no-deps `
--no-binary=:all: `
--wheel-dir $wheelDirectory `
$binding
}
$wheels = @(
Get-ChildItem -LiteralPath $wheelDirectory -Filter '*.whl' -File |
Sort-Object Name
)
Write-Output 'Locally built wheels:'
foreach ($wheel in $wheels) {
Write-Output " $($wheel.Name)"
}
$unexpectedWheels = @($wheels | Where-Object Name -NotLike '*win_amd64.whl')
if ($wheels.Count -ne 3 -or $unexpectedWheels.Count -ne 0) {
throw 'Expected exactly three locally built win_amd64 wheels'
}
$wheelPaths = @($wheels.FullName)
python -m pip install `
--no-index `
--force-reinstall `
--no-deps `
$wheelPaths
if: matrix.id == 'windows7-amd64' || matrix.id == 'windows10-amd64'
- name: Install pre-built Hysteria1 wheel
run: |
python -m pip install \
--only-binary=hysteria \
--no-cache-dir \
--no-deps \
hysteria
if: matrix.id == 'windows7-amd64' || matrix.id == 'windows10-amd64'
- name: Install supported binding wheels
run: |
if [[ "$RUNNER_OS" == "Windows" ]]; then
binary_policy="--only-binary=Xray-core,hysteria,hysteria2,tun2socks"
else
binary_policy=""
fi
python -m pip install \
${binary_policy} \
--no-cache-dir \
--no-deps \
Xray-core \
hysteria \
hysteria2 \
tun2socks
if: matrix.id != 'windows7-amd64' && matrix.id != 'windows10-amd64'
- name: Verify Windows native and UI imports
run: |
python - <<'PY'
import importlib
for name in ('xray', 'hysteria', 'hysteria2', 'tun2socks'):
module = importlib.import_module(name)
print(f'{name}: {module.__file__}')
# Native extension imports fail here if a wheel has the wrong machine type.
print('All native bindings imported successfully')
# Some Windows PySide6 distributions intentionally omit Qt WebEngine.
# The application must still import and use its non-WebEngine map fallback.
from Furious.Widget.EndpointInfoWidget import EndpointInfoWidget
print(f'Endpoint UI imported successfully: {EndpointInfoWidget.__name__}')
PY
if: runner.os == 'Windows'
- name: Verify Linux application imports without WebEngine
if: runner.os == 'Linux'
env:
QT_QPA_PLATFORM: offscreen
run: |
python - <<'PY'
import importlib
import Furious
import Furious.Application
import Furious.Window
from PySide6 import QtCore
from PySide6.QtWidgets import QApplication
endpointModule = importlib.import_module(
'Furious.Widget.EndpointInfoWidget'
)
if endpointModule.QWebEngineView is not None:
raise SystemExit('Qt WebEngine must be unavailable in Linux releases')
application = QApplication.instance() or QApplication([])
mapWidget = endpointModule._createEndpointMapWidget(None)
if not isinstance(
mapWidget,
endpointModule._UnavailableEndpointMapWidget,
):
raise SystemExit('the endpoint map did not use its no-WebEngine fallback')
mapWidget.close()
mapWidget.deleteLater()
application.sendPostedEvents(
None,
QtCore.QEvent.Type.DeferredDelete,
)
application.processEvents()
print(f'Furious imported successfully: {Furious.__name__}')
print('Application and window modules imported successfully')
print('Endpoint UI created its no-WebEngine fallback successfully')
PY
- name: Verify Qt WebEngine imports
if: runner.os == 'macOS'
run: |
python - <<'PY'
from PySide6 import QtWebChannel, QtWebEngineCore, QtWebEngineWidgets
print(f'QtWebChannel imported successfully: {QtWebChannel.__name__}')
print(f'QtWebEngineCore imported successfully: {QtWebEngineCore.__name__}')
print(f'QtWebEngineWidgets imported successfully: {QtWebEngineWidgets.__name__}')
PY
- name: Download latest asset files
run: |
python Deploy.py --download
- name: Run deploy script
env:
APPIMAGE_EXTRACT_AND_RUN: "1"
run: |
if [[ -n "${{ matrix.windows-compatibility }}" ]]; then
export WIN_VER_COMPATIBLE="${{ matrix.windows-compatibility }}"
fi
python Deploy.py
- name: Verify Linux distribution excludes WebEngine
if: runner.os == 'Linux'
run: |
python - <<'PY'
from pathlib import Path
root = Path('Furious-Deploy/Furious.dist')
if not root.is_dir():
raise SystemExit(f'Nuitka distribution is missing: {root}')
forbidden = ('qtwebchannel', 'qtwebengine')
unexpected = []
for path in root.rglob('*'):
relative = path.relative_to(root).as_posix().casefold()
if any(name in relative for name in forbidden):
unexpected.append(relative)
if unexpected:
raise SystemExit(
'WebEngine/Addons artifacts found in the Linux distribution:\n'
+ '\n'.join(f' {path}' for path in unexpected)
)
print(f'No Qt WebEngine or Qt WebChannel artifacts found below {root}')
PY
- name: Verify Flatpak native dependencies
if: runner.os == 'Linux'
run: |
bundle="$(find . -maxdepth 1 -name 'Furious-*.flatpak' -print -quit)"
if [[ -z "$bundle" ]]; then
echo 'Flatpak bundle was not generated'
exit 1
fi
flatpak install --user --noninteractive --reinstall -y "$bundle"
cleanup() {
flatpak uninstall \
--user \
--noninteractive \
--delete-data \
-y \
com.Furious.Furious || true
}
trap cleanup EXIT
flatpak run --user --command=bash com.Furious.Furious -c '
set -uo pipefail
application_root=/app/lib/Furious
dependency_report=/tmp/furious-flatpak-missing-dependencies
failures=0
unsupported_qt_plugins=(
"$application_root/PySide6/qt-plugins/imageformats/libqpdf.so"
"$application_root/PySide6/qt-plugins/imageformats/libqtiff.so"
"$application_root/PySide6/qt-plugins/imageformats/libqwebp.so"
"$application_root/PySide6/qt-plugins/platforms/libqeglfs.so"
"$application_root/PySide6/qt-plugins/egldeviceintegrations"
)
: > "$dependency_report"
if [[ ! -x "$application_root/Furious.bin" ]]; then
printf "Packaged application is missing or not executable: %s\n" \
"$application_root/Furious.bin" >> "$dependency_report"
failures=1
fi
double_conversion="$(
find /app/lib -type f -name "libdouble-conversion.so.*" \
-print -quit 2>> "$dependency_report"
)"
if [[ -z "$double_conversion" ]]; then
printf "Bundled libdouble-conversion.so.3 was not found below /app/lib\n" \
>> "$dependency_report"
failures=1
else
printf "Bundled double-conversion library: %s\n" "$double_conversion"
fi
for plugin in "${unsupported_qt_plugins[@]}"; do
if [[ -e "$plugin" ]]; then
printf "Unsupported Qt plugin was packaged: %s\n" "$plugin" \
>> "$dependency_report"
failures=1
fi
done
if ! command -v ldd >/dev/null; then
printf "The Flatpak runtime does not provide ldd\n" \
>> "$dependency_report"
failures=1
else
while IFS= read -r binary; do
if ! dependencies="$(ldd "$binary" 2>&1)"; then
printf "%s\n%s\n\n" \
"Failed to inspect $binary:" \
"$dependencies" >> "$dependency_report"
failures=1
elif printf "%s\n" "$dependencies" | grep -Fq "not found"; then
printf "%s\n%s\n\n" \
"Missing dependencies for $binary:" \
"$dependencies" >> "$dependency_report"
failures=1
fi
done < <(
find "$application_root" \
-type f \
\( -name "*.bin" -o -name "*.so" -o -name "*.so.*" \) \
-print
)
fi
if ((failures)); then
cat "$dependency_report"
exit 1
fi
echo "Flatpak native dependency check passed"
'
- name: Verify macOS application bundle
if: runner.os == 'macOS'
shell: bash
run: |
app='app/Furious-GUI.app'
source_app='Furious-Deploy/Furious-GUI.app'
macos_root="${app}/Contents/MacOS"
qt_root="${macos_root}/PySide6/Qt"
webengine_framework="${qt_root}/lib/QtWebEngineCore.framework"
relocated_qt_root="${app}/Contents/Frameworks/PySide6/Qt"
relocated_webengine_framework="${relocated_qt_root}/lib/QtWebEngineCore.framework"
webengine_helper="${relocated_webengine_framework}/Helpers/QtWebEngineProcess.app/Contents/MacOS/QtWebEngineProcess"
failures=0
check() {
local description="$1"
local status
shift
echo "::group::${description}"
if "$@"; then
status=0
echo "PASS: ${description} (exit code ${status})"
else
status=$?
echo "::error title=macOS bundle verification::FAIL: ${description} (exit code ${status})"
failures=$((failures + 1))
fi
echo '::endgroup::'
}
echo "Application bundle: ${app}"
echo "Qt runtime root: ${qt_root}"
echo "Relocated Qt root: ${relocated_qt_root}"
echo "WebEngine framework link: ${webengine_framework}"
echo "Relocated WebEngine framework: ${relocated_webengine_framework}"
echo "WebEngine helper: ${webengine_helper}"
check 'Application bundle exists' test -d "${app}"
check 'Nuitka source application bundle exists' test -d "${source_app}"
framework_paths=(
"${webengine_framework}"
"${relocated_webengine_framework}"
"${relocated_webengine_framework}/Versions/Current"
"${relocated_webengine_framework}/QtWebEngineCore"
)
check 'Relocated framework paths can be inspected' ls -ld "${framework_paths[@]}"
check 'MacOS WebEngine framework entry is a symbolic link' test -L "${webengine_framework}"
check 'Framework Current version is a symbolic link' test -L "${relocated_webengine_framework}/Versions/Current"
check 'Framework top-level binary is a symbolic link' test -L "${relocated_webengine_framework}/QtWebEngineCore"
check 'WebEngine process helper is executable' test -x "${webengine_helper}"
resource_paths=(
"${macos_root}/icudtl.dat"
"${macos_root}/qtwebengine_resources.pak"
)
check 'WebEngine resource metadata can be inspected' ls -l@ "${resource_paths[@]}"
check 'Chromium ICU data exists' test -f "${resource_paths[0]}"
check 'Chromium resource pack exists' test -f "${resource_paths[1]}"
check 'Nuitka source code signature is valid' codesign --verify --deep --strict --verbose=2 "${source_app}"
check 'Staged application code signature is valid' codesign --verify --deep --strict --verbose=2 "${app}"
size_paths=(
"${app}"
"${app}/Contents/Frameworks"
"${app}/Contents/MacOS"
)
check 'Application bundle sizes can be measured' du -sh "${size_paths[@]}"
if ((failures > 0)); then
echo "::error title=macOS bundle verification::${failures} check(s) failed"
exit 1
fi
- name: Verify packaged Windows binaries
env:
EXPECTED_WINDOWS_ARCHITECTURE: ${{ matrix.windows-architecture }}
WINDOWS_COMPATIBILITY: ${{ matrix.windows-compatibility }}
run: |
python - <<'PY'
import os
import struct
from pathlib import Path
machineCodes = {
'amd64': 0x8664,
'arm64': 0xAA64,
}
expected = os.environ['EXPECTED_WINDOWS_ARCHITECTURE']
expectedMachine = machineCodes[expected]
root = Path('Furious-Deploy/Furious.dist')
compatibilityDll = root / 'api-ms-win-core-path-l1-1-0.dll'
if (
os.environ['WINDOWS_COMPATIBILITY'] == 'windows7'
and not compatibilityDll.is_file()
):
raise SystemExit(f'Windows 7 compatibility DLL is missing: {compatibilityDll}')
binaries = sorted(
{
*root.rglob('*.exe'),
*root.rglob('*.pyd'),
*([compatibilityDll] if compatibilityDll.is_file() else []),
}
)
if not binaries:
raise SystemExit(f'no packaged Windows binaries found below {root}')
for binary in binaries:
with binary.open('rb') as stream:
if stream.read(2) != b'MZ':
raise SystemExit(f'{binary} is not a PE binary')
stream.seek(0x3C)
peOffset = struct.unpack('<I', stream.read(4))[0]
stream.seek(peOffset)
if stream.read(4) != b'PE\0\0':
raise SystemExit(f'{binary} has an invalid PE header')
machine = struct.unpack('<H', stream.read(2))[0]
if machine != expectedMachine:
raise SystemExit(
f'{binary} has PE machine 0x{machine:04x}, expected '
f'{expected} (0x{expectedMachine:04x})'
)
print(f'Verified {len(binaries)} packaged {expected} PE binaries')
PY
if: runner.os == 'Windows'
- name: Store the distribution packages
uses: actions/upload-artifact@v7
with:
name: binary-distributions-${{ matrix.id }}-Python${{ matrix.python-version }}
path: |
Furious-*.zip
Furious-*.msi
Furious-*.dmg
Furious-*.AppImage
Furious-*.deb
Furious-*.flatpak
Furious-*.rpm