Files
LorenEteval_Furious/.github/workflows/deploy-pypi.yml
T
Loren Eteval 9fdbcf1bde Fix workflow
Signed-off-by: Loren Eteval <loren.eteval@proton.me>
2026-08-23 18:18:22 +08:00

574 lines
20 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) 2024–present 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: Github Deploy
on: [ push, pull_request ]
defaults:
run:
shell: bash
jobs:
build-distribution:
name: Build distribution
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- name: Install libegl1
run: |
sudo apt update && sudo apt install -y libegl1
- name: Set up Python
uses: actions/setup-python@v7
with:
python-version: "3.12"
check-latest: true
- name: Download Python dependencies & latest asset files
run: |
python -c "import sys; print(sys.version)"
python -m pip install --upgrade pip
python -m pip install setuptools wheel
python -m pip install -r requirements.txt
python -m pip install requests
python Deploy.py --download
- name: Build a binary wheel and a source tarball
run: |
python setup.py sdist bdist_wheel
- name: Store the distribution packages
uses: actions/upload-artifact@v7
with:
name: python-package-distributions
path: |
dist/*.tar.gz
dist/*.whl
publish-to-pypi:
name: Publish Python distribution to PyPI
if: startsWith(github.ref, 'refs/tags/') # only publish to PyPI on tag pushes
needs:
- build-distribution
- deploy-binaries
runs-on: ubuntu-latest
environment:
name: deploy-pypi
url: https://pypi.org/p/Furious-GUI
permissions:
id-token: write # IMPORTANT: mandatory for trusted publishing
steps:
- name: Download all the dists
uses: actions/download-artifact@v8
with:
name: python-package-distributions
path: dist/
- name: Publish distribution to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
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: windows-win7-amd64
os: windows-2025
python-version: "3.13"
python-architecture: x64
pyside6-version: "6.8.3"
windows-compatibility: windows
windows-architecture: amd64
- id: windows-arm64
os: windows-11-vs2026-arm
python-version: "3.13"
python-architecture: arm64
pyside6-version: "6.11.2"
windows-compatibility: ""
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:
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:
- 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
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
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
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 == 'windows-win7-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'
- 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 == 'windows-win7-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-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 }}" != "windows-win7-amd64" ]]; then
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} \
"PySide6-Essentials==${{ matrix.pyside6-version }}" \
"PySide6-Addons==${{ matrix.pyside6-version }}"
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==${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: |
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 == '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
go env GOROOT GOOS GOARCH CGO_ENABLED
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
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
wheels = sorted(Path('win7-wheels').glob('*.whl'))
print('Locally built wheels:')
for wheel in wheels:
print(f' {wheel.name}')
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
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
run: |
python -m pip install \
--only-binary=hysteria \
--no-cache-dir \
--no-deps \
"hysteria==${HYSTERIA_VERSION}"
if: matrix.id == 'windows-win7-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==${XRAY_CORE_VERSION}" \
"hysteria==${HYSTERIA_VERSION}" \
"hysteria2==${HYSTERIA2_VERSION}" \
"tun2socks==${TUN2SOCKS_VERSION}"
if: matrix.id != 'windows-win7-amd64'
- name: Verify Windows binding 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')
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 - <<'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
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
github-release:
name: Upload to GitHub Release
needs:
- deploy-binaries
- publish-to-pypi
runs-on: ubuntu-latest
permissions:
contents: write # IMPORTANT: mandatory for making GitHub Releases
id-token: write # IMPORTANT: mandatory for sigstore
steps:
- name: Download all the dists
uses: actions/download-artifact@v8
with:
pattern: binary-distributions-*
merge-multiple: true
path: dist/
- name: Create GitHub Release
env:
GITHUB_TOKEN: ${{ github.token }}
run: >-
gh release create
'${{ github.ref_name }}'
--repo '${{ github.repository }}'
--notes ""
--generate-notes
--title 'Furious ${{ github.ref_name }}'
- name: Upload artifact to GitHub Release
env:
GITHUB_TOKEN: ${{ github.token }}
# Upload to GitHub Release using the `gh` CLI.
run: >-
gh release upload
'${{ github.ref_name }}' dist/**
--repo '${{ github.repository }}'
publish-winget:
name: Publish to WinGet
needs:
- github-release
runs-on: windows-latest
steps:
- uses: vedantmgoyal9/winget-releaser@main
with:
identifier: LorenEteval.Furious
release-tag: ${{ github.ref_name }}
installers-regex: '^Furious-.*-(?:windows-amd64|windows11-arm64)\.zip$'
max-versions-to-keep: 5
token: ${{ secrets.WINGET_TOKEN }}