Add support for specifying output .pb.go package

Add flag -pbout which allows the user to specify a go package
for the .pb.go files to be written to.
This commit is contained in:
Adam Ryman 2016-10-05 13:58:23 -07:00
Родитель a26f615ecf
Коммит 3ec08eaa64
16 изменённых файлов: 188 добавлений и 133 удалений

Просмотреть файл

@ -4,7 +4,7 @@
1. Everything required to install `truss`
2. go-bindata for compiling templates into binary `$ go get
github.com/jteeuwen/go-bindata`
github.com/jteeuwen/go-bindata/...`
## Building
@ -15,7 +15,7 @@ this is done with:
$ go generate github.com/TuneLab/go-truss/...
```
Then to build truss and it's two protoc plugins to your $GOPATH/bin directory:
Then to build truss and its protoc plugin to your $GOPATH/bin directory:
```
$ go install github.com/TuneLab/go-truss/...
@ -66,13 +66,3 @@ Additional internal packages of note used by these programs are:
- `deftree`, located in `deftree/`, which makes sense of the protobuf file
passed to it by `protoc`, and is used by `gengokit` and
`gendoc`
## Docker
BETA
To build the docker image `$ docker build -t tunelab/gob/truss .`
To use the docker image as `truss` on .proto files `$ docker run -it --rm
--name test -v $PWD:/gopath/src/microservice -w /gopath/src/microservice
tunelab/gob/truss *.proto`

Просмотреть файл

@ -36,6 +36,8 @@ func init() {
type templateExecutor struct {
// import path for the directory containing the definition .proto files
ImportPath string
// import path for .pb.go files containing service structs
PBImportPath string
// PackageName is the name of the package containing the service definition
PackageName string
// GRPC/Protobuff service, with all parameters and return values accessible
@ -48,13 +50,12 @@ type templateExecutor struct {
// newTemplateExecutor accepts a deftree and a goImportPath to construct a
// templateExecutor for templating a service
func newTemplateExecutor(dt deftree.Deftree, goImportPath string) (*templateExecutor, error) {
func newTemplateExecutor(dt deftree.Deftree, goImportPath, goPBImportPath string) (*templateExecutor, error) {
service, err := getProtoService(dt)
if err != nil {
return nil, errors.Wrap(err, "no service found; aborting generating gokit service")
}
importPath := goImportPath
funcMap := template.FuncMap{
"ToLower": strings.ToLower,
"Title": strings.Title,
@ -62,12 +63,13 @@ func newTemplateExecutor(dt deftree.Deftree, goImportPath string) (*templateExec
"TrimPrefix": strings.TrimPrefix,
}
return &templateExecutor{
ImportPath: importPath,
PackageName: dt.GetName(),
Service: service,
ClientArgs: clientarggen.New(service),
HTTPHelper: httptransport.NewHelper(service),
funcMap: funcMap,
ImportPath: goImportPath,
PBImportPath: goPBImportPath,
PackageName: dt.GetName(),
Service: service,
ClientArgs: clientarggen.New(service),
HTTPHelper: httptransport.NewHelper(service),
funcMap: funcMap,
}, nil
}
@ -77,8 +79,8 @@ func newTemplateExecutor(dt deftree.Deftree, goImportPath string) (*templateExec
// templating go code imports. GenerateGokit returns the a
// []truss.NamedReadWriter representing a generated gokit service file
// structure
func GenerateGokit(dt deftree.Deftree, previousFiles []truss.NamedReadWriter, goImportPath string) ([]truss.NamedReadWriter, error) {
te, err := newTemplateExecutor(dt, goImportPath)
func GenerateGokit(dt deftree.Deftree, previousFiles []truss.NamedReadWriter, goImportPath, goPBImportPath string) ([]truss.NamedReadWriter, error) {
te, err := newTemplateExecutor(dt, goImportPath, goPBImportPath)
if err != nil {
return nil, errors.Wrap(err, "could not create template executor")
}

Просмотреть файл

@ -71,7 +71,7 @@ func TestNewTemplateExecutor(t *testing.T) {
const goImportPath = "github.com/TuneLab/go-truss/gengokit/general-service"
te, err := newTemplateExecutor(dt, goImportPath)
te, err := newTemplateExecutor(dt, goImportPath, goImportPath)
if err != nil {
t.Fatal(err)
}
@ -168,7 +168,7 @@ func TestApplyTemplateFromPath(t *testing.T) {
t.Fatal(err)
}
te, err := newTemplateExecutor(dt, goImportPath)
te, err := newTemplateExecutor(dt, goImportPath, goImportPath)
if err != nil {
t.Fatal(err)
}
@ -293,7 +293,7 @@ func stringToTemplateExector(def, importPath string) (*templateExecutor, error)
return nil, err
}
te, err := newTemplateExecutor(dt, importPath)
te, err := newTemplateExecutor(dt, importPath, importPath)
if err != nil {
return nil, err
}
@ -339,7 +339,7 @@ func TestUpdateServerMethods(t *testing.T) {
t.Fatal(err)
}
te, err := newTemplateExecutor(dt, goImportPath)
te, err := newTemplateExecutor(dt, goImportPath, goImportPath)
if err != nil {
t.Fatal(err)
}
@ -462,7 +462,7 @@ func TestAllTemplates(t *testing.T) {
t.Fatal(err)
}
te, err := newTemplateExecutor(dt, goImportPath)
te, err := newTemplateExecutor(dt, goImportPath, goImportPath)
if err != nil {
t.Fatal(err)
}
@ -472,7 +472,7 @@ func TestAllTemplates(t *testing.T) {
t.Fatal(err)
}
te2, err := newTemplateExecutor(dt2, goImportPath)
te2, err := newTemplateExecutor(dt2, goImportPath, goImportPath)
if err != nil {
t.Fatal(err)
}

Просмотреть файл

@ -27,7 +27,7 @@ import (
clientHandler "{{$templateExecutor.ImportPath -}} /handlers/client"
grpcclient "{{$templateExecutor.ImportPath -}} /generated/client/grpc"
httpclient "{{$templateExecutor.ImportPath -}} /generated/client/http"
pb "{{$templateExecutor.ImportPath -}}"
pb "{{$templateExecutor.PBImportPath -}}"
)
var (

Просмотреть файл

@ -33,7 +33,7 @@ import (
// This Service
handler "{{$templateExecutor.ImportPath -}} /handlers/server"
svc "{{$templateExecutor.ImportPath -}} /generated"
pb "{{$templateExecutor.ImportPath -}}"
pb "{{$templateExecutor.PBImportPath -}}"
)

Просмотреть файл

@ -20,7 +20,7 @@ import (
// This Service
handler "{{$templateExecutor.ImportPath -}} /handlers/server"
svc "{{$templateExecutor.ImportPath -}} /generated"
pb "{{$templateExecutor.ImportPath -}}"
pb "{{$templateExecutor.PBImportPath -}}"
)
// New returns an AddService backed by a gRPC client connection. It is the

Просмотреть файл

@ -16,7 +16,7 @@ import (
//_ "github.com/go-kit/kit/metrics"
handler "{{.ImportPath -}} /handlers/server"
pb "{{.ImportPath -}}"
pb "{{.PBImportPath -}}"
)
// Endpoints collects all of the endpoints that compose an add service. It's

Просмотреть файл

@ -12,7 +12,7 @@ import (
//_ "github.com/go-kit/kit/log"
//_ "github.com/go-kit/kit/metrics"
//pb "{{.ImportPath -}} "
//pb "{{.PBImportPath -}} "
)
// Middleware describes a service (as opposed to endpoint) middleware.

Просмотреть файл

@ -13,7 +13,7 @@ import (
grpctransport "github.com/go-kit/kit/transport/grpc"
// This Service
pb "{{$templateExecutor.ImportPath -}}"
pb "{{$templateExecutor.PBImportPath -}}"
)

Просмотреть файл

@ -25,7 +25,7 @@ import (
httptransport "github.com/go-kit/kit/transport/http"
// This service
pb "{{$templateExecutor.ImportPath -}}"
pb "{{$templateExecutor.PBImportPath -}}"
)
var (

Просмотреть файл

@ -2,7 +2,7 @@
package clienthandler
import (
pb "{{$templateExecutor.ImportPath -}}"
pb "{{$templateExecutor.PBImportPath -}}"
)

Просмотреть файл

@ -12,7 +12,7 @@ import (
_ "github.com/go-kit/kit/log"
_ "github.com/go-kit/kit/metrics"
pb "{{.ImportPath -}}"
pb "{{.PBImportPath -}}"
)

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Просмотреть файл

@ -35,6 +35,12 @@ func TestBasicTypes(t *testing.T) {
testEndToEnd("1-basic", t)
}
func TestBasicTypesWithPBOutFlag(t *testing.T) {
testEndToEnd("1-basic", t,
"-pbout",
"github.com/TuneLab/go-truss/truss/_integration-tests/cli/test-service-definitions/1-basic/pbout")
}
// Disabled until repeated types are implemented for cliclient
func _TestRepeatedTypes(t *testing.T) {
testEndToEnd("2-repeated", t)
@ -62,13 +68,16 @@ type runReference struct {
serverOutput string
}
func testEndToEnd(defDir string, t *testing.T) {
func testEndToEnd(defDir string, t *testing.T, trussOptions ...string) {
port := 45360
wd, _ := os.Getwd()
fullpath := filepath.Join(wd, definitionDirectory, defDir)
trussOut, err := truss(fullpath)
// Remove tests if they exists
removeTestFiles(fullpath)
trussOut, err := truss(fullpath, trussOptions...)
// If truss fails, test error and skip communication
if err != nil {
@ -78,7 +87,7 @@ func testEndToEnd(defDir string, t *testing.T) {
// Build the service to be tested
err = buildTestService(fullpath)
if err != nil {
t.Fatalf("Could not buld service. Error: %v", err)
t.Fatalf("Could not build service. Error: %v", err)
}
// Run them save a reference to each run
@ -97,7 +106,7 @@ func testEndToEnd(defDir string, t *testing.T) {
// truss calls truss on *.proto in path
// Truss logs to Stdout when generation passes or fails
func truss(path string) (string, error) {
func truss(path string, options ...string) (string, error) {
var protofiles []string
files, err := ioutil.ReadDir(path)
for _, f := range files {
@ -109,9 +118,11 @@ func truss(path string) (string, error) {
}
}
args := append(options, protofiles...)
trussExec := exec.Command(
"truss",
protofiles...,
args...,
)
trussExec.Dir = path
@ -203,16 +214,6 @@ func goBuild(name, outputPath, relCodePath string, errChan chan error) {
errChan <- nil
}
// mkdir acts like $ mkdir -p path
func mkdir(path string) error {
dir := filepath.Dir(path)
// 0775 is the file mode that $ mkdir uses when creating a directoru
err := os.MkdirAll(dir, 0775)
return err
}
// runServerAndClient execs a TEST-server and TEST-client and puts a
// runReference to their interaction on the runRefs channel
func runServerAndClient(path string, port int, debugPort int) runReference {
@ -342,16 +343,23 @@ func cleanTests(servicesDir string) {
if !d.IsDir() {
continue
}
removeTestFiles(servicesDir + "/" + d.Name())
removeTestFiles(filepath.Join(servicesDir, d.Name()))
}
}
// removeTestFiles removes all files created by running truss and building the
// service from a single definition directory
func removeTestFiles(defDir string) {
if fileExists(defDir + "/TEST-service") {
os.RemoveAll(defDir + "/TEST-service")
os.RemoveAll(defDir + "/third_party")
os.RemoveAll(defDir + "/bin")
}
os.RemoveAll(filepath.Join(defDir, "TEST-service"))
os.RemoveAll(filepath.Join(defDir, "bin"))
os.RemoveAll(filepath.Join(defDir, "pbout"))
mkdir(filepath.Join(defDir, "pbout"))
}
// mkdir acts like $ mkdir -p path
func mkdir(path string) error {
// 0775 is the file mode that $ mkdir uses when creating a directoru
err := os.MkdirAll(path, 0775)
return err
}

Просмотреть файл

@ -12,82 +12,136 @@ import (
"github.com/pkg/errors"
"github.com/TuneLab/go-truss/truss/truss"
"github.com/TuneLab/go-truss/truss/protostuff"
"github.com/TuneLab/go-truss/truss/truss"
"github.com/TuneLab/go-truss/deftree"
"github.com/TuneLab/go-truss/gendoc"
"github.com/TuneLab/go-truss/gengokit"
)
var pbOutFlag = flag.String("pbout", "", "The go package path where the protoc-gen-go .pb.go structs will be written.")
func main() {
flag.Parse()
goPath := os.Getenv("GOPATH")
var pbOut string
if *pbOutFlag != "" {
pbOut = filepath.Join(goPath, "src", *pbOutFlag)
if !fileExists(pbOut) {
exitIfError(errors.Errorf("Go package directory does not exist: %q", pbOut))
}
}
if len(flag.Args()) == 0 {
exitIfError(errors.New("no arguments passed"))
flag.Usage()
os.Exit(1)
}
rawDefinitionPaths := flag.Args()
protoDir, definitionFiles, err := cleanProtofilePath(rawDefinitionPaths)
// Check truss is running in $GOPATH
goPath := os.Getenv("GOPATH")
exitIfError(err)
if !strings.HasPrefix(protoDir, goPath) {
exitIfError(errors.New("truss envoked on files outside of $GOPATH"))
}
protocOut, err := protostuff.CodeGeneratorRequest(definitionFiles, protoDir)
dt, err := buildDeftree(definitionFiles, protoDir)
exitIfError(err)
svcFile, err := protostuff.ServiceFile(protocOut, protoDir)
exitIfError(err)
// Make a deftree
dt, err := deftree.New(protocOut, svcFile)
exitIfError(err)
// Generate the .pb.go files containing the golang data structures
// From `$GOPATH/src/org/user/thing` get `org/user/thing` for importing in golang
svcName := dt.GetName() + "-service"
svcDir := filepath.Join(protoDir, svcName)
err = mkdir(svcDir)
exitIfError(err)
err = protostuff.GeneratePBataStructures(definitionFiles, svcDir)
exitIfError(err)
prevGen, err := readPreviousGeneration(protoDir, svcDir)
exitIfError(err)
// generate docs
genDocFiles := gendoc.GenerateDocs(dt)
// generate gokit microservice
goSvcImportPath, err := filepath.Rel(filepath.Join(goPath, "src"), svcDir)
exitIfError(err)
genFiles, err := gengokit.GenerateGokit(dt, prevGen, goSvcImportPath)
err = mkdir(svcDir)
exitIfError(err)
// append files together
genFiles = append(genFiles, genDocFiles...)
// Write files to disk
for _, f := range genFiles {
name := f.Name()
fullPath := filepath.Join(protoDir, name)
err := mkdir(fullPath)
exitIfError(err)
file, err := os.Create(fullPath)
exitIfError(errors.Wrapf(err, "could create file %v", fullPath))
_, err = io.Copy(file, f)
exitIfError(errors.Wrapf(err, "could not write to %v", fullPath))
// If not output directory for the .pb.go files has been selected then put them in the svcDir
if pbOut == "" {
pbOut = svcDir
}
err = protostuff.GeneratePBDotGo(definitionFiles, svcDir, protoDir, pbOut)
exitIfError(err)
// gokit service
goSvcImportPath, goPBImportPath, err := trussGoImports(svcDir, pbOut, goPath)
genGokitFiles, err := gengokit.GenerateGokit(dt, prevGen, goSvcImportPath, goPBImportPath)
exitIfError(err)
for _, f := range genGokitFiles {
err := writeFile(f, protoDir)
exitIfError(err)
}
// docs
genDocFiles := gendoc.GenerateDocs(dt)
for _, f := range genDocFiles {
err := writeFile(f, protoDir)
exitIfError(err)
}
}
func trussGoImports(svcDir, pbOutDir, goPath string) (string, string, error) {
goSvcImportPath, err := filepath.Rel(filepath.Join(goPath, "src"), svcDir)
if err != nil {
return "", "", err
}
goPBImportPath, err := filepath.Rel(filepath.Join(goPath, "src"), pbOutDir)
if err != nil {
return "", "", err
}
return goSvcImportPath, goPBImportPath, nil
}
func writeFile(f truss.NamedReadWriter, protoDir string) error {
name := f.Name()
fullPath := filepath.Join(protoDir, name)
err := mkdir(fullPath)
if err != nil {
return err
}
file, err := os.Create(fullPath)
if err != nil {
return errors.Wrapf(err, "could create file %v", fullPath)
}
_, err = io.Copy(file, f)
if err != nil {
return errors.Wrapf(err, "could not write to %v", fullPath)
}
return nil
}
func buildDeftree(definitionFiles []string, protoDir string) (deftree.Deftree, error) {
protocOut, err := protostuff.CodeGeneratorRequest(definitionFiles, protoDir)
if err != nil {
return nil, errors.Wrap(err, "could not use create a proto CodeGeneratorRequest")
}
svcFile, err := protostuff.ServiceFile(protocOut, protoDir)
if err != nil {
return nil, errors.Wrap(err, "coult not find service definition file")
}
// Make a deftree
dt, err := deftree.New(protocOut, svcFile)
if err != nil {
return nil, errors.Wrap(err, "could not construct deftree")
}
return dt, nil
}
// cleanProtofilePath takes a slice of file paths and returns the

Просмотреть файл

@ -14,31 +14,34 @@ import (
assets "github.com/TuneLab/go-truss/truss/template"
)
// GeneratePBDataStructures expects to be passed
// datastructures represened in the .proto files
func GeneratePBataStructures(protoFiles []string, svcDir string) error {
// GeneratePBDotGo
func GeneratePBDotGo(protoFiles []string, svcDir, protoDir, outDir string) error {
err := outputGoogleImport(svcDir)
if err != nil {
return err
}
importPath, err := filepath.Rel(filepath.Join(os.Getenv("GOPATH"), "src"), svcDir)
if err != nil {
return err
}
err = mkdir(outDir)
if err != nil {
return errors.Wrap(err, "could not make output directory")
}
genGoCode := "--go_out=Mgoogle/api/annotations.proto=" +
importPath + "/third_party/googleapis/google/api," +
"plugins=grpc:" +
svcDir
outDir
_, err = exec.LookPath("protoc-gen-go")
if err != nil {
return errors.Wrap(err, "protoc-gen-go not exist in $PATH")
}
protoDir := filepath.Dir(svcDir)
err = protoc(protoFiles, protoDir, svcDir, svcDir, genGoCode)
err = protoc(protoFiles, protoDir, svcDir, genGoCode)
if err != nil {
return errors.Wrap(err, "could not generate go code from .proto files")
}
@ -64,8 +67,7 @@ func CodeGeneratorRequest(protoFiles []string, protoDir string) (*plugin.CodeGen
}
// ServiceFile searches through the files in the request and returns the
// path to the first one which contains a service declaration. If no file in
// the request contains a service, returns an empty string.
// file which contains a service declaration.
func ServiceFile(req *plugin.CodeGeneratorRequest, protoFileDir string) (*os.File, error) {
var svcFileName string
for _, file := range req.GetProtoFile() {
@ -107,8 +109,9 @@ func getProtocOutput(protoFiles []string, protoFileDir string) ([]byte, error) {
return nil, errors.Wrapf(err, "could not write protoc imports to dir: %s", protocOutDir)
}
const plugin = "--truss-protocast_out=."
err = protoc(protoFiles, protoFileDir, protocOutDir, protocOutDir, plugin)
pluginCall := filepath.Join("--truss-protocast_out=", protocOutDir)
err = protoc(protoFiles, protoFileDir, protocOutDir, pluginCall)
if err != nil {
return nil, errors.Wrap(err, "protoc failed")
}
@ -133,7 +136,7 @@ func getProtocOutput(protoFiles []string, protoFileDir string) ([]byte, error) {
}
// protoc exec's $ protoc on protoFiles, on their full path which is created with protoDir
func protoc(protoFiles []string, protoDir, outDir, importDir, plugin string) error {
func protoc(protoFiles []string, protoDir, importDir, plugin string) error {
const googleAPIImportPath = "/third_party/googleapis"
var fullPaths []string
@ -154,8 +157,6 @@ func protoc(protoFiles []string, protoDir, outDir, importDir, plugin string) err
cmdArgs...,
)
protocExec.Dir = outDir
outBytes, err := protocExec.CombinedOutput()
if err != nil {
return errors.Wrapf(err,