mirror of
https://github.com/coder/coder.git
synced 2026-06-03 13:08:25 +00:00
a6a8fd94d7
`make gen` could not run with `-j` because inter-target dependency edges were missing. Multiple recipes compile `coderd/rbac` (which includes generated files like `object_gen.go`), and without explicit ordering, parallel runs produced syntax errors from mid-write reads. Three main changes: **Dependency graph fixes** declare the compile-time chain through `coderd/rbac` so that `object_gen.go` is written before anything that imports it is compiled. The DB generation targets use a GNU Make 4.3+ grouped target (`&:`) so Make knows `generate.sh` co-produces `querier.go`, `unique_constraint.go`, `dbmetrics`, and `dbauthz` in a single invocation. `SKIP_DUMP_SQL=1` avoids re-entrant `make` inside `generate.sh` when the Makefile already guarantees `dump.sql` is fresh. **`scripts/atomicwrite` package** replaces `os.WriteFile` in all gen scripts with a temp-file-in-same-dir + rename pattern, preventing interrupted runs from leaving partial files. **`.PRECIOUS` and shell atomic writes** protect git-tracked generated files from Make's default delete-on-error behavior. Since these files are committed, deletion is worse than staleness -- `git restore` is the recovery path. CI now runs `make -j --output-sync -B gen` (~32s, down from ~85s serial). | Scenario | Before | After | |-----------------------------------|--------------------|----------| | `make gen` (serial) | 95s | 95s | | `make -j gen` (parallel) | race error | **22s** | | CI `make -j --output-sync -B gen` | forced serial ~85s | **~32s** |
154 lines
3.3 KiB
Go
154 lines
3.3 KiB
Go
package main
|
|
|
|
import (
|
|
_ "embed"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"text/template"
|
|
|
|
"github.com/acarl005/stripansi"
|
|
|
|
"github.com/coder/coder/v2/buildinfo"
|
|
"github.com/coder/coder/v2/scripts/atomicwrite"
|
|
"github.com/coder/flog"
|
|
"github.com/coder/serpent"
|
|
)
|
|
|
|
//go:embed command.tpl
|
|
var commandTemplateRaw string
|
|
|
|
var commandTemplate *template.Template
|
|
|
|
func init() {
|
|
commandTemplate = template.Must(
|
|
template.New("command.tpl").Funcs(template.FuncMap{
|
|
"visibleSubcommands": func(cmd *serpent.Command) []*serpent.Command {
|
|
var visible []*serpent.Command
|
|
for _, sub := range cmd.Children {
|
|
if sub.Hidden {
|
|
continue
|
|
}
|
|
visible = append(visible, sub)
|
|
}
|
|
return visible
|
|
},
|
|
"visibleOptions": func(cmd *serpent.Command) []serpent.Option {
|
|
var visible []serpent.Option
|
|
for _, opt := range cmd.Options {
|
|
if opt.Hidden {
|
|
continue
|
|
}
|
|
visible = append(visible, opt)
|
|
}
|
|
return visible
|
|
},
|
|
"atRoot": func(cmd *serpent.Command) bool {
|
|
return cmd.FullName() == "coder"
|
|
},
|
|
"newLinesToBr": func(s string) string {
|
|
return strings.ReplaceAll(s, "\n", "<br/>")
|
|
},
|
|
"wrapCode": func(s string) string {
|
|
return fmt.Sprintf("<code>%s</code>", s)
|
|
},
|
|
"commandURI": fmtDocFilename,
|
|
"fullName": fullName,
|
|
"tableHeader": func() string {
|
|
return `| | |
|
|
| --- | --- |`
|
|
},
|
|
"typeHelper": func(opt *serpent.Option) string {
|
|
switch v := opt.Value.(type) {
|
|
case *serpent.Enum:
|
|
return strings.Join(v.Choices, "\\|")
|
|
case *serpent.EnumArray:
|
|
return fmt.Sprintf("[%s]", strings.Join(v.Choices, "\\|"))
|
|
default:
|
|
return v.Type()
|
|
}
|
|
},
|
|
},
|
|
).Parse(strings.TrimSpace(commandTemplateRaw)),
|
|
)
|
|
}
|
|
|
|
func fullName(cmd *serpent.Command) string {
|
|
if cmd.FullName() == "coder" {
|
|
return "coder"
|
|
}
|
|
return strings.TrimPrefix(cmd.FullName(), "coder ")
|
|
}
|
|
|
|
func fmtDocFilename(cmd *serpent.Command) string {
|
|
if cmd.FullName() == "coder" {
|
|
// Special case for index.
|
|
return "./index.md"
|
|
}
|
|
name := strings.ReplaceAll(fullName(cmd), " ", "_")
|
|
return fmt.Sprintf("%s.md", name)
|
|
}
|
|
|
|
func writeCommand(w io.Writer, cmd *serpent.Command) error {
|
|
var b strings.Builder
|
|
err := commandTemplate.Execute(&b, cmd)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
content := stripansi.Strip(b.String())
|
|
|
|
// Remove the version and its right space, since during this script running
|
|
// there is no build info available
|
|
content = strings.ReplaceAll(content, buildinfo.Version()+" ", "")
|
|
|
|
// Remove references to the current working directory
|
|
cwd, err := os.Getwd()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
content = strings.ReplaceAll(content, cwd, ".")
|
|
|
|
homedir, err := os.UserHomeDir()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
content = strings.ReplaceAll(content, homedir, "~")
|
|
|
|
_, err = w.Write([]byte(content))
|
|
return err
|
|
}
|
|
|
|
func genTree(dir string, cmd *serpent.Command, wroteLog map[string]*serpent.Command) error {
|
|
if cmd.Hidden {
|
|
return nil
|
|
}
|
|
|
|
path := filepath.Join(dir, fmtDocFilename(cmd))
|
|
|
|
var buf strings.Builder
|
|
err := writeCommand(&buf, cmd)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = atomicwrite.File(path, []byte(buf.String()))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
flog.Successf(
|
|
"wrote\t%s",
|
|
path,
|
|
)
|
|
wroteLog[path] = cmd
|
|
for _, sub := range cmd.Children {
|
|
err = genTree(dir, sub, wroteLog)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|