Merge pull request #70 from hasAdamr/refactor-protostage
Add -pbout flag for .pb.go out directory; Large refactor for pipeline changes.
This commit is contained in:
Коммит
d3caffcf87
|
@ -3,8 +3,10 @@
|
|||
## Dependencies
|
||||
|
||||
1. Everything required to install `truss`
|
||||
2. go-bindata for compiling templates into binary `$ go get
|
||||
github.com/jteeuwen/go-bindata`
|
||||
2. go-bindata for compiling templates into binary
|
||||
```
|
||||
$ go get github.com/jteeuwen/go-bindata/...
|
||||
```
|
||||
|
||||
## Building
|
||||
|
||||
|
@ -15,7 +17,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 +68,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`
|
||||
|
|
|
@ -19,7 +19,7 @@ import (
|
|||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/TuneLab/go-truss/deftree/svcparse"
|
||||
"github.com/TuneLab/go-truss/truss/protostage"
|
||||
"github.com/TuneLab/go-truss/truss/execprotoc"
|
||||
)
|
||||
|
||||
var gengo *generator.Generator
|
||||
|
@ -101,24 +101,21 @@ func NewFromString(def string) (Deftree, error) {
|
|||
}
|
||||
defer os.RemoveAll(protoDir)
|
||||
|
||||
err = ioutil.WriteFile(filepath.Join(protoDir, defFileName), []byte(def), 0666)
|
||||
defPath := filepath.Join(protoDir, defFileName)
|
||||
|
||||
err = ioutil.WriteFile(defPath, []byte(def), 0666)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not write proto definition to file")
|
||||
}
|
||||
|
||||
err = protostage.Stage(protoDir)
|
||||
req, err := execprotoc.CodeGeneratorRequest([]string{defPath})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "unable to prepare filesystem for generating deftree")
|
||||
return nil, errors.Wrap(err, "unable to create a proto CodeGeneratorRequest")
|
||||
}
|
||||
|
||||
req, svcFile, err := protostage.Compose([]string{defFileName}, protoDir)
|
||||
deftree, err := New(req, strings.NewReader(def))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "unable to get CodeGeneratorRequest for generating deftree")
|
||||
}
|
||||
|
||||
deftree, err := New(req, svcFile)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "can not create new deftree")
|
||||
return nil, errors.Wrap(err, "could not create new deftree from CodeGeneratorRequest and definition")
|
||||
}
|
||||
|
||||
return deftree, nil
|
||||
|
|
|
@ -31,11 +31,13 @@ func init() {
|
|||
})
|
||||
}
|
||||
|
||||
// templateExecutor is passed to templates as the executing struct its fields
|
||||
// templateExecutor is passed to templates as the executing struct; its fields
|
||||
// and methods are used to modify the template
|
||||
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
|
||||
|
@ -46,15 +48,12 @@ type templateExecutor struct {
|
|||
funcMap template.FuncMap
|
||||
}
|
||||
|
||||
// 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, goPackage, goPBPackage string) (*templateExecutor, error) {
|
||||
service, err := getProtoService(dt)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "no service found; aborting generating gokit service")
|
||||
}
|
||||
|
||||
importPath := goImportPath + "/" + dt.GetName() + "-service"
|
||||
funcMap := template.FuncMap{
|
||||
"ToLower": strings.ToLower,
|
||||
"Title": strings.Title,
|
||||
|
@ -62,28 +61,29 @@ 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: goPackage,
|
||||
PBImportPath: goPBPackage,
|
||||
PackageName: dt.GetName(),
|
||||
Service: service,
|
||||
ClientArgs: clientarggen.New(service),
|
||||
HTTPHelper: httptransport.NewHelper(service),
|
||||
funcMap: funcMap,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GenerateGokit accepts a deftree representing the ast of a group of .proto
|
||||
// files, a []truss.NamedReadWriter representing files generated previously
|
||||
// (which may be nil for no previously generated files), and a goImportPath for
|
||||
// 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)
|
||||
// GenerateGokit returns a gokit service generated from a service definition (deftree),
|
||||
// the package to the root of the generated service goPackage, the package
|
||||
// to the .pb.go service struct files (goPBPackage) and any prevously generated files.
|
||||
func GenerateGokit(dt deftree.Deftree, goPackage, goPBPackage string, previousFiles []truss.NamedReadWriter) ([]truss.NamedReadWriter, error) {
|
||||
te, err := newTemplateExecutor(dt, goPackage, goPBPackage)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not create template executor")
|
||||
}
|
||||
|
||||
fpm := filesToFilepathMap(previousFiles)
|
||||
fpm := make(map[string]io.Reader, len(previousFiles))
|
||||
for _, f := range previousFiles {
|
||||
fpm[f.Name()] = f
|
||||
}
|
||||
|
||||
var codeGenFiles []truss.NamedReadWriter
|
||||
|
||||
|
@ -192,7 +192,7 @@ func generateResponseFile(templFP string, te *templateExecutor, prevGenMap map[s
|
|||
// disk
|
||||
func templatePathToActual(templFilePath, packageName string) string {
|
||||
// Switch "NAME" in path with packageName.
|
||||
//i.e. for packageName = addsvc; /NAME-service/NAME-server -> /addsvc-service/addsvc-server
|
||||
// i.e. for packageName = addsvc; /NAME-service/NAME-server -> /addsvc-service/addsvc-server
|
||||
actual := strings.Replace(templFilePath, "NAME", packageName, -1)
|
||||
|
||||
actual = strings.TrimSuffix(actual, "template")
|
||||
|
@ -200,16 +200,6 @@ func templatePathToActual(templFilePath, packageName string) string {
|
|||
return actual
|
||||
}
|
||||
|
||||
// filesTofilepathMap accepts a slice of truss.NamedReadWriters and returns a map of filepaths
|
||||
// as strings to files as io.Reader
|
||||
func filesToFilepathMap(previousFiles []truss.NamedReadWriter) map[string]io.Reader {
|
||||
pathToFile := make(map[string]io.Reader, len(previousFiles))
|
||||
for _, f := range previousFiles {
|
||||
pathToFile[f.Name()] = f
|
||||
}
|
||||
return pathToFile
|
||||
}
|
||||
|
||||
// updateServerMethods accepts NAME-service/handlers/server/server_handlers.go
|
||||
// as an io.Reader and modifies it to contains only functions returned by
|
||||
// serviceFunctionNames.
|
||||
|
@ -320,8 +310,7 @@ func (te templateExecutor) trimServiceFuncs(funcsInFile map[string]bool) *templa
|
|||
return &te
|
||||
}
|
||||
|
||||
// applyTemplateFromPath accepts a path to a template and a templateExecutor and calls
|
||||
// applyTemplate with the template at that template path and returns the result
|
||||
// applyTemplateFromPath calls applyTemplate with the template at templFilePath
|
||||
func applyTemplateFromPath(templFilePath string, executor *templateExecutor) (io.Reader, error) {
|
||||
|
||||
templBytes, err := templateFileAssets.Asset(templFilePath)
|
||||
|
@ -332,9 +321,6 @@ func applyTemplateFromPath(templFilePath string, executor *templateExecutor) (io
|
|||
return applyTemplate(templBytes, templFilePath, executor)
|
||||
}
|
||||
|
||||
// applyTemplate accepts a []byte, and a string representing the content and
|
||||
// name of a template and a templateExecutor to execute on that template. Returns a
|
||||
// io.Reader containing the results of that execution
|
||||
func applyTemplate(templBytes []byte, templName string, executor *templateExecutor) (io.Reader, error) {
|
||||
templateString := string(templBytes)
|
||||
|
||||
|
|
|
@ -69,17 +69,14 @@ func TestNewTemplateExecutor(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
const goImportPath = "github.com/TuneLab/go-truss/gengokit"
|
||||
const goPackage = "github.com/TuneLab/go-truss/gengokit/general-service"
|
||||
const goPBPackage = "github.com/TuneLab/go-truss/gengokit/general-service"
|
||||
|
||||
te, err := newTemplateExecutor(dt, goImportPath)
|
||||
te, err := newTemplateExecutor(dt, goPackage, goPBPackage)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if got, want := te.ImportPath, goImportPath+"/general-service"; got != want {
|
||||
t.Fatalf("\n`%v` was ImportPath\n`%v` was wanted", got, want)
|
||||
}
|
||||
|
||||
if got, want := te.PackageName, dt.GetName(); got != want {
|
||||
t.Fatalf("\n`%v` was PackageName\n`%v` was wanted", got, want)
|
||||
}
|
||||
|
@ -165,14 +162,15 @@ func TestApplyTemplateFromPath(t *testing.T) {
|
|||
}
|
||||
`
|
||||
|
||||
const goImportPath = "github.com/TuneLab/go-truss/gengokit"
|
||||
const goPackage = "github.com/TuneLab/go-truss/gengokit"
|
||||
const goPBPackage = "github.com/TuneLab/go-truss/gengokit/general-service"
|
||||
|
||||
dt, err := deftree.NewFromString(def)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
te, err := newTemplateExecutor(dt, goImportPath)
|
||||
te, err := newTemplateExecutor(dt, goPackage, goPBPackage)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -195,7 +193,7 @@ func TestApplyTemplateFromPath(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestTrimTemplateExecutorServiceFuncs(t *testing.T) {
|
||||
const goImportPath = "github.com/TuneLab/go-truss/gengokit"
|
||||
const goPackage = "github.com/TuneLab/go-truss/gengokit"
|
||||
|
||||
const def = `
|
||||
syntax = "proto3";
|
||||
|
@ -247,7 +245,7 @@ func TestTrimTemplateExecutorServiceFuncs(t *testing.T) {
|
|||
"ProtoMethodAgainAgain": true,
|
||||
}
|
||||
|
||||
te, err := stringToTemplateExector(def, goImportPath)
|
||||
te, err := stringToTemplateExector(def, goPackage)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -297,7 +295,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
|
||||
}
|
||||
|
@ -305,6 +303,7 @@ func stringToTemplateExector(def, importPath string) (*templateExecutor, error)
|
|||
return te, nil
|
||||
|
||||
}
|
||||
|
||||
func TestUpdateServerMethods(t *testing.T) {
|
||||
const def = `
|
||||
syntax = "proto3";
|
||||
|
@ -336,14 +335,15 @@ func TestUpdateServerMethods(t *testing.T) {
|
|||
}
|
||||
`
|
||||
|
||||
const goImportPath = "github.com/TuneLab/go-truss/gengokit"
|
||||
const goPackage = "github.com/TuneLab/go-truss/gengokit"
|
||||
const goPBPackage = "github.com/TuneLab/go-truss/gengokit/general-service"
|
||||
|
||||
dt, err := deftree.NewFromString(def)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
te, err := newTemplateExecutor(dt, goImportPath)
|
||||
te, err := newTemplateExecutor(dt, goPackage, goPBPackage)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -392,7 +392,8 @@ func TestUpdateServerMethods(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestAllTemplates(t *testing.T) {
|
||||
const goImportPath = "github.com/TuneLab/go-truss/gengokit"
|
||||
const goPackage = "github.com/TuneLab/go-truss/gengokit"
|
||||
const goPBPackage = "github.com/TuneLab/go-truss/gengokit/general-service"
|
||||
|
||||
const def = `
|
||||
syntax = "proto3";
|
||||
|
@ -466,7 +467,7 @@ func TestAllTemplates(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
te, err := newTemplateExecutor(dt, goImportPath)
|
||||
te, err := newTemplateExecutor(dt, goPackage, goPBPackage)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -476,7 +477,7 @@ func TestAllTemplates(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
te2, err := newTemplateExecutor(dt2, goImportPath)
|
||||
te2, err := newTemplateExecutor(dt2, goPackage, goPBPackage)
|
||||
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 -}}"
|
||||
)
|
||||
|
||||
|
||||
|
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -7,17 +7,20 @@ HTTPTEST_MSG=\n$(OK_COLOR)Starting http end to end test:$(END_COLOR_LINE)
|
|||
|
||||
CLITEST_MSG=\n$(OK_COLOR)Start server and cliclient generate, build, and run test:$(END_COLOR_LINE)
|
||||
|
||||
|
||||
all: test
|
||||
|
||||
test: clean httptest clitest
|
||||
test: clean test-http test-cli
|
||||
|
||||
httptest:
|
||||
@echo -e '$(HTTPTEST_MSG)'
|
||||
test-http:
|
||||
@printf '$(HTTPTEST_MSG)'
|
||||
$(MAKE) -C http
|
||||
|
||||
clitest:
|
||||
@echo -e '$(CLITEST_MSG)'
|
||||
test-cli:
|
||||
@printf '$(CLITEST_MSG)'
|
||||
go test -v ./cli
|
||||
|
||||
clean:
|
||||
go test ./cli -clean
|
||||
$(MAKE) -C http clean
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
@ -136,7 +147,7 @@ func buildTestService(serviceDir string) (err error) {
|
|||
|
||||
binDir := serviceDir + "/bin"
|
||||
|
||||
err = mkdir(binDir)
|
||||
err = os.MkdirAll(binDir, 0777)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -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,15 @@ 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"))
|
||||
os.MkdirAll(filepath.Join(defDir, "pbout"), 0777)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,188 @@
|
|||
// Package execprotoc provides an interface for interacting with protoc
|
||||
// requiring only paths to files on disk
|
||||
package execprotoc
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
plugin "github.com/golang/protobuf/protoc-gen-go/plugin"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
assets "github.com/TuneLab/go-truss/truss/template"
|
||||
)
|
||||
|
||||
// GeneratePBDotGo creates .pb.go files from the passed protoPaths and writes
|
||||
// them to outDir. These files import Google api's which will be created at
|
||||
// importDir.
|
||||
func GeneratePBDotGo(protoPaths []string, importDir, outDir string) error {
|
||||
err := outputGoogleImport(importDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
goImportPath, err := filepath.Rel(filepath.Join(os.Getenv("GOPATH"), "src"), importDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
genGoCode := "--go_out=Mgoogle/api/annotations.proto=" +
|
||||
goImportPath + "/third_party/googleapis/google/api," +
|
||||
"plugins=grpc:" +
|
||||
outDir
|
||||
|
||||
_, err = exec.LookPath("protoc-gen-go")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "cannot find protoc-gen-go in PATH")
|
||||
}
|
||||
|
||||
err = protoc(protoPaths, importDir, genGoCode)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "cannot exec protoc with protoc-gen-go")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CodeGeneratorRequest returns a protoc CodeGeneratorRequest from running
|
||||
// protoc on protoPaths
|
||||
func CodeGeneratorRequest(protoPaths []string) (*plugin.CodeGeneratorRequest, error) {
|
||||
protocOut, err := getProtocOutput(protoPaths)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "cannot get output from protoc")
|
||||
}
|
||||
|
||||
req := new(plugin.CodeGeneratorRequest)
|
||||
if err = proto.Unmarshal(protocOut, req); err != nil {
|
||||
return nil, errors.Wrap(err, "cannot marshal protoc ouput to code generator request")
|
||||
}
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// ServiceFile returns the file in req that contains a service declaration.
|
||||
func ServiceFile(req *plugin.CodeGeneratorRequest, protoFileDir string) (*os.File, error) {
|
||||
var svcFileName string
|
||||
for _, file := range req.GetProtoFile() {
|
||||
if len(file.GetService()) > 0 {
|
||||
svcFileName = file.GetName()
|
||||
}
|
||||
}
|
||||
|
||||
if svcFileName == "" {
|
||||
return nil, errors.New("passed protofiles contain no service")
|
||||
}
|
||||
|
||||
svc, err := os.Open(filepath.Join(protoFileDir, svcFileName))
|
||||
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "cannot open service file: %v\n in path: %v",
|
||||
protoFileDir, svcFileName)
|
||||
}
|
||||
|
||||
return svc, nil
|
||||
}
|
||||
|
||||
// getProtocOutput executes protoc with the passed protofiles and the
|
||||
// protoc-gen-truss-protocast plugin and returns the output of protoc
|
||||
func getProtocOutput(protoPaths []string) ([]byte, error) {
|
||||
_, err := exec.LookPath("protoc-gen-truss-protocast")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "protoc-gen-truss-protocast does not exist in $PATH")
|
||||
}
|
||||
|
||||
protocOutDir, err := ioutil.TempDir("", "truss-")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "cannot create temp directory")
|
||||
}
|
||||
defer os.RemoveAll(protocOutDir)
|
||||
|
||||
err = outputGoogleImport(protocOutDir)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "cannot write protoc imports to dir: %s", protocOutDir)
|
||||
}
|
||||
|
||||
pluginCall := filepath.Join("--truss-protocast_out=", protocOutDir)
|
||||
|
||||
err = protoc(protoPaths, protocOutDir, pluginCall)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "protoc failed")
|
||||
}
|
||||
|
||||
fileInfo, err := ioutil.ReadDir(protocOutDir)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "cannot read directory: %v", protocOutDir)
|
||||
}
|
||||
|
||||
for _, f := range fileInfo {
|
||||
if f.IsDir() {
|
||||
continue
|
||||
}
|
||||
fPath := filepath.Join(protocOutDir, f.Name())
|
||||
protocOut, err := ioutil.ReadFile(fPath)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "cannot read file: %v", fPath)
|
||||
}
|
||||
return protocOut, nil
|
||||
}
|
||||
|
||||
return nil, errors.Errorf("no protoc output file found in: %v", protocOutDir)
|
||||
}
|
||||
|
||||
// protoc executes protoc on protoPaths
|
||||
func protoc(protoPaths []string, importDir, plugin string) error {
|
||||
const googleAPIImportPath = "/third_party/googleapis"
|
||||
|
||||
cmdArgs := []string{
|
||||
"-I" + filepath.Join(importDir, googleAPIImportPath),
|
||||
"--proto_path=" + filepath.Dir(protoPaths[0]),
|
||||
plugin,
|
||||
}
|
||||
// Append each definition file path to the end of that command args
|
||||
cmdArgs = append(cmdArgs, protoPaths...)
|
||||
|
||||
protocExec := exec.Command(
|
||||
"protoc",
|
||||
cmdArgs...,
|
||||
)
|
||||
|
||||
outBytes, err := protocExec.CombinedOutput()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err,
|
||||
"protoc exec failed.\nprotoc output:\n\n%v\nprotoc arguments:\n\n%v\n\n",
|
||||
string(outBytes), protocExec.Args)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// outputGoogleImport places imported and required google.api.http protobuf
|
||||
// option files
|
||||
func outputGoogleImport(dir string) error {
|
||||
// Output files that are stored in template package
|
||||
for _, assetPath := range assets.AssetNames() {
|
||||
fileBytes, _ := assets.Asset(assetPath)
|
||||
fullPath := filepath.Join(dir, assetPath)
|
||||
|
||||
// Rename .gotemplate to .go
|
||||
if strings.HasSuffix(fullPath, ".gotemplate") {
|
||||
fullPath = strings.TrimSuffix(fullPath, "template")
|
||||
}
|
||||
|
||||
err := os.MkdirAll(filepath.Dir(fullPath), 0777)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "cannot create directory %v", filepath.Dir(fullPath))
|
||||
}
|
||||
|
||||
err = ioutil.WriteFile(fullPath, fileBytes, 0666)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "cannot create template file at path %v", fullPath)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
317
truss/main.go
317
truss/main.go
|
@ -6,13 +6,12 @@ import (
|
|||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/TuneLab/go-truss/truss/protostage"
|
||||
"github.com/TuneLab/go-truss/truss/execprotoc"
|
||||
"github.com/TuneLab/go-truss/truss/truss"
|
||||
|
||||
"github.com/TuneLab/go-truss/deftree"
|
||||
|
@ -20,131 +19,207 @@ import (
|
|||
"github.com/TuneLab/go-truss/gengokit"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var (
|
||||
pbPackageFlag = flag.String("pbout", "", "The go package path where the protoc-gen-go .pb.go structs will be written.")
|
||||
)
|
||||
|
||||
func init() {
|
||||
flag.Usage = func() {
|
||||
fmt.Fprintf(os.Stderr, "Usage: %s [OPTION]... [*.proto]...\n", filepath.Base(os.Args[0]))
|
||||
flag.PrintDefaults()
|
||||
}
|
||||
|
||||
flag.Parse()
|
||||
|
||||
if len(flag.Args()) == 0 {
|
||||
exitIfError(errors.New("no arguments passed"))
|
||||
fmt.Fprintf(os.Stderr, "%s: missing .proto file(s)\n", filepath.Base(os.Args[0]))
|
||||
fmt.Fprintf(os.Stderr, "Try '%s --help' for more information.\n", filepath.Base(os.Args[0]))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
rawDefinitionPaths := flag.Args()
|
||||
|
||||
protoDir, definitionFiles, err := cleanProtofilePath(rawDefinitionPaths)
|
||||
|
||||
var files []*os.File
|
||||
for _, f := range definitionFiles {
|
||||
protoF, err := os.Open(f)
|
||||
exitIfError(errors.Wrapf(err, "could not open %v", protoF))
|
||||
|
||||
files = append(files, protoF)
|
||||
}
|
||||
|
||||
// Check truss is running in $GOPATH
|
||||
goPath := os.Getenv("GOPATH")
|
||||
|
||||
if !strings.HasPrefix(protoDir, goPath) {
|
||||
exitIfError(errors.New("truss envoked on files outside of $GOPATH"))
|
||||
}
|
||||
|
||||
// Stage directories and files needed on disk
|
||||
err = protostage.Stage(protoDir)
|
||||
exitIfError(err)
|
||||
|
||||
// Compose protocOut and service file to make a deftree
|
||||
protocOut, serviceFile, err := protostage.Compose(definitionFiles, protoDir)
|
||||
exitIfError(err)
|
||||
|
||||
// Make a deftree
|
||||
dt, err := deftree.New(protocOut, serviceFile)
|
||||
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
|
||||
mkdir(protoDir + "/" + dt.GetName() + "-service/")
|
||||
goImportPath := strings.TrimPrefix(protoDir, goPath+"/src/")
|
||||
err = protostage.GeneratePBDataStructures(definitionFiles, protoDir, goImportPath, dt.GetName())
|
||||
exitIfError(err)
|
||||
|
||||
prevGen, err := readPreviousGeneration(protoDir, dt.GetName())
|
||||
exitIfError(err)
|
||||
|
||||
// generate docs
|
||||
genDocFiles := gendoc.GenerateDocs(dt)
|
||||
|
||||
// generate gokit microservice
|
||||
genFiles, err := gengokit.GenerateGokit(dt, prevGen, goImportPath)
|
||||
exitIfError(err)
|
||||
|
||||
// append files together
|
||||
genFiles = append(genFiles, genDocFiles...)
|
||||
|
||||
// Write files to disk
|
||||
for _, f := range genFiles {
|
||||
name := f.Name()
|
||||
|
||||
mkdir(name)
|
||||
file, err := os.Create(name)
|
||||
exitIfError(errors.Wrapf(err, "could create file %v", name))
|
||||
|
||||
_, err = io.Copy(file, f)
|
||||
exitIfError(errors.Wrapf(err, "could not write to %v", name))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// cleanProtofilePath takes a slice of file paths and returns the
|
||||
// absolute directory that contains the file paths, an array of the basename
|
||||
// of the files, or an error if the files are not in the same directory
|
||||
func cleanProtofilePath(rawPaths []string) (wd string, definitionFiles []string, err error) {
|
||||
execWd, err := os.Getwd()
|
||||
func main() {
|
||||
cfg, err := parseInput()
|
||||
exitIfError(errors.Wrap(err, "cannot parse input"))
|
||||
|
||||
dt, err := parseServiceDefinition(cfg.DefPaths)
|
||||
exitIfError(errors.Wrap(err, "cannot parse input definition proto files"))
|
||||
|
||||
err = updateConfigWithService(cfg, dt)
|
||||
exitIfError(err)
|
||||
|
||||
genFiles, err := generateCode(cfg, dt)
|
||||
exitIfError(errors.Wrap(err, "cannot generate service"))
|
||||
|
||||
for _, f := range genFiles {
|
||||
err := writeGenFile(f, cfg.ServicePath())
|
||||
if err != nil {
|
||||
exitIfError(errors.Wrap(err, "cannot to write output"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// parseInput constructs a *truss.Config with all values needed to parse
|
||||
// service definition files.
|
||||
func parseInput() (*truss.Config, error) {
|
||||
var cfg truss.Config
|
||||
|
||||
// GOPATH
|
||||
goPaths := filepath.SplitList(os.Getenv("GOPATH"))
|
||||
if goPaths == nil {
|
||||
return nil, errors.New("GOPATH not set")
|
||||
}
|
||||
cfg.GOPATH = goPaths[0]
|
||||
|
||||
// DefPaths
|
||||
var err error
|
||||
rawDefinitionPaths := flag.Args()
|
||||
cfg.DefPaths, err = cleanProtofilePath(rawDefinitionPaths)
|
||||
if err != nil {
|
||||
return "", nil, errors.Wrap(err, "could not get working directoru of truss")
|
||||
return nil, errors.Wrap(err, "cannot parse input arguments")
|
||||
}
|
||||
|
||||
var workingDirectory string
|
||||
// PBGoPackage
|
||||
if *pbPackageFlag == "" {
|
||||
return &cfg, nil
|
||||
}
|
||||
|
||||
cfg.PBPackage = *pbPackageFlag
|
||||
if !fileExists(
|
||||
filepath.Join(cfg.GOPATH, "src", cfg.PBPackage)) {
|
||||
return nil, errors.Errorf(".pb.go output package directory does not exist: %q", cfg.PBPackage)
|
||||
}
|
||||
|
||||
return &cfg, nil
|
||||
}
|
||||
|
||||
// parseServiceDefinition returns a deftree which contains all needed for all
|
||||
// generating a truss service and documentation
|
||||
func parseServiceDefinition(definitionPaths []string) (deftree.Deftree, error) {
|
||||
protocOut, err := execprotoc.CodeGeneratorRequest(definitionPaths)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "cannot use parse input files with protoc")
|
||||
}
|
||||
|
||||
svcFile, err := execprotoc.ServiceFile(protocOut, filepath.Dir(definitionPaths[0]))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "cannot find service definition file")
|
||||
}
|
||||
|
||||
dt, err := deftree.New(protocOut, svcFile)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "cannot to construct service definition")
|
||||
}
|
||||
|
||||
return dt, nil
|
||||
}
|
||||
|
||||
// updateConfigWithService updates the config with all information needed to
|
||||
// generate a truss service using the parsedServiceDefinition deftree
|
||||
func updateConfigWithService(cfg *truss.Config, dt deftree.Deftree) error {
|
||||
var err error
|
||||
|
||||
// Service Path
|
||||
svcName := dt.GetName() + "-service"
|
||||
svcPath := filepath.Join(filepath.Dir(cfg.DefPaths[0]), svcName)
|
||||
cfg.ServicePackage, err = filepath.Rel(filepath.Join(cfg.GOPATH, "src"), svcPath)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "service path is not in GOPATH")
|
||||
}
|
||||
|
||||
// PrevGen
|
||||
cfg.PrevGen, err = readPreviousGeneration(cfg.ServicePath())
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "cannot read previously generated files")
|
||||
}
|
||||
|
||||
// PBGoPath
|
||||
if cfg.PBPackage == "" {
|
||||
cfg.PBPackage = cfg.ServicePackage
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// generateCode returns a []truss.NamedReadWriter that represents a gokit
|
||||
// service with documentation
|
||||
func generateCode(cfg *truss.Config, dt deftree.Deftree) ([]truss.NamedReadWriter, error) {
|
||||
if cfg.PrevGen == nil {
|
||||
err := os.Mkdir(cfg.ServicePath(), 0777)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "cannot create service directory")
|
||||
}
|
||||
}
|
||||
|
||||
err := execprotoc.GeneratePBDotGo(cfg.DefPaths, cfg.ServicePath(), cfg.PBPath())
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "cannot create .pb.go files")
|
||||
}
|
||||
|
||||
genGokitFiles, err := gengokit.GenerateGokit(dt, cfg.ServicePackage, cfg.PBPackage, cfg.PrevGen)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "cannot generate gokit service")
|
||||
}
|
||||
|
||||
genDocFiles := gendoc.GenerateDocs(dt)
|
||||
|
||||
genFiles := append(genGokitFiles, genDocFiles...)
|
||||
|
||||
return genFiles, nil
|
||||
}
|
||||
|
||||
// writeGenFile writes a truss.NamedReadWriter to the filesystem
|
||||
// to be contained within serviceDir
|
||||
func writeGenFile(f truss.NamedReadWriter, serviceDir string) error {
|
||||
// the serviceDir contains /NAME-service so we want to write to the
|
||||
// directory above
|
||||
outDir := filepath.Dir(serviceDir)
|
||||
|
||||
// i.e. NAME-service/generated/endpoint.go
|
||||
name := f.Name()
|
||||
|
||||
fullPath := filepath.Join(outDir, name)
|
||||
err := os.MkdirAll(filepath.Dir(fullPath), 0777)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
file, err := os.Create(fullPath)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "cannot create file %v", fullPath)
|
||||
}
|
||||
|
||||
_, err = io.Copy(file, f)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "cannot write to %v", fullPath)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// cleanProtofilePath returns the absolute filepath of a group of files
|
||||
// of the files, or an error if the files are not in the same directory
|
||||
func cleanProtofilePath(rawPaths []string) ([]string, error) {
|
||||
var fullPaths []string
|
||||
|
||||
// Parsed passed file paths
|
||||
for _, def := range rawPaths {
|
||||
// If the definition file path is not absolute, then make it absolute using trusses working directory
|
||||
if !path.IsAbs(def) {
|
||||
def = path.Clean(def)
|
||||
def = path.Join(execWd, def)
|
||||
full, err := filepath.Abs(def)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "cannot get working directory of truss")
|
||||
}
|
||||
|
||||
// The working direcotry for this definition file
|
||||
dir := path.Dir(def)
|
||||
// Add the base name of definition file to the slice
|
||||
definitionFiles = append(definitionFiles, path.Base(def))
|
||||
fullPaths = append(fullPaths, full)
|
||||
|
||||
// If the working directory has not beenset before set it
|
||||
if workingDirectory == "" {
|
||||
workingDirectory = dir
|
||||
} else {
|
||||
// If the working directory for this definition file is different than the previous
|
||||
if workingDirectory != dir {
|
||||
return "", nil,
|
||||
errors.Errorf(
|
||||
"all .proto files must reside in the same directory\n"+
|
||||
"these two differ: \n%v\n%v",
|
||||
wd,
|
||||
workingDirectory)
|
||||
}
|
||||
if filepath.Dir(fullPaths[0]) != filepath.Dir(full) {
|
||||
return nil, errors.Errorf("passed .proto files in different directories")
|
||||
}
|
||||
}
|
||||
|
||||
return workingDirectory, definitionFiles, 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
|
||||
return fullPaths, nil
|
||||
}
|
||||
|
||||
// exitIfError will print the error message and exit 1 if the passed error is
|
||||
// non-nil
|
||||
func exitIfError(err error) {
|
||||
if errors.Cause(err) != nil {
|
||||
defer os.Exit(1)
|
||||
|
@ -152,33 +227,32 @@ func exitIfError(err error) {
|
|||
}
|
||||
}
|
||||
|
||||
// readPreviousGeneration accepts the path to the directory where the inputed .proto files are stored, protoDir,
|
||||
// it returns a []truss.NamedReadWriter for all files in the service/ dir in protoDir
|
||||
func readPreviousGeneration(protoDir, packageName string) ([]truss.NamedReadWriter, error) {
|
||||
dir := protoDir + "/" + packageName + "-service"
|
||||
if fileExists(dir) != true {
|
||||
// readPreviousGeneration returns a []truss.NamedReadWriter for all files serviceDir
|
||||
func readPreviousGeneration(serviceDir string) ([]truss.NamedReadWriter, error) {
|
||||
if fileExists(serviceDir) != true {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var files []truss.NamedReadWriter
|
||||
dir, _ := filepath.Split(serviceDir)
|
||||
sfs := simpleFileConstructor{
|
||||
protoDir: protoDir,
|
||||
files: files,
|
||||
dir: dir,
|
||||
files: files,
|
||||
}
|
||||
err := filepath.Walk(dir, sfs.makeSimpleFile)
|
||||
err := filepath.Walk(serviceDir, sfs.makeSimpleFile)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not fully walk directory %v", protoDir)
|
||||
return nil, errors.Wrapf(err, "cannot fully walk directory %v", sfs.dir)
|
||||
}
|
||||
|
||||
return sfs.files, nil
|
||||
}
|
||||
|
||||
// simpleFileConstructor has the function makeSimpleFile which is of type filepath.WalkFunc
|
||||
// simpleFileConstructor has function makeSimpleFile of type filepath.WalkFunc
|
||||
// This allows for filepath.Walk to be called with makeSimpleFile and build a truss.SimpleFile
|
||||
// for all files in a direcotry
|
||||
type simpleFileConstructor struct {
|
||||
protoDir string
|
||||
files []truss.NamedReadWriter
|
||||
dir string
|
||||
files []truss.NamedReadWriter
|
||||
}
|
||||
|
||||
// makeSimpleFile is of type filepath.WalkFunc
|
||||
|
@ -191,12 +265,11 @@ func (sfs *simpleFileConstructor) makeSimpleFile(path string, info os.FileInfo,
|
|||
byteContent, ioErr := ioutil.ReadFile(path)
|
||||
|
||||
if ioErr != nil {
|
||||
return errors.Wrapf(ioErr, "could not read file: %v", path)
|
||||
return errors.Wrapf(ioErr, "cannot read file: %v", path)
|
||||
}
|
||||
|
||||
// name will be in the always start with "service/"
|
||||
// trim the prefix of the path to the proto files from the full path to the file
|
||||
name := strings.TrimPrefix(path, sfs.protoDir+"/")
|
||||
name := strings.TrimPrefix(path, sfs.dir)
|
||||
var file truss.SimpleFile
|
||||
file.Path = name
|
||||
file.Write(byteContent)
|
||||
|
|
|
@ -1,240 +0,0 @@
|
|||
package protostage
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/golang/protobuf/proto"
|
||||
plugin "github.com/golang/protobuf/protoc-gen-go/plugin"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
templates "github.com/TuneLab/go-truss/truss/template"
|
||||
)
|
||||
|
||||
// Stage outputs the third_party imports that are required for protoc imports
|
||||
// and go build.
|
||||
func Stage(protoDir string) error {
|
||||
err := buildDirectories(protoDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = outputGoogleImport(protoDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GeneratePBDataStructures calls $ protoc with the protoc-gen-go to output
|
||||
// .pb.go files in ./service/DONOTEDIT/pb which contain the golang
|
||||
// datastructures represened in the .proto files
|
||||
func GeneratePBDataStructures(protoFiles []string, protoDir, importPath, packageName string) error {
|
||||
pbDataStructureDir := "/" + packageName + "-service/"
|
||||
|
||||
genGoCode := "--go_out=Mgoogle/api/annotations.proto=" +
|
||||
importPath +
|
||||
"/third_party/googleapis/google/api," +
|
||||
"plugins=grpc:" +
|
||||
protoDir +
|
||||
"/" + packageName + "-service"
|
||||
|
||||
_, err := exec.LookPath("protoc-gen-go")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "protoc-gen-go not exist in $PATH")
|
||||
}
|
||||
|
||||
err = protoc(protoFiles, protoDir, protoDir+pbDataStructureDir, genGoCode)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not generate go code from .proto files")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Compose gets the parsed output from protoc, marshals that output to a
|
||||
// CodeGeneratorRequest. Then Compose finds the .proto file containing the
|
||||
// service definition and returns the CodeGeneratorRequest and the service
|
||||
// definition file
|
||||
func Compose(protoFiles []string, protoDir string) (*plugin.CodeGeneratorRequest, *os.File, error) {
|
||||
|
||||
protocOut, err := getProtocOutput(protoFiles, protoDir)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "could not get output from protoc")
|
||||
}
|
||||
|
||||
req := new(plugin.CodeGeneratorRequest)
|
||||
if err = proto.Unmarshal(protocOut, req); err != nil {
|
||||
return nil, nil, errors.Wrap(err, "could not marshal protoc ouput to code generator request")
|
||||
}
|
||||
|
||||
svcFile, err := findServiceFile(req, protoDir)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "unable to find which input file contains a service")
|
||||
}
|
||||
|
||||
return req, svcFile, nil
|
||||
}
|
||||
|
||||
// absoluteDir takes a path to file or directory if path is to a file return
|
||||
// the absolute path to the directory containing the file if path is to a
|
||||
// directory return the absolute path of that directory
|
||||
func absoluteDir(path string) (string, error) {
|
||||
absP, err := filepath.Abs(filepath.Dir(path))
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "cannot find absolute path of %v", path)
|
||||
}
|
||||
|
||||
return absP, nil
|
||||
}
|
||||
|
||||
// getProtocOutput calls exec's $ protoc with the passed protofiles and the
|
||||
// protoc-gen-truss-protocast plugin and returns the output of protoc
|
||||
func getProtocOutput(protoFiles []string, protoFileDir string) ([]byte, error) {
|
||||
_, err := exec.LookPath("protoc-gen-truss-protocast")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "protoc-gen-truss-protocast does not exist in $PATH")
|
||||
}
|
||||
|
||||
protocOutDir, err := ioutil.TempDir("", "truss-")
|
||||
defer os.RemoveAll(protocOutDir)
|
||||
|
||||
log.WithField("Protoc output dir", protocOutDir).Debug("Protoc output directory created")
|
||||
|
||||
const plugin = "--truss-protocast_out=."
|
||||
err = protoc(protoFiles, protoFileDir, protocOutDir, plugin)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "protoc failed")
|
||||
}
|
||||
|
||||
fileInfo, err := ioutil.ReadDir(protocOutDir)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not read directory: %v", protocOutDir)
|
||||
}
|
||||
|
||||
var protocOut []byte
|
||||
if len(fileInfo) > 0 {
|
||||
fileName := fileInfo[0].Name()
|
||||
filePath := protocOutDir + "/" + fileName
|
||||
protocOut, err = ioutil.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "cannot read file: %v", filePath)
|
||||
}
|
||||
} else {
|
||||
return nil, errors.Errorf("no protoc output file found in: %v", protocOutDir)
|
||||
}
|
||||
|
||||
return protocOut, nil
|
||||
}
|
||||
|
||||
// protoc exec's $ protoc on protoFiles, on their full path which is created with protoDir
|
||||
func protoc(protoFiles []string, protoDir, outDir, plugin string) error {
|
||||
const googleAPIHTTPImportPath = "/third_party/googleapis"
|
||||
|
||||
var fullPaths []string
|
||||
for _, f := range protoFiles {
|
||||
fullPaths = append(fullPaths, protoDir+"/"+f)
|
||||
}
|
||||
|
||||
cmdArgs := []string{
|
||||
//"-I.",
|
||||
"-I" + protoDir + googleAPIHTTPImportPath,
|
||||
"--proto_path=" + protoDir,
|
||||
plugin,
|
||||
}
|
||||
// Append each definition file path to the end of that command args
|
||||
cmdArgs = append(cmdArgs, fullPaths...)
|
||||
|
||||
protocExec := exec.Command(
|
||||
"protoc",
|
||||
cmdArgs...,
|
||||
)
|
||||
|
||||
log.Debug(protocExec.Args)
|
||||
|
||||
protocExec.Dir = outDir
|
||||
|
||||
outBytes, err := protocExec.CombinedOutput()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err,
|
||||
"protoc exec failed.\nprotoc output:\n\n%v\nprotoc arguments:\n\n%v\n\n",
|
||||
string(outBytes), protocExec.Args)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// findServiceFile 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.
|
||||
func findServiceFile(req *plugin.CodeGeneratorRequest, protoFileDir string) (*os.File, error) {
|
||||
var svcFileName string
|
||||
for _, file := range req.GetProtoFile() {
|
||||
if len(file.GetService()) > 0 {
|
||||
svcFileName = file.GetName()
|
||||
}
|
||||
}
|
||||
|
||||
if svcFileName == "" {
|
||||
return nil, errors.New("passed protofiles contain no service")
|
||||
}
|
||||
|
||||
svc, err := os.Open(protoFileDir + "/" + svcFileName)
|
||||
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not open service file: %v\n in path: %v",
|
||||
protoFileDir, svcFileName)
|
||||
}
|
||||
|
||||
return svc, nil
|
||||
}
|
||||
|
||||
// buildDirectories outputs the directories of the third_party imports
|
||||
func buildDirectories(protoDir string) error {
|
||||
// third_party created by going through assets in template
|
||||
// and creating directoires that are not there
|
||||
for _, fp := range templates.AssetNames() {
|
||||
dir := filepath.Dir(protoDir + "/" + fp)
|
||||
err := mkdir(dir)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "unable to create directory for %v", dir)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// outputGoogleImport places imported and required google.api.http protobuf option files
|
||||
// into their required directories as part of stage one generation
|
||||
func outputGoogleImport(workingDirectory string) error {
|
||||
// Output files that are stored in template package
|
||||
for _, filePath := range templates.AssetNames() {
|
||||
fileBytes, _ := templates.Asset(filePath)
|
||||
fullPath := workingDirectory + "/" + filePath
|
||||
|
||||
// Rename .gotemplate to .go
|
||||
if strings.HasSuffix(fullPath, ".gotemplate") {
|
||||
fullPath = strings.TrimSuffix(fullPath, "template")
|
||||
}
|
||||
|
||||
err := ioutil.WriteFile(fullPath, fileBytes, 0666)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "cannot create template file at path %v", fullPath)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -0,0 +1,35 @@
|
|||
package truss
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// Config defines the inputs to a truss service generation
|
||||
type Config struct {
|
||||
// The first path in $GOPATH
|
||||
GOPATH string
|
||||
|
||||
// The go packge where .pb.go files protoc-gen-go creates will be written
|
||||
PBPackage string
|
||||
// The go package where the service code will be written
|
||||
ServicePackage string
|
||||
|
||||
// The paths to each of the .proto files truss is being run against
|
||||
DefPaths []string
|
||||
// The files of a previously generated service, may be nil
|
||||
PrevGen []NamedReadWriter
|
||||
}
|
||||
|
||||
// ServicePath returns the full path to Config.ServicePackage
|
||||
func (c *Config) ServicePath() string {
|
||||
goSvcPath := filepath.Join(c.GOPATH, "src", c.ServicePackage)
|
||||
|
||||
return goSvcPath
|
||||
}
|
||||
|
||||
// PBPath returns the full paht to Config.PBPackage
|
||||
func (c *Config) PBPath() string {
|
||||
pbPath := filepath.Join(c.GOPATH, "src", c.PBPackage)
|
||||
|
||||
return pbPath
|
||||
}
|
|
@ -8,11 +8,12 @@ import (
|
|||
)
|
||||
|
||||
// NamedReadWriter represents a file name and that file's content
|
||||
// Name() is a path relative to the directory containing the .proto files
|
||||
// Name() should start with "service/" for all generated and read in files
|
||||
// os.file fulfills the NamedReadWriter interface
|
||||
type NamedReadWriter interface {
|
||||
io.ReadWriter
|
||||
// Name() is a path relative to the directory containing the .proto
|
||||
// files. Name() should start with "NAME-service/" for all files which have
|
||||
// been generated and read in. Note that os.file fulfills the
|
||||
// NamedReadWriter interface.
|
||||
Name() string
|
||||
}
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче