diff --git a/encoding/protobuf/parse.go b/encoding/protobuf/parse.go index 3c8713de7..9647df4f5 100644 --- a/encoding/protobuf/parse.go +++ b/encoding/protobuf/parse.go @@ -101,16 +101,26 @@ func (s *Extractor) parse(filename string, src interface{}) (p *protoConverter, failf(x.Position, "unquoting package filed: %v", err) } split := strings.Split(str, ";") - p.goPkgPath = split[0] - switch len(split) { - case 1: - p.goPkg = path.Base(str) - case 2: - p.goPkg = split[1] + switch { + case strings.Contains(split[0], "."): + p.cuePkgPath = split[0] + switch len(split) { + case 1: + p.shortPkgName = path.Base(str) + case 2: + p.shortPkgName = split[1] + default: + failf(x.Position, "unexpected ';' in %q", str) + } + p.file.Name = ast.NewIdent(p.shortPkgName) + + case len(split) == 1: + p.shortPkgName = split[0] + p.file.Name = ast.NewIdent(p.shortPkgName) + default: - failf(x.Position, "unexpected ';' in %q", str) + failf(x.Position, "malformed go_package clause %s", str) } - p.file.Name = ast.NewIdent(p.goPkg) // name.AddComment(comment(x.Comment, true)) // name.AddComment(comment(x.InlineComment, false)) } @@ -163,10 +173,10 @@ type protoConverter struct { proto3 bool - id string - protoPkg string - goPkg string - goPkgPath string + id string + protoPkg string + shortPkgName string + cuePkgPath string // w bytes.Buffer file *ast.File @@ -187,6 +197,23 @@ type mapping struct { pkg *protoConverter } +func (p *protoConverter) importPath() string { + if p.cuePkgPath == "" && p.protoPkg != "" { + dir := strings.Replace(p.protoPkg, ".", "/", -1) + p.cuePkgPath = path.Join("googleapis.com", dir) + } + return p.cuePkgPath +} + +func (p *protoConverter) shortName() string { + if p.shortPkgName == "" && p.protoPkg != "" { + split := strings.Split(p.protoPkg, ".") + p.shortPkgName = split[len(split)-1] + p.file.Name = ast.NewIdent(p.shortPkgName) + } + return p.shortPkgName +} + func (p *protoConverter) toCUEPos(pos scanner.Position) token.Pos { return p.tfile.Pos(pos.Offset, 0) } @@ -297,7 +324,7 @@ func (p *protoConverter) resolveTopScope(pos scanner.Position, name string, opti } if m, ok := p.scope[0][name[:i]]; ok { if m.pkg != nil { - p.imported[m.pkg.goPkgPath] = true + p.imported[m.pkg.importPath()] = true // TODO: do something more principled. } cueName := strings.Replace(name[i:], ".", "_", -1) @@ -340,8 +367,8 @@ func (p *protoConverter) doImport(v *proto.Import) error { } prefix := "" - if imp.goPkgPath != p.goPkgPath { - prefix = imp.goPkg + "." + if imp.importPath() != p.importPath() { + prefix = imp.shortName() + "." } pkgNamespace := strings.Split(imp.protoPkg, ".") @@ -354,7 +381,7 @@ func (p *protoConverter) doImport(v *proto.Import) error { } if _, ok := p.scope[0][ref]; !ok { pkg := imp - if imp.goPkgPath == p.goPkgPath { + if imp.importPath() == p.importPath() { pkg = nil } p.scope[0][ref] = mapping{prefix + k, "", pkg} diff --git a/encoding/protobuf/protobuf.go b/encoding/protobuf/protobuf.go index a2a1413de..0baed411e 100644 --- a/encoding/protobuf/protobuf.go +++ b/encoding/protobuf/protobuf.go @@ -16,8 +16,24 @@ // definitions and instances. // // Proto definition mapping follows the guidelines of mapping Proto to JSON as -// discussed in https://developers.google.com/protocol-buffers/docs/proto3, -// and carries some of the mapping further when possible with CUE. +// discussed in https://developers.google.com/protocol-buffers/docs/proto3, and +// carries some of the mapping further when possible with CUE. +// +// +// Package Paths +// +// If a .proto file contains a go_package directive, it will be used as the +// destination package fo the generated .cue files. A common use case is to +// generate the CUE in the same directory as the .proto definition. If a +// destination package is not within the current CUE module, it will be written +// relative to the pkg directory. +// +// If a .proto file does not specify go_package, it will convert a proto package +// "google.parent.sub" to the import path "googleapis.com/google/parent/sub". +// It is safe to mix package with and without a go_package within the same +// project. +// +// Type Mappings // // The following type mappings of defintions apply: // @@ -49,8 +65,8 @@ // Timestamp time.Time See struct.proto. // Duration time.Duration See struct.proto. // -// Protobuf definitions can be annotated with CUE constraints that are -// included in the generated CUE: +// Protobuf definitions can be annotated with CUE constraints that are included +// in the generated CUE: // (cue.val) string CUE expression defining a constraint for this // field. The string may refer to other fields // in a message definition using their JSON name. @@ -286,10 +302,10 @@ func (b *Extractor) getInst(p *protoConverter) *build.Instance { if b.errs != nil { return nil } - importPath := p.goPkgPath + importPath := p.importPath() if importPath == "" { err := errors.Newf(token.NoPos, - "no go_package for proto package %q in file %s", p.id, p.file.Filename) + "no package clause for proto package %q in file %s", p.id, p.file.Filename) b.errs = errors.Append(b.errs, err) // TODO: find an alternative. Is proto package good enough? return nil @@ -320,7 +336,7 @@ func (b *Extractor) getInst(p *protoConverter) *build.Instance { Root: b.root, Dir: dir, ImportPath: importPath, - PkgName: p.goPkg, + PkgName: p.shortPkgName, DisplayPath: p.protoPkg, } b.imports[importPath] = inst diff --git a/encoding/protobuf/testdata/acme/test.proto b/encoding/protobuf/testdata/acme/test.proto new file mode 100644 index 000000000..64de468b1 --- /dev/null +++ b/encoding/protobuf/testdata/acme/test.proto @@ -0,0 +1,7 @@ +syntax = "proto3"; + +package acme.test; + +message Test { + int32 test = 1; +} diff --git a/encoding/protobuf/testdata/acme/test/test.proto b/encoding/protobuf/testdata/acme/test/test.proto new file mode 100644 index 000000000..186ce1ae4 --- /dev/null +++ b/encoding/protobuf/testdata/acme/test/test.proto @@ -0,0 +1,11 @@ +syntax = "proto3"; + +package acme.test.test; + +// Override the short name only of this package. This notation is seen in some +// gogoproto files. +option go_package = "test_test" + +message AnotherTest { + int32 test = 1; +} diff --git a/encoding/protobuf/testdata/attributes.proto.out.cue b/encoding/protobuf/testdata/attributes.proto.out.cue index d67876007..4911be40d 100644 --- a/encoding/protobuf/testdata/attributes.proto.out.cue +++ b/encoding/protobuf/testdata/attributes.proto.out.cue @@ -14,7 +14,11 @@ // limitations under the License. package v1 -import "time" +import ( + "googleapis.com/acme/test" + "googleapis.com/acme/test/test" + "time" +) StructWrap: { struct?: {} @protobuf(1,type=google.protobuf.Struct) @@ -42,24 +46,6 @@ StructWrap: { // target.service: example // ``` // -// A given Istio deployment has a fixed vocabulary of attributes that it understands. -// The specific vocabulary is determined by the set of attribute producers being used -// in the deployment. The primary attribute producer in Istio is Envoy, although -// specialized Mixer adapters and services can also generate attributes. -// -// The common baseline set of attributes available in most Istio deployments is defined -// [here](https://istio.io/docs/reference/config/policy-and-telemetry/attribute-vocabulary/). -// -// Attributes are strongly typed. The supported attribute types are defined by -// [ValueType](https://github.com/istio/api/blob/master/policy/v1beta1/value_type.proto). -// Each type of value is encoded into one of the so-called transport types present -// in this message. -// -// Defines a map of attributes in uncompressed format. -// Following places may use this message: -// 1) Configure Istio/Proxy with static per-proxy attributes, such as source.uid. -// 2) Service IDL definition to extract api attributes for active requests. -// 3) Forward attributes from client proxy to server proxy for HTTP requests. Attributes: { // A map of attribute name to its value. attributes: { @@ -95,6 +81,10 @@ Attributes_AttributeValue: { } | { // Used for values of type STRING_MAP stringMapValue: Attributes_StringMap @protobuf(9,type=StringMap,name=string_map_value) +} | { + testValue: test.Test @protobuf(10,type=acme.test.Test,name=test_value) +} | { + testValue: test_test.AnotherTest @protobuf(11,type=acme.test.test.AnotherTest,name=test_value) } // Defines a string map. diff --git a/encoding/protobuf/testdata/istio.io/api/mixer/v1/attributes.proto b/encoding/protobuf/testdata/istio.io/api/mixer/v1/attributes.proto index ddc1b949e..83c96442c 100644 --- a/encoding/protobuf/testdata/istio.io/api/mixer/v1/attributes.proto +++ b/encoding/protobuf/testdata/istio.io/api/mixer/v1/attributes.proto @@ -22,6 +22,8 @@ import "gogoproto/gogo.proto"; import "google/protobuf/duration.proto"; import "google/protobuf/timestamp.proto"; import "google/protobuf/struct.proto"; +import "acme/test.proto"; +import "acme/test/test.proto"; option (gogoproto.goproto_getters_all) = false; option (gogoproto.equal_all) = false; @@ -56,24 +58,6 @@ message StructWrap { // target.service: example // ``` // -// A given Istio deployment has a fixed vocabulary of attributes that it understands. -// The specific vocabulary is determined by the set of attribute producers being used -// in the deployment. The primary attribute producer in Istio is Envoy, although -// specialized Mixer adapters and services can also generate attributes. -// -// The common baseline set of attributes available in most Istio deployments is defined -// [here](https://istio.io/docs/reference/config/policy-and-telemetry/attribute-vocabulary/). -// -// Attributes are strongly typed. The supported attribute types are defined by -// [ValueType](https://github.com/istio/api/blob/master/policy/v1beta1/value_type.proto). -// Each type of value is encoded into one of the so-called transport types present -// in this message. -// -// Defines a map of attributes in uncompressed format. -// Following places may use this message: -// 1) Configure Istio/Proxy with static per-proxy attributes, such as source.uid. -// 2) Service IDL definition to extract api attributes for active requests. -// 3) Forward attributes from client proxy to server proxy for HTTP requests. message Attributes { // A map of attribute name to its value. map attributes = 1; @@ -105,6 +89,9 @@ message Attributes { // Used for values of type STRING_MAP StringMap string_map_value = 9; + + acme.test.Test test_value = 10; + acme.test.test.AnotherTest test_value = 11; } } diff --git a/encoding/protobuf/testdata/istio.io/api/mixer/v1/attributes_proto_gen.cue b/encoding/protobuf/testdata/istio.io/api/mixer/v1/attributes_proto_gen.cue index feced599a..cf13972ff 100644 --- a/encoding/protobuf/testdata/istio.io/api/mixer/v1/attributes_proto_gen.cue +++ b/encoding/protobuf/testdata/istio.io/api/mixer/v1/attributes_proto_gen.cue @@ -14,7 +14,11 @@ // limitations under the License. package v1 -import "time" +import ( + "googleapis.com/acme/test" + "googleapis.com/acme/test/test" + "time" +) StructWrap: { struct?: {} @protobuf(1,type=google.protobuf.Struct) @@ -42,24 +46,6 @@ StructWrap: { // target.service: example // ``` // -// A given Istio deployment has a fixed vocabulary of attributes that it understands. -// The specific vocabulary is determined by the set of attribute producers being used -// in the deployment. The primary attribute producer in Istio is Envoy, although -// specialized Mixer adapters and services can also generate attributes. -// -// The common baseline set of attributes available in most Istio deployments is defined -// [here](https://istio.io/docs/reference/config/policy-and-telemetry/attribute-vocabulary/). -// -// Attributes are strongly typed. The supported attribute types are defined by -// [ValueType](https://github.com/istio/api/blob/master/policy/v1beta1/value_type.proto). -// Each type of value is encoded into one of the so-called transport types present -// in this message. -// -// Defines a map of attributes in uncompressed format. -// Following places may use this message: -// 1) Configure Istio/Proxy with static per-proxy attributes, such as source.uid. -// 2) Service IDL definition to extract api attributes for active requests. -// 3) Forward attributes from client proxy to server proxy for HTTP requests. Attributes: { // A map of attribute name to its value. attributes: { @@ -95,6 +81,10 @@ Attributes_AttributeValue: { } | { // Used for values of type STRING_MAP stringMapValue: Attributes_StringMap @protobuf(9,type=StringMap,name=string_map_value) +} | { + testValue: test.Test @protobuf(10,type=acme.test.Test,name=test_value) +} | { + testValue: test_test.AnotherTest @protobuf(11,type=acme.test.test.AnotherTest,name=test_value) } // Defines a string map. diff --git a/encoding/protobuf/testdata/istio.io/api/pkg/googleapis.com/acme/test/test/test_proto_gen.cue b/encoding/protobuf/testdata/istio.io/api/pkg/googleapis.com/acme/test/test/test_proto_gen.cue new file mode 100644 index 000000000..efe315a0a --- /dev/null +++ b/encoding/protobuf/testdata/istio.io/api/pkg/googleapis.com/acme/test/test/test_proto_gen.cue @@ -0,0 +1,5 @@ +package test_test + +AnotherTest: { + test?: int32 @protobuf(1) +} diff --git a/encoding/protobuf/testdata/istio.io/api/pkg/googleapis.com/acme/test/test_proto_gen.cue b/encoding/protobuf/testdata/istio.io/api/pkg/googleapis.com/acme/test/test_proto_gen.cue new file mode 100644 index 000000000..329db3e54 --- /dev/null +++ b/encoding/protobuf/testdata/istio.io/api/pkg/googleapis.com/acme/test/test_proto_gen.cue @@ -0,0 +1,5 @@ +package test + +Test: { + test?: int32 @protobuf(1) +} diff --git a/encoding/protobuf/types.go b/encoding/protobuf/types.go index 2191afef0..687819b3d 100644 --- a/encoding/protobuf/types.go +++ b/encoding/protobuf/types.go @@ -53,8 +53,8 @@ func (p *protoConverter) setBuiltin(from, to string, pkg *protoConverter) { } var ( - pkgTime = &protoConverter{goPkgPath: "time"} - pkgStruct = &protoConverter{goPkgPath: "struct"} + pkgTime = &protoConverter{cuePkgPath: "time"} + pkgStruct = &protoConverter{cuePkgPath: "struct"} ) func (p *protoConverter) mapBuiltinPackage(pos scanner.Position, file string, required bool) (generate bool) {