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