mirror of
https://github.com/LorenEteval/Furious.git
synced 2026-10-06 13:58:01 +03:00
@@ -78,33 +78,63 @@ jobs:
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
|
||||
deploy-binaries:
|
||||
name: Deploy binaries on ${{ matrix.os }} Python ${{ matrix.python-version }}
|
||||
name: Deploy ${{ matrix.id }} on ${{ matrix.os }} Python ${{ matrix.python-version }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
continue-on-error: true
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ ubuntu-22.04, ubuntu-22.04-arm, windows-2025, macos-15-intel, macos-14 ]
|
||||
python-version: [ "3.11", "3.13" ]
|
||||
exclude:
|
||||
- os: ubuntu-22.04
|
||||
python-version: "3.11"
|
||||
- os: windows-2025
|
||||
python-version: "3.11"
|
||||
- os: macos-15-intel
|
||||
python-version: "3.11"
|
||||
- os: macos-14
|
||||
python-version: "3.11"
|
||||
- os: ubuntu-22.04-arm
|
||||
python-version: "3.13"
|
||||
include:
|
||||
- os: windows-2025
|
||||
id: win7
|
||||
- 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:
|
||||
# Last PySide6 version that supports macOS 10.9 & Python 3.11
|
||||
PYSIDE6_LEGACY_VERSION: "6.4.3"
|
||||
PYSIDE6_TARGET_VERSION: "6.8.3"
|
||||
# PySide6 has Windows ARM64 build since 6.9.0
|
||||
PYSIDE6_LATEST_VERSION: "6.9.1"
|
||||
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"
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- name: Install macOS dependencies
|
||||
@@ -149,97 +179,223 @@ jobs:
|
||||
if: runner.os == 'Linux'
|
||||
- name: Install Windows dependencies
|
||||
run: |
|
||||
if [ "$RUNNER_ARCH" == "X64" ]; then
|
||||
toolset_arch="x64"
|
||||
elif [ "$RUNNER_ARCH" == "ARM64" ]; then
|
||||
toolset_arch="arm64"
|
||||
else
|
||||
echo "Unknown runner architecture: $RUNNER_ARCH"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
winget install --id WiXToolset.WiXCLI --version 6.0.2.0 -e --source winget --accept-package-agreements --accept-source-agreements
|
||||
echo "C:/Program Files/WiX Toolset v6.0/bin" >> $GITHUB_PATH
|
||||
echo "C:/Program Files/WiX Toolset v6.0/bin/${toolset_arch}" >> $GITHUB_PATH
|
||||
dotnet tool install --global wix --version "${WIX_VERSION}"
|
||||
echo "$HOME/.dotnet/tools" >> $GITHUB_PATH
|
||||
if: runner.os == 'Windows'
|
||||
- name: Check Windows dependencies
|
||||
run: |
|
||||
wix --version
|
||||
wix extension add WixToolset.UI.wixext/6.0.2
|
||||
wix extension add "WixToolset.UI.wixext/${WIX_VERSION}"
|
||||
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 == 'win7'
|
||||
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 != 'win7'
|
||||
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: ${{ env.PYSIDE6_TARGET_VERSION }}
|
||||
if: matrix.id == 'win7'
|
||||
- name: Download Python dependencies & latest asset files
|
||||
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'
|
||||
from pathlib import Path
|
||||
import re
|
||||
|
||||
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"
|
||||
python -m pip install PySide6-Essentials==6.5.3
|
||||
fi
|
||||
|
||||
python -m pip install -r .binary-requirements.txt
|
||||
python -m pip install "Nuitka==${NUITKA_VERSION}" imageio requests
|
||||
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
|
||||
run: |
|
||||
command -v go
|
||||
go version
|
||||
go env GOROOT GOOS GOARCH CGO_ENABLED
|
||||
command -v gcc
|
||||
gcc -dumpmachine
|
||||
|
||||
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}" \
|
||||
"tun2socks==${TUN2SOCKS_VERSION}"
|
||||
|
||||
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
|
||||
python -m pip install PySide6-Essentials==${PYSIDE6_TARGET_VERSION}
|
||||
binary_policy=""
|
||||
fi
|
||||
python -m pip install -r requirements.txt
|
||||
python -m pip install nuitka imageio requests
|
||||
|
||||
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 architecture and imports
|
||||
run: |
|
||||
python VerifyWindowsArchitecture.py \
|
||||
--expected "${{ matrix.windows-architecture }}" \
|
||||
--bindings
|
||||
if: runner.os == 'Windows'
|
||||
- name: Download latest asset files
|
||||
run: |
|
||||
python Deploy.py --download
|
||||
- name: Set up go
|
||||
uses: actions/setup-go@v7
|
||||
with:
|
||||
go-version: "1.26"
|
||||
check-latest: true
|
||||
if: matrix.id != 'win7'
|
||||
- name: Install go dependencies
|
||||
run: |
|
||||
if [[ "${{ matrix.id }}" == "win7" ]]; then
|
||||
go_name="go-for-win7-windows-amd64"
|
||||
go_file="${go_name}.zip"
|
||||
|
||||
curl -L -o ${go_file} https://github.com/XTLS/go-win7/releases/download/patched-1.26.7/${go_file}
|
||||
7z x ${go_file} -o${go_name} -y
|
||||
|
||||
export GOROOT="$PWD/${go_name}"
|
||||
export CGO_ENABLED=1
|
||||
export PATH="${GOROOT}/bin:$PATH"
|
||||
fi
|
||||
go version
|
||||
python -m pip install "Xray-core >= 1.8.8"
|
||||
python -m pip install "hysteria2 >= 2.0.4"
|
||||
python -m pip install "tun2socks > 2.5.2"
|
||||
- name: Set up go legacy
|
||||
uses: actions/setup-go@v7
|
||||
with:
|
||||
go-version: "1.20"
|
||||
check-latest: true
|
||||
- name: Install go legacy dependencies
|
||||
run: |
|
||||
go version
|
||||
python -m pip install "hysteria > 1.3.5"
|
||||
- name: Run deploy script
|
||||
run: |
|
||||
if [[ "${{ matrix.id }}" == "win7" ]]; then
|
||||
export WIN_VER_COMPATIBLE="windows"
|
||||
if [[ -n "${{ matrix.windows-compatibility }}" ]]; then
|
||||
export WIN_VER_COMPATIBLE="${{ matrix.windows-compatibility }}"
|
||||
fi
|
||||
python Deploy.py
|
||||
- name: Verify packaged Windows binaries
|
||||
run: |
|
||||
python VerifyWindowsArchitecture.py \
|
||||
--expected "${{ matrix.windows-architecture }}" \
|
||||
--tree "Furious-Deploy/Furious.dist"
|
||||
if: runner.os == 'Windows'
|
||||
- name: Store the distribution packages
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: binary-distributions-${{ matrix.os }}-Python${{ matrix.python-version }}
|
||||
name: binary-distributions-${{ matrix.id }}-Python${{ matrix.python-version }}
|
||||
path: |
|
||||
Furious-*.zip
|
||||
Furious-*.msi
|
||||
@@ -294,6 +450,6 @@ jobs:
|
||||
with:
|
||||
identifier: LorenEteval.Furious
|
||||
release-tag: ${{ github.ref_name }}
|
||||
installers-regex: '^Furious-.*\.zip$'
|
||||
installers-regex: '^Furious-.*-(?:windows-amd64|windows11-arm64)\.zip$'
|
||||
max-versions-to-keep: 5
|
||||
token: ${{ secrets.WINGET_TOKEN }}
|
||||
|
||||
@@ -68,6 +68,11 @@
|
||||
- For backend/process/platform work, verify error cleanup and bounded shutdown. For Qt ownership work, follow
|
||||
`Furious/AGENTS.md` and `Furious/Qt/AGENTS.md`, use the `manage-qt-pyside6-lifetimes` skill, and run the relevant
|
||||
lifetime tests.
|
||||
- Windows 7 release bindings that require newer Go runtimes must use locally built wheels produced with the patched
|
||||
`go-win7` toolchain; an ordinary modern PyPI wheel does not establish Windows 7 compatibility. A binding built with an
|
||||
official Go release that still supports Windows 7 may use a verified prebuilt wheel.
|
||||
- Architecture-specific Windows release jobs must verify the active Python, installed native extensions, and packaged
|
||||
executable architecture before publishing uniquely named artifacts.
|
||||
- Review for duplicated state authorities, mutation of persisted configuration during runtime preparation, broad
|
||||
swallowed errors, unbounded waits/caches, generated-file edits without regeneration, and protocol branches that belong
|
||||
in a capability.
|
||||
|
||||
@@ -62,6 +62,21 @@ NUITKA_BINARY_VERSION_OPTION = (
|
||||
|
||||
PLATFORM_MACHINE_LOWER = PLATFORM_MACHINE.casefold()
|
||||
|
||||
if PLATFORM == 'Windows':
|
||||
windowsArchitectures = {
|
||||
'amd64': ('amd64', 'x64'),
|
||||
'x86_64': ('amd64', 'x64'),
|
||||
'arm64': ('arm64', 'arm64'),
|
||||
'aarch64': ('arm64', 'arm64'),
|
||||
}
|
||||
|
||||
try:
|
||||
WINDOWS_ARTIFACT_ARCHITECTURE, WINDOWS_WIX_ARCHITECTURE = windowsArchitectures[
|
||||
PLATFORM_MACHINE_LOWER
|
||||
]
|
||||
except KeyError:
|
||||
raise RuntimeError(f'unsupported Windows architecture: {PLATFORM_MACHINE}')
|
||||
|
||||
if PLATFORM == 'Windows':
|
||||
NUITKA_BINARY_VERSION_OPTION += f'--file-description=\"{APPLICATION_DESCRIPTION}\" '
|
||||
|
||||
@@ -126,10 +141,10 @@ if PLATFORM == 'Windows':
|
||||
|
||||
ARTIFACT_NAME = (
|
||||
f'{APPLICATION_NAME}-{APPLICATION_VERSION}-'
|
||||
f'{winVerCompatible}-{PLATFORM_MACHINE_LOWER}'
|
||||
f'{winVerCompatible}-{WINDOWS_ARTIFACT_ARCHITECTURE}'
|
||||
)
|
||||
|
||||
WIN_UNZIPPED = f'{APPLICATION_NAME}-{APPLICATION_VERSION}-{winVerCompatible}'
|
||||
WIN_UNZIPPED = ARTIFACT_NAME
|
||||
elif PLATFORM == 'Darwin':
|
||||
value = versionToValue(PYSIDE6_VERSION)
|
||||
|
||||
@@ -493,28 +508,6 @@ def main():
|
||||
|
||||
os.makedirs(msifolder, exist_ok=True)
|
||||
|
||||
try:
|
||||
# Requires "heat" in PATH
|
||||
result = runExternalCommand(
|
||||
'heat dir'.split()
|
||||
+ [ROOT_DIR / DEPLOY_DIR_NAME / 'SourceDir']
|
||||
+ '-cg FuriousComponents -dr INSTALLFOLDER -srd -scom -sreg -gg -out'.split()
|
||||
+ [msifolder / 'FuriousFiles.wxs'],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
check=True,
|
||||
)
|
||||
except subprocess.CalledProcessError as err:
|
||||
logger.error(f'run heat failed with returncode {err.returncode}')
|
||||
|
||||
printStandardStream(err.stdout, err.stderr)
|
||||
|
||||
sys.exit(EXIT_FAILURE)
|
||||
else:
|
||||
logger.info(f'run heat success')
|
||||
|
||||
printStandardStream(result.stdout, result.stderr)
|
||||
|
||||
# Create Product.wxs file
|
||||
try:
|
||||
with open(
|
||||
@@ -547,10 +540,15 @@ def main():
|
||||
f' <WixVariable Id=\"WixUILicenseRtf\" Value=\"LICENSE.rtf\" />\n'
|
||||
f'\n'
|
||||
f' <!-- Installation directory -->\n'
|
||||
f' <StandardDirectory Id=\"ProgramFiles64Folder\">\n'
|
||||
f' <StandardDirectory Id=\"ProgramFiles6432Folder\">\n'
|
||||
f' <Directory Id=\"INSTALLFOLDER\" Name=\"{WIN_UNZIPPED}\" />\n'
|
||||
f' </StandardDirectory>\n'
|
||||
f'\n'
|
||||
f' <!-- Files harvested by WiX for the selected architecture -->\n'
|
||||
f' <ComponentGroup Id="FuriousComponents" Directory="INSTALLFOLDER">\n'
|
||||
f' <Files Include="{xml_escape(str(ROOT_DIR / DEPLOY_DIR_NAME / "SourceDir" / "**"))}" />\n'
|
||||
f' </ComponentGroup>\n'
|
||||
f'\n'
|
||||
f' <!-- Features -->\n'
|
||||
f' <Feature Id=\"MainFeature\" Title=\"{APPLICATION_NAME}\" Level=\"1\">\n'
|
||||
f' <ComponentGroupRef Id=\"FuriousComponents\" />\n'
|
||||
@@ -606,8 +604,9 @@ def main():
|
||||
[
|
||||
'wix',
|
||||
'build',
|
||||
msifolder / 'FuriousFiles.wxs',
|
||||
msifolder / 'Product.wxs',
|
||||
'-arch',
|
||||
WINDOWS_WIX_ARCHITECTURE,
|
||||
'-o',
|
||||
ROOT_DIR / f'{ARTIFACT_NAME}.msi',
|
||||
'-ext',
|
||||
|
||||
Reference in New Issue
Block a user