Sfoglia il codice sorgente

Merge pull request #24 from vmarkovtsev/master

Add serialization support to plugin gen
Vadim Markovtsev 7 anni fa
parent
commit
2d3198c7a5
4 ha cambiato i file con 137 aggiunte e 15 eliminazioni
  1. 4 1
      .travis.yml
  2. 21 0
      PLUGINS.md
  3. 64 10
      cmd/hercules-generate-plugin/main.go
  4. 48 4
      cmd/hercules-generate-plugin/plugin.template

+ 4 - 1
.travis.yml

@@ -21,6 +21,9 @@ go:
 go_import_path: gopkg.in/src-d/hercules.v3
 
 before_install:
+  - wget -O protoc.zip https://github.com/google/protobuf/releases/download/v3.5.0/protoc-3.5.0-linux-x86_64.zip
+  - unzip -d ~/.local protoc.zip && rm protoc.zip
+  - go get -v github.com/gogo/protobuf/protoc-gen-gogo
   - wget http://mirrors.kernel.org/ubuntu/pool/main/m/make-dfsg/make_4.1-9.1_amd64.deb
   - dpkg -x make_4.1-9.1_amd64.deb ~ && rm make_4.1-9.1_amd64.deb
   - wget https://bootstrap.pypa.io/get-pip.py && python3 get-pip.py --user && rm get-pip.py
@@ -42,7 +45,7 @@ script:
   - $GOPATH/bin/hercules -burndown -burndown-files -burndown-people -couples -quiet https://github.com/src-d/hercules | python3 labours.py -m all -o out --backend Agg --disable-projector
   - $GOPATH/bin/hercules -burndown -burndown-files -burndown-people -couples -quiet -pb https://github.com/src-d/hercules | python3 labours.py -f pb -m all -o out --backend Agg --disable-projector
   - $GOPATH/bin/hercules-generate-plugin -version
-  - $GOPATH/bin/hercules-generate-plugin -n MyTest && test -e my_test.go
+  - $GOPATH/bin/hercules-generate-plugin -n MyPlug -o myplug && cd myplug && make
 
 after_success:
   - bash <(curl -s https://codecov.io/bash)

+ 21 - 0
PLUGINS.md

@@ -0,0 +1,21 @@
+Hercules plugins
+================
+
+Generate a new plugin skeleton:
+
+```
+hercules-generate-plugin -n MyPluginName -o my_plugin
+```
+
+Compile:
+
+```
+cd my_plugin
+make
+```
+
+Use:
+
+```
+hercules -plugin my_plugin_name.so -my-plugin-name https://github.com/user/repo
+```

+ 64 - 10
cmd/hercules-generate-plugin/main.go

@@ -1,9 +1,12 @@
 package main
 
 import (
+  "bytes"
   "flag"
   "fmt"
+  "io/ioutil"
   "os"
+  "os/exec"
   "path"
   "runtime"
   "strings"
@@ -23,16 +26,16 @@ var SHLIB_EXT = map[string]string {
 }
 
 func main() {
-  var outputPath, name, varname, _flag, pkg string
-  var printVersion bool
+  var outputDir, name, varname, _flag, pkg string
+  var printVersion, disableMakefile bool
   flag.StringVar(&name, "n", "", "Name of the plugin, CamelCase. Required.")
-  flag.StringVar(&outputPath, "o", "", "Output path of the generated plugin code. If not " +
-      "specified, inferred from -n.")
+  flag.StringVar(&outputDir, "o", ".", "Output directory for the generated plugin files.")
   flag.StringVar(&varname, "varname", "", "Name of the plugin instance variable, If not " +
       "specified, inferred from -n.")
   flag.StringVar(&_flag, "flag", "", "Name of the plugin activation cmdline flag, If not " +
       "specified, inferred from -varname.")
   flag.BoolVar(&printVersion, "version", false, "Print version information and exit.")
+  flag.BoolVar(&disableMakefile, "no-makefile", false, "Do not generate the Makefile.")
   flag.StringVar(&pkg, "package", "main", "Name of the package.")
   flag.Parse()
   if printVersion {
@@ -45,11 +48,11 @@ func main() {
     os.Exit(1)
   }
   splitted := camelcase.Split(name)
-  if outputPath == "" {
-    outputPath = strings.ToLower(strings.Join(splitted, "_")) + ".go"
-  } else if !strings.HasSuffix(outputPath, ".go") {
-    panic("-o must end with \".go\"")
+  err := os.MkdirAll(outputDir, os.ModePerm)
+  if err != nil {
+    panic(err)
   }
+  outputPath := path.Join(outputDir, strings.ToLower(strings.Join(splitted, "_")) + ".go")
   gen := template.Must(template.New("plugin").Parse(PLUGIN_TEMPLATE_SOURCE))
   outFile, err := os.Create(outputPath)
   if err != nil {
@@ -60,15 +63,66 @@ func main() {
     varname = strings.ToLower(splitted[0])
   }
   if _flag == "" {
-    _flag = strings.Join(splitted, "-")
+    _flag = strings.ToLower(strings.Join(splitted, "-"))
   }
   outputBase := path.Base(outputPath)
   shlib := outputBase[:len(outputBase)-2] + SHLIB_EXT[runtime.GOOS]
+  protoBuf := outputPath[:len(outputPath)-3] + ".proto"
+  pbGo := outputPath[:len(outputPath)-3] + ".pb.go"
   dict := map[string]string{
     "name": name, "varname": varname, "flag": _flag, "package": pkg,
-    "output": outputPath, "shlib": shlib}
+    "output": outputPath, "shlib": shlib, "proto": protoBuf, "protogo": pbGo,
+    "outdir": outputDir}
   err = gen.Execute(outFile, dict)
   if err != nil {
     panic(err)
   }
+  // write pb file
+  ioutil.WriteFile(protoBuf, []byte(fmt.Sprintf(`syntax = "proto3";
+option go_package = "%s";
+
+message %sResultMessage {
+  // add fields here
+  // reference: https://developers.google.com/protocol-buffers/docs/proto3
+  // example: pb/pb.proto https://github.com/src-d/hercules/blob/master/pb/pb.proto
+}`, pkg, name)), 0666)
+  // generate the pb Go file
+  protoc, err := exec.LookPath("protoc")
+  args := [...]string{
+    protoc,
+    "--gogo_out=" + outputDir,
+    "--proto_path=" + outputDir,
+    protoBuf,
+  }
+  env := os.Environ()
+  env = append(env, fmt.Sprintf(
+    "PATH=%s:%s", os.Getenv("PATH"), path.Join(os.Getenv("GOPATH"), "bin")))
+  if err != nil {
+    panic("protoc was not found at " + env[len(env)-1])
+  }
+  cmd := exec.Cmd{Path: protoc, Args: args[:], Env: env, Stdout: os.Stdout, Stderr: os.Stderr}
+  err = cmd.Run()
+  if err != nil {
+    panic(err)
+  }
+  if !disableMakefile {
+    makefile := path.Join(outputDir, "Makefile")
+    gen = template.Must(template.New("plugin").Parse(`all: {{.shlib}}
+
+{{.shlib}}: {{.output}} {{.protogo}}
+` + "\t" + `go build -buildmode=plugin {{.output}} {{.protogo}}
+
+{{.protogo}}:
+` + "\t" + `PATH=$PATH:$GOPATH/bin protoc --gogo_out=. --proto_path=. {{.proto}}
+`))
+    buffer := new(bytes.Buffer)
+    mkrelative := func(name string) {
+      dict[name] = path.Base(dict[name])
+    }
+    mkrelative("output")
+    mkrelative("protogo")
+    mkrelative("proto")
+    gen.Execute(buffer, dict)
+    ioutil.WriteFile(makefile, buffer.Bytes(), 0666)
+  }
 }

+ 48 - 4
cmd/hercules-generate-plugin/plugin.template

@@ -1,11 +1,27 @@
-// Hercules plugin
-// How to build: go build -buildmode=plugin {{.output}}
-// This command creates ./{{.shlib}}
-// Usage: hercules -plugin {{.shlib}} -{{.flag}}
+// Hercules plugin "{{.name}}"
+//
+// How to build: execute "make" *OR*
+//
+// 1. Update the Protocol Buffers definition as needed, regenerate {{.protogo}}
+//
+//     PATH=$PATH:$GOPATH/bin protoc --gogo_out={{.outdir}} --proto_path={{.outdir}} {{.proto}}
+//
+// 2. Build {{.shlib}}
+//
+//     go build -buildmode=plugin {{.output}} {{.protogo}}
+//
+// Step (1) requires GoGo Protobuf https://github.com/gogo/protobuf
+//
+// Usage:
+//
+//    hercules -plugin {{.shlib}} -{{.flag}}
 
 package {{.package}}
 
 import (
+  "io"
+
+  "github.com/gogo/protobuf/proto"
   "gopkg.in/src-d/go-git.v4"
   "gopkg.in/src-d/hercules.v3"
 )
@@ -61,16 +77,44 @@ func ({{.varname}} *{{.name}}) Configure(facts map[string]interface{}) {
 func ({{.varname}} *{{.name}}) Initialize(repository *git.Repository) {
 }
 
+// Consume is called for every commit in the sequence.
 func ({{.varname}} *{{.name}}) Consume(deps map[string]interface{}) (map[string]interface{}, error) {
   return nil, nil
 }
 
+// Finalize produces the result of the analysis. No more Consume() calls are expected afterwards.
 func ({{.varname}} *{{.name}}) Finalize() interface{} {
   result := {{.name}}Result{}
   // insert code here
   return result
 }
 
+// Serialize converts the result from Finalize() to either Protocol Buffers or YAML.
+func ({{.varname}} *{{.name}}) Serialize(result interface{}, binary bool, writer io.Writer) error {
+  {{.varname}}Result := result.({{.name}}Result)
+  if binary {
+    return {{.varname}}.serializeBinary(&{{.varname}}Result, writer)
+  }
+  {{.varname}}.serializeText(&{{.varname}}Result, writer)
+  return nil
+}
+
+func ({{.varname}} *{{.name}}) serializeText(result *{{.name}}Result, writer io.Writer) {
+  // write YAML to writer
+}
+
+func ({{.varname}} *{{.name}}) serializeBinary(result *{{.name}}Result, writer io.Writer) error {
+  message := {{.name}}ResultMessage{
+    // fill me
+  }
+  serialized, err := proto.Marshal(&message)
+  if err != nil {
+    return err
+  }
+  writer.Write(serialized)
+  return nil
+}
+
 func init() {
   hercules.Registry.Register(&{{.name}}{})
 }