Build Executable (on Release) #46
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 Executable (on Release) | |
| on: | |
| release: | |
| types: [published] | |
| workflow_dispatch: | |
| env: | |
| PYINSTALLER_VERSION: v6.11.1 # Match 'requirements-build-pyinstaller.txt' | |
| jobs: | |
| build: | |
| runs-on: ${{ matrix.os }} | |
| permissions: write-all | |
| env: | |
| MACOSX_DEPLOYMENT_TARGET: 11.0 # Lowest version compatible with PySide 6.7 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| # 32 bit python is not included as PySide6 is not compatible | |
| include: | |
| - platform: windows | |
| os: windows-latest | |
| architecture: x64 | |
| upx_suffix: win64 | |
| exe_suffix: .exe | |
| - platform: linux | |
| os: ubuntu-latest | |
| architecture: x64 | |
| upx_suffix: amd64_linux | |
| exe_suffix: '' | |
| - platform: macos | |
| os: macos-15-intel | |
| architecture: x64 | |
| upx_suffix: '' # UPX is problematic with macOS | |
| exe_suffix: '.app' | |
| - platform: macos | |
| os: macos-latest | |
| architecture: arm64 | |
| upx_suffix: '' | |
| exe_suffix: .app | |
| steps: | |
| - name: Checkout partial repository | |
| if: github.ref_type == 'tag' | |
| uses: actions/checkout@v4 | |
| - name: Checkout full repository | |
| if: github.ref_type != 'tag' | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Set up Python 3.11 ${{ matrix.architecture }} | |
| uses: actions/setup-python@v4 | |
| with: | |
| python-version: "3.11" | |
| architecture: ${{ matrix.architecture }} | |
| - name: Create virtual environment | |
| shell: bash | |
| run: python -m venv .venv | |
| - name: Create build folder | |
| shell: bash | |
| run: | | |
| rm -rf build | |
| mkdir build | |
| - name: Update components | |
| shell: bash | |
| run: | | |
| if [[ "${{ runner.os }}" == "Windows" ]]; then | |
| source .venv/Scripts/activate | |
| else | |
| source .venv/bin/activate | |
| fi | |
| python -m pip install --upgrade pip wheel | |
| deactivate | |
| - name: Build PyInstaller Bootloader | |
| shell: bash | |
| run: | | |
| if [[ "${{ runner.os }}" == "Windows" ]]; then | |
| source .venv/Scripts/activate | |
| else | |
| source .venv/bin/activate | |
| fi | |
| echo "--- Cloning PyInstaller ${{ env.PYINSTALLER_VERSION }} ---" | |
| git clone --branch ${{ env.PYINSTALLER_VERSION }} --depth 1 https://github.com/pyinstaller/pyinstaller.git build/pyinstaller | |
| echo "--- Building Bootloader ---" | |
| cd build/pyinstaller/bootloader | |
| python ./waf distclean all | |
| cd ../.. # Back to build directory | |
| cd .. # Back to root | |
| echo "--- Installing Custom PyInstaller from build directory ---" | |
| pip install ./build/pyinstaller | |
| deactivate | |
| - name: Install dependencies | |
| shell: bash | |
| run: | | |
| if [[ "${{ runner.os }}" == "Windows" ]]; then | |
| source .venv/Scripts/activate | |
| else | |
| source .venv/bin/activate | |
| fi | |
| echo "--- Installing main requirements ---" | |
| python -m pip install --upgrade -r requirements.txt | |
| echo "--- Installing build requirements ---" | |
| python -m pip install --upgrade -r requirements-build-pyinstaller.txt | |
| deactivate | |
| - name: Install Legacy macOS Dependencies | |
| if: matrix.platform == 'macos' && matrix.architecture == 'x64' | |
| shell: bash | |
| run: | | |
| source .venv/bin/activate | |
| echo "Attempting to manually fetch macOS 10.9+ compatible wheels for dependencies..." | |
| mkdir -p manual_wheels | |
| # Iterate over each requirement | |
| grep -vE '^\s*#|^\s*$' requirements.txt | while read -r line; do | |
| req_line=$(echo "$line" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//') | |
| if [[ -z "$req_line" ]]; then continue; fi | |
| echo "Checking compatibility for: $req_line" | |
| python -m pip download \ | |
| --dest manual_wheels \ | |
| --platform macosx_10_9_x86_64 \ | |
| --only-binary=:all: \ | |
| --no-deps \ | |
| "$req_line" || echo " > Could not force 10.9 binary for '$req_line'." | |
| done | |
| # Install all found wheels | |
| if ls manual_wheels/*.whl 1> /dev/null 2>&1; then | |
| echo "Installing downloaded wheels:" | |
| ls -l manual_wheels | |
| python -m pip install manual_wheels/*.whl --force-reinstall | |
| else | |
| echo "No force-compatible wheels found." | |
| fi | |
| deactivate | |
| - name: Get version details from tag | |
| if: github.ref_type == 'tag' | |
| shell: python | |
| run: | | |
| import os | |
| tag_name = os.environ['GITHUB_REF_NAME'] # eg. v2.0.0 | |
| version = tag_name.lstrip('v') # eg. 2.0.0 | |
| print(f'Tag: {tag_name}') | |
| print(f'Version: {version}') | |
| with open(os.environ['GITHUB_ENV'], 'a') as fh_env: | |
| print(f'VERSION={version}', file=fh_env) | |
| - name: Get version details from file | |
| if: github.ref_type != 'tag' | |
| shell: bash | |
| run: | | |
| if [[ "${{ runner.os }}" == "Windows" ]]; then | |
| source .venv/Scripts/activate | |
| else | |
| source .venv/bin/activate | |
| fi | |
| python .github/scripts/get-version.py | |
| deactivate | |
| - name: Set dynamic executable name | |
| id: set_names | |
| shell: bash | |
| run: | | |
| # VERSION env var should be set by previous 'Get version' steps | |
| if [ -z "$VERSION" ]; then | |
| echo "::error::VERSION environment variable not set!" | |
| exit 1 | |
| fi | |
| EXE_BASENAME="MouseTracks-${VERSION}-${{ matrix.platform }}-${{ matrix.architecture }}" | |
| FULL_EXE_NAME="${EXE_BASENAME}${{ matrix.exe_suffix }}" | |
| INSTALLER_BASENAME="${EXE_BASENAME}-setup" | |
| FULL_INSTALLER_NAME="${INSTALLER_BASENAME}${{ matrix.exe_suffix }}" | |
| # Set environment variables for use in later steps of this job | |
| echo "EXE_BASENAME=${EXE_BASENAME}" >> $GITHUB_ENV | |
| echo "FULL_EXE_NAME=${FULL_EXE_NAME}" >> $GITHUB_ENV | |
| echo "INSTALLER_BASENAME=${INSTALLER_BASENAME}" >> $GITHUB_ENV | |
| echo "FULL_INSTALLER_NAME=${FULL_INSTALLER_NAME}" >> $GITHUB_ENV | |
| echo "exe_basename=${EXE_BASENAME}" >> $GITHUB_OUTPUT | |
| echo "full_exe_name=${FULL_EXE_NAME}" >> $GITHUB_OUTPUT | |
| echo "Executable basename: ${EXE_BASENAME}" | |
| echo "Full executable name: ${FULL_EXE_NAME}" | |
| echo "Installer basename: ${INSTALLER_BASENAME}" | |
| echo "Full installer name: ${FULL_INSTALLER_NAME}" | |
| - name: Install UPX | |
| if: matrix.upx_suffix != '' | |
| shell: bash | |
| run: | | |
| if [[ "${{ runner.os }}" == "Windows" ]]; then | |
| UPX_URL="https://github.com/upx/upx/releases/download/v5.0.1/upx-5.0.1-${{ matrix.upx_suffix }}.zip" | |
| curl -L -o upx.zip "$UPX_URL" | |
| unzip -o upx.zip -d upx_extracted | |
| echo "PATH=$(pwd)/upx_extracted/upx-5.0.1-${{ matrix.upx_suffix }};$PATH" >> $GITHUB_ENV | |
| else # Linux | |
| UPX_URL="https://github.com/upx/upx/releases/download/v5.0.1/upx-5.0.1-${{ matrix.upx_suffix }}.tar.xz" | |
| curl -L -o upx.tar.xz "$UPX_URL" | |
| tar -xf upx.tar.xz | |
| echo "PATH=$(pwd)/upx-5.0.1-${{ matrix.upx_suffix }}:$PATH" >> $GITHUB_ENV | |
| fi | |
| - name: Create version.rc | |
| if: runner.os == 'Windows' | |
| shell: bash | |
| run: | | |
| source .venv/Scripts/activate | |
| pyivf-make_version --outfile "build/version.rc" --version "${{ env.VERSION }}" --file-description "MouseTracks ${{ env.VERSION }}" --internal-name "MouseTracks" --legal-copyright "Peter Hunt" --original-filename "${{ env.FULL_EXE_NAME }}" --product-name "MouseTracks ${{ env.VERSION }}" | |
| pyivf-make_version --outfile "build/version-installer.rc" --version "${{ env.VERSION }}" --file-description "MouseTracks ${{ env.VERSION }}" --internal-name "MouseTracks" --legal-copyright "Peter Hunt" --original-filename "${{ env.FULL_INSTALLER_NAME }}" --product-name "MouseTracks ${{ env.VERSION }}" | |
| deactivate | |
| - name: Write public key | |
| if: runner.os == 'Windows' | |
| shell: bash | |
| run: | | |
| source .venv/Scripts/activate | |
| python launch.py --write-public-key | |
| deactivate | |
| - name: Build executable | |
| shell: bash | |
| run: | | |
| if [[ "${{ runner.os }}" == "Windows" ]]; then | |
| source .venv/Scripts/activate | |
| else | |
| source .venv/bin/activate | |
| fi | |
| pyinstaller MouseTracks.spec | |
| deactivate | |
| - name: Sign executable | |
| env: | |
| PRIVATE_SIGNING_KEY: ${{ secrets.PRIVATE_SIGNING_KEY }} | |
| if: runner.os == 'Windows' && env.PRIVATE_SIGNING_KEY != '' | |
| shell: bash | |
| run: | | |
| source .venv/Scripts/activate | |
| echo -n "$PRIVATE_SIGNING_KEY" > keys/private.key | |
| python launch.py --sign-executable "dist/MouseTracks.exe" | |
| python launch.py --sign-executable "dist/${FULL_EXE_NAME}" | |
| rm keys/private.key | |
| deactivate | |
| - name: Build Installer for Windows | |
| if: runner.os == 'Windows' | |
| shell: cmd | |
| run: | | |
| :: Add Inno Setup to PATH (GitHub runners have it installed by default) | |
| set "PATH=%PATH%;C:\Program Files (x86)\Inno Setup 6" | |
| set "SOURCE_BASE_NAME=dist\%EXE_BASENAME%" | |
| set "DEST_BASE_NAME=dist\%INSTALLER_BASENAME%" | |
| echo --- Building Installer --- | |
| echo Source Base: %SOURCE_BASE_NAME% | |
| echo Dest Base: %DEST_BASE_NAME% | |
| :: Run ISCC | |
| ISCC.exe /DMyAppVersion="${{ env.VERSION }}" /DMySourceBaseName="%SOURCE_BASE_NAME%" /DMyDestinationBaseName="%DEST_BASE_NAME%" "MouseTracks.iss" | |
| :: Verify it was created | |
| if exist "%DEST_BASE_NAME%.exe" ( | |
| echo Installer created successfully. | |
| echo INSTALLER_FILENAME=%DEST_BASE_NAME%.exe >> %GITHUB_ENV% | |
| ) else ( | |
| echo ::error::Installer creation failed! | |
| exit /b 1 | |
| ) | |
| - name: Package macOS App | |
| if: matrix.platform == 'macos' | |
| shell: bash | |
| run: | | |
| cd dist | |
| ZIP_NAME="${FULL_EXE_NAME%.*}.zip" | |
| echo "Zipping $FULL_EXE_NAME into $ZIP_NAME..." | |
| ditto -c -k --keepParent "$FULL_EXE_NAME" "$ZIP_NAME" | |
| echo "FULL_EXE_NAME=$ZIP_NAME" >> $GITHUB_ENV | |
| - name: Attach Main Executable to Release | |
| if: github.event_name == 'release' | |
| uses: softprops/action-gh-release@v1 | |
| with: | |
| files: dist/${{ env.FULL_EXE_NAME }} | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.BUILD_TOKEN }} | |
| - name: Attach Installer to Release | |
| if: github.event_name == 'release' && runner.os == 'Windows' | |
| uses: softprops/action-gh-release@v1 | |
| with: | |
| files: dist/${{ env.FULL_INSTALLER_NAME }} | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.BUILD_TOKEN }} | |
| - name: Upload Executable as Artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: MouseTracks-${{ env.VERSION }}-${{ matrix.platform }}-${{ matrix.architecture }} | |
| path: dist/${{ env.FULL_EXE_NAME }} | |
| - name: Upload Installer as Artifact | |
| if: runner.os == 'Windows' | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: MouseTracks-${{ env.VERSION }}-${{ matrix.platform }}-${{ matrix.architecture }}-setup | |
| path: dist/${{ env.FULL_INSTALLER_NAME }} |