diff options
| author | 2023-07-03 14:43:54 -0700 | |
|---|---|---|
| committer | 2023-07-03 14:43:54 -0700 | |
| commit | 1fe003113e9b850c3a399a7c05c7fd27d87da69e (patch) | |
| tree | ddb83175f31530b2aa2aec3d4cf50612893523ca | |
| parent | Merge pull request #11007 from zeltermann/dbus-obey-utf8 (diff) | |
| parent | CI: add auto-publishing steps for Android (diff) | |
| download | yuzu-1fe003113e9b850c3a399a7c05c7fd27d87da69e.tar.gz yuzu-1fe003113e9b850c3a399a7c05c7fd27d87da69e.tar.xz yuzu-1fe003113e9b850c3a399a7c05c7fd27d87da69e.zip | |
Merge pull request #10814 from liushuyu/android-pub
CI: auto-publish Android releases
| -rw-r--r-- | .github/workflows/android-build.yml | 79 | ||||
| -rw-r--r-- | .github/workflows/android-merge.js | 218 | ||||
| -rw-r--r-- | .github/workflows/android-publish.yml | 57 | ||||
| -rw-r--r-- | .github/workflows/verify.yml | 4 |
4 files changed, 356 insertions, 2 deletions
diff --git a/.github/workflows/android-build.yml b/.github/workflows/android-build.yml new file mode 100644 index 000000000..e639e965a --- /dev/null +++ b/.github/workflows/android-build.yml | |||
| @@ -0,0 +1,79 @@ | |||
| 1 | # SPDX-FileCopyrightText: 2022 yuzu Emulator Project | ||
| 2 | # SPDX-License-Identifier: GPL-3.0-or-later | ||
| 3 | |||
| 4 | name: 'yuzu-android-build' | ||
| 5 | |||
| 6 | on: | ||
| 7 | push: | ||
| 8 | tags: [ "*" ] | ||
| 9 | |||
| 10 | jobs: | ||
| 11 | android: | ||
| 12 | runs-on: ubuntu-latest | ||
| 13 | steps: | ||
| 14 | - uses: actions/checkout@v3 | ||
| 15 | with: | ||
| 16 | submodules: recursive | ||
| 17 | fetch-depth: 0 | ||
| 18 | - name: Set up JDK 17 | ||
| 19 | uses: actions/setup-java@v3 | ||
| 20 | with: | ||
| 21 | java-version: '17' | ||
| 22 | distribution: 'temurin' | ||
| 23 | - name: Set up cache | ||
| 24 | uses: actions/cache@v3 | ||
| 25 | with: | ||
| 26 | path: | | ||
| 27 | ~/.gradle/caches | ||
| 28 | ~/.gradle/wrapper | ||
| 29 | ~/.ccache | ||
| 30 | key: ${{ runner.os }}-android-${{ github.sha }} | ||
| 31 | restore-keys: | | ||
| 32 | ${{ runner.os }}-android- | ||
| 33 | - name: Query tag name | ||
| 34 | uses: olegtarasov/get-tag@v2.1.2 | ||
| 35 | id: tagName | ||
| 36 | - name: Install dependencies | ||
| 37 | run: | | ||
| 38 | sudo apt-get update | ||
| 39 | sudo apt-get install -y ccache apksigner glslang-dev glslang-tools | ||
| 40 | - name: Build | ||
| 41 | run: ./.ci/scripts/android/build.sh | ||
| 42 | - name: Copy and sign artifacts | ||
| 43 | env: | ||
| 44 | ANDROID_KEYSTORE_B64: ${{ secrets.ANDROID_KEYSTORE_B64 }} | ||
| 45 | ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }} | ||
| 46 | ANDROID_KEYSTORE_PASS: ${{ secrets.ANDROID_KEYSTORE_PASS }} | ||
| 47 | run: ./.ci/scripts/android/upload.sh | ||
| 48 | - name: Upload | ||
| 49 | uses: actions/upload-artifact@v3 | ||
| 50 | with: | ||
| 51 | name: android | ||
| 52 | path: artifacts/ | ||
| 53 | # release steps | ||
| 54 | release-android: | ||
| 55 | runs-on: ubuntu-latest | ||
| 56 | needs: [android] | ||
| 57 | if: ${{ startsWith(github.ref, 'refs/tags/') }} | ||
| 58 | permissions: | ||
| 59 | contents: write | ||
| 60 | steps: | ||
| 61 | - uses: actions/download-artifact@v3 | ||
| 62 | - name: Query tag name | ||
| 63 | uses: olegtarasov/get-tag@v2.1.2 | ||
| 64 | id: tagName | ||
| 65 | - name: Create release | ||
| 66 | uses: actions/create-release@v1 | ||
| 67 | env: | ||
| 68 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
| 69 | with: | ||
| 70 | tag_name: ${{ steps.tagName.outputs.tag }} | ||
| 71 | release_name: ${{ steps.tagName.outputs.tag }} | ||
| 72 | draft: false | ||
| 73 | prerelease: false | ||
| 74 | - name: Upload artifacts | ||
| 75 | uses: alexellis/upload-assets@0.2.3 | ||
| 76 | env: | ||
| 77 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
| 78 | with: | ||
| 79 | asset_paths: '["./**/*.apk","./**/*.aab"]' | ||
diff --git a/.github/workflows/android-merge.js b/.github/workflows/android-merge.js new file mode 100644 index 000000000..7e02dc9e5 --- /dev/null +++ b/.github/workflows/android-merge.js | |||
| @@ -0,0 +1,218 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | // Note: This is a GitHub Actions script | ||
| 5 | // It is not meant to be executed directly on your machine without modifications | ||
| 6 | |||
| 7 | const fs = require("fs"); | ||
| 8 | // which label to check for changes | ||
| 9 | const CHANGE_LABEL = 'android-merge'; | ||
| 10 | // how far back in time should we consider the changes are "recent"? (default: 24 hours) | ||
| 11 | const DETECTION_TIME_FRAME = (parseInt(process.env.DETECTION_TIME_FRAME)) || (24 * 3600 * 1000); | ||
| 12 | |||
| 13 | async function checkBaseChanges(github, context) { | ||
| 14 | // query the commit date of the latest commit on this branch | ||
| 15 | const query = `query($owner:String!, $name:String!, $ref:String!) { | ||
| 16 | repository(name:$name, owner:$owner) { | ||
| 17 | ref(qualifiedName:$ref) { | ||
| 18 | target { | ||
| 19 | ... on Commit { id pushedDate oid } | ||
| 20 | } | ||
| 21 | } | ||
| 22 | } | ||
| 23 | }`; | ||
| 24 | const variables = { | ||
| 25 | owner: context.repo.owner, | ||
| 26 | name: context.repo.repo, | ||
| 27 | ref: 'refs/heads/master', | ||
| 28 | }; | ||
| 29 | const result = await github.graphql(query, variables); | ||
| 30 | const pushedAt = result.repository.ref.target.pushedDate; | ||
| 31 | console.log(`Last commit pushed at ${pushedAt}.`); | ||
| 32 | const delta = new Date() - new Date(pushedAt); | ||
| 33 | if (delta <= DETECTION_TIME_FRAME) { | ||
| 34 | console.info('New changes detected, triggering a new build.'); | ||
| 35 | return true; | ||
| 36 | } | ||
| 37 | console.info('No new changes detected.'); | ||
| 38 | return false; | ||
| 39 | } | ||
| 40 | |||
| 41 | async function checkAndroidChanges(github, context) { | ||
| 42 | if (checkBaseChanges(github, context)) return true; | ||
| 43 | const query = `query($owner:String!, $name:String!, $label:String!) { | ||
| 44 | repository(name:$name, owner:$owner) { | ||
| 45 | pullRequests(labels: [$label], states: OPEN, first: 100) { | ||
| 46 | nodes { number headRepository { pushedAt } } | ||
| 47 | } | ||
| 48 | } | ||
| 49 | }`; | ||
| 50 | const variables = { | ||
| 51 | owner: context.repo.owner, | ||
| 52 | name: context.repo.repo, | ||
| 53 | label: CHANGE_LABEL, | ||
| 54 | }; | ||
| 55 | const result = await github.graphql(query, variables); | ||
| 56 | const pulls = result.repository.pullRequests.nodes; | ||
| 57 | for (let i = 0; i < pulls.length; i++) { | ||
| 58 | let pull = pulls[i]; | ||
| 59 | if (new Date() - new Date(pull.headRepository.pushedAt) <= DETECTION_TIME_FRAME) { | ||
| 60 | console.info(`${pull.number} updated at ${pull.headRepository.pushedAt}`); | ||
| 61 | return true; | ||
| 62 | } | ||
| 63 | } | ||
| 64 | console.info("No changes detected in any tagged pull requests."); | ||
| 65 | return false; | ||
| 66 | } | ||
| 67 | |||
| 68 | async function tagAndPush(github, owner, repo, execa, commit=false) { | ||
| 69 | let altToken = process.env.ALT_GITHUB_TOKEN; | ||
| 70 | if (!altToken) { | ||
| 71 | throw `Please set ALT_GITHUB_TOKEN environment variable. This token should have write access to ${owner}/${repo}.`; | ||
| 72 | } | ||
| 73 | const query = `query ($owner:String!, $name:String!) { | ||
| 74 | repository(name:$name, owner:$owner) { | ||
| 75 | refs(refPrefix: "refs/tags/", orderBy: {field: TAG_COMMIT_DATE, direction: DESC}, first: 10) { | ||
| 76 | nodes { name } | ||
| 77 | } | ||
| 78 | } | ||
| 79 | }`; | ||
| 80 | const variables = { | ||
| 81 | owner: owner, | ||
| 82 | name: repo, | ||
| 83 | }; | ||
| 84 | const tags = await github.graphql(query, variables); | ||
| 85 | const tagList = tags.repository.refs.nodes; | ||
| 86 | const lastTag = tagList[0] ? tagList[0].name : 'dummy-0'; | ||
| 87 | const tagNumber = /\w+-(\d+)/.exec(lastTag)[1] | 0; | ||
| 88 | const channel = repo.split('-')[1]; | ||
| 89 | const newTag = `${channel}-${tagNumber + 1}`; | ||
| 90 | console.log(`New tag: ${newTag}`); | ||
| 91 | if (commit) { | ||
| 92 | let channelName = channel[0].toUpperCase() + channel.slice(1); | ||
| 93 | console.info(`Committing pending commit as ${channelName} #${tagNumber + 1}`); | ||
| 94 | await execa("git", ['commit', '-m', `${channelName} #${tagNumber + 1}`]); | ||
| 95 | } | ||
| 96 | console.info('Pushing tags to GitHub ...'); | ||
| 97 | await execa("git", ['tag', newTag]); | ||
| 98 | await execa("git", ['remote', 'add', 'target', `https://${altToken}@github.com/${owner}/${repo}.git`]); | ||
| 99 | await execa("git", ['push', 'target', 'master', '-f']); | ||
| 100 | await execa("git", ['push', 'target', 'master', '--tags']); | ||
| 101 | console.info('Successfully pushed new changes.'); | ||
| 102 | } | ||
| 103 | |||
| 104 | async function generateReadme(pulls, context, mergeResults, execa) { | ||
| 105 | let baseUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/`; | ||
| 106 | let output = | ||
| 107 | "| Pull Request | Commit | Title | Author | Merged? |\n|----|----|----|----|----|\n"; | ||
| 108 | for (let pull of pulls) { | ||
| 109 | let pr = pull.number; | ||
| 110 | let result = mergeResults[pr]; | ||
| 111 | output += `| [${pr}](${baseUrl}/pull/${pr}) | [\`${result.rev || "N/A"}\`](${baseUrl}/pull/${pr}/files) | ${pull.title} | [${pull.author.login}](https://github.com/${pull.author.login}/) | ${result.success ? "Yes" : "No"} |\n`; | ||
| 112 | } | ||
| 113 | output += | ||
| 114 | "\n\nEnd of merge log. You can find the original README.md below the break.\n\n-----\n\n"; | ||
| 115 | output += fs.readFileSync("./README.md"); | ||
| 116 | fs.writeFileSync("./README.md", output); | ||
| 117 | await execa("git", ["add", "README.md"]); | ||
| 118 | } | ||
| 119 | |||
| 120 | async function fetchPullRequests(pulls, repoUrl, execa) { | ||
| 121 | console.log("::group::Fetch pull requests"); | ||
| 122 | for (let pull of pulls) { | ||
| 123 | let pr = pull.number; | ||
| 124 | console.info(`Fetching PR ${pr} ...`); | ||
| 125 | await execa("git", [ | ||
| 126 | "fetch", | ||
| 127 | "-f", | ||
| 128 | "--no-recurse-submodules", | ||
| 129 | repoUrl, | ||
| 130 | `pull/${pr}/head:pr-${pr}`, | ||
| 131 | ]); | ||
| 132 | } | ||
| 133 | console.log("::endgroup::"); | ||
| 134 | } | ||
| 135 | |||
| 136 | async function mergePullRequests(pulls, execa) { | ||
| 137 | let mergeResults = {}; | ||
| 138 | console.log("::group::Merge pull requests"); | ||
| 139 | await execa("git", ["config", "--global", "user.name", "yuzubot"]); | ||
| 140 | await execa("git", [ | ||
| 141 | "config", | ||
| 142 | "--global", | ||
| 143 | "user.email", | ||
| 144 | "yuzu\x40yuzu-emu\x2eorg", // prevent email harvesters from scraping the address | ||
| 145 | ]); | ||
| 146 | let hasFailed = false; | ||
| 147 | for (let pull of pulls) { | ||
| 148 | let pr = pull.number; | ||
| 149 | console.info(`Merging PR ${pr} ...`); | ||
| 150 | try { | ||
| 151 | const process1 = execa("git", [ | ||
| 152 | "merge", | ||
| 153 | "--squash", | ||
| 154 | "--no-edit", | ||
| 155 | `pr-${pr}`, | ||
| 156 | ]); | ||
| 157 | process1.stdout.pipe(process.stdout); | ||
| 158 | await process1; | ||
| 159 | |||
| 160 | const process2 = execa("git", ["commit", "-m", `Merge PR ${pr}`]); | ||
| 161 | process2.stdout.pipe(process.stdout); | ||
| 162 | await process2; | ||
| 163 | |||
| 164 | const process3 = await execa("git", ["rev-parse", "--short", `pr-${pr}`]); | ||
| 165 | mergeResults[pr] = { | ||
| 166 | success: true, | ||
| 167 | rev: process3.stdout, | ||
| 168 | }; | ||
| 169 | } catch (err) { | ||
| 170 | console.log( | ||
| 171 | `::error title=#${pr} not merged::Failed to merge pull request: ${pr}: ${err}` | ||
| 172 | ); | ||
| 173 | mergeResults[pr] = { success: false }; | ||
| 174 | hasFailed = true; | ||
| 175 | await execa("git", ["reset", "--hard"]); | ||
| 176 | } | ||
| 177 | } | ||
| 178 | console.log("::endgroup::"); | ||
| 179 | if (hasFailed) { | ||
| 180 | throw 'There are merge failures. Aborting!'; | ||
| 181 | } | ||
| 182 | return mergeResults; | ||
| 183 | } | ||
| 184 | |||
| 185 | async function mergebot(github, context, execa) { | ||
| 186 | const query = `query ($owner:String!, $name:String!, $label:String!) { | ||
| 187 | repository(name:$name, owner:$owner) { | ||
| 188 | pullRequests(labels: [$label], states: OPEN, first: 100) { | ||
| 189 | nodes { | ||
| 190 | number title author { login } | ||
| 191 | } | ||
| 192 | } | ||
| 193 | } | ||
| 194 | }`; | ||
| 195 | const variables = { | ||
| 196 | owner: context.repo.owner, | ||
| 197 | name: context.repo.repo, | ||
| 198 | label: CHANGE_LABEL, | ||
| 199 | }; | ||
| 200 | const result = await github.graphql(query, variables); | ||
| 201 | const pulls = result.repository.pullRequests.nodes; | ||
| 202 | let displayList = []; | ||
| 203 | for (let i = 0; i < pulls.length; i++) { | ||
| 204 | let pull = pulls[i]; | ||
| 205 | displayList.push({ PR: pull.number, Title: pull.title }); | ||
| 206 | } | ||
| 207 | console.info("The following pull requests will be merged:"); | ||
| 208 | console.table(displayList); | ||
| 209 | await fetchPullRequests(pulls, "https://github.com/yuzu-emu/yuzu", execa); | ||
| 210 | const mergeResults = await mergePullRequests(pulls, execa); | ||
| 211 | await generateReadme(pulls, context, mergeResults, execa); | ||
| 212 | await tagAndPush(github, context.repo.owner, `${context.repo.repo}-android`, execa, true); | ||
| 213 | } | ||
| 214 | |||
| 215 | module.exports.mergebot = mergebot; | ||
| 216 | module.exports.checkAndroidChanges = checkAndroidChanges; | ||
| 217 | module.exports.tagAndPush = tagAndPush; | ||
| 218 | module.exports.checkBaseChanges = checkBaseChanges; | ||
diff --git a/.github/workflows/android-publish.yml b/.github/workflows/android-publish.yml new file mode 100644 index 000000000..8f46fcf74 --- /dev/null +++ b/.github/workflows/android-publish.yml | |||
| @@ -0,0 +1,57 @@ | |||
| 1 | # SPDX-FileCopyrightText: 2023 yuzu Emulator Project | ||
| 2 | # SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | name: yuzu-android-publish | ||
| 5 | |||
| 6 | on: | ||
| 7 | schedule: | ||
| 8 | - cron: '37 0 * * *' | ||
| 9 | workflow_dispatch: | ||
| 10 | inputs: | ||
| 11 | android: | ||
| 12 | description: 'Whether to trigger an Android build (true/false/auto)' | ||
| 13 | required: false | ||
| 14 | default: 'true' | ||
| 15 | |||
| 16 | jobs: | ||
| 17 | android: | ||
| 18 | runs-on: ubuntu-latest | ||
| 19 | if: ${{ github.event.inputs.android != 'false' && github.repository == 'yuzu-emu/yuzu' }} | ||
| 20 | steps: | ||
| 21 | # this checkout is required to make sure the GitHub Actions scripts are available | ||
| 22 | - uses: actions/checkout@v3 | ||
| 23 | name: Pre-checkout | ||
| 24 | with: | ||
| 25 | submodules: false | ||
| 26 | - uses: actions/github-script@v6 | ||
| 27 | id: check-changes | ||
| 28 | name: 'Check for new changes' | ||
| 29 | env: | ||
| 30 | # 24 hours | ||
| 31 | DETECTION_TIME_FRAME: 86400000 | ||
| 32 | with: | ||
| 33 | script: | | ||
| 34 | if (context.payload.inputs && context.payload.inputs.android === 'true') return true; | ||
| 35 | const checkAndroidChanges = require('./.github/workflows/android-merge.js').checkAndroidChanges; | ||
| 36 | return checkAndroidChanges(github, context); | ||
| 37 | - run: npm install execa@5 | ||
| 38 | if: ${{ steps.check-changes.outputs.result == 'true' }} | ||
| 39 | - uses: actions/checkout@v3 | ||
| 40 | name: Checkout | ||
| 41 | if: ${{ steps.check-changes.outputs.result == 'true' }} | ||
| 42 | with: | ||
| 43 | path: 'yuzu-merge' | ||
| 44 | fetch-depth: 0 | ||
| 45 | submodules: true | ||
| 46 | token: ${{ secrets.ALT_GITHUB_TOKEN }} | ||
| 47 | - uses: actions/github-script@v5 | ||
| 48 | name: 'Check and merge Android changes' | ||
| 49 | if: ${{ steps.check-changes.outputs.result == 'true' }} | ||
| 50 | env: | ||
| 51 | ALT_GITHUB_TOKEN: ${{ secrets.ALT_GITHUB_TOKEN }} | ||
| 52 | with: | ||
| 53 | script: | | ||
| 54 | const execa = require("execa"); | ||
| 55 | const mergebot = require('./.github/workflows/android-merge.js').mergebot; | ||
| 56 | process.chdir('${{ github.workspace }}/yuzu-merge'); | ||
| 57 | mergebot(github, context, execa); | ||
diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml index bd4141f56..b5d338199 100644 --- a/.github/workflows/verify.yml +++ b/.github/workflows/verify.yml | |||
| @@ -129,11 +129,12 @@ jobs: | |||
| 129 | - uses: actions/checkout@v3 | 129 | - uses: actions/checkout@v3 |
| 130 | with: | 130 | with: |
| 131 | submodules: recursive | 131 | submodules: recursive |
| 132 | fetch-depth: 0 | ||
| 132 | - name: set up JDK 17 | 133 | - name: set up JDK 17 |
| 133 | uses: actions/setup-java@v3 | 134 | uses: actions/setup-java@v3 |
| 134 | with: | 135 | with: |
| 135 | java-version: '17' | 136 | java-version: '17' |
| 136 | distribution: 'adopt' | 137 | distribution: 'temurin' |
| 137 | - name: Set up cache | 138 | - name: Set up cache |
| 138 | uses: actions/cache@v3 | 139 | uses: actions/cache@v3 |
| 139 | with: | 140 | with: |
| @@ -151,7 +152,6 @@ jobs: | |||
| 151 | run: | | 152 | run: | |
| 152 | sudo apt-get update | 153 | sudo apt-get update |
| 153 | sudo apt-get install -y ccache apksigner glslang-dev glslang-tools | 154 | sudo apt-get install -y ccache apksigner glslang-dev glslang-tools |
| 154 | git -C ./externals/vcpkg/ fetch --all --unshallow | ||
| 155 | - name: Build | 155 | - name: Build |
| 156 | run: ./.ci/scripts/android/build.sh | 156 | run: ./.ci/scripts/android/build.sh |
| 157 | - name: Copy and sign artifacts | 157 | - name: Copy and sign artifacts |