Add PyInstaller release workflow
This commit is contained in:
parent
fefe3a1567
commit
c5373aa9e0
9 changed files with 320 additions and 3 deletions
166
.github/workflows/release.yml
vendored
Normal file
166
.github/workflows/release.yml
vendored
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
name: Release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
build-windows:
|
||||
name: Build Windows
|
||||
if: github.ref == 'refs/heads/main'
|
||||
runs-on: windows-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout Source Code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Get Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.12"
|
||||
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
python -m pip install -e ".[build]"
|
||||
|
||||
- name: Test
|
||||
run: python -m unittest discover -s tests
|
||||
|
||||
- name: Build
|
||||
run: python -m PyInstaller --clean --noconfirm "packaging/NTE History Exporter.spec"
|
||||
|
||||
- name: Smoke Test
|
||||
run: .\dist\nte-history-exporter.exe --help
|
||||
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: windows
|
||||
path: dist/nte-history-exporter.exe
|
||||
|
||||
build-linux:
|
||||
name: Build Linux
|
||||
if: github.ref == 'refs/heads/main'
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout Source Code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install libpcap
|
||||
run: sudo apt-get update && sudo apt-get install -y libpcap-dev
|
||||
|
||||
- name: Get Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.12"
|
||||
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
python -m pip install -e ".[build]"
|
||||
|
||||
- name: Test
|
||||
run: python -m unittest discover -s tests
|
||||
|
||||
- name: Build
|
||||
run: python -m PyInstaller --clean --noconfirm "packaging/NTE History Exporter.spec"
|
||||
|
||||
- name: Smoke Test
|
||||
run: ./dist/nte-history-exporter --help
|
||||
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: linux
|
||||
path: dist/nte-history-exporter
|
||||
|
||||
build-macos:
|
||||
name: Build Macos
|
||||
if: github.ref == 'refs/heads/main'
|
||||
runs-on: macos-15-intel
|
||||
|
||||
steps:
|
||||
- name: Checkout Source Code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install libpcap
|
||||
run: brew update && brew install libpcap
|
||||
|
||||
- name: Get Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.12"
|
||||
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
python -m pip install -e ".[build]"
|
||||
|
||||
- name: Test
|
||||
run: python -m unittest discover -s tests
|
||||
|
||||
- name: Build
|
||||
run: python -m PyInstaller --clean --noconfirm "packaging/NTE History Exporter.spec"
|
||||
|
||||
- name: Smoke Test
|
||||
run: ./dist/nte-history-exporter --help
|
||||
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: macos
|
||||
path: dist/nte-history-exporter
|
||||
|
||||
release:
|
||||
name: Release
|
||||
if: github.ref == 'refs/heads/main'
|
||||
needs: [build-windows, build-linux, build-macos]
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout Source Code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Get package version
|
||||
id: package-version
|
||||
run: |
|
||||
version="$(python -c "import tomllib; print(tomllib.load(open('pyproject.toml', 'rb'))['project']['version'])")"
|
||||
echo "version=$version" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Download Artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
|
||||
- name: Rename
|
||||
run: |
|
||||
version="${{ steps.package-version.outputs.version }}"
|
||||
cp windows/nte-history-exporter.exe nte-history-exporter.exe
|
||||
zip nte-history-exporter-v${version}-x86_64-pc-windows-msvc.zip nte-history-exporter.exe
|
||||
cp linux/nte-history-exporter nte-history-exporter
|
||||
chmod +x nte-history-exporter
|
||||
cp nte-history-exporter nte-history-exporter-linux
|
||||
zip nte-history-exporter-v${version}-x86_64-unknown-linux-gnu.zip nte-history-exporter
|
||||
cp macos/nte-history-exporter nte-history-exporter
|
||||
chmod +x nte-history-exporter
|
||||
cp nte-history-exporter nte-history-exporter-macos
|
||||
zip nte-history-exporter-v${version}-x86_64-apple-darwin.zip nte-history-exporter
|
||||
|
||||
- name: Release
|
||||
uses: marvinpinto/action-automatic-releases@latest
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
automatic_release_tag: v${{ steps.package-version.outputs.version }}
|
||||
prerelease: false
|
||||
title: v${{ steps.package-version.outputs.version }} Release
|
||||
files: |
|
||||
nte-history-exporter.exe
|
||||
nte-history-exporter-linux
|
||||
nte-history-exporter-macos
|
||||
nte-history-exporter-v${{ steps.package-version.outputs.version }}-x86_64-pc-windows-msvc.zip
|
||||
nte-history-exporter-v${{ steps.package-version.outputs.version }}-x86_64-unknown-linux-gnu.zip
|
||||
nte-history-exporter-v${{ steps.package-version.outputs.version }}-x86_64-apple-darwin.zip
|
||||
45
docs/packaging.md
Normal file
45
docs/packaging.md
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
# Packaging
|
||||
|
||||
## Windows local build
|
||||
|
||||
Build the Windows console executable with:
|
||||
|
||||
```powershell
|
||||
.\tools\build-windows.ps1
|
||||
```
|
||||
|
||||
The output is:
|
||||
|
||||
```text
|
||||
dist\nte-history-exporter.exe
|
||||
```
|
||||
|
||||
Run live capture from an elevated PowerShell prompt:
|
||||
|
||||
```powershell
|
||||
.\dist\nte-history-exporter.exe --live
|
||||
```
|
||||
|
||||
## Packet capture runtime
|
||||
|
||||
Windows live capture defaults to `--capture-backend auto`, which tries an installed system Npcap runtime first, then the built-in raw-socket backend. If `--capture-backend libpcap` is specified, raw fallback is disabled. If `--capture-backend raw` is specified, only the Windows raw-socket backend is used.
|
||||
|
||||
Npcap is Windows-only; Linux and macOS use the system libpcap runtime.
|
||||
|
||||
The GitHub Actions release does not download or bundle Npcap. The Windows executable can use a user's installed Npcap runtime when present, then falls back to raw sockets. A future pktmon backend can provide the same kind of no-extra-install Windows path that stardb-exporter uses.
|
||||
|
||||
Npcap is not committed or bundled by default. Windows users who want the Npcap backend should install Npcap separately. Users who do not install Npcap can still use the Windows raw-socket fallback.
|
||||
|
||||
## Cross-platform CI shape
|
||||
|
||||
Use one GitHub Actions job per OS. PyInstaller must build on the target platform, so Windows produces `.exe`, macOS produces a macOS binary, and Linux produces a Linux binary.
|
||||
|
||||
This repository has a manual release workflow at:
|
||||
|
||||
```text
|
||||
.github\workflows\release.yml
|
||||
```
|
||||
|
||||
It builds Windows, Linux, and macOS artifacts, then publishes a `v<version>` GitHub release with raw binaries and versioned zip files.
|
||||
|
||||
Linux runners install the libpcap runtime for parity with live-capture usage, but the build itself uses `ctypes` and does not link against libpcap at package time.
|
||||
47
packaging/NTE History Exporter.spec
Normal file
47
packaging/NTE History Exporter.spec
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
# -*- mode: python ; coding: utf-8 -*-
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
block_cipher = None
|
||||
root = Path(SPECPATH).parent
|
||||
|
||||
|
||||
a = Analysis(
|
||||
[str(root / "src" / "nte_history_exporter" / "cli.py")],
|
||||
pathex=[str(root / "src")],
|
||||
binaries=[],
|
||||
datas=[(str(root / "mappings"), "mappings")],
|
||||
hiddenimports=[],
|
||||
hookspath=[],
|
||||
hooksconfig={},
|
||||
runtime_hooks=[],
|
||||
excludes=[],
|
||||
win_no_prefer_redirects=False,
|
||||
win_private_assemblies=False,
|
||||
cipher=block_cipher,
|
||||
noarchive=False,
|
||||
)
|
||||
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
|
||||
|
||||
exe = EXE(
|
||||
pyz,
|
||||
a.scripts,
|
||||
a.binaries,
|
||||
a.zipfiles,
|
||||
a.datas,
|
||||
[],
|
||||
name="nte-history-exporter",
|
||||
debug=False,
|
||||
bootloader_ignore_signals=False,
|
||||
strip=False,
|
||||
upx=True,
|
||||
upx_exclude=[],
|
||||
runtime_tmpdir=None,
|
||||
console=True,
|
||||
disable_windowed_traceback=False,
|
||||
argv_emulation=False,
|
||||
target_arch=None,
|
||||
codesign_identity=None,
|
||||
entitlements_file=None,
|
||||
)
|
||||
|
|
@ -5,6 +5,9 @@ description = "Cross-platform Neverness to Everness pull-history exporter."
|
|||
requires-python = ">=3.10"
|
||||
dependencies = []
|
||||
|
||||
[project.optional-dependencies]
|
||||
build = ["pyinstaller>=6.0"]
|
||||
|
||||
[project.scripts]
|
||||
nte-history-exporter = "nte_history_exporter.cli:main"
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,10 @@ def build_parser() -> argparse.ArgumentParser:
|
|||
"--capture-backend",
|
||||
choices=["auto", "libpcap", "raw"],
|
||||
default="auto",
|
||||
help="live capture backend; auto prefers Npcap/libpcap and falls back to raw sockets on Windows",
|
||||
help=(
|
||||
"live capture backend; auto prefers Npcap/libpcap and falls back to raw sockets on Windows "
|
||||
"(default: %(default)s)"
|
||||
),
|
||||
)
|
||||
parser.add_argument("--copy-clipboard", action="store_true", help="copy a single live export to clipboard")
|
||||
parser.add_argument("--debug", action="store_true", help="also write research CSVs next to the JSON exports")
|
||||
|
|
|
|||
|
|
@ -103,7 +103,8 @@ def _load_library() -> ctypes.CDLL:
|
|||
npcap_dir = _windows_npcap_directory()
|
||||
if npcap_dir.is_dir() and hasattr(os, "add_dll_directory"):
|
||||
dll_directory = os.add_dll_directory(str(npcap_dir))
|
||||
candidates.extend([str(npcap_dir / "wpcap.dll"), "wpcap.dll"])
|
||||
candidates.append(str(npcap_dir / "wpcap.dll"))
|
||||
candidates.append("wpcap.dll")
|
||||
elif sys.platform == "darwin":
|
||||
candidates.extend(
|
||||
[
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
PROJECT_ROOT = Path(__file__).resolve().parents[2]
|
||||
PROJECT_ROOT = Path(getattr(sys, "_MEIPASS", Path(__file__).resolve().parents[2]))
|
||||
MAPPINGS_DIR = PROJECT_ROOT / "mappings"
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ from nte_history_exporter.live_capture.libpcap import (
|
|||
DLT_LOOP,
|
||||
DLT_RAW,
|
||||
_extract_ipv4_frame,
|
||||
_load_library,
|
||||
LibpcapUnavailable,
|
||||
)
|
||||
from nte_history_exporter.live_capture.windows_raw import parse_ipv4_packet
|
||||
|
|
@ -138,6 +139,29 @@ class BoundaryExportTests(unittest.TestCase):
|
|||
|
||||
raw_capture.assert_not_called()
|
||||
|
||||
def test_windows_libpcap_load_prefers_system_then_path(self):
|
||||
system_dir = Path("C:/Windows/System32/Npcap")
|
||||
loaded = Mock()
|
||||
with (
|
||||
patch("nte_history_exporter.live_capture.libpcap.sys.platform", "win32"),
|
||||
patch("nte_history_exporter.live_capture.libpcap._windows_npcap_directory", return_value=system_dir),
|
||||
patch("nte_history_exporter.live_capture.libpcap.Path.is_dir", return_value=True),
|
||||
patch("nte_history_exporter.live_capture.libpcap.os.add_dll_directory", return_value=Mock()),
|
||||
patch(
|
||||
"nte_history_exporter.live_capture.libpcap.ctypes.CDLL",
|
||||
side_effect=[OSError("system missing"), loaded],
|
||||
) as cdll,
|
||||
):
|
||||
self.assertIs(_load_library(), loaded)
|
||||
|
||||
self.assertEqual(
|
||||
[call.args[0] for call in cdll.call_args_list],
|
||||
[
|
||||
str(system_dir / "wpcap.dll"),
|
||||
"wpcap.dll",
|
||||
],
|
||||
)
|
||||
|
||||
def test_libpcap_link_layers_extract_ipv4_packets(self):
|
||||
ip_packet = bytes.fromhex("4500001c0000000040110000c0000201c6336402") + bytes(8)
|
||||
ethernet = bytes(12) + bytes.fromhex("0800") + ip_packet
|
||||
|
|
|
|||
27
tools/build-windows.ps1
Normal file
27
tools/build-windows.ps1
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
Set-Location (Split-Path -Parent $PSScriptRoot)
|
||||
|
||||
$python = Join-Path $PWD '.venv\Scripts\python.exe'
|
||||
if (-not (Test-Path $python)) {
|
||||
python -m venv .venv
|
||||
}
|
||||
|
||||
& $python -m pip install --upgrade pip
|
||||
& $python -m pip install -e '.[build]'
|
||||
|
||||
& $python -m PyInstaller --clean --noconfirm 'packaging\NTE History Exporter.spec'
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "PyInstaller failed with exit code $LASTEXITCODE"
|
||||
}
|
||||
|
||||
$exe = Join-Path $PWD 'dist\nte-history-exporter.exe'
|
||||
if (-not (Test-Path $exe)) {
|
||||
throw "Expected build output was not created: $exe"
|
||||
}
|
||||
|
||||
Write-Host "Built $exe"
|
||||
& $exe --help
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "Built executable smoke test failed with exit code $LASTEXITCODE"
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue