mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-03-31 09:01:33 +02:00
415 lines
14 KiB
YAML
415 lines
14 KiB
YAML
name: Release
|
|
|
|
on:
|
|
push:
|
|
tags:
|
|
- "v*"
|
|
workflow_dispatch:
|
|
inputs:
|
|
version:
|
|
description: "Version tag (e.g., v1.0.0)"
|
|
required: true
|
|
default: "v1.0.0"
|
|
|
|
jobs:
|
|
# Get version first (quick job)
|
|
get-version:
|
|
runs-on: ubuntu-latest
|
|
outputs:
|
|
version: ${{ steps.version.outputs.version }}
|
|
is_prerelease: ${{ steps.version.outputs.is_prerelease }}
|
|
steps:
|
|
- name: Get version
|
|
id: version
|
|
run: |
|
|
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
|
VERSION="${{ github.event.inputs.version }}"
|
|
else
|
|
VERSION="${GITHUB_REF#refs/tags/}"
|
|
fi
|
|
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
|
|
|
# Check if version contains -preview, -beta, -rc, or -alpha (NOT -hotfix)
|
|
VERSION_LOWER=$(echo "$VERSION" | tr '[:upper:]' '[:lower:]')
|
|
if [[ "$VERSION_LOWER" == *"-preview"* ]] || [[ "$VERSION_LOWER" == *"-beta"* ]] || [[ "$VERSION_LOWER" == *"-rc"* ]] || [[ "$VERSION_LOWER" == *"-alpha"* ]]; then
|
|
echo "is_prerelease=true" >> $GITHUB_OUTPUT
|
|
echo "Detected pre-release version: $VERSION"
|
|
else
|
|
echo "is_prerelease=false" >> $GITHUB_OUTPUT
|
|
echo "Detected stable version: $VERSION"
|
|
fi
|
|
|
|
# Android and iOS build in PARALLEL
|
|
build-android:
|
|
runs-on: ubuntu-latest
|
|
needs: get-version
|
|
|
|
steps:
|
|
- name: Free disk space
|
|
run: |
|
|
# Remove large unused tools (~15GB total)
|
|
sudo rm -rf /usr/share/dotnet
|
|
sudo rm -rf /opt/ghc
|
|
sudo rm -rf /opt/hostedtoolcache/CodeQL
|
|
sudo rm -rf /usr/local/share/boost
|
|
sudo rm -rf /usr/share/swift
|
|
sudo rm -rf /usr/local/.ghcup
|
|
# Clean docker images
|
|
sudo docker image prune --all --force
|
|
# Show available space
|
|
df -h
|
|
|
|
- name: Checkout repository
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Setup Java
|
|
uses: actions/setup-java@v4
|
|
with:
|
|
distribution: "temurin"
|
|
java-version: "17"
|
|
|
|
- name: Setup Go
|
|
uses: actions/setup-go@v5
|
|
with:
|
|
go-version: "1.21"
|
|
cache-dependency-path: go_backend/go.sum
|
|
|
|
# Cache Gradle for faster builds
|
|
- name: Cache Gradle
|
|
uses: actions/cache@v4
|
|
with:
|
|
path: |
|
|
~/.gradle/caches
|
|
~/.gradle/wrapper
|
|
key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
|
|
restore-keys: gradle-${{ runner.os }}-
|
|
|
|
- name: Install Android SDK & NDK
|
|
run: |
|
|
# Use pre-installed Android SDK on GitHub runners
|
|
echo "ANDROID_HOME=$ANDROID_HOME"
|
|
echo "ANDROID_SDK_ROOT=$ANDROID_SDK_ROOT"
|
|
|
|
# Accept licenses
|
|
yes | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --licenses || true
|
|
|
|
# Install NDK r27d LTS (required for 16KB page size support on Android 15+)
|
|
# Platform android-36 and build-tools 36.0.0 for targetSdk 36 (Android 16)
|
|
$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager "ndk;27.3.13750724" "platforms;android-36" "build-tools;36.0.0"
|
|
|
|
# Set NDK path
|
|
echo "ANDROID_NDK_HOME=$ANDROID_HOME/ndk/27.3.13750724" >> $GITHUB_ENV
|
|
|
|
- name: Install gomobile
|
|
run: |
|
|
go install golang.org/x/mobile/cmd/gomobile@latest
|
|
gomobile init
|
|
|
|
- name: Build Go backend for Android
|
|
working-directory: go_backend
|
|
run: |
|
|
mkdir -p ../android/app/libs
|
|
gomobile bind -target=android -androidapi 24 -o ../android/app/libs/gobackend.aar .
|
|
env:
|
|
CGO_ENABLED: 1
|
|
|
|
- name: Setup Flutter
|
|
uses: subosito/flutter-action@v2
|
|
with:
|
|
channel: "stable"
|
|
cache: true
|
|
|
|
- name: Get Flutter dependencies
|
|
run: flutter pub get
|
|
|
|
- name: Generate app icons
|
|
run: dart run flutter_launcher_icons
|
|
|
|
- name: Build APK (Release - unsigned)
|
|
run: |
|
|
flutter build apk --release --split-per-abi || true
|
|
# Verify APKs were created
|
|
ls -la build/app/outputs/flutter-apk/
|
|
if [ ! -f "build/app/outputs/flutter-apk/app-arm64-v8a-release.apk" ]; then
|
|
echo "ERROR: APK not found!"
|
|
exit 1
|
|
fi
|
|
|
|
- name: Sign APKs
|
|
uses: r0adkll/sign-android-release@v1
|
|
id: sign_arm64
|
|
with:
|
|
releaseDirectory: build/app/outputs/flutter-apk
|
|
signingKeyBase64: ${{ secrets.KEYSTORE_BASE64 }}
|
|
alias: ${{ secrets.KEY_ALIAS }}
|
|
keyStorePassword: ${{ secrets.KEYSTORE_PASSWORD }}
|
|
keyPassword: ${{ secrets.KEY_PASSWORD }}
|
|
env:
|
|
BUILD_TOOLS_VERSION: "36.0.0"
|
|
|
|
- name: Rename APKs
|
|
run: |
|
|
VERSION=${{ needs.get-version.outputs.version }}
|
|
cd build/app/outputs/flutter-apk
|
|
# Signed files have -signed suffix
|
|
mv app-arm64-v8a-release-signed.apk SpotiFLAC-${VERSION}-arm64.apk || mv app-arm64-v8a-release.apk SpotiFLAC-${VERSION}-arm64.apk || true
|
|
mv app-armeabi-v7a-release-signed.apk SpotiFLAC-${VERSION}-arm32.apk || mv app-armeabi-v7a-release.apk SpotiFLAC-${VERSION}-arm32.apk || true
|
|
mv app-release-signed.apk SpotiFLAC-${VERSION}-universal.apk || mv app-release.apk SpotiFLAC-${VERSION}-universal.apk || true
|
|
ls -la
|
|
|
|
- name: Upload APK artifact
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: android-apk
|
|
path: build/app/outputs/flutter-apk/SpotiFLAC-*.apk
|
|
|
|
build-ios:
|
|
runs-on: macos-latest
|
|
needs: get-version # Only depends on version, NOT android build!
|
|
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Setup Go
|
|
uses: actions/setup-go@v5
|
|
with:
|
|
go-version: "1.21"
|
|
cache-dependency-path: go_backend/go.sum
|
|
|
|
# Cache CocoaPods
|
|
- name: Cache CocoaPods
|
|
uses: actions/cache@v4
|
|
with:
|
|
path: ios/Pods
|
|
key: pods-${{ runner.os }}-${{ hashFiles('ios/Podfile.lock') }}
|
|
restore-keys: pods-${{ runner.os }}-
|
|
|
|
- name: Install gomobile
|
|
run: |
|
|
go install golang.org/x/mobile/cmd/gomobile@latest
|
|
gomobile init
|
|
|
|
- name: Build Go backend for iOS
|
|
working-directory: go_backend
|
|
run: |
|
|
mkdir -p ../ios/Frameworks
|
|
gomobile bind -target=ios -o ../ios/Frameworks/Gobackend.xcframework .
|
|
env:
|
|
CGO_ENABLED: 1
|
|
|
|
- name: Verify XCFramework created
|
|
run: |
|
|
ls -la ios/Frameworks/
|
|
ls -la ios/Frameworks/Gobackend.xcframework/ || (echo "ERROR: XCFramework not found!" && exit 1)
|
|
|
|
- name: Add XCFramework to Xcode project
|
|
run: |
|
|
# Install xcodeproj gem for modifying Xcode project
|
|
sudo gem install xcodeproj
|
|
|
|
# Create Ruby script to add framework
|
|
cat > add_framework.rb << 'EOF'
|
|
require 'xcodeproj'
|
|
|
|
project_path = 'ios/Runner.xcodeproj'
|
|
project = Xcodeproj::Project.open(project_path)
|
|
|
|
# Get the main target
|
|
target = project.targets.find { |t| t.name == 'Runner' }
|
|
|
|
# Get or create Frameworks group
|
|
frameworks_group = project.main_group.find_subpath('Frameworks', true)
|
|
frameworks_group ||= project.main_group.new_group('Frameworks')
|
|
|
|
# Add XCFramework reference
|
|
framework_path = 'Frameworks/Gobackend.xcframework'
|
|
framework_ref = frameworks_group.new_file(framework_path, :project)
|
|
|
|
# Add to frameworks build phase
|
|
frameworks_build_phase = target.frameworks_build_phase
|
|
frameworks_build_phase.add_file_reference(framework_ref)
|
|
|
|
# Add to embed frameworks build phase
|
|
embed_phase = target.build_phases.find { |p| p.is_a?(Xcodeproj::Project::Object::PBXCopyFilesBuildPhase) && p.name == 'Embed Frameworks' }
|
|
if embed_phase
|
|
build_file = embed_phase.add_file_reference(framework_ref)
|
|
build_file.settings = { 'ATTRIBUTES' => ['CodeSignOnCopy', 'RemoveHeadersOnCopy'] }
|
|
end
|
|
|
|
project.save
|
|
puts "Successfully added Gobackend.xcframework to Xcode project"
|
|
EOF
|
|
|
|
ruby add_framework.rb
|
|
|
|
- name: Setup Flutter
|
|
uses: subosito/flutter-action@v2
|
|
with:
|
|
channel: "stable"
|
|
cache: true
|
|
|
|
# Swap pubspec for iOS build (includes ffmpeg_kit_flutter)
|
|
- name: Use iOS pubspec with FFmpeg plugin
|
|
run: |
|
|
cp pubspec.yaml pubspec_android_backup.yaml
|
|
cp pubspec_ios.yaml pubspec.yaml
|
|
echo "Swapped to iOS pubspec with ffmpeg_kit_flutter"
|
|
|
|
# Swap FFmpeg service for iOS
|
|
- name: Use iOS FFmpeg service
|
|
run: |
|
|
cp lib/services/ffmpeg_service.dart lib/services/ffmpeg_service_android.dart
|
|
cp build_assets/ffmpeg_service_ios.dart lib/services/ffmpeg_service.dart
|
|
# Update class name in the swapped file
|
|
sed -i '' 's/FFmpegServiceIOS/FFmpegService/g' lib/services/ffmpeg_service.dart
|
|
sed -i '' 's/FFmpegResultIOS/FFmpegResult/g' lib/services/ffmpeg_service.dart
|
|
echo "Swapped to iOS FFmpeg service"
|
|
|
|
- name: Get Flutter dependencies
|
|
run: flutter pub get
|
|
|
|
- name: Generate app icons
|
|
run: dart run flutter_launcher_icons
|
|
|
|
- name: Build iOS (unsigned)
|
|
run: |
|
|
# Build Flutter iOS without codesigning
|
|
flutter build ios --release --no-codesign --config-only
|
|
|
|
# Use xcodebuild with code signing disabled
|
|
cd ios
|
|
xcodebuild -workspace Runner.xcworkspace \
|
|
-scheme Runner \
|
|
-configuration Release \
|
|
-sdk iphoneos \
|
|
-destination 'generic/platform=iOS' \
|
|
-archivePath build/Runner.xcarchive \
|
|
archive \
|
|
CODE_SIGNING_ALLOWED=NO \
|
|
CODE_SIGNING_REQUIRED=NO \
|
|
CODE_SIGN_IDENTITY="" \
|
|
DEVELOPMENT_TEAM=""
|
|
|
|
- name: Create IPA
|
|
run: |
|
|
VERSION=${{ needs.get-version.outputs.version }}
|
|
mkdir -p build/ios/ipa
|
|
cd ios/build/Runner.xcarchive/Products/Applications
|
|
mkdir Payload
|
|
cp -r Runner.app Payload/
|
|
# Use absolute path to avoid relative path issues
|
|
zip -r $GITHUB_WORKSPACE/build/ios/ipa/SpotiFLAC-${VERSION}-ios-unsigned.ipa Payload
|
|
rm -rf Payload
|
|
|
|
- name: Verify IPA created
|
|
run: |
|
|
ls -la build/ios/ipa/
|
|
VERSION=${{ needs.get-version.outputs.version }}
|
|
if [ ! -f "build/ios/ipa/SpotiFLAC-${VERSION}-ios-unsigned.ipa" ]; then
|
|
echo "ERROR: IPA not created!"
|
|
exit 1
|
|
fi
|
|
|
|
- name: Upload IPA artifact
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: ios-ipa
|
|
path: build/ios/ipa/SpotiFLAC-*.ipa
|
|
|
|
create-release:
|
|
runs-on: ubuntu-latest
|
|
needs: [get-version, build-android, build-ios]
|
|
permissions:
|
|
contents: write
|
|
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Extract changelog for version
|
|
id: changelog
|
|
run: |
|
|
VERSION=${{ needs.get-version.outputs.version }}
|
|
VERSION_NUM=${VERSION#v} # Remove 'v' prefix
|
|
|
|
echo "Looking for version: $VERSION_NUM"
|
|
|
|
# Extract changelog section for this version using sed
|
|
# Find the line with version, then print until next version header or end
|
|
CHANGELOG=$(sed -n "/^## \[$VERSION_NUM\]/,/^## \[/{ /^## \[$VERSION_NUM\]/d; /^## \[/d; p; }" CHANGELOG.md)
|
|
|
|
# If no changelog found, use default message
|
|
if [ -z "$CHANGELOG" ]; then
|
|
echo "No changelog found for version $VERSION_NUM"
|
|
CHANGELOG="See CHANGELOG.md for details."
|
|
else
|
|
echo "Found changelog content"
|
|
# Remove trailing --- separator if present (CHANGELOG uses --- between versions)
|
|
CHANGELOG=$(echo "$CHANGELOG" | sed '/^---$/d')
|
|
fi
|
|
|
|
# Save to file for multiline support
|
|
echo "$CHANGELOG" > /tmp/changelog.txt
|
|
echo "Extracted changelog:"
|
|
cat /tmp/changelog.txt
|
|
|
|
- name: Download Android APK
|
|
uses: actions/download-artifact@v4
|
|
with:
|
|
name: android-apk
|
|
path: ./release
|
|
|
|
- name: Download iOS IPA
|
|
uses: actions/download-artifact@v4
|
|
with:
|
|
name: ios-ipa
|
|
path: ./release
|
|
|
|
- name: Prepare release body
|
|
run: |
|
|
VERSION=${{ needs.get-version.outputs.version }}
|
|
cat > /tmp/release_body.txt << 'HEADER'
|
|
### What's New
|
|
HEADER
|
|
|
|
cat /tmp/changelog.txt >> /tmp/release_body.txt
|
|
|
|
REPO_OWNER="${{ github.repository_owner }}"
|
|
REPO_NAME="${{ github.event.repository.name }}"
|
|
|
|
cat >> /tmp/release_body.txt << FOOTER
|
|
|
|
---
|
|
|
|
### Downloads
|
|
|
|
#### Android
|
|
- **arm64**: \`SpotiFLAC-${VERSION}-arm64.apk\` (recommended for modern devices)
|
|
- **arm32**: \`SpotiFLAC-${VERSION}-arm32.apk\` (older devices)
|
|
|
|
#### iOS
|
|
- **iOS**: \`SpotiFLAC-${VERSION}-ios-unsigned.ipa\` (sideload required)
|
|
|
|
### Installation
|
|
**Android**: Enable "Install from unknown sources" and install the APK
|
|
**iOS**: Use AltStore, Sideloadly, or similar tools to sideload the IPA
|
|
|
|
  
|
|
FOOTER
|
|
|
|
echo "Release body:"
|
|
cat /tmp/release_body.txt
|
|
|
|
- name: Create Release
|
|
uses: softprops/action-gh-release@v1
|
|
with:
|
|
tag_name: ${{ needs.get-version.outputs.version }}
|
|
name: SpotiFLAC ${{ needs.get-version.outputs.version }}
|
|
body_path: /tmp/release_body.txt
|
|
files: ./release/*
|
|
draft: false
|
|
prerelease: ${{ needs.get-version.outputs.is_prerelease == 'true' }}
|
|
env:
|
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|