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