123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304 |
- package core
- import (
- "fmt"
- "reflect"
- "sort"
- "strings"
- "unsafe"
- "github.com/spf13/cobra"
- "github.com/spf13/pflag"
- )
- // PipelineItemRegistry contains all the known PipelineItem-s.
- type PipelineItemRegistry struct {
- provided map[string][]reflect.Type
- registered map[string]reflect.Type
- flags map[string]reflect.Type
- featureFlags arrayFeatureFlags
- }
- // Register adds another PipelineItem to the registry.
- func (registry *PipelineItemRegistry) Register(example PipelineItem) {
- t := reflect.TypeOf(example)
- registry.registered[example.Name()] = t
- if fpi, ok := example.(LeafPipelineItem); ok {
- registry.flags[fpi.Flag()] = t
- }
- for _, dep := range example.Provides() {
- ts := registry.provided[dep]
- if ts == nil {
- ts = []reflect.Type{}
- }
- ts = append(ts, t)
- registry.provided[dep] = ts
- }
- }
- // Summon searches for PipelineItem-s which provide the specified entity or named after
- // the specified string. It materializes all the found types and returns them.
- func (registry *PipelineItemRegistry) Summon(providesOrName string) []PipelineItem {
- if registry.provided == nil {
- return nil
- }
- ts := registry.provided[providesOrName]
- var items []PipelineItem
- for _, t := range ts {
- items = append(items, reflect.New(t.Elem()).Interface().(PipelineItem))
- }
- if t, exists := registry.registered[providesOrName]; exists {
- items = append(items, reflect.New(t.Elem()).Interface().(PipelineItem))
- }
- return items
- }
- // GetLeaves returns all LeafPipelineItem-s registered.
- func (registry *PipelineItemRegistry) GetLeaves() []LeafPipelineItem {
- keys := []string{}
- for key := range registry.flags {
- keys = append(keys, key)
- }
- sort.Strings(keys)
- items := []LeafPipelineItem{}
- for _, key := range keys {
- items = append(items, reflect.New(registry.flags[key].Elem()).Interface().(LeafPipelineItem))
- }
- return items
- }
- // GetPlumbingItems returns all non-LeafPipelineItem-s registered.
- func (registry *PipelineItemRegistry) GetPlumbingItems() []PipelineItem {
- keys := []string{}
- for key := range registry.registered {
- keys = append(keys, key)
- }
- sort.Strings(keys)
- items := []PipelineItem{}
- for _, key := range keys {
- iface := reflect.New(registry.registered[key].Elem()).Interface()
- if _, ok := iface.(LeafPipelineItem); !ok {
- items = append(items, iface.(PipelineItem))
- }
- }
- return items
- }
- // GetFeaturedItems returns all FeaturedPipelineItem-s registered.
- func (registry *PipelineItemRegistry) GetFeaturedItems() map[string][]PipelineItem {
- features := map[string][]PipelineItem{}
- for _, t := range registry.registered {
- item := reflect.New(t.Elem()).Interface().(PipelineItem)
- deps := registry.CollectAllDependencies(item)
- deps = append(deps, item)
- depFeatures := map[string]bool{}
- for _, dep := range deps {
- if fiface, ok := dep.(FeaturedPipelineItem); ok {
- for _, f := range fiface.Features() {
- depFeatures[f] = true
- }
- }
- }
- for f := range depFeatures {
- features[f] = append(features[f], item)
- }
- }
- for _, vals := range features {
- sort.Slice(vals, func(i, j int) bool {
- return vals[i].Name() < vals[j].Name()
- })
- }
- return features
- }
- // CollectAllDependencies recursively builds the list of all the items on which the specified item
- // depends.
- func (registry *PipelineItemRegistry) CollectAllDependencies(item PipelineItem) []PipelineItem {
- deps := map[string]PipelineItem{}
- for stack := []PipelineItem{item}; len(stack) > 0; {
- head := stack[len(stack)-1]
- stack = stack[:len(stack)-1]
- for _, reqID := range head.Requires() {
- req := registry.Summon(reqID)[0]
- if _, exists := deps[reqID]; !exists {
- deps[reqID] = req
- stack = append(stack, req)
- }
- }
- }
- result := make([]PipelineItem, 0, len(deps))
- for _, val := range deps {
- result = append(result, val)
- }
- sort.Slice(result, func(i, j int) bool {
- return result[i].Name() < result[j].Name()
- })
- return result
- }
- var pathFlagTypeMasquerade bool
- // EnablePathFlagTypeMasquerade changes the type of all "path" command line arguments from "string"
- // to "path". This operation cannot be canceled and is intended to be used for better --help output.
- func EnablePathFlagTypeMasquerade() {
- pathFlagTypeMasquerade = true
- }
- type pathValue struct {
- origin pflag.Value
- }
- func wrapPathValue(val pflag.Value) pflag.Value {
- return &pathValue{val}
- }
- func (s *pathValue) Set(val string) error {
- return s.origin.Set(val)
- }
- func (s *pathValue) Type() string {
- if pathFlagTypeMasquerade {
- return "path"
- }
- return "string"
- }
- func (s *pathValue) String() string {
- return s.origin.String()
- }
- // PathifyFlagValue changes the type of a string command line argument to "path".
- func PathifyFlagValue(flag *pflag.Flag) {
- flag.Value = wrapPathValue(flag.Value)
- }
- type arrayFeatureFlags struct {
- // Flags contains the features activated through the command line.
- Flags []string
- // Choices contains all registered features.
- Choices map[string]bool
- }
- func (acf *arrayFeatureFlags) String() string {
- return strings.Join([]string(acf.Flags), ", ")
- }
- func (acf *arrayFeatureFlags) Set(value string) error {
- if _, exists := acf.Choices[value]; !exists {
- return fmt.Errorf("feature \"%s\" is not registered", value)
- }
- acf.Flags = append(acf.Flags, value)
- return nil
- }
- func (acf *arrayFeatureFlags) Type() string {
- return "string"
- }
- // AddFlags inserts the cmdline options from PipelineItem.ListConfigurationOptions(),
- // FeaturedPipelineItem().Features() and LeafPipelineItem.Flag() into the global "flag" parser
- // built into the Go runtime.
- // Returns the "facts" which can be fed into PipelineItem.Configure() and the dictionary of
- // runnable analysis (LeafPipelineItem) choices. E.g. if "BurndownAnalysis" was activated
- // through "-burndown" cmdline argument, this mapping would contain ["BurndownAnalysis"] = *true.
- func (registry *PipelineItemRegistry) AddFlags(flagSet *pflag.FlagSet) (
- map[string]interface{}, map[string]*bool) {
- flags := map[string]interface{}{}
- deployed := map[string]*bool{}
- for name, it := range registry.registered {
- formatHelp := func(desc string) string {
- return fmt.Sprintf("%s [%s]", desc, name)
- }
- itemIface := reflect.New(it.Elem()).Interface()
- for _, opt := range itemIface.(PipelineItem).ListConfigurationOptions() {
- var iface interface{}
- getPtr := func() unsafe.Pointer {
- return unsafe.Pointer(uintptr(unsafe.Pointer(&iface)) + unsafe.Sizeof(&iface))
- }
- switch opt.Type {
- case BoolConfigurationOption:
- iface = interface{}(true)
- ptr := (**bool)(getPtr())
- *ptr = flagSet.Bool(opt.Flag, opt.Default.(bool), formatHelp(opt.Description))
- case IntConfigurationOption:
- iface = interface{}(0)
- ptr := (**int)(getPtr())
- *ptr = flagSet.Int(opt.Flag, opt.Default.(int), formatHelp(opt.Description))
- case StringConfigurationOption, PathConfigurationOption:
- iface = interface{}("")
- ptr := (**string)(getPtr())
- *ptr = flagSet.String(opt.Flag, opt.Default.(string), formatHelp(opt.Description))
- if opt.Type == PathConfigurationOption {
- err := cobra.MarkFlagFilename(flagSet, opt.Flag)
- if err != nil {
- panic(err)
- }
- PathifyFlagValue(flagSet.Lookup(opt.Flag))
- }
- case FloatConfigurationOption:
- iface = interface{}(float32(0))
- ptr := (**float32)(getPtr())
- *ptr = flagSet.Float32(opt.Flag, opt.Default.(float32), formatHelp(opt.Description))
- case StringsConfigurationOption:
- iface = interface{}([]string{})
- ptr := (**[]string)(getPtr())
- *ptr = flagSet.StringSlice(opt.Flag, opt.Default.([]string), formatHelp(opt.Description))
- }
- flags[opt.Name] = iface
- }
- if fpi, ok := itemIface.(FeaturedPipelineItem); ok {
- for _, f := range fpi.Features() {
- registry.featureFlags.Choices[f] = true
- }
- }
- if fpi, ok := itemIface.(LeafPipelineItem); ok {
- deployed[fpi.Name()] = flagSet.Bool(
- fpi.Flag(), false, fmt.Sprintf("Runs %s analysis.", fpi.Name()))
- }
- }
- {
- // Pipeline flags
- iface := interface{}("")
- ptr1 := (**string)(unsafe.Pointer(uintptr(unsafe.Pointer(&iface)) + unsafe.Sizeof(&iface)))
- *ptr1 = flagSet.String("dump-dag", "", "Write the pipeline DAG to a Graphviz file.")
- flags[ConfigPipelineDAGPath] = iface
- PathifyFlagValue(flagSet.Lookup("dump-dag"))
- iface = interface{}(true)
- ptr2 := (**bool)(unsafe.Pointer(uintptr(unsafe.Pointer(&iface)) + unsafe.Sizeof(&iface)))
- *ptr2 = flagSet.Bool("dry-run", false, "Do not run any analyses - only resolve the DAG. "+
- "Useful for --dump-dag or --dump-plan.")
- flags[ConfigPipelineDryRun] = iface
- iface = interface{}(true)
- ptr3 := (**bool)(unsafe.Pointer(uintptr(unsafe.Pointer(&iface)) + unsafe.Sizeof(&iface)))
- *ptr3 = flagSet.Bool("dump-plan", false, "Print the pipeline execution plan to stderr.")
- flags[ConfigPipelineDumpPlan] = iface
- iface = interface{}(0)
- ptr4 := (**int)(unsafe.Pointer(uintptr(unsafe.Pointer(&iface)) + unsafe.Sizeof(&iface)))
- *ptr4 = flagSet.Int("hibernation-distance", 0,
- "Minimum number of actions between two sequential usages of a branch to activate "+
- "the hibernation optimization (cpu-memory trade-off). 0 disables.")
- flags[ConfigPipelineHibernationDistance] = iface
- iface = interface{}(true)
- ptr5 := (**bool)(unsafe.Pointer(uintptr(unsafe.Pointer(&iface)) + unsafe.Sizeof(&iface)))
- *ptr5 = flagSet.Bool("print-actions", false, "Print the executed actions to stderr.")
- flags[ConfigPipelinePrintActions] = iface
- }
- var features []string
- for f := range registry.featureFlags.Choices {
- features = append(features, f)
- }
- flagSet.Var(®istry.featureFlags, "feature",
- fmt.Sprintf("Enables the items which depend on the specified features. Can be specified "+
- "multiple times. Available features: [%s] (see --feature below).",
- strings.Join(features, ", ")))
- return flags, deployed
- }
- // Registry contains all known pipeline item types.
- var Registry = &PipelineItemRegistry{
- provided: map[string][]reflect.Type{},
- registered: map[string]reflect.Type{},
- flags: map[string]reflect.Type{},
- featureFlags: arrayFeatureFlags{Flags: []string{}, Choices: map[string]bool{}},
- }
|