ci: align the pipelines with the hand-written templates
Assisted-by: GLM 5.3 Flash
This commit is contained in:
@@ -0,0 +1,36 @@
|
|||||||
|
# Race, Go. Dispatched by hand, and run as part of the release gates.
|
||||||
|
#
|
||||||
|
# The race detector roughly doubles both time and memory, which the shared runner box
|
||||||
|
# cannot afford on every push. Locally it belongs to `just gates`, which runs it once per
|
||||||
|
# task; here it is an explicit decision rather than a routine.
|
||||||
|
#
|
||||||
|
# Every step is one command, so the step that fails is the gate that failed.
|
||||||
|
name: Race
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
env:
|
||||||
|
GOAMD64: v3
|
||||||
|
# One core: parallelism buys no speed here and costs memory the box does not have.
|
||||||
|
GOFLAGS: -p=1
|
||||||
|
GOMAXPROCS: "2"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
race:
|
||||||
|
runs-on: fedora
|
||||||
|
timeout-minutes: 45
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v7
|
||||||
|
|
||||||
|
- uses: actions/setup-go@v6
|
||||||
|
with:
|
||||||
|
go-version-file: go.mod
|
||||||
|
cache: true
|
||||||
|
|
||||||
|
- name: Install gcc
|
||||||
|
# The race detector needs cgo and the runner image carries no C compiler.
|
||||||
|
run: dnf install -y gcc
|
||||||
|
|
||||||
|
- name: Race
|
||||||
|
run: go test -race -count=1 -timeout 30m ./...
|
||||||
+244
-86
@@ -1,71 +1,185 @@
|
|||||||
# Release — gasm binaries. Runs on version tags (v0.28.0) pushed to main.
|
# Release, Go binaries. Runs on version tags (v1.2.3) pushed to main.
|
||||||
|
#
|
||||||
|
# The version contract these steps implement is in the `release` skill, and its point is
|
||||||
|
# that nothing is injected: the toolchain records the tag into the binary's build
|
||||||
|
# information, so the build simply has to happen at the tag, which the trigger guarantees.
|
||||||
|
#
|
||||||
|
# The gates run in their own job, once, before the matrix. Putting them inside the matrix
|
||||||
|
# would run the whole suite and the race detector once per target on the box that also hosts
|
||||||
|
# the forge. Each job validates the tag for itself rather than passing a value between jobs,
|
||||||
|
# so no workflow feature has to be trusted for the version to reach the file name.
|
||||||
name: Release
|
name: Release
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
tags: ["v*"]
|
tags: ["v*"]
|
||||||
|
|
||||||
|
env:
|
||||||
|
GOAMD64: v3
|
||||||
|
# The box is shared with the forge, so parallelism is bounded on purpose. The gates job
|
||||||
|
# needs it most; the build jobs inherit it for their parallel compilation.
|
||||||
|
GOFLAGS: -p=1
|
||||||
|
GOMAXPROCS: "2"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
gates:
|
||||||
|
runs-on: fedora
|
||||||
|
timeout-minutes: 25
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v7
|
||||||
|
|
||||||
|
- uses: actions/setup-go@v6
|
||||||
|
with:
|
||||||
|
go-version-file: go.mod
|
||||||
|
cache: true
|
||||||
|
|
||||||
|
- name: Install Perl and gcc
|
||||||
|
# Perl for the steps below, gcc for the race detector. Both are no-ops where the
|
||||||
|
# package is already present.
|
||||||
|
run: dnf install -y perl gcc
|
||||||
|
|
||||||
|
- name: Validate the tag
|
||||||
|
env:
|
||||||
|
VERSION: ${{ gitea.ref_name }}
|
||||||
|
run: |
|
||||||
|
perl -e '
|
||||||
|
my $v = $ENV{VERSION} // q{};
|
||||||
|
$v =~ m{^v[0-9]+(\.[0-9]+){0,2}([-+].*)?$}
|
||||||
|
or die qq{ERROR: expected a semver tag like v1.2.3, got: $v\n};
|
||||||
|
print qq{tag $v\n};
|
||||||
|
'
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: go build ./...
|
||||||
|
|
||||||
|
- name: Format
|
||||||
|
run: |
|
||||||
|
perl -e '
|
||||||
|
open(my $g, q{-|}, q{gofmt}, q{-l}, q{.}) or die qq{gofmt: $!};
|
||||||
|
my @bad = <$g>;
|
||||||
|
close($g);
|
||||||
|
print @bad;
|
||||||
|
exit(@bad ? 1 : 0);
|
||||||
|
'
|
||||||
|
|
||||||
|
- name: Vet
|
||||||
|
run: go vet ./...
|
||||||
|
|
||||||
|
- name: Modernise
|
||||||
|
run: go fix -diff ./...
|
||||||
|
|
||||||
|
- name: Tests
|
||||||
|
# The same command as in test.yml, so the floor is the same number everywhere.
|
||||||
|
run: go test -count=1 -timeout 30m -coverprofile=coverage.out -coverpkg=./arch/...,./asm/...,./ast/...,./disasm/...,./format/...,./lexer/...,./lint/...,./lsp/...,./parser/...,./token/...,./verify/... ./...
|
||||||
|
|
||||||
|
- name: Coverage floor
|
||||||
|
run: |
|
||||||
|
perl -e '
|
||||||
|
open(my $c, q{-|}, q{go}, q{tool}, q{cover}, q{-func=coverage.out}) or die qq{cover: $!};
|
||||||
|
my $total;
|
||||||
|
while (my $l = <$c>) { $total = $1 if $l =~ m{^total:\s+\S+\s+([0-9.]+)%} }
|
||||||
|
close($c);
|
||||||
|
die qq{no total line in coverage.out\n} unless defined $total;
|
||||||
|
printf qq{Total coverage: %s%%\n}, $total;
|
||||||
|
exit($total < 80 ? 1 : 0);
|
||||||
|
'
|
||||||
|
|
||||||
|
- name: Race
|
||||||
|
run: go test -race -count=1 -timeout 30m ./...
|
||||||
|
|
||||||
build:
|
build:
|
||||||
runs-on: fedora
|
runs-on: fedora
|
||||||
|
timeout-minutes: 25
|
||||||
|
needs: gates
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
|
# Portable targets: amd64 (v3 baseline), arm64, loong64 and riscv64 on Linux.
|
||||||
|
# No 32-bit, no wasm, no macOS, no Windows. FreeBSD waits until verify/jit.go
|
||||||
|
# ports off syscall.Mprotect, which the freebsd build does not carry.
|
||||||
include:
|
include:
|
||||||
- goos: linux
|
- goos: linux
|
||||||
goarch: amd64
|
goarch: amd64
|
||||||
- goos: linux
|
- goos: linux
|
||||||
goarch: arm64
|
goarch: arm64
|
||||||
- goos: linux
|
|
||||||
goarch: riscv64
|
|
||||||
- goos: linux
|
- goos: linux
|
||||||
goarch: loong64
|
goarch: loong64
|
||||||
|
- goos: linux
|
||||||
|
goarch: riscv64
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v7
|
- uses: actions/checkout@v7
|
||||||
|
|
||||||
- uses: actions/setup-go@v6
|
- uses: actions/setup-go@v6
|
||||||
with:
|
with:
|
||||||
go-version: "1.27"
|
go-version-file: go.mod
|
||||||
|
cache: true
|
||||||
|
|
||||||
- name: Download dependencies
|
- name: Install Perl
|
||||||
run: go mod download
|
run: dnf install -y perl
|
||||||
|
|
||||||
- name: Validate tag and build
|
- name: Validate the tag
|
||||||
id: build
|
id: version
|
||||||
env:
|
env:
|
||||||
VERSION: ${{ gitea.ref_name }}
|
VERSION: ${{ gitea.ref_name }}
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
perl -e '
|
||||||
|
my $v = $ENV{VERSION} // q{};
|
||||||
|
$v =~ m{^v[0-9]+(\.[0-9]+){0,2}([-+].*)?$}
|
||||||
|
or die qq{ERROR: expected a semver tag like v1.2.3, got: $v\n};
|
||||||
|
(my $nv = $v) =~ s{^v}{};
|
||||||
|
open(my $o, q{>>}, $ENV{GITEA_OUTPUT}) or die qq{GITEA_OUTPUT: $!};
|
||||||
|
print $o qq{version_no_v=$nv\n};
|
||||||
|
close($o);
|
||||||
|
print qq{version $nv\n};
|
||||||
|
'
|
||||||
|
|
||||||
if ! echo "$VERSION" | grep -qE '^v[0-9]+(\.[0-9]+){0,2}([-+].*)?$'; then
|
- name: Build
|
||||||
echo "ERROR: expected a semver tag like v1.2.3, got: '$VERSION'"
|
env:
|
||||||
exit 1
|
VERSION_NO_V: ${{ steps.version.outputs.version_no_v }}
|
||||||
fi
|
GOOS: ${{ matrix.goos }}
|
||||||
|
GOARCH: ${{ matrix.goarch }}
|
||||||
VERSION_NO_V="${VERSION#v}"
|
CGO_ENABLED: "0"
|
||||||
echo "version_no_v=${VERSION_NO_V}" >> "$GITEA_OUTPUT"
|
run: |
|
||||||
|
# Nothing is injected. The toolchain records the tag into the binary's build
|
||||||
mkdir -p bin
|
# information, so the version is right because this build happens at the tag, and
|
||||||
GOOS=${{ matrix.goos }} GOARCH=${{ matrix.goarch }} CGO_ENABLED=0 \
|
# there is no path for anyone to get wrong. -s -w only strips symbols.
|
||||||
go build -ldflags "-s -w -X main.version=${VERSION_NO_V}" \
|
go build -ldflags "-s -w" -o "bin/gasm-${VERSION_NO_V}-${GOOS}-${GOARCH}" ./cmd/gasm
|
||||||
-o "bin/gasm-${VERSION_NO_V}-${{ matrix.goos }}-${{ matrix.goarch }}" \
|
|
||||||
./cmd/gasm
|
|
||||||
|
|
||||||
|
# Artifacts stay on v3: v4 and later detect Gitea as GHES and abort.
|
||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: gasm-${{ matrix.goos }}-${{ matrix.goarch }}
|
name: gasm-${{ matrix.goos }}-${{ matrix.goarch }}
|
||||||
path: bin/gasm-${{ steps.build.outputs.version_no_v }}-${{ matrix.goos }}-${{ matrix.goarch }}
|
path: bin/gasm-${{ steps.version.outputs.version_no_v }}-${{ matrix.goos }}-${{ matrix.goarch }}
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
|
||||||
- name: Smoke test
|
- name: Smoke test
|
||||||
|
# Only a binary matching the runner can be run here. The check is not that --version
|
||||||
|
# exits cleanly but that it reports the tag and nothing more: a build outside version
|
||||||
|
# control reports (devel), and a build whose tree was dirty reports +dirty, and both
|
||||||
|
# would otherwise be published.
|
||||||
if: matrix.goos == 'linux' && matrix.goarch == 'amd64'
|
if: matrix.goos == 'linux' && matrix.goarch == 'amd64'
|
||||||
|
env:
|
||||||
|
TAG: ${{ gitea.ref_name }}
|
||||||
|
BIN: bin/gasm-${{ steps.version.outputs.version_no_v }}-${{ matrix.goos }}-${{ matrix.goarch }}
|
||||||
run: |
|
run: |
|
||||||
chmod +x bin/gasm-${{ steps.build.outputs.version_no_v }}-${{ matrix.goos }}-${{ matrix.goarch }}
|
perl -e '
|
||||||
./bin/gasm-${{ steps.build.outputs.version_no_v }}-${{ matrix.goos }}-${{ matrix.goarch }} --version
|
my $want = $ENV{TAG} // die qq{ERROR: no tag\n};
|
||||||
|
open(my $bin, q{-|}, $ENV{BIN}, q{--version}) or die qq{$ENV{BIN}: $!};
|
||||||
|
my $got = <$bin>;
|
||||||
|
close($bin);
|
||||||
|
$got = defined $got ? $got : q{};
|
||||||
|
chomp $got;
|
||||||
|
index($got, $want) >= 0
|
||||||
|
or die qq{ERROR: the binary printed "$got", which does not contain $want. Version control was disabled, so there is no recorded version.\n};
|
||||||
|
index($got, q{+dirty}) < 0
|
||||||
|
or die qq{ERROR: the binary printed "$got". The tree was dirty at build time, which means the checkout was not the tag, or the build artefacts are not ignored.\n};
|
||||||
|
print qq{$ENV{BIN} reports $got\n};
|
||||||
|
'
|
||||||
|
|
||||||
release:
|
release:
|
||||||
runs-on: fedora
|
runs-on: fedora
|
||||||
|
timeout-minutes: 15
|
||||||
needs: build
|
needs: build
|
||||||
permissions:
|
permissions:
|
||||||
releases: write
|
releases: write
|
||||||
@@ -77,81 +191,125 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
path: dist
|
path: dist
|
||||||
|
|
||||||
- name: Extract CHANGELOG section
|
- name: Install Perl
|
||||||
|
run: dnf install -y perl
|
||||||
|
|
||||||
|
- name: Extract the CHANGELOG section
|
||||||
env:
|
env:
|
||||||
VERSION: ${{ gitea.ref_name }}
|
VERSION: ${{ gitea.ref_name }}
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
# Each step derives what it needs from the tag, so no value has to travel between
|
||||||
VERSION_NO_V="${VERSION#v}"
|
# jobs.
|
||||||
|
perl -e '
|
||||||
|
my $v = $ENV{VERSION} // q{};
|
||||||
|
$v =~ s{^v}{};
|
||||||
|
open(my $vout, q{>}, q{version-no-v.txt}) or die qq{version-no-v.txt: $!};
|
||||||
|
print $vout $v;
|
||||||
|
close($vout);
|
||||||
|
open(my $in, q{<}, q{CHANGELOG.md}) or die qq{CHANGELOG.md: $!};
|
||||||
|
my @lines = <$in>;
|
||||||
|
close($in);
|
||||||
|
my ($start, $end) = (-1, scalar @lines);
|
||||||
|
for my $i (0 .. $#lines) {
|
||||||
|
if ($start < 0) { $start = $i if $lines[$i] =~ m{^##\s+\[\Q$v\E\]} }
|
||||||
|
elsif ($lines[$i] =~ m{^##\s+\[}) { $end = $i; last }
|
||||||
|
}
|
||||||
|
$start >= 0 or die qq{ERROR: no CHANGELOG section for $v, expected a heading like: ## [$v] - YYYY-MM-DD\n};
|
||||||
|
my @body = grep { m{\S} } @lines[$start + 1 .. $end - 1];
|
||||||
|
@body or die qq{ERROR: the CHANGELOG section for $v is empty\n};
|
||||||
|
open(my $out, q{>}, q{release-body.md}) or die qq{release-body.md: $!};
|
||||||
|
print $out @body;
|
||||||
|
close($out);
|
||||||
|
printf qq{notes for %s: %d lines\n}, $v, scalar @body;
|
||||||
|
'
|
||||||
|
|
||||||
sed -n "/^## \[${VERSION_NO_V}\] /,/^## \[/p" CHANGELOG.md \
|
- name: Build the release request
|
||||||
| sed '$d' \
|
run: |
|
||||||
| tail -n +2 \
|
perl -e '
|
||||||
> release-body.md
|
open(my $vin, q{<}, q{version-no-v.txt}) or die qq{version-no-v.txt: $!};
|
||||||
|
my $v = <$vin>;
|
||||||
|
close($vin);
|
||||||
|
chomp $v;
|
||||||
|
open(my $in, q{<:raw}, q{release-body.md}) or die qq{release-body.md: $!};
|
||||||
|
my $body = do { local $/; <$in> };
|
||||||
|
close($in);
|
||||||
|
# Byte-oriented escaping: JSON is UTF-8, so non-ASCII passes through and only the
|
||||||
|
# characters JSON forbids are rewritten.
|
||||||
|
$body =~ s/([\\"])/\\$1/g;
|
||||||
|
$body =~ s/\t/\\t/g;
|
||||||
|
$body =~ s/\r//g;
|
||||||
|
$body =~ s/\n/\\n/g;
|
||||||
|
$body =~ s/([\x00-\x08\x0b\x0c\x0e-\x1f])/sprintf(q{\u%04x}, ord($1))/ge;
|
||||||
|
my $json = sprintf(qq{{"tag_name":"v%s","name":"v%s","body":"%s","draft":false,"prerelease":false}}, $v, $v, $body);
|
||||||
|
open(my $out, q{>}, q{release.json}) or die qq{release.json: $!};
|
||||||
|
print $out $json;
|
||||||
|
close($out);
|
||||||
|
print qq{release.json written for v$v\n};
|
||||||
|
'
|
||||||
|
|
||||||
if [ ! -s release-body.md ]; then
|
- name: Create the release
|
||||||
echo "ERROR: no CHANGELOG section found for ${VERSION_NO_V}"
|
|
||||||
echo "Expected a heading like: ## [${VERSION_NO_V}] — YYYY-MM-DD"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Create release
|
|
||||||
env:
|
env:
|
||||||
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
||||||
GITEA_SERVER_URL: ${{ gitea.server_url }}
|
GITEA_SERVER_URL: ${{ gitea.server_url }}
|
||||||
GITEA_REPOSITORY: ${{ gitea.repository }}
|
GITEA_REPOSITORY: ${{ gitea.repository }}
|
||||||
GITEA_REF_NAME: ${{ gitea.ref_name }}
|
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
perl -e '
|
||||||
|
my @cmd = (q{curl}, q{-sS}, q{-o}, q{response.json}, q{-w}, q{%{http_code}},
|
||||||
BODY=$(sed -e 's/\\/\\\\/g' -e 's/"/\\"/g' -e 's/\t/\\t/g' -e 's/\r//g' release-body.md | sed ':a;N;$!ba;s/\n/\\n/g')
|
q{-H}, qq{Authorization: token $ENV{GITEA_TOKEN}},
|
||||||
BODY="\"${BODY}\""
|
q{-H}, q{Content-Type: application/json},
|
||||||
|
q{-X}, q{POST},
|
||||||
response=$(curl -sS -w '\n%{http_code}' \
|
qq{$ENV{GITEA_SERVER_URL}/api/v1/repos/$ENV{GITEA_REPOSITORY}/releases},
|
||||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
q{--data-binary}, q{@release.json});
|
||||||
-H "Content-Type: application/json" \
|
open(my $curl, q{-|}, @cmd) or die qq{curl: $!};
|
||||||
-X POST \
|
my $code = <$curl>;
|
||||||
"${GITEA_SERVER_URL}/api/v1/repos/${GITEA_REPOSITORY}/releases" \
|
my $ok = close($curl);
|
||||||
-d "{\"tag_name\":\"${GITEA_REF_NAME}\",\"name\":\"${GITEA_REF_NAME}\",\"body\":${BODY},\"draft\":false,\"prerelease\":false}")
|
my $exit = $? >> 8;
|
||||||
|
$code = defined $code ? $code : q{};
|
||||||
http_code=$(echo "$response" | tail -1)
|
$ok or die qq{ERROR: curl failed (exit $exit) calling $ENV{GITEA_SERVER_URL}\n};
|
||||||
payload=$(echo "$response" | sed '$d')
|
open(my $r, q{<:raw}, q{response.json}) or die qq{response.json: $!};
|
||||||
|
my $body = do { local $/; <$r> };
|
||||||
echo "HTTP ${http_code}"
|
close($r);
|
||||||
if [ "$http_code" != "201" ]; then
|
$code eq q{201} or die qq{ERROR: the release was not created, HTTP $code: $body\n};
|
||||||
echo "Failed to create release: ${payload}"
|
$body =~ m{"id"\s*:\s*([0-9]+)} or die qq{ERROR: no release id in the response: $body\n};
|
||||||
exit 1
|
open(my $o, q{>}, q{release-id.txt}) or die qq{release-id.txt: $!};
|
||||||
fi
|
print $o $1;
|
||||||
|
close($o);
|
||||||
RELEASE_ID=$(echo "$payload" | grep -oE '"id"[[:space:]]*:[[:space:]]*[0-9]+' | head -1 | grep -oE '[0-9]+')
|
print qq{release id $1\n};
|
||||||
echo "Created release ID=${RELEASE_ID}"
|
'
|
||||||
printf '%s' "${RELEASE_ID}" > release-id.txt
|
|
||||||
|
|
||||||
- name: Upload assets
|
- name: Upload assets
|
||||||
env:
|
env:
|
||||||
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
||||||
GITEA_SERVER_URL: ${{ gitea.server_url }}
|
GITEA_SERVER_URL: ${{ gitea.server_url }}
|
||||||
GITEA_REPOSITORY: ${{ gitea.repository }}
|
GITEA_REPOSITORY: ${{ gitea.repository }}
|
||||||
GITEA_REF_NAME: ${{ gitea.ref_name }}
|
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
perl -e '
|
||||||
RELEASE_ID=$(cat release-id.txt)
|
open(my $f, q{<}, q{release-id.txt}) or die qq{release-id.txt: $!};
|
||||||
|
my $id = <$f>;
|
||||||
for binary in dist/gasm-*/gasm-*; do
|
close($f);
|
||||||
[ -f "$binary" ] || continue
|
chomp $id;
|
||||||
fname=$(basename "$binary")
|
my @files = grep { -f $_ } glob(q{dist/*/*});
|
||||||
echo "Uploading ${fname}..."
|
@files or die qq{ERROR: no assets under dist/\n};
|
||||||
http_code=$(curl -sS -o /dev/null -w '%{http_code}' \
|
my $bad = 0;
|
||||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
for my $path (@files) {
|
||||||
-H "Content-Type: application/octet-stream" \
|
(my $name = $path) =~ s{.*/}{};
|
||||||
-X POST \
|
my @cmd = (q{curl}, q{-sS}, q{-o}, q{/dev/null}, q{-w}, q{%{http_code}},
|
||||||
--data-binary "@${binary}" \
|
q{-H}, qq{Authorization: token $ENV{GITEA_TOKEN}},
|
||||||
"${GITEA_SERVER_URL}/api/v1/repos/${GITEA_REPOSITORY}/releases/${RELEASE_ID}/assets?name=${fname}")
|
q{-H}, q{Content-Type: application/octet-stream},
|
||||||
echo " HTTP ${http_code}"
|
q{-X}, q{POST}, q{--data-binary}, qq{@$path},
|
||||||
if [ "$http_code" != "201" ]; then
|
qq{$ENV{GITEA_SERVER_URL}/api/v1/repos/$ENV{GITEA_REPOSITORY}/releases/$id/assets?name=$name});
|
||||||
echo "Failed to upload ${fname}"
|
open(my $curl, q{-|}, @cmd) or die qq{curl: $!};
|
||||||
exit 1
|
my $code = <$curl>;
|
||||||
fi
|
my $ok = close($curl);
|
||||||
done
|
my $exit = $? >> 8;
|
||||||
|
$code = defined $code ? $code : q{};
|
||||||
echo "Release ${GITEA_REF_NAME} is live."
|
unless ($ok) {
|
||||||
|
printf qq{%s: curl failed (exit %d)\n}, $name, $exit;
|
||||||
|
$bad = 1;
|
||||||
|
next;
|
||||||
|
}
|
||||||
|
printf qq{%s: HTTP %s\n}, $name, $code;
|
||||||
|
$bad = 1 if $code ne q{201};
|
||||||
|
}
|
||||||
|
exit($bad ? 1 : 0);
|
||||||
|
'
|
||||||
|
|||||||
+60
-72
@@ -1,4 +1,17 @@
|
|||||||
# Test — gasm-devkit. Runs on push and pull request to development.
|
# Test, Go. Push and pull request to development. Never on main.
|
||||||
|
#
|
||||||
|
# The gates are the ones the justfile's `gates` recipe runs, minus race: the shared
|
||||||
|
# runner box cannot afford the race detector on every push, so it lives in race.yml.
|
||||||
|
# The box is one core and 2 GB beside Gitea, so parallelism is bounded on purpose and
|
||||||
|
# everything runs in one job. Extra jobs would duplicate the checkout, the Go setup and
|
||||||
|
# the dependency download three times without buying any parallelism.
|
||||||
|
#
|
||||||
|
# Every step is one command, so the step that fails is the gate that failed, and no shell
|
||||||
|
# option has to be trusted for the run to stop. The scripted steps are Perl, not shell and
|
||||||
|
# not Python: Perl behaves the same on both runner images, there is no bashism to trip over
|
||||||
|
# on ash, and it is one language instead of two. The Perl uses builtins only, because
|
||||||
|
# Fedora packages the Perl modules separately and nothing beyond `perl` itself may be
|
||||||
|
# assumed present.
|
||||||
name: Test
|
name: Test
|
||||||
|
|
||||||
on:
|
on:
|
||||||
@@ -7,90 +20,65 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
branches: [development]
|
branches: [development]
|
||||||
|
|
||||||
|
env:
|
||||||
|
# Portable baseline, x86-64-v3 minimum. Other GOARCH values ignore it.
|
||||||
|
GOAMD64: v3
|
||||||
|
# One core: parallelism buys no speed here and costs memory the box does not have.
|
||||||
|
GOFLAGS: -p=1
|
||||||
|
GOMAXPROCS: "2"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
vet:
|
|
||||||
runs-on: fedora
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v7
|
|
||||||
|
|
||||||
- uses: actions/setup-go@v6
|
|
||||||
with:
|
|
||||||
go-version: "1.27"
|
|
||||||
|
|
||||||
- name: Download dependencies
|
|
||||||
run: go mod download
|
|
||||||
|
|
||||||
- name: gofmt
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
unformatted=$(gofmt -l .)
|
|
||||||
if [ -n "$unformatted" ]; then
|
|
||||||
echo "These files need gofmt:"
|
|
||||||
echo "$unformatted"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: go vet
|
|
||||||
run: go vet ./...
|
|
||||||
|
|
||||||
test:
|
test:
|
||||||
runs-on: fedora
|
runs-on: fedora
|
||||||
needs: vet
|
timeout-minutes: 20
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v7
|
- uses: actions/checkout@v7
|
||||||
|
|
||||||
- uses: actions/setup-go@v6
|
- uses: actions/setup-go@v6
|
||||||
with:
|
with:
|
||||||
go-version: "1.27"
|
# The module is the source of truth for the version, so it cannot drift.
|
||||||
|
go-version-file: go.mod
|
||||||
|
cache: true
|
||||||
|
|
||||||
- name: Download dependencies
|
- name: Install Perl
|
||||||
run: go mod download
|
# The runner images are minimal and Perl is not guaranteed. The install is a
|
||||||
|
# no-op where it is already present; drop this step once verified on the box.
|
||||||
|
run: dnf install -y perl
|
||||||
|
|
||||||
- name: Install gcc
|
- name: Format
|
||||||
run: dnf install -y gcc
|
|
||||||
|
|
||||||
- name: go test -race
|
|
||||||
run: go test -race -count=1 ./...
|
|
||||||
|
|
||||||
- name: Coverage gate — 80 % minimum
|
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
perl -e '
|
||||||
# Exclude packages inherently untestable without hardware:
|
open(my $g, q{-|}, q{gofmt}, q{-l}, q{.}) or die qq{gofmt: $!};
|
||||||
# debug — interactive ptrace, requires a live process
|
my @bad = <$g>;
|
||||||
# cmd/gasm — CLI glue, covered by integration tests
|
close($g);
|
||||||
go test -coverprofile=coverage.out \
|
print @bad;
|
||||||
sourcedock.dev/petrbalvin/gasm-devkit/arch \
|
exit(@bad ? 1 : 0);
|
||||||
sourcedock.dev/petrbalvin/gasm-devkit/asm \
|
'
|
||||||
sourcedock.dev/petrbalvin/gasm-devkit/ast \
|
|
||||||
sourcedock.dev/petrbalvin/gasm-devkit/format \
|
|
||||||
sourcedock.dev/petrbalvin/gasm-devkit/lexer \
|
|
||||||
sourcedock.dev/petrbalvin/gasm-devkit/lint \
|
|
||||||
sourcedock.dev/petrbalvin/gasm-devkit/lsp \
|
|
||||||
sourcedock.dev/petrbalvin/gasm-devkit/parser \
|
|
||||||
sourcedock.dev/petrbalvin/gasm-devkit/token \
|
|
||||||
sourcedock.dev/petrbalvin/gasm-devkit/verify
|
|
||||||
coverage=$(go tool cover -func=coverage.out | awk '/^total:/ { gsub("%", "", $3); print $3 }')
|
|
||||||
echo "Total coverage: ${coverage}%"
|
|
||||||
if awk -v c="$coverage" 'BEGIN { exit !(c+0 < 80) }'; then
|
|
||||||
echo "ERROR: coverage ${coverage}% is below the 80% threshold"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
build:
|
- name: Vet
|
||||||
runs-on: fedora
|
run: go vet ./...
|
||||||
needs: test
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v7
|
|
||||||
|
|
||||||
- uses: actions/setup-go@v6
|
- name: Modernise
|
||||||
with:
|
# Exits non-zero when it has something to rewrite, so it needs no output capture.
|
||||||
go-version: "1.27"
|
run: go fix -diff ./...
|
||||||
|
|
||||||
- name: Download dependencies
|
|
||||||
run: go mod download
|
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: go build -ldflags="-s -w" -o bin/gasm ./cmd/gasm
|
run: go build ./...
|
||||||
|
|
||||||
- name: Smoke test
|
- name: Tests
|
||||||
run: ./bin/gasm --version
|
# The sweep is `packages` in the project's justfile and the floor is computed over
|
||||||
|
# the same product packages as the justfile's `coverpkg`, so the number is the one
|
||||||
|
# `just test` reports locally.
|
||||||
|
run: go test -count=1 -timeout 30m -coverprofile=coverage.out -coverpkg=./arch/...,./asm/...,./ast/...,./disasm/...,./format/...,./lexer/...,./lint/...,./lsp/...,./parser/...,./token/...,./verify/... ./...
|
||||||
|
|
||||||
|
- name: Coverage floor
|
||||||
|
run: |
|
||||||
|
perl -e '
|
||||||
|
open(my $c, q{-|}, q{go}, q{tool}, q{cover}, q{-func=coverage.out}) or die qq{cover: $!};
|
||||||
|
my $total;
|
||||||
|
while (my $l = <$c>) { $total = $1 if $l =~ m{^total:\s+\S+\s+([0-9.]+)%} }
|
||||||
|
close($c);
|
||||||
|
die qq{no total line in coverage.out\n} unless defined $total;
|
||||||
|
printf qq{Total coverage: %s%%\n}, $total;
|
||||||
|
exit($total < 80 ? 1 : 0);
|
||||||
|
'
|
||||||
|
|||||||
Reference in New Issue
Block a user