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