combine.go 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. package main
  2. import (
  3. "bufio"
  4. "bytes"
  5. "fmt"
  6. "io"
  7. "io/ioutil"
  8. "os"
  9. "sort"
  10. "strings"
  11. "github.com/gogo/protobuf/proto"
  12. "github.com/spf13/cobra"
  13. "gopkg.in/src-d/hercules.v7"
  14. "gopkg.in/src-d/hercules.v7/internal/pb"
  15. )
  16. // combineCmd represents the combine command
  17. var combineCmd = &cobra.Command{
  18. Use: "combine",
  19. Short: "Merge several binary analysis results together.",
  20. Long: ``,
  21. Args: cobra.MinimumNArgs(1),
  22. Run: func(cmd *cobra.Command, files []string) {
  23. if len(files) == 1 {
  24. file, err := os.Open(files[0])
  25. if err != nil {
  26. panic(err)
  27. }
  28. defer file.Close()
  29. io.Copy(os.Stdout, bufio.NewReader(file))
  30. return
  31. }
  32. repos := []string{}
  33. allErrors := map[string][]string{}
  34. mergedResults := map[string]interface{}{}
  35. mergedMetadata := &hercules.CommonAnalysisResult{}
  36. for _, fileName := range files {
  37. anotherResults, anotherMetadata, errs := loadMessage(fileName, &repos)
  38. if anotherMetadata != nil {
  39. mergeResults(mergedResults, mergedMetadata, anotherResults, anotherMetadata)
  40. }
  41. allErrors[fileName] = errs
  42. }
  43. printErrors(allErrors)
  44. sort.Strings(repos)
  45. if mergedMetadata == nil {
  46. return
  47. }
  48. mergedMessage := pb.AnalysisResults{
  49. Header: &pb.Metadata{
  50. Version: int32(hercules.BinaryVersion),
  51. Hash: hercules.BinaryGitHash,
  52. Repository: strings.Join(repos, " & "),
  53. },
  54. Contents: map[string][]byte{},
  55. }
  56. mergedMetadata.FillMetadata(mergedMessage.Header)
  57. for key, val := range mergedResults {
  58. buffer := bytes.Buffer{}
  59. hercules.Registry.Summon(key)[0].(hercules.LeafPipelineItem).Serialize(
  60. val, true, &buffer)
  61. mergedMessage.Contents[key] = buffer.Bytes()
  62. }
  63. serialized, err := proto.Marshal(&mergedMessage)
  64. if err != nil {
  65. panic(err)
  66. }
  67. os.Stdout.Write(serialized)
  68. },
  69. }
  70. func loadMessage(fileName string, repos *[]string) (
  71. map[string]interface{}, *hercules.CommonAnalysisResult, []string) {
  72. errs := []string{}
  73. buffer, err := ioutil.ReadFile(fileName)
  74. if err != nil {
  75. errs = append(errs, "Cannot read "+fileName+": "+err.Error())
  76. return nil, nil, errs
  77. }
  78. message := pb.AnalysisResults{}
  79. err = proto.Unmarshal(buffer, &message)
  80. if err != nil {
  81. errs = append(errs, "Cannot parse "+fileName+": "+err.Error())
  82. return nil, nil, errs
  83. }
  84. *repos = append(*repos, message.Header.Repository)
  85. results := map[string]interface{}{}
  86. for key, val := range message.Contents {
  87. summoned := hercules.Registry.Summon(key)
  88. if len(summoned) == 0 {
  89. errs = append(errs, fileName+": item not found: "+key)
  90. continue
  91. }
  92. mpi, ok := summoned[0].(hercules.ResultMergeablePipelineItem)
  93. if !ok {
  94. errs = append(errs, fileName+": "+key+": ResultMergeablePipelineItem is not implemented")
  95. continue
  96. }
  97. msg, err := mpi.Deserialize(val)
  98. if err != nil {
  99. errs = append(errs, fileName+": deserialization failed: "+key+": "+err.Error())
  100. continue
  101. }
  102. results[key] = msg
  103. }
  104. return results, hercules.MetadataToCommonAnalysisResult(message.Header), errs
  105. }
  106. func printErrors(allErrors map[string][]string) {
  107. needToPrintErrors := false
  108. for _, errs := range allErrors {
  109. if len(errs) > 0 {
  110. needToPrintErrors = true
  111. break
  112. }
  113. }
  114. if !needToPrintErrors {
  115. return
  116. }
  117. fmt.Fprintln(os.Stderr, "Errors:")
  118. for key, errs := range allErrors {
  119. if len(errs) > 0 {
  120. fmt.Fprintln(os.Stderr, " "+key)
  121. for _, err := range errs {
  122. fmt.Fprintln(os.Stderr, " "+err)
  123. }
  124. }
  125. }
  126. }
  127. func mergeResults(mergedResults map[string]interface{},
  128. mergedCommons *hercules.CommonAnalysisResult,
  129. anotherResults map[string]interface{},
  130. anotherCommons *hercules.CommonAnalysisResult) {
  131. for key, val := range anotherResults {
  132. mergedResult, exists := mergedResults[key]
  133. if !exists {
  134. mergedResults[key] = val
  135. continue
  136. }
  137. item := hercules.Registry.Summon(key)[0].(hercules.ResultMergeablePipelineItem)
  138. mergedResult = item.MergeResults(mergedResult, val, mergedCommons, anotherCommons)
  139. mergedResults[key] = mergedResult
  140. }
  141. if mergedCommons.CommitsNumber == 0 {
  142. *mergedCommons = *anotherCommons
  143. } else {
  144. mergedCommons.Merge(anotherCommons)
  145. }
  146. }
  147. func init() {
  148. rootCmd.AddCommand(combineCmd)
  149. combineCmd.SetUsageFunc(combineCmd.UsageFunc())
  150. }