mirror of
https://github.com/austinried/subtracks.git
synced 2026-11-11 15:29:15 +01:00
Compare commits
5
Commits
v3.0.0
..
d171825b5e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d171825b5e | ||
|
|
6f997dceb9 | ||
|
|
43ee2d9f27 | ||
|
|
3c184a71a6 | ||
|
|
8396f98a85 |
@@ -38,8 +38,8 @@ runs:
|
|||||||
- uses: actions/cache@v6
|
- uses: actions/cache@v6
|
||||||
with:
|
with:
|
||||||
path: ~/.gradle/caches
|
path: ~/.gradle/caches
|
||||||
key: gradle-${{ hashFiles('**/*.gradle*', '**/gradle.properties', 'gradle/libs.versions.toml', 'gradle/wrapper/gradle-wrapper.properties') }}
|
key: gradle-v2-${{ hashFiles('**/*.gradle*', '**/gradle.properties', 'gradle/libs.versions.toml', 'gradle/wrapper/gradle-wrapper.properties') }}
|
||||||
restore-keys: gradle-
|
restore-keys: gradle-v2-
|
||||||
- id: signing
|
- id: signing
|
||||||
if: inputs.sign == 'true'
|
if: inputs.sign == 'true'
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|||||||
@@ -117,31 +117,11 @@ jobs:
|
|||||||
printf '%s\n' "$certs"
|
printf '%s\n' "$certs"
|
||||||
fingerprint=$(printf '%s\n' "$certs" | sed -n 's/.*certificate SHA-256 digest: //p' | head -1 | tr '[:lower:]' '[:upper:]' | sed 's/\(..\)/\1:/g; s/:$//')
|
fingerprint=$(printf '%s\n' "$certs" | sed -n 's/.*certificate SHA-256 digest: //p' | head -1 | tr '[:lower:]' '[:upper:]' | sed 's/\(..\)/\1:/g; s/:$//')
|
||||||
echo "fingerprint=$fingerprint" >> "$GITHUB_OUTPUT"
|
echo "fingerprint=$fingerprint" >> "$GITHUB_OUTPUT"
|
||||||
- name: Compose release notes
|
|
||||||
run: |
|
|
||||||
{
|
|
||||||
cat "metadata/en-US/changelogs/${{ needs.build.outputs.version-code }}.txt"
|
|
||||||
echo
|
|
||||||
echo '---'
|
|
||||||
echo
|
|
||||||
echo "APK SHA-256: \`$(grep 'subtracks\.apk$' SHA256SUMS | cut -d ' ' -f1)\`"
|
|
||||||
echo
|
|
||||||
echo "AAB SHA-256: \`$(grep 'subtracks\.aab$' SHA256SUMS | cut -d ' ' -f1)\`"
|
|
||||||
echo
|
|
||||||
echo "Signing certificate SHA-256: \`${{ steps.checksums.outputs.fingerprint }}\`"
|
|
||||||
echo
|
|
||||||
echo 'The signing certificate is attached as `release-certificate.pem`.'
|
|
||||||
if [ "${{ steps.stable.outputs.value }}" = "true" ]; then
|
|
||||||
echo 'The `.aab` is the exact bundle uploaded to Google Play; the `.apk` is the universal APK for sideloading.'
|
|
||||||
else
|
|
||||||
echo 'The `.aab` is the built app bundle (this pre-release was not uploaded to Google Play); the `.apk` is the universal APK for sideloading.'
|
|
||||||
fi
|
|
||||||
} > release-notes.md
|
|
||||||
- name: Create GitHub release
|
- name: Create GitHub release
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
args=(--verify-tag --title "$GITHUB_REF_NAME" --notes-file release-notes.md)
|
args=(--verify-tag --title "$GITHUB_REF_NAME" --notes-file "metadata/en-US/changelogs/${{ needs.build.outputs.version-code }}.txt")
|
||||||
if [ "${{ steps.stable.outputs.value }}" != "true" ]; then
|
if [ "${{ steps.stable.outputs.value }}" != "true" ]; then
|
||||||
args+=(--prerelease)
|
args+=(--prerelease)
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -34,11 +34,7 @@ Everything comes from the Nix flake devshell; do not install toolchains by hand.
|
|||||||
|
|
||||||
## CI
|
## CI
|
||||||
|
|
||||||
- Forgejo Actions, `.forgejo/workflows/ci.yml`, a single `ci` job that runs lint, the unit tests, the debug and nightly assembles and the integration suite (one cache restore/save and Gradle warm-up instead of several); the GitHub copy uploads the debug and nightly APKs and the checksums as separate artifacts on a push to `main`.
|
- The CI workflows live under `.forgejo/` and `.github/`, and the GitHub mirror and the GitHub PR import under `.forgejo/`; see `.forgejo/README.md`.
|
||||||
- Runs on the `nix-docker` runner (the `localhost/nix-ci` image built by the separate `nix-home` repo) inside the devshell, restoring the Nix store via `cache-nix-action` and caching Gradle and the integration test music.
|
|
||||||
- The `nix-docker` and `bookworm` runner labels, their images and the runner's networking are defined in the separate `nix-home` repo (`hosts-incus/git-runner.nix`).
|
|
||||||
- Pull requests from a fork need approval before the workflow runs.
|
|
||||||
- The GitHub mirror and the GitHub PR import live under `.forgejo/`; see `.forgejo/README.md`.
|
|
||||||
|
|
||||||
## Conventions
|
## Conventions
|
||||||
|
|
||||||
@@ -60,7 +56,6 @@ Everything comes from the Nix flake devshell; do not install toolchains by hand.
|
|||||||
- Room 3 does not map `PagingSource` automatically: a DAO that returns it needs `@DaoReturnTypeConverters(PagingSourceDaoReturnTypeConverter::class)`.
|
- Room 3 does not map `PagingSource` automatically: a DAO that returns it needs `@DaoReturnTypeConverters(PagingSourceDaoReturnTypeConverter::class)`.
|
||||||
- Material icons are not pulled in by `material3`; use the BOM-managed `material-icons-core`/`material-icons-extended` (frozen at 1.7.8). Extended is large in debug builds but R8 strips unused icons from release.
|
- Material icons are not pulled in by `material3`; use the BOM-managed `material-icons-core`/`material-icons-extended` (frozen at 1.7.8). Extended is large in debug builds but R8 strips unused icons from release.
|
||||||
- Robolectric creates a fresh `Application` per test in one JVM, so `SubtracksApp.onCreate` stops any running Koin before `startKoin`.
|
- Robolectric creates a fresh `Application` per test in one JVM, so `SubtracksApp.onCreate` stops any running Koin before `startKoin`.
|
||||||
- The CI image provides `nix-ld`, `jq`, `sqlite`, `node`, `zstd` and a `runner` user that `cache-nix-action` expects. None of that is needed locally beyond the devshell.
|
|
||||||
- Integration servers: navidrome on 4533 (`admin`/`password`), gonic on 4747 (`admin`/`admin`), lms on 5082 (`admin`/`subtracks-lms`), Nextcloud Music on 8090 (`admin`/`subtracks-nextcloud`, base URL `http://localhost:8090/index.php/apps/music/subsonic/`). The test music is cached in `.integration/music` (gitignored).
|
- Integration servers: navidrome on 4533 (`admin`/`password`), gonic on 4747 (`admin`/`admin`), lms on 5082 (`admin`/`subtracks-lms`), Nextcloud Music on 8090 (`admin`/`subtracks-nextcloud`, base URL `http://localhost:8090/index.php/apps/music/subsonic/`). The test music is cached in `.integration/music` (gitignored).
|
||||||
- lms and Nextcloud Music authenticate the Subsonic API with per-user **API keys** instead of passwords. The harness seeds a key and most tests use `useTokenAuth = false`; `SubsonicAuthIntegrationTest` drives `useTokenAuth = true` and asserts the fallback to plaintext (lms answers code 42, Nextcloud 41).
|
- lms and Nextcloud Music authenticate the Subsonic API with per-user **API keys** instead of passwords. The harness seeds a key and most tests use `useTokenAuth = false`; `SubsonicAuthIntegrationTest` drives `useTokenAuth = true` and asserts the fallback to plaintext (lms answers code 42, Nextcloud 41).
|
||||||
- `nix develop` does not source your shell rc files (so tools like atuin are not active); use direnv if you want your normal interactive shell.
|
- `nix develop` does not source your shell rc files (so tools like atuin are not active); use direnv if you want your normal interactive shell.
|
||||||
|
|||||||
@@ -189,6 +189,7 @@ tasks.withType<Test>().configureEach {
|
|||||||
testLogging {
|
testLogging {
|
||||||
exceptionFormat = TestExceptionFormat.FULL
|
exceptionFormat = TestExceptionFormat.FULL
|
||||||
}
|
}
|
||||||
|
outputs.cacheIf { false }
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.register<Test>("integrationTest") {
|
tasks.register<Test>("integrationTest") {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import kotlinx.coroutines.CoroutineScope
|
|||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.cancelAndJoin
|
import kotlinx.coroutines.cancelAndJoin
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.junit.Assert.assertTrue
|
import org.junit.Assert.fail
|
||||||
|
|
||||||
const val TEST_TIMEOUT_MS = 30_000L
|
const val TEST_TIMEOUT_MS = 30_000L
|
||||||
|
|
||||||
@@ -18,5 +18,14 @@ fun awaitUntil(
|
|||||||
) {
|
) {
|
||||||
val deadline = System.nanoTime() + TEST_TIMEOUT_MS * 1_000_000
|
val deadline = System.nanoTime() + TEST_TIMEOUT_MS * 1_000_000
|
||||||
while (!predicate() && System.nanoTime() < deadline) Thread.sleep(10)
|
while (!predicate() && System.nanoTime() < deadline) Thread.sleep(10)
|
||||||
assertTrue("Timed out after ${TEST_TIMEOUT_MS}ms $message", predicate())
|
if (predicate()) return
|
||||||
|
// A wall-clock wait that expires means something stalled rather than ran slow: the suite
|
||||||
|
// finishes in milliseconds per test. Print every thread's stack in the failure message so the
|
||||||
|
// stall is diagnosable from the CI log, which shows exception messages but not the test XML.
|
||||||
|
fail("Timed out after ${TEST_TIMEOUT_MS}ms $message\n\n${threadDump()}")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun threadDump(): String =
|
||||||
|
Thread.getAllStackTraces().entries.joinToString("\n\n") { (thread, frames) ->
|
||||||
|
"${thread.name} [${thread.state}]\n" + frames.joinToString("\n") { " at $it" }
|
||||||
|
}
|
||||||
|
|||||||
@@ -163,4 +163,4 @@ No analytics or third-party telemetry. Credentials are stored locally in app dat
|
|||||||
|
|
||||||
## Build and CI
|
## Build and CI
|
||||||
|
|
||||||
The Nix flake devshell provides the whole toolchain, and `direnv` loads it into the working shell. CI (`.forgejo/workflows/ci.yml`) runs `unit`, `lint` and `integration` on a containerized Nix runner, restoring the Nix store from the Actions cache and caching Gradle and the integration test music.
|
The Nix flake devshell provides the whole toolchain, and `direnv` loads it into the working shell. CI runs `unit`, `lint` and `integration`, restoring the Nix store from the Actions cache and caching Gradle and the integration test music.
|
||||||
|
|||||||
Reference in New Issue
Block a user