combine.go 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  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. var errs []string
  73. fi, err := os.Stat(fileName)
  74. if err != nil {
  75. errs = append(errs, "Cannot access "+fileName+": "+err.Error())
  76. return nil, nil, errs
  77. }
  78. if fi.Size() == 0 {
  79. errs = append(errs, "Cannot parse "+fileName+": file size is 0")
  80. return nil, nil, errs
  81. }
  82. buffer, err := ioutil.ReadFile(fileName)
  83. if err != nil {
  84. errs = append(errs, "Cannot read "+fileName+": "+err.Error())
  85. return nil, nil, errs
  86. }
  87. message := pb.AnalysisResults{}
  88. err = proto.Unmarshal(buffer, &message)
  89. if err != nil {
  90. errs = append(errs, "Cannot parse "+fileName+": "+err.Error())
  91. return nil, nil, errs
  92. }
  93. if message.Header == nil {
  94. errs = append(errs, "Cannot parse "+fileName+": corrupted header")
  95. return nil, nil, errs
  96. }
  97. *repos = append(*repos, message.Header.Repository)
  98. results := map[string]interface{}{}
  99. for key, val := range message.Contents {
  100. summoned := hercules.Registry.Summon(key)
  101. if len(summoned) == 0 {
  102. errs = append(errs, fileName+": item not found: "+key)
  103. continue
  104. }
  105. mpi, ok := summoned[0].(hercules.ResultMergeablePipelineItem)
  106. if !ok {
  107. errs = append(errs, fileName+": "+key+": ResultMergeablePipelineItem is not implemented")
  108. continue
  109. }
  110. msg, err := mpi.Deserialize(val)
  111. if err != nil {
  112. errs = append(errs, fileName+": deserialization failed: "+key+": "+err.Error())
  113. continue
  114. }
  115. results[key] = msg
  116. }
  117. return results, hercules.MetadataToCommonAnalysisResult(message.Header), errs
  118. }
  119. func printErrors(allErrors map[string][]string) {
  120. needToPrintErrors := false
  121. for _, errs := range allErrors {
  122. if len(errs) > 0 {
  123. needToPrintErrors = true
  124. break
  125. }
  126. }
  127. if !needToPrintErrors {
  128. return
  129. }
  130. fmt.Fprintln(os.Stderr, "Errors:")
  131. for key, errs := range allErrors {
  132. if len(errs) > 0 {
  133. fmt.Fprintln(os.Stderr, " "+key)
  134. for _, err := range errs {
  135. fmt.Fprintln(os.Stderr, " "+err)
  136. }
  137. }
  138. }
  139. }
  140. func mergeResults(mergedResults map[string]interface{},
  141. mergedCommons *hercules.CommonAnalysisResult,
  142. anotherResults map[string]interface{},
  143. anotherCommons *hercules.CommonAnalysisResult) {
  144. for key, val := range anotherResults {
  145. mergedResult, exists := mergedResults[key]
  146. if !exists {
  147. mergedResults[key] = val
  148. continue
  149. }
  150. item := hercules.Registry.Summon(key)[0].(hercules.ResultMergeablePipelineItem)
  151. mergedResult = item.MergeResults(mergedResult, val, mergedCommons, anotherCommons)
  152. mergedResults[key] = mergedResult
  153. }
  154. if mergedCommons.CommitsNumber == 0 {
  155. *mergedCommons = *anotherCommons
  156. } else {
  157. mergedCommons.Merge(anotherCommons)
  158. }
  159. }
  160. func init() {
  161. rootCmd.AddCommand(combineCmd)
  162. combineCmd.SetUsageFunc(combineCmd.UsageFunc())
  163. }