Releases of the Meadow firmware. A release pulls together the binaries built from two repositories:
- Meadow.OS: the OS, the Mono runtime and the
managed assemblies (
Meadow.OS.bin,Meadow.OS.Update.bin,Meadow.OS.Runtime.bin,meadow_assemblies/). - Meadow-ESP32: the ESP32 firmware
(
bootloader.bin,partition-table.bin,MeadowComms.bin).
The releases are published as GitHub Releases in this repository, in the layout the Meadow CLI
(meadow firmware download) expects.
A release is made by running create_release.py from a laptop, by a release manager. The script
does the work; the release manager decides which commits go in.
- Python 3.9 or later and PyYAML (
pip install pyyaml). - The GitHub CLI (
gh), logged in (gh auth login) with write access to this repository, and to Meadow.OS and Meadow-ESP32 (the script tags commits and opens pull requests there). - A clone of this repository. The script does not need clones of the other two.
release.yaml says what to release. Edit it, commit it and push it before a real release:
Release:
Tag: 2.999.51
Version: 2.999.51
Description: Beta 1, NuttX 12, Managed Code
Notes: |
## Highlights
- NuttX 12
- Managed code updates
Type: Beta
OS:
Source: Rebase
Commit: <full Meadow.OS commit hash>
ESP32:
Commit: <full Meadow-ESP32 commit hash>| Field | Meaning |
|---|---|
Version |
major.minor.y, for example 2.999.51. The script adds the build number (see below). You may write all four parts, in which case the script checks them. |
Tag |
Optional. Leave it out, or make it the same as Version, and the tag is the full version, for example 2.999.51.3. |
Description |
Optional text for the release title and notes. Keep it to one line. |
Notes |
Optional Markdown for the body of the release, shown after the description. Write it as a YAML block (Notes: | with the text indented below it) to keep several lines. |
Type |
Optional, Beta or Full. It is worked out from y, and the script stops if it disagrees. |
OS.Source |
Rebase or Meadow.OS (the Meadow.OS repository). Legacy is not supported yet. |
OS.Commit |
The Meadow.OS commit to release. |
ESP32.Commit |
The Meadow-ESP32 commit to release. |
Use full 40-character hashes. The script resolves shorter ones, but a short hash can be ambiguous.
A version is major.minor.y.build, for example 3.0.1.412.
major.minor.ycomes fromversion.txtin the repository. Meadow.OS and Meadow-ESP32 must have the samemajor.minor.yat the commits you pick.buildis worked out from the Meadow.OS commit exactly asbuild.shworks it out: the number of commits sinceversion.txtlast changed. You never type it. The script also checks that the binary was stamped with the same number.- If
yis odd the release is a Beta and is published as a GitHub pre-release. Ifyis even (or 0) it is a Full release. - After a release the version moves on: odd
ygoes up by 2, evenyby 1, so the next build is a beta. The script does this for you (see "The version bump").
The commit must be one that GitHub Actions built when it was merged. The script finds the build by the commit hash and downloads its binaries, so a commit with no successful build cannot be released.
- Meadow.OS builds on pushes to
main(andMergeManagedCode): the workflowBuild OS and RT. - Meadow-ESP32 builds on pushes to
develop: the workflowBuild.
Pull request builds do not count, because they build the merge of the pull request, not the commit. Build artifacts expire after 90 days, so release soon after the merge.
A repository that merges with merge commits shows them with --merges. Fetch first so you see
what is on GitHub:
git -C <Meadow.OS clone> fetch origin
git -C <Meadow.OS clone> log origin/main --merges -5 --format='%H %ad %s' --date=short
git -C <Meadow-ESP32 clone> fetch origin
git -C <Meadow-ESP32 clone> log origin/develop --merges -5 --format='%H %ad %s' --date=shortA squash merge leaves one ordinary commit, which --merges does not list. To see everything that
landed on the branch, whichever way it was merged, follow the first parent only:
git -C <Meadow.OS clone> log origin/main --first-parent -5 --format='%H %ad %s' --date=short
git -C <Meadow-ESP32 clone> log origin/develop --first-parent -5 --format='%H %ad %s' --date=shortBoth commands print the full hash (%H), the date and the subject.
Without a clone, GitHub can list the last 5 merge commits (commits with two parents). It cannot
separate squash commits from the commits inside a merged pull request, so for squash merges use a
clone and --first-parent:
gh api 'repos/WildernessLabs/Meadow.OS/commits?sha=main&per_page=50' \
--jq '[.[] | select((.parents | length) > 1)][:5][] | "\(.sha) \(.commit.message | split("\n")[0])"'
gh api 'repos/WildernessLabs/Meadow-ESP32/commits?sha=develop&per_page=50' \
--jq '[.[] | select((.parents | length) > 1)][:5][] | "\(.sha) \(.commit.message | split("\n")[0])"'Pick a commit from the lists above that also appears here with a success result:
gh run list -R WildernessLabs/Meadow.OS --workflow "Build OS and RT" --event push --branch main -L 10 \
--json headSha,conclusion,createdAt --jq '.[] | "\(.headSha) \(.conclusion) \(.createdAt)"'
gh run list -R WildernessLabs/Meadow-ESP32 --workflow Build --event push --branch develop -L 10 \
--json headSha,conclusion,createdAt --jq '.[] | "\(.headSha) \(.conclusion) \(.createdAt)"'-
Choose the commits (above) and put them in
release.yaml, with theVersion(major.minor.y) and aDescription. -
Commit and push
release.yaml. The tag points at the commit holding it. -
Do a dry run. It runs every check and builds the release assets, but publishes nothing and changes nothing on GitHub:
./create_release.py --dry-run
Read the output. Every line is
ok,WARNorERROR; an error stops the release. The staged assets are left inwork/<tag>/<timestamp>/release/so you can inspect them. Use--skip-sourcesto skip the large source archives while testing. -
When the dry run is clean, make the release:
./create_release.py
It repeats all the checks, prints a summary and asks you to type the tag to confirm. Then it creates the GitHub Release (a beta is a pre-release), tags the source commits, and does the version bump.
Each run works in a new timestamped folder under work/, which git ignores. Delete old ones when
you no longer need them.
- The release file is complete and the version is valid.
Type(if given) matchesy.- Both commits exist, and the Meadow.OS and Meadow-ESP32 versions match
release.yaml. - The build number derived from the Meadow.OS commit matches any build number you typed.
- The Mono runtime pinned by
MONO_RUNTIME_COMMITat the OS commit exists, with the right tree. - Your login can write to this repository, and the tag and release do not already exist.
release.yamlis committed and pushed (a warning in a dry run, an error in a real one).- You can write to Meadow.OS and Meadow-ESP32, and neither has the release tag on a different commit.
- A successful push build exists for each commit, and its artifacts have not expired.
- The binaries came from the commits: the OS
build-info.jsonand the build stamp inMeadow.OS.Update.bin, the build hash inMeadowComms.bin, and the ESP32.md5files.
| Asset | Contents |
|---|---|
Meadow.OS.zip |
Meadow.OS.bin, Meadow.OS.Update.bin, Meadow.OS.Runtime.bin, meadow_assemblies/ |
Meadow.Network.zip |
bootloader.bin, partition-table.bin, MeadowComms.bin and their .md5 files |
Meadow.Debug.zip |
ELF and map files for the OS, bootloader and ESP32 |
manifest.json |
Generated: version, type, the SHA-256 of every file, and the full source commits |
release.yaml |
The release file that was used |
sources-*.tar.gz |
The Meadow.OS and Meadow-ESP32 sources at the released commits |
After the release is published the script tags the released commit in each source repository with
the release tag, for example 2.999.51.3, so a release can be found from the source. The tag is an
annotated tag whose message links to the release. Tags are created in Meadow.OS and Meadow-ESP32,
not in Meadow-ESP32's other branches or in the runtime.
- A tag that already exists on the same commit is left alone.
- A tag that exists on a different commit is an error: tags are never moved. The dry run reports it before anything is published.
- Tagging happens before the version bump. The tags are on branch commits, so they do not start the build workflows, which run on branch pushes only. (A commit that is tagged is reported with its tag instead of its branch name in the build info of any later build of it.)
If tagging fails after the release was published, the release stands. Fix the cause and repeat just the tagging:
./create_release.py --tag-only --dry-run # see what it would do
./create_release.py --tag-only # do it (asks you to type the tag)After the release the script moves version.txt on in both source repositories, so the next build
is a beta:
| Repository | Base branch |
|---|---|
| Meadow.OS | main |
| Meadow-ESP32 | develop |
| Meadow (Legacy, not used yet) | main |
For each one it creates a branch meadow-<new version> from the base branch, commits the new
version.txt, and opens a pull request. A repository is skipped, with a message, if its base
branch is already at the new version, its version.txt is not what was expected, or the pull
request already exists. Review and merge the pull requests as normal.
The bump is safe to repeat. If it fails after the release was published, the release stands. Fix the cause and run just the bump again:
./create_release.py --bump-only --dry-run # see what it would do
./create_release.py --bump-only # do it (asks you to type 'bump')| Message | Cause and fix |
|---|---|
no successful 'Build ...' push run for ... |
The commit was never built on merge. Choose a commit from the "Which commits were built" lists. |
artifact ... has expired |
The build is more than 90 days old. Rebuild the commit, or release a newer one. |
release.yaml says version ... but the derived version is ... |
You typed a build number that does not match. Leave it out (major.minor.y). |
version.txt is ..., but release.yaml says ... |
The commit is on a different version line from the one you asked for. |
Type ... does not match version |
An odd y is a Beta and an even y is a Full release. |
... already has a tag ... on a different commit |
A source repository already has the release tag on another commit. Tags are not moved; check the version and the commits. |
a tag named ... already exists |
That version was already released. Releases are never replaced; choose a new commit and version. |
release.yaml has uncommitted changes |
Commit and push it before a real release. |
Meadow.OS.Update.bin is not stamped with build ... |
The CI build counted a different build number. Do not release that commit. |
import_history.py adds old firmware releases to this repository so the CLI can download them. Put
each version in its own folder, in the layout the Meadow CLI keeps its downloaded firmware in
(work/History/<version>/; work/ is not tracked by git, so the history folder stays on your
machine):
work/History/1.0.0.0/
Meadow.OS.bin Meadow.OS.Update.bin Meadow.OS.Runtime.bin
bootloader.bin partition-table.bin MeadowComms.bin
build-info.json (optional, records the OS commit and build date)
meadow_assemblies/ (the .dll and .pdb files)
work/History/1.0.0.0.md (optional release notes, in Markdown)
The release notes file sits beside the folder and has the same name plus .md. If it is there its
contents go into the body of the release, after the description. A notes file with no folder of
the same name (a misnamed file, for example) is reported as a warning and not used.
Then:
./import_history.py --dry-run # check everything and stage the assets, publish nothing
./import_history.py # the same, then create the releases (asks you to type 'import')For each version it creates a release named after the folder, in the same form as a new release:
Meadow.OS.zip, Meadow.Network.zip, manifest.json, build-info.json, and the OS source archive
when the commit is known. The manifest says "generation": "legacy" and "imported": true.
Historical releases are never marked as the latest release. Options: --version V (repeatable) to
import some versions only, --skip-existing to carry on after a failure or a re-run, --skip-sources
to leave out the large source archive (the 1.0.0.0 one is about 145 MB), and --info FILE.
- The ESP32 source commit is not in the binaries, so the manifest records it as
nulland the ESP32 source is not saved. If you know it, add it tohistory.yaml. - The OS source is saved only if
build-info.jsonis there and the commit is inWildernessLabs/Meadow(the repository the historical OS was built from). Submodule contents are not included, as for new releases. - No debug symbols, no
.md5files for the ESP32 binaries, and no runtime pin: none of these were saved with the old builds. - The minimum CLI version is not recorded anywhere, so it is
0.0.0(any CLI) unless you give it. - The date. GitHub dates a release when it is created, so the release shows the day you
imported it. The real build date is in the manifest (
buildDate) and the release notes. - Each tag points at the current pushed commit of this repository, not at the original source.
Facts that the folders do not hold go in an optional work/History/history.yaml (or the file named
by --info). Every field is optional:
Releases:
1.0.0.0:
Description: Meadow 1.0
Type: Full # Beta or Full; default Full
MinCLIVersion: 1.0.0
OS:
Commit: <full commit in WildernessLabs/Meadow> # overrides build-info.json
ESP32:
Commit: <full commit in WildernessLabs/Meadow-ESP32>A version tag such as 1.0.0.0 cannot clash with new releases, which have versions like 2.999.51.3.
Check the dry run output before the real run: it lists every warning (for example the unknown ESP32
commit) and tells you if a tag or release already exists.
create_release.py is typed and documented. The logic that needs no network has unit tests:
python3 -m unittest -vThe settings for the linters (ruff and pylint) are in pyproject.toml; the script is checked with
ruff, pylint and mypy --strict. After a change, run the tests, then a --dry-run against a real
release file.
OS.Source: Legacyis not supported, and old releases have not been imported from S3.- The Meadow CLI still reads the old S3 location. It must be changed to download from these
releases and to check the SHA-256 values in
manifest.json. - Tag and release creation is not yet restricted to release managers on GitHub. This repository
needs a tag ruleset limiting who can create, move or delete release tags, review (CODEOWNERS) of
release.yamland the script, and GitHub's immutable releases setting.