combine.go 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  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. progress "gopkg.in/cheggaaa/pb.v1"
  14. "gopkg.in/src-d/hercules.v7"
  15. "gopkg.in/src-d/hercules.v7/internal/pb"
  16. )
  17. // combineCmd represents the combine command
  18. var combineCmd = &cobra.Command{
  19. Use: "combine",
  20. Short: "Merge several binary analysis results together.",
  21. Long: ``,
  22. Args: cobra.MinimumNArgs(1),
  23. Run: func(cmd *cobra.Command, files []string) {
  24. if len(files) == 1 {
  25. file, err := os.Open(files[0])
  26. if err != nil {
  27. panic(err)
  28. }
  29. defer file.Close()
  30. io.Copy(os.Stdout, bufio.NewReader(file))
  31. return
  32. }
  33. var repos []string
  34. allErrors := map[string][]string{}
  35. mergedResults := map[string]interface{}{}
  36. mergedMetadata := &hercules.CommonAnalysisResult{}
  37. var fileName string
  38. bar := progress.New(len(files))
  39. bar.Callback = func(msg string) {
  40. os.Stderr.WriteString("\033[2K\r" + msg + " " + fileName)
  41. }
  42. bar.NotPrint = true
  43. bar.ShowPercent = false
  44. bar.ShowSpeed = false
  45. bar.SetMaxWidth(80).Start()
  46. for _, fileName = range files {
  47. bar.Increment()
  48. anotherResults, anotherMetadata, errs := loadMessage(fileName, &repos)
  49. if anotherMetadata != nil {
  50. mergeResults(mergedResults, mergedMetadata, anotherResults, anotherMetadata)
  51. }
  52. allErrors[fileName] = errs
  53. }
  54. bar.Finish()
  55. printErrors(allErrors)
  56. sort.Strings(repos)
  57. if mergedMetadata == nil {
  58. return
  59. }
  60. mergedMessage := pb.AnalysisResults{
  61. Header: &pb.Metadata{
  62. Version: int32(hercules.BinaryVersion),
  63. Hash: hercules.BinaryGitHash,
  64. Repository: strings.Join(repos, " & "),
  65. },
  66. Contents: map[string][]byte{},
  67. }
  68. mergedMetadata.FillMetadata(mergedMessage.Header)
  69. for key, val := range mergedResults {
  70. buffer := bytes.Buffer{}
  71. err := hercules.Registry.Summon(key)[0].(hercules.LeafPipelineItem).Serialize(
  72. val, true, &buffer)
  73. if err != nil {
  74. panic(err)
  75. }
  76. mergedMessage.Contents[key] = buffer.Bytes()
  77. }
  78. serialized, err := proto.Marshal(&mergedMessage)
  79. if err != nil {
  80. panic(err)
  81. }
  82. os.Stdout.Write(serialized)
  83. },
  84. }
  85. func loadMessage(fileName string, repos *[]string) (
  86. map[string]interface{}, *hercules.CommonAnalysisResult, []string) {
  87. var errs []string
  88. fi, err := os.Stat(fileName)
  89. if err != nil {
  90. errs = append(errs, "Cannot access "+fileName+": "+err.Error())
  91. return nil, nil, errs
  92. }
  93. if fi.Size() == 0 {
  94. errs = append(errs, "Cannot parse "+fileName+": file size is 0")
  95. return nil, nil, errs
  96. }
  97. buffer, err := ioutil.ReadFile(fileName)
  98. if err != nil {
  99. errs = append(errs, "Cannot read "+fileName+": "+err.Error())
  100. return nil, nil, errs
  101. }
  102. message := pb.AnalysisResults{}
  103. err = proto.Unmarshal(buffer, &message)
  104. if err != nil {
  105. errs = append(errs, "Cannot parse "+fileName+": "+err.Error())
  106. return nil, nil, errs
  107. }
  108. if message.Header == nil {
  109. errs = append(errs, "Cannot parse "+fileName+": corrupted header")
  110. return nil, nil, errs
  111. }
  112. *repos = append(*repos, message.Header.Repository)
  113. results := map[string]interface{}{}
  114. for key, val := range message.Contents {
  115. summoned := hercules.Registry.Summon(key)
  116. if len(summoned) == 0 {
  117. errs = append(errs, fileName+": item not found: "+key)
  118. continue
  119. }
  120. mpi, ok := summoned[0].(hercules.ResultMergeablePipelineItem)
  121. if !ok {
  122. errs = append(errs, fileName+": "+key+": ResultMergeablePipelineItem is not implemented")
  123. continue
  124. }
  125. msg, err := mpi.Deserialize(val)
  126. if err != nil {
  127. errs = append(errs, fileName+": deserialization failed: "+key+": "+err.Error())
  128. continue
  129. }
  130. results[key] = msg
  131. }
  132. return results, hercules.MetadataToCommonAnalysisResult(message.Header), errs
  133. }
  134. func printErrors(allErrors map[string][]string) {
  135. needToPrintErrors := false
  136. for _, errs := range allErrors {
  137. if len(errs) > 0 {
  138. needToPrintErrors = true
  139. break
  140. }
  141. }
  142. if !needToPrintErrors {
  143. return
  144. }
  145. fmt.Fprintln(os.Stderr, "Errors:")
  146. for key, errs := range allErrors {
  147. if len(errs) > 0 {
  148. fmt.Fprintln(os.Stderr, " "+key)
  149. for _, err := range errs {
  150. fmt.Fprintln(os.Stderr, " "+err)
  151. }
  152. }
  153. }
  154. }
  155. func mergeResults(mergedResults map[string]interface{},
  156. mergedCommons *hercules.CommonAnalysisResult,
  157. anotherResults map[string]interface{},
  158. anotherCommons *hercules.CommonAnalysisResult) {
  159. for key, val := range anotherResults {
  160. mergedResult, exists := mergedResults[key]
  161. if !exists {
  162. mergedResults[key] = val
  163. continue
  164. }
  165. item := hercules.Registry.Summon(key)[0].(hercules.ResultMergeablePipelineItem)
  166. mergedResult = item.MergeResults(mergedResult, val, mergedCommons, anotherCommons)
  167. mergedResults[key] = mergedResult
  168. }
  169. if mergedCommons.CommitsNumber == 0 {
  170. *mergedCommons = *anotherCommons
  171. } else {
  172. mergedCommons.Merge(anotherCommons)
  173. }
  174. }
  175. func init() {
  176. rootCmd.AddCommand(combineCmd)
  177. combineCmd.SetUsageFunc(combineCmd.UsageFunc())
  178. }