1// Copyright 2020 The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package rust
16
17import (
18	"fmt"
19	"strings"
20
21	"android/soong/android"
22)
23
24var (
25	defaultProtobufFlags = []string{""}
26)
27
28const (
29	grpcSuffix = "_grpc"
30)
31
32type PluginType int
33
34func init() {
35	android.RegisterModuleType("rust_protobuf", RustProtobufFactory)
36	android.RegisterModuleType("rust_protobuf_host", RustProtobufHostFactory)
37}
38
39var _ SourceProvider = (*protobufDecorator)(nil)
40
41type ProtobufProperties struct {
42	// List of relative paths to proto files that will be used to generate the source.
43	// Either this or grpc_protos must be defined.
44	Protos []string `android:"path,arch_variant"`
45
46	// List of relative paths to GRPC-containing proto files that will be used to generate the source.
47	// Either this or protos must be defined.
48	Grpc_protos []string `android:"path,arch_variant"`
49
50	// List of additional flags to pass to aprotoc
51	Proto_flags []string `android:"arch_variant"`
52
53	// List of libraries which export include paths required for this module
54	Header_libs []string `android:"arch_variant,variant_prepend"`
55}
56
57type protobufDecorator struct {
58	*BaseSourceProvider
59
60	Properties ProtobufProperties
61	protoNames []string
62	grpcNames  []string
63
64	grpcProtoFlags android.ProtoFlags
65	protoFlags     android.ProtoFlags
66}
67
68func (proto *protobufDecorator) GenerateSource(ctx ModuleContext, deps PathDeps) android.Path {
69	var protoFlags android.ProtoFlags
70	var grpcProtoFlags android.ProtoFlags
71	var commonProtoFlags []string
72
73	outDir := android.PathForModuleOut(ctx)
74	protoFiles := android.PathsForModuleSrc(ctx, proto.Properties.Protos)
75	grpcFiles := android.PathsForModuleSrc(ctx, proto.Properties.Grpc_protos)
76	protoPluginPath := ctx.Config().HostToolPath(ctx, "protoc-gen-rust")
77
78	commonProtoFlags = append(commonProtoFlags, defaultProtobufFlags...)
79	commonProtoFlags = append(commonProtoFlags, proto.Properties.Proto_flags...)
80	commonProtoFlags = append(commonProtoFlags, "--plugin=protoc-gen-rust="+protoPluginPath.String())
81
82	if len(protoFiles) > 0 {
83		protoFlags.OutTypeFlag = "--rust_out"
84		protoFlags.Flags = append(protoFlags.Flags, commonProtoFlags...)
85
86		protoFlags.Deps = append(protoFlags.Deps, protoPluginPath)
87	}
88
89	if len(grpcFiles) > 0 {
90		grpcPath := ctx.Config().HostToolPath(ctx, "grpc_rust_plugin")
91
92		grpcProtoFlags.OutTypeFlag = "--rust_out"
93		grpcProtoFlags.Flags = append(grpcProtoFlags.Flags, "--grpc_out="+outDir.String())
94		grpcProtoFlags.Flags = append(grpcProtoFlags.Flags, "--plugin=protoc-gen-grpc="+grpcPath.String())
95		grpcProtoFlags.Flags = append(grpcProtoFlags.Flags, commonProtoFlags...)
96
97		grpcProtoFlags.Deps = append(grpcProtoFlags.Deps, grpcPath, protoPluginPath)
98	}
99
100	if len(protoFiles) == 0 && len(grpcFiles) == 0 {
101		ctx.PropertyErrorf("protos",
102			"at least one protobuf must be defined in either protos or grpc_protos.")
103	}
104
105	// Add exported dependency include paths
106	for _, include := range deps.depIncludePaths {
107		protoFlags.Flags = append(protoFlags.Flags, "-I"+include.String())
108		grpcProtoFlags.Flags = append(grpcProtoFlags.Flags, "-I"+include.String())
109	}
110
111	stem := proto.BaseSourceProvider.getStem(ctx)
112
113	// The mod_stem.rs file is used to avoid collisions if this is not included as a crate.
114	stemFile := android.PathForModuleOut(ctx, "mod_"+stem+".rs")
115
116	// stemFile must be first here as the first path in BaseSourceProvider.OutputFiles is the library entry-point.
117	var outputs android.WritablePaths
118
119	rule := android.NewRuleBuilder(pctx, ctx)
120
121	for _, protoFile := range protoFiles {
122		// Since we're iterating over the protoFiles already, make sure they're not redeclared in grpcFiles
123		if android.InList(protoFile.String(), grpcFiles.Strings()) {
124			ctx.PropertyErrorf("protos",
125				"A proto can only be added once to either grpc_protos or protos. %q is declared in both properties",
126				protoFile.String())
127		}
128
129		protoName := strings.TrimSuffix(protoFile.Base(), ".proto")
130		proto.protoNames = append(proto.protoNames, protoName)
131
132		protoOut := android.PathForModuleOut(ctx, protoName+".rs")
133		depFile := android.PathForModuleOut(ctx, protoName+".d")
134
135		ruleOutputs := android.WritablePaths{protoOut, depFile}
136
137		android.ProtoRule(rule, protoFile, protoFlags, protoFlags.Deps, outDir, depFile, ruleOutputs)
138		outputs = append(outputs, ruleOutputs...)
139	}
140
141	for _, grpcFile := range grpcFiles {
142		grpcName := strings.TrimSuffix(grpcFile.Base(), ".proto")
143		proto.grpcNames = append(proto.grpcNames, grpcName)
144
145		// GRPC protos produce two files, a proto.rs and a proto_grpc.rs
146		protoOut := android.WritablePath(android.PathForModuleOut(ctx, grpcName+".rs"))
147		grpcOut := android.WritablePath(android.PathForModuleOut(ctx, grpcName+grpcSuffix+".rs"))
148		depFile := android.PathForModuleOut(ctx, grpcName+".d")
149
150		ruleOutputs := android.WritablePaths{protoOut, grpcOut, depFile}
151
152		android.ProtoRule(rule, grpcFile, grpcProtoFlags, grpcProtoFlags.Deps, outDir, depFile, ruleOutputs)
153		outputs = append(outputs, ruleOutputs...)
154	}
155
156	// Check that all proto base filenames are unique as outputs are written to the same directory.
157	baseFilenames := append(proto.protoNames, proto.grpcNames...)
158	if len(baseFilenames) != len(android.FirstUniqueStrings(baseFilenames)) {
159		ctx.PropertyErrorf("protos", "proto filenames must be unique across  'protos' and 'grpc_protos' "+
160			"to be used in the same rust_protobuf module. For example, foo.proto and src/foo.proto will conflict.")
161	}
162
163	android.WriteFileRule(ctx, stemFile, proto.genModFileContents())
164
165	rule.Build("protoc_"+ctx.ModuleName(), "protoc "+ctx.ModuleName())
166
167	// stemFile must be first here as the first path in BaseSourceProvider.OutputFiles is the library entry-point.
168	proto.BaseSourceProvider.OutputFiles = append(android.Paths{stemFile}, outputs.Paths()...)
169
170	// mod_stem.rs is the entry-point for our library modules, so this is what we return.
171	return stemFile
172}
173
174func (proto *protobufDecorator) genModFileContents() string {
175	lines := []string{
176		"// @Soong generated Source",
177	}
178	for _, protoName := range proto.protoNames {
179		lines = append(lines, fmt.Sprintf("pub mod %s;", protoName))
180	}
181
182	for _, grpcName := range proto.grpcNames {
183		lines = append(lines, fmt.Sprintf("pub mod %s;", grpcName))
184		lines = append(lines, fmt.Sprintf("pub mod %s%s;", grpcName, grpcSuffix))
185	}
186	if len(proto.grpcNames) > 0 {
187		lines = append(
188			lines,
189			"pub mod empty {",
190			"    pub use protobuf::well_known_types::Empty;",
191			"}")
192	}
193
194	return strings.Join(lines, "\n")
195}
196
197func (proto *protobufDecorator) SourceProviderProps() []interface{} {
198	return append(proto.BaseSourceProvider.SourceProviderProps(), &proto.Properties)
199}
200
201func (proto *protobufDecorator) SourceProviderDeps(ctx DepsContext, deps Deps) Deps {
202	deps = proto.BaseSourceProvider.SourceProviderDeps(ctx, deps)
203	deps.Rustlibs = append(deps.Rustlibs, "libprotobuf")
204	deps.HeaderLibs = append(deps.SharedLibs, proto.Properties.Header_libs...)
205
206	if len(proto.Properties.Grpc_protos) > 0 {
207		deps.Rustlibs = append(deps.Rustlibs, "libgrpcio", "libfutures")
208		deps.HeaderLibs = append(deps.HeaderLibs, "libprotobuf-cpp-full")
209	}
210
211	return deps
212}
213
214// rust_protobuf generates protobuf rust code from the provided proto file. This uses the protoc-gen-rust plugin for
215// protoc. Additional flags to the protoc command can be passed via the proto_flags property. This module type will
216// create library variants that can be used as a crate dependency by adding it to the rlibs, dylibs, and rustlibs
217// properties of other modules.
218func RustProtobufFactory() android.Module {
219	module, _ := NewRustProtobuf(android.HostAndDeviceSupported)
220	return module.Init()
221}
222
223// A host-only variant of rust_protobuf. Refer to rust_protobuf for more details.
224func RustProtobufHostFactory() android.Module {
225	module, _ := NewRustProtobuf(android.HostSupported)
226	return module.Init()
227}
228
229func NewRustProtobuf(hod android.HostOrDeviceSupported) (*Module, *protobufDecorator) {
230	protobuf := &protobufDecorator{
231		BaseSourceProvider: NewSourceProvider(),
232		Properties:         ProtobufProperties{},
233	}
234
235	module := NewSourceProviderModule(hod, protobuf, false)
236
237	return module, protobuf
238}
239