blob: 2a45d3b3b09b6d4f191e1c26cd384187a84d7f0e [file] [log] [blame]
Jeongik Chada36d5a2021-01-20 00:43:34 +09001// 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
20 "fmt"
21 "io"
22 "path/filepath"
23 "strconv"
24 "strings"
25
26 "github.com/google/blueprint"
27 "github.com/google/blueprint/proptools"
28)
29
30var (
31 aidlDumpApiRule = pctx.StaticRule("aidlDumpApiRule", blueprint.RuleParams{
32 Command: `rm -rf "${outDir}" && mkdir -p "${outDir}" && ` +
33 `${aidlCmd} --dumpapi --structured ${imports} ${optionalFlags} --out ${outDir} ${in} && ` +
34 `(cd ${outDir} && find ./ -name "*.aidl" -print0 | LC_ALL=C sort -z | xargs -0 sha1sum && echo ${latestVersion}) | sha1sum | cut -d " " -f 1 > ${hashFile} `,
35 CommandDeps: []string{"${aidlCmd}"},
36 }, "optionalFlags", "imports", "outDir", "hashFile", "latestVersion")
37
38 aidlCheckApiRule = pctx.StaticRule("aidlCheckApiRule", blueprint.RuleParams{
Jooyung Hanb8a97772021-01-19 01:27:38 +090039 Command: `(${aidlCmd} ${optionalFlags} --checkapi=${checkApiLevel} ${old} ${new} && touch ${out}) || ` +
Jeongik Chada36d5a2021-01-20 00:43:34 +090040 `(cat ${messageFile} && exit 1)`,
41 CommandDeps: []string{"${aidlCmd}"},
42 Description: "AIDL CHECK API: ${new} against ${old}",
Jooyung Hanb8a97772021-01-19 01:27:38 +090043 }, "optionalFlags", "old", "new", "messageFile", "checkApiLevel")
Jeongik Chada36d5a2021-01-20 00:43:34 +090044
45 aidlVerifyHashRule = pctx.StaticRule("aidlVerifyHashRule", blueprint.RuleParams{
46 Command: `if [ $$(cd '${apiDir}' && { find ./ -name "*.aidl" -print0 | LC_ALL=C sort -z | xargs -0 sha1sum && echo ${version}; } | sha1sum | cut -d " " -f 1) = $$(read -r <'${hashFile}' hash extra; printf %s $$hash) ]; then ` +
47 `touch ${out}; else cat '${messageFile}' && exit 1; fi`,
48 Description: "Verify ${apiDir} files have not been modified",
49 }, "apiDir", "version", "messageFile", "hashFile")
50)
51
52type aidlApiProperties struct {
53 BaseName string
54 Srcs []string `android:"path"`
55 AidlRoot string // base directory for the input aidl file
56 Stability *string
57 Imports []string
58 Versions []string
59}
60
61type aidlApi struct {
62 android.ModuleBase
63
64 properties aidlApiProperties
65
66 // for triggering api check for version X against version X-1
67 checkApiTimestamps android.WritablePaths
68
69 // for triggering updating current API
70 updateApiTimestamp android.WritablePath
71
72 // for triggering check that files have not been modified
73 checkHashTimestamps android.WritablePaths
74
75 // for triggering freezing API as the new version
76 freezeApiTimestamp android.WritablePath
77}
78
79func (m *aidlApi) apiDir() string {
80 return filepath.Join(aidlApiDir, m.properties.BaseName)
81}
82
83// `m <iface>-freeze-api` will freeze ToT as this version
84func (m *aidlApi) nextVersion() string {
Jeongik Cha52e98022021-01-20 18:37:20 +090085 return nextVersion(m.properties.Versions)
Jeongik Chada36d5a2021-01-20 00:43:34 +090086}
87
88type apiDump struct {
89 dir android.Path
90 files android.Paths
91 hashFile android.OptionalPath
92}
93
94func (m *aidlApi) createApiDumpFromSource(ctx android.ModuleContext) apiDump {
95 srcs, imports := getPaths(ctx, m.properties.Srcs)
96
97 if ctx.Failed() {
98 return apiDump{}
99 }
100
101 var importPaths []string
102 importPaths = append(importPaths, imports...)
103 ctx.VisitDirectDeps(func(dep android.Module) {
104 if importedAidl, ok := dep.(*aidlInterface); ok {
105 importPaths = append(importPaths, importedAidl.properties.Full_import_paths...)
106 }
107 })
108
109 var apiDir android.WritablePath
110 var apiFiles android.WritablePaths
111 var hashFile android.WritablePath
112
113 apiDir = android.PathForModuleOut(ctx, "dump")
114 aidlRoot := android.PathForModuleSrc(ctx, m.properties.AidlRoot)
115 for _, src := range srcs {
116 baseDir := getBaseDir(ctx, src, aidlRoot)
117 relPath, _ := filepath.Rel(baseDir, src.String())
118 outFile := android.PathForModuleOut(ctx, "dump", relPath)
119 apiFiles = append(apiFiles, outFile)
120 }
121 hashFile = android.PathForModuleOut(ctx, "dump", ".hash")
122 latestVersion := "latest-version"
123 if len(m.properties.Versions) >= 1 {
124 latestVersion = m.properties.Versions[len(m.properties.Versions)-1]
125 }
126
127 var optionalFlags []string
128 if m.properties.Stability != nil {
129 optionalFlags = append(optionalFlags, "--stability", *m.properties.Stability)
130 }
131
132 ctx.Build(pctx, android.BuildParams{
133 Rule: aidlDumpApiRule,
134 Outputs: append(apiFiles, hashFile),
135 Inputs: srcs,
136 Args: map[string]string{
137 "optionalFlags": strings.Join(optionalFlags, " "),
138 "imports": strings.Join(wrap("-I", importPaths, ""), " "),
139 "outDir": apiDir.String(),
140 "hashFile": hashFile.String(),
141 "latestVersion": latestVersion,
142 },
143 })
144 return apiDump{apiDir, apiFiles.Paths(), android.OptionalPathForPath(hashFile)}
145}
146
147func (m *aidlApi) makeApiDumpAsVersion(ctx android.ModuleContext, dump apiDump, version string) android.WritablePath {
148 timestampFile := android.PathForModuleOut(ctx, "updateapi_"+version+".timestamp")
149
150 modulePath := android.PathForModuleSrc(ctx).String()
151
152 targetDir := filepath.Join(modulePath, m.apiDir(), version)
153 rb := android.NewRuleBuilder(pctx, ctx)
154 // Wipe the target directory and then copy the API dump into the directory
155 rb.Command().Text("mkdir -p " + targetDir)
156 rb.Command().Text("rm -rf " + targetDir + "/*")
157 if version != currentVersion {
158 rb.Command().Text("cp -rf " + dump.dir.String() + "/. " + targetDir).Implicits(dump.files)
159 // If this is making a new frozen (i.e. non-current) version of the interface,
160 // modify Android.bp file to add the new version to the 'versions' property.
161 rb.Command().BuiltTool("bpmodify").
162 Text("-w -m " + m.properties.BaseName).
163 Text("-parameter versions -a " + version).
164 Text(android.PathForModuleSrc(ctx, "Android.bp").String())
165 } else {
166 // In this case (unfrozen interface), don't copy .hash
167 rb.Command().Text("cp -rf " + dump.dir.String() + "/* " + targetDir).Implicits(dump.files)
168 }
169 rb.Command().Text("touch").Output(timestampFile)
170
171 rb.Build("dump_aidl_api"+m.properties.BaseName+"_"+version,
172 "Making AIDL API of "+m.properties.BaseName+" as version "+version)
173 return timestampFile
174}
175
Jooyung Hanb8a97772021-01-19 01:27:38 +0900176func (m *aidlApi) checkApi(ctx android.ModuleContext, oldDump, newDump apiDump, checkApiLevel string, messageFile android.Path) android.WritablePath {
Jeongik Chada36d5a2021-01-20 00:43:34 +0900177 newVersion := newDump.dir.Base()
178 timestampFile := android.PathForModuleOut(ctx, "checkapi_"+newVersion+".timestamp")
Jeongik Chada36d5a2021-01-20 00:43:34 +0900179
180 var optionalFlags []string
181 if m.properties.Stability != nil {
182 optionalFlags = append(optionalFlags, "--stability", *m.properties.Stability)
183 }
184
185 var implicits android.Paths
186 implicits = append(implicits, oldDump.files...)
187 implicits = append(implicits, newDump.files...)
188 implicits = append(implicits, messageFile)
189 ctx.Build(pctx, android.BuildParams{
190 Rule: aidlCheckApiRule,
191 Implicits: implicits,
192 Output: timestampFile,
193 Args: map[string]string{
194 "optionalFlags": strings.Join(optionalFlags, " "),
195 "old": oldDump.dir.String(),
196 "new": newDump.dir.String(),
197 "messageFile": messageFile.String(),
Jooyung Hanb8a97772021-01-19 01:27:38 +0900198 "checkApiLevel": checkApiLevel,
Jeongik Chada36d5a2021-01-20 00:43:34 +0900199 },
200 })
201 return timestampFile
202}
203
Jooyung Hanb8a97772021-01-19 01:27:38 +0900204func (m *aidlApi) checkCompatibility(ctx android.ModuleContext, oldDump, newDump apiDump) android.WritablePath {
205 messageFile := android.PathForSource(ctx, "system/tools/aidl/build/message_check_compatibility.txt")
206 return m.checkApi(ctx, oldDump, newDump, "compatible", messageFile)
207}
Jeongik Chada36d5a2021-01-20 00:43:34 +0900208
Jooyung Hanb8a97772021-01-19 01:27:38 +0900209func (m *aidlApi) checkEquality(ctx android.ModuleContext, oldDump apiDump, newDump apiDump) android.WritablePath {
Jeongik Chada36d5a2021-01-20 00:43:34 +0900210 // Use different messages depending on whether platform SDK is finalized or not.
211 // In case when it is finalized, we should never allow updating the already frozen API.
212 // If it's not finalized, we let users to update the current version by invoking
213 // `m <name>-update-api`.
214 messageFile := android.PathForSource(ctx, "system/tools/aidl/build/message_check_equality.txt")
215 sdkIsFinal := !ctx.Config().DefaultAppTargetSdk(ctx).IsPreview()
216 if sdkIsFinal {
217 messageFile = android.PathForSource(ctx, "system/tools/aidl/build/message_check_equality_release.txt")
218 }
219 formattedMessageFile := android.PathForModuleOut(ctx, "message_check_equality.txt")
220 rb := android.NewRuleBuilder(pctx, ctx)
221 rb.Command().Text("sed").Flag(" s/%s/" + m.properties.BaseName + "/g ").Input(messageFile).Text(" > ").Output(formattedMessageFile)
222 rb.Build("format_message_"+m.properties.BaseName, "")
223
224 var implicits android.Paths
225 implicits = append(implicits, oldDump.files...)
226 implicits = append(implicits, newDump.files...)
227 implicits = append(implicits, formattedMessageFile)
Jooyung Hanb8a97772021-01-19 01:27:38 +0900228 return m.checkApi(ctx, oldDump, newDump, "equal", formattedMessageFile)
Jeongik Chada36d5a2021-01-20 00:43:34 +0900229}
230
231func (m *aidlApi) checkIntegrity(ctx android.ModuleContext, dump apiDump) android.WritablePath {
232 version := dump.dir.Base()
233 timestampFile := android.PathForModuleOut(ctx, "checkhash_"+version+".timestamp")
234 messageFile := android.PathForSource(ctx, "system/tools/aidl/build/message_check_integrity.txt")
235
236 i, _ := strconv.Atoi(version)
237 if i == 1 {
238 version = "latest-version"
239 } else {
240 version = strconv.Itoa(i - 1)
241 }
242
243 var implicits android.Paths
244 implicits = append(implicits, dump.files...)
245 implicits = append(implicits, dump.hashFile.Path())
246 implicits = append(implicits, messageFile)
247 ctx.Build(pctx, android.BuildParams{
248 Rule: aidlVerifyHashRule,
249 Implicits: implicits,
250 Output: timestampFile,
251 Args: map[string]string{
252 "apiDir": dump.dir.String(),
253 "version": version,
254 "hashFile": dump.hashFile.Path().String(),
255 "messageFile": messageFile.String(),
256 },
257 })
258 return timestampFile
259}
260
261func (m *aidlApi) GenerateAndroidBuildActions(ctx android.ModuleContext) {
262 // An API dump is created from source and it is compared against the API dump of the
263 // 'current' (yet-to-be-finalized) version. By checking this we enforce that any change in
264 // the AIDL interface is gated by the AIDL API review even before the interface is frozen as
265 // a new version.
266 totApiDump := m.createApiDumpFromSource(ctx)
267 currentApiDir := android.ExistentPathForSource(ctx, ctx.ModuleDir(), m.apiDir(), currentVersion)
268 var currentApiDump apiDump
269 if currentApiDir.Valid() {
270 currentApiDump = apiDump{
271 dir: currentApiDir.Path(),
272 files: ctx.Glob(filepath.Join(currentApiDir.Path().String(), "**/*.aidl"), nil),
273 hashFile: android.ExistentPathForSource(ctx, ctx.ModuleDir(), m.apiDir(), currentVersion, ".hash"),
274 }
275 checked := m.checkEquality(ctx, currentApiDump, totApiDump)
276 m.checkApiTimestamps = append(m.checkApiTimestamps, checked)
277 } else {
278 // The "current" directory might not exist, in case when the interface is first created.
279 // Instruct user to create one by executing `m <name>-update-api`.
280 rb := android.NewRuleBuilder(pctx, ctx)
281 ifaceName := m.properties.BaseName
282 rb.Command().Text(fmt.Sprintf(`echo "API dump for the current version of AIDL interface %s does not exist."`, ifaceName))
283 rb.Command().Text(fmt.Sprintf(`echo Run "m %s-update-api", or add "unstable: true" to the build rule `+
284 `for the interface if it does not need to be versioned`, ifaceName))
285 // This file will never be created. Otherwise, the build will pass simply by running 'm; m'.
286 alwaysChecked := android.PathForModuleOut(ctx, "checkapi_current.timestamp")
287 rb.Command().Text("false").ImplicitOutput(alwaysChecked)
288 rb.Build("check_current_aidl_api", "")
289 m.checkApiTimestamps = append(m.checkApiTimestamps, alwaysChecked)
290 }
291
292 // Also check that version X is backwards compatible with version X-1.
293 // "current" is checked against the latest version.
294 var dumps []apiDump
295 for _, ver := range m.properties.Versions {
296 apiDir := filepath.Join(ctx.ModuleDir(), m.apiDir(), ver)
297 apiDirPath := android.ExistentPathForSource(ctx, apiDir)
298 if apiDirPath.Valid() {
299 dumps = append(dumps, apiDump{
300 dir: apiDirPath.Path(),
301 files: ctx.Glob(filepath.Join(apiDirPath.String(), "**/*.aidl"), nil),
302 hashFile: android.ExistentPathForSource(ctx, ctx.ModuleDir(), m.apiDir(), ver, ".hash"),
303 })
304 } else if ctx.Config().AllowMissingDependencies() {
305 ctx.AddMissingDependencies([]string{apiDir})
306 } else {
307 ctx.ModuleErrorf("API version %s path %s does not exist", ver, apiDir)
308 }
309 }
310 if currentApiDir.Valid() {
311 dumps = append(dumps, currentApiDump)
312 }
313 for i, _ := range dumps {
314 if dumps[i].hashFile.Valid() {
315 checkHashTimestamp := m.checkIntegrity(ctx, dumps[i])
316 m.checkHashTimestamps = append(m.checkHashTimestamps, checkHashTimestamp)
317 }
318
319 if i == 0 {
320 continue
321 }
322 checked := m.checkCompatibility(ctx, dumps[i-1], dumps[i])
323 m.checkApiTimestamps = append(m.checkApiTimestamps, checked)
324 }
325
326 // API dump from source is updated to the 'current' version. Triggered by `m <name>-update-api`
327 m.updateApiTimestamp = m.makeApiDumpAsVersion(ctx, totApiDump, currentVersion)
328
329 // API dump from source is frozen as the next stable version. Triggered by `m <name>-freeze-api`
330 nextVersion := m.nextVersion()
331 m.freezeApiTimestamp = m.makeApiDumpAsVersion(ctx, totApiDump, nextVersion)
332}
333
334func (m *aidlApi) AndroidMk() android.AndroidMkData {
335 return android.AndroidMkData{
336 Custom: func(w io.Writer, name, prefix, moduleDir string, data android.AndroidMkData) {
337 android.WriteAndroidMkData(w, data)
338 targetName := m.properties.BaseName + "-freeze-api"
339 fmt.Fprintln(w, ".PHONY:", targetName)
340 fmt.Fprintln(w, targetName+":", m.freezeApiTimestamp.String())
341
342 targetName = m.properties.BaseName + "-update-api"
343 fmt.Fprintln(w, ".PHONY:", targetName)
344 fmt.Fprintln(w, targetName+":", m.updateApiTimestamp.String())
345 },
346 }
347}
348
349func (m *aidlApi) DepsMutator(ctx android.BottomUpMutatorContext) {
350 ctx.AddDependency(ctx.Module(), nil, wrap("", m.properties.Imports, aidlInterfaceSuffix)...)
351}
352
353func aidlApiFactory() android.Module {
354 m := &aidlApi{}
355 m.AddProperties(&m.properties)
356 android.InitAndroidModule(m)
357 return m
358}
359
360func addApiModule(mctx android.LoadHookContext, i *aidlInterface) string {
361 apiModule := i.ModuleBase.Name() + aidlApiSuffix
Jeongik Cha52e98022021-01-20 18:37:20 +0900362 srcs, aidlRoot := i.srcsForVersion(mctx, i.nextVersion())
Jeongik Chada36d5a2021-01-20 00:43:34 +0900363 mctx.CreateModule(aidlApiFactory, &nameProperties{
364 Name: proptools.StringPtr(apiModule),
365 }, &aidlApiProperties{
366 BaseName: i.ModuleBase.Name(),
367 Srcs: srcs,
368 AidlRoot: aidlRoot,
369 Stability: i.properties.Stability,
370 Imports: concat(i.properties.Imports, []string{i.ModuleBase.Name()}),
371 Versions: i.properties.Versions,
372 })
373 return apiModule
374}