diff --git a/.env.example b/.env.example index 22abd24b..56665663 100644 --- a/.env.example +++ b/.env.example @@ -9,3 +9,6 @@ ENABLE_UPDATE_CHECK= LASTFM_API_KEY= LASTFM_API_SECRET= + +# Release channel. Can be: nightly, stable +RELEASE_CHANNEL= diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..32795182 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,155 @@ +name: Build +on: + workflow_dispatch: + inputs: + channel: + type: choice + description: Release Channel + required: true + options: + - stable + - nightly + default: nightly + debug: + description: Debug on failed when channel is nightly + required: true + type: boolean + default: false + dry_run: + description: Dry run + required: true + type: boolean + default: true + +env: + FLUTTER_VERSION: 3.19.5 + +jobs: + build_platform: + strategy: + matrix: + include: + - os: ubuntu-latest + platform: linux + files: | + dist/Spotube-linux-x86_64.deb + dist/Spotube-linux-x86_64.rpm + dist/spotube-linux-*-x86_64.tar.xz + - os: ubuntu-latest + platform: android + files: | + build/Spotube-android-all-arch.apk + build/Spotube-playstore-all-arch.aab + - os: windows-latest + platform: windows + files: | + dist/Spotube-windows-x86_64.nupkg + dist/Spotube-windows-x86_64-setup.exe + - os: macos-latest + platform: ios + files: | + Spotube-iOS.ipa + - os: macos-14 + platform: macos + files: | + build/Spotube-macos-universal.dmg + build/Spotube-macos-universal.pkg + runs-on: ${{matrix.os}} + steps: + - uses: actions/checkout@v4 + - uses: subosito/flutter-action@v2.12.0 + with: + cache: true + flutter-version: ${{ env.FLUTTER_VERSION }} + - name: Setup Java + if: ${{matrix.platform == 'android'}} + uses: actions/setup-java@v4 + with: + distribution: 'zulu' + java-version: '17' + cache: 'gradle' + check-latest: true + + - name: Install ${{matrix.platform}} dependencies + run: dart cli/cli.dart install-dependencies --platform=${{matrix.platform}} + + - name: Sign Apk + if: ${{matrix.platform == 'android'}} + run: | + echo '${{ secrets.KEYSTORE }}' | base64 --decode > android/app/upload-keystore.jks + echo '${{ secrets.KEY_PROPERTIES }}' > android/key.properties + + - name: Build ${{matrix.platform}} binaries + run: dart cli/cli.dart build ${{matrix.platform}} + env: + CHANNEL: ${{inputs.channel}} + DOTENV: ${{secrets.DOTENV_RELEASE}} + + - uses: actions/upload-artifact@v3 + with: + if-no-files-found: error + name: Spotube-Release-Binaries + path: ${{matrix.files}} + + - name: Debug With SSH When fails + if: ${{ failure() && inputs.debug && inputs.channel == 'nightly' }} + uses: mxschmitt/action-tmate@v3 + with: + limit-access-to-actor: true + + upload: + runs-on: ubuntu-latest + needs: + - build_platform + steps: + - uses: actions/download-artifact@v3 + with: + name: Spotube-Release-Binaries + path: ./Spotube-Release-Binaries + + - name: Install dependencies + run: sudo apt-get install tree -y + + - name: Generate Checksums + run: | + tree . + md5sum Spotube-Release-Binaries/* >> RELEASE.md5sum + sha256sum Spotube-Release-Binaries/* >> RELEASE.sha256sum + sed -i 's|Spotube-Release-Binaries/||' RELEASE.sha256sum RELEASE.md5sum + + - name: Extract pubspec version + run: | + echo "PUBSPEC_VERSION=$(grep -oP 'version:\s*\K[^+]+(?=\+)' pubspec.yaml)" >> $GITHUB_ENV + + - uses: actions/upload-artifact@v3 + with: + if-no-files-found: error + name: Spotube-Release-Binaries + path: | + RELEASE.md5sum + RELEASE.sha256sum + + - name: Upload Release Binaries (stable) + if: ${{ !inputs.dry_run && inputs.channel == 'stable' }} + uses: ncipollo/release-action@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + tag: v${{ env.PUBSPEC_VERSION }} # mind the "v" prefix + omitBodyDuringUpdate: true + omitNameDuringUpdate: true + omitPrereleaseDuringUpdate: true + allowUpdates: true + artifacts: Spotube-Release-Binaries/*,RELEASE.sha256sum,RELEASE.md5sum + + - name: Upload Release Binaries (nightly) + if: ${{ !inputs.dry_run && inputs.channel == 'nightly' }} + uses: ncipollo/release-action@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + tag: nightly + omitBodyDuringUpdate: true + omitNameDuringUpdate: true + omitPrereleaseDuringUpdate: true + allowUpdates: true + artifacts: Spotube-Release-Binaries/*,RELEASE.sha256sum,RELEASE.md5sum + body: 'Build Number: ${{github.run_number}}' diff --git a/cli/cli.dart b/cli/cli.dart index 03f17742..3210f557 100644 --- a/cli/cli.dart +++ b/cli/cli.dart @@ -1,10 +1,16 @@ import 'package:args/command_runner.dart'; +import 'commands/build.dart'; +import 'commands/install-dependencies.dart'; + void main(List args) { final commandRunner = CommandRunner( "cli", "Configuration CLI for Spotube", ); + commandRunner.addCommand(InstallDependenciesCommand()); + commandRunner.addCommand(BuildCommand()); + commandRunner.run(args); } diff --git a/cli/commands/build.dart b/cli/commands/build.dart new file mode 100644 index 00000000..2b8af20b --- /dev/null +++ b/cli/commands/build.dart @@ -0,0 +1,23 @@ +import 'package:args/command_runner.dart'; + +import 'build/android.dart'; +import 'build/ios.dart'; +import 'build/linux.dart'; +import 'build/macos.dart'; +import 'build/windows.dart'; + +class BuildCommand extends Command { + @override + String get description => "Build for different platforms"; + + @override + String get name => "build"; + + BuildCommand() { + addSubcommand(AndroidBuildCommand()); + addSubcommand(IosBuildCommand()); + addSubcommand(LinuxBuildCommand()); + addSubcommand(MacosBuildCommand()); + addSubcommand(WindowsBuildCommand()); + } +} diff --git a/cli/commands/build/common.dart b/cli/commands/build/common.dart index 377a2842..8484b5dd 100644 --- a/cli/commands/build/common.dart +++ b/cli/commands/build/common.dart @@ -36,7 +36,21 @@ mixin BuildCommandCommonSteps on Command { Future bootstrap() async { await dotEnvFile.create(recursive: true); - await dotEnvFile.writeAsString(CliEnv.dotenv); + await dotEnvFile.writeAsString( + "${CliEnv.dotenv}\n" + "RELEASE_CHANNEL=${CliEnv.channel}\n", + ); + + if (CliEnv.channel == BuildChannel.nightly) { + final pubspecFile = File(join(cwd.path, "pubspec.yaml")); + + pubspecFile.writeAsStringSync( + pubspecFile.readAsStringSync().replaceAll( + "version: ${pubspec.version!.canonicalizedVersion}", + "version: $versionWithoutBuildNumber+${CliEnv.ghRunNumber}" + ), + ); + } await shell.run( """ diff --git a/cli/commands/install-dependencies.dart b/cli/commands/install-dependencies.dart new file mode 100644 index 00000000..c7fba051 --- /dev/null +++ b/cli/commands/install-dependencies.dart @@ -0,0 +1,65 @@ +import 'dart:async'; + +import 'package:args/command_runner.dart'; +import 'package:process_run/shell_run.dart'; + +class InstallDependenciesCommand extends Command { + @override + String get description => "Install platform dependencies"; + + @override + String get name => "install-dependencies"; + + InstallDependenciesCommand() { + argParser.addOption( + "platform", + abbr: "p", + allowed: [ + "windows", + "linux", + "macos", + "ios", + "android", + ], + mandatory: true, + ); + } + + @override + FutureOr? run() async { + final shell = Shell(); + + switch (argResults!.option("platform")) { + case "windows": + break; + case "linux": + await shell.run( + """ + sudo apt-get update -y + sudo apt-get install -y tar clang cmake ninja-build pkg-config libgtk-3-dev make python3-pip python3-setuptools desktop-file-utils libgdk-pixbuf2.0-dev fakeroot strace fuse libunwind-dev locate patchelf gir1.2-appindicator3-0.1 libappindicator3-1 libappindicator3-dev libsecret-1-0 libjsoncpp25 libsecret-1-dev libjsoncpp-dev libnotify-bin libnotify-dev mpv libmpv-dev + """, + ); + break; + case "macos": + await shell.run( + """ + brew install python-setuptools + npm install -g appdmg + """, + ); + break; + case "ios": + break; + case "android": + await shell.run( + """ + sudo apt-get update -y + sudo apt-get install -y clang cmake ninja-build pkg-config libgtk-3-dev make python3-pip python3-setuptools patchelf desktop-file-utils libgdk-pixbuf2.0-dev fakeroot strace fuse + """, + ); + break; + default: + break; + } + } +} diff --git a/cli/core/env.dart b/cli/core/env.dart index 84875e2d..a826f9a3 100644 --- a/cli/core/env.dart +++ b/cli/core/env.dart @@ -19,4 +19,5 @@ enum BuildChannel { class CliEnv { static final channel = BuildChannel.fromEnvironment("CHANNEL"); static final dotenv = Platform.environment["DOTENV"]!; + static final ghRunNumber = Platform.environment["GITHUB_RUN_NUMBER"]; } diff --git a/lib/collections/env.dart b/lib/collections/env.dart index 14f33b80..8081c81e 100644 --- a/lib/collections/env.dart +++ b/lib/collections/env.dart @@ -3,6 +3,11 @@ import 'package:spotube/utils/platform.dart'; part 'env.g.dart'; +enum ReleaseChannel { + nightly, + stable, +} + @Envied(obfuscate: true, requireEnvFile: true, path: ".env") abstract class Env { @EnviedField(varName: 'SPOTIFY_SECRETS') @@ -25,6 +30,13 @@ abstract class Env { @EnviedField(varName: 'ENABLE_UPDATE_CHECK', defaultValue: "1") static final String _enableUpdateChecker = _Env._enableUpdateChecker; + @EnviedField(varName: "RELEASE_CHANNEL", defaultValue: "nightly") + static final String _releaseChannel = _Env._releaseChannel; + + ReleaseChannel get releaseChannel => _releaseChannel == "stable" + ? ReleaseChannel.stable + : ReleaseChannel.nightly; + static bool get enableUpdateChecker => kIsFlatpak || _enableUpdateChecker == "1";