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(ctx android.ModuleContext, info *CcInfo) android.Paths { 200 return info.CompilerInfo.Srcs 201 }, 202 "getModuleType": getModuleType, 203 "getAidlInterface": func(info *CcInfo) AidlInterfaceInfo { 204 return info.CompilerInfo.AidlInterfaceInfo 205 }, 206 "getCflagsProperty": func(ctx android.ModuleContext, info *CcInfo) []string { 207 return info.CompilerInfo.Cflags 208 }, 209 "getWholeStaticLibsProperty": func(ctx android.ModuleContext, info *CcInfo) []string { 210 return info.LinkerInfo.WholeStaticLibs 211 }, 212 "getStaticLibsProperty": func(ctx android.ModuleContext, info *CcInfo) []string { 213 return info.LinkerInfo.StaticLibs 214 }, 215 "getSharedLibsProperty": func(ctx android.ModuleContext, info *CcInfo) []string { 216 return info.LinkerInfo.SharedLibs 217 }, 218 "getHeaderLibsProperty": func(ctx android.ModuleContext, info *CcInfo) []string { 219 return info.LinkerInfo.HeaderLibs 220 }, 221 "getExtraLibs": getExtraLibs, 222 "getIncludeDirs": getIncludeDirs, 223 "mapLibraries": func(ctx android.ModuleContext, m android.ModuleProxy, libs []string, mapping map[string]LibraryMappingProperty) []string { 224 var mappedLibs []string 225 for _, lib := range libs { 226 mappedLib, exists := mapping[lib] 227 if exists { 228 lib = mappedLib.Mapped_name 229 } else { 230 if !ctx.OtherModuleExists(lib) { 231 ctx.OtherModuleErrorf(m, "Dependency %s doesn't exist", lib) 232 } 233 lib = "android::" + lib 234 } 235 if lib == "" { 236 continue 237 } 238 mappedLibs = append(mappedLibs, lib) 239 } 240 sort.Strings(mappedLibs) 241 mappedLibs = slices.Compact(mappedLibs) 242 return mappedLibs 243 }, 244 "getAidlSources": func(info *CcInfo) []string { 245 aidlInterface := info.CompilerInfo.AidlInterfaceInfo 246 aidlRoot := aidlInterface.AidlRoot + string(filepath.Separator) 247 if aidlInterface.AidlRoot == "" { 248 aidlRoot = "" 249 } 250 var sources []string 251 for _, src := range aidlInterface.Sources { 252 if !strings.HasPrefix(src, aidlRoot) { 253 panic(fmt.Sprintf("Aidl source '%v' doesn't start with '%v'", src, aidlRoot)) 254 } 255 sources = append(sources, src[len(aidlRoot):]) 256 } 257 return sources 258 }, 259 } 260 261 return template.Must(template.New("").Delims("<<", ">>").Funcs(funcMap).Parse(templateContents)) 262} 263 264func sliceWithPrefix(prefix string, slice []string) []string { 265 output := make([]string, len(slice)) 266 for i, elem := range slice { 267 output[i] = prefix + elem 268 } 269 return output 270} 271 272func templateListBuilder(builder *strings.Builder, itemPrefix string, items []string) { 273 if len(items) > 0 { 274 builder.WriteString("\n") 275 for _, item := range items { 276 builder.WriteString(" " + itemPrefix + item + "\n") 277 } 278 } 279 builder.WriteString(")") 280} 281 282func executeTemplate(templ *template.Template, buffer *bytes.Buffer, data any) string { 283 buffer.Reset() 284 if err := templ.Execute(buffer, data); err != nil { 285 panic(err) 286 } 287 output := strings.TrimSpace(buffer.String()) 288 buffer.Reset() 289 return output 290} 291 292func (m *CmakeSnapshot) DepsMutator(ctx android.BottomUpMutatorContext) { 293 deviceVariations := ctx.Config().AndroidFirstDeviceTarget.Variations() 294 deviceSystemVariations := append(deviceVariations, blueprint.Variation{"image", ""}) 295 deviceVendorVariations := append(deviceVariations, blueprint.Variation{"image", "vendor"}) 296 hostVariations := ctx.Config().BuildOSTarget.Variations() 297 298 ctx.AddVariationDependencies(hostVariations, cmakeSnapshotModuleTag, m.Properties.Modules_host...) 299 ctx.AddVariationDependencies(deviceSystemVariations, cmakeSnapshotModuleTag, m.Properties.Modules_system...) 300 ctx.AddVariationDependencies(deviceVendorVariations, cmakeSnapshotModuleTag, m.Properties.Modules_vendor...) 301 302 if len(m.Properties.Prebuilts) > 0 { 303 prebuilts := append(m.Properties.Prebuilts, "libc++") 304 ctx.AddVariationDependencies(hostVariations, cmakeSnapshotPrebuiltTag, prebuilts...) 305 } 306} 307 308func (m *CmakeSnapshot) GenerateAndroidBuildActions(ctx android.ModuleContext) { 309 var templateBuffer bytes.Buffer 310 var pprop cmakeProcessedProperties 311 m.zipPath = android.PathForModuleOut(ctx, ctx.ModuleName()+".zip") 312 313 // Process Library_mapping for more efficient lookups 314 pprop.LibraryMapping = map[string]LibraryMappingProperty{} 315 for _, elem := range m.Properties.Library_mapping { 316 pprop.LibraryMapping[elem.Android_name] = elem 317 318 if elem.Package_pregenerated != "" { 319 pprop.PregeneratedPackages = append(pprop.PregeneratedPackages, elem.Package_pregenerated) 320 } 321 sort.Strings(pprop.PregeneratedPackages) 322 pprop.PregeneratedPackages = slices.Compact(pprop.PregeneratedPackages) 323 324 if elem.Package_system != "" { 325 pprop.SystemPackages = append(pprop.SystemPackages, elem.Package_system) 326 } 327 sort.Strings(pprop.SystemPackages) 328 pprop.SystemPackages = slices.Compact(pprop.SystemPackages) 329 } 330 331 // Generating CMakeLists.txt rules for all modules in dependency tree 332 moduleDirs := map[string][]string{} 333 sourceFiles := map[string]android.Path{} 334 visitedModules := map[string]bool{} 335 var pregeneratedModules []android.ModuleProxy 336 ctx.WalkDepsProxy(func(dep, parent android.ModuleProxy) bool { 337 moduleName := ctx.OtherModuleName(dep) 338 if visited := visitedModules[moduleName]; visited { 339 return false // visit only once 340 } 341 visitedModules[moduleName] = true 342 ccInfo, ok := android.OtherModuleProvider(ctx, dep, CcInfoProvider) 343 if !ok { 344 return false // not a cc module 345 } 346 if mapping, ok := pprop.LibraryMapping[moduleName]; ok { 347 if mapping.Package_pregenerated != "" { 348 pregeneratedModules = append(pregeneratedModules, dep) 349 } 350 return false // mapped to system or pregenerated (we'll handle these later) 351 } 352 if ctx.OtherModuleDependencyTag(dep) == cmakeSnapshotPrebuiltTag { 353 return false // we'll handle cmakeSnapshotPrebuiltTag later 354 } 355 if slices.Contains(ignoredSystemLibs, moduleName) { 356 return false // system libs built in-tree for Android 357 } 358 if ccInfo.IsPrebuilt { 359 return false // prebuilts are not supported 360 } 361 if ccInfo.CompilerInfo == nil { 362 return false // unsupported module type 363 } 364 isAidlModule := ccInfo.CompilerInfo.AidlInterfaceInfo.Lang != "" 365 366 if !ccInfo.CmakeSnapshotSupported { 367 ctx.OtherModulePropertyErrorf(dep, "cmake_snapshot_supported", 368 "CMake snapshots not supported, despite being a dependency for %s", 369 ctx.OtherModuleName(parent)) 370 return false 371 } 372 373 if veryVerbose { 374 fmt.Println("WalkDeps: " + ctx.OtherModuleName(parent) + " -> " + moduleName) 375 } 376 377 // Generate CMakeLists.txt fragment for this module 378 templateToUse := templateCmakeModuleCc 379 if isAidlModule { 380 templateToUse = templateCmakeModuleAidl 381 } 382 moduleFragment := executeTemplate(templateToUse, &templateBuffer, struct { 383 Ctx *android.ModuleContext 384 M android.ModuleProxy 385 CcInfo *CcInfo 386 Snapshot *CmakeSnapshot 387 Pprop *cmakeProcessedProperties 388 }{ 389 &ctx, 390 dep, 391 ccInfo, 392 m, 393 &pprop, 394 }) 395 moduleDir := ctx.OtherModuleDir(dep) 396 moduleDirs[moduleDir] = append(moduleDirs[moduleDir], moduleFragment) 397 398 if m.Properties.Include_sources { 399 files, _ := android.OtherModuleProvider(ctx, dep, cmakeSnapshotSourcesProvider) 400 for _, file := range files { 401 sourceFiles[file.String()] = file 402 } 403 } 404 405 // if it's AIDL module, no need to dive into their dependencies 406 return !isAidlModule 407 }) 408 409 // Enumerate sources for pregenerated modules 410 if m.Properties.Include_sources { 411 for _, dep := range pregeneratedModules { 412 if !android.OtherModuleProviderOrDefault(ctx, dep, CcInfoProvider).CmakeSnapshotSupported { 413 ctx.OtherModulePropertyErrorf(dep, "cmake_snapshot_supported", 414 "Pregenerated CMake snapshots not supported, despite being requested for %s", 415 ctx.ModuleName()) 416 continue 417 } 418 419 files, _ := android.OtherModuleProvider(ctx, dep, cmakeSnapshotSourcesProvider) 420 for _, file := range files { 421 sourceFiles[file.String()] = file 422 } 423 } 424 } 425 426 // Merging CMakeLists.txt contents for every module directory 427 var makefilesList android.Paths 428 for _, moduleDir := range android.SortedKeys(moduleDirs) { 429 fragments := moduleDirs[moduleDir] 430 moduleCmakePath := android.PathForModuleGen(ctx, moduleDir, "CMakeLists.txt") 431 makefilesList = append(makefilesList, moduleCmakePath) 432 sort.Strings(fragments) 433 android.WriteFileRule(ctx, moduleCmakePath, strings.Join(fragments, "\n\n\n")) 434 } 435 436 // Generating top-level CMakeLists.txt 437 mainCmakePath := android.PathForModuleGen(ctx, "CMakeLists.txt") 438 makefilesList = append(makefilesList, mainCmakePath) 439 mainContents := executeTemplate(templateCmakeMain, &templateBuffer, struct { 440 Ctx *android.ModuleContext 441 M *CmakeSnapshot 442 ModuleDirs map[string][]string 443 Pprop *cmakeProcessedProperties 444 }{ 445 &ctx, 446 m, 447 moduleDirs, 448 &pprop, 449 }) 450 android.WriteFileRule(ctx, mainCmakePath, mainContents) 451 452 // Generating CMake extensions 453 extPath := android.PathForModuleGen(ctx, "cmake", "AppendCxxFlagsIfSupported.cmake") 454 makefilesList = append(makefilesList, extPath) 455 android.WriteFileRuleVerbatim(ctx, extPath, cmakeExtAppendFlags) 456 extPath = android.PathForModuleGen(ctx, "cmake", "AddAidlLibrary.cmake") 457 makefilesList = append(makefilesList, extPath) 458 android.WriteFileRuleVerbatim(ctx, extPath, cmakeExtAddAidlLibrary) 459 460 // Generating the final zip file 461 zipRule := android.NewRuleBuilder(pctx, ctx) 462 zipCmd := zipRule.Command(). 463 BuiltTool("soong_zip"). 464 FlagWithOutput("-o ", m.zipPath) 465 466 // Packaging all sources into the zip file 467 if m.Properties.Include_sources { 468 var sourcesList android.Paths 469 for _, file := range android.SortedKeys(sourceFiles) { 470 path := sourceFiles[file] 471 sourcesList = append(sourcesList, path) 472 } 473 474 sourcesRspFile := android.PathForModuleObj(ctx, ctx.ModuleName()+"_sources.rsp") 475 zipCmd.FlagWithRspFileInputList("-r ", sourcesRspFile, sourcesList) 476 } 477 478 // Packaging all make files into the zip file 479 makefilesRspFile := android.PathForModuleObj(ctx, ctx.ModuleName()+"_makefiles.rsp") 480 zipCmd. 481 FlagWithArg("-C ", android.PathForModuleGen(ctx).String()). 482 FlagWithRspFileInputList("-r ", makefilesRspFile, makefilesList) 483 484 // Packaging all prebuilts into the zip file 485 if len(m.Properties.Prebuilts) > 0 { 486 var prebuiltsList android.Paths 487 488 ctx.VisitDirectDepsProxyWithTag(cmakeSnapshotPrebuiltTag, func(dep android.ModuleProxy) { 489 for _, file := range android.OtherModuleProviderOrDefault( 490 ctx, dep, android.InstallFilesProvider).InstallFiles { 491 prebuiltsList = append(prebuiltsList, file) 492 } 493 }) 494 495 prebuiltsRspFile := android.PathForModuleObj(ctx, ctx.ModuleName()+"_prebuilts.rsp") 496 zipCmd. 497 FlagWithArg("-C ", android.PathForArbitraryOutput(ctx).String()). 498 FlagWithArg("-P ", "prebuilts"). 499 FlagWithRspFileInputList("-r ", prebuiltsRspFile, prebuiltsList) 500 } 501 502 // Finish generating the final zip file 503 zipRule.Build(m.zipPath.String(), "archiving "+ctx.ModuleName()) 504 505 ctx.SetOutputFiles(android.Paths{m.zipPath}, "") 506} 507 508func (m *CmakeSnapshot) AndroidMkEntries() []android.AndroidMkEntries { 509 return []android.AndroidMkEntries{{ 510 Class: "DATA", 511 OutputFile: android.OptionalPathForPath(m.zipPath), 512 ExtraEntries: []android.AndroidMkExtraEntriesFunc{ 513 func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) { 514 entries.SetBool("LOCAL_UNINSTALLABLE_MODULE", true) 515 }, 516 }, 517 }} 518} 519 520func getModuleType(info *CcInfo) string { 521 if info.LinkerInfo.BinaryDecoratorInfo != nil { 522 return "executable" 523 } else if info.LinkerInfo.LibraryDecoratorInfo != nil { 524 return "library" 525 } else if info.LinkerInfo.TestBinaryInfo != nil || info.LinkerInfo.BenchmarkDecoratorInfo != nil { 526 return "test" 527 } else if info.LinkerInfo.ObjectLinkerInfo != nil { 528 return "object" 529 } 530 panic(fmt.Sprintf("Unexpected module type for LinkerInfo")) 531} 532 533func getExtraLibs(info *CcInfo) []string { 534 if info.LinkerInfo.TestBinaryInfo != nil { 535 if info.LinkerInfo.TestBinaryInfo.Gtest { 536 return []string{ 537 "libgtest", 538 "libgtest_main", 539 } 540 } 541 } else if info.LinkerInfo.BenchmarkDecoratorInfo != nil { 542 return []string{"libgoogle-benchmark"} 543 } 544 return nil 545} 546 547func getIncludeDirs(ctx android.ModuleContext, m android.ModuleProxy, info *CcInfo) []string { 548 moduleDir := ctx.OtherModuleDir(m) + string(filepath.Separator) 549 if info.CompilerInfo.LibraryDecoratorInfo != nil { 550 return sliceWithPrefix(moduleDir, info.CompilerInfo.LibraryDecoratorInfo.ExportIncludeDirs) 551 } 552 return nil 553} 554 555func cmakeSnapshotLoadHook(ctx android.LoadHookContext) { 556 props := struct { 557 Target struct { 558 Darwin struct { 559 Enabled *bool 560 } 561 Windows struct { 562 Enabled *bool 563 } 564 } 565 }{} 566 props.Target.Darwin.Enabled = proptools.BoolPtr(false) 567 props.Target.Windows.Enabled = proptools.BoolPtr(false) 568 ctx.AppendProperties(&props) 569} 570 571// cmake_snapshot allows defining source packages for release outside of Android build tree. 572// As a result of cmake_snapshot module build, a zip file is generated with CMake build definitions 573// for selected source modules, their dependencies and optionally also the source code itself. 574func CmakeSnapshotFactory() android.Module { 575 module := &CmakeSnapshot{} 576 module.AddProperties(&module.Properties) 577 android.AddLoadHook(module, cmakeSnapshotLoadHook) 578 android.InitAndroidArchModule(module, android.HostSupported, android.MultilibFirst) 579 return module 580} 581 582func init() { 583 android.InitRegistrationContext.RegisterModuleType("cc_cmake_snapshot", CmakeSnapshotFactory) 584} 585