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
32 changes: 29 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ jobs:
run:
shell: bash
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
with:
go-version-file: go.mod
- name: Install mercurial and subversion (Linux)
Expand Down Expand Up @@ -54,12 +54,38 @@ jobs:
run: go mod tidy && git diff --exit-code go.mod go.sum
- name: Unit and end-to-end tests
run: go test -count=1 ./...
install-scripts:
name: Installers against a fake release (${{ matrix.os }})
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
timeout-minutes: 15
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
with:
go-version-file: go.mod
- name: install.sh and install.ps1
if: runner.os != 'Windows'
run: scripts/install-test.sh
- name: install.ps1
if: runner.os == 'Windows'
shell: pwsh
run: scripts/install-test.ps1
changelog-fragments:
name: Changelog fragments well-formed
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- run: python3 scripts/changelog.py check
changelog:
name: Changelog fragment
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
fetch-depth: 0
- name: Require a fragment for code changes
Expand Down
206 changes: 165 additions & 41 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,61 +1,185 @@
name: Release

# A maintainer tags vX.Y.Z on main (CONTRIBUTING.md § Releases). The tag publishes nothing
# until the repository gate is green on every platform the release is built for; then the
# archives are built, their checksum manifest signed with cosign (keyless, the identity being
# this workflow on the tag), and the GitHub release created with the CHANGELOG.md section
# of the version as its notes. The installers are then run against the published release.

on:
push:
tags: ["v*"]

permissions:
contents: write
contents: read

env:
SYSPI_REQUIRE_VCS: "1"

jobs:
build:
name: Build ${{ matrix.goos }}/${{ matrix.goarch }}
runs-on: ubuntu-latest
gate:
name: Go gate (${{ matrix.os }})
strategy:
fail-fast: false
matrix:
goos: [linux, darwin, windows]
goarch: [amd64, arm64]
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
timeout-minutes: 20
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
with:
go-version-file: go.mod
- name: Install mercurial and subversion (Linux)
if: runner.os == 'Linux'
run: sudo apt-get update -q && sudo apt-get install -y -q mercurial subversion
- name: Install mercurial and subversion (macOS)
if: runner.os == 'macOS'
run: brew install mercurial subversion
- name: Install mercurial and subversion (Windows)
if: runner.os == 'Windows'
shell: pwsh
run: |
choco install -y --no-progress hg sliksvn
Add-Content $env:GITHUB_PATH 'C:\Program Files\Mercurial'
Add-Content $env:GITHUB_PATH 'C:\Program Files\SlikSvn\bin'
- name: Versions
run: go version && git --version && hg --version | head -1 && svn --version --quiet
- name: Build
env:
GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goarch }}
CGO_ENABLED: "0"
run: |
ext=""
[ "$GOOS" = windows ] && ext=".exe"
name="syspi_${GITHUB_REF_NAME}_${GOOS}_${GOARCH}"
mkdir -p dist
go build -trimpath -ldflags "-s -w -X main.Version=${GITHUB_REF_NAME}" -o "dist/${name}${ext}" ./cmd/syspi
- uses: actions/upload-artifact@v4
with:
name: syspi-${{ matrix.goos }}-${{ matrix.goarch }}
path: dist/*
if-no-files-found: error
release:
name: Checksums and GitHub release
needs: build
run: go build ./...
- name: Vet
run: go vet ./...
- name: gofmt
run: test -z "$(gofmt -l .)" || { gofmt -l .; exit 1; }
- name: Tidy
run: go mod tidy && git diff --exit-code go.mod go.sum
- name: Unit and end-to-end tests
run: go test -count=1 ./...

publish:
name: Build, sign and publish
needs: gate
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: write # create the release and upload its assets
id-token: write # cosign keyless signing
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
with:
go-version-file: go.mod

- name: Check the tag names a version with a changelog section
env:
TAG: ${{ github.ref_name }}
run: |
[[ "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$ ]] || { echo "::error::tag $TAG is not vX.Y.Z"; exit 1; }
Comment thread
devin-ai-integration[bot] marked this conversation as resolved.
Resolved
python3 scripts/changelog.py release-notes "${TAG#v}" > "$RUNNER_TEMP/notes.md"
echo "notes for $TAG:"; cat "$RUNNER_TEMP/notes.md"

- name: Build the release archives
env:
TAG: ${{ github.ref_name }}
run: scripts/build-release-artifacts.sh "$TAG" dist

- name: Check the checksum manifest
run: |
cd dist
repeated=$(awk '{ print $2 }' SHA256SUMS.txt | sort | uniq -d)
[[ -z "$repeated" ]] || { echo "::error::SHA256SUMS.txt lists more than once: $repeated"; exit 1; }
[[ $(wc -l < SHA256SUMS.txt) -eq 6 ]] || { echo "::error::SHA256SUMS.txt lists $(wc -l < SHA256SUMS.txt) assets, not 6"; exit 1; }
sha256sum --check --strict SHA256SUMS.txt

- name: Install cosign
uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
with:
path: dist
merge-multiple: true
- name: Check the tag equals the version it carries
run: |
case "$GITHUB_REF_NAME" in
v[0-9]*.[0-9]*.[0-9]*) ;;
*) echo "tag $GITHUB_REF_NAME is not v<semver>"; exit 1 ;;
esac
- name: SHA-256 checksums
working-directory: dist
run: sha256sum syspi_* > SHA256SUMS && cat SHA256SUMS
- name: Create the release
cosign-release: v3.0.3

- name: Sign the checksum manifest with cosign keyless
run: |
cd dist
cosign sign-blob SHA256SUMS.txt \
--bundle SHA256SUMS.txt.bundle \
--use-signing-config=false \
--yes

- name: Verify the signature against the identity install.sh checks
env:
WORKFLOW_REF: ${{ github.workflow_ref }}
run: |
regexp=$(sed -n "s/^RELEASE_IDENTITY_REGEXP='\(.*\)'$/\1/p" install.sh)
[[ -n "$regexp" ]] || { echo "::error::RELEASE_IDENTITY_REGEXP not found in install.sh"; exit 1; }
cd dist
cosign verify-blob SHA256SUMS.txt \
--bundle SHA256SUMS.txt.bundle \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
--certificate-identity "https://github.com/${WORKFLOW_REF}"
cosign verify-blob SHA256SUMS.txt \
--bundle SHA256SUMS.txt.bundle \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
--certificate-identity-regexp "$regexp"

- name: Create the GitHub release
env:
GH_TOKEN: ${{ github.token }}
run: gh release create "$GITHUB_REF_NAME" dist/* --title "$GITHUB_REF_NAME" --generate-notes --verify-tag
TAG: ${{ github.ref_name }}
run: |
cat >> "$RUNNER_TEMP/notes.md" <<'NOTES'

---

Install with the scripts in the repository (`install.sh`, `install.ps1`; see the
README) or download an archive and check it against `SHA256SUMS.txt`, which is signed
keyless by this workflow (`SHA256SUMS.txt.bundle`):

```sh
cosign verify-blob SHA256SUMS.txt --bundle SHA256SUMS.txt.bundle \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
--certificate-identity https://github.com/Open-MBEE/SysPI/.github/workflows/release.yml@refs/tags/TAG
sha256sum --check --ignore-missing SHA256SUMS.txt
```
NOTES
sed -i "s|@refs/tags/TAG|@refs/tags/$TAG|" "$RUNNER_TEMP/notes.md"
gh release create "$TAG" \
--title "$TAG" \
--notes-file "$RUNNER_TEMP/notes.md" \
--verify-tag \
dist/syspi-* dist/SHA256SUMS.txt dist/SHA256SUMS.txt.bundle
gh release view "$TAG" --json url,assets --jq '.url, (.assets[].name)'

validate-install:
name: Install from the release (${{ matrix.os }})
needs: publish
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
timeout-minutes: 10
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Install cosign
if: runner.os != 'Windows'
uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
with:
cosign-release: v3.0.3
- name: install.sh
if: runner.os != 'Windows'
env:
TAG: ${{ github.ref_name }}
run: |
sh install.sh --version "$TAG" --prefix "$RUNNER_TEMP/syspi" --verify-signature
"$RUNNER_TEMP/syspi/bin/syspi" version | grep -Fx "syspi $TAG"
- name: install.ps1
if: runner.os == 'Windows'
shell: pwsh
env:
TAG: ${{ github.ref_name }}
run: |
.\install.ps1 -Version $env:TAG -InstallDir "$env:RUNNER_TEMP\syspi" -NoPath
$reported = & "$env:RUNNER_TEMP\syspi\syspi.exe" version
if ($reported -ne "syspi $env:TAG") { throw "reports '$reported'" }
18 changes: 14 additions & 4 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,20 @@ The repository follows git-flow:

## Releases

A maintainer tags `vX.Y.Z` on `main`; the release workflow builds Linux, macOS and Windows
binaries for amd64 and arm64 with a `SHA256SUMS` file and attaches them to the GitHub release.
The `.project.json` convention for packages applies to SysPI's own releases: the tag equals the
version.
1. On a `release/x.y.z` branch from `develop`, run `python3 scripts/changelog.py release x.y.z`:
it folds `changes/unreleased/` into a dated `## x.y.z` section of `CHANGELOG.md` and deletes
the fragments. Open the pull request against `develop` and merge it.
2. Merge `develop` into `main` and tag `vx.y.z` on `main`; the tag equals the version, as the
`.project.json` convention requires of the packages SysPI installs.
3. The release workflow (`.github/workflows/release.yml`) runs the gate on Linux, macOS and
Windows, builds `syspi-<os>-<arch>.tar.gz` (`.zip` on Windows) for amd64 and arm64 with
`scripts/build-release-artifacts.sh`, signs `SHA256SUMS.txt` with cosign (keyless; the
identity is the workflow on the tag), creates the GitHub release with the changelog section
as its notes, then installs it on all three systems with `install.sh` / `install.ps1`.
A tag without a `CHANGELOG.md` section publishes nothing.

`scripts/install-test.sh` runs the installers against a fake release served locally; CI runs it
on every pull request. To rehearse a release locally: `scripts/build-release-artifacts.sh v0.0.0-rc dist`.

## Licence

Expand Down
38 changes: 36 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,12 +77,46 @@ hg commit, so two builds of the same tree are byte-identical.

## Install

Linux and macOS:

```sh
curl -fsSL https://raw.githubusercontent.com/Open-MBEE/SysPI/main/install.sh | sh
```

Windows (PowerShell):

```powershell
irm https://raw.githubusercontent.com/Open-MBEE/SysPI/main/install.ps1 | iex
```

Both install the latest release's `syspi` for this machine (`/usr/local/bin` or `~/.local/bin`;
`%LOCALAPPDATA%\Programs\SysPI` on Windows) after checking the archive against the release's
`SHA256SUMS.txt`, and run it once to confirm it reports the release. `install.sh --help` lists
the options (`--version v0.1.0`, `--prefix`, `--bin-dir`, `--verify-signature` for the cosign
check of the manifest, `--dry-run`); `install.ps1` takes the same as `-Version`, `-InstallDir`,
`-VerifySignature`, `-DryRun`, and the environment variables `SYSPI_VERSION`, `SYSPI_PREFIX` /
`SYSPI_INSTALL_DIR` and `SYSPI_DOWNLOAD_BASE` configure a piped run. The checksum guards against a
corrupt or partial download; it comes from the same place as the archive, so against a replaced
release only the signature check helps — pass `--verify-signature` / `-VerifySignature` (needs
[cosign](https://docs.sigstore.dev/cosign/system_config/installation/)) or verify by hand as below.

Each release attaches `syspi-<os>-<arch>.tar.gz` (`.zip` on Windows) for Linux, macOS and
Windows on amd64 and arm64, `SHA256SUMS.txt`, and its keyless cosign signature
`SHA256SUMS.txt.bundle`, verifiable with

```sh
cosign verify-blob SHA256SUMS.txt --bundle SHA256SUMS.txt.bundle \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
--certificate-identity https://github.com/Open-MBEE/SysPI/.github/workflows/release.yml@refs/tags/v0.1.0
```

From source, with Go 1.25 or later:

```sh
go install github.com/Open-MBEE/SysPI/cmd/syspi@latest
```

Release binaries for Linux, macOS and Windows (amd64, arm64) with a `SHA256SUMS` file are
attached to each `v*` tag. Requires `git` (and `svn` / `hg` for packages hosted there) on `PATH`.
Requires `git` (and `svn` / `hg` for packages hosted there) on `PATH`.

## Interoperability

Expand Down
2 changes: 1 addition & 1 deletion changes/unreleased/ecma-427-identities.changed.md
Original file line number Diff line number Diff line change
@@ -1 +1 @@
- Package identities are ECMA-427 Package-URLs: a repository package is `pkg:generic/<name>?vcs_url=<vcs>+<transport>://<host>/<path>[@<version>][#<subpath>]` with the Annex B.3.5 `vcs_url` qualifier percent-encoded (`<name>` is the repository's last path segment, `<version>` a tag naming a semantic version). `pkg:git/<host>/<path>[@<version>][#<subpath>]` and the bare `<vcs>+<transport>://…` notation are accepted as input and normalised to it; `syspi add` writes the canonical form into `.project.json`. `<vcs>+file:///path` addresses a repository on this machine. A plain `http(s)` IRI is only dereferenced as a project interchange file (KerML 1.1 §10.3 Table 12).
- Package identities are ECMA-427 Package-URLs: a repository package is `pkg:generic/<name>?vcs_url=<vcs>+<transport>://<host>/<path>[@<version>][#<subpath>]` with the Annex B.3.5 `vcs_url` qualifier percent-encoded (`<name>` is the repository's last path segment, `<version>` a tag naming a semantic version). `pkg:git/<host>/<path>[@<version>][#<subpath>]` and the bare `<vcs>+<transport>://…` notation are accepted as input and normalised to it; `syspi add` writes the canonical form into `.project.json`. `<vcs>+file:///path` addresses a repository on this machine. A plain `http(s)` IRI is only dereferenced as a project interchange file (KerML 1.1 §10.3 Table 12). Other spellings (`pkg:svn/…`, `pkg:hg/…`, clone URLs such as `https://host/path.git@v`, `git@host:path`, `file:///path@v`) are refused with a message naming the accepted ones.
2 changes: 0 additions & 2 deletions changes/unreleased/ecma-427-identities.removed.md

This file was deleted.

1 change: 1 addition & 0 deletions changes/unreleased/release-line.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Releases publish `syspi-<os>-<arch>.tar.gz` (`.zip` on Windows) for Linux, macOS and Windows on amd64 and arm64, each holding `syspi` and `LICENSE`, with `SHA256SUMS.txt` signed keyless by the release workflow (`SHA256SUMS.txt.bundle`); the tag publishes nothing until the repository gate is green on all three systems. `install.sh` (Linux, macOS) and `install.ps1` (Windows) install a release after checking the download against the manifest and the installed binary against the tag.
2 changes: 1 addition & 1 deletion changes/unreleased/syspi-phase-1.added.md
Original file line number Diff line number Diff line change
@@ -1 +1 @@
- The `syspi` command: `init`, `add`, `install`, `sync`, `lock`, `bundle` and `index emit` for KerML / SysML v2 project-interchange packages identified as `pkg:<vcs>/<host>/<path>[@<version>][#<subpath>]` (git, svn, hg), local directories, local and remote `.kpar` files, OMG standard-library IRIs and `pkg:sysand/<publisher>/<name>` through a static index. Packages are verified before use, installed under `lib/<publisher>-<name>_<version>` beside `.project.json` and recorded in a deterministic `.project.lock.json` with a VCS-neutral tree hash.
- The `syspi` command: `init`, `add`, `install`, `sync`, `lock`, `bundle` and `version` for KerML / SysML v2 project-interchange packages — any git, svn or hg repository with `.project.json` at a tag, a local directory, a local or remote `.kpar` file, or an OMG standard-library IRI. Packages are verified before use, installed under `lib/<publisher>-<name>_<version>` beside `.project.json` and recorded in a deterministic `.project.lock.json` with a VCS-neutral tree hash.
Loading
Loading