Skip to content

Commit bcb92cd

Browse files
committed
feat(build): optimize Linux build script by simplifying installation steps and improving error handling
1 parent e9eb223 commit bcb92cd

File tree

1 file changed

+60
-91
lines changed

1 file changed

+60
-91
lines changed

scripts/build/build_linux.sh

Lines changed: 60 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
#!/usr/bin/env bash
2-
# filepath: /workspaces/pyprophet/scripts/build/build_linux.sh
32
# Build script for PyProphet Linux executable using PyInstaller
43

54
set -euo pipefail
@@ -17,50 +16,30 @@ echo "Using Python version: $PYTHON_VERSION"
1716
if [[ $(echo "$PYTHON_VERSION < 3.11" | bc -l) -eq 1 ]]; then
1817
echo "ERROR: Python 3.11+ is required for building."
1918
echo "Your Python version: $PYTHON_VERSION"
20-
echo ""
21-
echo "Please install Python 3.11 or later:"
22-
echo " sudo apt update"
23-
echo " sudo apt install python3.11 python3.11-venv python3.11-dev"
24-
echo ""
25-
echo "Then run the build with:"
26-
echo " PYTHON=python3.11 bash scripts/build/build_linux.sh"
2719
exit 1
2820
fi
2921

30-
# Install system tools for stripping
22+
# Install system tools
3123
echo "Installing build tools..."
3224
sudo apt-get update -qq
3325
sudo apt-get install -y binutils
3426

3527
# Save the original directory
3628
ORIGINAL_DIR="$(pwd)"
3729

38-
# Clean ALL build artifacts
30+
# Clean build artifacts
3931
echo "Cleaning build artifacts..."
4032
rm -rf build dist pyprophet.spec *.egg-info .eggs
4133
find . -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null || true
4234
find . -type f -name "*.pyc" -delete 2>/dev/null || true
4335
find . -type f -name "*.pyo" -delete 2>/dev/null || true
4436

45-
# Create a clean virtual environment
46-
echo "Creating clean virtual environment..."
47-
VENV_DIR=$(mktemp -d)/build_venv
48-
$PYTHON -m venv "$VENV_DIR"
49-
source "$VENV_DIR/bin/activate"
50-
51-
# Upgrade pip and install build tools
52-
pip install --upgrade pip setuptools wheel build
53-
54-
# Build wheel
55-
echo "Building pyprophet wheel..."
56-
python -m build --wheel --outdir /tmp/pyprophet_wheels
57-
58-
# Install dependencies
59-
echo "Installing dependencies..."
60-
pip install cython numpy pyinstaller
37+
# Install/upgrade build dependencies
38+
$PYTHON -m pip install --upgrade pip setuptools wheel cython numpy pyinstaller
6139

6240
# Parse and install runtime dependencies
63-
python << 'PYEOF'
41+
echo "Installing runtime dependencies..."
42+
$PYTHON << 'PYEOF'
6443
import tomllib
6544
import subprocess
6645
import sys
@@ -74,106 +53,96 @@ for dep in config['project']['dependencies']:
7453
try:
7554
print(f"Installing: {dep}")
7655
subprocess.check_call([sys.executable, '-m', 'pip', 'install', '--no-cache-dir', dep])
77-
except subprocess.CalledProcessError:
78-
pass
56+
except subprocess.CalledProcessError as e:
57+
print(f"Warning: Failed to install {dep}: {e}")
7958
PYEOF
8059

81-
# Install pyprophet from wheel (NOT editable)
82-
echo "Installing pyprophet from wheel..."
83-
pip install --force-reinstall --no-deps /tmp/pyprophet_wheels/pyprophet-*.whl
84-
85-
# Verify installation
86-
echo "Verifying installation..."
87-
python -c "import pyprophet; print(f'PyProphet: {pyprophet.__file__}')"
88-
python -c "import numpy; print(f'NumPy: {numpy.__file__}')"
89-
python -c "import pandas; print('✓ All imports successful')"
60+
# Install the package in editable mode
61+
echo "Installing pyprophet in editable mode..."
62+
$PYTHON -m pip install -e .
9063

91-
# Get site-packages
92-
SITE_PACKAGES=$(python -c "import pyprophet, os; print(os.path.dirname(pyprophet.__file__))")
93-
echo "Site-packages: ${SITE_PACKAGES}"
64+
# Build C extensions in-place
65+
echo "Building C extensions..."
66+
$PYTHON setup.py build_ext --inplace
9467

95-
# Collect binaries
68+
# Collect compiled extension binaries
9669
ADD_BINARY_ARGS=()
97-
for so in "${SITE_PACKAGES}"/pyprophet/scoring/_optimized*.so; do
70+
for so in pyprophet/scoring/_optimized*.so pyprophet/scoring/_optimized*.cpython-*.so; do
9871
if [ -f "$so" ]; then
9972
ADD_BINARY_ARGS+=(--add-binary "$so:pyprophet/scoring")
100-
echo "Found binary: $so"
73+
echo "Adding binary: $so"
10174
fi
10275
done
10376

104-
# Create build directory
105-
mkdir -p "${ORIGINAL_DIR}/dist"
106-
BUILD_DIR=$(mktemp -d)
107-
cd "${BUILD_DIR}"
77+
# Locate xgboost native library
78+
SO_PATH=$($PYTHON - <<'PY'
79+
import importlib, os
80+
try:
81+
m = importlib.import_module("xgboost")
82+
p = os.path.join(os.path.dirname(m.__file__), "lib", "libxgboost.so")
83+
print(p if os.path.exists(p) else "")
84+
except Exception:
85+
print("")
86+
PY
87+
)
88+
if [ -n "$SO_PATH" ]; then
89+
echo "Including xgboost native lib: $SO_PATH"
90+
ADD_BINARY_ARGS+=(--add-binary "$SO_PATH:xgboost/lib")
91+
fi
10892

109-
# Copy PyInstaller files
110-
cp "${ORIGINAL_DIR}/packaging/pyinstaller/run_pyprophet.py" .
111-
mkdir -p hooks
112-
cp "${ORIGINAL_DIR}/packaging/pyinstaller/hooks"/*.py hooks/ 2>/dev/null || true
93+
# Clean previous builds
94+
rm -rf build dist
95+
mkdir -p dist
11396

114-
# Run PyInstaller
115-
echo "Running PyInstaller..."
116-
python -m PyInstaller \
97+
# Run PyInstaller with --onefile (using --collect-all for problematic packages)
98+
echo "Running PyInstaller (onefile mode)..."
99+
$PYTHON -m PyInstaller \
100+
--name pyprophet \
101+
--onefile \
102+
--console \
117103
--clean \
118104
--noconfirm \
119-
--onefile \
120-
--name pyprophet \
121-
--strip \
122105
--log-level INFO \
123-
--additional-hooks-dir hooks \
124-
--exclude-module sphinx \
125-
--exclude-module sphinx_rtd_theme \
126-
--exclude-module pydata_sphinx_theme \
127-
--exclude-module sphinx_copybutton \
128-
--exclude-module sphinx.ext \
129-
--exclude-module alabaster \
130-
--exclude-module babel \
131-
--exclude-module docutils \
132-
--exclude-module mypy \
133-
--exclude-module pytest \
134-
--exclude-module pytest-regtest \
135-
--exclude-module pytest-xdist \
136-
--exclude-module black \
137-
--exclude-module ruff \
106+
--additional-hooks-dir packaging/pyinstaller/hooks \
107+
--hidden-import=pyprophet \
108+
--hidden-import=pyprophet.main \
138109
--collect-submodules pyprophet \
139-
--copy-metadata numpy \
140-
--copy-metadata pandas \
141-
--copy-metadata scipy \
142-
--copy-metadata scikit-learn \
143-
--copy-metadata pyopenms \
110+
--collect-all numpy \
111+
--collect-all pandas \
112+
--collect-all scipy \
113+
--collect-all sklearn \
114+
--collect-all pyopenms \
144115
--copy-metadata duckdb \
145116
--copy-metadata duckdb-extensions \
146117
--copy-metadata duckdb-extension-sqlite-scanner \
118+
--copy-metadata pyopenms \
147119
"${ADD_BINARY_ARGS[@]}" \
148-
run_pyprophet.py
149-
150-
# Move executable
151-
mv dist/pyprophet "${ORIGINAL_DIR}/dist/pyprophet"
152-
cd "${ORIGINAL_DIR}"
153-
154-
# Cleanup
155-
deactivate
156-
rm -rf "$(dirname "$VENV_DIR")" "${BUILD_DIR}" /tmp/pyprophet_wheels
120+
packaging/pyinstaller/run_pyprophet.py
157121

158122
echo "============================================"
159-
echo "Build complete! Single executable at: dist/pyprophet"
123+
echo "Build complete! Executable at: dist/pyprophet"
160124
ls -lh dist/pyprophet
161125
echo "============================================"
162126

163-
# Create archive
127+
# Create compressed archive for distribution
128+
echo "Creating distribution archive..."
164129
cd dist
165130
ARCH=$(uname -m)
166131
ARCHIVE_NAME="pyprophet-linux-${ARCH}.tar.gz"
167132
tar -czf "../${ARCHIVE_NAME}" pyprophet
168133
cd ..
169134

135+
echo "============================================"
170136
echo "Archive created: ${ARCHIVE_NAME}"
137+
echo "============================================"
171138

172-
# Generate checksum
139+
# Generate SHA256 checksum
173140
if command -v sha256sum &> /dev/null; then
174141
sha256sum "${ARCHIVE_NAME}" > "${ARCHIVE_NAME}.sha256"
142+
echo "Checksum: ${ARCHIVE_NAME}.sha256"
175143
cat "${ARCHIVE_NAME}.sha256"
176144
fi
177145

178146
echo ""
179-
echo "Test with: ./dist/pyprophet --help"
147+
echo "To test locally:"
148+
echo " ./dist/pyprophet --help"

0 commit comments

Comments
 (0)