unpinned flutter version #196
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 Applications | |
| on: | |
| push: | |
| branches: | |
| - develop | |
| workflow_dispatch: | |
| env: | |
| STRAVA_CLIENT_ID: ${{ secrets.STRAVA_CLIENT_ID }} | |
| STRAVA_CLIENT_SECRET: ${{ secrets.STRAVA_CLIENT_SECRET }} | |
| INTERVALS_CLIENT_ID: ${{ secrets.INTERVALS_CLIENT_ID }} | |
| INTERVALS_CLIENT_SECRET: ${{ secrets.INTERVALS_CLIENT_SECRET }} | |
| jobs: | |
| prepare: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| version: ${{ steps.set_version.outputs.version }} | |
| steps: | |
| - uses: actions/checkout@v2 | |
| # Download and extract latest SmartSpin2k firmware.bin from GitHub release | |
| - name: Download and extract latest SmartSpin2k firmware | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| set -e | |
| mkdir -p assets | |
| # Get latest release info | |
| release_json=$(curl -s -H "Authorization: token $GITHUB_TOKEN" \ | |
| https://api.github.com/repos/doudar/SmartSpin2k/releases/latest) | |
| # Find .bin.zip asset URL | |
| asset_url=$(echo "$release_json" | grep 'browser_download_url' | grep '.bin.zip' | head -n1 | cut -d '"' -f4) | |
| if [ -z "$asset_url" ]; then | |
| echo "No .bin.zip asset found in latest release!" >&2 | |
| exit 1 | |
| fi | |
| echo "Downloading: $asset_url" | |
| curl -L -o /tmp/firmware.zip "$asset_url" | |
| unzip -o /tmp/firmware.zip -d /tmp/firmware_extracted | |
| if [ ! -f /tmp/firmware_extracted/firmware.bin ]; then | |
| echo "firmware.bin not found in zip!" >&2 | |
| exit 1 | |
| fi | |
| mv /tmp/firmware_extracted/firmware.bin assets/firmware.bin | |
| echo "Firmware placed at assets/firmware.bin" | |
| # Extract version tag and write to assets/version.txt | |
| version_tag=$(echo "$release_json" | grep '"tag_name"' | head -n1 | cut -d '"' -f4) | |
| if [ -z "$version_tag" ]; then | |
| echo "No version tag found in release!" >&2 | |
| exit 1 | |
| fi | |
| echo "$version_tag" > assets/version.txt | |
| echo "Version written to assets/version.txt: $version_tag" | |
| # Commit and push firmware.bin and version.txt to the repository | |
| - name: Commit, push, and open PR for updated firmware and version | |
| if: github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/main' | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| set -e | |
| git config --global user.name "github-actions[bot]" | |
| git config --global user.email "github-actions[bot]@users.noreply.github.com" | |
| git fetch origin | |
| git checkout ${{ github.ref_name }} | |
| git add assets/firmware.bin assets/version.txt | |
| if git diff --cached --quiet; then | |
| echo "No changes to commit." | |
| exit 0 | |
| fi | |
| git commit -m "chore: update firmware.bin and version.txt from latest SmartSpin2k release" | |
| # Try to push to the current branch (will fail if protected) | |
| if git push origin HEAD:${{ github.ref_name }}; then | |
| echo "Pushed directly to ${{ github.ref_name }}." | |
| exit 0 | |
| fi | |
| # If push fails, create a new branch and open a PR | |
| update_branch="auto/firmware-update-$(date +%Y%m%d%H%M%S)" | |
| git checkout -b "$update_branch" | |
| git push origin "$update_branch" | |
| echo "Creating pull request to ${{ github.ref_name }}..." | |
| # Install GitHub CLI if not present | |
| if ! command -v gh >/dev/null 2>&1; then | |
| echo "Installing GitHub CLI..." | |
| sudo apt-get update && sudo apt-get install -y gh | |
| fi | |
| # Use GH_TOKEN for authentication (no gh auth login needed in CI) | |
| gh pr create --base "${{ github.ref_name }}" --head "$update_branch" --title "chore: update firmware.bin and version.txt from latest SmartSpin2k release" --body "Automated update of firmware.bin and version.txt from the latest SmartSpin2k release." | |
| - name: Extract version from pubspec.yaml | |
| id: extract_version | |
| run: | | |
| VERSION=$(grep 'version: ' pubspec.yaml | sed 's/version: //') | |
| echo "VERSION=${VERSION}" >> $GITHUB_ENV | |
| - name: Check if tag exists | |
| id: check_tag | |
| run: | | |
| TAG_EXISTS=$(git tag -l "$VERSION") | |
| if [[ "$TAG_EXISTS" == "$VERSION" ]]; then | |
| SUFFIX=1 | |
| NEW_TAG="$VERSION-$SUFFIX" | |
| while [[ $(git tag -l "$NEW_TAG") == "$NEW_TAG" ]]; do | |
| SUFFIX=$((SUFFIX+1)) | |
| NEW_TAG="$VERSION-$SUFFIX" | |
| done | |
| echo "NEW_TAG=$NEW_TAG" >> $GITHUB_ENV | |
| else | |
| echo "NEW_TAG=$VERSION" >> $GITHUB_ENV | |
| fi | |
| - name: Set output | |
| id: set_version | |
| run: echo "::set-output name=version::$NEW_TAG" | |
| build-android: | |
| needs: prepare | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v2 | |
| - name: Setup Java | |
| uses: actions/setup-java@v2 | |
| with: | |
| java-version: '21' | |
| distribution: 'temurin' | |
| - name: Setup Dart SDK | |
| uses: dart-lang/setup-dart@v1 | |
| with: | |
| sdk: 'stable' | |
| - name: Setup Flutter | |
| uses: subosito/flutter-action@v2 | |
| with: | |
| channel: stable | |
| flutter-version-file: pubspec.yaml | |
| - name: Accept Android SDK licenses | |
| shell: bash | |
| run: | | |
| yes | sdkmanager --licenses || true | |
| sdkmanager --install "platforms;android-36" "build-tools;36.0.0" || true | |
| - name: Create env.local.dart | |
| env: | |
| STRAVA_CLIENT_ID: ${{ secrets.STRAVA_CLIENT_ID }} | |
| STRAVA_CLIENT_SECRET: ${{ secrets.STRAVA_CLIENT_SECRET }} | |
| INTERVALS_CLIENT_ID: ${{ secrets.INTERVALS_CLIENT_ID }} | |
| INTERVALS_CLIENT_SECRET: ${{ secrets.INTERVALS_CLIENT_SECRET }} | |
| run: | | |
| mkdir -p lib/config | |
| umask 077 | |
| cat > lib/config/env.local.dart <<EOL | |
| class Environment { | |
| static const String stravaClientId = '${STRAVA_CLIENT_ID}'; | |
| static const String stravaClientSecret = '${STRAVA_CLIENT_SECRET}'; | |
| static const String intervalsClientId = '${INTERVALS_CLIENT_ID}'; | |
| static const String intervalsClientSecret = '${INTERVALS_CLIENT_SECRET}'; | |
| static bool get hasStravaConfig => | |
| stravaClientId.isNotEmpty && stravaClientSecret.isNotEmpty; | |
| static bool get hasIntervalsConfig => | |
| intervalsClientId.isNotEmpty && intervalsClientSecret.isNotEmpty; | |
| } | |
| EOL | |
| # Replace environment variables | |
| envsubst < lib/config/env.local.dart > lib/config/env.local.dart.tmp | |
| mv lib/config/env.local.dart.tmp lib/config/env.local.dart | |
| - name: Build Android APK | |
| run: flutter build apk --no-tree-shake-icons | |
| - name: Create artifacts | |
| run: | | |
| mkdir -p artifacts | |
| mv build/app/outputs/flutter-apk/app-release.apk artifacts/ss2kconfigapp-${{ needs.prepare.outputs.version }}.apk | |
| - name: Upload artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: android-artifacts | |
| path: artifacts/ | |
| build-macos: | |
| needs: prepare | |
| runs-on: macos-latest | |
| steps: | |
| - uses: actions/checkout@v2 | |
| - name: Setup Dart SDK | |
| uses: dart-lang/setup-dart@v1 | |
| with: | |
| sdk: 'stable' | |
| - name: Setup Flutter | |
| uses: subosito/flutter-action@v2 | |
| with: | |
| channel: stable | |
| flutter-version-file: pubspec.yaml | |
| # Debug environment variables | |
| - name: Debug Environment Variables | |
| run: | | |
| echo "Checking environment variables (secrets will be masked):" | |
| env | grep -i STRAVA || true | |
| - name: Create env.local.dart | |
| env: | |
| STRAVA_CLIENT_ID: ${{ secrets.STRAVA_CLIENT_ID }} | |
| STRAVA_CLIENT_SECRET: ${{ secrets.STRAVA_CLIENT_SECRET }} | |
| INTERVALS_CLIENT_ID: ${{ secrets.INTERVALS_CLIENT_ID }} | |
| INTERVALS_CLIENT_SECRET: ${{ secrets.INTERVALS_CLIENT_SECRET }} | |
| run: | | |
| mkdir -p lib/config | |
| umask 077 | |
| cat > lib/config/env.local.dart << \EOL | |
| class Environment { | |
| static const String stravaClientId = '${STRAVA_CLIENT_ID}'; | |
| static const String stravaClientSecret = '${STRAVA_CLIENT_SECRET}'; | |
| static const String intervalsClientId = '${INTERVALS_CLIENT_ID}'; | |
| static const String intervalsClientSecret = '${INTERVALS_CLIENT_SECRET}'; | |
| static bool get hasStravaConfig => | |
| stravaClientId.isNotEmpty && stravaClientSecret.isNotEmpty; | |
| static bool get hasIntervalsConfig => | |
| intervalsClientId.isNotEmpty && intervalsClientSecret.isNotEmpty; | |
| } | |
| EOL | |
| # Replace environment variables | |
| envsubst < lib/config/env.local.dart > lib/config/env.local.dart.tmp | |
| mv lib/config/env.local.dart.tmp lib/config/env.local.dart | |
| # Verify file exists but don't show contents | |
| ls -l lib/config/env.local.dart | |
| - name: Build iOS App | |
| run: flutter build ios --release --no-codesign --no-tree-shake-icons | |
| - name: Build macOS App | |
| run: flutter build macos --release --no-tree-shake-icons | |
| - name: Create artifacts | |
| run: | | |
| mkdir -p artifacts | |
| zip -r artifacts/ss2kconfigapp-${{ needs.prepare.outputs.version }}.zip build/ios/iphoneos build/macos/Build/Products/Release | |
| # Clean up sensitive files | |
| - name: Clean up env.local.dart | |
| if: always() | |
| run: | | |
| if [ -f lib/config/env.local.dart ]; then | |
| rm lib/config/env.local.dart | |
| echo "Cleaned up env.local.dart" | |
| fi | |
| - name: Upload artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: macos-artifacts | |
| path: artifacts/ | |
| build-linux: | |
| needs: prepare | |
| # Use native runners for each architecture (amd64 & arm64) instead of emulation | |
| runs-on: ${{ matrix.runner }} | |
| strategy: | |
| matrix: | |
| include: | |
| - arch: amd64 | |
| runner: ubuntu-latest | |
| - arch: arm64 | |
| runner: ubuntu-24.04-arm | |
| steps: | |
| - uses: actions/checkout@v2 | |
| - name: Setup Dart SDK | |
| uses: dart-lang/setup-dart@v1 | |
| with: | |
| sdk: 'stable' | |
| # Use official action for amd64 | |
| - name: Setup Flutter (amd64) | |
| if: matrix.arch == 'amd64' | |
| uses: subosito/flutter-action@v2 | |
| with: | |
| channel: stable | |
| flutter-version-file: pubspec.yaml | |
| # Manual install for arm64 (action currently fails to resolve version on ubuntu-24.04-arm) | |
| - name: Setup Flutter (arm64 manual) | |
| if: matrix.arch == 'arm64' | |
| run: | | |
| set -e | |
| sudo apt-get update | |
| sudo DEBIAN_FRONTEND=noninteractive apt-get install -y git curl unzip xz-utils zip | |
| # Clone flutter stable at desired version (tag 3.38.1) | |
| git clone https://github.com/flutter/flutter.git -b stable $HOME/flutter | |
| cd $HOME/flutter | |
| # Ensure we are at or past desired version; attempt to checkout tag if exists | |
| if git rev-parse -q --verify refs/tags/3.38.1 >/dev/null; then | |
| git fetch --tags | |
| git checkout 3.38.1 || true | |
| fi | |
| echo "$HOME/flutter/bin" >> $GITHUB_PATH | |
| $HOME/flutter/bin/flutter config --no-analytics | |
| $HOME/flutter/bin/flutter doctor -v || true | |
| - name: Install Dart (arm64, stable) | |
| if: matrix.arch == 'arm64' | |
| run: | | |
| set -e | |
| sudo apt-get update | |
| sudo DEBIAN_FRONTEND=noninteractive apt-get install -y apt-transport-https curl gnupg ca-certificates | |
| curl -fsSL https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo gpg --dearmor -o /usr/share/keyrings/dart.gpg | |
| echo "deb [signed-by=/usr/share/keyrings/dart.gpg] https://storage.googleapis.com/download.dartlang.org/linux/debian stable main" | sudo tee /etc/apt/sources.list.d/dart_stable.list | |
| sudo apt-get update | |
| sudo DEBIAN_FRONTEND=noninteractive apt-get install -y dart | |
| echo "/usr/lib/dart/bin" >> $GITHUB_PATH | |
| - name: Create env.local.dart | |
| env: | |
| STRAVA_CLIENT_ID: ${{ secrets.STRAVA_CLIENT_ID }} | |
| STRAVA_CLIENT_SECRET: ${{ secrets.STRAVA_CLIENT_SECRET }} | |
| INTERVALS_CLIENT_ID: ${{ secrets.INTERVALS_CLIENT_ID }} | |
| INTERVALS_CLIENT_SECRET: ${{ secrets.INTERVALS_CLIENT_SECRET }} | |
| run: | | |
| mkdir -p lib/config | |
| umask 077 | |
| cat > lib/config/env.local.dart <<EOL | |
| class Environment { | |
| static const String stravaClientId = '${STRAVA_CLIENT_ID}'; | |
| static const String stravaClientSecret = '${STRAVA_CLIENT_SECRET}'; | |
| static const String intervalsClientId = '${INTERVALS_CLIENT_ID}'; | |
| static const String intervalsClientSecret = '${INTERVALS_CLIENT_SECRET}'; | |
| static bool get hasStravaConfig => | |
| stravaClientId.isNotEmpty && stravaClientSecret.isNotEmpty; | |
| static bool get hasIntervalsConfig => | |
| intervalsClientId.isNotEmpty && intervalsClientSecret.isNotEmpty; | |
| } | |
| EOL | |
| # Replace environment variables | |
| envsubst < lib/config/env.local.dart > lib/config/env.local.dart.tmp | |
| mv lib/config/env.local.dart.tmp lib/config/env.local.dart | |
| - name: Install Linux dependencies | |
| run: | | |
| sudo apt-get update | |
| sudo DEBIAN_FRONTEND=noninteractive apt-get install -y \ | |
| libgtk-3-dev libx11-dev pkg-config cmake ninja-build \ | |
| libblkid-dev liblzma-dev libsecret-1-dev libjsoncpp-dev \ | |
| libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libunwind-dev | |
| - name: Enable Linux Desktop | |
| run: flutter config --enable-linux-desktop | |
| - name: Build Linux App | |
| run: | | |
| # Install additional build dependencies (clang & runtime libs) | |
| sudo apt-get update | |
| sudo DEBIAN_FRONTEND=noninteractive apt-get install -y \ | |
| clang libstdc++-12-dev libglu1-mesa fontconfig libpulse0 || true | |
| # Handle ALSA time64 transition on Ubuntu 24.04 (libasound2t64) | |
| sudo DEBIAN_FRONTEND=noninteractive apt-get install -y libasound2 || \ | |
| sudo DEBIAN_FRONTEND=noninteractive apt-get install -y libasound2t64 || true | |
| export CXX=clang++ | |
| flutter config --no-analytics | |
| flutter pub get | |
| # Build (native arch) | |
| flutter build linux --release --no-tree-shake-icons -v | |
| - name: Create Debian Package | |
| run: | | |
| # Create package directory structure | |
| mkdir -p debian/DEBIAN | |
| mkdir -p debian/usr/bin | |
| mkdir -p debian/usr/share/applications | |
| mkdir -p debian/usr/share/icons/hicolor/256x256/apps | |
| # Create control file | |
| cat > debian/DEBIAN/control <<EOL | |
| Package: ss2kconfigapp | |
| Version: ${{ needs.prepare.outputs.version }} | |
| Section: utils | |
| Priority: optional | |
| Architecture: ${{ matrix.arch }} | |
| Maintainer: SmartSpin2k Team | |
| Description: SmartSpin2k Configuration Application | |
| A Flutter application for configuring and controlling SmartSpin2k devices. | |
| EOL | |
| # Copy application files based on architecture | |
| BUILD_PATH="" | |
| if [ "${{ matrix.arch }}" = "arm64" ]; then | |
| BUILD_PATH="build/linux/arm64/release/bundle" | |
| else | |
| BUILD_PATH="build/linux/x64/release/bundle" | |
| fi | |
| # Verify build path exists | |
| if [ ! -d "$BUILD_PATH" ]; then | |
| echo "Error: Build output not found at $BUILD_PATH" | |
| ls -la build/linux/ | |
| exit 1 | |
| fi | |
| # Copy files | |
| echo "Copying files from $BUILD_PATH to debian/usr/bin/" | |
| cp -rv "$BUILD_PATH"/* debian/usr/bin/ | |
| # Copy icon | |
| cp assets/icons/ss2kv3.png debian/usr/share/icons/hicolor/256x256/apps/ss2kconfigapp.png | |
| # Create desktop file | |
| cat > debian/usr/share/applications/ss2kconfigapp.desktop <<EOL | |
| [Desktop Entry] | |
| Name=SmartSpin2k Config App | |
| Exec=/usr/bin/ss2kconfigapp | |
| Icon=ss2kconfigapp | |
| Type=Application | |
| Categories=Utility; | |
| EOL | |
| # Set permissions | |
| chmod 755 debian/usr/bin/ss2kconfigapp | |
| chmod -R 755 debian/DEBIAN | |
| # Build the package with architecture in filename | |
| dpkg-deb --build debian ss2kconfigapp-${{ matrix.arch }}.deb | |
| - name: Create artifacts | |
| run: | | |
| mkdir -p artifacts | |
| mv ss2kconfigapp-${{ matrix.arch }}.deb artifacts/ | |
| - name: Upload artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: linux-artifacts-${{ matrix.arch }} | |
| path: artifacts/ | |
| create-release: | |
| needs: [prepare, build-android, build-macos, build-linux] | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Download all artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| path: all-artifacts | |
| - name: Prepare release bundle | |
| run: | | |
| mkdir -p release | |
| cp all-artifacts/android-artifacts/ss2kconfigapp-${{ needs.prepare.outputs.version }}.apk release/ss2kconfigapp-${{ needs.prepare.outputs.version }}.apk | |
| cp all-artifacts/macos-artifacts/ss2kconfigapp-${{ needs.prepare.outputs.version }}.zip release/ | |
| cp all-artifacts/linux-artifacts-amd64/ss2kconfigapp-amd64.deb release/ss2kconfigapp-${{ needs.prepare.outputs.version }}-amd64.deb | |
| cp all-artifacts/linux-artifacts-arm64/ss2kconfigapp-arm64.deb release/ss2kconfigapp-${{ needs.prepare.outputs.version }}-arm64.deb | |
| - name: Create release | |
| uses: softprops/action-gh-release@v1 | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| with: | |
| tag_name: ${{ needs.prepare.outputs.version }} | |
| name: SmartSpin2kConfigApp ${{ needs.prepare.outputs.version }} | |
| draft: false | |
| prerelease: false | |
| generate_release_notes: true | |
| body: ${{ github.event.head_commit.message }} | |
| files: | | |
| release/ss2kconfigapp-${{ needs.prepare.outputs.version }}.apk | |
| release/ss2kconfigapp${{ needs.prepare.outputs.version }}.zip | |
| release/ss2kconfigapp-${{ needs.prepare.outputs.version }}-amd64.deb | |
| release/ss2kconfigapp-${{ needs.prepare.outputs.version }}-arm64.deb |