1// Copyright 2021 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 android 16 17import ( 18 "bufio" 19 "errors" 20 "strings" 21 22 "github.com/google/blueprint" 23 "github.com/google/blueprint/proptools" 24 25 "android/soong/android/allowlists" 26) 27 28const ( 29 // A sentinel value to be used as a key in Bp2BuildConfig for modules with 30 // no package path. This is also the module dir for top level Android.bp 31 // modules. 32 Bp2BuildTopLevel = "." 33) 34 35// FileGroupAsLibrary describes a filegroup module that is converted to some library 36// such as aidl_library or proto_library. 37type FileGroupAsLibrary interface { 38 ShouldConvertToAidlLibrary(ctx BazelConversionPathContext) bool 39 ShouldConvertToProtoLibrary(ctx BazelConversionPathContext) bool 40 GetAidlLibraryLabel(ctx BazelConversionPathContext) string 41 GetProtoLibraryLabel(ctx BazelConversionPathContext) string 42} 43 44type BazelConversionStatus struct { 45 // Information about _all_ bp2build targets generated by this module. Multiple targets are 46 // supported as Soong handles some things within a single target that we may choose to split into 47 // multiple targets, e.g. renderscript, protos, yacc within a cc module. 48 Bp2buildInfo []bp2buildInfo `blueprint:"mutated"` 49 50 // UnconvertedBp2buildDep stores the module names of direct dependency that were not converted to 51 // Bazel 52 UnconvertedDeps []string `blueprint:"mutated"` 53 54 // MissingBp2buildDep stores the module names of direct dependency that were not found 55 MissingDeps []string `blueprint:"mutated"` 56} 57 58type bazelModuleProperties struct { 59 // The label of the Bazel target replacing this Soong module. When run in conversion mode, this 60 // will import the handcrafted build target into the autogenerated file. Note: this may result in 61 // a conflict due to duplicate targets if bp2build_available is also set. 62 Label *string 63 64 // If true, bp2build will generate the converted Bazel target for this module. Note: this may 65 // cause a conflict due to the duplicate targets if label is also set. 66 // 67 // This is a bool pointer to support tristates: true, false, not set. 68 // 69 // To opt in a module, set bazel_module: { bp2build_available: true } 70 // To opt out a module, set bazel_module: { bp2build_available: false } 71 // To defer the default setting for the directory, do not set the value. 72 Bp2build_available *bool 73 74 // CanConvertToBazel is set via InitBazelModule to indicate that a module type can be converted to 75 // Bazel with Bp2build. 76 CanConvertToBazel bool `blueprint:"mutated"` 77} 78 79// Properties contains common module properties for Bazel migration purposes. 80type properties struct { 81 // In "Bazel mixed build" mode, this represents the Bazel target replacing 82 // this Soong module. 83 Bazel_module bazelModuleProperties 84} 85 86// namespacedVariableProperties is a map from a string representing a Soong 87// config variable namespace, like "android" or "vendor_name" to a slice of 88// pointer to a struct containing a single field called Soong_config_variables 89// whose value mirrors the structure in the Blueprint file. 90type namespacedVariableProperties map[string][]interface{} 91 92// BazelModuleBase contains the property structs with metadata for modules which can be converted to 93// Bazel. 94type BazelModuleBase struct { 95 bazelProperties properties 96 97 // namespacedVariableProperties is used for soong_config_module_type support 98 // in bp2build. Soong config modules allow users to set module properties 99 // based on custom product variables defined in Android.bp files. These 100 // variables are namespaced to prevent clobbering, especially when set from 101 // Makefiles. 102 namespacedVariableProperties namespacedVariableProperties 103 104 // baseModuleType is set when this module was created from a module type 105 // defined by a soong_config_module_type. Every soong_config_module_type 106 // "wraps" another module type, e.g. a soong_config_module_type can wrap a 107 // cc_defaults to a custom_cc_defaults, or cc_binary to a custom_cc_binary. 108 // This baseModuleType is set to the wrapped module type. 109 baseModuleType string 110} 111 112// Bazelable is specifies the interface for modules that can be converted to Bazel. 113type Bazelable interface { 114 bazelProps() *properties 115 HasHandcraftedLabel() bool 116 HandcraftedLabel() string 117 GetBazelLabel(ctx BazelConversionPathContext, module blueprint.Module) string 118 ShouldConvertWithBp2build(ctx BazelConversionContext) bool 119 shouldConvertWithBp2build(ctx bazelOtherModuleContext, module blueprint.Module) bool 120 ConvertWithBp2build(ctx TopDownMutatorContext) 121 122 // namespacedVariableProps is a map from a soong config variable namespace 123 // (e.g. acme, android) to a map of interfaces{}, which are really 124 // reflect.Struct pointers, representing the value of the 125 // soong_config_variables property of a module. The struct pointer is the 126 // one with the single member called Soong_config_variables, which itself is 127 // a struct containing fields for each supported feature in that namespace. 128 // 129 // The reason for using a slice of interface{} is to support defaults 130 // propagation of the struct pointers. 131 namespacedVariableProps() namespacedVariableProperties 132 setNamespacedVariableProps(props namespacedVariableProperties) 133 BaseModuleType() string 134 SetBaseModuleType(baseModuleType string) 135} 136 137// ApiProvider is implemented by modules that contribute to an API surface 138type ApiProvider interface { 139 ConvertWithApiBp2build(ctx TopDownMutatorContext) 140} 141 142// MixedBuildBuildable is an interface that module types should implement in order 143// to be "handled by Bazel" in a mixed build. 144type MixedBuildBuildable interface { 145 // IsMixedBuildSupported returns true if and only if this module should be 146 // "handled by Bazel" in a mixed build. 147 // This "escape hatch" allows modules with corner-case scenarios to opt out 148 // of being built with Bazel. 149 IsMixedBuildSupported(ctx BaseModuleContext) bool 150 151 // QueueBazelCall invokes request-queueing functions on the BazelContext 152 // so that these requests are handled when Bazel's cquery is invoked. 153 QueueBazelCall(ctx BaseModuleContext) 154 155 // ProcessBazelQueryResponse uses Bazel information (obtained from the BazelContext) 156 // to set module fields and providers to propagate this module's metadata upstream. 157 // This effectively "bridges the gap" between Bazel and Soong in a mixed build. 158 // Soong modules depending on this module should be oblivious to the fact that 159 // this module was handled by Bazel. 160 ProcessBazelQueryResponse(ctx ModuleContext) 161} 162 163// BazelModule is a lightweight wrapper interface around Module for Bazel-convertible modules. 164type BazelModule interface { 165 Module 166 Bazelable 167} 168 169// InitBazelModule is a wrapper function that decorates a BazelModule with Bazel-conversion 170// properties. 171func InitBazelModule(module BazelModule) { 172 module.AddProperties(module.bazelProps()) 173 module.bazelProps().Bazel_module.CanConvertToBazel = true 174} 175 176// bazelProps returns the Bazel properties for the given BazelModuleBase. 177func (b *BazelModuleBase) bazelProps() *properties { 178 return &b.bazelProperties 179} 180 181func (b *BazelModuleBase) namespacedVariableProps() namespacedVariableProperties { 182 return b.namespacedVariableProperties 183} 184 185func (b *BazelModuleBase) setNamespacedVariableProps(props namespacedVariableProperties) { 186 b.namespacedVariableProperties = props 187} 188 189func (b *BazelModuleBase) BaseModuleType() string { 190 return b.baseModuleType 191} 192 193func (b *BazelModuleBase) SetBaseModuleType(baseModuleType string) { 194 b.baseModuleType = baseModuleType 195} 196 197// HasHandcraftedLabel returns whether this module has a handcrafted Bazel label. 198func (b *BazelModuleBase) HasHandcraftedLabel() bool { 199 return b.bazelProperties.Bazel_module.Label != nil 200} 201 202// HandcraftedLabel returns the handcrafted label for this module, or empty string if there is none 203func (b *BazelModuleBase) HandcraftedLabel() string { 204 return proptools.String(b.bazelProperties.Bazel_module.Label) 205} 206 207// GetBazelLabel returns the Bazel label for the given BazelModuleBase. 208func (b *BazelModuleBase) GetBazelLabel(ctx BazelConversionPathContext, module blueprint.Module) string { 209 if b.HasHandcraftedLabel() { 210 return b.HandcraftedLabel() 211 } 212 if b.ShouldConvertWithBp2build(ctx) { 213 return bp2buildModuleLabel(ctx, module) 214 } 215 return "" // no label for unconverted module 216} 217 218type Bp2BuildConversionAllowlist struct { 219 // Configure modules in these directories to enable bp2build_available: true or false by default. 220 defaultConfig allowlists.Bp2BuildConfig 221 222 // Keep any existing BUILD files (and do not generate new BUILD files) for these directories 223 // in the synthetic Bazel workspace. 224 keepExistingBuildFile map[string]bool 225 226 // Per-module allowlist to always opt modules into both bp2build and Bazel Dev Mode mixed 227 // builds. These modules are usually in directories with many other modules that are not ready 228 // for conversion. 229 // 230 // A module can either be in this list or its directory allowlisted entirely 231 // in bp2buildDefaultConfig, but not both at the same time. 232 moduleAlwaysConvert map[string]bool 233 234 // Per-module-type allowlist to always opt modules in to both bp2build and 235 // Bazel Dev Mode mixed builds when they have the same type as one listed. 236 moduleTypeAlwaysConvert map[string]bool 237 238 // Per-module denylist to always opt modules out of bp2build conversion. 239 moduleDoNotConvert map[string]bool 240} 241 242// NewBp2BuildAllowlist creates a new, empty Bp2BuildConversionAllowlist 243// which can be populated using builder pattern Set* methods 244func NewBp2BuildAllowlist() Bp2BuildConversionAllowlist { 245 return Bp2BuildConversionAllowlist{ 246 allowlists.Bp2BuildConfig{}, 247 map[string]bool{}, 248 map[string]bool{}, 249 map[string]bool{}, 250 map[string]bool{}, 251 } 252} 253 254// SetDefaultConfig copies the entries from defaultConfig into the allowlist 255func (a Bp2BuildConversionAllowlist) SetDefaultConfig(defaultConfig allowlists.Bp2BuildConfig) Bp2BuildConversionAllowlist { 256 if a.defaultConfig == nil { 257 a.defaultConfig = allowlists.Bp2BuildConfig{} 258 } 259 for k, v := range defaultConfig { 260 a.defaultConfig[k] = v 261 } 262 263 return a 264} 265 266// SetKeepExistingBuildFile copies the entries from keepExistingBuildFile into the allowlist 267func (a Bp2BuildConversionAllowlist) SetKeepExistingBuildFile(keepExistingBuildFile map[string]bool) Bp2BuildConversionAllowlist { 268 if a.keepExistingBuildFile == nil { 269 a.keepExistingBuildFile = map[string]bool{} 270 } 271 for k, v := range keepExistingBuildFile { 272 a.keepExistingBuildFile[k] = v 273 } 274 275 return a 276} 277 278// SetModuleAlwaysConvertList copies the entries from moduleAlwaysConvert into the allowlist 279func (a Bp2BuildConversionAllowlist) SetModuleAlwaysConvertList(moduleAlwaysConvert []string) Bp2BuildConversionAllowlist { 280 if a.moduleAlwaysConvert == nil { 281 a.moduleAlwaysConvert = map[string]bool{} 282 } 283 for _, m := range moduleAlwaysConvert { 284 a.moduleAlwaysConvert[m] = true 285 } 286 287 return a 288} 289 290// SetModuleTypeAlwaysConvertList copies the entries from moduleTypeAlwaysConvert into the allowlist 291func (a Bp2BuildConversionAllowlist) SetModuleTypeAlwaysConvertList(moduleTypeAlwaysConvert []string) Bp2BuildConversionAllowlist { 292 if a.moduleTypeAlwaysConvert == nil { 293 a.moduleTypeAlwaysConvert = map[string]bool{} 294 } 295 for _, m := range moduleTypeAlwaysConvert { 296 a.moduleTypeAlwaysConvert[m] = true 297 } 298 299 return a 300} 301 302// SetModuleDoNotConvertList copies the entries from moduleDoNotConvert into the allowlist 303func (a Bp2BuildConversionAllowlist) SetModuleDoNotConvertList(moduleDoNotConvert []string) Bp2BuildConversionAllowlist { 304 if a.moduleDoNotConvert == nil { 305 a.moduleDoNotConvert = map[string]bool{} 306 } 307 for _, m := range moduleDoNotConvert { 308 a.moduleDoNotConvert[m] = true 309 } 310 311 return a 312} 313 314// ShouldKeepExistingBuildFileForDir returns whether an existing BUILD file should be 315// added to the build symlink forest based on the current global configuration. 316func (a Bp2BuildConversionAllowlist) ShouldKeepExistingBuildFileForDir(dir string) bool { 317 if _, ok := a.keepExistingBuildFile[dir]; ok { 318 // Exact dir match 319 return true 320 } 321 var i int 322 // Check if subtree match 323 for { 324 j := strings.Index(dir[i:], "/") 325 if j == -1 { 326 return false //default 327 } 328 prefix := dir[0 : i+j] 329 i = i + j + 1 // skip the "/" 330 if recursive, ok := a.keepExistingBuildFile[prefix]; ok && recursive { 331 return true 332 } 333 } 334} 335 336var bp2BuildAllowListKey = NewOnceKey("Bp2BuildAllowlist") 337var bp2buildAllowlist OncePer 338 339func GetBp2BuildAllowList() Bp2BuildConversionAllowlist { 340 return bp2buildAllowlist.Once(bp2BuildAllowListKey, func() interface{} { 341 return NewBp2BuildAllowlist().SetDefaultConfig(allowlists.Bp2buildDefaultConfig). 342 SetKeepExistingBuildFile(allowlists.Bp2buildKeepExistingBuildFile). 343 SetModuleAlwaysConvertList(allowlists.Bp2buildModuleAlwaysConvertList). 344 SetModuleTypeAlwaysConvertList(allowlists.Bp2buildModuleTypeAlwaysConvertList). 345 SetModuleDoNotConvertList(allowlists.Bp2buildModuleDoNotConvertList) 346 }).(Bp2BuildConversionAllowlist) 347} 348 349// MixedBuildsEnabled returns true if a module is ready to be replaced by a 350// converted or handcrafted Bazel target. As a side effect, calling this 351// method will also log whether this module is mixed build enabled for 352// metrics reporting. 353func MixedBuildsEnabled(ctx BaseModuleContext) bool { 354 module := ctx.Module() 355 apexInfo := ctx.Provider(ApexInfoProvider).(ApexInfo) 356 withinApex := !apexInfo.IsForPlatform() 357 mixedBuildEnabled := ctx.Config().IsMixedBuildsEnabled() && 358 ctx.Os() != Windows && // Windows toolchains are not currently supported. 359 ctx.Os() != LinuxBionic && // Linux Bionic toolchains are not currently supported. 360 ctx.Os() != LinuxMusl && // Linux musl toolchains are not currently supported (b/259266326). 361 ctx.Arch().ArchType != Riscv64 && // TODO(b/262192655) Riscv64 toolchains are not currently supported. 362 module.Enabled() && 363 convertedToBazel(ctx, module) && 364 ctx.Config().BazelContext.IsModuleNameAllowed(module.Name(), withinApex) 365 ctx.Config().LogMixedBuild(ctx, mixedBuildEnabled) 366 return mixedBuildEnabled 367} 368 369// ConvertedToBazel returns whether this module has been converted (with bp2build or manually) to Bazel. 370func convertedToBazel(ctx BazelConversionContext, module blueprint.Module) bool { 371 b, ok := module.(Bazelable) 372 if !ok { 373 return false 374 } 375 return b.shouldConvertWithBp2build(ctx, module) || b.HasHandcraftedLabel() 376} 377 378// ShouldConvertWithBp2build returns whether the given BazelModuleBase should be converted with bp2build 379func (b *BazelModuleBase) ShouldConvertWithBp2build(ctx BazelConversionContext) bool { 380 return b.shouldConvertWithBp2build(ctx, ctx.Module()) 381} 382 383type bazelOtherModuleContext interface { 384 ModuleErrorf(format string, args ...interface{}) 385 Config() Config 386 OtherModuleType(m blueprint.Module) string 387 OtherModuleName(m blueprint.Module) string 388 OtherModuleDir(m blueprint.Module) string 389} 390 391func (b *BazelModuleBase) shouldConvertWithBp2build(ctx bazelOtherModuleContext, module blueprint.Module) bool { 392 if !b.bazelProps().Bazel_module.CanConvertToBazel { 393 return false 394 } 395 396 // In api_bp2build mode, all soong modules that can provide API contributions should be converted 397 // This is irrespective of its presence/absence in bp2build allowlists 398 if ctx.Config().BuildMode == ApiBp2build { 399 _, providesApis := module.(ApiProvider) 400 return providesApis 401 } 402 403 propValue := b.bazelProperties.Bazel_module.Bp2build_available 404 packagePath := ctx.OtherModuleDir(module) 405 406 // Modules in unit tests which are enabled in the allowlist by type or name 407 // trigger this conditional because unit tests run under the "." package path 408 isTestModule := packagePath == Bp2BuildTopLevel && proptools.BoolDefault(propValue, false) 409 if isTestModule { 410 return true 411 } 412 413 moduleName := module.Name() 414 allowlist := ctx.Config().Bp2buildPackageConfig 415 moduleNameAllowed := allowlist.moduleAlwaysConvert[moduleName] 416 moduleTypeAllowed := allowlist.moduleTypeAlwaysConvert[ctx.OtherModuleType(module)] 417 allowlistConvert := moduleNameAllowed || moduleTypeAllowed 418 if moduleNameAllowed && moduleTypeAllowed { 419 ctx.ModuleErrorf("A module cannot be in moduleAlwaysConvert and also be in moduleTypeAlwaysConvert") 420 return false 421 } 422 423 if allowlist.moduleDoNotConvert[moduleName] { 424 if moduleNameAllowed { 425 ctx.ModuleErrorf("a module cannot be in moduleDoNotConvert and also be in moduleAlwaysConvert") 426 } 427 return false 428 } 429 430 // This is a tristate value: true, false, or unset. 431 if ok, directoryPath := bp2buildDefaultTrueRecursively(packagePath, allowlist.defaultConfig); ok { 432 if moduleNameAllowed { 433 ctx.ModuleErrorf("A module cannot be in a directory marked Bp2BuildDefaultTrue"+ 434 " or Bp2BuildDefaultTrueRecursively and also be in moduleAlwaysConvert. Directory: '%s'"+ 435 " Module: '%s'", directoryPath, moduleName) 436 return false 437 } 438 439 // Allow modules to explicitly opt-out. 440 return proptools.BoolDefault(propValue, true) 441 } 442 443 // Allow modules to explicitly opt-in. 444 return proptools.BoolDefault(propValue, allowlistConvert) 445} 446 447// bp2buildDefaultTrueRecursively checks that the package contains a prefix from the 448// set of package prefixes where all modules must be converted. That is, if the 449// package is x/y/z, and the list contains either x, x/y, or x/y/z, this function will 450// return true. 451// 452// However, if the package is x/y, and it matches a Bp2BuildDefaultFalse "x/y" entry 453// exactly, this module will return false early. 454// 455// This function will also return false if the package doesn't match anything in 456// the config. 457// 458// This function will also return the allowlist entry which caused a particular 459// package to be enabled. Since packages can be enabled via a recursive declaration, 460// the path returned will not always be the same as the one provided. 461func bp2buildDefaultTrueRecursively(packagePath string, config allowlists.Bp2BuildConfig) (bool, string) { 462 // Check if the package path has an exact match in the config. 463 if config[packagePath] == allowlists.Bp2BuildDefaultTrue || config[packagePath] == allowlists.Bp2BuildDefaultTrueRecursively { 464 return true, packagePath 465 } else if config[packagePath] == allowlists.Bp2BuildDefaultFalse || config[packagePath] == allowlists.Bp2BuildDefaultFalseRecursively { 466 return false, packagePath 467 } 468 469 // If not, check for the config recursively. 470 packagePrefix := packagePath 471 472 // e.g. for x/y/z, iterate over x/y, then x, taking the most-specific value from the allowlist. 473 for strings.Contains(packagePrefix, "/") { 474 dirIndex := strings.LastIndex(packagePrefix, "/") 475 packagePrefix = packagePrefix[:dirIndex] 476 switch value := config[packagePrefix]; value { 477 case allowlists.Bp2BuildDefaultTrueRecursively: 478 // package contains this prefix and this prefix should convert all modules 479 return true, packagePrefix 480 case allowlists.Bp2BuildDefaultFalseRecursively: 481 //package contains this prefix and this prefix should NOT convert any modules 482 return false, packagePrefix 483 } 484 // Continue to the next part of the package dir. 485 486 } 487 488 return false, packagePath 489} 490 491func registerBp2buildConversionMutator(ctx RegisterMutatorsContext) { 492 ctx.TopDown("bp2build_conversion", convertWithBp2build).Parallel() 493} 494 495func convertWithBp2build(ctx TopDownMutatorContext) { 496 bModule, ok := ctx.Module().(Bazelable) 497 if !ok || !bModule.shouldConvertWithBp2build(ctx, ctx.Module()) { 498 return 499 } 500 501 bModule.ConvertWithBp2build(ctx) 502} 503 504func registerApiBp2buildConversionMutator(ctx RegisterMutatorsContext) { 505 ctx.TopDown("apiBp2build_conversion", convertWithApiBp2build).Parallel() 506} 507 508// Generate API contribution targets if the Soong module provides APIs 509func convertWithApiBp2build(ctx TopDownMutatorContext) { 510 if m, ok := ctx.Module().(ApiProvider); ok { 511 m.ConvertWithApiBp2build(ctx) 512 } 513} 514 515// GetMainClassInManifest scans the manifest file specified in filepath and returns 516// the value of attribute Main-Class in the manifest file if it exists, or returns error. 517// WARNING: this is for bp2build converters of java_* modules only. 518func GetMainClassInManifest(c Config, filepath string) (string, error) { 519 file, err := c.fs.Open(filepath) 520 if err != nil { 521 return "", err 522 } 523 defer file.Close() 524 scanner := bufio.NewScanner(file) 525 for scanner.Scan() { 526 line := scanner.Text() 527 if strings.HasPrefix(line, "Main-Class:") { 528 return strings.TrimSpace(line[len("Main-Class:"):]), nil 529 } 530 } 531 532 return "", errors.New("Main-Class is not found.") 533} 534 535func AttachValidationActions(ctx ModuleContext, outputFilePath Path, validations Paths) ModuleOutPath { 536 validatedOutputFilePath := PathForModuleOut(ctx, "validated", outputFilePath.Base()) 537 ctx.Build(pctx, BuildParams{ 538 Rule: CpNoPreserveSymlink, 539 Description: "run validations " + outputFilePath.Base(), 540 Output: validatedOutputFilePath, 541 Input: outputFilePath, 542 Validations: validations, 543 }) 544 return validatedOutputFilePath 545} 546