v3.2.1 #28
Workflow file for this run
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
| # ============================================================================= | |
| # MCCOutlet Build Workflow | |
| # ============================================================================= | |
| # Builds, packages, and optionally signs/notarizes MCCOutlet for | |
| # Linux and macOS. Windows is not supported (uldaq requires libusb + POSIX). | |
| # | |
| # Features: | |
| # - Multi-platform builds (Linux, macOS) | |
| # - Qt6 integration | |
| # - Automatic liblsl and uldaq fetch | |
| # - CPack packaging | |
| # - macOS code signing and notarization (on release) | |
| # ============================================================================= | |
| name: Build | |
| on: | |
| push: | |
| branches: [main, master, dev] | |
| tags: ['v*'] | |
| pull_request: | |
| branches: [main, master] | |
| release: | |
| types: [published] | |
| workflow_dispatch: | |
| env: | |
| BUILD_TYPE: Release | |
| jobs: | |
| # =========================================================================== | |
| # Build Job - Multi-platform builds | |
| # =========================================================================== | |
| build: | |
| name: ${{ matrix.config.name }} | |
| runs-on: ${{ matrix.config.os }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| config: | |
| - { name: "Ubuntu 22.04", os: ubuntu-22.04 } | |
| - { name: "Ubuntu 24.04", os: ubuntu-24.04 } | |
| - { name: "macOS", os: macos-14 } | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| # ----------------------------------------------------------------------- | |
| # Install CMake 3.28+ (Ubuntu 22.04 ships with 3.22) | |
| # ----------------------------------------------------------------------- | |
| - name: Install CMake | |
| if: runner.os == 'Linux' | |
| uses: lukka/get-cmake@latest | |
| # ----------------------------------------------------------------------- | |
| # Install system dependencies | |
| # ----------------------------------------------------------------------- | |
| - name: Install Linux dependencies | |
| if: runner.os == 'Linux' | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y \ | |
| autoconf automake libtool \ | |
| libusb-1.0-0-dev \ | |
| libgl1-mesa-dev libxkbcommon-dev libxcb-cursor0 | |
| - name: Install macOS dependencies | |
| if: runner.os == 'macOS' | |
| run: | | |
| brew install autoconf automake libtool libusb | |
| # ----------------------------------------------------------------------- | |
| # Install Qt6 | |
| # ----------------------------------------------------------------------- | |
| - name: Install Qt | |
| uses: jurplel/install-qt-action@v4 | |
| with: | |
| version: '6.8.*' | |
| cache: true | |
| # ----------------------------------------------------------------------- | |
| # Configure | |
| # ----------------------------------------------------------------------- | |
| - name: Configure CMake | |
| run: > | |
| cmake -S . -B build | |
| -DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }} | |
| -DCMAKE_INSTALL_PREFIX=${{ github.workspace }}/install | |
| ${{ matrix.config.cmake_extra }} | |
| # ----------------------------------------------------------------------- | |
| # Build | |
| # ----------------------------------------------------------------------- | |
| - name: Build | |
| run: cmake --build build --config ${{ env.BUILD_TYPE }} --parallel | |
| # ----------------------------------------------------------------------- | |
| # Install | |
| # ----------------------------------------------------------------------- | |
| - name: Install | |
| run: cmake --install build --config ${{ env.BUILD_TYPE }} | |
| # ----------------------------------------------------------------------- | |
| # Test CLI | |
| # ----------------------------------------------------------------------- | |
| - name: Test CLI (Linux) | |
| if: runner.os == 'Linux' | |
| run: ./install/bin/MCCOutletCLI --help | |
| - name: Test CLI (macOS) | |
| if: runner.os == 'macOS' | |
| run: ./install/MCCOutletCLI --help | |
| # ----------------------------------------------------------------------- | |
| # Package | |
| # ----------------------------------------------------------------------- | |
| - name: Package | |
| run: cpack -C ${{ env.BUILD_TYPE }} | |
| working-directory: build | |
| # ----------------------------------------------------------------------- | |
| # Upload Artifacts | |
| # ----------------------------------------------------------------------- | |
| - name: Upload Artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: package-${{ matrix.config.os }} | |
| path: | | |
| build/*.tar.gz | |
| build/*.deb | |
| if-no-files-found: ignore | |
| # =========================================================================== | |
| # macOS Signing and Notarization (Release only) | |
| # =========================================================================== | |
| sign-macos: | |
| name: Sign & Notarize (macOS) | |
| needs: build | |
| if: github.event_name == 'release' | |
| runs-on: macos-14 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Download macOS Artifact | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: package-macos-14 | |
| path: packages | |
| - name: Extract Package | |
| run: | | |
| cd packages | |
| tar -xzf *.tar.gz | |
| # Move contents out of versioned subdirectory to packages/ | |
| SUBDIR=$(ls -d MCCOutlet-*/ | head -1) | |
| mv "$SUBDIR"/* . | |
| rmdir "$SUBDIR" | |
| ls -la | |
| # ----------------------------------------------------------------------- | |
| # Install Apple Certificates | |
| # ----------------------------------------------------------------------- | |
| - name: Install Apple Certificates | |
| env: | |
| MACOS_CERTIFICATE: ${{ secrets.PROD_MACOS_CERTIFICATE }} | |
| MACOS_CERTIFICATE_PWD: ${{ secrets.PROD_MACOS_CERTIFICATE_PWD }} | |
| run: | | |
| # Create temporary keychain with random password | |
| KEYCHAIN_PATH=$RUNNER_TEMP/build.keychain | |
| MACOS_CI_KEYCHAIN_PWD=$(openssl rand -base64 32) | |
| security create-keychain -p "$MACOS_CI_KEYCHAIN_PWD" $KEYCHAIN_PATH | |
| security default-keychain -s $KEYCHAIN_PATH | |
| security set-keychain-settings -lut 21600 $KEYCHAIN_PATH | |
| security unlock-keychain -p "$MACOS_CI_KEYCHAIN_PWD" $KEYCHAIN_PATH | |
| # Import certificate | |
| CERTIFICATE_PATH=$RUNNER_TEMP/build_certificate.p12 | |
| echo -n "$MACOS_CERTIFICATE" | base64 --decode -o $CERTIFICATE_PATH | |
| security import $CERTIFICATE_PATH -P "$MACOS_CERTIFICATE_PWD" -k $KEYCHAIN_PATH -A -t cert -f pkcs12 | |
| rm $CERTIFICATE_PATH | |
| # Allow codesign to access keychain | |
| security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$MACOS_CI_KEYCHAIN_PWD" $KEYCHAIN_PATH | |
| security list-keychain -d user -s $KEYCHAIN_PATH | |
| # Extract identity name and export to environment | |
| IDENTITY=$(security find-identity -v -p codesigning $KEYCHAIN_PATH | grep "Developer ID Application" | head -1 | awk -F'"' '{print $2}') | |
| echo "APPLE_CODE_SIGN_IDENTITY_APP=$IDENTITY" >> $GITHUB_ENV | |
| # ----------------------------------------------------------------------- | |
| # Setup Notarization Credentials | |
| # ----------------------------------------------------------------------- | |
| - name: Setup Notarization | |
| env: | |
| NOTARIZATION_APPLE_ID: ${{ secrets.PROD_MACOS_NOTARIZATION_APPLE_ID }} | |
| NOTARIZATION_PWD: ${{ secrets.PROD_MACOS_NOTARIZATION_PWD }} | |
| NOTARIZATION_TEAM_ID: ${{ secrets.PROD_MACOS_NOTARIZATION_TEAM_ID }} | |
| run: | | |
| xcrun notarytool store-credentials "notarize-profile" \ | |
| --apple-id "$NOTARIZATION_APPLE_ID" \ | |
| --password "$NOTARIZATION_PWD" \ | |
| --team-id "$NOTARIZATION_TEAM_ID" | |
| echo "APPLE_NOTARIZE_KEYCHAIN_PROFILE=notarize-profile" >> $GITHUB_ENV | |
| # ----------------------------------------------------------------------- | |
| # Sign and Notarize | |
| # ----------------------------------------------------------------------- | |
| - name: Sign and Notarize | |
| env: | |
| ENTITLEMENTS_FILE: ${{ github.workspace }}/app.entitlements | |
| run: | | |
| # Sign GUI app bundle (--deep handles all nested code including lsl.framework) | |
| APP_PATH=$(find packages -name "*.app" -type d | head -1) | |
| if [[ -n "$APP_PATH" ]]; then | |
| ./scripts/sign_and_notarize.sh "$APP_PATH" --notarize | |
| fi | |
| # Sign CLI and its bundled lsl.framework + libusb | |
| CLI_PATH=$(find packages -name "MCCOutletCLI" -type f | head -1) | |
| if [[ -n "$CLI_PATH" ]]; then | |
| CLI_DIR=$(dirname "$CLI_PATH") | |
| mkdir -p "$CLI_DIR/Frameworks" | |
| # ----------------------------------------------------------------- | |
| # Bundle libusb next to the CLI, exactly like lsl.framework. | |
| # | |
| # The CLI links libusb by an absolute Homebrew path. Once the CLI | |
| # is Developer-ID signed with the hardened runtime it is subject to | |
| # library validation and may only load dylibs signed by Apple or | |
| # the same Team ID. Homebrew's libusb bottle is signed by a | |
| # different team, so dyld refuses it unconditionally and the signed | |
| # CLI is unrunnable on every machine. Fix: copy libusb in, normalize | |
| # its install name to @rpath (resolved via the CLI's existing | |
| # @executable_path/Frameworks rpath), repoint the CLI, and sign it | |
| # with our identity BEFORE the CLI (dependency before dependent). | |
| # ----------------------------------------------------------------- | |
| LIBUSB_DEST="$CLI_DIR/Frameworks/libusb-1.0.0.dylib" | |
| if [[ ! -f "$LIBUSB_DEST" ]]; then | |
| # Prefer the copy macdeployqt already placed in the app bundle, | |
| # otherwise fall back to the reference the CLI currently links | |
| # (brew prefix differs: arm64 /opt/homebrew, x86_64 /usr/local). | |
| APP_LIBUSB=$(find packages -path "*.app/Contents/Frameworks/libusb-1.0.0.dylib" -type f | head -1) | |
| if [[ -n "$APP_LIBUSB" ]]; then | |
| cp "$APP_LIBUSB" "$LIBUSB_DEST" | |
| else | |
| cp "$(otool -L "$CLI_PATH" | awk '/libusb-1\.0\.0\.dylib/{print $1; exit}')" "$LIBUSB_DEST" | |
| fi | |
| chmod u+w "$LIBUSB_DEST" | |
| fi | |
| install_name_tool -id @rpath/libusb-1.0.0.dylib "$LIBUSB_DEST" | |
| # Repoint the CLI from the absolute brew path to the bundled copy. | |
| CLI_LIBUSB_REF=$(otool -L "$CLI_PATH" | awk '/libusb-1\.0\.0\.dylib/{print $1; exit}') | |
| if [[ -n "$CLI_LIBUSB_REF" && "$CLI_LIBUSB_REF" != "@rpath/libusb-1.0.0.dylib" ]]; then | |
| install_name_tool -change "$CLI_LIBUSB_REF" @rpath/libusb-1.0.0.dylib "$CLI_PATH" | |
| fi | |
| # Sign bundled dependencies first (must precede the dependent CLI). | |
| if [[ -d "$CLI_DIR/Frameworks/lsl.framework" ]]; then | |
| codesign --force --sign "$APPLE_CODE_SIGN_IDENTITY_APP" --options runtime \ | |
| "$CLI_DIR/Frameworks/lsl.framework" | |
| fi | |
| codesign --force --sign "$APPLE_CODE_SIGN_IDENTITY_APP" --options runtime \ | |
| "$LIBUSB_DEST" | |
| ./scripts/sign_and_notarize.sh "$CLI_PATH" --notarize | |
| # Guardrail: a signed CLI that still links an absolute brew dylib is | |
| # dead on arrival under library validation. Fail the build instead. | |
| if otool -L "$CLI_PATH" | grep -qE '/(opt/homebrew|usr/local)/'; then | |
| echo "ERROR: signed CLI links non-bundled dylibs:" | |
| otool -L "$CLI_PATH" | |
| exit 1 | |
| fi | |
| fi | |
| # ----------------------------------------------------------------------- | |
| # Repackage | |
| # ----------------------------------------------------------------------- | |
| - name: Repackage | |
| run: | | |
| cd packages | |
| # Remove original unsigned package | |
| rm -f *.tar.gz | |
| # Get project version from CMakeLists.txt (match the project() block, not cmake_minimum_required) | |
| VERSION=$(grep -A1 'project(MCCOutlet' ../CMakeLists.txt | grep VERSION | sed 's/.*VERSION \([0-9.]*\).*/\1/') | |
| ARCH=$(uname -m) | |
| echo "Detected version: $VERSION, arch: $ARCH" | |
| # Create signed package | |
| tar -cvzf "MCCOutlet-${VERSION}-macOS_${ARCH}-signed.tar.gz" \ | |
| MCCOutlet.app MCCOutletCLI Frameworks | |
| echo "Created package:" | |
| ls -la *.tar.gz | |
| - name: Upload Signed Package | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: package-macos-signed | |
| path: packages/*-signed.tar.gz | |
| - name: Upload to Release | |
| if: github.event_name == 'release' | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| files: packages/*-signed.tar.gz | |
| # =========================================================================== | |
| # Upload unsigned packages to release | |
| # =========================================================================== | |
| release: | |
| name: Upload to Release | |
| needs: build | |
| if: github.event_name == 'release' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Download All Artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| path: artifacts | |
| - name: Upload to Release | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| files: | | |
| artifacts/package-ubuntu-*/*.tar.gz | |
| artifacts/**/*.deb |