Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
517f7a6aa8 | ||
|
|
1e5093b631 | ||
|
|
c1ee8ec1f4 | ||
|
|
7ce9eddabf | ||
|
|
8592bba9e3 |
@@ -123,3 +123,4 @@ dagger-certs
|
||||
/go
|
||||
.last_deployed_sha
|
||||
.fail_count
|
||||
/*.kubeconfig
|
||||
|
||||
@@ -26,13 +26,13 @@ repos:
|
||||
- id: forbidden-files-hook
|
||||
name: check for forbidden home-directory files
|
||||
language: system
|
||||
entry: bash -c 'cd "$(git rev-parse --show-toplevel)" && nix develop --command task check-hygiene'
|
||||
entry: bash -c 'cd "$(git rev-parse --show-toplevel)" && task check-hygiene'
|
||||
pass_filenames: false
|
||||
always_run: true
|
||||
- id: dart-check
|
||||
name: dart format (autofix) + check-fast (parallel)
|
||||
language: system
|
||||
entry: bash -c 'cd "$(git rev-parse --show-toplevel)" && nix develop --command dagger call --progress=plain -q -m ci --source=. check-fast'
|
||||
entry: bash -c 'cd "$(git rev-parse --show-toplevel)" && dagger call --progress=plain -q -m ci --source=. check-fast'
|
||||
pass_filenames: false
|
||||
always_run: true
|
||||
- id: ci-no-direct-dagger
|
||||
@@ -50,6 +50,12 @@ repos:
|
||||
- id: ci-image-exists
|
||||
name: verify container images in ci/main.go are reachable
|
||||
language: system
|
||||
entry: bash -c 'cd "$(git rev-parse --show-toplevel)" && nix develop --command task check-ci-images'
|
||||
entry: bash -c 'cd "$(git rev-parse --show-toplevel)" && task check-ci-images'
|
||||
pass_filenames: false
|
||||
files: ^(ci/main\.go|\.fvmrc)$
|
||||
- id: dagger-versions-aligned
|
||||
name: verify Dagger version is consistent across dagger.json, Dockerfile and DAGGER.md
|
||||
language: system
|
||||
entry: bash -c 'cd "$(git rev-parse --show-toplevel)" && scripts/check_dagger_versions.sh'
|
||||
pass_filenames: false
|
||||
files: ^(ci/dagger\.json|\.forgejo/Dockerfile|DAGGER\.md)$
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
# Development and Testing Container for SharedInbox
|
||||
# Replaces the Nix shell environment.
|
||||
FROM ghcr.io/cirruslabs/flutter:3.44.0
|
||||
|
||||
# Install Linux desktop build and test dependencies, Go, NodeJS, python3, and utilities
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
clang \
|
||||
cmake \
|
||||
ninja-build \
|
||||
pkg-config \
|
||||
libgtk-3-dev \
|
||||
liblzma-dev \
|
||||
libsecret-1-dev \
|
||||
libgcrypt20-dev \
|
||||
libjsoncpp-dev \
|
||||
sqlite3 \
|
||||
iproute2 \
|
||||
netcat-openbsd \
|
||||
xvfb \
|
||||
libosmesa6 \
|
||||
libegl1 \
|
||||
lld \
|
||||
git \
|
||||
curl \
|
||||
jq \
|
||||
python3-pip \
|
||||
nodejs \
|
||||
npm \
|
||||
hugo \
|
||||
lcov \
|
||||
rsync \
|
||||
openssh-client \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install Task runner
|
||||
RUN curl -fsSL https://taskfile.dev/install.sh \
|
||||
| sh -s -- -b /usr/local/bin v3.48.0
|
||||
|
||||
# Install Dagger CLI
|
||||
RUN curl -fsSL https://dl.dagger.io/dagger/install.sh \
|
||||
| DAGGER_VERSION=0.20.8 BIN_DIR=/usr/local/bin sh
|
||||
|
||||
# Install python packages (Play Store API clients + pre-commit)
|
||||
RUN pip install --break-system-packages --no-cache-dir \
|
||||
google-api-python-client \
|
||||
google-auth-httplib2 \
|
||||
httplib2 \
|
||||
pre-commit==4.5.1
|
||||
|
||||
# Install acpx CLI globally
|
||||
RUN npm install -g acpx@0.10.0
|
||||
|
||||
# Setup user "ci"
|
||||
RUN useradd -m -s /bin/bash ci
|
||||
USER ci
|
||||
ENV HOME=/home/ci
|
||||
ENV PATH=/home/ci/.pub-cache/bin:$PATH
|
||||
|
||||
WORKDIR /src
|
||||
@@ -1,69 +0,0 @@
|
||||
## Background — what already exists in this repo
|
||||
|
||||
The Android AAB build-and-upload pipeline is already fully automated. The user does **not** need to drop a file into Play Console at all — the same machinery that publishes to `internal` can publish to the closed-testing track.
|
||||
|
||||
- `ci/main.go:952` — `PublishAndroid()` builds a release AAB, stamps the versionCode (`time.Now().Unix()`), re-signs with the upload key, and uploads to Play Store.
|
||||
- `ci/main.go:899` — `UploadToPlayStore()` invokes `scripts/deploy_playstore.py` inside a container.
|
||||
- `scripts/deploy_playstore.py:14` — pinned to `TRACK = "internal"`. This is the only thing limiting the existing pipeline to internal testing.
|
||||
- `Taskfile.yml:230` — `task publish-android` wraps the Dagger call.
|
||||
- `.forgejo/workflows/deploy.yml` — `deploy-playstore` job runs every hour (`0 * * * *`) and calls `task publish-android` whenever android-relevant files changed since the last successful deploy.
|
||||
- `Taskfile.yml:214` — `task build-android-bundle` builds an **unsigned** AAB locally to `build/app/outputs/bundle/release/app-release.aab`. The signed AAB built by `PublishAndroid` lives only inside Dagger and is **not** currently exported to disk on the runner.
|
||||
|
||||
## Recommended path (A): publish to the closed-testing track from CI — no manual upload
|
||||
|
||||
This piggybacks on what's already running every hour.
|
||||
|
||||
1. **In Play Console**, decide which track maps to "closed testing":
|
||||
- The built-in `alpha` track is treated as closed testing by the API.
|
||||
- Or create a custom closed-testing track (e.g. `closed-testers`) under Testing → Closed testing → Create track. Configure the testers list there. Note the track ID exactly as it appears in the URL/console.
|
||||
|
||||
2. **Edit `scripts/deploy_playstore.py`** so the upload goes to both internal and the closed track in the same Play edit (one commit, atomic). Concretely:
|
||||
- Replace `TRACK = "internal"` (line 14) with `TRACKS = ["internal", "alpha"]` (or `["internal", "<custom-track-id>"]`).
|
||||
- Replace the single `track_resp = session.put(... /tracks/{TRACK} ...)` block (lines 97-101) with a loop that PUTs the same `versionCodes: [version_code]` payload to each track in `TRACKS` before the commit at line 104.
|
||||
- Internal stays as the smoke-test target; the closed track is what gets the testers.
|
||||
|
||||
3. **Update the test fixture** in `scripts/test_deploy_playstore.py` (and any test for `verify_playstore_deploy.py`) to expect the new track list. Run `python3 scripts/test_deploy_playstore.py` locally.
|
||||
|
||||
4. **Trigger a deploy**: push the change to `main` and either wait for the next hourly run or `workflow_dispatch` `.forgejo/workflows/deploy.yml` from the Forgejo UI. Note that `deploy.yml:114` already includes `scripts/deploy_playstore\.py` in the change-detection regex, so any normal hourly tick after the merge will pick it up.
|
||||
|
||||
5. **Result**: closed-testing release appears in Play Console with the AAB pre-attached. Reviewers only need to edit the release notes (already generated by `task generate-changelog` — see `Taskfile.yml:232`) and click "Roll out". No "Drop app bundles here" step.
|
||||
|
||||
Caveat — first time a track is used, Google requires that internal testing has been previously published; that is already true here, so no blocker.
|
||||
|
||||
## Alternative path (B): one-shot manual drop — export the signed AAB as a CI artifact
|
||||
|
||||
Use this if you want to do this single closed-testing release by hand and decide on full automation later.
|
||||
|
||||
1. **Modify `ci/main.go`** to expose the signed AAB as a file return: add a thin wrapper exporting the result of `SignAndroidBundle(StampAndroidVersionCode(BuildAndroidRelease(commitHash), versionCode), ...)` as `*dagger.File` (something like `SignedAndroidBundle(...) *dagger.File`). The existing `PublishAndroid` discards the signed bundle after upload, so a separate entry point is cleaner than reshaping it.
|
||||
|
||||
2. **Add a `Taskfile.yml` target** `build-android-bundle-signed` that calls the new Dagger function with `-o build/app/outputs/bundle/release/app-release.aab` (mirroring `build-android-bundle` at `Taskfile.yml:218`).
|
||||
|
||||
3. **Add to `.forgejo/workflows/deploy.yml` `deploy-playstore` job** (after `task publish-android`) — or to a new dedicated job — these two steps:
|
||||
```yaml
|
||||
- name: Export signed AAB locally
|
||||
env: { DAGGER_NO_NAG: "1" }
|
||||
run: task build-android-bundle-signed
|
||||
- name: Upload AAB artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: app-release-aab
|
||||
path: build/app/outputs/bundle/release/app-release.aab
|
||||
if-no-files-found: error
|
||||
```
|
||||
|
||||
4. **Trigger the workflow** (`workflow_dispatch`), then download `app-release-aab` from the run's artifacts page on Forgejo. Drop it into Play Console.
|
||||
|
||||
Risk to flag: the version code stamped via the artifact path must be strictly greater than every version code already on internal. Since the same Unix-timestamp scheme is used everywhere this naturally holds, but if the artifact run and a regular publish run race, the closed release may fail with "Version code N has already been used." Easy fix: only run one or the other.
|
||||
|
||||
## Alternative path (C): build locally — fastest one-off
|
||||
|
||||
```bash
|
||||
export ANDROID_KEYSTORE_BASE64=... # same secret used by CI
|
||||
bash scripts/build_android_bundle_local.sh
|
||||
```
|
||||
|
||||
AAB lands at `build/app/outputs/bundle/release/app-release.aab`. Note this script uses `fvm flutter build appbundle` directly and does **not** match the Dagger pipeline's stamp/sign step exactly — the keystore path is wired via `ANDROID_KEYSTORE_PATH` and the signing config in `android/app/build.gradle.kts` reads from there. Verify the bundle is correctly signed (`jarsigner -verify -verbose -certs`) before uploading.
|
||||
|
||||
## Recommendation
|
||||
|
||||
Go with path A. The diff is small (~10 lines in `deploy_playstore.py` + test update), reuses the build-and-sign pipeline that has already been proven in `deploy.yml` for months, and removes the "Drop app bundles here" step entirely instead of just satisfying it once.
|
||||
+6
-1
@@ -544,7 +544,7 @@ tasks:
|
||||
- sops exec-env secrets.enc.yaml 'bash scripts/build_android_bundle_local.sh'
|
||||
|
||||
deploy-android-bundle:
|
||||
desc: Build release AAB and upload to Play Store internal track (local/fvm)
|
||||
desc: Build release AAB and upload to Play Store internal + closed-testing tracks (local/fvm)
|
||||
deps: [build-android-bundle-local]
|
||||
dotenv: [".env"]
|
||||
cmds:
|
||||
@@ -712,6 +712,11 @@ tasks:
|
||||
cmds:
|
||||
- scripts/check_ci_images.sh
|
||||
|
||||
check-dagger-versions:
|
||||
desc: Verify ci/dagger.json, flake.nix, .forgejo/Dockerfile and DAGGER.md pin the same Dagger version
|
||||
cmds:
|
||||
- scripts/check_dagger_versions.sh
|
||||
|
||||
_integrations:
|
||||
internal: true
|
||||
run: once
|
||||
|
||||
+1
-1
@@ -896,7 +896,7 @@ func withGoCache(c *dagger.Container) *dagger.Container {
|
||||
WithEnvVariable("GOMODCACHE", "/home/ci/go/pkg/mod")
|
||||
}
|
||||
|
||||
// UploadToPlayStore uploads a pre-built AAB to the Play Store internal track.
|
||||
// UploadToPlayStore uploads a pre-built AAB to the Play Store internal and closed-testing (alpha) tracks.
|
||||
func (m *Ci) UploadToPlayStore(
|
||||
ctx context.Context,
|
||||
aab *dagger.File,
|
||||
|
||||
Generated
-82
@@ -1,82 +0,0 @@
|
||||
{
|
||||
"nodes": {
|
||||
"dagger": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1778107833,
|
||||
"narHash": "sha256-q5XQep2mpgTPiWwuYB1+L2dsFeACT6sHx8J939iM+HE=",
|
||||
"owner": "dagger",
|
||||
"repo": "nix",
|
||||
"rev": "873cc22ba46b73d4a6c1aa6c102ef3aabc736496",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "dagger",
|
||||
"repo": "nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1731533236,
|
||||
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1778737229,
|
||||
"narHash": "sha256-6xWoytx8jFW4PF1GjRm/i/53trbpKGfz6zjzQGBr4cI=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "d7a713c0b7e47c908258e71cba7a2d77cc8d71d5",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-25.11",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"dagger": "dagger",
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
@@ -1,164 +0,0 @@
|
||||
{
|
||||
description = "SharedInbox — IMAP/SMTP Flutter client";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11";
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
dagger.url = "github:dagger/nix";
|
||||
dagger.inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, flake-utils, dagger }:
|
||||
flake-utils.lib.eachDefaultSystem (system:
|
||||
let
|
||||
pkgs = nixpkgs.legacyPackages.${system};
|
||||
|
||||
# All Linux desktop runtime libraries needed by flutter build linux and
|
||||
# the UI integration tests (xvfb-run). Kept as a list so we can reuse
|
||||
# it for both buildInputs and LD_LIBRARY_PATH / PKG_CONFIG_PATH.
|
||||
linuxDesktopLibs = with pkgs; [
|
||||
gtk3
|
||||
libsecret
|
||||
fontconfig
|
||||
libepoxy
|
||||
mesa
|
||||
libGL # libglvnd — vendor-neutral GL/EGL/GLX dispatch layer
|
||||
at-spi2-core
|
||||
glib
|
||||
pango
|
||||
cairo
|
||||
gdk-pixbuf
|
||||
harfbuzz
|
||||
# Dagger remote setup dependencies
|
||||
stunnel
|
||||
netcat
|
||||
];
|
||||
|
||||
fgj = pkgs.stdenv.mkDerivation {
|
||||
pname = "fgj";
|
||||
version = "0.4.0";
|
||||
src = pkgs.fetchurl {
|
||||
url = "https://codeberg.org/romaintb/fgj/releases/download/v0.4.0/fgj_linux_amd64";
|
||||
sha256 = "07pia03facvvxq9i1dgl7p47ccv1iqj4drpkp45gvw26d4afkbj7";
|
||||
};
|
||||
dontUnpack = true;
|
||||
installPhase = ''
|
||||
mkdir -p $out/bin
|
||||
cp $src $out/bin/fgj
|
||||
chmod +x $out/bin/fgj
|
||||
'';
|
||||
};
|
||||
|
||||
# The dagger/nix flake pins 0.20.8, whose Nix wrapper is a broken self-exec
|
||||
# loop. Fetch 0.21.4 directly so the pre-commit dart-check hook can run.
|
||||
dagger021 = pkgs.stdenv.mkDerivation {
|
||||
pname = "dagger";
|
||||
version = "0.21.4";
|
||||
src = pkgs.fetchurl {
|
||||
url = "https://dl.dagger.io/dagger/releases/0.21.4/dagger_v0.21.4_linux_amd64.tar.gz";
|
||||
sha256 = "0wlnbr4g5069755131yjp2a6alacn64f1c8b27xn0cbynq3zicjd";
|
||||
};
|
||||
sourceRoot = ".";
|
||||
installPhase = ''
|
||||
mkdir -p $out/bin
|
||||
cp dagger $out/bin/dagger
|
||||
chmod +x $out/bin/dagger
|
||||
'';
|
||||
};
|
||||
in {
|
||||
devShells.default = pkgs.mkShell {
|
||||
buildInputs = with pkgs; [
|
||||
# Dagger CLI
|
||||
dagger021
|
||||
|
||||
# Go compiler — for Dagger development
|
||||
go
|
||||
|
||||
# Java JDK — required by Gradle for Android builds
|
||||
|
||||
# Task runner
|
||||
go-task
|
||||
|
||||
# Flutter version manager — needed for host builds (task build-linux, task run)
|
||||
fvm
|
||||
|
||||
# Git hooks
|
||||
pre-commit
|
||||
|
||||
# Linux desktop build + runtime dependencies (flutter build linux / task run)
|
||||
] ++ linuxDesktopLibs ++ (with pkgs; [
|
||||
pkg-config
|
||||
clang
|
||||
cmake
|
||||
ninja
|
||||
|
||||
# Local IMAP/SMTP dev server for integration tests
|
||||
stalwart-mail
|
||||
|
||||
# Headless display for UI integration tests
|
||||
xvfb-run # wraps Xvfb; xvfb-run --auto-servernum ...
|
||||
|
||||
# Coverage merging (flutter test --merge-coverage requires lcov)
|
||||
lcov
|
||||
|
||||
# Website
|
||||
hugo
|
||||
|
||||
# Utilities
|
||||
git
|
||||
curl
|
||||
jq
|
||||
sqlite
|
||||
# python3 base + Google Play API client (for scripts/deploy_playstore.py)
|
||||
(python3.withPackages (ps: with ps; [
|
||||
google-api-python-client
|
||||
google-auth-httplib2
|
||||
httplib2
|
||||
])) # used by stalwart-dev/start and deploy_playstore.py
|
||||
fgj # Codeberg/Forgejo CLI (like gh for GitHub)
|
||||
skopeo # inspect OCI image manifests without pulling layers (used by check-ci-images)
|
||||
librsvg # rsvg-convert — SVG→PNG for generate-icons task
|
||||
]);
|
||||
|
||||
shellHook = ''
|
||||
# nix develop --command does not set IN_NIX_SHELL; set it so _preflight passes in CI
|
||||
export IN_NIX_SHELL=1
|
||||
|
||||
# Point Dagger client at the running engine socket
|
||||
export DAGGER_HOST=unix:///run/dagger/engine.sock
|
||||
|
||||
# Disable Flutter telemetry inside dev shell
|
||||
export FLUTTER_SUPPRESS_ANALYTICS=true
|
||||
|
||||
# Expose dev headers to cmake's FindPkgConfig.
|
||||
# The nix pkg-config wrapper works in bash but cmake invokes pkg-config
|
||||
# as a subprocess and needs PKG_CONFIG_PATH set explicitly.
|
||||
export PKG_CONFIG_PATH="${pkgs.gtk3.dev}/lib/pkgconfig:${pkgs.glib.dev}/lib/pkgconfig:${pkgs.pango.dev}/lib/pkgconfig:${pkgs.cairo.dev}/lib/pkgconfig:${pkgs.gdk-pixbuf.dev}/lib/pkgconfig:${pkgs.at-spi2-core.dev}/lib/pkgconfig:${pkgs.harfbuzz.dev}/lib/pkgconfig:${pkgs.libsecret}/lib/pkgconfig:${pkgs.fontconfig.dev}/lib/pkgconfig:${pkgs.libepoxy}/lib/pkgconfig:$PKG_CONFIG_PATH"
|
||||
|
||||
# Nix ld uses --no-copy-dt-needed-entries (strict mode): transitive shared-lib
|
||||
# deps are not followed automatically, so link them explicitly.
|
||||
export LDFLAGS="-L${pkgs.fontconfig.lib}/lib -lfontconfig $LDFLAGS"
|
||||
|
||||
# Make nix-built runtime libs visible to the dynamic linker so the
|
||||
# Flutter Linux bundle and integration-ui tests can run.
|
||||
export LD_LIBRARY_PATH="${pkgs.lib.makeLibraryPath linuxDesktopLibs}:$LD_LIBRARY_PATH"
|
||||
|
||||
# Wire the libglvnd dispatch to the nix mesa vendor ICDs so GTK/Flutter
|
||||
# can create an OpenGL (EGL + GLX) context under Xvfb without a real GPU.
|
||||
export __EGL_VENDOR_LIBRARY_DIRS="${pkgs.mesa}/share/glvnd/egl_vendor.d"
|
||||
export __GLX_VENDOR_LIBRARY_DIRS="${pkgs.mesa}/lib"
|
||||
export LIBGL_ALWAYS_SOFTWARE=1
|
||||
export MESA_LOADER_DRIVER_OVERRIDE=softpipe
|
||||
|
||||
echo "SharedInbox Flutter dev environment ready."
|
||||
echo " Analyze : task analyze"
|
||||
echo " Unit tests : task test"
|
||||
echo " Integration : task integration"
|
||||
echo " All checks : task check"
|
||||
echo " Run (Linux) : task run"
|
||||
echo " Start Stalwart : stalwart-dev/start"
|
||||
'';
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
Executable
+43
@@ -0,0 +1,43 @@
|
||||
#!/usr/bin/env bash
|
||||
# Verify that the Dagger version is consistent across the project.
|
||||
#
|
||||
# The Dagger CLI must speak the same protocol as the engine it talks to. We
|
||||
# pin the version in four places (engine image in DAGGER.md, the CLI in
|
||||
# flake.nix, the CLI in the Forgejo runner Dockerfile, and the module
|
||||
# engineVersion in ci/dagger.json). This script fails if any of them drift.
|
||||
set -euo pipefail
|
||||
|
||||
ROOT=$(git rev-parse --show-toplevel)
|
||||
|
||||
# ci/dagger.json — strip leading "v" for comparison.
|
||||
dagger_json=$(grep -oE '"engineVersion"[[:space:]]*:[[:space:]]*"[^"]+"' "$ROOT/ci/dagger.json" \
|
||||
| sed -E 's/.*"v?([^"]+)"$/\1/')
|
||||
|
||||
# .forgejo/Dockerfile — DAGGER_VERSION env on the install line.
|
||||
dockerfile=$(grep -oE 'DAGGER_VERSION=[0-9]+\.[0-9]+\.[0-9]+' "$ROOT/.forgejo/Dockerfile" \
|
||||
| head -n1 \
|
||||
| cut -d= -f2)
|
||||
|
||||
# DAGGER.md — engine image tag in the example systemd unit.
|
||||
dagger_md=$(grep -oE 'dagger/nix/v[0-9]+\.[0-9]+\.[0-9]+' "$ROOT/DAGGER.md" \
|
||||
| head -n1 \
|
||||
| sed -E 's@.*/v@@')
|
||||
|
||||
printf 'ci/dagger.json engineVersion = v%s\n' "$dagger_json"
|
||||
printf '.forgejo/Dockerf. DAGGER_VERSION= %s\n' "$dockerfile"
|
||||
printf 'DAGGER.md engine tag = v%s\n' "$dagger_md"
|
||||
|
||||
for v in "$dockerfile" "$dagger_md"; do
|
||||
if [ -z "$v" ]; then
|
||||
echo "ERROR: failed to parse a Dagger version reference." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ "$v" != "$dagger_json" ]; then
|
||||
echo "" >&2
|
||||
echo "ERROR: Dagger versions are out of sync." >&2
|
||||
echo " Align ci/dagger.json, .forgejo/Dockerfile and DAGGER.md to the same version." >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
echo "Dagger versions aligned (v$dagger_json)."
|
||||
@@ -1,5 +1,11 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Upload an Android App Bundle to the Google Play Store internal track."""
|
||||
"""Upload an Android App Bundle to the Google Play Store.
|
||||
|
||||
The bundle is published to every track in ``TRACKS`` within a single Play edit,
|
||||
so internal testing and closed testing share the same version code. ``alpha``
|
||||
is what the Play Console labels "Closed testing"; publishing there removes the
|
||||
need to manually drag-and-drop the AAB into the closed-testing release form.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
@@ -11,7 +17,7 @@ from google.oauth2 import service_account
|
||||
|
||||
PACKAGE_NAME = "de.sharedinbox.mua"
|
||||
AAB_PATH = "build/app/outputs/bundle/release/app-release.aab"
|
||||
TRACK = "internal"
|
||||
TRACKS = ("internal", "alpha")
|
||||
_BASE = "https://androidpublisher.googleapis.com/androidpublisher/v3/applications"
|
||||
_UPLOAD_BASE = "https://androidpublisher.googleapis.com/upload/androidpublisher/v3/applications"
|
||||
_MAX_UPLOAD_ATTEMPTS = 3
|
||||
@@ -94,19 +100,20 @@ def main():
|
||||
version_code = bundle["versionCode"]
|
||||
print(f"Uploaded AAB, version code: {version_code}")
|
||||
|
||||
track_resp = session.put(
|
||||
f"{_BASE}/{PACKAGE_NAME}/edits/{edit_id}/tracks/{TRACK}",
|
||||
json={"releases": [{"versionCodes": [version_code], "status": "completed"}]},
|
||||
timeout=30,
|
||||
)
|
||||
track_resp.raise_for_status()
|
||||
for track in TRACKS:
|
||||
track_resp = session.put(
|
||||
f"{_BASE}/{PACKAGE_NAME}/edits/{edit_id}/tracks/{track}",
|
||||
json={"releases": [{"versionCodes": [version_code], "status": "completed"}]},
|
||||
timeout=30,
|
||||
)
|
||||
track_resp.raise_for_status()
|
||||
|
||||
commit_resp = session.post(
|
||||
f"{_BASE}/{PACKAGE_NAME}/edits/{edit_id}:commit",
|
||||
timeout=30,
|
||||
)
|
||||
commit_resp.raise_for_status()
|
||||
print(f"Deployed version {version_code} to {TRACK} track")
|
||||
print(f"Deployed version {version_code} to tracks: {', '.join(TRACKS)}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -95,6 +95,30 @@ class TestMainHappyPath(unittest.TestCase):
|
||||
track_call = session.put.call_args_list[0]
|
||||
self.assertIn("/tracks/", track_call[0][0])
|
||||
|
||||
def test_updates_all_configured_tracks(self):
|
||||
session = self._run_main()
|
||||
track_urls = [c[0][0] for c in session.put.call_args_list]
|
||||
self.assertEqual(len(track_urls), len(deploy_playstore.TRACKS))
|
||||
for track in deploy_playstore.TRACKS:
|
||||
self.assertTrue(
|
||||
any(url.endswith(f"/tracks/{track}") for url in track_urls),
|
||||
f"no PUT to /tracks/{track} (saw {track_urls})",
|
||||
)
|
||||
|
||||
def test_commits_after_all_track_updates(self):
|
||||
session = self._run_main()
|
||||
# All PUTs are track updates; commit is the second POST after the
|
||||
# initial edit-create. Verify PUTs precede the commit by checking
|
||||
# mock_calls order across both methods.
|
||||
method_order = [c[0] for c in session.method_calls]
|
||||
commit_idx = next(
|
||||
i for i, m in enumerate(method_order)
|
||||
if m == "post" and ":commit" in session.method_calls[i][1][0]
|
||||
)
|
||||
put_indices = [i for i, m in enumerate(method_order) if m == "put"]
|
||||
self.assertEqual(len(put_indices), len(deploy_playstore.TRACKS))
|
||||
self.assertTrue(all(i < commit_idx for i in put_indices))
|
||||
|
||||
|
||||
class TestUploadRetry(unittest.TestCase):
|
||||
def _run_main(self, upload_side_effects, sleep_mock=None):
|
||||
|
||||
Reference in New Issue
Block a user