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