Skip to content

Commit def32c1

Browse files
authored
Merge pull request #30 from maphew/feat/android-app
Add native Android app and store packaging
2 parents bf9dcfd + 69bbe14 commit def32c1

51 files changed

Lines changed: 3339 additions & 19 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.gitattributes‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
*.bat text eol=crlf
2+
# The POSIX wrapper script must stay LF even on core.autocrlf=true clones,
3+
# or /bin/sh rejects it with "bad interpreter".
4+
android/gradlew text eol=lf

‎.github/workflows/ci.yml‎

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,3 +77,77 @@ jobs:
7777
# tests that never touch the real registry or spawn a real terminal.
7878
- name: Run Windows tests
7979
run: cargo test --all-targets --all-features --locked
80+
81+
android-check:
82+
name: Build Android packages
83+
runs-on: ubuntu-latest
84+
85+
steps:
86+
- name: Checkout
87+
# actions/checkout v5
88+
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
89+
90+
- name: Validate Gradle wrapper
91+
# gradle/actions/wrapper-validation v4.4.4
92+
uses: gradle/actions/wrapper-validation@da187c8e6ffbd3802e00f2477aa5a822b25f2dda
93+
94+
- name: Install JDK 17
95+
# actions/setup-java v4
96+
uses: actions/setup-java@c1e323688fd81a25caa38c78aa6df2d33d3e20d9
97+
with:
98+
distribution: temurin
99+
java-version: "17"
100+
101+
- name: Install Android SDK tools
102+
# android-actions/setup-android v3
103+
uses: android-actions/setup-android@9fc6c4e9069bf8d3d10b2204b1fb8f6ef7065407
104+
105+
- name: Install Android platform and NDK
106+
run: sdkmanager "platforms;android-36" "build-tools;36.0.0" "ndk;28.2.13676358"
107+
108+
- name: Install Rust Android target
109+
# dtolnay/rust-toolchain stable
110+
uses: dtolnay/rust-toolchain@4be7066ada62dd38de10e7b70166bc74ed198c30
111+
with:
112+
targets: aarch64-linux-android
113+
114+
- name: Install cargo-ndk
115+
# taiki-e/install-action v2
116+
uses: taiki-e/install-action@2ca9b94c269419b7b0c711c09d0b21c4e1d51145
117+
with:
118+
tool: cargo-ndk
119+
120+
- name: Build debug APK and unsigned release packages
121+
working-directory: android
122+
run: ./gradlew assembleDebug assembleRelease bundleRelease lintDebug lintRelease --stacktrace
123+
124+
- name: Exercise release signing with a disposable key
125+
working-directory: android
126+
env:
127+
MDO_ANDROID_KEYSTORE: ${{ runner.temp }}/mdo-ci-signing.jks
128+
MDO_ANDROID_KEYSTORE_PASSWORD: android-test
129+
MDO_ANDROID_KEY_ALIAS: mdo-ci
130+
MDO_ANDROID_KEY_PASSWORD: android-test
131+
run: |
132+
keytool -genkeypair -noprompt \
133+
-keystore "${MDO_ANDROID_KEYSTORE}" \
134+
-storepass "${MDO_ANDROID_KEYSTORE_PASSWORD}" \
135+
-alias "${MDO_ANDROID_KEY_ALIAS}" \
136+
-keypass "${MDO_ANDROID_KEY_PASSWORD}" \
137+
-keyalg RSA -keysize 2048 -validity 1 \
138+
-dname "CN=mdo CI,O=mdo,C=US"
139+
./gradlew --no-daemon assembleRelease bundleRelease --stacktrace
140+
"${ANDROID_HOME}/build-tools/36.0.0/apksigner" verify --verbose app/build/outputs/apk/release/app-release.apk
141+
# Android upload keys are self-signed, which jarsigner -strict
142+
# rejects. Plain -verify exits 0 even for an unsigned jar, so
143+
# assert the "jar verified" verdict explicitly.
144+
jarsigner -verify app/build/outputs/bundle/release/app-release.aab | tee /dev/stderr | grep -q '^jar verified'
145+
146+
- name: Upload debug APK
147+
# actions/upload-artifact v4
148+
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
149+
with:
150+
name: mdo-android-arm64-debug
151+
path: android/app/build/outputs/apk/debug/app-debug.apk
152+
if-no-files-found: error
153+
retention-days: 14

‎.github/workflows/release.yml‎

Lines changed: 141 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,10 +154,150 @@ jobs:
154154
if-no-files-found: error
155155
retention-days: 7
156156

157+
android-build:
158+
name: Build signed Android packages
159+
runs-on: ubuntu-latest
160+
permissions:
161+
contents: read
162+
env:
163+
ANDROID_SIGNING_ENABLED: ${{ vars.ANDROID_RELEASE_SIGNING_ENABLED }}
164+
RELEASE_TAG: ${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref_name }}
165+
166+
steps:
167+
- name: Report disabled Android signing
168+
if: env.ANDROID_SIGNING_ENABLED != 'true'
169+
run: echo "Android release packaging is disabled; set ANDROID_RELEASE_SIGNING_ENABLED=true after configuring the four Android signing secrets."
170+
171+
- name: Checkout
172+
if: env.ANDROID_SIGNING_ENABLED == 'true'
173+
# actions/checkout v5
174+
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
175+
with:
176+
ref: refs/tags/${{ env.RELEASE_TAG }}
177+
persist-credentials: false
178+
179+
- name: Verify checkout matches release tag
180+
if: env.ANDROID_SIGNING_ENABLED == 'true'
181+
shell: bash
182+
run: |
183+
set -euo pipefail
184+
tag_commit="$(git rev-parse "${RELEASE_TAG}^{commit}")"
185+
head_commit="$(git rev-parse HEAD)"
186+
if [ "${head_commit}" != "${tag_commit}" ]; then
187+
echo "HEAD ${head_commit} does not match ${RELEASE_TAG} commit ${tag_commit}" >&2
188+
exit 1
189+
fi
190+
191+
- name: Validate Gradle wrapper
192+
if: env.ANDROID_SIGNING_ENABLED == 'true'
193+
# gradle/actions/wrapper-validation v4.4.4
194+
uses: gradle/actions/wrapper-validation@da187c8e6ffbd3802e00f2477aa5a822b25f2dda
195+
196+
- name: Install JDK 17
197+
if: env.ANDROID_SIGNING_ENABLED == 'true'
198+
# actions/setup-java v4
199+
uses: actions/setup-java@c1e323688fd81a25caa38c78aa6df2d33d3e20d9
200+
with:
201+
distribution: temurin
202+
java-version: "17"
203+
204+
- name: Install Android SDK tools
205+
if: env.ANDROID_SIGNING_ENABLED == 'true'
206+
# android-actions/setup-android v3
207+
uses: android-actions/setup-android@9fc6c4e9069bf8d3d10b2204b1fb8f6ef7065407
208+
209+
- name: Install Android platform and NDK
210+
if: env.ANDROID_SIGNING_ENABLED == 'true'
211+
run: sdkmanager "platforms;android-36" "build-tools;36.0.0" "ndk;28.2.13676358"
212+
213+
- name: Install Rust Android target
214+
if: env.ANDROID_SIGNING_ENABLED == 'true'
215+
# dtolnay/rust-toolchain stable
216+
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8
217+
with:
218+
targets: aarch64-linux-android
219+
220+
- name: Install cargo-ndk
221+
if: env.ANDROID_SIGNING_ENABLED == 'true'
222+
# taiki-e/install-action v2
223+
uses: taiki-e/install-action@2ca9b94c269419b7b0c711c09d0b21c4e1d51145
224+
with:
225+
tool: cargo-ndk
226+
227+
- name: Restore Android signing key
228+
if: env.ANDROID_SIGNING_ENABLED == 'true'
229+
env:
230+
ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
231+
MDO_ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
232+
MDO_ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
233+
MDO_ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
234+
run: |
235+
set -euo pipefail
236+
for name in \
237+
ANDROID_KEYSTORE_BASE64 \
238+
MDO_ANDROID_KEYSTORE_PASSWORD \
239+
MDO_ANDROID_KEY_ALIAS \
240+
MDO_ANDROID_KEY_PASSWORD; do
241+
if [[ -z "${!name}" ]]; then
242+
echo "${name} is not configured" >&2
243+
exit 1
244+
fi
245+
done
246+
printf '%s' "${ANDROID_KEYSTORE_BASE64}" | base64 --decode > "${RUNNER_TEMP}/mdo-upload.jks"
247+
248+
- name: Verify Android version matches release tag
249+
if: env.ANDROID_SIGNING_ENABLED == 'true'
250+
run: |
251+
set -euo pipefail
252+
tag_version="${RELEASE_TAG#v}"
253+
app_version="$(sed -nE 's/^[[:space:]]*versionName "([^"]+)"/\1/p' android/app/build.gradle)"
254+
if [[ "${app_version}" != "${tag_version}" ]]; then
255+
echo "Android versionName ${app_version} does not match release tag ${RELEASE_TAG}" >&2
256+
exit 1
257+
fi
258+
259+
- name: Build signed APK and app bundle
260+
if: env.ANDROID_SIGNING_ENABLED == 'true'
261+
working-directory: android
262+
env:
263+
MDO_ANDROID_KEYSTORE: ${{ runner.temp }}/mdo-upload.jks
264+
MDO_ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
265+
MDO_ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
266+
MDO_ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
267+
run: ./gradlew assembleRelease bundleRelease --stacktrace
268+
269+
- name: Verify and stage Android packages
270+
if: env.ANDROID_SIGNING_ENABLED == 'true'
271+
run: |
272+
set -euo pipefail
273+
"${ANDROID_HOME}/build-tools/36.0.0/apksigner" verify --verbose --print-certs android/app/build/outputs/apk/release/app-release.apk
274+
# Android upload keys are self-signed, which jarsigner -strict
275+
# rejects. Plain -verify exits 0 even for an unsigned jar, so
276+
# assert the "jar verified" verdict explicitly.
277+
jarsigner -verify android/app/build/outputs/bundle/release/app-release.aab | tee /dev/stderr | grep -q '^jar verified'
278+
mkdir -p target/dist
279+
cp android/app/build/outputs/apk/release/app-release.apk target/dist/mdo-android-arm64.apk
280+
cp android/app/build/outputs/bundle/release/app-release.aab target/dist/mdo-android-arm64.aab
281+
282+
- name: Upload Android release artifacts
283+
if: env.ANDROID_SIGNING_ENABLED == 'true'
284+
# actions/upload-artifact v4
285+
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
286+
with:
287+
name: mdo-android-arm64
288+
path: |
289+
target/dist/mdo-android-arm64.apk
290+
target/dist/mdo-android-arm64.aab
291+
if-no-files-found: error
292+
retention-days: 7
293+
157294
publish:
158295
name: Publish GitHub Release
159296
runs-on: ubuntu-latest
160-
needs: build
297+
needs: [build, android-build]
298+
# An Android failure must not block the desktop archives that released
299+
# independently before android-build existed; it fails loudly on its own.
300+
if: always() && needs.build.result == 'success'
161301
permissions:
162302
contents: write
163303

‎.gitignore‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
/target
2+
/android/.gradle
3+
/android/build
4+
/android/local.properties
5+
/android/app/build
26
__pycache__/
37
*.pyc
48
docs/adr/*.html

‎CHANGELOG.md‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
# Changelog
22

3+
## Unreleased
4+
5+
- Add a native Android app that opens Markdown through the system document
6+
picker or file/share intents and renders it offline through mdo's existing
7+
Rust renderer and sanitizer.
8+
- Add Android debug, release-APK, App Bundle, and lint coverage to CI, plus
9+
optional secret-backed APK/AAB signing and publication in tagged releases.
10+
- Add upstream store metadata, a no-data-collection privacy policy, and
11+
preparation material for F-Droid and Google Play submissions.
12+
313
## 0.6.0 - 2026-07-18
414

515
- `--open` now fails honestly: when the browser launch fails after a

0 commit comments

Comments
 (0)