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