registry.go 8.0 KB

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