blob: 18c01803ddf770f9df160e7a11b90986c8d2cc2b [file] [log] [blame]
Hao Chen1c8ea5b2023-10-20 23:03:45 +00001// Copyright 2024 Google Inc. All rights reserved.
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 cc
16
17import (
18 "android/soong/android"
19 "bytes"
20 _ "embed"
21 "fmt"
22 "path/filepath"
23 "slices"
24 "sort"
25 "strings"
26 "text/template"
27
28 "github.com/google/blueprint"
29 "github.com/google/blueprint/proptools"
30)
31
32const veryVerbose bool = false
33
34//go:embed cmake_main.txt
35var templateCmakeMainRaw string
36var templateCmakeMain *template.Template = parseTemplate(templateCmakeMainRaw)
37
38//go:embed cmake_module_cc.txt
39var templateCmakeModuleCcRaw string
40var templateCmakeModuleCc *template.Template = parseTemplate(templateCmakeModuleCcRaw)
41
42//go:embed cmake_module_aidl.txt
43var templateCmakeModuleAidlRaw string
44var templateCmakeModuleAidl *template.Template = parseTemplate(templateCmakeModuleAidlRaw)
45
46//go:embed cmake_ext_add_aidl_library.txt
47var cmakeExtAddAidlLibrary string
48
49//go:embed cmake_ext_append_flags.txt
50var cmakeExtAppendFlags string
51
52var defaultUnportableFlags []string = []string{
53 "-Wno-class-memaccess",
54 "-Wno-exit-time-destructors",
55 "-Wno-inconsistent-missing-override",
56 "-Wreorder-init-list",
57 "-Wno-reorder-init-list",
58 "-Wno-restrict",
59 "-Wno-stringop-overread",
60 "-Wno-subobject-linkage",
61}
62
63var ignoredSystemLibs []string = []string{
Tomasz Wasilczyk2493fcc2024-06-20 15:29:09 -070064 "crtbegin_dynamic",
65 "crtend_android",
66 "libc",
Hao Chen1c8ea5b2023-10-20 23:03:45 +000067 "libc++",
68 "libc++_static",
Tomasz Wasilczyk2493fcc2024-06-20 15:29:09 -070069 "libdl",
70 "libm",
Hao Chen1c8ea5b2023-10-20 23:03:45 +000071 "prebuilt_libclang_rt.builtins",
72 "prebuilt_libclang_rt.ubsan_minimal",
73}
74
75// Mapping entry between Android's library name and the one used when building outside Android tree.
76type LibraryMappingProperty struct {
77 // Android library name.
78 Android_name string
79
80 // Library name used when building outside Android.
81 Mapped_name string
82
83 // If the make file is already present in Android source tree, specify its location.
84 Package_pregenerated string
85
86 // If the package is expected to be installed on the build host OS, specify its name.
87 Package_system string
88}
89
90type CmakeSnapshotProperties struct {
91 // Modules to add to the snapshot package. Their dependencies are pulled in automatically.
92 Modules []string
93
94 // Host prebuilts to bundle with the snapshot. These are tools needed to build outside Android.
95 Prebuilts []string
96
97 // Global cflags to add when building outside Android.
98 Cflags []string
99
100 // Flags to skip when building outside Android.
101 Cflags_ignored []string
102
103 // Mapping between library names used in Android tree and externally.
104 Library_mapping []LibraryMappingProperty
105
106 // List of cflags that are not portable between compilers that could potentially be used to
107 // build a generated package. If left empty, it's initialized with a default list.
108 Unportable_flags []string
109
110 // Whether to include source code as part of the snapshot package.
111 Include_sources bool
112}
113
114var cmakeSnapshotSourcesProvider = blueprint.NewProvider[android.Paths]()
115
116type CmakeSnapshot struct {
117 android.ModuleBase
118
119 Properties CmakeSnapshotProperties
120
121 zipPath android.WritablePath
122}
123
124type cmakeProcessedProperties struct {
125 LibraryMapping map[string]LibraryMappingProperty
126 PregeneratedPackages []string
127 SystemPackages []string
128}
129
130type cmakeSnapshotDependencyTag struct {
131 blueprint.BaseDependencyTag
132 name string
133}
134
135var (
136 cmakeSnapshotModuleTag = cmakeSnapshotDependencyTag{name: "cmake-snapshot-module"}
137 cmakeSnapshotPrebuiltTag = cmakeSnapshotDependencyTag{name: "cmake-snapshot-prebuilt"}
138)
139
140func parseTemplate(templateContents string) *template.Template {
141 funcMap := template.FuncMap{
142 "setList": func(name string, nameSuffix string, itemPrefix string, items []string) string {
143 var list strings.Builder
144 list.WriteString("set(" + name + nameSuffix)
145 templateListBuilder(&list, itemPrefix, items)
146 return list.String()
147 },
148 "toStrings": func(files android.Paths) []string {
149 strings := make([]string, len(files))
150 for idx, file := range files {
151 strings[idx] = file.String()
152 }
153 return strings
154 },
155 "concat5": func(list1 []string, list2 []string, list3 []string, list4 []string, list5 []string) []string {
156 return append(append(append(append(list1, list2...), list3...), list4...), list5...)
157 },
158 "cflagsList": func(name string, nameSuffix string, flags []string,
159 unportableFlags []string, ignoredFlags []string) string {
160 if len(unportableFlags) == 0 {
161 unportableFlags = defaultUnportableFlags
162 }
163
164 var filteredPortable []string
165 var filteredUnportable []string
166 for _, flag := range flags {
167 if slices.Contains(ignoredFlags, flag) {
168 continue
169 } else if slices.Contains(unportableFlags, flag) {
170 filteredUnportable = append(filteredUnportable, flag)
171 } else {
172 filteredPortable = append(filteredPortable, flag)
173 }
174 }
175
176 var list strings.Builder
177
178 list.WriteString("set(" + name + nameSuffix)
179 templateListBuilder(&list, "", filteredPortable)
180
181 if len(filteredUnportable) > 0 {
182 list.WriteString("\nappend_cxx_flags_if_supported(" + name + nameSuffix)
183 templateListBuilder(&list, "", filteredUnportable)
184 }
185
186 return list.String()
187 },
188 "getSources": func(m *Module) android.Paths {
189 return m.compiler.(CompiledInterface).Srcs()
190 },
191 "getModuleType": getModuleType,
192 "getCompilerProperties": func(m *Module) BaseCompilerProperties {
193 return m.compiler.baseCompilerProps()
194 },
195 "getLinkerProperties": func(m *Module) BaseLinkerProperties {
196 return m.linker.baseLinkerProps()
197 },
198 "getExtraLibs": getExtraLibs,
199 "getIncludeDirs": getIncludeDirs,
Tomasz Wasilczyk1e831bf2024-05-10 15:15:21 -0700200 "mapLibraries": func(ctx android.ModuleContext, m *Module, libs []string, mapping map[string]LibraryMappingProperty) []string {
Hao Chen1c8ea5b2023-10-20 23:03:45 +0000201 var mappedLibs []string
202 for _, lib := range libs {
203 mappedLib, exists := mapping[lib]
204 if exists {
205 lib = mappedLib.Mapped_name
206 } else {
Tomasz Wasilczyk1e831bf2024-05-10 15:15:21 -0700207 if !ctx.OtherModuleExists(lib) {
208 ctx.OtherModuleErrorf(m, "Dependency %s doesn't exist", lib)
209 }
Hao Chen1c8ea5b2023-10-20 23:03:45 +0000210 lib = "android::" + lib
211 }
212 if lib == "" {
213 continue
214 }
215 mappedLibs = append(mappedLibs, lib)
216 }
217 sort.Strings(mappedLibs)
218 mappedLibs = slices.Compact(mappedLibs)
219 return mappedLibs
220 },
Tomasz Wasilczyk1e831bf2024-05-10 15:15:21 -0700221 "getAidlSources": func(m *Module) []string {
222 aidlInterface := m.compiler.baseCompilerProps().AidlInterface
223 aidlRoot := aidlInterface.AidlRoot + string(filepath.Separator)
224 if aidlInterface.AidlRoot == "" {
225 aidlRoot = ""
226 }
227 var sources []string
228 for _, src := range aidlInterface.Sources {
229 if !strings.HasPrefix(src, aidlRoot) {
230 panic(fmt.Sprintf("Aidl source '%v' doesn't start with '%v'", src, aidlRoot))
231 }
232 sources = append(sources, src[len(aidlRoot):])
233 }
234 return sources
235 },
Hao Chen1c8ea5b2023-10-20 23:03:45 +0000236 }
237
238 return template.Must(template.New("").Delims("<<", ">>").Funcs(funcMap).Parse(templateContents))
239}
240
241func sliceWithPrefix(prefix string, slice []string) []string {
242 output := make([]string, len(slice))
243 for i, elem := range slice {
244 output[i] = prefix + elem
245 }
246 return output
247}
248
249func templateListBuilder(builder *strings.Builder, itemPrefix string, items []string) {
250 if len(items) > 0 {
251 builder.WriteString("\n")
252 for _, item := range items {
253 builder.WriteString(" " + itemPrefix + item + "\n")
254 }
255 }
256 builder.WriteString(")")
257}
258
259func executeTemplate(templ *template.Template, buffer *bytes.Buffer, data any) string {
260 buffer.Reset()
261 if err := templ.Execute(buffer, data); err != nil {
262 panic(err)
263 }
264 output := strings.TrimSpace(buffer.String())
265 buffer.Reset()
266 return output
267}
268
269func (m *CmakeSnapshot) DepsMutator(ctx android.BottomUpMutatorContext) {
270 variations := []blueprint.Variation{
271 {"os", "linux_glibc"},
272 {"arch", "x86_64"},
273 }
274 ctx.AddVariationDependencies(variations, cmakeSnapshotModuleTag, m.Properties.Modules...)
Tomasz Wasilczyk2493fcc2024-06-20 15:29:09 -0700275
276 if len(m.Properties.Prebuilts) > 0 {
277 prebuilts := append(m.Properties.Prebuilts, "libc++")
278 ctx.AddVariationDependencies(variations, cmakeSnapshotPrebuiltTag, prebuilts...)
279 }
Hao Chen1c8ea5b2023-10-20 23:03:45 +0000280}
281
282func (m *CmakeSnapshot) GenerateAndroidBuildActions(ctx android.ModuleContext) {
283 var templateBuffer bytes.Buffer
284 var pprop cmakeProcessedProperties
285 m.zipPath = android.PathForModuleOut(ctx, ctx.ModuleName()+".zip")
286
287 // Process Library_mapping for more efficient lookups
288 pprop.LibraryMapping = map[string]LibraryMappingProperty{}
289 for _, elem := range m.Properties.Library_mapping {
290 pprop.LibraryMapping[elem.Android_name] = elem
291
292 if elem.Package_pregenerated != "" {
293 pprop.PregeneratedPackages = append(pprop.PregeneratedPackages, elem.Package_pregenerated)
294 }
295 sort.Strings(pprop.PregeneratedPackages)
296 pprop.PregeneratedPackages = slices.Compact(pprop.PregeneratedPackages)
297
298 if elem.Package_system != "" {
299 pprop.SystemPackages = append(pprop.SystemPackages, elem.Package_system)
300 }
301 sort.Strings(pprop.SystemPackages)
302 pprop.SystemPackages = slices.Compact(pprop.SystemPackages)
303 }
304
305 // Generating CMakeLists.txt rules for all modules in dependency tree
306 moduleDirs := map[string][]string{}
307 sourceFiles := map[string]android.Path{}
308 visitedModules := map[string]bool{}
309 var pregeneratedModules []*Module
310 ctx.WalkDeps(func(dep_a android.Module, parent android.Module) bool {
311 moduleName := ctx.OtherModuleName(dep_a)
Hao Chen1c8ea5b2023-10-20 23:03:45 +0000312 if visited := visitedModules[moduleName]; visited {
313 return false // visit only once
314 }
315 visitedModules[moduleName] = true
Tomasz Wasilczyk1e831bf2024-05-10 15:15:21 -0700316 dep, ok := dep_a.(*Module)
317 if !ok {
318 return false // not a cc module
319 }
Hao Chen1c8ea5b2023-10-20 23:03:45 +0000320 if mapping, ok := pprop.LibraryMapping[moduleName]; ok {
321 if mapping.Package_pregenerated != "" {
322 pregeneratedModules = append(pregeneratedModules, dep)
323 }
324 return false // mapped to system or pregenerated (we'll handle these later)
325 }
326 if ctx.OtherModuleDependencyTag(dep) == cmakeSnapshotPrebuiltTag {
327 return false // we'll handle cmakeSnapshotPrebuiltTag later
328 }
329 if slices.Contains(ignoredSystemLibs, moduleName) {
330 return false // system libs built in-tree for Android
331 }
332 if dep.compiler == nil {
333 return false // unsupported module type (e.g. prebuilt)
334 }
335 isAidlModule := dep.compiler.baseCompilerProps().AidlInterface.Lang != ""
336
337 if !proptools.Bool(dep.Properties.Cmake_snapshot_supported) {
338 ctx.OtherModulePropertyErrorf(dep, "cmake_snapshot_supported",
339 "CMake snapshots not supported, despite being a dependency for %s",
340 ctx.OtherModuleName(parent))
341 return false
342 }
343
344 if veryVerbose {
345 fmt.Println("WalkDeps: " + ctx.OtherModuleName(parent) + " -> " + moduleName)
346 }
347
348 // Generate CMakeLists.txt fragment for this module
349 templateToUse := templateCmakeModuleCc
350 if isAidlModule {
351 templateToUse = templateCmakeModuleAidl
352 }
353 moduleFragment := executeTemplate(templateToUse, &templateBuffer, struct {
354 Ctx *android.ModuleContext
355 M *Module
356 Snapshot *CmakeSnapshot
357 Pprop *cmakeProcessedProperties
358 }{
359 &ctx,
360 dep,
361 m,
362 &pprop,
363 })
364 moduleDir := ctx.OtherModuleDir(dep)
365 moduleDirs[moduleDir] = append(moduleDirs[moduleDir], moduleFragment)
366
367 if m.Properties.Include_sources {
368 files, _ := android.OtherModuleProvider(ctx, dep, cmakeSnapshotSourcesProvider)
369 for _, file := range files {
370 sourceFiles[file.String()] = file
371 }
372 }
373
374 // if it's AIDL module, no need to dive into their dependencies
375 return !isAidlModule
376 })
377
378 // Enumerate sources for pregenerated modules
379 if m.Properties.Include_sources {
380 for _, dep := range pregeneratedModules {
381 if !proptools.Bool(dep.Properties.Cmake_snapshot_supported) {
382 ctx.OtherModulePropertyErrorf(dep, "cmake_snapshot_supported",
383 "Pregenerated CMake snapshots not supported, despite being requested for %s",
384 ctx.ModuleName())
385 continue
386 }
387
388 files, _ := android.OtherModuleProvider(ctx, dep, cmakeSnapshotSourcesProvider)
389 for _, file := range files {
390 sourceFiles[file.String()] = file
391 }
392 }
393 }
394
395 // Merging CMakeLists.txt contents for every module directory
396 var makefilesList android.Paths
397 for moduleDir, fragments := range moduleDirs {
398 moduleCmakePath := android.PathForModuleGen(ctx, moduleDir, "CMakeLists.txt")
399 makefilesList = append(makefilesList, moduleCmakePath)
400 sort.Strings(fragments)
401 android.WriteFileRule(ctx, moduleCmakePath, strings.Join(fragments, "\n\n\n"))
402 }
403
404 // Generating top-level CMakeLists.txt
405 mainCmakePath := android.PathForModuleGen(ctx, "CMakeLists.txt")
406 makefilesList = append(makefilesList, mainCmakePath)
407 mainContents := executeTemplate(templateCmakeMain, &templateBuffer, struct {
408 Ctx *android.ModuleContext
409 M *CmakeSnapshot
410 ModuleDirs map[string][]string
411 Pprop *cmakeProcessedProperties
412 }{
413 &ctx,
414 m,
415 moduleDirs,
416 &pprop,
417 })
418 android.WriteFileRule(ctx, mainCmakePath, mainContents)
419
420 // Generating CMake extensions
421 extPath := android.PathForModuleGen(ctx, "cmake", "AppendCxxFlagsIfSupported.cmake")
422 makefilesList = append(makefilesList, extPath)
423 android.WriteFileRuleVerbatim(ctx, extPath, cmakeExtAppendFlags)
424 extPath = android.PathForModuleGen(ctx, "cmake", "AddAidlLibrary.cmake")
425 makefilesList = append(makefilesList, extPath)
426 android.WriteFileRuleVerbatim(ctx, extPath, cmakeExtAddAidlLibrary)
427
428 // Generating the final zip file
429 zipRule := android.NewRuleBuilder(pctx, ctx)
430 zipCmd := zipRule.Command().
431 BuiltTool("soong_zip").
432 FlagWithOutput("-o ", m.zipPath)
433
434 // Packaging all sources into the zip file
435 if m.Properties.Include_sources {
436 var sourcesList android.Paths
437 for _, file := range sourceFiles {
438 sourcesList = append(sourcesList, file)
439 }
440
441 sourcesRspFile := android.PathForModuleObj(ctx, ctx.ModuleName()+"_sources.rsp")
442 zipCmd.FlagWithRspFileInputList("-r ", sourcesRspFile, sourcesList)
443 }
444
445 // Packaging all make files into the zip file
446 makefilesRspFile := android.PathForModuleObj(ctx, ctx.ModuleName()+"_makefiles.rsp")
447 zipCmd.
448 FlagWithArg("-C ", android.PathForModuleGen(ctx).OutputPath.String()).
449 FlagWithRspFileInputList("-r ", makefilesRspFile, makefilesList)
450
451 // Packaging all prebuilts into the zip file
452 if len(m.Properties.Prebuilts) > 0 {
453 var prebuiltsList android.Paths
454
455 ctx.VisitDirectDepsWithTag(cmakeSnapshotPrebuiltTag, func(dep android.Module) {
456 for _, file := range dep.FilesToInstall() {
457 prebuiltsList = append(prebuiltsList, file)
458 }
459 })
460
461 prebuiltsRspFile := android.PathForModuleObj(ctx, ctx.ModuleName()+"_prebuilts.rsp")
462 zipCmd.
463 FlagWithArg("-C ", android.PathForArbitraryOutput(ctx).String()).
464 FlagWithArg("-P ", "prebuilts").
465 FlagWithRspFileInputList("-r ", prebuiltsRspFile, prebuiltsList)
466 }
467
468 // Finish generating the final zip file
469 zipRule.Build(m.zipPath.String(), "archiving "+ctx.ModuleName())
470}
471
472func (m *CmakeSnapshot) OutputFiles(tag string) (android.Paths, error) {
473 switch tag {
474 case "":
475 return android.Paths{m.zipPath}, nil
476 default:
477 return nil, fmt.Errorf("unsupported module reference tag %q", tag)
478 }
479}
480
481func (m *CmakeSnapshot) AndroidMkEntries() []android.AndroidMkEntries {
482 return []android.AndroidMkEntries{{
483 Class: "DATA",
484 OutputFile: android.OptionalPathForPath(m.zipPath),
485 ExtraEntries: []android.AndroidMkExtraEntriesFunc{
486 func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
487 entries.SetBool("LOCAL_UNINSTALLABLE_MODULE", true)
488 },
489 },
490 }}
491}
492
493func getModuleType(m *Module) string {
494 switch m.linker.(type) {
495 case *binaryDecorator:
496 return "executable"
497 case *libraryDecorator:
498 return "library"
499 case *testBinary:
Tomasz Wasilczykc3177e02024-06-10 14:38:45 -0700500 return "test"
Tomasz Wasilczyk6e2b8c02024-05-30 07:48:40 -0700501 case *benchmarkDecorator:
Tomasz Wasilczykc3177e02024-06-10 14:38:45 -0700502 return "test"
Hao Chen1c8ea5b2023-10-20 23:03:45 +0000503 }
Tomasz Wasilczyk6e2b8c02024-05-30 07:48:40 -0700504 panic(fmt.Sprintf("Unexpected module type: %T", m.linker))
Hao Chen1c8ea5b2023-10-20 23:03:45 +0000505}
506
507func getExtraLibs(m *Module) []string {
508 switch decorator := m.linker.(type) {
509 case *testBinary:
510 if decorator.testDecorator.gtest() {
Tomasz Wasilczyk6e2b8c02024-05-30 07:48:40 -0700511 return []string{
512 "libgtest",
513 "libgtest_main",
514 }
Hao Chen1c8ea5b2023-10-20 23:03:45 +0000515 }
Tomasz Wasilczyk6e2b8c02024-05-30 07:48:40 -0700516 case *benchmarkDecorator:
517 return []string{"libgoogle-benchmark"}
Hao Chen1c8ea5b2023-10-20 23:03:45 +0000518 }
519 return nil
520}
521
522func getIncludeDirs(ctx android.ModuleContext, m *Module) []string {
523 moduleDir := ctx.OtherModuleDir(m) + string(filepath.Separator)
524 switch decorator := m.compiler.(type) {
525 case *libraryDecorator:
Aleks Todorovc9becde2024-06-10 12:51:53 +0100526 return sliceWithPrefix(moduleDir, decorator.flagExporter.Properties.Export_include_dirs.GetOrDefault(ctx, nil))
Hao Chen1c8ea5b2023-10-20 23:03:45 +0000527 }
528 return nil
529}
530
Tomasz Wasilczykd848dcc2024-05-10 09:16:37 -0700531func cmakeSnapshotLoadHook(ctx android.LoadHookContext) {
532 props := struct {
533 Target struct {
534 Darwin struct {
535 Enabled *bool
536 }
537 Windows struct {
538 Enabled *bool
539 }
540 }
541 }{}
542 props.Target.Darwin.Enabled = proptools.BoolPtr(false)
543 props.Target.Windows.Enabled = proptools.BoolPtr(false)
544 ctx.AppendProperties(&props)
545}
546
Hao Chen1c8ea5b2023-10-20 23:03:45 +0000547// cmake_snapshot allows defining source packages for release outside of Android build tree.
548// As a result of cmake_snapshot module build, a zip file is generated with CMake build definitions
549// for selected source modules, their dependencies and optionally also the source code itself.
550func CmakeSnapshotFactory() android.Module {
551 module := &CmakeSnapshot{}
552 module.AddProperties(&module.Properties)
Tomasz Wasilczykd848dcc2024-05-10 09:16:37 -0700553 android.AddLoadHook(module, cmakeSnapshotLoadHook)
554 android.InitAndroidArchModule(module, android.HostSupported, android.MultilibFirst)
Hao Chen1c8ea5b2023-10-20 23:03:45 +0000555 return module
556}
557
558func init() {
559 android.InitRegistrationContext.RegisterModuleType("cc_cmake_snapshot", CmakeSnapshotFactory)
560}