Skip to content

Commit e02aacc

Browse files
committed
Install script
1 parent 0081f26 commit e02aacc

4 files changed

Lines changed: 178 additions & 3 deletions

File tree

build_release.ps1

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
param(
2+
[string]$PythonBin = "python"
3+
)
4+
5+
$ErrorActionPreference = "Stop"
6+
7+
function Log {
8+
param([string]$Message)
9+
$ts = Get-Date -Format "HH:mm:ss"
10+
Write-Host ""
11+
Write-Host "[$ts] $Message"
12+
}
13+
14+
function Run {
15+
param(
16+
[string]$Exe,
17+
[string[]]$Args
18+
)
19+
Write-Host "+ $Exe $($Args -join ' ')"
20+
& $Exe @Args
21+
if ($LASTEXITCODE -ne 0) {
22+
throw "Command failed with exit code $LASTEXITCODE: $Exe $($Args -join ' ')"
23+
}
24+
}
25+
26+
$RootDir = Split-Path -Parent $MyInvocation.MyCommand.Path
27+
$SpecFile = Join-Path $RootDir "SerialUI.spec"
28+
29+
if (-not (Test-Path -Path $SpecFile -PathType Leaf)) {
30+
throw "Missing spec file: $SpecFile"
31+
}
32+
33+
Log "Installing/upgrading PyInstaller tooling"
34+
Run $PythonBin @("-m", "pip", "install", "--upgrade", "pip", "pyinstaller")
35+
36+
Log "Building standalone executable with PyInstaller"
37+
Push-Location $RootDir
38+
try {
39+
$oldNoUserSite = $env:PYTHONNOUSERSITE
40+
$env:PYTHONNOUSERSITE = "1"
41+
try {
42+
Run $PythonBin @("-m", "PyInstaller", "--clean", "--noconfirm", "SerialUI.spec")
43+
}
44+
finally {
45+
if ($null -eq $oldNoUserSite) {
46+
Remove-Item Env:PYTHONNOUSERSITE -ErrorAction SilentlyContinue
47+
} else {
48+
$env:PYTHONNOUSERSITE = $oldNoUserSite
49+
}
50+
}
51+
52+
Get-ChildItem -Path "dist" | Format-Table -AutoSize
53+
}
54+
finally {
55+
Pop-Location
56+
}
57+
58+
Log "Done"
59+
Write-Host "Standalone app: $RootDir\dist\SerialUI"

build_release.sh

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
# End-to-end release/build script for SerialUI + line_parsers.
5+
#
6+
# What it does:
7+
# 1) Installs/upgrades build tooling
8+
# 2) Builds C extensions in helpers/line_parsers
9+
# 3) Builds wheel/sdist for helpers package (line_parsers)
10+
# 4) Runs twine check
11+
# 5) Reinstalls the freshly built wheel locally
12+
# 6) Optionally uploads to PyPI
13+
# 7) Builds standalone app via PyInstaller spec
14+
#
15+
# Usage:
16+
# ./build_release.sh
17+
#
18+
# Optional environment variables:
19+
# PYTHON_BIN=python3
20+
# PACKAGE_NAME=line_parsers
21+
# UPLOAD_PYPI=1
22+
# TWINE_USERNAME=__token__
23+
# TWINE_PASSWORD=<your-token>
24+
# TWINE_REPOSITORY_URL=https://upload.pypi.org/legacy/
25+
26+
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
27+
HELPERS_DIR="${ROOT_DIR}/helpers"
28+
PYTHON_BIN="${PYTHON_BIN:-python3}"
29+
PACKAGE_NAME="${PACKAGE_NAME:-line_parsers}"
30+
UPLOAD_PYPI="${UPLOAD_PYPI:-0}"
31+
TWINE_REPOSITORY_URL="${TWINE_REPOSITORY_URL:-https://upload.pypi.org/legacy/}"
32+
33+
log() {
34+
printf '\n[%s] %s\n' "$(date +'%H:%M:%S')" "$*"
35+
}
36+
37+
require_dir() {
38+
local d="$1"
39+
if [[ ! -d "${d}" ]]; then
40+
echo "Required directory does not exist: ${d}" >&2
41+
exit 1
42+
fi
43+
}
44+
45+
require_file() {
46+
local f="$1"
47+
if [[ ! -f "${f}" ]]; then
48+
echo "Required file does not exist: ${f}" >&2
49+
exit 1
50+
fi
51+
}
52+
53+
run() {
54+
echo "+ $*"
55+
"$@"
56+
}
57+
58+
require_dir "${HELPERS_DIR}"
59+
require_file "${ROOT_DIR}/SerialUI.spec"
60+
require_file "${HELPERS_DIR}/setup.py"
61+
62+
log "Installing/upgrading build tools"
63+
run "${PYTHON_BIN}" -m pip install --upgrade pip build twine pyinstaller pybind11 setuptools wheel
64+
65+
log "Building C-accelerated parsers"
66+
pushd "${HELPERS_DIR}" >/dev/null
67+
run "${PYTHON_BIN}" setup.py clean --all || true
68+
run rm -rf build dist ./*.egg-info .eggs
69+
run "${PYTHON_BIN}" setup.py build_ext --inplace -v
70+
71+
log "Building wheel and source distribution"
72+
# Use the current environment (we installed build deps above),
73+
# so builds don't fail in restricted/offline environments.
74+
run "${PYTHON_BIN}" -m build --no-isolation
75+
run "${PYTHON_BIN}" -m twine check dist/*
76+
run ls -lh dist
77+
78+
WHEEL_FILE="$(ls -1t dist/*.whl | head -n 1)"
79+
if [[ -z "${WHEEL_FILE}" ]]; then
80+
echo "No wheel file found in ${HELPERS_DIR}/dist" >&2
81+
exit 1
82+
fi
83+
84+
log "Reinstalling built wheel locally (${WHEEL_FILE})"
85+
run "${PYTHON_BIN}" -m pip uninstall -y "${PACKAGE_NAME}" || true
86+
run "${PYTHON_BIN}" -m pip install --no-deps --force-reinstall --no-cache-dir "${WHEEL_FILE}"
87+
88+
if [[ "${UPLOAD_PYPI}" == "1" ]]; then
89+
log "Uploading distributions to PyPI"
90+
export TWINE_USERNAME="${TWINE_USERNAME:-__token__}"
91+
: "${TWINE_PASSWORD:?Set TWINE_PASSWORD when UPLOAD_PYPI=1}"
92+
run "${PYTHON_BIN}" -m twine upload --repository-url "${TWINE_REPOSITORY_URL}" dist/*
93+
else
94+
log "Skipping PyPI upload (set UPLOAD_PYPI=1 to enable)"
95+
fi
96+
popd >/dev/null
97+
98+
log "Building standalone executable with PyInstaller"
99+
pushd "${ROOT_DIR}" >/dev/null
100+
# Ignore user-site packages (e.g. obsolete pathlib backport in ~/.local).
101+
run env PYTHONNOUSERSITE=1 "${PYTHON_BIN}" -m PyInstaller --clean --noconfirm SerialUI.spec
102+
run ls -lh dist
103+
popd >/dev/null
104+
105+
log "Done"
106+
echo "Wheel artifacts: ${HELPERS_DIR}/dist"
107+
echo "Standalone app: ${ROOT_DIR}/dist/SerialUI"

helpers/MANIFEST.in

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
include pyproject.toml
2+
include setup.py
3+
4+
recursive-include line_parsers *.py
5+
recursive-include line_parsers *.cpp *.cc *.cxx
6+
recursive-include line_parsers *.h *.hpp
7+
recursive-include line_parsers *.txt *.md LICENSE

helpers/setup.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212
DEBUG = False # Set to True for debugging flags, False for production flags
1313
BUILD_PROFILE = False # Set to True to generate profiling data, then include it with -fprofile-use in follow up build
1414

15-
this_dir = os.path.dirname(__file__)
15+
this_dir = os.path.dirname(os.path.abspath(__file__))
16+
# Ensure relative source paths behave the same no matter where setup.py is invoked from.
17+
os.chdir(this_dir)
1618

1719
is_windows = sys.platform.startswith('win')
1820
is_macos = sys.platform == "darwin"
@@ -179,13 +181,13 @@
179181

180182
simple_ext = Extension(
181183
"line_parsers.simple_parser",
182-
sources=[os.path.join("line_parsers", "simple_parser.cpp")],
184+
sources=[os.path.join(this_dir, "line_parsers", "simple_parser.cpp")],
183185
**common,
184186
)
185187

186188
header_ext = Extension(
187189
"line_parsers.header_parser",
188-
sources=[os.path.join("line_parsers", "header_parser.cpp")],
190+
sources=[os.path.join(this_dir, "line_parsers", "header_parser.cpp")],
189191
**common,
190192
)
191193

0 commit comments

Comments
 (0)