registry.go 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. package core
  2. import (
  3. "fmt"
  4. "reflect"
  5. "sort"
  6. "strings"
  7. "unsafe"
  8. "github.com/spf13/cobra"
  9. "github.com/spf13/pflag"
  10. )
  11. // PipelineItemRegistry contains all the known PipelineItem-s.
  12. type PipelineItemRegistry struct {
  13. provided map[string][]reflect.Type
  14. registered map[string]reflect.Type
  15. flags map[string]reflect.Type
  16. featureFlags arrayFeatureFlags
  17. }
  18. // Register adds another PipelineItem to the registry.
  19. func (registry *PipelineItemRegistry) Register(example PipelineItem) {
  20. t := reflect.TypeOf(example)
  21. registry.registered[example.Name()] = t
  22. if fpi, ok := example.(LeafPipelineItem); ok {
  23. registry.flags[fpi.Flag()] = t
  24. }
  25. for _, dep := range example.Provides() {
  26. ts := registry.provided[dep]
  27. if ts == nil {
  28. ts = []reflect.Type{}
  29. }
  30. ts = append(ts, t)
  31. registry.provided[dep] = ts
  32. }
  33. }
  34. // Summon searches for PipelineItem-s which provide the specified entity or named after
  35. // the specified string. It materializes all the found types and returns them.
  36. func (registry *PipelineItemRegistry) Summon(providesOrName string) []PipelineItem {
  37. if registry.provided == nil {
  38. return nil
  39. }
  40. ts := registry.provided[providesOrName]
  41. var items []PipelineItem
  42. for _, t := range ts {
  43. items = append(items, reflect.New(t.Elem()).Interface().(PipelineItem))
  44. }
  45. if t, exists := registry.registered[providesOrName]; exists {
  46. items = append(items, reflect.New(t.Elem()).Interface().(PipelineItem))
  47. }
  48. return items
  49. }
  50. // GetLeaves returns all LeafPipelineItem-s registered.
  51. func (registry *PipelineItemRegistry) GetLeaves() []LeafPipelineItem {
  52. keys := []string{}
  53. for key := range registry.flags {
  54. keys = append(keys, key)
  55. }
  56. sort.Strings(keys)
  57. items := []LeafPipelineItem{}
  58. for _, key := range keys {
  59. items = append(items, reflect.New(registry.flags[key].Elem()).Interface().(LeafPipelineItem))
  60. }
  61. return items
  62. }
  63. // GetPlumbingItems returns all non-LeafPipelineItem-s registered.
  64. func (registry *PipelineItemRegistry) GetPlumbingItems() []PipelineItem {
  65. keys := []string{}
  66. for key := range registry.registered {
  67. keys = append(keys, key)
  68. }
  69. sort.Strings(keys)
  70. items := []PipelineItem{}
  71. for _, key := range keys {
  72. iface := reflect.New(registry.registered[key].Elem()).Interface()
  73. if _, ok := iface.(LeafPipelineItem); !ok {
  74. items = append(items, iface.(PipelineItem))
  75. }
  76. }
  77. return items
  78. }
  79. // GetFeaturedItems returns all FeaturedPipelineItem-s registered.
  80. func (registry *PipelineItemRegistry) GetFeaturedItems() map[string][]PipelineItem {
  81. features := map[string][]PipelineItem{}
  82. for _, t := range registry.registered {
  83. item := reflect.New(t.Elem()).Interface().(PipelineItem)
  84. deps := registry.CollectAllDependencies(item)
  85. deps = append(deps, item)
  86. depFeatures := map[string]bool{}
  87. for _, dep := range deps {
  88. if fiface, ok := dep.(FeaturedPipelineItem); ok {
  89. for _, f := range fiface.Features() {
  90. depFeatures[f] = true
  91. }
  92. }
  93. }
  94. for f := range depFeatures {
  95. features[f] = append(features[f], item)
  96. }
  97. }
  98. for _, vals := range features {
  99. sort.Slice(vals, func(i, j int) bool {
  100. return vals[i].Name() < vals[j].Name()
  101. })
  102. }
  103. return features
  104. }
  105. // CollectAllDependencies recursively builds the list of all the items on which the specified item
  106. // depends.
  107. func (registry *PipelineItemRegistry) CollectAllDependencies(item PipelineItem) []PipelineItem {
  108. deps := map[string]PipelineItem{}
  109. for stack := []PipelineItem{item}; len(stack) > 0; {
  110. head := stack[len(stack)-1]
  111. stack = stack[:len(stack)-1]
  112. for _, reqID := range head.Requires() {
  113. req := registry.Summon(reqID)[0]
  114. if _, exists := deps[reqID]; !exists {
  115. deps[reqID] = req
  116. stack = append(stack, req)
  117. }
  118. }
  119. }
  120. result := make([]PipelineItem, 0, len(deps))
  121. for _, val := range deps {
  122. result = append(result, val)
  123. }
  124. sort.Slice(result, func(i, j int) bool {
  125. return result[i].Name() < result[j].Name()
  126. })
  127. return result
  128. }
  129. var pathFlagTypeMasquerade bool
  130. // EnablePathFlagTypeMasquerade changes the type of all "path" command line arguments from "string"
  131. // to "path". This operation cannot be canceled and is intended to be used for better --help output.
  132. func EnablePathFlagTypeMasquerade() {
  133. pathFlagTypeMasquerade = true
  134. }
  135. type pathValue struct {
  136. origin pflag.Value
  137. }
  138. func wrapPathValue(val pflag.Value) pflag.Value {
  139. return &pathValue{val}
  140. }
  141. func (s *pathValue) Set(val string) error {
  142. return s.origin.Set(val)
  143. }
  144. func (s *pathValue) Type() string {
  145. if pathFlagTypeMasquerade {
  146. return "path"
  147. }
  148. return "string"
  149. }
  150. func (s *pathValue) String() string {
  151. return s.origin.String()
  152. }
  153. // PathifyFlagValue changes the type of a string command line argument to "path".
  154. func PathifyFlagValue(flag *pflag.Flag) {
  155. flag.Value = wrapPathValue(flag.Value)
  156. }
  157. type arrayFeatureFlags struct {
  158. // Flags contains the features activated through the command line.
  159. Flags []string
  160. // Choices contains all registered features.
  161. Choices map[string]bool
  162. }
  163. func (acf *arrayFeatureFlags) String() string {
  164. return strings.Join([]string(acf.Flags), ", ")
  165. }
  166. func (acf *arrayFeatureFlags) Set(value string) error {
  167. if _, exists := acf.Choices[value]; !exists {
  168. return fmt.Errorf("feature \"%s\" is not registered", value)
  169. }
  170. acf.Flags = append(acf.Flags, value)
  171. return nil
  172. }
  173. func (acf *arrayFeatureFlags) Type() string {
  174. return "string"
  175. }
  176. // AddFlags inserts the cmdline options from PipelineItem.ListConfigurationOptions(),
  177. // FeaturedPipelineItem().Features() and LeafPipelineItem.Flag() into the global "flag" parser
  178. // built into the Go runtime.
  179. // Returns the "facts" which can be fed into PipelineItem.Configure() and the dictionary of
  180. // runnable analysis (LeafPipelineItem) choices. E.g. if "BurndownAnalysis" was activated
  181. // through "-burndown" cmdline argument, this mapping would contain ["BurndownAnalysis"] = *true.
  182. func (registry *PipelineItemRegistry) AddFlags(flagSet *pflag.FlagSet) (
  183. map[string]interface{}, map[string]*bool) {
  184. flags := map[string]interface{}{}
  185. deployed := map[string]*bool{}
  186. for name, it := range registry.registered {
  187. formatHelp := func(desc string) string {
  188. return fmt.Sprintf("%s [%s]", desc, name)
  189. }
  190. itemIface := reflect.New(it.Elem()).Interface()
  191. for _, opt := range itemIface.(PipelineItem).ListConfigurationOptions() {
  192. var iface interface{}
  193. getPtr := func() unsafe.Pointer {
  194. return unsafe.Pointer(uintptr(unsafe.Pointer(&iface)) + unsafe.Sizeof(&iface))
  195. }
  196. switch opt.Type {
  197. case BoolConfigurationOption:
  198. iface = interface{}(true)
  199. ptr := (**bool)(getPtr())
  200. *ptr = flagSet.Bool(opt.Flag, opt.Default.(bool), formatHelp(opt.Description))
  201. case IntConfigurationOption:
  202. iface = interface{}(0)
  203. ptr := (**int)(getPtr())
  204. *ptr = flagSet.Int(opt.Flag, opt.Default.(int), formatHelp(opt.Description))
  205. case StringConfigurationOption, PathConfigurationOption:
  206. iface = interface{}("")
  207. ptr := (**string)(getPtr())
  208. *ptr = flagSet.String(opt.Flag, opt.Default.(string), formatHelp(opt.Description))
  209. if opt.Type == PathConfigurationOption {
  210. err := cobra.MarkFlagFilename(flagSet, opt.Flag)
  211. if err != nil {
  212. panic(err)
  213. }
  214. PathifyFlagValue(flagSet.Lookup(opt.Flag))
  215. }
  216. case FloatConfigurationOption:
  217. iface = interface{}(float32(0))
  218. ptr := (**float32)(getPtr())
  219. *ptr = flagSet.Float32(opt.Flag, opt.Default.(float32), formatHelp(opt.Description))
  220. case StringsConfigurationOption:
  221. iface = interface{}([]string{})
  222. ptr := (**[]string)(getPtr())
  223. *ptr = flagSet.StringSlice(opt.Flag, opt.Default.([]string), formatHelp(opt.Description))
  224. }
  225. flags[opt.Name] = iface
  226. }
  227. if fpi, ok := itemIface.(FeaturedPipelineItem); ok {
  228. for _, f := range fpi.Features() {
  229. registry.featureFlags.Choices[f] = true
  230. }
  231. }
  232. if fpi, ok := itemIface.(LeafPipelineItem); ok {
  233. deployed[fpi.Name()] = flagSet.Bool(
  234. fpi.Flag(), false, fmt.Sprintf("Runs %s analysis.", fpi.Name()))
  235. }
  236. }
  237. {
  238. // Pipeline flags
  239. iface := interface{}("")
  240. ptr1 := (**string)(unsafe.Pointer(uintptr(unsafe.Pointer(&iface)) + unsafe.Sizeof(&iface)))
  241. *ptr1 = flagSet.String("dump-dag", "", "Write the pipeline DAG to a Graphviz file.")
  242. flags[ConfigPipelineDAGPath] = iface
  243. PathifyFlagValue(flagSet.Lookup("dump-dag"))
  244. iface = interface{}(true)
  245. ptr2 := (**bool)(unsafe.Pointer(uintptr(unsafe.Pointer(&iface)) + unsafe.Sizeof(&iface)))
  246. *ptr2 = flagSet.Bool("dry-run", false, "Do not run any analyses - only resolve the DAG. "+
  247. "Useful for --dump-dag or --dump-plan.")
  248. flags[ConfigPipelineDryRun] = iface
  249. iface = interface{}(true)
  250. ptr3 := (**bool)(unsafe.Pointer(uintptr(unsafe.Pointer(&iface)) + unsafe.Sizeof(&iface)))
  251. *ptr3 = flagSet.Bool("dump-plan", false, "Print the pipeline execution plan to stderr.")
  252. flags[ConfigPipelineDumpPlan] = iface
  253. iface = interface{}(0)
  254. ptr4 := (**int)(unsafe.Pointer(uintptr(unsafe.Pointer(&iface)) + unsafe.Sizeof(&iface)))
  255. *ptr4 = flagSet.Int("hibernation-distance", 0,
  256. "Minimum number of actions between two sequential usages of a branch to activate "+
  257. "the hibernation optimization (cpu-memory trade-off). 0 disables.")
  258. flags[ConfigPipelineHibernationDistance] = iface
  259. iface = interface{}(true)
  260. ptr5 := (**bool)(unsafe.Pointer(uintptr(unsafe.Pointer(&iface)) + unsafe.Sizeof(&iface)))
  261. *ptr5 = flagSet.Bool("print-actions", false, "Print the executed actions to stderr.")
  262. flags[ConfigPipelinePrintActions] = iface
  263. }
  264. var features []string
  265. for f := range registry.featureFlags.Choices {
  266. features = append(features, f)
  267. }
  268. flagSet.Var(&registry.featureFlags, "feature",
  269. fmt.Sprintf("Enables the items which depend on the specified features. Can be specified "+
  270. "multiple times. Available features: [%s] (see --feature below).",
  271. strings.Join(features, ", ")))
  272. return flags, deployed
  273. }
  274. // Registry contains all known pipeline item types.
  275. var Registry = &PipelineItemRegistry{
  276. provided: map[string][]reflect.Type{},
  277. registered: map[string]reflect.Type{},
  278. flags: map[string]reflect.Type{},
  279. featureFlags: arrayFeatureFlags{Flags: []string{}, Choices: map[string]bool{}},
  280. }