aboutsummaryrefslogtreecommitdiffhomepage
path: root/internal
diff options
context:
space:
mode:
Diffstat (limited to 'internal')
-rw-r--r--internal/workflows/doc.go10
-rw-r--r--internal/workflows/main.go149
-rw-r--r--internal/workflows/release.go37
-rw-r--r--internal/workflows/test.go227
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")