mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-11-12 11:31:08 +00:00
Cemu never showed a frame on DroidDeck: its Getting Started dialog blocked every first launch (the folder picker cannot reach ROMs), the session's controller helper aborted it, and the desktop's Vulkan renderer drew no cursor. With the Turnip KGSL fix in the driver and the haptic-optional Cemu in the catalog, this makes the app side complete: - bannerlator-pad-defaults seeds Cemu's settings.xml once: the Wii U games folder, Vulkan, and Cubeb audio on the default device for both the TV and the GamePad speaker at full volume (Wipeout 3 plays through the GamePad; Cemu's own default is 20 %). - The Cemu tile launches full screen (-f). - libfakeinput.so is preloaded into every session process and exported its static C++ runtime (~7000 symbols); programs unwound through it and aborted (shadPS4 in pthread_exit, Cemu's controller thread). Link with --exclude-libs,ALL, and fail the build if the runtime leaks out again. - On the Vulkan and GLES desktops the pointer worked but no cursor was drawn: wlroots hands the cursor to the app's compositor, which draws none. WLR_NO_HARDWARE_CURSORS=1 makes labwc composite it itself. Device-tested on the FIT: Super Mario 3D World in-game at 35-37 fps with sound, Deus Ex HR DC and Wipeout 3 launch with sound; RPCS3, Dolphin and a Steam session (FlatOut) unchanged on the same driver.
432 lines
19 KiB
YAML
432 lines
19 KiB
YAML
name: Build APK
|
|
run-name: ${{ github.event_name == 'pull_request' && format('building pr {0} on {1}', github.event.pull_request.number, github.event.pull_request.head.sha) || format('building {0} on {1}', github.ref_name, github.sha) }}
|
|
|
|
on:
|
|
push:
|
|
branches: [ main ]
|
|
paths-ignore:
|
|
- '**/*.md'
|
|
- 'LICENSE'
|
|
- '.gitignore'
|
|
pull_request:
|
|
branches: [ main ]
|
|
types: [ opened, synchronize, reopened, ready_for_review ]
|
|
paths-ignore:
|
|
- '**/*.md'
|
|
- 'LICENSE'
|
|
- '.gitignore'
|
|
workflow_dispatch:
|
|
|
|
permissions:
|
|
contents: read
|
|
pull-requests: write
|
|
|
|
concurrency:
|
|
group: build-${{ github.event.pull_request.number || github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
env:
|
|
PRELOAD_CACHE_VERSION: v2
|
|
|
|
jobs:
|
|
build:
|
|
name: Build APK
|
|
if: ${{ github.event_name != 'pull_request' || !github.event.pull_request.draft }}
|
|
runs-on: ubuntu-24.04
|
|
timeout-minutes: 45
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
with:
|
|
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
|
|
fetch-depth: 0
|
|
|
|
- name: No private key in the tree
|
|
run: |
|
|
set -euo pipefail
|
|
keys=$(git ls-files | grep -iE '\.(jks|keystore|p12|pfx|pk8|pem|key)$' | grep -vx 'keystore/testkey.p12' || true)
|
|
if [ -n "$keys" ]; then
|
|
echo "::error::a private key is committed - remove it from the history and make a new one: $keys"
|
|
exit 1
|
|
fi
|
|
|
|
- name: Merge onto main, or say what conflicts
|
|
if: github.event_name == 'pull_request'
|
|
run: |
|
|
set -uo pipefail
|
|
git config user.name "ci"
|
|
git config user.email "ci@users.noreply.github.com"
|
|
git fetch --no-tags origin main
|
|
BASE=$(git rev-parse --short origin/main)
|
|
HEAD=$(git rev-parse --short HEAD)
|
|
if git merge --no-ff --no-edit origin/main >/tmp/merge.out 2>&1; then
|
|
{
|
|
echo "### Mergeable"
|
|
echo
|
|
echo "\`$HEAD\` merges cleanly onto main (\`$BASE\`). What follows was built from the merged result."
|
|
} > /tmp/pr-merge.md
|
|
cat /tmp/pr-merge.md >> "$GITHUB_STEP_SUMMARY"
|
|
exit 0
|
|
fi
|
|
FILES=$(git diff --name-only --diff-filter=U)
|
|
if [ -z "$FILES" ]; then
|
|
{
|
|
echo "### ERROR: Could not merge onto main (\`$BASE\`)"
|
|
echo
|
|
echo '```'
|
|
cat /tmp/merge.out
|
|
echo '```'
|
|
} > /tmp/pr-merge.md
|
|
cat /tmp/pr-merge.md >> "$GITHUB_STEP_SUMMARY"
|
|
echo "::error::git could not merge main: $(head -3 /tmp/merge.out | tr '\n' ' ')"
|
|
exit 1
|
|
fi
|
|
{
|
|
echo "### ERROR: Not mergeable - conflicts with main (\`$BASE\`)"
|
|
echo
|
|
echo "Files that conflict:"
|
|
echo
|
|
for f in $FILES; do echo "- \`$f\`"; done
|
|
echo
|
|
echo "Each conflicting hunk, as git leaves it: \`<<<<<<< HEAD\` is this pull request, \`>>>>>>> origin/main\` is main."
|
|
echo
|
|
for f in $FILES; do
|
|
echo "<details><summary><code>$f</code></summary>"
|
|
echo
|
|
echo '```'
|
|
awk '/^<<<<<<< /{p=1} p{print} /^>>>>>>> /{p=0}' "$f" | head -300
|
|
echo '```'
|
|
echo "</details>"
|
|
echo
|
|
done
|
|
echo "Not built: rebase or merge main into the branch and push again."
|
|
} > /tmp/pr-merge.md
|
|
cat /tmp/pr-merge.md >> "$GITHUB_STEP_SUMMARY"
|
|
git merge --abort || true
|
|
echo "::error::the pull request conflicts with main: $(echo $FILES | tr '\n' ' ')"
|
|
exit 1
|
|
|
|
- uses: actions/setup-java@v4
|
|
with:
|
|
distribution: temurin
|
|
java-version: '17'
|
|
|
|
- uses: gradle/actions/setup-gradle@v4
|
|
with:
|
|
cache-cleanup: on-success
|
|
|
|
- name: Resolve the runner's NDK
|
|
run: |
|
|
NDK="${ANDROID_NDK_LATEST_HOME:-${ANDROID_NDK_HOME:-$ANDROID_NDK_ROOT}}"
|
|
VERSION=$(sed -n 's/^Pkg.Revision *= *//p' "$NDK/source.properties")
|
|
echo "NDK_VERSION=$VERSION" >> "$GITHUB_ENV"
|
|
|
|
- name: Set the APK build label
|
|
id: build-label
|
|
env:
|
|
PR_NUMBER: ${{ github.event.pull_request.number }}
|
|
PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
|
BUILD_REF: ${{ github.ref_name }}
|
|
BUILD_SHA: ${{ github.sha }}
|
|
run: |
|
|
if [[ -n "$(git status --porcelain)" ]]; then
|
|
tree_state=dirty
|
|
else
|
|
tree_state=clean
|
|
fi
|
|
echo "DROIDDECK_BUILD_TREE_STATE=$tree_state" >> "$GITHUB_ENV"
|
|
if [[ -n "$PR_NUMBER" ]]; then
|
|
label="pr${PR_NUMBER} at ${PR_HEAD_SHA:0:7}"
|
|
else
|
|
label="${BUILD_REF} at ${BUILD_SHA:0:7}"
|
|
fi
|
|
echo "label=$label" >> "$GITHUB_OUTPUT"
|
|
echo "Build: \`$label\`" >> "$GITHUB_STEP_SUMMARY"
|
|
|
|
- name: Cache the preloads
|
|
id: preloads
|
|
uses: actions/cache@v4
|
|
with:
|
|
path: app/src/main/assets/linuxfs
|
|
key: preloads-${{ env.PRELOAD_CACHE_VERSION }}-${{ hashFiles('app/src/main/cpp/fakeinput_steam.cpp', 'tools/linuxfs/preload/*.c', 'tools/linuxfs/overlay/usr/local/bin/*', 'tools/linuxfs/overlay/usr/bin/**', 'tools/linuxfs/desktop/*', 'tools/gamescope/release.env', 'tools/wlroots/release.env', 'tools/mangoapp/*') }}
|
|
|
|
- name: Build libfakeinput.so and libblsession.so (glibc aarch64)
|
|
if: steps.preloads.outputs.cache-hit != 'true'
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
run: |
|
|
set -euo pipefail
|
|
sudo apt-get update -qq
|
|
sudo apt-get install -y -qq gcc-aarch64-linux-gnu g++-aarch64-linux-gnu >/dev/null
|
|
D=app/src/main/assets/linuxfs
|
|
mkdir -p "$D"
|
|
aarch64-linux-gnu-g++ -shared -fPIC -O2 -Wall -Wno-attributes -Wno-nonnull-compare \
|
|
-pthread -std=c++17 -static-libstdc++ -static-libgcc -Wl,--exclude-libs,ALL \
|
|
-o "$D/libfakeinput.so" app/src/main/cpp/fakeinput_steam.cpp -ldl
|
|
aarch64-linux-gnu-strip --strip-unneeded "$D/libfakeinput.so"
|
|
aarch64-linux-gnu-gcc -shared -fPIC -O2 -Wall -pthread \
|
|
-o "$D/libblsession.so" tools/linuxfs/preload/*.c -ldl
|
|
aarch64-linux-gnu-strip --strip-unneeded "$D/libblsession.so"
|
|
for script in tools/linuxfs/overlay/usr/local/bin/bannerlator-*; do
|
|
install -Dm644 "$script" "$D/usr/local/bin/$(basename "$script")"
|
|
done
|
|
for f in tools/linuxfs/overlay/usr/bin/* tools/linuxfs/overlay/usr/bin/steamos-polkit-helpers/*; do
|
|
[ -f "$f" ] && install -Dm644 "$f" "$D/${f#tools/linuxfs/overlay/}"
|
|
done
|
|
if [ -f tools/gamescope/release.env ]; then
|
|
. tools/gamescope/release.env
|
|
gh release download "${GAMESCOPE_TAG}" -R "${GITHUB_REPOSITORY}" -p gamescope.tzst -O gamescope.tzst
|
|
echo "${GAMESCOPE_SHA256} gamescope.tzst" | sha256sum -c -
|
|
tar --use-compress-program=unzstd -xf gamescope.tzst -C "$D"
|
|
test -f "$D/usr/local/bin/gamescope"
|
|
rm gamescope.tzst
|
|
fi
|
|
if [ -f tools/wlroots/release.env ]; then
|
|
. tools/wlroots/release.env
|
|
gh release download "${WLROOTS_TAG}" -R "${GITHUB_REPOSITORY}" -p wlroots.tzst -O wlroots.tzst
|
|
echo "${WLROOTS_SHA256} wlroots.tzst" | sha256sum -c -
|
|
tar --use-compress-program=unzstd -xf wlroots.tzst -C "$D"
|
|
test -f "$D/usr/local/lib/droiddeck-wlroots/libwlroots-0.20.so"
|
|
rm wlroots.tzst
|
|
fi
|
|
M="$D/usr/local/lib/mangoapp"
|
|
mkdir -p "$M" mango-pkgs
|
|
grep -v '^#' tools/mangoapp/packages.txt | while read -r sum url; do
|
|
[ -n "$url" ] || continue
|
|
curl -fsSL --retry 3 -o mango-pkgs/pkg.tar.zst "$url"
|
|
echo "$sum mango-pkgs/pkg.tar.zst" | sha256sum -c -
|
|
tar --use-compress-program=unzstd -xf mango-pkgs/pkg.tar.zst -C mango-pkgs
|
|
rm mango-pkgs/pkg.tar.zst
|
|
done
|
|
install -m644 mango-pkgs/usr/bin/mangoapp "$M/mangoapp"
|
|
for so in libfmt.so.10 libspdlog.so.1.13 libglfw.so.3 libtraceevent.so.1 libtracefs.so.1; do
|
|
cp -L "mango-pkgs/usr/lib/$so" "$M/$so"
|
|
done
|
|
install -Dm644 tools/mangoapp/mangoapp "$D/usr/local/bin/mangoapp"
|
|
rm -rf mango-pkgs
|
|
ls -l "$M"
|
|
install -Dm644 tools/linuxfs/desktop/droiddeck-desktop "$D/usr/local/bin/droiddeck-desktop"
|
|
install -Dm644 tools/linuxfs/desktop/droiddeck-gpu "$D/usr/local/bin/droiddeck-gpu"
|
|
install -Dm644 tools/linuxfs/desktop/droiddeck-desktop-gpu "$D/usr/local/bin/droiddeck-desktop-gpu"
|
|
install -Dm644 tools/linuxfs/desktop/autostart "$D/etc/xdg/labwc/autostart"
|
|
install -Dm644 tools/linuxfs/desktop/rc.xml "$D/etc/xdg/labwc/rc.xml"
|
|
install -Dm644 tools/linuxfs/desktop/panel.conf "$D/etc/xdg/lxqt/panel.conf"
|
|
install -Dm644 tools/linuxfs/desktop/firefox-droiddeck.js "$D/usr/lib/firefox/defaults/pref/droiddeck.js"
|
|
ls -lR "$D"
|
|
|
|
- name: Cache PulseAudio 13.0 source
|
|
id: pa-src
|
|
uses: actions/cache@v4
|
|
with:
|
|
path: pa13
|
|
key: pulseaudio-src-13.0
|
|
- name: Fetch PulseAudio 13.0 source
|
|
if: steps.pa-src.outputs.cache-hit != 'true'
|
|
run: |
|
|
set -euo pipefail
|
|
curl -fsSL -o pa.tar.gz https://github.com/pulseaudio/pulseaudio/archive/refs/tags/v13.0.tar.gz
|
|
mkdir -p pa13 && tar -xzf pa.tar.gz -C pa13 --strip-components=1 && rm pa.tar.gz
|
|
- name: Build the AAudio sink into the audio bundle
|
|
run: |
|
|
set -euo pipefail
|
|
export NDK="${ANDROID_NDK_LATEST_HOME:-${ANDROID_NDK_HOME:-$ANDROID_NDK_ROOT}}"
|
|
tools/aaudio-sink/build.sh "$PWD/pa13" "$PWD/sink-out"
|
|
rm -rf pa-bundle && mkdir -p pa-bundle
|
|
tar --use-compress-program=unzstd -xf app/src/main/assets/pulseaudio.tzst -C pa-bundle
|
|
test ! -f pa-bundle/modules/arm64/module-aaudio-sink.so
|
|
install -m755 sink-out/module-aaudio-sink.so pa-bundle/modules/arm64/module-aaudio-sink.so
|
|
install -m755 sink-out/module-directaudio-sink.so pa-bundle/modules/arm64/module-directaudio-sink.so
|
|
(cd pa-bundle && tar --use-compress-program='zstd -19' -cf ../pulseaudio.tzst .)
|
|
mv pulseaudio.tzst app/src/main/assets/pulseaudio.tzst
|
|
tar --use-compress-program=unzstd -tf app/src/main/assets/pulseaudio.tzst | grep -c '\.so$'
|
|
rm -rf pa-bundle sink-out
|
|
|
|
- name: Cache proot
|
|
id: proot
|
|
uses: actions/cache@v4
|
|
with:
|
|
path: |
|
|
app/src/main/jniLibs/arm64-v8a/libproot.so
|
|
app/src/main/jniLibs/arm64-v8a/libproot-loader.so
|
|
key: proot-${{ env.NDK_VERSION }}-${{ hashFiles('tools/proot/**') }}
|
|
- name: Build proot
|
|
if: steps.proot.outputs.cache-hit != 'true'
|
|
run: |
|
|
set -euo pipefail
|
|
export NDK="${ANDROID_NDK_LATEST_HOME:-${ANDROID_NDK_HOME:-$ANDROID_NDK_ROOT}}"
|
|
tools/proot/build.sh app/src/main/jniLibs/arm64-v8a
|
|
|
|
- name: Check the preloads
|
|
run: |
|
|
set -euo pipefail
|
|
D=app/src/main/assets/linuxfs
|
|
NEED=$(readelf -d "$D/libfakeinput.so" | sed -n 's/.*NEEDED.*\[\(.*\)\]/\1/p')
|
|
for bad in libstdc++.so.6 libgcc_s.so.1; do
|
|
if printf '%s\n' "$NEED" | grep -qx "$bad"; then
|
|
echo "::error::libfakeinput.so links $bad - the C++ runtime must stay static"; exit 1
|
|
fi
|
|
done
|
|
syms() { readelf -Ws "$1" | awk '$4 == "FUNC" && $5 == "GLOBAL" {sub(/@.*/, "", $8); print $8}'; }
|
|
FAKE=$(syms "$D/libfakeinput.so")
|
|
for sym in open openat ioctl read close poll ppoll select stat fstat access scandir; do
|
|
grep -qx "$sym" <<<"$FAKE" || { echo "::error::libfakeinput.so does not export $sym"; exit 1; }
|
|
done
|
|
# The static C++ runtime stays private: every session process preloads this library,
|
|
# and an exported personality routine takes over other programs' unwinding (shadPS4
|
|
# aborted in pthread_exit).
|
|
if grep -qxE '__gxx_personality_v0|__cxa_throw|_Unwind_Resume' <<<"$FAKE"; then
|
|
echo "::error::libfakeinput.so exports the C++ runtime"; exit 1
|
|
fi
|
|
SESSION=$(syms "$D/libblsession.so")
|
|
for sym in socket bind getsockname setsockopt statfs statvfs; do
|
|
grep -qx "$sym" <<<"$SESSION" || { echo "::error::libblsession.so does not export $sym"; exit 1; }
|
|
done
|
|
test -f "$D/usr/local/bin/bannerlator-session"
|
|
test -f "$D/usr/local/bin/bannerlator-proton-extra"
|
|
|
|
- name: Cache the CMake build tree
|
|
uses: actions/cache@v4
|
|
with:
|
|
path: |
|
|
app/.cxx
|
|
app/build/intermediates/cxx
|
|
key: cxx-${{ env.NDK_VERSION }}-${{ hashFiles('app/src/main/cpp/**') }}
|
|
restore-keys: |
|
|
cxx-${{ env.NDK_VERSION }}-
|
|
|
|
- name: Build
|
|
id: build
|
|
env:
|
|
DROIDDECK_BUILD_LABEL: ${{ steps.build-label.outputs.label }}
|
|
run: |
|
|
set -o pipefail
|
|
./gradlew assembleRelease --console=plain -PndkVersion="$NDK_VERSION" 2>&1 | tee /tmp/build.log
|
|
|
|
- name: Unit tests
|
|
run: ./gradlew testReleaseUnitTest --console=plain -PndkVersion="$NDK_VERSION"
|
|
|
|
- name: Check the native dependency closure
|
|
run: |
|
|
set -euo pipefail
|
|
APK=$(find app/build/outputs/apk -name '*.apk' | head -1)
|
|
WORK=$(mktemp -d)
|
|
unzip -q "$APK" 'lib/arm64-v8a/*' -d "$WORK"
|
|
cd "$WORK/lib/arm64-v8a"
|
|
SYSTEM="libc.so libm.so libdl.so liblog.so libandroid.so libz.so libvulkan.so libstdc++.so
|
|
libGLESv2.so libEGL.so libnativewindow.so libjnigraphics.so libaaudio.so
|
|
libOpenSLES.so libmediandk.so libcamera2ndk.so libsync.so libneuralnetworks.so"
|
|
fail=0
|
|
for so in *.so; do
|
|
for need in $(readelf -d "$so" | sed -n 's/.*NEEDED.*\[\(.*\)\]/\1/p'); do
|
|
[ -f "$need" ] && continue
|
|
case " $(echo $SYSTEM) " in *" $need "*) continue ;; esac
|
|
echo "::error::$so needs $need, which is neither in the apk nor a platform library"
|
|
fail=1
|
|
done
|
|
done
|
|
exit $fail
|
|
|
|
- name: Sign with the testkey (v1 + v2 + v3)
|
|
run: |
|
|
set -euo pipefail
|
|
APK=$(find app/build/outputs/apk -name '*.apk' | head -1)
|
|
TOOLS=$(find "$ANDROID_HOME/build-tools" -maxdepth 1 -type d | sort -V | tail -1)
|
|
"$TOOLS/zipalign" -p -f 4 "$APK" "$APK.aligned"
|
|
"$TOOLS/apksigner" sign \
|
|
--ks keystore/testkey.p12 --ks-type PKCS12 --ks-pass pass:android \
|
|
--ks-key-alias testkey --key-pass pass:android \
|
|
--v1-signing-enabled true --v2-signing-enabled true --v3-signing-enabled true \
|
|
--out "$APK" "$APK.aligned"
|
|
rm -f "$APK.aligned" "$APK.idsig"
|
|
|
|
- name: Check the signature
|
|
run: |
|
|
set -euo pipefail
|
|
APK=$(find app/build/outputs/apk -name '*.apk' | head -1)
|
|
APKSIGNER=$(find "$ANDROID_HOME/build-tools" -name apksigner | sort -V | tail -1)
|
|
"$APKSIGNER" verify --min-sdk-version 21 --verbose --print-certs "$APK" | tee /tmp/sig.txt
|
|
unzip -l "$APK" | grep -qE 'META-INF/.*\.(SF|RSA|DSA)$' \
|
|
|| { echo "::error::apk carries no JAR signature files"; exit 1; }
|
|
for scheme in "v1 scheme (JAR signing): true" "v2 scheme (APK Signature Scheme v2): true" \
|
|
"v3 scheme (APK Signature Scheme v3): true"; do
|
|
grep -qF "$scheme" /tmp/sig.txt || { echo "::error::apk is not signed with $scheme"; exit 1; }
|
|
done
|
|
grep -qi "CN=Android, OU=Android, O=Android" /tmp/sig.txt \
|
|
|| { echo "::error::apk is not signed with the AOSP testkey"; exit 1; }
|
|
|
|
- name: Report on the pull request
|
|
if: always() && github.event_name == 'pull_request'
|
|
continue-on-error: true
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
PR: ${{ github.event.pull_request.number }}
|
|
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
|
BUILD_OUTCOME: ${{ steps.build.outcome }}
|
|
run: |
|
|
set -uo pipefail
|
|
MARK="<!-- droiddeck-pr-build -->"
|
|
{
|
|
echo "$MARK"
|
|
echo "## Build of this pull request"
|
|
echo
|
|
[ -f /tmp/pr-merge.md ] && cat /tmp/pr-merge.md
|
|
echo
|
|
case "$BUILD_OUTCOME" in
|
|
success)
|
|
echo "### Build passed"
|
|
echo
|
|
echo "The \`droiddeck-apk\` artifact is [on this run]($RUN_URL), signed with the public test key. After approval, a maintainer can run **PR Release Publish** from \`main\` to create an APK that installs over the release."
|
|
;;
|
|
failure)
|
|
echo "### ERROR: Build failed"
|
|
echo
|
|
echo "The compiler's errors ([full log]($RUN_URL)):"
|
|
echo
|
|
echo '```'
|
|
{ grep -E '^e: |^w: .*error|error:|CMake Error|fatal error|ninja: error|FAILED|What went wrong|Execution failed for task|Unresolved reference|Type mismatch|Unresolved' /tmp/build.log 2>/dev/null \
|
|
| sed -e 's#file:///home/runner/work/[^/]*/[^/]*/##' -e 's#/home/runner/work/[^/]*/[^/]*/##' \
|
|
| grep -v '^w: ' | awk '!seen[$0]++' | head -80; } || true
|
|
echo '```'
|
|
echo
|
|
echo "<details><summary>What went wrong (Gradle)</summary>"
|
|
echo
|
|
echo '```'
|
|
awk '/^\* What went wrong/{p=1} /^\* Try:/{p=0} p{print}' /tmp/build.log 2>/dev/null | head -60
|
|
echo '```'
|
|
echo "</details>"
|
|
;;
|
|
skipped|cancelled)
|
|
echo "### Not built"
|
|
echo
|
|
echo "The build did not run (see above, or [the run]($RUN_URL))."
|
|
;;
|
|
esac
|
|
echo
|
|
} > /tmp/pr-report.md
|
|
cat /tmp/pr-report.md >> "$GITHUB_STEP_SUMMARY"
|
|
ID=$(gh api "repos/${GITHUB_REPOSITORY}/issues/${PR}/comments" --paginate --jq ".[] | select(.body | startswith(\"$MARK\")) | .id" | head -1 || true)
|
|
if [ -n "$ID" ]; then
|
|
gh api -X PATCH "repos/${GITHUB_REPOSITORY}/issues/comments/${ID}" -F body=@/tmp/pr-report.md >/dev/null
|
|
else
|
|
gh api -X POST "repos/${GITHUB_REPOSITORY}/issues/${PR}/comments" -F body=@/tmp/pr-report.md >/dev/null
|
|
fi
|
|
|
|
- uses: actions/upload-artifact@v4
|
|
with:
|
|
name: droiddeck-apk
|
|
path: app/build/outputs/apk/release/*.apk
|
|
if-no-files-found: error
|
|
retention-days: 30
|
|
compression-level: 0
|
|
|
|
sign:
|
|
needs: build
|
|
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main'
|
|
permissions:
|
|
contents: read
|
|
actions: read
|
|
uses: ./.github/workflows/sign.yml
|
|
with:
|
|
environment: signing-main
|
|
variants: '["standard"]'
|
|
secrets: inherit
|