|  | 
|  | 1 | +package cosmosproto | 
|  | 2 | + | 
|  | 3 | +import ( | 
|  | 4 | +	"context" | 
|  | 5 | +	"io/ioutil" | 
|  | 6 | +	"os" | 
|  | 7 | +	"path/filepath" | 
|  | 8 | +	"strings" | 
|  | 9 | + | 
|  | 10 | +	"github.com/otiai10/copy" | 
|  | 11 | +	"github.com/pkg/errors" | 
|  | 12 | +	"github.com/tendermint/starport/starport/pkg/cmdrunner" | 
|  | 13 | +	"github.com/tendermint/starport/starport/pkg/cmdrunner/step" | 
|  | 14 | +	"github.com/tendermint/starport/starport/pkg/gomodule" | 
|  | 15 | +	"github.com/tendermint/starport/starport/pkg/nodetime/protobufjs" | 
|  | 16 | +	"github.com/tendermint/starport/starport/pkg/protoanalysis" | 
|  | 17 | +	"github.com/tendermint/starport/starport/pkg/protoc" | 
|  | 18 | +	"github.com/tendermint/starport/starport/pkg/protopath" | 
|  | 19 | +	"golang.org/x/mod/modfile" | 
|  | 20 | +) | 
|  | 21 | + | 
|  | 22 | +var ( | 
|  | 23 | +	protocOuts = []string{ | 
|  | 24 | +		"--gocosmos_out=plugins=interfacetype+grpc,Mgoogle/protobuf/any.proto=github.com/cosmos/cosmos-sdk/codec/types:.", | 
|  | 25 | +		"--grpc-gateway_out=logtostderr=true:.", | 
|  | 26 | +	} | 
|  | 27 | + | 
|  | 28 | +	sdkImport          = "github.com/cosmos/cosmos-sdk" | 
|  | 29 | +	sdkProto           = "proto" | 
|  | 30 | +	sdkProtoThirdParty = "third_party/proto" | 
|  | 31 | + | 
|  | 32 | +	fileTypes = "types" | 
|  | 33 | +) | 
|  | 34 | + | 
|  | 35 | +type generateOptions struct { | 
|  | 36 | +	gomodPath string | 
|  | 37 | +	jsOut     func(protoanalysis.Package, string) string | 
|  | 38 | +} | 
|  | 39 | + | 
|  | 40 | +// Target adds a new code generation target to Generate. | 
|  | 41 | +type Target func(*generateOptions) | 
|  | 42 | + | 
|  | 43 | +// WithJSGeneration adds JS code generation. | 
|  | 44 | +func WithJSGeneration(out func(pkg protoanalysis.Package, moduleName string) (path string)) Target { | 
|  | 45 | +	return func(o *generateOptions) { | 
|  | 46 | +		o.jsOut = out | 
|  | 47 | +	} | 
|  | 48 | +} | 
|  | 49 | + | 
|  | 50 | +// WithGoGeneration adds Go code generation. | 
|  | 51 | +func WithGoGeneration(gomodPath string) Target { | 
|  | 52 | +	return func(o *generateOptions) { | 
|  | 53 | +		o.gomodPath = gomodPath | 
|  | 54 | +	} | 
|  | 55 | +} | 
|  | 56 | + | 
|  | 57 | +// generator generates code for sdk and sdk apps. | 
|  | 58 | +type generator struct { | 
|  | 59 | +	ctx          context.Context | 
|  | 60 | +	projectPath  string | 
|  | 61 | +	protoPath    string | 
|  | 62 | +	includePaths []string | 
|  | 63 | +	o            *generateOptions | 
|  | 64 | +	modfile      *modfile.File | 
|  | 65 | +} | 
|  | 66 | + | 
|  | 67 | +// Generate generates code from proto app's proto files. | 
|  | 68 | +// make sure that all paths are absolute. | 
|  | 69 | +func Generate( | 
|  | 70 | +	ctx context.Context, | 
|  | 71 | +	projectPath, | 
|  | 72 | +	protoPath string, | 
|  | 73 | +	includePaths []string, | 
|  | 74 | +	target Target, | 
|  | 75 | +	otherTargets ...Target, | 
|  | 76 | +) error { | 
|  | 77 | +	g := &generator{ | 
|  | 78 | +		ctx:          ctx, | 
|  | 79 | +		projectPath:  projectPath, | 
|  | 80 | +		protoPath:    protoPath, | 
|  | 81 | +		includePaths: includePaths, | 
|  | 82 | +		o:            &generateOptions{}, | 
|  | 83 | +	} | 
|  | 84 | + | 
|  | 85 | +	for _, target := range append(otherTargets, target) { | 
|  | 86 | +		target(g.o) | 
|  | 87 | +	} | 
|  | 88 | + | 
|  | 89 | +	if err := g.setup(); err != nil { | 
|  | 90 | +		return err | 
|  | 91 | +	} | 
|  | 92 | + | 
|  | 93 | +	if g.o.jsOut != nil { | 
|  | 94 | +		if err := g.generateJS(); err != nil { | 
|  | 95 | +			return err | 
|  | 96 | +		} | 
|  | 97 | +	} | 
|  | 98 | + | 
|  | 99 | +	if g.o.gomodPath != "" { | 
|  | 100 | +		if err := g.generateGo(); err != nil { | 
|  | 101 | +			return err | 
|  | 102 | +		} | 
|  | 103 | +	} | 
|  | 104 | + | 
|  | 105 | +	return nil | 
|  | 106 | +} | 
|  | 107 | + | 
|  | 108 | +func (g *generator) setup() (err error) { | 
|  | 109 | +	// Cosmos SDK hosts proto files of own x/ modules and some third party ones needed by itself and | 
|  | 110 | +	// blockchain apps. Generate should be aware of these and make them available to the blockchain | 
|  | 111 | +	// app that wants to generate code for its own proto. | 
|  | 112 | +	// | 
|  | 113 | +	// blockchain apps may use different versions of the SDK. following code first makes sure that | 
|  | 114 | +	// app's dependencies are download by 'go mod' and cached under the local filesystem. | 
|  | 115 | +	// and then, it determines which version of the SDK is used by the app and what is the absolute path | 
|  | 116 | +	// of its source code. | 
|  | 117 | +	if err := cmdrunner. | 
|  | 118 | +		New(cmdrunner.DefaultWorkdir(g.projectPath)). | 
|  | 119 | +		Run(g.ctx, step.New(step.Exec("go", "mod", "download"))); err != nil { | 
|  | 120 | +		return err | 
|  | 121 | +	} | 
|  | 122 | + | 
|  | 123 | +	// parse the go.mod of the app. | 
|  | 124 | +	g.modfile, err = gomodule.ParseAt(g.projectPath) | 
|  | 125 | + | 
|  | 126 | +	return | 
|  | 127 | +} | 
|  | 128 | + | 
|  | 129 | +func (g *generator) generateGo() error { | 
|  | 130 | +	includePaths, err := g.resolveInclude(protopath.NewModule(sdkImport, sdkProto, sdkProtoThirdParty)) | 
|  | 131 | +	if err != nil { | 
|  | 132 | +		return err | 
|  | 133 | +	} | 
|  | 134 | + | 
|  | 135 | +	// created a temporary dir to locate generated code under which later only some of them will be moved to the | 
|  | 136 | +	// app's source code. this also prevents having leftover files in the app's source code or its parent dir -when | 
|  | 137 | +	// command executed directly there- in case of an interrupt. | 
|  | 138 | +	tmp, err := ioutil.TempDir("", "") | 
|  | 139 | +	if err != nil { | 
|  | 140 | +		return err | 
|  | 141 | +	} | 
|  | 142 | +	defer os.RemoveAll(tmp) | 
|  | 143 | + | 
|  | 144 | +	if err := protoc.Generate(g.ctx, tmp, g.protoPath, append(g.includePaths, includePaths...), protocOuts); err != nil { | 
|  | 145 | +		return err | 
|  | 146 | +	} | 
|  | 147 | + | 
|  | 148 | +	// move generated code for the app under the relative locations in its source code. | 
|  | 149 | +	generatedPath := filepath.Join(tmp, g.o.gomodPath) | 
|  | 150 | +	err = copy.Copy(generatedPath, g.projectPath) | 
|  | 151 | +	return errors.Wrap(err, "cannot copy path") | 
|  | 152 | +} | 
|  | 153 | + | 
|  | 154 | +func (g *generator) generateJS() error { | 
|  | 155 | +	includePaths, err := g.resolveInclude(protopath.NewModule(sdkImport, sdkProto)) | 
|  | 156 | +	if err != nil { | 
|  | 157 | +		return err | 
|  | 158 | +	} | 
|  | 159 | + | 
|  | 160 | +	pkgs, err := protoanalysis.DiscoverPackages(g.protoPath) | 
|  | 161 | +	if err != nil { | 
|  | 162 | +		return err | 
|  | 163 | +	} | 
|  | 164 | + | 
|  | 165 | +	for _, pkg := range pkgs { | 
|  | 166 | +		msp := strings.Split(pkg.Name, ".") | 
|  | 167 | +		moduleName := msp[len(msp)-1] | 
|  | 168 | + | 
|  | 169 | +		if err := protobufjs.Generate( | 
|  | 170 | +			g.ctx, | 
|  | 171 | +			g.o.jsOut(pkg, moduleName), | 
|  | 172 | +			fileTypes, | 
|  | 173 | +			pkg.Path, | 
|  | 174 | +			append(g.includePaths, includePaths...), | 
|  | 175 | +		); err != nil { | 
|  | 176 | +			return err | 
|  | 177 | +		} | 
|  | 178 | +	} | 
|  | 179 | + | 
|  | 180 | +	return nil | 
|  | 181 | +} | 
|  | 182 | + | 
|  | 183 | +func (g *generator) resolveInclude(modules ...protopath.Module) (paths []string, err error) { | 
|  | 184 | +	return protopath.ResolveDependencyPaths(g.modfile.Require, modules...) | 
|  | 185 | +} | 
0 commit comments