Преглед изворни кода

ci: android: Play store publishing setup

t895 пре 2 година
родитељ
комит
99ea31faa8

+ 21 - 0
.ci/scripts/android/eabuild.sh

@@ -0,0 +1,21 @@
+#!/bin/bash -ex
+
+# SPDX-FileCopyrightText: 2024 yuzu Emulator Project
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+export NDK_CCACHE="$(which ccache)"
+ccache -s
+
+export ANDROID_KEYSTORE_FILE="${GITHUB_WORKSPACE}/ks.jks"
+base64 --decode <<< "${EA_PLAY_ANDROID_KEYSTORE_B64}" > "${ANDROID_KEYSTORE_FILE}"
+export ANDROID_KEY_ALIAS="${PLAY_ANDROID_KEY_ALIAS}"
+export ANDROID_KEYSTORE_PASS="${PLAY_ANDROID_KEYSTORE_PASS}"
+export SERVICE_ACCOUNT_KEY_PATH="${GITHUB_WORKSPACE}/sa.json"
+base64 --decode <<< "${EA_SERVICE_ACCOUNT_KEY_B64}" > "${SERVICE_ACCOUNT_KEY_PATH}"
+./gradlew "publishEaReleaseBundle"
+
+ccache -s
+
+if [ ! -z "${ANDROID_KEYSTORE_B64}" ]; then
+    rm "${ANDROID_KEYSTORE_FILE}"
+fi

+ 21 - 0
.ci/scripts/android/mainlinebuild.sh

@@ -0,0 +1,21 @@
+#!/bin/bash -ex
+
+# SPDX-FileCopyrightText: 2024 yuzu Emulator Project
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+export NDK_CCACHE="$(which ccache)"
+ccache -s
+
+export ANDROID_KEYSTORE_FILE="${GITHUB_WORKSPACE}/ks.jks"
+base64 --decode <<< "${MAINLINE_PLAY_ANDROID_KEYSTORE_B64}" > "${ANDROID_KEYSTORE_FILE}"
+export ANDROID_KEY_ALIAS="${PLAY_ANDROID_KEY_ALIAS}"
+export ANDROID_KEYSTORE_PASS="${PLAY_ANDROID_KEYSTORE_PASS}"
+export SERVICE_ACCOUNT_KEY_PATH="${GITHUB_WORKSPACE}/sa.json"
+base64 --decode <<< "${MAINLINE_SERVICE_ACCOUNT_KEY_B64}" > "${SERVICE_ACCOUNT_KEY_PATH}"
+./gradlew "publishMainlineReleaseBundle"
+
+ccache -s
+
+if [ ! -z "${ANDROID_KEYSTORE_B64}" ]; then
+    rm "${ANDROID_KEYSTORE_FILE}"
+fi

+ 66 - 0
.github/workflows/android-ea-play-release.yml

@@ -0,0 +1,66 @@
+# SPDX-FileCopyrightText: 2024 yuzu Emulator Project
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+name: yuzu-android-ea-play-release
+
+on:
+  workflow_dispatch:
+    inputs:
+      release-track:
+        description: 'Play store release track (internal/alpha/beta/production)'
+        required: true
+        default: 'alpha'
+
+jobs:
+  android:
+    runs-on: ubuntu-latest
+    if: ${{ github.repository == 'yuzu-emu/yuzu' }}
+    steps:
+      - uses: actions/checkout@v3
+        name: Checkout
+        with:
+          fetch-depth: 0
+          submodules: true
+          token: ${{ secrets.ALT_GITHUB_TOKEN }}
+      - run: npm install execa@5
+      - uses: actions/github-script@v5
+        name: 'Merge and publish Android EA changes'
+        env:
+          ALT_GITHUB_TOKEN: ${{ secrets.ALT_GITHUB_TOKEN }}
+          BUILD_EA: true
+        with:
+          script: |
+            const execa = require("execa");
+            const mergebot = require('./.github/workflows/android-merge.js').mergebot;
+            process.chdir('${{ github.workspace }}');
+            mergebot(github, context, execa);
+      - name: Get tag name
+        run: echo "GIT_TAG_NAME=$(cat tag-name.txt)" >> $GITHUB_ENV
+      - name: Set up JDK 17
+        uses: actions/setup-java@v3
+        with:
+          java-version: '17'
+          distribution: 'temurin'
+      - name: Install dependencies
+        run: |
+          sudo apt-get update
+          sudo apt-get install -y ccache apksigner glslang-dev glslang-tools
+      - name: Build
+        run: ./.ci/scripts/android/eabuild.sh
+        env:
+          EA_PLAY_ANDROID_KEYSTORE_B64: ${{ secrets.PLAY_ANDROID_KEYSTORE_B64 }}
+          PLAY_ANDROID_KEY_ALIAS: ${{ secrets.PLAY_ANDROID_KEY_ALIAS }}
+          PLAY_ANDROID_KEYSTORE_PASS: ${{ secrets.PLAY_ANDROID_KEYSTORE_PASS }}
+          EA_SERVICE_ACCOUNT_KEY_B64: ${{ secrets.EA_SERVICE_ACCOUNT_KEY_B64 }}
+          STORE_TRACK: ${{ github.event.inputs.release-track }}
+          AUTO_VERSIONED: true
+          BUILD_EA: true
+      - name: Create release
+        uses: softprops/action-gh-release@v1
+        with:
+          tag_name: ${{ env.EA_TAG_NAME }}
+          name: ${{ env.EA_TAG_NAME }}
+          draft: false
+          prerelease: false
+          repository: yuzu/yuzu-android
+          token: ${{ secrets.ALT_GITHUB_TOKEN }}

+ 59 - 0
.github/workflows/android-mainline-play-release.yml

@@ -0,0 +1,59 @@
+# SPDX-FileCopyrightText: 2024 yuzu Emulator Project
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+name: yuzu-android-mainline-play-release
+
+on:
+  workflow_dispatch:
+    inputs:
+      release-tag:
+        description: 'Tag # from yuzu-android that you want to build and publish'
+        required: true
+        default: '200'
+      release-track:
+        description: 'Play store release track (internal/alpha/beta/production)'
+        required: true
+        default: 'alpha'
+
+jobs:
+  android:
+    runs-on: ubuntu-latest
+    if: ${{ github.repository == 'yuzu-emu/yuzu' }}
+    steps:
+      - uses: actions/checkout@v3
+        name: Checkout
+        with:
+          fetch-depth: 0
+          submodules: true
+          token: ${{ secrets.ALT_GITHUB_TOKEN }}
+      - run: npm install execa@5
+      - uses: actions/github-script@v5
+        name: 'Pull mainline tag'
+        env:
+          MAINLINE_TAG: ${{ github.event.inputs.release-tag }}
+        with:
+          script: |
+            const execa = require("execa");
+            const mergebot = require('./.github/workflows/android-merge.js').getMainlineTag;
+            process.chdir('${{ github.workspace }}');
+            mergebot(execa);
+      - name: Set up JDK 17
+        uses: actions/setup-java@v3
+        with:
+          java-version: '17'
+          distribution: 'temurin'
+      - name: Install dependencies
+        run: |
+          sudo apt-get update
+          sudo apt-get install -y ccache apksigner glslang-dev glslang-tools
+      - name: Build
+        run: |
+          echo "GIT_TAG_NAME=android-${{ github.event.inputs.releast-tag }}" >> $GITHUB_ENV
+          ./.ci/scripts/android/mainlinebuild.sh
+        env:
+          MAINLINE_PLAY_ANDROID_KEYSTORE_B64: ${{ secrets.PLAY_ANDROID_KEYSTORE_B64 }}
+          PLAY_ANDROID_KEY_ALIAS: ${{ secrets.PLAY_ANDROID_KEY_ALIAS }}
+          PLAY_ANDROID_KEYSTORE_PASS: ${{ secrets.PLAY_ANDROID_KEYSTORE_PASS }}
+          SERVICE_ACCOUNT_KEY_B64: ${{ secrets.MAINLINE_SERVICE_ACCOUNT_KEY_B64 }}
+          STORE_TRACK: ${{ github.event.inputs.release-track }}
+          AUTO_VERSIONED: true

+ 103 - 26
.github/workflows/android-merge.js

@@ -6,9 +6,12 @@
 
 const fs = require("fs");
 // which label to check for changes
-const CHANGE_LABEL = 'android-merge';
+const CHANGE_LABEL_MAINLINE = 'android-merge';
+const CHANGE_LABEL_EA = 'android-ea-merge';
 // how far back in time should we consider the changes are "recent"? (default: 24 hours)
 const DETECTION_TIME_FRAME = (parseInt(process.env.DETECTION_TIME_FRAME)) || (24 * 3600 * 1000);
+const BUILD_EA = process.env.BUILD_EA == 'true';
+const MAINLINE_TAG = process.env.MAINLINE_TAG;
 
 async function checkBaseChanges(github) {
     // query the commit date of the latest commit on this branch
@@ -40,20 +43,7 @@ async function checkBaseChanges(github) {
 
 async function checkAndroidChanges(github) {
     if (checkBaseChanges(github)) return true;
-    const query = `query($owner:String!, $name:String!, $label:String!) {
-        repository(name:$name, owner:$owner) {
-            pullRequests(labels: [$label], states: OPEN, first: 100) {
-                nodes { number headRepository { pushedAt } }
-            }
-        }
-    }`;
-    const variables = {
-        owner: 'yuzu-emu',
-        name: 'yuzu',
-        label: CHANGE_LABEL,
-    };
-    const result = await github.graphql(query, variables);
-    const pulls = result.repository.pullRequests.nodes;
+    const pulls = getPulls(github, false);
     for (let i = 0; i < pulls.length; i++) {
         let pull = pulls[i];
         if (new Date() - new Date(pull.headRepository.pushedAt) <= DETECTION_TIME_FRAME) {
@@ -83,7 +73,13 @@ async function tagAndPush(github, owner, repo, execa, commit=false) {
     };
     const tags = await github.graphql(query, variables);
     const tagList = tags.repository.refs.nodes;
-    const lastTag = tagList[0] ? tagList[0].name : 'dummy-0';
+    let lastTag = 'android-1';
+    for (let i = 0; i < tagList.length; ++i) {
+        if (tagList[i].name.includes('android-')) {
+            lastTag = tagList[i].name;
+            break;
+        }
+    }
     const tagNumber = /\w+-(\d+)/.exec(lastTag)[1] | 0;
     const channel = repo.split('-')[1];
     const newTag = `${channel}-${tagNumber + 1}`;
@@ -101,6 +97,48 @@ async function tagAndPush(github, owner, repo, execa, commit=false) {
     console.info('Successfully pushed new changes.');
 }
 
+async function tagAndPushEA(github, owner, repo, execa) {
+    let altToken = process.env.ALT_GITHUB_TOKEN;
+    if (!altToken) {
+        throw `Please set ALT_GITHUB_TOKEN environment variable. This token should have write access to ${owner}/${repo}.`;
+    }
+    const query = `query ($owner:String!, $name:String!) {
+        repository(name:$name, owner:$owner) {
+            refs(refPrefix: "refs/tags/", orderBy: {field: TAG_COMMIT_DATE, direction: DESC}, first: 10) {
+                nodes { name }
+            }
+        }
+    }`;
+    const variables = {
+        owner: owner,
+        name: repo,
+    };
+    const tags = await github.graphql(query, variables);
+    const tagList = tags.repository.refs.nodes;
+    let lastTag = 'ea-1';
+    for (let i = 0; i < tagList.length; ++i) {
+        if (tagList[i].name.includes('ea-')) {
+            lastTag = tagList[i].name;
+            break;
+        }
+    }
+    const tagNumber = /\w+-(\d+)/.exec(lastTag)[1] | 0;
+    const newTag = `ea-${tagNumber + 1}`;
+    console.log(`New tag: ${newTag}`);
+    console.info('Pushing tags to GitHub ...');
+    await execa("git", ["remote", "add", "android", "https://github.com/yuzu-emu/yuzu-android.git"]);
+    await execa("git", ["fetch", "android"]);
+
+    await execa("git", ['tag', newTag]);
+    await execa("git", ['push', 'android', `${newTag}`]);
+
+    fs.writeFile('tag-name.txt', newTag, (err) => {
+        if (err) throw 'Could not write tag name to file!'
+    })
+
+    console.info('Successfully pushed new changes.');
+}
+
 async function generateReadme(pulls, context, mergeResults, execa) {
     let baseUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/`;
     let output =
@@ -202,10 +240,7 @@ async function resetBranch(execa) {
     }
 }
 
-async function mergebot(github, context, execa) {
-    // Reset our local copy of master to what appears on yuzu-emu/yuzu - master
-    await resetBranch(execa);
-
+async function getPulls(github) {
     const query = `query ($owner:String!, $name:String!, $label:String!) {
         repository(name:$name, owner:$owner) {
             pullRequests(labels: [$label], states: OPEN, first: 100) {
@@ -215,13 +250,49 @@ async function mergebot(github, context, execa) {
             }
         }
     }`;
-    const variables = {
+    const mainlineVariables = {
         owner: 'yuzu-emu',
         name: 'yuzu',
-        label: CHANGE_LABEL,
+        label: CHANGE_LABEL_MAINLINE,
     };
-    const result = await github.graphql(query, variables);
-    const pulls = result.repository.pullRequests.nodes;
+    const mainlineResult = await github.graphql(query, mainlineVariables);
+    const pulls = mainlineResult.repository.pullRequests.nodes;
+    if (BUILD_EA) {
+        const eaVariables = {
+            owner: 'yuzu-emu',
+            name: 'yuzu',
+            label: CHANGE_LABEL_EA,
+        };
+        const eaResult = await github.graphql(query, eaVariables);
+        const eaPulls = eaResult.repository.pullRequests.nodes;
+        return pulls.concat(eaPulls);
+    }
+    return pulls;
+}
+
+async function getMainlineTag(execa) {
+    console.log(`::group::Getting mainline tag android-${MAINLINE_TAG}`);
+    let hasFailed = false;
+    try {
+        await execa("git", ["remote", "add", "mainline", "https://github.com/yuzu-emu/yuzu-android.git"]);
+        await execa("git", ["fetch", "mainline", "--tags"]);
+        await execa("git", ["checkout", `tags/android-${MAINLINE_TAG}`]);
+        await execa("git", ["submodule", "update", "--init", "--recursive"]);
+    } catch (err) {
+        console.log('::error title=Failed pull tag');
+        hasFailed = true;
+    }
+    console.log("::endgroup::");
+    if (hasFailed) {
+        throw 'Failed pull mainline tag. Aborting!';
+    }
+}
+
+async function mergebot(github, context, execa) {
+    // Reset our local copy of master to what appears on yuzu-emu/yuzu - master
+    await resetBranch(execa);
+
+    const pulls = await getPulls(github);
     let displayList = [];
     for (let i = 0; i < pulls.length; i++) {
         let pull = pulls[i];
@@ -231,11 +302,17 @@ async function mergebot(github, context, execa) {
     console.table(displayList);
     await fetchPullRequests(pulls, "https://github.com/yuzu-emu/yuzu", execa);
     const mergeResults = await mergePullRequests(pulls, execa);
-    await generateReadme(pulls, context, mergeResults, execa);
-    await tagAndPush(github, 'yuzu-emu', `yuzu-android`, execa, true);
+
+    if (BUILD_EA) {
+        await tagAndPushEA(github, 'yuzu-emu', `yuzu-android`, execa);
+    } else {
+        await generateReadme(pulls, context, mergeResults, execa);
+        await tagAndPush(github, 'yuzu-emu', `yuzu-android`, execa, true);
+    }
 }
 
 module.exports.mergebot = mergebot;
 module.exports.checkAndroidChanges = checkAndroidChanges;
 module.exports.tagAndPush = tagAndPush;
 module.exports.checkBaseChanges = checkBaseChanges;
+module.exports.getMainlineTag = getMainlineTag;

+ 2 - 2
.github/workflows/android-publish.yml

@@ -1,4 +1,4 @@
-# SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+# SPDX-FileCopyrightText: 2024 yuzu Emulator Project
 # SPDX-License-Identifier: GPL-2.0-or-later
 
 name: yuzu-android-publish
@@ -16,7 +16,7 @@ on:
 jobs:
   android:
     runs-on: ubuntu-latest
-    if: ${{ github.event.inputs.android != 'false' && github.repository == 'yuzu-emu/yuzu-android' }}
+    if: ${{ github.event.inputs.android != 'false' && github.repository == 'yuzu-emu/yuzu' }}
     steps:
       # this checkout is required to make sure the GitHub Actions scripts are available
       - uses: actions/checkout@v3

+ 16 - 15
src/android/app/build.gradle.kts

@@ -3,8 +3,8 @@
 
 import android.annotation.SuppressLint
 import kotlin.collections.setOf
-import org.jetbrains.kotlin.konan.properties.Properties
 import org.jlleitschuh.gradle.ktlint.reporter.ReporterType
+import com.github.triplet.gradle.androidpublisher.ReleaseStatus
 
 plugins {
     id("com.android.application")
@@ -13,6 +13,7 @@ plugins {
     kotlin("plugin.serialization") version "1.9.20"
     id("androidx.navigation.safeargs.kotlin")
     id("org.jlleitschuh.gradle.ktlint") version "11.4.0"
+    id("com.github.triplet.play") version "3.8.6"
 }
 
 /**
@@ -58,15 +59,7 @@ android {
         targetSdk = 34
         versionName = getGitVersion()
 
-        // If you want to use autoVersion for the versionCode, create a property in local.properties
-        // named "autoVersioned" and set it to "true"
-        val properties = Properties()
-        val versionProperty = try {
-            properties.load(project.rootProject.file("local.properties").inputStream())
-            properties.getProperty("autoVersioned") ?: ""
-        } catch (e: Exception) { "" }
-
-        versionCode = if (versionProperty == "true") {
+        versionCode = if (System.getenv("AUTO_VERSIONED") == "true") {
             autoVersion
         } else {
             1
@@ -221,6 +214,15 @@ ktlint {
     }
 }
 
+play {
+    val keyPath = System.getenv("SERVICE_ACCOUNT_KEY_PATH")
+    if (keyPath != null) {
+        serviceAccountCredentials.set(File(keyPath))
+    }
+    track.set(System.getenv("STORE_TRACK") ?: "internal")
+    releaseStatus.set(ReleaseStatus.COMPLETED)
+}
+
 dependencies {
     implementation("androidx.core:core-ktx:1.12.0")
     implementation("androidx.appcompat:appcompat:1.6.1")
@@ -257,14 +259,13 @@ fun runGitCommand(command: List<String>): String {
 }
 
 fun getGitVersion(): String {
+    val gitVersion = runGitCommand(listOf("git", "describe", "--always", "--long"))
     val versionName = if (System.getenv("GITHUB_ACTIONS") != null) {
-        val gitTag = System.getenv("GIT_TAG_NAME") ?: ""
-        gitTag
+        System.getenv("GIT_TAG_NAME") ?: gitVersion
     } else {
-        runGitCommand(listOf("git", "describe", "--always", "--long"))
-            .replace(Regex("(-0)?-[^-]+$"), "")
+        gitVersion
     }
-    return versionName.ifEmpty { "0.0" }
+    return versionName.replace(Regex("(-0)?-[^-]+$"), "").ifEmpty { "0.0" }
 }
 
 fun getGitHash(): String =