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