Fix workflow

Signed-off-by: Loren Eteval <loren.eteval@proton.me>
This commit is contained in:
Loren Eteval
2026-08-23 18:18:22 +08:00
parent 6ec3ef25dc
commit 9fdbcf1bde
4 changed files with 173 additions and 45 deletions
+148 -30
View File
@@ -80,7 +80,6 @@ jobs:
deploy-binaries:
name: Deploy ${{ matrix.id }} on ${{ matrix.os }} Python ${{ matrix.python-version }}
runs-on: ${{ matrix.os }}
continue-on-error: true
strategy:
fail-fast: false
matrix:
@@ -135,6 +134,7 @@ jobs:
GO_WIN7_VERSION: "1.26.7"
NUITKA_VERSION: "4.1.3"
WIX_VERSION: "6.0.2"
WIX_INSTALLER_SHA256: "A8A5CC7443353CEF3AB900C60CD7A3A5EE601746319D104AC7B12AD0CED2345C"
steps:
- uses: actions/checkout@v7
- name: Install macOS dependencies
@@ -154,38 +154,67 @@ jobs:
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
echo "QMAKE=$(which qmake6)" >> $GITHUB_ENV
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
linuxdeploy_arch="x86_64"
appimage_arch="x86_64"
elif [ "$RUNNER_ARCH" == "ARM64" ]; then
linuxdeploy_arch="aarch64"
appimage_arch="aarch64"
else
echo "Unknown runner architecture: $RUNNER_ARCH"
exit 1
fi
echo "linuxdeploy architecture is $linuxdeploy_arch"
echo "appimagetool architecture is $appimage_arch"
linuxdeploy_name=".linuxdeploy-bin"
mkdir ${linuxdeploy_name}
echo "${PWD}/${linuxdeploy_name}" >> $GITHUB_PATH
wget -P ${linuxdeploy_name} https://github.com/linuxdeploy/linuxdeploy/releases/latest/download/linuxdeploy-${linuxdeploy_arch}.AppImage
wget -P ${linuxdeploy_name} https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/latest/download/linuxdeploy-plugin-qt-${linuxdeploy_arch}.AppImage
wget -P ${linuxdeploy_name} https://github.com/AppImage/appimagetool/releases/latest/download/appimagetool-${linuxdeploy_arch}.AppImage
chmod +x ${linuxdeploy_name}/*
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: |
dotnet tool install --global wix --version "${WIX_VERSION}"
echo "$HOME/.dotnet/tools" >> $GITHUB_PATH
$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 "WixToolset.UI.wixext/${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
@@ -247,8 +276,8 @@ jobs:
python -m pip install setuptools wheel
python - <<'PY'
from pathlib import Path
import re
from pathlib import Path
excluded = {
'pyside6-addons',
@@ -258,6 +287,7 @@ jobs:
'hysteria2',
'tun2socks',
}
requirements = []
for line in Path('requirements.txt').read_text(encoding='utf-8').splitlines():
@@ -292,7 +322,9 @@ jobs:
fi
python -m pip install -r .binary-requirements.txt
python -m pip install "Nuitka==${NUITKA_VERSION}" 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
run: |
@@ -309,6 +341,11 @@ jobs:
echo "${go_root}\\bin" >> $GITHUB_PATH
if: matrix.id == 'windows-win7-amd64'
- name: Build Windows7 bindings from source with patched Go
env:
CC: gcc
CXX: g++
CMAKE_BUILD_PARALLEL_LEVEL: "2"
GOPROXY: https://proxy.golang.org,direct
run: |
command -v go
go version
@@ -316,16 +353,45 @@ jobs:
command -v gcc
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"
python - <<'PY'
import os
import tempfile
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" \
"pybind11>=3.0.1,<3.1"
rm -rf win7-wheels
mkdir win7-wheels
python -m pip wheel \
--no-cache-dir \
--no-deps \
--no-binary=Xray-core,hysteria2,tun2socks \
--wheel-dir win7-wheels \
"Xray-core==${XRAY_CORE_VERSION}" \
"hysteria2==${HYSTERIA2_VERSION}" \
bindings=(
"Xray-core==${XRAY_CORE_VERSION}"
"hysteria2==${HYSTERIA2_VERSION}"
"tun2socks==${TUN2SOCKS_VERSION}"
)
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
python - <<'PY'
from pathlib import Path
@@ -371,26 +437,78 @@ jobs:
"hysteria2==${HYSTERIA2_VERSION}" \
"tun2socks==${TUN2SOCKS_VERSION}"
if: matrix.id != 'windows-win7-amd64'
- name: Verify Windows binding architecture and imports
- name: Verify Windows binding imports
run: |
python VerifyWindowsArchitecture.py \
--expected "${{ matrix.windows-architecture }}" \
--bindings
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')
PY
if: runner.os == 'Windows'
- 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 packaged Windows binaries
env:
EXPECTED_WINDOWS_ARCHITECTURE: ${{ matrix.windows-architecture }}
run: |
python VerifyWindowsArchitecture.py \
--expected "${{ matrix.windows-architecture }}" \
--tree "Furious-Deploy/Furious.dist"
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')
binaries = sorted(
{
*root.rglob('*.exe'),
*root.rglob('*.pyd'),
}
)
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
+23 -13
View File
@@ -90,6 +90,7 @@ if PLATFORM == 'Windows':
f'--disable-console '
f'--assume-yes-for-downloads '
f'--include-package-data=Furious '
f'--nofollow-import-to=numpy '
f'{NUITKA_BINARY_VERSION_OPTION}'
f'--windows-icon-from-ico=\"Icons/png/rocket-takeoff-window.png\" '
f'--force-stdout-spec=^%TEMP^%/_Furious_Enable_Stdout '
@@ -107,6 +108,7 @@ elif PLATFORM == 'Darwin':
f'--disable-console '
f'--assume-yes-for-downloads '
f'--include-package-data=Furious '
f'--nofollow-import-to=numpy '
f'{NUITKA_BINARY_VERSION_OPTION}'
f'--macos-create-app-bundle '
f'--macos-app-icon=\"Icons/png/rocket-takeoff-window.png\" '
@@ -121,6 +123,7 @@ elif PLATFORM == 'Linux':
f'--disable-console '
f'--assume-yes-for-downloads '
f'--include-package-data=Furious '
f'--nofollow-import-to=numpy '
f'{NUITKA_BINARY_VERSION_OPTION}'
f'Furious '
f'--output-dir=\"{ROOT_DIR / DEPLOY_DIR_NAME}\"'
@@ -186,14 +189,11 @@ elif PLATFORM == 'Linux':
)
LINUX_APPIMAGE_FILENAME = f'{ARTIFACT_NAME}.AppImage'
LINUX_CREATE_APPIMAGE_CMD = (
f'linuxdeploy-{PLATFORM_MACHINE}.AppImage '
f'--appdir AppDir '
f'-d \"{LINUX_APP_DIR / LINUX_DESKTOP_FILE}\" '
f'-i \"{LINUX_APP_DIR / LINUX_ICON_FILE}\" '
f'--plugin qt '
f'--output appimage'
)
LINUX_CREATE_APPIMAGE_CMD = [
f'appimagetool-{PLATFORM_MACHINE}.AppImage',
LINUX_APP_DIR,
ROOT_DIR / LINUX_APPIMAGE_FILENAME,
]
LINUX_DEBIAN_DIR = ROOT_DIR / 'debian'
LINUX_DEB_FILENAME = f'{ARTIFACT_NAME}.deb'
@@ -721,18 +721,28 @@ def main():
raise
appRun = LINUX_APP_DIR / 'AppRun'
with open(appRun, 'w', encoding='utf-8') as file:
file.write(
'#!/bin/sh\n'
'APPDIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"\n'
f'exec "$APPDIR/usr/bin/{APPLICATION_NAME}.bin" "$@"\n'
)
appRun.chmod(0o755)
logger.info('generating AppImage')
# https://github.com/linuxdeploy/linuxdeploy-plugin-appimage/blob/master/README.md
os.environ['LDAI_OUTPUT'] = LINUX_APPIMAGE_FILENAME
os.environ['LINUXDEPLOY_OUTPUT_VERSION'] = APPLICATION_VERSION
# Nuitka's standalone directory already owns its complete Qt runtime.
# appimagetool packages it as-is instead of harvesting Qt a second time.
os.environ['ARCH'] = PLATFORM_MACHINE
try:
result = runExternalCommand(
runExternalCommand(
LINUX_CREATE_APPIMAGE_CMD,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
shell=True,
check=True,
)
except subprocess.CalledProcessError as err:
+1 -1
View File
@@ -20,7 +20,7 @@ dependencies = [
"segno",
"icmplib",
"mss",
"zxing-cpp<3",
"zxing-cpp",
"pillow",
"Xray-core",
"hysteria",
+1 -1
View File
@@ -5,7 +5,7 @@ pybase64
segno
icmplib
mss
zxing-cpp<3
zxing-cpp
pillow
Xray-core
hysteria