Signed-off-by: Loren Eteval <loren.eteval@proton.me>
This commit is contained in:
Loren Eteval
2026-08-24 00:39:42 +08:00
parent 9fdbcf1bde
commit db7d2dd055
5 changed files with 484 additions and 123 deletions
+232 -87
View File
@@ -85,25 +85,34 @@ jobs:
matrix:
include:
- id: linux-amd64
os: ubuntu-22.04
os: ubuntu-24.04
python-version: "3.13"
python-architecture: x64
pyside6-version: "6.8.3"
linux-appimage-builder: linuxdeploy
windows-compatibility: ""
windows-architecture: ""
- id: linux-arm64
os: ubuntu-22.04-arm
os: ubuntu-24.04-arm
python-version: "3.11"
python-architecture: arm64
pyside6-version: "6.5.3"
pyside6-version: "6.8.3"
linux-appimage-builder: appimagetool
windows-compatibility: ""
windows-architecture: ""
- id: windows-win7-amd64
- id: windows7-amd64
os: windows-2025
python-version: "3.13"
python-architecture: x64
pyside6-version: "6.8.3"
windows-compatibility: windows
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: windows-arm64
os: windows-11-vs2026-arm
@@ -127,12 +136,7 @@ jobs:
windows-compatibility: ""
windows-architecture: ""
env:
XRAY_CORE_VERSION: "1.8.26.9"
HYSTERIA_VERSION: "1.3.5.4"
HYSTERIA2_VERSION: "2.12.1.1"
TUN2SOCKS_VERSION: "2.7.0.1"
GO_WIN7_VERSION: "1.26.7"
NUITKA_VERSION: "4.1.3"
WIX_VERSION: "6.0.2"
WIX_INSTALLER_SHA256: "A8A5CC7443353CEF3AB900C60CD7A3A5EE601746319D104AC7B12AD0CED2345C"
steps:
@@ -151,7 +155,26 @@ jobs:
- name: Install Linux dependencies
run: |
sudo apt update
sudo apt install -y libxcb-cursor0 qt6-base-dev patchelf ccache zlib1g-dev
if [ "$RUNNER_ARCH" == "ARM64" ]; then
if apt-cache show libminizip1t64 >/dev/null 2>&1; then
minizip_package='libminizip1t64'
else
minizip_package='libminizip1'
fi
sudo apt install -y \
libevent-2.1-7 \
libwebp7 \
"$minizip_package"
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
@@ -159,20 +182,33 @@ jobs:
flatpak install -y flathub org.kde.Platform//6.8 org.kde.Sdk//6.8
if [ "$RUNNER_ARCH" == "X64" ]; then
appimage_arch="x86_64"
echo "QMAKE=$(command -v qmake6)" >> "$GITHUB_ENV"
linuxdeploy_arch="x86_64"
linuxdeploy_tools=".linuxdeploy-bin"
mkdir "$linuxdeploy_tools"
echo "${PWD}/${linuxdeploy_tools}" >> "$GITHUB_PATH"
wget -P "$linuxdeploy_tools" \
"https://github.com/linuxdeploy/linuxdeploy/releases/latest/download/linuxdeploy-${linuxdeploy_arch}.AppImage"
wget -P "$linuxdeploy_tools" \
"https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/latest/download/linuxdeploy-plugin-qt-${linuxdeploy_arch}.AppImage"
wget -P "$linuxdeploy_tools" \
"https://github.com/AppImage/appimagetool/releases/latest/download/appimagetool-${linuxdeploy_arch}.AppImage"
chmod +x "$linuxdeploy_tools"/*
elif [ "$RUNNER_ARCH" == "ARM64" ]; then
appimage_arch="aarch64"
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"/*
else
echo "Unknown runner architecture: $RUNNER_ARCH"
exit 1
fi
echo "appimagetool architecture is $appimage_arch"
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
@@ -221,14 +257,14 @@ jobs:
with:
python-version: ${{ matrix.python-version }}
check-latest: true
if: matrix.id == 'windows-win7-amd64'
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 != 'windows-win7-amd64'
if: matrix.id != 'windows7-amd64'
- name: Verify runner and Python architecture
env:
EXPECTED_ARCHITECTURE: ${{ matrix.python-architecture }}
@@ -268,7 +304,7 @@ jobs:
uses: LorenEteval/setup-pyside6-win7@v1
with:
pyside6-version: ${{ matrix.pyside6-version }}
if: matrix.id == 'windows-win7-amd64'
if: matrix.id == 'windows7-amd64'
- name: Install Python build and runtime dependencies
run: |
python -c "import sys; print(sys.version)"
@@ -304,7 +340,7 @@ jobs:
)
PY
if [[ "${{ matrix.id }}" != "windows-win7-amd64" ]]; then
if [[ "${{ matrix.id }}" != "windows7-amd64" ]]; then
if [[ "$RUNNER_OS" == "Windows" ]]; then
pyside_binary_policy="--only-binary=PySide6-Essentials,PySide6-Addons"
else
@@ -322,8 +358,7 @@ jobs:
fi
python -m pip install -r .binary-requirements.txt
python -m pip install "Nuitka==${NUITKA_VERSION}" imageio requests
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: Install patched Go for Windows7
@@ -339,87 +374,97 @@ jobs:
echo "GOROOT=${go_root}" >> $GITHUB_ENV
echo "CGO_ENABLED=1" >> $GITHUB_ENV
echo "${go_root}\\bin" >> $GITHUB_PATH
if: matrix.id == 'windows-win7-amd64'
- name: Build Windows7 bindings from source with patched Go
if: matrix.id == 'windows7-amd64'
- name: Set up Go for Windows10
uses: actions/setup-go@v7
with:
go-version: "1.26"
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: |
command -v go
$ErrorActionPreference = 'Stop'
$PSNativeCommandUseErrorActionPreference = $true
Write-Output "Go executable: $((Get-Command go -ErrorAction Stop).Source)"
go version
go env GOROOT GOOS GOARCH CGO_ENABLED
command -v gcc
Write-Output "GCC executable: $((Get-Command gcc -ErrorAction Stop).Source)"
gcc -dumpmachine
short_temp="$RUNNER_TEMP/w7"
mkdir -p "$short_temp"
short_temp="$(cygpath -w "$short_temp")"
export TMPDIR="$short_temp"
export TEMP="$short_temp"
export TMP="$short_temp"
$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
}
python - <<'PY'
import os
import tempfile
Write-Output "Configured temporary directory: $temporaryDirectory"
python -c "import tempfile; print(f'Python temporary directory: {tempfile.gettempdir()}')"
print(f'Configured temporary directory: {os.environ["TEMP"]}')
print(f'Python temporary directory: {tempfile.gettempdir()}')
PY
python -m pip install \
"setuptools>=68" \
wheel \
"cmake>=3.15" \
python -m pip install `
"setuptools>=68" `
wheel `
"cmake>=3.15" `
"pybind11>=3.0.1,<3.1"
rm -rf win7-wheels
mkdir win7-wheels
bindings=(
"Xray-core==${XRAY_CORE_VERSION}"
"hysteria2==${HYSTERIA2_VERSION}"
"tun2socks==${TUN2SOCKS_VERSION}"
$wheelDirectory = Join-Path $temporaryDirectory 'wheels'
New-Item -ItemType Directory -Force -Path $wheelDirectory | Out-Null
$bindings = @(
'Xray-core'
'hysteria2'
'tun2socks'
)
for binding in "${bindings[@]}"
do
python -m pip wheel \
--no-cache-dir \
--no-build-isolation \
--no-deps \
--no-binary=:all: \
--wheel-dir win7-wheels \
"${binding}"
done
foreach ($binding in $bindings) {
python -m pip wheel `
--no-cache-dir `
--no-build-isolation `
--no-deps `
--no-binary=:all: `
--wheel-dir $wheelDirectory `
$binding
}
python - <<'PY'
from pathlib import Path
$wheels = @(
Get-ChildItem -LiteralPath $wheelDirectory -Filter '*.whl' -File |
Sort-Object Name
)
Write-Output 'Locally built wheels:'
wheels = sorted(Path('win7-wheels').glob('*.whl'))
print('Locally built wheels:')
foreach ($wheel in $wheels) {
Write-Output " $($wheel.Name)"
}
for wheel in wheels:
print(f' {wheel.name}')
$unexpectedWheels = @($wheels | Where-Object Name -NotLike '*win_amd64.whl')
if len(wheels) != 3 or any('win_amd64.whl' not in wheel.name for wheel in wheels):
raise SystemExit('expected exactly three locally built win_amd64 wheels')
PY
if ($wheels.Count -ne 3 -or $unexpectedWheels.Count -ne 0) {
throw 'Expected exactly three locally built win_amd64 wheels'
}
python -m pip install \
--no-index \
--force-reinstall \
--no-deps \
win7-wheels/*.whl
if: matrix.id == 'windows-win7-amd64'
- name: Install Windows7-compatible Hysteria1 wheel
$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==${HYSTERIA_VERSION}"
if: matrix.id == 'windows-win7-amd64'
hysteria
if: matrix.id == 'windows7-amd64' || matrix.id == 'windows10-amd64'
- name: Install supported binding wheels
run: |
if [[ "$RUNNER_OS" == "Windows" ]]; then
@@ -432,12 +477,12 @@ jobs:
${binary_policy} \
--no-cache-dir \
--no-deps \
"Xray-core==${XRAY_CORE_VERSION}" \
"hysteria==${HYSTERIA_VERSION}" \
"hysteria2==${HYSTERIA2_VERSION}" \
"tun2socks==${TUN2SOCKS_VERSION}"
if: matrix.id != 'windows-win7-amd64'
- name: Verify Windows binding imports
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
@@ -448,22 +493,113 @@ jobs:
# 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 Qt WebEngine imports
if: runner.os != 'Windows'
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"
LINUX_APPIMAGE_BUILDER: ${{ matrix.linux-appimage-builder }}
run: |
if [[ -n "${{ matrix.windows-compatibility }}" ]]; then
export WIN_VER_COMPATIBLE="${{ matrix.windows-compatibility }}"
fi
python Deploy.py
- 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
@@ -477,10 +613,19 @@ jobs:
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 []),
}
)
@@ -568,6 +713,6 @@ jobs:
with:
identifier: LorenEteval.Furious
release-tag: ${{ github.ref_name }}
installers-regex: '^Furious-.*-(?:windows-amd64|windows11-arm64)\.zip$'
installers-regex: '^Furious-.*-(?:windows10-amd64|windows11-arm64)\.zip$'
max-versions-to-keep: 5
token: ${{ secrets.WINGET_TOKEN }}