Documentation
¶
Overview ¶
Package gen is the compiler side of the plugin protocol: which backends exist, how a request is built, and what happens to the files that come back.
It is private and free to change. The `plugin` package is the surface a backend author sees.
Index ¶
- Constants
- Variables
- func Builtin(name string) (plugin.Backend, bool)
- func BuiltinNames() []string
- func CheckDirectives(target string, model *ir.Model, desc plugin.Description) []*plugin.Diagnostic
- func Clean(out string) ([]string, error)
- func Fatal(diags []*plugin.Diagnostic) bool
- func Mark(out string) error
- func Owned(out string) bool
- func Resolve(name string) (plugin.Backend, error)
- func Watch(done <-chan struct{}, path string, onChange func())
- func Write(out string, files []*plugin.File) ([]string, error)
- type Mode
- type Result
- type Session
- type Stale
- type Subprocess
- type Target
Constants ¶
const CommandPrefix = "tdl-gen-"
CommandPrefix is what a target name is prefixed with to find its executable. A target tdl has no backend for resolves to tdl-gen-<name> on PATH, the way git and protoc find their subcommands.
const DefaultTimeout = 2 * time.Minute
DefaultTimeout bounds how long a plugin has to answer.
A hung plugin in CI should fail with a diagnosis rather than consume the job's whole time budget and report nothing. A legitimately slow backend will want to raise this, which is what tdl.toml is for; until that exists it is a constant.
const MarkerName = ".tdl-output"
MarkerName is the file tdl drops in a directory it writes to.
It is how --clean knows a directory is its own. A directory holding files but no marker belongs to someone else, and cleaning it is an error rather than a judgment call: output directories are not always exclusively owned by tdl.
const WatchInterval = time.Second
WatchInterval is how often a watched file is checked.
Polling rather than an OS notification API keeps this dependency-free and behaves the same everywhere; a second is fast enough for a person saving a file and slow enough to be free.
Variables ¶
var ErrNotOurs = errors.New("the output directory was not written by tdl")
ErrNotOurs is returned when --clean is asked to empty a directory that has files but no marker.
Functions ¶
func BuiltinNames ¶
func BuiltinNames() []string
BuiltinNames lists the compiled-in backends, sorted.
func CheckDirectives ¶
func CheckDirectives(target string, model *ir.Model, desc plugin.Description) []*plugin.Diagnostic
CheckDirectives compares the directives a target block uses against what its backend says it understands.
A declared directive used with the wrong number or kind of arguments is an error, reported with the position in the .tdl file, before anything is generated: the alternative is a backend discovering it half way through writing files.
A directive the backend did not declare is a warning and is passed through anyway. Under-declaring is a plugin bug that should not break a working project, and a backend is free to handle more than it advertises. The warning still names it, so a typo stays visible.
func Clean ¶
Clean empties an output directory tdl owns, leaving the marker.
A directory that does not exist is already clean. One that exists and is empty is adopted, since there is nothing there to belong to anyone else. One with contents and no marker is ErrNotOurs.
func Fatal ¶ added in v0.1.8
func Fatal(diags []*plugin.Diagnostic) bool
Fatal reports whether any diagnostic is an error rather than a warning.
func Resolve ¶
Resolve returns the backend serving a target: the compiled-in one if there is one, otherwise tdl-gen-<name> on PATH.
Both speak the same protocol, so everything above this treats them the same. Preferring the built-in means a name tdl ships cannot be shadowed by whatever happens to be on PATH.
func Watch ¶
func Watch(done <-chan struct{}, path string, onChange func())
Watch calls onChange whenever path's contents change, until ctx is done.
It compares contents rather than modification time, so an editor that rewrites a file without changing it does not trigger a regeneration.
A poll can land while a file is being written, and an editor that truncates before writing will briefly present an empty file. That reads as a change, and the regeneration it triggers fails to parse. The next poll sees the finished file and succeeds, so it corrects itself; an editor that saves by renaming never shows the intermediate state at all.
func Write ¶
Write puts a response's files under out.
A backend returns contents rather than writing them, which is what lets this enforce where they land. A path is relative to out, and an absolute one or one climbing out with ".." is refused before anything is written: a plugin cannot reach outside the directory the project pointed it at, whatever it intends.
Types ¶
type Result ¶
type Result struct {
Target string
Written []string
Removed []string
Stale []Stale
Diagnostics []*plugin.Diagnostic
}
Result is what one target produced.
type Session ¶
type Session struct {
// contains filtered or unexported fields
}
Session is a plugin kept alive across generations.
A plugin that declared reuse serves more than one request on one connection, which is what makes a watch loop cheap. It must treat each request as independent; carrying state between them is a plugin bug, and nothing here can catch it.
func Open ¶
func Open(ctx context.Context, sub *Subprocess) (*Session, error)
Open starts a plugin and holds the connection if it declared reuse.
A plugin that did not gets a fresh process per generation, which is the default: reuse is something a backend opts into by saying it can. The one handshake answers both questions, so a plugin that will not shake hands is an error here rather than a description of nothing.
func (*Session) Describe ¶
func (s *Session) Describe() plugin.Description
Describe reports what the plugin said about itself when it was opened, without starting another process to ask again.
type Stale ¶
Stale describes one way an output directory disagrees with what a backend would write.
type Subprocess ¶
Subprocess is a backend running as tdl-gen-<name>.
It implements plugin.Backend, so everything above it treats a plugin and a compiled-in backend the same way. That is the protocol's one real claim, and this type is what makes it testable rather than asserted.
func Find ¶
func Find(name string) (*Subprocess, error)
Find locates the executable for a target name.
func (*Subprocess) Describe ¶
func (s *Subprocess) Describe() plugin.Description
Describe starts the plugin, shakes hands, and stops it again.
Describing costs a process. Generate starts its own, so a description fetched here is not carried into it; keeping one connection across both is what phase 8's reuse is for.
type Target ¶
type Target struct {
Name string
Out string
Block *ir.TargetBlock
}
Target is one target block resolved into what a backend needs.