registry.go 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. package hercules
  2. import (
  3. "errors"
  4. "fmt"
  5. "reflect"
  6. "sort"
  7. "strings"
  8. "unsafe"
  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. }
  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. func (registry *PipelineItemRegistry) Summon(providesOrName string) []PipelineItem {
  34. if registry.provided == nil {
  35. return []PipelineItem{}
  36. }
  37. ts := registry.provided[providesOrName]
  38. items := []PipelineItem{}
  39. for _, t := range ts {
  40. items = append(items, reflect.New(t.Elem()).Interface().(PipelineItem))
  41. }
  42. if t, exists := registry.registered[providesOrName]; exists {
  43. items = append(items, reflect.New(t.Elem()).Interface().(PipelineItem))
  44. }
  45. return items
  46. }
  47. func (registry *PipelineItemRegistry) GetLeaves() []LeafPipelineItem {
  48. keys := []string{}
  49. for key := range registry.flags {
  50. keys = append(keys, key)
  51. }
  52. sort.Strings(keys)
  53. items := []LeafPipelineItem{}
  54. for _, key := range keys {
  55. items = append(items, reflect.New(registry.flags[key].Elem()).Interface().(LeafPipelineItem))
  56. }
  57. return items
  58. }
  59. func (registry *PipelineItemRegistry) GetPlumbingItems() []PipelineItem {
  60. keys := []string{}
  61. for key := range registry.registered {
  62. keys = append(keys, key)
  63. }
  64. sort.Strings(keys)
  65. items := []PipelineItem{}
  66. for _, key := range keys {
  67. iface := reflect.New(registry.registered[key].Elem()).Interface()
  68. if _, ok := iface.(LeafPipelineItem); !ok {
  69. items = append(items, iface.(PipelineItem))
  70. }
  71. }
  72. return items
  73. }
  74. type orderedFeaturedItems []FeaturedPipelineItem
  75. func (ofi orderedFeaturedItems) Len() int {
  76. return len([]FeaturedPipelineItem(ofi))
  77. }
  78. func (ofi orderedFeaturedItems) Less(i, j int) bool {
  79. cofi := []FeaturedPipelineItem(ofi)
  80. return cofi[i].Name() < cofi[j].Name()
  81. }
  82. func (ofi orderedFeaturedItems) Swap(i, j int) {
  83. cofi := []FeaturedPipelineItem(ofi)
  84. cofi[i], cofi[j] = cofi[j], cofi[i]
  85. }
  86. func (registry *PipelineItemRegistry) GetFeaturedItems() map[string][]FeaturedPipelineItem {
  87. features := map[string][]FeaturedPipelineItem{}
  88. for _, t := range registry.registered {
  89. if fiface, ok := reflect.New(t.Elem()).Interface().(FeaturedPipelineItem); ok {
  90. for _, f := range fiface.Features() {
  91. list := features[f]
  92. if list == nil {
  93. list = []FeaturedPipelineItem{}
  94. }
  95. list = append(list, fiface)
  96. features[f] = list
  97. }
  98. }
  99. }
  100. for _, vals := range features {
  101. sort.Sort(orderedFeaturedItems(vals))
  102. }
  103. return features
  104. }
  105. type arrayFeatureFlags struct {
  106. // Flags containts the features activated through the command line.
  107. Flags []string
  108. // Choices contains all registered features.
  109. Choices map[string]bool
  110. }
  111. func (acf *arrayFeatureFlags) String() string {
  112. return strings.Join([]string(acf.Flags), ", ")
  113. }
  114. func (acf *arrayFeatureFlags) Set(value string) error {
  115. if _, exists := acf.Choices[value]; !exists {
  116. return errors.New(fmt.Sprintf("Feature \"%s\" is not registered.", value))
  117. }
  118. acf.Flags = append(acf.Flags, value)
  119. return nil
  120. }
  121. func (acf *arrayFeatureFlags) Type() string {
  122. return "string"
  123. }
  124. var featureFlags = arrayFeatureFlags{Flags: []string{}, Choices: map[string]bool{}}
  125. // AddFlags inserts the cmdline options from PipelineItem.ListConfigurationOptions(),
  126. // FeaturedPipelineItem().Features() and LeafPipelineItem.Flag() into the global "flag" parser
  127. // built into the Go runtime.
  128. // Returns the "facts" which can be fed into PipelineItem.Configure() and the dictionary of
  129. // runnable analysis (LeafPipelineItem) choices. E.g. if "BurndownAnalysis" was activated
  130. // through "-burndown" cmdline argument, this mapping would contain ["BurndownAnalysis"] = *true.
  131. func (registry *PipelineItemRegistry) AddFlags(flagSet *pflag.FlagSet) (
  132. map[string]interface{}, map[string]*bool) {
  133. flags := map[string]interface{}{}
  134. deployed := map[string]*bool{}
  135. for name, it := range registry.registered {
  136. formatHelp := func(desc string) string {
  137. return fmt.Sprintf("%s [%s]", desc, name)
  138. }
  139. itemIface := reflect.New(it.Elem()).Interface()
  140. for _, opt := range itemIface.(PipelineItem).ListConfigurationOptions() {
  141. var iface interface{}
  142. switch opt.Type {
  143. case BoolConfigurationOption:
  144. iface = interface{}(true)
  145. ptr := (**bool)(unsafe.Pointer(uintptr(unsafe.Pointer(&iface)) + unsafe.Sizeof(&iface)))
  146. *ptr = flagSet.Bool(opt.Flag, opt.Default.(bool), formatHelp(opt.Description))
  147. case IntConfigurationOption:
  148. iface = interface{}(0)
  149. ptr := (**int)(unsafe.Pointer(uintptr(unsafe.Pointer(&iface)) + unsafe.Sizeof(&iface)))
  150. *ptr = flagSet.Int(opt.Flag, opt.Default.(int), formatHelp(opt.Description))
  151. case StringConfigurationOption:
  152. iface = interface{}("")
  153. ptr := (**string)(unsafe.Pointer(uintptr(unsafe.Pointer(&iface)) + unsafe.Sizeof(&iface)))
  154. *ptr = flagSet.String(opt.Flag, opt.Default.(string), formatHelp(opt.Description))
  155. }
  156. flags[opt.Name] = iface
  157. }
  158. if fpi, ok := itemIface.(FeaturedPipelineItem); ok {
  159. for _, f := range fpi.Features() {
  160. featureFlags.Choices[f] = true
  161. }
  162. }
  163. if fpi, ok := itemIface.(LeafPipelineItem); ok {
  164. deployed[fpi.Name()] = flagSet.Bool(
  165. fpi.Flag(), false, fmt.Sprintf("Runs %s analysis.", fpi.Name()))
  166. }
  167. }
  168. {
  169. // Pipeline flags
  170. iface := interface{}("")
  171. ptr1 := (**string)(unsafe.Pointer(uintptr(unsafe.Pointer(&iface)) + unsafe.Sizeof(&iface)))
  172. *ptr1 = flagSet.String("dump-dag", "", "Write the pipeline DAG to a Graphviz file.")
  173. flags[ConfigPipelineDumpPath] = iface
  174. iface = interface{}(true)
  175. ptr2 := (**bool)(unsafe.Pointer(uintptr(unsafe.Pointer(&iface)) + unsafe.Sizeof(&iface)))
  176. *ptr2 = flagSet.Bool("dry-run", false, "Do not run any analyses - only resolve the DAG. "+
  177. "Useful for -dump-dag.")
  178. flags[ConfigPipelineDryRun] = iface
  179. }
  180. features := []string{}
  181. for f := range featureFlags.Choices {
  182. features = append(features, f)
  183. }
  184. flagSet.Var(&featureFlags, "feature",
  185. fmt.Sprintf("Enables the items which depend on the specified features. Can be specified "+
  186. "multiple times. Available features: [%s] (see --feature below).",
  187. strings.Join(features, ", ")))
  188. return flags, deployed
  189. }
  190. // Registry contains all known pipeline item types.
  191. var Registry = &PipelineItemRegistry{
  192. provided: map[string][]reflect.Type{},
  193. registered: map[string]reflect.Type{},
  194. flags: map[string]reflect.Type{},
  195. }