diff --git a/.github/scripts/select-unpublished.py b/.github/scripts/select-unpublished.py new file mode 100755 index 0000000..a766e84 --- /dev/null +++ b/.github/scripts/select-unpublished.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +"""Print the Mill selector for packages whose version is not on Maven Central yet. + +Reads `mill show __.artifactMetadata` on stdin and writes `count=` / `selector=` lines +suitable for $GITHUB_OUTPUT, with a per-package report on stderr. + +Which packages a release publishes is derived from Central rather than from +release-please's outputs, for two reasons. The publish job runs *before* the tagging half, +so those outputs do not exist yet; and asking Central makes the job idempotent — re-running +after a partial failure uploads exactly what is still missing, which is what the +workflow_dispatch escape hatch relies on. + +Caveat: repo1 lags a Portal publish by a few minutes, so a re-run started immediately after +a successful upload can still see a package as missing and try to publish it again, which +Central rejects. Wait for the deployment to reach PUBLISHED before re-running. +""" +import json +import subprocess +import sys + +BASE = "https://repo1.maven.org/maven2" + + +def published(group: str, artifact: str, version: str) -> bool: + """True if the POM is already on Central. Uses curl rather than urllib so the + system CA bundle is used; some Python installs do not have one configured.""" + path = f"{group.replace('.', '/')}/{artifact}/{version}/{artifact}-{version}.pom" + out = subprocess.run( + ["curl", "-sS", "-I", "--max-time", "30", "-o", "/dev/null", + "-w", "%{http_code}", f"{BASE}/{path}"], + capture_output=True, text=True, check=True, + ).stdout.strip() + if out == "200": + return True + if out == "404": + return False + raise RuntimeError(f"unexpected HTTP {out} for {path}") + + +def main() -> int: + meta = json.load(sys.stdin) + missing = [] + for task, m in sorted(meta.items()): + module = task[: -len(".artifactMetadata")] + coord = f"{m['group']}:{m['id']}:{m['version']}" + if published(m["group"], m["id"], m["version"]): + print(f" present {coord}", file=sys.stderr) + else: + print(f" MISSING {coord} ({module})", file=sys.stderr) + missing.append(module) + + print(f"count={len(missing)}") + if len(missing) == 1: + # Mill's brace syntax needs at least two alternatives: `{a,b}` parses, `{a}` does not. + print(f"selector={missing[0]}") + elif missing: + print("selector={" + ",".join(missing) + "}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/.github/scripts/sync-example-versions.py b/.github/scripts/sync-example-versions.py new file mode 100755 index 0000000..40f9384 --- /dev/null +++ b/.github/scripts/sync-example-versions.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python3 +"""Pin the examples to the currently released version of each Chippy package. + +The examples depend on Chippy by released coordinate rather than through `moduleDeps`, so +their version literals have to be maintained by hand. release-please cannot do it once +packages version independently: its generic updater has no component-scoped annotations +and keeps a single version context per file, while every example build file mixes packages +from several version lines. Annotating one would rewrite every coordinate in it with the +same version. + +The artifact -> version mapping is read from the build rather than duplicated here, so it +cannot drift from `versionLine` in build.mill: + + ./mill show '__.artifactMetadata' 2>/dev/null | python3 .github/scripts/sync-example-versions.py + ./mill show '__.artifactMetadata' 2>/dev/null | python3 .github/scripts/sync-example-versions.py --check + +Run it *after* a release has published, not on the release PR: pinning a version that is +not on Maven Central yet fails the examples job for everyone until it is. +""" +import json +import pathlib +import re +import sys + +COORD = re.compile(r'(mvn"io\.github\.ucb-substrate::)([A-Za-z0-9._-]+)(:)([^"]+)(")') +SKIP = ("saturn-vectors", "shuttle") + + +def main() -> int: + check = "--check" in sys.argv[1:] + meta = json.load(sys.stdin) + # "cde_2.13" -> "cde"; the `::` in a coordinate is what appends the Scala suffix. + versions = { + m["id"].rsplit("_", 1)[0]: m["version"] for m in meta.values() + } + + stale, unknown = [], [] + for f in sorted(pathlib.Path("examples").rglob("*.mill")): + if any(s in str(f) for s in SKIP): + continue + text = f.read_text() + + def repl(m: re.Match) -> str: + artifact, current = m.group(2), m.group(4) + want = versions.get(artifact) + if want is None: + unknown.append((str(f), artifact)) + return m.group(0) + if want != current: + stale.append((str(f), artifact, current, want)) + return f"{m.group(1)}{artifact}{m.group(3)}{want}{m.group(5)}" + + updated = COORD.sub(repl, text) + if updated != text and not check: + f.write_text(updated) + + for path, artifact in unknown: + print(f"unknown artifact {artifact!r} in {path}", file=sys.stderr) + for path, artifact, current, want in stale: + print(f"{'stale' if check else 'updated'}: {path}: {artifact} {current} -> {want}") + + if unknown: + return 2 + if not stale: + print("examples already match the released versions") + return 0 + return 1 if check else 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7ee0f4d..6266859 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -125,13 +125,15 @@ jobs: key: mill-out-examples-${{ runner.os }}-${{ github.sha }} restore-keys: mill-out-examples-${{ runner.os }}- - # The one case where the released artifacts cannot be used: a release bumps the - # examples to the version it is about to publish, so that version is not on Maven - # Central yet. Build it from source instead — on release-please's own PR, which it - # always opens from this branch, and on the commit that lands when that PR is - # merged, which is titled `chore(main): release X.Y.Z`. Its `autorelease:` label is - # not used for this: it is attached after the PR is created and flips to - # `autorelease: tagged` once the release exists. + # Covers the window where an example pins a version that is not on Maven Central + # yet. Example versions are no longer bumped by release-please — see the README — + # so the intended workflow keeps them on published versions and this never fires. + # It stays because syncing them on the release PR instead is a reasonable thing for + # someone to do, and it should not break ci when they do. It triggers on + # release-please's own PR, which it always opens from this branch, and on the commit + # that lands when that PR is merged, which is titled `chore(main): release ...`. Its + # `autorelease:` label is not used for this: it is attached after the PR is created + # and flips to `autorelease: tagged` once the release exists. # # Deliberately conditional rather than a repository that is always available as a # fallback: a package missing from Maven Central has to fail here, not be quietly diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 264084a..5e5ba65 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,10 +10,10 @@ on: workflows: [ci] types: [completed] branches: [main] - # Escape hatch: publish the version currently in version.txt and finish tagging it. - # Unlike the GitHub Pages repository this replaced, Maven Central refuses a version it - # already holds, so this recovers a run that failed before Central accepted the bundle. - # A run that failed after that point is finished by re-running the tag job alone. + # Escape hatch: publish whatever the manifest names that Central does not have yet, and + # finish tagging it. Maven Central refuses a version it already holds, but the publish + # job selects on exactly that, so this is safe to re-run — it uploads only what is still + # missing and does nothing at all once everything has landed. workflow_dispatch: permissions: {} @@ -28,7 +28,7 @@ concurrency: # are live. A publish failure therefore leaves no tag to clean up, and re-running finishes # the release rather than duplicating it. # -# publish -> tag -> release-pr +# publish -> tag -> release-pr -> sync-examples # # The file lists them in that order too; `release-pr` runs last because tagging first is # what stops it from re-proposing a release that is already on its way out. @@ -36,7 +36,9 @@ jobs: # Uploads this release's packages to Maven Central through the Sonatype Central Portal. # # Nothing has been tagged at this point, so the release is identified by the commit - # release-please's PR lands: "chore(main): release X.Y.Z". `release_created` is not + # release-please's PR lands: "chore(main): release". The title carries no version — the + # merged manifest PR takes its component and version from a root ("." path) package, and + # this repository has none, so both are empty. `release_created` is not # available yet by design — it comes from the tagging half, which now runs last. publish: if: >- @@ -71,26 +73,35 @@ jobs: key: mill-${{ runner.os }}-${{ hashFiles('build.mill', 'mill') }} restore-keys: mill-${{ runner.os }}- - # `publishAll` with no `--publishArtifacts` resolves every PublishModule in the - # build, which is exactly the eleven packages and none of the examples — the same - # wildcard property the README asks you not to break by making an example a - # PublishModule. + # Packages version independently, so a release publishes only the ones whose version + # is not on Central yet — which is most of the point: rocket-chip is nearly half the + # bytes of a full release and only moves when its submodule pointer does. # - # Naming a bundle sends all of them to Central as one deployment, so it validates - # and releases them together or not at all. A half-published version is not a state - # this repository can be in — every package shares `version.txt` — and one - # deployment is also one release event against Central's publishing limits rather - # than eleven. + # The set is derived by asking Central rather than by reading release-please's + # outputs, which do not exist yet at this point: the tagging half runs after this + # job, deliberately. Deriving it from Central also makes the job idempotent, which is + # what the workflow_dispatch escape hatch relies on — a re-run uploads exactly what + # is still missing rather than failing on what already landed. + - name: Work out which packages need publishing + id: select + run: | + ./mill show '__.artifactMetadata' 2>/dev/null > metadata.json + python3 .github/scripts/select-unpublished.py < metadata.json >> "$GITHUB_OUTPUT" + + # Whatever is selected goes up as one bundle, so Central validates and releases it + # atomically, and the release costs one deployment against Central's publishing + # limits rather than one per package. # # Signing uses Mill's built-in PGP worker rather than a gpg binary, so # MILL_PGP_SECRET_BASE64 is all the runner needs. `shouldRelease` defaults to true, # which publishes the bundle as soon as Central has validated it instead of leaving # it sitting in the portal for someone to release by hand. # - # The await timeout defaults to two minutes, which validating an eleven-package - # bundle can outrun; twenty costs nothing when things go well, since the call - # returns as soon as Central reports a terminal state. + # The await timeout defaults to two minutes, which validating a large bundle can + # outrun; twenty costs nothing when things go well, since the call returns as soon + # as Central reports a terminal state. - name: Publish to Maven Central + if: steps.select.outputs.count != '0' env: MILL_SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }} MILL_SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} @@ -98,14 +109,29 @@ jobs: MILL_PGP_PASSPHRASE: ${{ secrets.PGP_PASSPHRASE }} run: | ./mill mill.javalib.SonatypeCentralPublishModule/publishAll \ - --bundleName "chippy-$(cat version.txt)" \ + --publishArtifacts '${{ steps.select.outputs.selector }}.publishArtifacts' \ + --bundleName "chippy-$(git rev-parse --short HEAD)" \ --awaitTimeout 1200000 - # Tags the release and creates the GitHub release, now that the artifacts it points at - # are actually resolvable. This keys off the merged release PR's `autorelease: pending` - # label rather than off anything in this run, so re-running after a partial failure - # finishes the release instead of creating a second one. On an ordinary push it is - # skipped along with the publish job it depends on. + # Not a failure: an ordinary release only moves some packages, and a re-run after a + # successful publish legitimately has nothing left to do. + - name: Nothing to publish + if: steps.select.outputs.count == '0' + run: echo "Every package's current version is already on Maven Central." + + # Tags each released package and creates its GitHub release, now that the artifacts they + # point at are actually resolvable. With per-package versions this is one tag and one + # release per package the run actually bumped, not one per package in the repository. + # + # Both halves of this are release-please's own: it has no tag-only mode, because creating + # the tag is a side effect of creating the release. That is also what clears the merged + # release PR's `autorelease: pending` label — release-please refuses to propose anything + # new while a merged release PR still carries it, so the `release-pr` job below depends + # on this one having run. + # + # Keys off that label rather than off anything in this run, so re-running after a partial + # failure finishes the release instead of creating a second one. On an ordinary push it + # is skipped along with the publish job it depends on. tag: needs: [publish] runs-on: ubuntu-latest @@ -119,7 +145,7 @@ jobs: # The tagging half only; the release PR is maintained by the job below. skip-github-pull-request: true - # Maintains the release PR ("chore(main): release X.Y.Z") for every push to main that + # Maintains the release PR ("chore(main): release") for every push to main that # passes ci. On a release push it waits for the tag: release-please decides what to # propose from the commits since the last tag, so running it while a release is still # untagged would have it propose that same release again. If tagging fails it does not @@ -140,3 +166,75 @@ jobs: with: token: ${{ secrets.GITHUB_TOKEN }} skip-github-release: true + + # Adds the example version bumps to the release PR release-please just opened, so a + # release is one pull request rather than a release followed by a cleanup. + # + # release-please cannot do this itself: its component-scoped updaters only handle + # json/toml/yaml/xml, and every example build file mixes packages from several version + # lines, so the annotation-based generic updater would rewrite each file's coordinates + # with a single package's version. + # + # The versions being pinned are not on Maven Central yet — they are the ones this PR + # proposes to publish. That is exactly the window the ci workflow's "Build Chippy from + # source for a release" step covers, keyed off this branch name and off the + # `chore(main): release` commit that lands when the PR merges. + # + # release-please force-pushes its branch whenever it regenerates the PR, dropping this + # commit; this job runs after it on every push to main, so it is simply re-applied. + sync-examples: + needs: [release-pr] + if: ${{ !cancelled() && needs.release-pr.result == 'success' }} + runs-on: ubuntu-latest + permissions: + contents: write + steps: + # There is only a branch to update when a release is actually pending. + - name: Look for a pending release PR + id: check + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + if gh api "repos/${{ github.repository }}/branches/release-please--branches--main" >/dev/null 2>&1; then + echo "exists=true" >> "$GITHUB_OUTPUT" + else + echo "exists=false" >> "$GITHUB_OUTPUT" + echo "no release pending; nothing to sync" + fi + + - uses: actions/checkout@v7 + if: steps.check.outputs.exists == 'true' + with: + ref: release-please--branches--main + submodules: recursive + + - uses: actions/setup-java@v5 + if: steps.check.outputs.exists == 'true' + with: + distribution: temurin + java-version: '21' + + - uses: actions/cache@v6 + if: steps.check.outputs.exists == 'true' + with: + path: | + ~/.cache/coursier + ~/.cache/mill + key: mill-${{ runner.os }}-${{ hashFiles('build.mill', 'mill') }} + restore-keys: mill-${{ runner.os }}- + + - name: Pin the examples to the pending release + if: steps.check.outputs.exists == 'true' + run: | + set -euo pipefail + ./mill show '__.artifactMetadata' 2>/dev/null > metadata.json + python3 .github/scripts/sync-example-versions.py < metadata.json + rm -f metadata.json + if git diff --quiet; then + echo "examples already pin the pending release" + exit 0 + fi + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git commit -am "chore(examples): pin to the pending release" + git push origin HEAD:release-please--branches--main diff --git a/.release-please-manifest.json b/.release-please-manifest.json index a915e8c..3815790 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,9 @@ { - ".": "0.1.1" + "rocket-chip": "0.1.1", + "rocket-chip-blocks": "0.1.1", + "rocket-chip-inclusive-cache": "0.1.1", + "testchipip": "0.1.1", + "constellation": "0.1.1", + "chipyard": "0.1.1", + "chippy": "0.1.1" } diff --git a/README.md b/README.md index 2d79b6b..f47195b 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,28 @@ The namespace is a coordinate, not a package name: the Scala packages are unchan still lives in `edu.berkeley.cs.chippy` and rocket-chip in `freechips.rocketchip`. Nothing in an `import` moves. +### Packages + +Eleven artifacts are published, listed here roughly bottom-up. API documentation is rendered from the +published Scaladoc jars by [javadoc.io](https://javadoc.io); the first request for a given version +takes a moment while it unpacks the jar. The +[Maven Central namespace page](https://central.sonatype.com/namespace/io.github.ucb-substrate) is the +canonical listing. + +| Package | Description | API docs | +| --- | --- | --- | +| `cde` | A Scala library for Context-Dependent Environments. | [docs](https://javadoc.io/doc/io.github.ucb-substrate/cde_2.13/latest) | +| `diplomacy` | A parameter negotiation framework for Chisel. | [docs](https://javadoc.io/doc/io.github.ucb-substrate/diplomacy_2.13/latest) | +| `hardfloat` | Hardware floating-point units written in Chisel. | [docs](https://javadoc.io/doc/io.github.ucb-substrate/hardfloat_2.13/latest) | +| `rocketchip-macros` | Scala macros used by the Rocket Chip generator. | [docs](https://javadoc.io/doc/io.github.ucb-substrate/rocketchip-macros_2.13/latest) | +| `rocketchip` | The Rocket Chip generator. | [docs](https://javadoc.io/doc/io.github.ucb-substrate/rocketchip_2.13/latest) | +| `rocketchip-blocks` | RTL blocks compatible with the Rocket Chip generator. | [docs](https://javadoc.io/doc/io.github.ucb-substrate/rocketchip-blocks_2.13/latest) | +| `rocketchip-inclusive-cache` | An RTL generator for a last-level shared inclusive TileLink cache controller. | [docs](https://javadoc.io/doc/io.github.ucb-substrate/rocketchip-inclusive-cache_2.13/latest) | +| `testchipip` | Useful IP components for chips. | [docs](https://javadoc.io/doc/io.github.ucb-substrate/testchipip_2.13/latest) | +| `constellation` | A Chisel NoC RTL generator framework designed to provide the core interconnect fabric for heterogeneous many-core, many-accelerator SoCs. | [docs](https://javadoc.io/doc/io.github.ucb-substrate/constellation_2.13/latest) | +| `chippy` | An SoC design framework for integrating cores, accelerators, and other peripherals. | [docs](https://javadoc.io/doc/io.github.ucb-substrate/chippy_2.13/latest) | +| `chipyard` | Prebuilt config fragments and full chip configs for Chippy. | [docs](https://javadoc.io/doc/io.github.ucb-substrate/chipyard_2.13/latest) | + To include a package in a new project, add the dependency. If you are using Mill 1.1.2, for example, add the following to your `build.mill` to use the `diplomacy` package: @@ -47,9 +69,32 @@ Usage examples can be found in the `examples/` folder. ## Releasing -Versions are managed by [release-please](https://github.com/googleapis/release-please) and are shared -by every published package. `version.txt` is the single source of truth; `build.mill` reads it, so no -version numbers are hardcoded in the build. +Versions are managed by [release-please](https://github.com/googleapis/release-please) and are **per +package**, so a release publishes only what changed. `.release-please-manifest.json` is the single +source of truth; `build.mill` reads it, so no version numbers are hardcoded in the build. + +There are seven version lines, one per submodule rather than one per artifact: + +| Version line | Artifacts | +| --- | --- | +| `rocket-chip` | `rocketchip`, `rocketchip-macros`, `cde`, `diplomacy`, `hardfloat` | +| `rocket-chip-blocks` | `rocketchip-blocks` | +| `rocket-chip-inclusive-cache` | `rocketchip-inclusive-cache` | +| `testchipip` | `testchipip` | +| `constellation` | `constellation` | +| `chipyard` | `chipyard` | +| `chippy` | `chippy` | + +The grouping is forced rather than chosen. release-please attributes a commit to a package by the +paths it touches, and every source tree above except `chippy/` is a git submodule — so bumping one +appears as a change to exactly one path. The five artifacts built out of `rocket-chip` share a +version because nothing distinguishes them in the commit history, which is also how they actually +change. + +Version lines move independently, and a package is **not** republished when something it depends on +bumps: `chippy` at 0.1.1 keeps referencing whatever `rocketchip` version it was built against, which +is ordinary Maven behaviour. Mill derives each POM's dependency versions from the depended-on +module's own `publishVersion`, so this needs no bookkeeping. The flow is: @@ -58,14 +103,26 @@ The flow is: footer also bumps the minor version while the project is pre-1.0. Commits with any other prefix (`chore:`, `docs:`, ...) do not trigger a release. Since the repository squash-merges, the **PR title** is what ends up in the commit history and therefore what release-please parses. -2. release-please keeps a `chore(main): release X.Y.Z` PR open with the pending version bump. -3. Merging that PR publishes every package to Maven Central and then tags the release. +2. release-please keeps a single `chore(main): release` PR open covering every package with a pending + bump. +3. Merging that PR publishes the packages whose versions are not on Maven Central yet, then tags + each of them. Tags carry the component: `chippy-v0.1.2`, `rocketchip-v0.2.0`. + +Tagging and the GitHub release for a package are one step, done by release-please: creating the tag +is a side effect of creating the release, and the same step clears the merged release PR's +`autorelease: pending` label, which release-please requires before it will propose anything new. Only +the packages a run actually bumped are tagged and released, not all seven. + +The publish job works out what to upload by asking Central which versions already exist, rather than +by reading release-please's outputs — those do not exist yet, because tagging deliberately runs +afterwards. That also makes it idempotent: re-running uploads only what is still missing. ### Publishing credentials -Every package goes to Central as a single signed bundle, uploaded by -`mill.javalib.SonatypeCentralPublishModule/publishAll`, so a version is either published whole or not -at all. Unlike the GitHub Pages repository this replaced, that needs four repository secrets, which +Whatever a release publishes goes to Central as a single signed bundle, uploaded by +`mill.javalib.SonatypeCentralPublishModule/publishAll`, so it is accepted whole or not at all — and +costs one deployment against Central's publishing limits rather than one per package. Unlike the +GitHub Pages repository this replaced, that needs four repository secrets, which `.github/workflows/release.yml` passes to Mill under its own `MILL_`-prefixed names: - `SONATYPE_USERNAME` and `SONATYPE_PASSWORD` — a *user token*, generated from the account page of @@ -81,9 +138,11 @@ personal account, Central does not grant it automatically on login: add the name then create a public repository in the organization named after the verification key it hands back. Everything else still runs with the built-in `GITHUB_TOKEN`. -Central refuses a version it already holds, so a release cannot be re-published over itself. The -`workflow_dispatch` escape hatch therefore recovers a run that failed *before* the bundle was -accepted; one that failed after that is finished by re-running the `tag` job alone. +Central refuses a version it already holds, but the publish job selects on exactly that, so the +`workflow_dispatch` escape hatch is safe to re-run: it uploads only what is still missing and does +nothing once everything has landed. Give Central a few minutes to reach `PUBLISHED` before +re-running, though — `repo1` lags a publish, so a re-run started immediately after one can still see +a package as missing and try it again. ### Examples @@ -94,17 +153,33 @@ keeps them out of the wildcards the release and ci jobs resolve. **Do not make a `PublishModule`**: it would be picked up by the release, and because its dependencies are the artifacts that same job is producing, the build would fail to resolve them on a clean checkout. -Their pinned versions are bumped automatically. Every `io.github.ucb-substrate` dependency line -carries an `x-release-please-version` comment, and the snippets in this README are wrapped in the block form of -the same annotation, so the release PR updates them alongside `version.txt`. The build files use the -per-line form rather than the block form on purpose: a block rewrites every semver-looking literal it -spans, which would also catch neighbouring lines such as the ScalaTest dependency. +Their pinned versions are bumped automatically, as part of the release PR. The `sync-examples` job in +`.github/workflows/release.yml` runs after release-please has opened or updated that PR, rewrites the +example coordinates to the versions it proposes, and pushes the result onto the same branch. A +release is therefore one pull request, and the examples always demonstrate the version being +published. + +Those versions are not on Maven Central at that point — they are what the PR is proposing to publish. +That is exactly the window the ci workflow's "Build Chippy from source for a release" step covers, +which is keyed off release-please's branch name and off the `chore(main): release` commit that lands +when the PR merges. -Note that release-please scans this file too, so avoid writing the literal block-annotation markers -in prose — an unmatched opening marker turns the rest of the file into a replacement zone. +release-please cannot do the rewrite itself. Its component-scoped updaters only handle +json/toml/yaml/xml, and the annotation-based generic updater keeps a single version context per file, +while every example build file mixes packages from several version lines — annotating one would +rewrite every coordinate in it with the same version. Keeping literal versions in the build files +rather than reading them from the manifest is also what keeps the examples copy-pasteable. + +`.github/scripts/sync-example-versions.py` does the rewrite, taking the artifact-to-version mapping +from the build itself so it cannot drift from `versionLine`. To run it by hand: + +``` +./mill show '__.artifactMetadata' 2>/dev/null | python3 .github/scripts/sync-example-versions.py +./mill show '__.artifactMetadata' 2>/dev/null | python3 .github/scripts/sync-example-versions.py --check +``` -Because the release PR bumps the examples to the version it is about to publish, the examples briefly -reference a version that does not exist yet — from the moment the release PR is opened until the -publish job finishes after it is merged. Central takes a few more minutes to propagate a published -bundle to the mirrors coursier fetches from, so a run started immediately after a release may extend -that window slightly. +The snippets in this README *are* still updated automatically: they only ever show `diplomacy`, so +the file is listed under the `rocket-chip` package's `extra-files` and the block annotations in it +have a single version context. Note that release-please scans this file, so avoid writing the literal +block-annotation markers in prose — an unmatched opening marker turns the rest of the file into a +replacement zone. diff --git a/build.mill b/build.mill index f3b072b..e1ed413 100644 --- a/build.mill +++ b/build.mill @@ -21,10 +21,18 @@ trait ChippyModule extends ScalaModule { } trait ChippyPublishModule extends ChippyModule, PublishModule { - // Single source of truth for the version of every published Chippy artifact. - // Bumped automatically by release-please; see .github/workflows/release.yml. - def versionFile = Task.Source(BuildCtx.workspaceRoot / "version.txt") - def publishVersion = Task { os.read(versionFile().path).trim } + // The release-please package this artifact's version comes from. Packages are one per + // submodule rather than one per artifact, because release-please attributes commits by + // path and a submodule bump touches exactly one: the five artifacts built out of + // `rocket-chip` therefore share a version, which is also how they actually change. + def versionLine: String + + // Versions live only in the release-please manifest, keyed by that same path. Nothing + // duplicates them — release-please's `simple` strategy does write a `version.txt` under + // each package path, but with createIfMissing: false, and these paths are submodules + // where no such file exists, so the manifest is the sole source of truth. + def versionManifest = Task.Source(BuildCtx.workspaceRoot / ".release-please-manifest.json") + def publishVersion = Task { ujson.read(os.read(versionManifest().path))(versionLine).str } def makePomSettings(description: String) = PomSettings( description = description, @@ -39,6 +47,7 @@ trait ChippyPublishModule extends ChippyModule, PublishModule { } object rocketchip extends ChippyPublishModule { + def versionLine = "rocket-chip" def moduleDir = super.moduleDir / os.up / "rocket-chip" def resources = Task.Sources { moduleDir / "src" / "main" / "resources" } @@ -54,18 +63,20 @@ object rocketchip extends ChippyPublishModule { mvn"org.chipsalliance:::chisel-plugin:${chiselVersion}" ) - def pomSettings = makePomSettings("Hardware floating-point units written in Chisel.") + def pomSettings = makePomSettings("The Rocket Chip generator.") object macros extends ChippyPublishModule { + def versionLine = "rocket-chip" def mvnDeps = Seq( mvn"org.scala-lang:scala-reflect:${scalaVersion}", ) - def pomSettings = makePomSettings("Hardware floating-point units written in Chisel.") + def pomSettings = makePomSettings("Scala macros used by the Rocket Chip generator.") } object dependencies extends Module { object cde extends ChippyPublishModule { + def versionLine = "rocket-chip" def artifactName = "cde" def sources = Task.Sources(this.moduleDir / "cde" / "src") @@ -82,6 +93,7 @@ object rocketchip extends ChippyPublishModule { } object diplomacy extends ChippyPublishModule { + def versionLine = "rocket-chip" def artifactName = "diplomacy" def sources = Task.Sources(this.moduleDir / "diplomacy" / "src") @@ -100,6 +112,7 @@ object rocketchip extends ChippyPublishModule { } object hardfloat extends ChippyPublishModule { + def versionLine = "rocket-chip" def artifactName = "hardfloat" def sources = Task.Sources(this.moduleDir / "hardfloat" / "src") @@ -127,6 +140,7 @@ object rocketchip extends ChippyPublishModule { } object `rocketchip-blocks` extends ChippyPublishModule { + def versionLine = "rocket-chip-blocks" def moduleDir = super.moduleDir / os.up / "rocket-chip-blocks" def moduleDeps = Seq(rocketchip) @@ -138,10 +152,11 @@ object `rocketchip-blocks` extends ChippyPublishModule { mvn"org.chipsalliance:::chisel-plugin:${chiselVersion}" ) - def pomSettings = makePomSettings("RTL generators designed to be compatible with Rocket Chip.") + def pomSettings = makePomSettings("RTL blocks compatible with the Rocket Chip generator.") } object `rocketchip-inclusive-cache` extends ChippyPublishModule { + def versionLine = "rocket-chip-inclusive-cache" def moduleDir = super.moduleDir / os.up / "rocket-chip-inclusive-cache" def sources = Task.Sources(moduleDir / "design" / "craft" / "inclusivecache") @@ -158,6 +173,7 @@ object `rocketchip-inclusive-cache` extends ChippyPublishModule { } object testchipip extends ChippyPublishModule { + def versionLine = "testchipip" def resources = Task.Sources { moduleDir / "src" / "main" / "resources" } def moduleDeps = Seq(rocketchip.dependencies.cde, rocketchip, `rocketchip-blocks`) @@ -173,6 +189,7 @@ object testchipip extends ChippyPublishModule { } object constellation extends ChippyPublishModule { + def versionLine = "constellation" def sources = Task.Sources(this.moduleDir / "src" / "main") def moduleDeps = Seq(rocketchip.dependencies.cde, rocketchip, rocketchip.macros) @@ -196,6 +213,7 @@ object constellation extends ChippyPublishModule { } object chippy extends ChippyPublishModule { + def versionLine = "chippy" def moduleDeps = Seq(rocketchip.dependencies.cde, rocketchip.dependencies.diplomacy, rocketchip) def mvnDeps = Seq( @@ -205,10 +223,11 @@ object chippy extends ChippyPublishModule { mvn"org.chipsalliance:::chisel-plugin:${chiselVersion}" ) - def pomSettings = makePomSettings("An SoC design framework for integrating cores, accelerators, and other peripherals. ") + def pomSettings = makePomSettings("An SoC design framework for integrating cores, accelerators, and other peripherals.") } object chipyard extends ChippyPublishModule { + def versionLine = "chipyard" def sources = Task.Sources(this.moduleDir / "src" / "main") def resources = Task.Sources { moduleDir / "src" / "main" / "resources" } @@ -222,7 +241,7 @@ object chipyard extends ChippyPublishModule { mvn"org.chipsalliance:::chisel-plugin:${chiselVersion}" ) - def pomSettings = makePomSettings("An SoC design framework for integrating cores, accelerators, and other peripherals. ") + def pomSettings = makePomSettings("Prebuilt config fragments and full chip configs for Chippy.") object test extends ScalaTests, TestModule.ScalaTest { def sources = Task.Sources(this.moduleDir / os.up / "src" / "test") diff --git a/examples/mmio-adder/build.mill b/examples/mmio-adder/build.mill index 01eb649..6b0b2e3 100644 --- a/examples/mmio-adder/build.mill +++ b/examples/mmio-adder/build.mill @@ -11,10 +11,10 @@ object `package` extends ScalaModule { def mvnDeps = Seq( mvn"org.chipsalliance::chisel:${chiselVersion}", - mvn"io.github.ucb-substrate::diplomacy:0.1.1", // x-release-please-version - mvn"io.github.ucb-substrate::rocketchip:0.1.1", // x-release-please-version - mvn"io.github.ucb-substrate::chippy:0.1.1", // x-release-please-version - mvn"io.github.ucb-substrate::testchipip:0.1.1" // x-release-please-version + mvn"io.github.ucb-substrate::diplomacy:0.1.1", + mvn"io.github.ucb-substrate::rocketchip:0.1.1", + mvn"io.github.ucb-substrate::chippy:0.1.1", + mvn"io.github.ucb-substrate::testchipip:0.1.1" ) def scalacPluginMvnDeps = Seq( mvn"org.chipsalliance:::chisel-plugin:${chiselVersion}", diff --git a/examples/rocket-config/build.mill b/examples/rocket-config/build.mill index b4f2139..8d08d22 100644 --- a/examples/rocket-config/build.mill +++ b/examples/rocket-config/build.mill @@ -11,13 +11,13 @@ object `package` extends ScalaModule { def mvnDeps = Seq( mvn"org.chipsalliance::chisel:${chiselVersion}", - mvn"io.github.ucb-substrate::cde:0.1.1", // x-release-please-version - mvn"io.github.ucb-substrate::diplomacy:0.1.1", // x-release-please-version - mvn"io.github.ucb-substrate::rocketchip:0.1.1", // x-release-please-version - mvn"io.github.ucb-substrate::rocketchip-blocks:0.1.1", // x-release-please-version - mvn"io.github.ucb-substrate::rocketchip-inclusive-cache:0.1.1", // x-release-please-version - mvn"io.github.ucb-substrate::testchipip:0.1.1", // x-release-please-version - mvn"io.github.ucb-substrate::chippy:0.1.1", // x-release-please-version + mvn"io.github.ucb-substrate::cde:0.1.1", + mvn"io.github.ucb-substrate::diplomacy:0.1.1", + mvn"io.github.ucb-substrate::rocketchip:0.1.1", + mvn"io.github.ucb-substrate::rocketchip-blocks:0.1.1", + mvn"io.github.ucb-substrate::rocketchip-inclusive-cache:0.1.1", + mvn"io.github.ucb-substrate::testchipip:0.1.1", + mvn"io.github.ucb-substrate::chippy:0.1.1", ) def scalacPluginMvnDeps = Seq( mvn"org.chipsalliance:::chisel-plugin:${chiselVersion}", @@ -26,7 +26,7 @@ object `package` extends ScalaModule { object test extends ScalaTests, TestModule.ScalaTest { def mvnDeps = Seq( mvn"org.scalatest::scalatest:3.2.19", - mvn"io.github.ucb-substrate::testchipip:0.1.1", // x-release-please-version + mvn"io.github.ucb-substrate::testchipip:0.1.1", ) } } diff --git a/examples/sky130-chip/build.mill b/examples/sky130-chip/build.mill index f8224bb..acb830e 100644 --- a/examples/sky130-chip/build.mill +++ b/examples/sky130-chip/build.mill @@ -11,11 +11,11 @@ object `package` extends build.`digital-chip`.ChipModule { def mvnDeps = Seq( mvn"org.chipsalliance::chisel:${chiselVersion}", - mvn"io.github.ucb-substrate::cde:0.1.1", // x-release-please-version - mvn"io.github.ucb-substrate::diplomacy:0.1.1", // x-release-please-version - mvn"io.github.ucb-substrate::rocketchip:0.1.1", // x-release-please-version - mvn"io.github.ucb-substrate::chippy:0.1.1", // x-release-please-version - mvn"io.github.ucb-substrate::constellation:0.1.1", // x-release-please-version + mvn"io.github.ucb-substrate::cde:0.1.1", + mvn"io.github.ucb-substrate::diplomacy:0.1.1", + mvn"io.github.ucb-substrate::rocketchip:0.1.1", + mvn"io.github.ucb-substrate::chippy:0.1.1", + mvn"io.github.ucb-substrate::constellation:0.1.1", ) def scalacPluginMvnDeps = Seq( mvn"org.chipsalliance:::chisel-plugin:${chiselVersion}", diff --git a/examples/sky130-chip/digital-chip/build.mill b/examples/sky130-chip/digital-chip/build.mill index 05304fa..79b4ad0 100644 --- a/examples/sky130-chip/digital-chip/build.mill +++ b/examples/sky130-chip/digital-chip/build.mill @@ -10,14 +10,14 @@ object `package` extends ChipModule { def mvnDeps = Seq( mvn"org.chipsalliance::chisel:${chiselVersion}", - mvn"io.github.ucb-substrate::cde:0.1.1", // x-release-please-version - mvn"io.github.ucb-substrate::diplomacy:0.1.1", // x-release-please-version - mvn"io.github.ucb-substrate::testchipip:0.1.1", // x-release-please-version - mvn"io.github.ucb-substrate::rocketchip-blocks:0.1.1", // x-release-please-version - mvn"io.github.ucb-substrate::rocketchip-inclusive-cache:0.1.1", // x-release-please-version - mvn"io.github.ucb-substrate::rocketchip:0.1.1", // x-release-please-version - mvn"io.github.ucb-substrate::chippy:0.1.1", // x-release-please-version - mvn"io.github.ucb-substrate::constellation:0.1.1", // x-release-please-version + mvn"io.github.ucb-substrate::cde:0.1.1", + mvn"io.github.ucb-substrate::diplomacy:0.1.1", + mvn"io.github.ucb-substrate::testchipip:0.1.1", + mvn"io.github.ucb-substrate::rocketchip-blocks:0.1.1", + mvn"io.github.ucb-substrate::rocketchip-inclusive-cache:0.1.1", + mvn"io.github.ucb-substrate::rocketchip:0.1.1", + mvn"io.github.ucb-substrate::chippy:0.1.1", + mvn"io.github.ucb-substrate::constellation:0.1.1", ) def scalacPluginMvnDeps = Seq( mvn"org.chipsalliance:::chisel-plugin:${chiselVersion}", @@ -26,16 +26,16 @@ object `package` extends ChipModule { object test extends ScalaTests, TestModule.ScalaTest { def mvnDeps = Seq( mvn"org.scalatest::scalatest:3.2.19", - mvn"io.github.ucb-substrate::testchipip:0.1.1", // x-release-please-version + mvn"io.github.ucb-substrate::testchipip:0.1.1", ) } object shuttle extends ChipModule { def mvnDeps = Seq( mvn"org.chipsalliance::chisel:${chiselVersion}", - mvn"io.github.ucb-substrate::cde:0.1.1", // x-release-please-version - mvn"io.github.ucb-substrate::diplomacy:0.1.1", // x-release-please-version - mvn"io.github.ucb-substrate::rocketchip:0.1.1", // x-release-please-version + mvn"io.github.ucb-substrate::cde:0.1.1", + mvn"io.github.ucb-substrate::diplomacy:0.1.1", + mvn"io.github.ucb-substrate::rocketchip:0.1.1", ) def scalacPluginMvnDeps = Seq( mvn"org.chipsalliance:::chisel-plugin:${chiselVersion}", @@ -49,9 +49,9 @@ object `package` extends ChipModule { def mvnDeps = Seq( mvn"org.chipsalliance::chisel:${chiselVersion}", - mvn"io.github.ucb-substrate::cde:0.1.1", // x-release-please-version - mvn"io.github.ucb-substrate::diplomacy:0.1.1", // x-release-please-version - mvn"io.github.ucb-substrate::rocketchip:0.1.1", // x-release-please-version + mvn"io.github.ucb-substrate::cde:0.1.1", + mvn"io.github.ucb-substrate::diplomacy:0.1.1", + mvn"io.github.ucb-substrate::rocketchip:0.1.1", ) def scalacPluginMvnDeps = Seq( mvn"org.chipsalliance:::chisel-plugin:${chiselVersion}", diff --git a/release-please-config.json b/release-please-config.json index 719d7ad..e70e3c9 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -4,13 +4,32 @@ "skip-changelog": true, "bump-minor-pre-major": true, "include-v-in-tag": true, - "include-component-in-tag": false, + "include-component-in-tag": true, + "separate-pull-requests": false, "packages": { - ".": { + "rocket-chip": { + "component": "rocketchip", "extra-files": [ - "README.md", - { "type": "generic", "path": "examples/**/*.mill", "glob": true } + "README.md" ] + }, + "rocket-chip-blocks": { + "component": "rocketchip-blocks" + }, + "rocket-chip-inclusive-cache": { + "component": "rocketchip-inclusive-cache" + }, + "testchipip": { + "component": "testchipip" + }, + "constellation": { + "component": "constellation" + }, + "chipyard": { + "component": "chipyard" + }, + "chippy": { + "component": "chippy" } } } diff --git a/version.txt b/version.txt deleted file mode 100644 index 17e51c3..0000000 --- a/version.txt +++ /dev/null @@ -1 +0,0 @@ -0.1.1