Build and Release #4
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name: Build and Release | |
on: | |
workflow_dispatch: | |
inputs: | |
version: | |
description: 'Release version (e.g., v1.0.0)' | |
required: true | |
release_notes: | |
description: 'Release notes' | |
required: true | |
jobs: | |
build: | |
strategy: | |
fail-fast: false | |
matrix: | |
os: [windows-latest, ubuntu-latest, macos-latest] | |
include: | |
- os: windows-latest | |
output_name: VoiceCommand.exe | |
asset_name: VoiceCommand-Windows.exe | |
data_separator: ";" | |
- os: ubuntu-latest | |
output_name: VoiceCommand | |
asset_name: VoiceCommand-Linux | |
data_separator: ":" | |
- os: macos-latest | |
output_name: VoiceCommand | |
asset_name: VoiceCommand-MacOS | |
data_separator: ":" | |
runs-on: ${{ matrix.os }} | |
steps: | |
- name: Checkout code | |
uses: actions/checkout@v4 | |
- name: Set up Python | |
uses: actions/setup-python@v5 | |
with: | |
python-version: '3.10' | |
cache: 'pip' | |
- name: Install dependencies | |
if: matrix.os != 'ubuntu-latest' | |
run: | | |
python -m pip install --upgrade pip | |
pip install -r requirements.txt | |
pip install pyinstaller | |
# Print Python environment info for debugging | |
- name: Print Python environment info | |
run: | | |
python --version | |
pip --version | |
pip list | |
echo "Python executable: $(which python || where python)" | |
# Install system dependencies for Linux | |
- name: Install Linux system dependencies | |
if: matrix.os == 'ubuntu-latest' | |
run: | | |
sudo apt-get update | |
# Install comprehensive list of audio libraries and dependencies | |
sudo apt-get install -y \ | |
portaudio19-dev \ | |
python3-pyaudio \ | |
libxcb-xinerama0 \ | |
libasound2-dev \ | |
libpulse-dev \ | |
libsndfile1-dev \ | |
libdbus-1-dev \ | |
libglib2.0-dev \ | |
pkg-config \ | |
build-essential \ | |
ffmpeg | |
# Special handling for PyAudio on macOS | |
- name: Install PyAudio on macOS | |
if: matrix.os == 'macos-latest' | |
run: | | |
brew install portaudio | |
pip install pyaudio | |
# Special handling for PyAudio on Windows | |
- name: Install PyAudio on Windows | |
if: matrix.os == 'windows-latest' | |
shell: pwsh | |
run: | | |
# Windows typically doesn't need special handling for PyAudio if pre-built wheels are available | |
pip install pyaudio | |
# Special handling for requirements on Linux | |
- name: Install dependencies on Linux | |
if: matrix.os == 'ubuntu-latest' | |
run: | | |
# Link to the system-installed PyAudio | |
python -m pip install --upgrade pip | |
# Create modified requirements file without PyAudio | |
grep -v "pyaudio" requirements.txt > requirements_no_pyaudio.txt | |
# Install other requirements | |
pip install -r requirements_no_pyaudio.txt | |
pip install pyinstaller | |
echo "Using system-installed PyAudio" | |
python -c "import pyaudio; print('PyAudio version:', pyaudio.__version__)" || { | |
echo "System PyAudio not working. Attempting to compile PyAudio directly." | |
export PYTHONPATH=/usr/lib/python3/dist-packages:$PYTHONPATH | |
# Attempt to compile PyAudio from source with more verbose output | |
pip install --verbose --force-reinstall pyaudio || { | |
echo "Failed to compile PyAudio. Creating a stub module." | |
mkdir -p $(python -c "import site; print(site.getsitepackages()[0])")/pyaudio | |
echo "__version__ = 'stub-0.0.0'" > $(python -c "import site; print(site.getsitepackages()[0])")/pyaudio/__init__.py | |
echo "class PyAudio: pass" >> $(python -c "import site; print(site.getsitepackages()[0])")/pyaudio/__init__.py | |
} | |
} | |
echo "Python environment information:" | |
python -c "import sys; print(sys.path)" | |
# Build with PyInstaller, using the appropriate data separator for each OS | |
- name: List directories (Linux/macOS) | |
if: runner.os != 'Windows' | |
shell: bash | |
run: | | |
echo "Current directory contents:" | |
ls -la | |
echo "Public directory contents:" | |
ls -la public | |
- name: List directories (Windows) | |
if: runner.os == 'Windows' | |
shell: pwsh | |
run: | | |
echo "Current directory contents:" | |
dir | |
echo "Public directory contents:" | |
dir public | |
- name: Build with PyInstaller (Linux) | |
if: runner.os == 'Linux' | |
shell: bash | |
run: | | |
# Create PyAudio stub if needed | |
if ! python -c "import pyaudio" 2>/dev/null; then | |
echo "Creating PyAudio stub because import failed" | |
mkdir -p pyaudio_stub | |
echo "__version__ = 'stub-0.0.0'" > pyaudio_stub/__init__.py | |
echo "class PyAudio: pass" >> pyaudio_stub/__init__.py | |
PYTHONPATH="$PWD:$PYTHONPATH" | |
fi | |
python -m pip install --verbose pyinstaller | |
# Create a stub main script that catches PyAudio errors | |
cat > app_wrapper.py << 'EOL' | |
try: | |
import app | |
except ImportError as e: | |
print(f"Import error: {e}") | |
if "pyaudio" in str(e).lower(): | |
print("PyAudio import failed - creating mock") | |
import sys | |
import types | |
sys.modules['pyaudio'] = types.ModuleType('pyaudio') | |
sys.modules['pyaudio'].__version__ = 'stub-0.0.0' | |
sys.modules['pyaudio'].PyAudio = type('PyAudio', (), {}) | |
import app | |
EOL | |
# Use --hidden-import to ensure audio libraries are included and --exclude to avoid PyAudio if needed | |
pyinstaller --debug=all --onefile \ | |
--add-data "public${{ matrix.data_separator }}public" \ | |
--icon=public/favicon.ico \ | |
--hidden-import=pyaudio \ | |
--hidden-import=speech_recognition \ | |
--hidden-import=typing \ | |
--hidden-import=_portaudio \ | |
--hidden-import=sounddevice \ | |
app_wrapper.py \ | |
--name VoiceCommand | |
echo "Dist directory contents after build:" | |
ls -la dist | |
- name: Build with PyInstaller (macOS) | |
if: runner.os == 'macOS' | |
shell: bash | |
run: | | |
python -m pip install --verbose pyinstaller | |
pyinstaller --onefile --add-data "public${{ matrix.data_separator }}public" --icon=public/favicon.ico --name VoiceCommand app.py | |
echo "Dist directory contents after build:" | |
ls -la dist | |
- name: Build with PyInstaller (Windows) | |
if: runner.os == 'Windows' | |
shell: pwsh | |
run: | | |
python -m pip install --verbose pyinstaller | |
pyinstaller --onefile --add-data "public${{ matrix.data_separator }}public" --icon=public/favicon.ico --name VoiceCommand app.py | |
echo "Dist directory contents after build:" | |
dir dist | |
# Set executable permissions for Linux and macOS | |
- name: Set executable permissions | |
if: matrix.os != 'windows-latest' | |
shell: bash | |
run: | | |
chmod +x dist/${{ matrix.output_name }} | |
# Upload artifacts (separate from release for diagnostic purposes) | |
- name: Upload build artifacts | |
uses: actions/upload-artifact@v4 | |
with: | |
name: ${{ matrix.asset_name }} | |
path: dist/${{ matrix.output_name }} | |
# Create a release with all builds attached | |
create-release: | |
needs: build | |
runs-on: ubuntu-latest | |
if: always() && !contains(needs.build.result, 'cancelled') | |
steps: | |
- name: Download all artifacts | |
uses: actions/download-artifact@v4 | |
with: | |
path: artifacts | |
- name: List downloaded artifacts | |
shell: bash | |
run: | | |
echo "Downloaded artifacts:" | |
find artifacts -type f | sort | |
- name: Prepare release files | |
shell: bash | |
id: prepare_files | |
run: | | |
FILES="" | |
# Check if Windows build succeeded | |
if [ -f "artifacts/VoiceCommand-Windows.exe/VoiceCommand.exe" ]; then | |
FILES="${FILES}artifacts/VoiceCommand-Windows.exe/VoiceCommand.exe\n" | |
echo "Windows build found" | |
else | |
echo "Windows build not found or failed" | |
fi | |
# Check if Linux build succeeded | |
if [ -f "artifacts/VoiceCommand-Linux/VoiceCommand" ]; then | |
FILES="${FILES}artifacts/VoiceCommand-Linux/VoiceCommand\n" | |
echo "Linux build found" | |
else | |
echo "Linux build not found or failed" | |
fi | |
# Check if macOS build succeeded | |
if [ -f "artifacts/VoiceCommand-MacOS/VoiceCommand" ]; then | |
FILES="${FILES}artifacts/VoiceCommand-MacOS/VoiceCommand\n" | |
echo "macOS build found" | |
else | |
echo "macOS build not found or failed" | |
fi | |
# Trim the trailing newline | |
FILES=$(echo -e "${FILES}" | sed -e 's/\\n$//') | |
# Set output for next step | |
echo "FILES<<EOF" >> $GITHUB_OUTPUT | |
echo -e "${FILES}" >> $GITHUB_OUTPUT | |
echo "EOF" >> $GITHUB_OUTPUT | |
- name: Create Release | |
id: create_release | |
uses: softprops/action-gh-release@v1 | |
with: | |
tag_name: ${{ github.event.inputs.version }} | |
name: Release ${{ github.event.inputs.version }} | |
body: ${{ github.event.inputs.release_notes }} | |
draft: false | |
prerelease: false | |
files: ${{ steps.prepare_files.outputs.FILES }} | |
env: | |
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |