Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 33 additions & 4 deletions .github/workflows/android.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
name: Build Android

# Pull requests only. A push to `main` is handled by release.yml,
# which builds the signed, uploadable artifact — running this as well would
# spend a second runner producing an unsigned one nobody installs.
# Pull requests, and the pushes to `main` that publish no snapshot. A push
# that does is handled by release.yml, which builds the signed, uploadable
# artifact — running this as well would spend a second runner producing an
# unsigned one nobody installs. One that does not would otherwise be built by
# nothing at all.
on:
pull_request:
branches: ["main"]
push:
branches: ["main"]

jobs:
changes:
Expand All @@ -14,8 +18,14 @@ jobs:
contents: read
outputs:
android: ${{ steps.filter.outputs.android }}
release: ${{ steps.release.outputs.builds }}
steps:
- uses: actions/checkout@v5
with:
# A push reads its own commit messages below: full history, without
# the file contents nothing here looks at.
fetch-depth: ${{ github.event_name == 'push' && '0' || '1' }}
filter: ${{ github.event_name == 'push' && 'blob:none' || '' }}
- uses: dorny/paths-filter@v4
id: filter
with:
Expand All @@ -26,9 +36,28 @@ jobs:
- 'lib/**'
- 'pubspec.yaml'

# Whether release.yml builds this push, for the job below. It publishes
# a snapshot only when a commit since the last tag carries a changelog
# entry; asked here of the push's own commits, which are always within
# that range. So "yes" here is always "yes" there, and a push this
# answers "no" for is built here — at worst by both, never by neither.
- name: Does release.yml build this push
id: release
if: github.event_name == 'push'
env:
BEFORE: ${{ github.event.before }}
run: |
if bash tool/release/notes.sh --any "$BEFORE..$GITHUB_SHA"; then
echo "builds=true" >> "$GITHUB_OUTPUT"
echo "this push brings a changelog entry: release.yml builds it"
else
echo "builds=false" >> "$GITHUB_OUTPUT"
echo "no changelog entry in this push: no snapshot, so it is built here"
fi

Android:
needs: changes
if: ${{ needs.changes.outputs.android == 'true' }}
if: ${{ needs.changes.outputs.android == 'true' && needs.changes.outputs.release != 'true' }}
runs-on: ubuntu-latest
permissions:
contents: read
Expand Down
37 changes: 33 additions & 4 deletions .github/workflows/ios.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
name: Build iOS

# Pull requests only. A push to `main` is handled by release.yml,
# which builds the signed, uploadable artifact — running this as well would
# spend a second runner producing an unsigned one nobody installs.
# Pull requests, and the pushes to `main` that publish no snapshot. A push
# that does is handled by release.yml, which builds the signed, uploadable
# artifact — running this as well would spend a second runner producing an
# unsigned one nobody installs. One that does not would otherwise be built by
# nothing at all.
#
# Every pull request, not only the ones targeting `main`. A stacked branch
# opens its pull request against the layer below it, so `branches: [main]`
Expand All @@ -11,6 +13,8 @@ name: Build iOS
# commits reached review that way without Swift being built once.
on:
pull_request:
push:
branches: [main]

# One in-flight run per ref; a new push cancels the previous run. This matters
# more now that the trigger is every pull request: restacking a five-deep chain
Expand All @@ -26,8 +30,14 @@ jobs:
contents: read
outputs:
ios: ${{ steps.filter.outputs.ios }}
release: ${{ steps.release.outputs.builds }}
steps:
- uses: actions/checkout@v5
with:
# A push reads its own commit messages below: full history, without
# the file contents nothing here looks at.
fetch-depth: ${{ github.event_name == 'push' && '0' || '1' }}
filter: ${{ github.event_name == 'push' && 'blob:none' || '' }}
- uses: dorny/paths-filter@v4
id: filter
with:
Expand All @@ -38,9 +48,28 @@ jobs:
- 'lib/**'
- 'pubspec.yaml'

# Whether release.yml builds this push, for the job below. It publishes
# a snapshot only when a commit since the last tag carries a changelog
# entry; asked here of the push's own commits, which are always within
# that range. So "yes" here is always "yes" there, and a push this
# answers "no" for is built here — at worst by both, never by neither.
- name: Does release.yml build this push
id: release
if: github.event_name == 'push'
env:
BEFORE: ${{ github.event.before }}
run: |
if bash tool/release/notes.sh --any "$BEFORE..$GITHUB_SHA"; then
echo "builds=true" >> "$GITHUB_OUTPUT"
echo "this push brings a changelog entry: release.yml builds it"
else
echo "builds=false" >> "$GITHUB_OUTPUT"
echo "no changelog entry in this push: no snapshot, so it is built here"
fi

iOS:
needs: changes
if: ${{ needs.changes.outputs.ios == 'true' }}
if: ${{ needs.changes.outputs.ios == 'true' && needs.changes.outputs.release != 'true' }}
runs-on: macos-latest
permissions:
contents: read
Expand Down
30 changes: 27 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
# Every commit on main is a snapshot; every tag is a release.
# A push to main that brings a changelog entry is a snapshot; every tag is a
# release. A push whose snapshot note would be empty — no commit since the last
# tag carrying an entry — publishes nothing, and android.yml and ios.yml build it
# instead. Run by hand, a snapshot is published regardless.
#
# The three values a build carries come from `tool/release/version.sh` and nowhere
# else — no human edits a version, and `pubspec.yaml`'s stays a placeholder for
Expand Down Expand Up @@ -30,6 +33,7 @@ jobs:
code: ${{ steps.ordinal.outputs.code }}
date: ${{ steps.v.outputs.date }}
prerelease: ${{ steps.v.outputs.prerelease }}
publish: ${{ steps.changelog.outputs.publish }}
steps:
# Full history: the label counts this week's commits and the train reads
# the newest tag. A shallow clone has neither.
Expand Down Expand Up @@ -70,8 +74,8 @@ jobs:
#
# **Equal** on a tag means this commit was already published as a
# snapshot. That is not an accident, it is the normal shape of a release:
# every push to main publishes, so by the time anyone can tag a commit it
# has already spent its number, and both uploads below run for a snapshot
# every push to main with a changelog entry publishes, so by the time
# anyone can tag a commit it has usually already spent its number, and both uploads below run for a snapshot
# exactly as they do for a release. `v26.1` only shipped because its
# commit never went out as a snapshot; on `v26.2` (`d2b4664b`, carrying
# both `26w35g` and `v26.2`) the tag run computed the snapshot's own
Expand Down Expand Up @@ -118,8 +122,28 @@ jobs:
fi
echo "code=$CODE" >> "$GITHUB_OUTPUT"

# Nothing a user would see: the snapshot's note would say so and nothing
# else, and the stores would get a build that changes nothing. A push to
# main only — a tag is a release, and run by hand, someone asked for one.
# The next push that does bring an entry publishes these commits too,
# since its note runs from the same tag.
- name: Anything to publish
id: changelog
env:
EVENT: ${{ github.event_name }}
run: |
if [ "$GITHUB_REF_TYPE" = branch ] && [ "$EVENT" = push ] &&
! bash tool/release/notes.sh --any; then
echo "publish=false" >> "$GITHUB_OUTPUT"
echo "::notice::no commit since $(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo 'the first') carries a changelog entry — no snapshot; android.yml and ios.yml build this push"
else
echo "publish=true" >> "$GITHUB_OUTPUT"
fi

build:
needs: version
# `publish`, which needs this, is skipped with it.
if: needs.version.outputs.publish == 'true'
strategy:
# Independent: one store failing must not stop the other shipping.
fail-fast: false
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ DPIP(Disaster Prevention Information Platform)是臺灣本土團隊開發的
| **多語言** | 介面支援 11 種語言 |

> [!NOTE]
> 這個儲存庫正在進行架構重寫(feature-first 分層,見 [ARCHITECTURE.md](ARCHITECTURE.md))。`main` 上的每一次提交都會發布一個快照版本,快照未經完整審查。想要穩定版本請從商店安裝。
> 這個儲存庫正在進行架構重寫(feature-first 分層,見 [ARCHITECTURE.md](ARCHITECTURE.md))。推上 `main` 的變更裡有使用者看得到的修改時,會發布一個快照版本,快照未經完整審查。想要穩定版本請從商店安裝。

## 資料來源

Expand Down
27 changes: 24 additions & 3 deletions tool/release/notes.sh
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,25 @@
# changed under them.
#
# Usage: tool/release/notes.sh <label> <code> [--release]
# tool/release/notes.sh --any [<range>]
#
# `--any` writes no note. It exits 0 when a commit in <range> carries a
# changelog entry and 1 when none does — by default the range a snapshot note
# would cover, so 1 means that note would say there is nothing user-facing.
# release.yml publishes no snapshot then; android.yml and ios.yml ask about the
# push's own commits, to build the pushes release.yml does not.
set -euo pipefail

label="${1:?label required}"
code="${2:?build code required}"
kind="${3:-}"
if [ "${1:-}" = "--any" ]; then
mode=any
any_range="${2:-}"
label="" code="" kind=""
else
mode=note
label="${1:?label required}"
code="${2:?build code required}"
kind="${3:-}"
fi

# The primary language, printed unfolded. Every other language is published in
# its own folded, marked block that the in-app changelog picks by locale.
Expand Down Expand Up @@ -109,6 +123,7 @@ else
since="$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || true)"
fi
range="${since:+$since..}HEAD"
[ "$mode" = any ] && [ -n "$any_range" ] && range="$any_range"

# Which platforms an entry applies to, always stated.
#
Expand Down Expand Up @@ -283,6 +298,7 @@ for sha in $(git rev-list --no-merges --reverse "$range" 2>/dev/null); do
END { if (held) printf "\n" }
')"
printf '%s\n' "$body" | grep -Eq "$LINE_RE" || continue
[ "$mode" = any ] && exit 0

tag="$(platform_tag "$sha")"
who="$(authors_of "$sha")"
Expand Down Expand Up @@ -314,6 +330,11 @@ $body
EOF
done

# A range that does not resolve (a `before` the clone does not have) lists no
# commits and lands here too: "no entry" is the answer that gets a push
# built, so an unknown range errs toward building.
[ "$mode" = any ] && exit 1

# Primary first, then the rest alphabetically, so a note's language order does
# not shuffle between builds.
ordered_locales() {
Expand Down