diff options
| author | Ophestra <cat@gensokyo.uk> | 2026-09-28 17:37:30 +0900 |
|---|---|---|
| committer | Ophestra <cat@gensokyo.uk> | 2026-09-28 20:30:39 +0900 |
| commit | c8f3200823f564df96c037783c63bee9c5792d76 (patch) | |
| tree | c477ae68b65d8a0d89089785d5fff4526e810dd6 /internal | |
| parent | aae629a16bdce3931c508f498db7055b2301d156 (diff) | |
internal/workflows: migrate workflows
This represents workflow documents via compound literals and removes
the ugly and footgun-heavy yaml documents.
Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'internal')
| -rw-r--r-- | internal/workflows/doc.go | 10 | ||||
| -rw-r--r-- | internal/workflows/main.go | 149 | ||||
| -rw-r--r-- | internal/workflows/release.go | 37 | ||||
| -rw-r--r-- | internal/workflows/test.go | 227 |
4 files changed, 423 insertions, 0 deletions
diff --git a/internal/workflows/doc.go b/internal/workflows/doc.go new file mode 100644 index 00000000..6a9dc5e6 --- /dev/null +++ b/internal/workflows/doc.go @@ -0,0 +1,10 @@ +//go:build !workflows + +// The workflows program manages the .gitea directory to avoid having to write +// yaml. It is never invoked directly or built for the distribution tarball. +// +// This program is an internal detail of the hakurei project and is not usable +// on its own. It is not covered by the compatibility promise. +package workflows + +//go:generate go run -tags=workflows . diff --git a/internal/workflows/main.go b/internal/workflows/main.go new file mode 100644 index 00000000..46f7e054 --- /dev/null +++ b/internal/workflows/main.go @@ -0,0 +1,149 @@ +//go:build workflows + +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "os" + "path/filepath" + "sync" +) + +// KV represents a string to value pair. +type KV[T any] struct { + K string + V T +} + +// A Map represents an ordered set of key-value pairs. +type Map[T any] []KV[T] + +// MarshalJSON encodes m as a key-value pair in JSON. +func (m *Map[T]) MarshalJSON() ([]byte, error) { + if m == nil || len(*m) == 0 { + return []byte("{}"), nil + } + + buf := bytes.NewBuffer(make([]byte, 0, len(*m)<<5)) + buf.WriteByte('{') + + last := len(*m) - 1 + for i, pair := range *m { + p, err := json.Marshal(pair.K) + if err != nil { + return nil, fmt.Errorf("key %q: %w", pair.K, err) + } + buf.Write(p) + buf.WriteByte(':') + + p, err = json.Marshal(pair.V) + if err != nil { + return nil, fmt.Errorf("value of key %q: %w", pair.K, err) + } + buf.Write(p) + + if i < last { + buf.WriteByte(',') + } + } + + buf.WriteByte('}') + return buf.Bytes(), nil +} + +// A Step is a task ran as part of a [Job]. +type Step struct { + // A name for the step. + Name string `json:"name,omitempty"` + // Selects an action to run as part of a step in the job. + Uses string `json:"uses,omitempty"` + // A unique identifier for the step. + ID string `json:"id,omitempty"` + // Prevent a step from running unless a condition is met. + If string `json:"if,omitempty"` + // Runs command-line programs that do not exceed 21,000 characters using the + // operating system's shell. + Run string `json:"run,omitempty"` + // Key-value pairs of input parameters defined by the action. + With Map[any] `json:"with,omitempty"` +} + +// A Job is an entry in a [Workflow] document. +type Job struct { + // Set a name for the job. + Name string `json:"name,omitempty"` + // Identifies any jobs that must complete successfully before this job will + // run. + Needs []string `json:"needs,omitempty"` + // Define the type of machine to run the job on. + On string `json:"runs-on,omitempty"` + // A sequence of tasks. + Steps []Step `json:"steps,omitempty"` +} + +// Workflow describes a workflow document. +type Workflow struct { + // The name of the workflow. + Name string `json:"name,omitempty"` + // The name for workflow runs generated from the workflow. + RunName string `json:"run-name,omitempty"` + // Define which events can cause the workflow to run. + On any `json:"on"` + + // Named jobs part of the workflow run. + Jobs Map[Job] `json:"jobs,omitempty"` +} + +// workflowsDir is the relative pathname of the workflows directory. +const workflowsDir = "../../.gitea/workflows" + +// writeFile writes the action to the .gitea directory. +func (w *Workflow) writeFile(name string) error { + f, err := os.OpenFile( + filepath.Join(workflowsDir, name+".yml"), + os.O_CREATE|os.O_EXCL|os.O_WRONLY, + 0444, + ) + if err != nil { + return err + } + + err = json.NewEncoder(f).Encode(w) + if closeErr := f.Close(); err == nil { + err = closeErr + } + return err +} + +// removeOnce synchronises removal of stale workflow files. +var removeOnce sync.Once + +// removeWorkflows removes existing workflow files once. +func removeWorkflows() { + removeOnce.Do(func() { + if err := os.RemoveAll(workflowsDir); err != nil { + fmt.Println(err) + os.Exit(1) + } + if err := os.Mkdir(workflowsDir, 0755); err != nil { + fmt.Println(err) + os.Exit(1) + } + }) +} + +// mustWriteFile is like writeFile, but terminates the program if a non-nil +// error is returned. +func (w *Workflow) mustWriteFile(name string) (_ struct{}) { + removeWorkflows() + + if err := w.writeFile(name); err != nil { + fmt.Println(err) + os.Exit(1) + } + return +} + +func main() {} diff --git a/internal/workflows/release.go b/internal/workflows/release.go new file mode 100644 index 00000000..8b7f30bb --- /dev/null +++ b/internal/workflows/release.go @@ -0,0 +1,37 @@ +//go:build workflows + +package main + +var _ = (&Workflow{ + Name: "Release", + On: Map[any]{{K: "push", V: Map[string]{{K: "tags", V: "v*"}}}}, + + Jobs: []KV[Job]{ + {K: "release", V: Job{ + Name: "Create release", + On: "nix", + + Steps: []Step{ + {Name: "Checkout", Uses: "actions/checkout@v4"}, + + { + Name: "Build for release", + Run: "nix build " + + "--print-out-paths " + + "--print-build-logs " + + "./test#dist", + }, + + { + Name: "Release", + Uses: "https://gitea.com/actions/release-action@main", + + With: []KV[any]{ + {K: "files", V: "result/hakurei-**"}, + {K: "api_key", V: "${{secrets.RELEASE_TOKEN}}"}, + }, + }, + }, + }}, + }, +}).mustWriteFile("release") diff --git a/internal/workflows/test.go b/internal/workflows/test.go new file mode 100644 index 00000000..1bbb04f9 --- /dev/null +++ b/internal/workflows/test.go @@ -0,0 +1,227 @@ +//go:build workflows + +package main + +var _ = (&Workflow{ + Name: "Test", + On: []any{"push"}, + + Jobs: Map[Job]{ + {K: "hakurei", V: Job{ + Name: "Hakurei", + On: "nix", + + Steps: []Step{ + { + Name: "Checkout", + Uses: "actions/checkout@v4", + }, + + { + Name: "Run NixOS test", + Run: "nix build " + + "--out-link result " + + "--print-out-paths " + + "--print-build-logs " + + "./test#checks.x86_64-linux.hakurei", + }, + + { + Name: "Upload test output", + Uses: "actions/upload-artifact@v3", + + With: Map[any]{ + {K: "name", V: "hakurei-vm-output"}, + {K: "path", V: "result/*"}, + + {K: "retention-days", V: 1}, + }, + }, + }, + }}, + + {K: "race", V: Job{ + Name: "Hakurei (race detector)", + On: "nix", + + Steps: []Step{ + {Name: "Checkout", Uses: "actions/checkout@v4"}, + + { + Name: "Run NixOS test", + Run: "nix build " + + "--out-link result " + + "--print-out-paths " + + "--print-build-logs " + + "./test#checks.x86_64-linux.race", + }, + + { + Name: "Upload test output", + Uses: "actions/upload-artifact@v3", + + With: Map[any]{ + {K: "name", V: "hakurei-race-vm-output"}, + {K: "path", V: "result/*"}, + + {K: "retention-days", V: 1}, + }, + }, + }, + }}, + + {K: "sandbox", V: Job{ + Name: "Sandbox", + On: "nix", + + Steps: []Step{ + {Name: "Checkout", Uses: "actions/checkout@v4"}, + + { + Name: "Run NixOS test", + Run: "nix build " + + "--out-link result " + + "--print-out-paths " + + "--print-build-logs " + + "./test#checks.x86_64-linux.sandbox", + }, + + { + Name: "Upload test output", + Uses: "actions/upload-artifact@v3", + + With: Map[any]{ + {K: "name", V: "sandbox-vm-output"}, + {K: "path", V: "result/*"}, + + {K: "retention-days", V: 1}, + }, + }, + }, + }}, + + {K: "sandbox-race", V: Job{ + Name: "Sandbox (race detector)", + On: "nix", + + Steps: []Step{ + {Name: "Checkout", Uses: "actions/checkout@v4"}, + + { + Name: "Run NixOS test", + Run: "nix build " + + "--out-link result " + + "--print-out-paths " + + "--print-build-logs " + + "./test#checks.x86_64-linux.sandbox-race", + }, + + { + Name: "Upload test output", + Uses: "actions/upload-artifact@v3", + + With: Map[any]{ + {K: "name", V: "sandbox-race-vm-output"}, + {K: "path", V: "result/*"}, + + {K: "retention-days", V: 1}, + }, + }, + }, + }}, + + {K: "sharefs", V: Job{ + Name: "ShareFS", + On: "nix", + + Steps: []Step{ + {Name: "Checkout", Uses: "actions/checkout@v4"}, + + { + Name: "Run NixOS test", + Run: "nix build " + + "--out-link result " + + "--print-out-paths " + + "--print-build-logs " + + "./test#checks.x86_64-linux.sharefs", + }, + + { + Name: "Upload test output", + Uses: "actions/upload-artifact@v3", + + With: Map[any]{ + {K: "name", V: "sharefs-vm-output"}, + {K: "path", V: "result/*"}, + + {K: "retention-days", V: 1}, + }, + }, + }, + }}, + + {K: "check", V: Job{ + Name: "Flake checks", + On: "nix", + + Needs: []string{ + "hakurei", + "race", + "sandbox", + "sandbox-race", + "sharefs", + }, + + Steps: []Step{ + { + Name: "Checkout", + Uses: "actions/checkout@v4", + }, + + { + Name: "Run checks", + Run: "nix " + + "--print-build-logs " + + "--experimental-features 'nix-command flakes' " + + "flake check ./test", + }, + }, + }}, + + {K: "dist", V: Job{ + Name: "Create distribution", + On: "nix", + + Steps: []Step{ + { + Name: "Checkout", + Uses: "actions/checkout@v4", + }, + + { + Name: "Build for test", + ID: "build-test", + Run: "export HAKUREI_REV=\"$(git rev-parse --short HEAD)\" && " + + "sed -i.old " + + "'s/version = /version = \"0.0.0-'$HAKUREI_REV'\"; # version = /' " + + "test/package.nix && " + + "nix build --print-out-paths --print-build-logs ./test#dist && " + + "mv test/{package.nix.old,package.nix} && " + + "echo \"rev=$HAKUREI_REV\" >> $GITHUB_OUTPUT", + }, + + { + Name: "Upload test build", + Uses: "actions/upload-artifact@v3", + + With: Map[any]{ + {K: "name", V: "hakurei-${{ steps.build-test.outputs.rev }}"}, + {K: "path", V: "result/*"}, + + {K: "retention-days", V: 1}, + }, + }, + }, + }}, + }, +}).mustWriteFile("test") |
