1// Copyright (C) 2021 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 aidl
16
17import (
18	"android/soong/android"
19	"android/soong/genrule"
20	"strconv"
21
22	"path/filepath"
23	"strings"
24
25	"github.com/google/blueprint"
26	"github.com/google/blueprint/pathtools"
27	"github.com/google/blueprint/proptools"
28)
29
30var (
31	aidlDirPrepareRule = pctx.StaticRule("aidlDirPrepareRule", blueprint.RuleParams{
32		Command:     `mkdir -p "${outDir}" && touch ${out} # ${in}`,
33		Description: "create ${out}",
34	}, "outDir")
35
36	aidlCppRule = pctx.StaticRule("aidlCppRule", blueprint.RuleParams{
37		Command: `mkdir -p "${headerDir}" && ` +
38			`mkdir -p "${outDir}/staging" && ` +
39			`mkdir -p "${headerDir}/staging" && ` +
40			`${aidlCmd} --lang=${lang} ${optionalFlags} --ninja -d ${outStagingFile}.d ` +
41			`-h ${headerDir}/staging -o ${outDir}/staging ${imports} ${nextImports} ${in} && ` +
42			`rsync --checksum ${outStagingFile}.d ${out}.d && ` +
43			`rsync --checksum ${outStagingFile} ${out} && ` +
44			`( [ -z "${stagingHeaders}" ] || rsync --checksum ${stagingHeaders} ${fullHeaderDir} ) && ` +
45			`sed -i 's/\/gen\/staging\//\/gen\//g' ${out}.d && ` +
46			`rm ${outStagingFile} ${outStagingFile}.d ${stagingHeaders}`,
47		Depfile:     "${out}.d",
48		Deps:        blueprint.DepsGCC,
49		CommandDeps: []string{"${aidlCmd}"},
50		Restat:      true,
51		Description: "AIDL ${lang} ${in}",
52	}, "imports", "nextImports", "lang", "headerDir", "outDir", "optionalFlags", "stagingHeaders", "outStagingFile",
53		"fullHeaderDir")
54
55	aidlJavaRule = pctx.StaticRule("aidlJavaRule", blueprint.RuleParams{
56		Command: `${aidlCmd} --lang=java ${optionalFlags} --ninja -d ${out}.d ` +
57			`-o ${outDir} ${imports} ${nextImports} ${in}`,
58		Depfile:     "${out}.d",
59		Deps:        blueprint.DepsGCC,
60		CommandDeps: []string{"${aidlCmd}"},
61		Restat:      true,
62		Description: "AIDL Java ${in}",
63	}, "imports", "nextImports", "outDir", "optionalFlags")
64
65	aidlRustRule = pctx.StaticRule("aidlRustRule", blueprint.RuleParams{
66		Command: `${aidlCmd} --lang=rust ${optionalFlags} --ninja -d ${out}.d ` +
67			`-o ${outDir} ${imports} ${nextImports} ${in}`,
68		Depfile:     "${out}.d",
69		Deps:        blueprint.DepsGCC,
70		CommandDeps: []string{"${aidlCmd}"},
71		Restat:      true,
72		Description: "AIDL Rust ${in}",
73	}, "imports", "nextImports", "outDir", "optionalFlags")
74
75	aidlPhonyRule = pctx.StaticRule("aidlPhonyRule", blueprint.RuleParams{
76		Command:     `touch ${out}`,
77		Description: "create ${out}",
78	})
79)
80
81type aidlGenProperties struct {
82	Srcs                []string `android:"path"`
83	AidlRoot            string   // base directory for the input aidl file
84	Imports             []string
85	Headers             []string
86	Stability           *string
87	Min_sdk_version     *string
88	Platform_apis       bool
89	Lang                string // target language [java|cpp|ndk|rust]
90	BaseName            string
91	GenLog              bool
92	Version             string
93	GenRpc              bool
94	GenTrace            bool
95	GenMockall          bool
96	Unstable            *bool
97	NotFrozen           bool
98	RequireFrozenReason string
99	Visibility          []string
100	Flags               []string
101	UseUnfrozen         bool
102}
103
104type aidlGenRule struct {
105	android.ModuleBase
106
107	properties aidlGenProperties
108
109	deps            deps
110	implicitInputs  android.Paths
111	importFlags     string
112	nextImportFlags string
113
114	// A frozen aidl_interface always have a hash file
115	hashFile android.Path
116
117	genOutDir     android.ModuleGenPath
118	genHeaderDir  android.ModuleGenPath
119	genHeaderDeps android.Paths
120	genOutputs    android.WritablePaths
121}
122
123var _ android.SourceFileProducer = (*aidlGenRule)(nil)
124var _ genrule.SourceFileGenerator = (*aidlGenRule)(nil)
125
126func (g *aidlGenRule) aidlInterface(ctx android.BaseModuleContext) *aidlInterface {
127	return ctx.GetDirectDepWithTag(g.properties.BaseName, interfaceDep).(*aidlInterface)
128}
129
130func (g *aidlGenRule) getImports(ctx android.ModuleContext) map[string]string {
131	iface := g.aidlInterface(ctx)
132	return iface.getImports(g.properties.Version)
133}
134
135func (g *aidlGenRule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
136	srcs, nextImports := getPaths(ctx, g.properties.Srcs, g.properties.AidlRoot)
137
138	g.deps = getDeps(ctx, g.getImports(ctx))
139
140	if ctx.Failed() {
141		return
142	}
143
144	genDirTimestamp := android.PathForModuleGen(ctx, "timestamp") // $out/gen/timestamp
145	g.implicitInputs = append(g.implicitInputs, genDirTimestamp)
146	g.implicitInputs = append(g.implicitInputs, g.deps.implicits...)
147	g.implicitInputs = append(g.implicitInputs, g.deps.preprocessed...)
148
149	g.nextImportFlags = strings.Join(wrap("-N", nextImports, ""), " ")
150	g.importFlags = strings.Join(wrap("-I", g.deps.imports, ""), " ")
151
152	g.genOutDir = android.PathForModuleGen(ctx)
153	g.genHeaderDir = android.PathForModuleGen(ctx, "include")
154	for _, src := range srcs {
155		outFile, headers := g.generateBuildActionsForSingleAidl(ctx, src)
156		g.genOutputs = append(g.genOutputs, outFile)
157		g.genHeaderDeps = append(g.genHeaderDeps, headers...)
158	}
159
160	// This is to clean genOutDir before generating any file
161	ctx.Build(pctx, android.BuildParams{
162		Rule:   aidlDirPrepareRule,
163		Inputs: srcs,
164		Output: genDirTimestamp,
165		Args: map[string]string{
166			"outDir": g.genOutDir.String(),
167		},
168	})
169
170	// This is to trigger genrule alone
171	ctx.Build(pctx, android.BuildParams{
172		Rule:   aidlPhonyRule,
173		Output: android.PathForModuleOut(ctx, "timestamp"), // $out/timestamp
174		Inputs: g.genOutputs.Paths(),
175	})
176}
177
178func (g *aidlGenRule) generateBuildActionsForSingleAidl(ctx android.ModuleContext, src android.Path) (android.WritablePath, android.Paths) {
179	relPath := src.Rel()
180	baseDir := strings.TrimSuffix(strings.TrimSuffix(src.String(), relPath), "/")
181
182	var ext string
183	if g.properties.Lang == langJava {
184		ext = "java"
185	} else if g.properties.Lang == langRust {
186		ext = "rs"
187	} else {
188		ext = "cpp"
189	}
190	outFile := android.PathForModuleGen(ctx, pathtools.ReplaceExtension(relPath, ext))
191	outStagingFile := android.PathForModuleGen(ctx, pathtools.ReplaceExtension("staging/"+relPath, ext))
192	implicits := g.implicitInputs
193
194	// default version is 1 for any stable interface
195	version := "1"
196	previousVersion := ""
197	previousApiDir := ""
198	if g.properties.Version != "" {
199		version = g.properties.Version
200	}
201	versionInt, err := strconv.Atoi(version)
202	if err != nil && g.properties.Version != "" {
203		ctx.PropertyErrorf(g.properties.Version, "Invalid Version string: %s", g.properties.Version)
204	} else if err == nil && versionInt > 1 {
205		previousVersion = strconv.Itoa(versionInt - 1)
206		previousApiDir = filepath.Join(ctx.ModuleDir(), aidlApiDir, g.properties.BaseName, previousVersion)
207	}
208
209	optionalFlags := append([]string{}, g.properties.Flags...)
210	if proptools.Bool(g.properties.Unstable) != true {
211		optionalFlags = append(optionalFlags, "--structured")
212		optionalFlags = append(optionalFlags, "--version "+version)
213		hash := "notfrozen"
214		if !strings.HasPrefix(baseDir, ctx.Config().SoongOutDir()) {
215			hashFile := android.ExistentPathForSource(ctx, baseDir, ".hash")
216			if hashFile.Valid() {
217				hash = "$$(tail -1 '" + hashFile.Path().String() + "')"
218				implicits = append(implicits, hashFile.Path())
219
220				g.hashFile = hashFile.Path()
221			}
222		}
223		optionalFlags = append(optionalFlags, "--hash "+hash)
224	}
225	if g.properties.GenRpc {
226		optionalFlags = append(optionalFlags, "--rpc")
227	}
228	if g.properties.GenTrace {
229		optionalFlags = append(optionalFlags, "-t")
230	}
231	if g.properties.GenMockall {
232		optionalFlags = append(optionalFlags, "--mockall")
233	}
234	if g.properties.Stability != nil {
235		optionalFlags = append(optionalFlags, "--stability", *g.properties.Stability)
236	}
237	if g.properties.Platform_apis {
238		optionalFlags = append(optionalFlags, "--min_sdk_version platform_apis")
239	} else {
240		minSdkVer := proptools.StringDefault(g.properties.Min_sdk_version, "current")
241		optionalFlags = append(optionalFlags, "--min_sdk_version "+minSdkVer)
242	}
243	optionalFlags = append(optionalFlags, wrap("-p", g.deps.preprocessed.Strings(), "")...)
244
245	// If this is an unfrozen version of a previously frozen interface, we want (1) the location
246	// of the previously frozen source and (2) the previously frozen hash so the generated
247	// library can behave like both versions at run time.
248	if !g.properties.UseUnfrozen && previousVersion != "" &&
249		!proptools.Bool(g.properties.Unstable) && g.hashFile == nil {
250		apiDirPath := android.ExistentPathForSource(ctx, previousApiDir)
251		if apiDirPath.Valid() {
252			optionalFlags = append(optionalFlags, "--previous_api_dir="+apiDirPath.Path().String())
253		} else {
254			ctx.PropertyErrorf("--previous_api_dir is invalid: %s", apiDirPath.Path().String())
255		}
256		hashFile := android.ExistentPathForSource(ctx, previousApiDir, ".hash")
257		if hashFile.Valid() {
258			previousHash := "$$(tail -1 '" + hashFile.Path().String() + "')"
259			implicits = append(implicits, hashFile.Path())
260			optionalFlags = append(optionalFlags, "--previous_hash "+previousHash)
261		} else {
262			ctx.ModuleErrorf("Failed to find previous version's hash file in %s", previousApiDir)
263		}
264	}
265
266	var headers android.WritablePaths
267	if g.properties.Lang == langJava {
268		ctx.Build(pctx, android.BuildParams{
269			Rule:      aidlJavaRule,
270			Input:     src,
271			Implicits: implicits,
272			Output:    outFile,
273			Args: map[string]string{
274				"imports":       g.importFlags,
275				"nextImports":   g.nextImportFlags,
276				"outDir":        g.genOutDir.String(),
277				"optionalFlags": strings.Join(optionalFlags, " "),
278			},
279		})
280	} else if g.properties.Lang == langRust {
281		ctx.Build(pctx, android.BuildParams{
282			Rule:      aidlRustRule,
283			Input:     src,
284			Implicits: implicits,
285			Output:    outFile,
286			Args: map[string]string{
287				"imports":       g.importFlags,
288				"nextImports":   g.nextImportFlags,
289				"outDir":        g.genOutDir.String(),
290				"optionalFlags": strings.Join(optionalFlags, " "),
291			},
292		})
293	} else {
294		typeName := strings.TrimSuffix(filepath.Base(relPath), ".aidl")
295		packagePath := filepath.Dir(relPath)
296		baseName := typeName
297		// TODO(b/111362593): aidl_to_cpp_common.cpp uses heuristics to figure out if
298		//   an interface name has a leading I. Those same heuristics have been
299		//   moved here.
300		if len(baseName) >= 2 && baseName[0] == 'I' &&
301			strings.ToUpper(baseName)[1] == baseName[1] {
302			baseName = strings.TrimPrefix(typeName, "I")
303		}
304
305		prefix := ""
306		if g.properties.Lang == langNdk || g.properties.Lang == langNdkPlatform {
307			prefix = "aidl"
308		}
309
310		var stagingHeaders []string
311		var fullHeaderDir = g.genHeaderDir.Join(ctx, prefix, packagePath)
312		if g.properties.Lang != langCppAnalyzer {
313			headers = append(headers, g.genHeaderDir.Join(ctx, prefix, packagePath, typeName+".h"))
314			stagingHeaders = append(stagingHeaders, g.genHeaderDir.Join(ctx, "staging/"+prefix, packagePath, typeName+".h").String())
315			headers = append(headers, g.genHeaderDir.Join(ctx, prefix, packagePath, "Bp"+baseName+".h"))
316			stagingHeaders = append(stagingHeaders, g.genHeaderDir.Join(ctx, "staging/"+prefix, packagePath, "Bp"+baseName+".h").String())
317			headers = append(headers, g.genHeaderDir.Join(ctx, prefix, packagePath, "Bn"+baseName+".h"))
318			stagingHeaders = append(stagingHeaders, g.genHeaderDir.Join(ctx, "staging/"+prefix, packagePath, "Bn"+baseName+".h").String())
319		}
320
321		if g.properties.GenLog {
322			optionalFlags = append(optionalFlags, "--log")
323		}
324
325		aidlLang := g.properties.Lang
326		if aidlLang == langNdkPlatform {
327			aidlLang = "ndk"
328		}
329
330		ctx.Build(pctx, android.BuildParams{
331			Rule:            aidlCppRule,
332			Input:           src,
333			Implicits:       implicits,
334			Output:          outFile,
335			ImplicitOutputs: headers,
336			Args: map[string]string{
337				"imports":        g.importFlags,
338				"nextImports":    g.nextImportFlags,
339				"lang":           aidlLang,
340				"headerDir":      g.genHeaderDir.String(),
341				"fullHeaderDir":  fullHeaderDir.String(),
342				"outDir":         g.genOutDir.String(),
343				"outStagingFile": outStagingFile.String(),
344				"optionalFlags":  strings.Join(optionalFlags, " "),
345				"stagingHeaders": strings.Join(stagingHeaders, " "),
346			},
347		})
348	}
349
350	return outFile, headers.Paths()
351}
352
353func (g *aidlGenRule) GeneratedSourceFiles() android.Paths {
354	return g.genOutputs.Paths()
355}
356
357func (g *aidlGenRule) Srcs() android.Paths {
358	return g.genOutputs.Paths()
359}
360
361func (g *aidlGenRule) GeneratedDeps() android.Paths {
362	return g.genHeaderDeps
363}
364
365func (g *aidlGenRule) GeneratedHeaderDirs() android.Paths {
366	return android.Paths{g.genHeaderDir}
367}
368
369func (g *aidlGenRule) DepsMutator(ctx android.BottomUpMutatorContext) {
370	ctx.AddReverseDependency(ctx.Module(), nil, aidlMetadataSingletonName)
371}
372func aidlGenFactory() android.Module {
373	g := &aidlGenRule{}
374	g.AddProperties(&g.properties)
375	android.InitAndroidModule(g)
376	return g
377}
378