1// Copyright 2020 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 bp2build 16 17/* 18For shareable/common functionality for conversion from soong-module to build files 19for queryview/bp2build 20*/ 21 22import ( 23 "fmt" 24 "reflect" 25 "sort" 26 "strings" 27 28 "android/soong/android" 29 "android/soong/bazel" 30 "android/soong/starlark_fmt" 31 "github.com/google/blueprint" 32 "github.com/google/blueprint/proptools" 33) 34 35type BazelAttributes struct { 36 Attrs map[string]string 37} 38 39type BazelTarget struct { 40 name string 41 packageName string 42 content string 43 ruleClass string 44 bzlLoadLocation string 45} 46 47// IsLoadedFromStarlark determines if the BazelTarget's rule class is loaded from a .bzl file, 48// as opposed to a native rule built into Bazel. 49func (t BazelTarget) IsLoadedFromStarlark() bool { 50 return t.bzlLoadLocation != "" 51} 52 53// Label is the fully qualified Bazel label constructed from the BazelTarget's 54// package name and target name. 55func (t BazelTarget) Label() string { 56 if t.packageName == "." { 57 return "//:" + t.name 58 } else { 59 return "//" + t.packageName + ":" + t.name 60 } 61} 62 63// PackageName returns the package of the Bazel target. 64// Defaults to root of tree. 65func (t BazelTarget) PackageName() string { 66 if t.packageName == "" { 67 return "." 68 } 69 return t.packageName 70} 71 72// BazelTargets is a typedef for a slice of BazelTarget objects. 73type BazelTargets []BazelTarget 74 75func (targets BazelTargets) packageRule() *BazelTarget { 76 for _, target := range targets { 77 if target.ruleClass == "package" { 78 return &target 79 } 80 } 81 return nil 82} 83 84// sort a list of BazelTargets in-place, by name, and by generated/handcrafted types. 85func (targets BazelTargets) sort() { 86 sort.Slice(targets, func(i, j int) bool { 87 return targets[i].name < targets[j].name 88 }) 89} 90 91// String returns the string representation of BazelTargets, without load 92// statements (use LoadStatements for that), since the targets are usually not 93// adjacent to the load statements at the top of the BUILD file. 94func (targets BazelTargets) String() string { 95 var res string 96 for i, target := range targets { 97 if target.ruleClass != "package" { 98 res += target.content 99 } 100 if i != len(targets)-1 { 101 res += "\n\n" 102 } 103 } 104 return res 105} 106 107// LoadStatements return the string representation of the sorted and deduplicated 108// Starlark rule load statements needed by a group of BazelTargets. 109func (targets BazelTargets) LoadStatements() string { 110 bzlToLoadedSymbols := map[string][]string{} 111 for _, target := range targets { 112 if target.IsLoadedFromStarlark() { 113 bzlToLoadedSymbols[target.bzlLoadLocation] = 114 append(bzlToLoadedSymbols[target.bzlLoadLocation], target.ruleClass) 115 } 116 } 117 118 var loadStatements []string 119 for bzl, ruleClasses := range bzlToLoadedSymbols { 120 loadStatement := "load(\"" 121 loadStatement += bzl 122 loadStatement += "\", " 123 ruleClasses = android.SortedUniqueStrings(ruleClasses) 124 for i, ruleClass := range ruleClasses { 125 loadStatement += "\"" + ruleClass + "\"" 126 if i != len(ruleClasses)-1 { 127 loadStatement += ", " 128 } 129 } 130 loadStatement += ")" 131 loadStatements = append(loadStatements, loadStatement) 132 } 133 return strings.Join(android.SortedUniqueStrings(loadStatements), "\n") 134} 135 136type bpToBuildContext interface { 137 ModuleName(module blueprint.Module) string 138 ModuleDir(module blueprint.Module) string 139 ModuleSubDir(module blueprint.Module) string 140 ModuleType(module blueprint.Module) string 141 142 VisitAllModules(visit func(blueprint.Module)) 143 VisitDirectDeps(module blueprint.Module, visit func(blueprint.Module)) 144} 145 146type CodegenContext struct { 147 config android.Config 148 context *android.Context 149 mode CodegenMode 150 additionalDeps []string 151 unconvertedDepMode unconvertedDepsMode 152 topDir string 153} 154 155func (ctx *CodegenContext) Mode() CodegenMode { 156 return ctx.mode 157} 158 159// CodegenMode is an enum to differentiate code-generation modes. 160type CodegenMode int 161 162const ( 163 // Bp2Build - generate BUILD files with targets buildable by Bazel directly. 164 // 165 // This mode is used for the Soong->Bazel build definition conversion. 166 Bp2Build CodegenMode = iota 167 168 // QueryView - generate BUILD files with targets representing fully mutated 169 // Soong modules, representing the fully configured Soong module graph with 170 // variants and dependency edges. 171 // 172 // This mode is used for discovering and introspecting the existing Soong 173 // module graph. 174 QueryView 175 176 // ApiBp2build - generate BUILD files for API contribution targets 177 ApiBp2build 178) 179 180type unconvertedDepsMode int 181 182const ( 183 // Include a warning in conversion metrics about converted modules with unconverted direct deps 184 warnUnconvertedDeps unconvertedDepsMode = iota 185 // Error and fail conversion if encountering a module with unconverted direct deps 186 // Enabled by setting environment variable `BP2BUILD_ERROR_UNCONVERTED` 187 errorModulesUnconvertedDeps 188) 189 190func (mode CodegenMode) String() string { 191 switch mode { 192 case Bp2Build: 193 return "Bp2Build" 194 case QueryView: 195 return "QueryView" 196 case ApiBp2build: 197 return "ApiBp2build" 198 default: 199 return fmt.Sprintf("%d", mode) 200 } 201} 202 203// AddNinjaFileDeps adds dependencies on the specified files to be added to the ninja manifest. The 204// primary builder will be rerun whenever the specified files are modified. Allows us to fulfill the 205// PathContext interface in order to add dependencies on hand-crafted BUILD files. Note: must also 206// call AdditionalNinjaDeps and add them manually to the ninja file. 207func (ctx *CodegenContext) AddNinjaFileDeps(deps ...string) { 208 ctx.additionalDeps = append(ctx.additionalDeps, deps...) 209} 210 211// AdditionalNinjaDeps returns additional ninja deps added by CodegenContext 212func (ctx *CodegenContext) AdditionalNinjaDeps() []string { 213 return ctx.additionalDeps 214} 215 216func (ctx *CodegenContext) Config() android.Config { return ctx.config } 217func (ctx *CodegenContext) Context() *android.Context { return ctx.context } 218 219// NewCodegenContext creates a wrapper context that conforms to PathContext for 220// writing BUILD files in the output directory. 221func NewCodegenContext(config android.Config, context *android.Context, mode CodegenMode, topDir string) *CodegenContext { 222 var unconvertedDeps unconvertedDepsMode 223 if config.IsEnvTrue("BP2BUILD_ERROR_UNCONVERTED") { 224 unconvertedDeps = errorModulesUnconvertedDeps 225 } 226 return &CodegenContext{ 227 context: context, 228 config: config, 229 mode: mode, 230 unconvertedDepMode: unconvertedDeps, 231 topDir: topDir, 232 } 233} 234 235// props is an unsorted map. This function ensures that 236// the generated attributes are sorted to ensure determinism. 237func propsToAttributes(props map[string]string) string { 238 var attributes string 239 for _, propName := range android.SortedKeys(props) { 240 attributes += fmt.Sprintf(" %s = %s,\n", propName, props[propName]) 241 } 242 return attributes 243} 244 245type conversionResults struct { 246 buildFileToTargets map[string]BazelTargets 247 metrics CodegenMetrics 248} 249 250func (r conversionResults) BuildDirToTargets() map[string]BazelTargets { 251 return r.buildFileToTargets 252} 253 254func GenerateBazelTargets(ctx *CodegenContext, generateFilegroups bool) (conversionResults, []error) { 255 buildFileToTargets := make(map[string]BazelTargets) 256 257 // Simple metrics tracking for bp2build 258 metrics := CreateCodegenMetrics() 259 260 dirs := make(map[string]bool) 261 262 var errs []error 263 264 bpCtx := ctx.Context() 265 bpCtx.VisitAllModules(func(m blueprint.Module) { 266 dir := bpCtx.ModuleDir(m) 267 moduleType := bpCtx.ModuleType(m) 268 dirs[dir] = true 269 270 var targets []BazelTarget 271 272 switch ctx.Mode() { 273 case Bp2Build: 274 // There are two main ways of converting a Soong module to Bazel: 275 // 1) Manually handcrafting a Bazel target and associating the module with its label 276 // 2) Automatically generating with bp2build converters 277 // 278 // bp2build converters are used for the majority of modules. 279 if b, ok := m.(android.Bazelable); ok && b.HasHandcraftedLabel() { 280 // Handle modules converted to handcrafted targets. 281 // 282 // Since these modules are associated with some handcrafted 283 // target in a BUILD file, we don't autoconvert them. 284 285 // Log the module. 286 metrics.AddConvertedModule(m, moduleType, dir, Handcrafted) 287 } else if aModule, ok := m.(android.Module); ok && aModule.IsConvertedByBp2build() { 288 // Handle modules converted to generated targets. 289 290 // Log the module. 291 metrics.AddConvertedModule(aModule, moduleType, dir, Generated) 292 293 // Handle modules with unconverted deps. By default, emit a warning. 294 if unconvertedDeps := aModule.GetUnconvertedBp2buildDeps(); len(unconvertedDeps) > 0 { 295 msg := fmt.Sprintf("%s %s:%s depends on unconverted modules: %s", 296 moduleType, bpCtx.ModuleDir(m), m.Name(), strings.Join(unconvertedDeps, ", ")) 297 switch ctx.unconvertedDepMode { 298 case warnUnconvertedDeps: 299 metrics.moduleWithUnconvertedDepsMsgs = append(metrics.moduleWithUnconvertedDepsMsgs, msg) 300 case errorModulesUnconvertedDeps: 301 errs = append(errs, fmt.Errorf(msg)) 302 return 303 } 304 } 305 if unconvertedDeps := aModule.GetMissingBp2buildDeps(); len(unconvertedDeps) > 0 { 306 msg := fmt.Sprintf("%s %s:%s depends on missing modules: %s", 307 moduleType, bpCtx.ModuleDir(m), m.Name(), strings.Join(unconvertedDeps, ", ")) 308 switch ctx.unconvertedDepMode { 309 case warnUnconvertedDeps: 310 metrics.moduleWithMissingDepsMsgs = append(metrics.moduleWithMissingDepsMsgs, msg) 311 case errorModulesUnconvertedDeps: 312 errs = append(errs, fmt.Errorf(msg)) 313 return 314 } 315 } 316 var targetErrs []error 317 targets, targetErrs = generateBazelTargets(bpCtx, aModule) 318 errs = append(errs, targetErrs...) 319 for _, t := range targets { 320 // A module can potentially generate more than 1 Bazel 321 // target, each of a different rule class. 322 metrics.IncrementRuleClassCount(t.ruleClass) 323 } 324 } else if _, ok := ctx.Config().BazelModulesForceEnabledByFlag()[m.Name()]; ok && m.Name() != "" { 325 err := fmt.Errorf("Force Enabled Module %s not converted", m.Name()) 326 errs = append(errs, err) 327 } else { 328 metrics.AddUnconvertedModule(moduleType) 329 return 330 } 331 case QueryView: 332 // Blocklist certain module types from being generated. 333 if canonicalizeModuleType(bpCtx.ModuleType(m)) == "package" { 334 // package module name contain slashes, and thus cannot 335 // be mapped cleanly to a bazel label. 336 return 337 } 338 t, err := generateSoongModuleTarget(bpCtx, m) 339 if err != nil { 340 errs = append(errs, err) 341 } 342 targets = append(targets, t) 343 case ApiBp2build: 344 if aModule, ok := m.(android.Module); ok && aModule.IsConvertedByBp2build() { 345 targets, errs = generateBazelTargets(bpCtx, aModule) 346 } 347 default: 348 errs = append(errs, fmt.Errorf("Unknown code-generation mode: %s", ctx.Mode())) 349 return 350 } 351 352 for _, target := range targets { 353 targetDir := target.PackageName() 354 buildFileToTargets[targetDir] = append(buildFileToTargets[targetDir], target) 355 } 356 }) 357 358 if len(errs) > 0 { 359 return conversionResults{}, errs 360 } 361 362 if generateFilegroups { 363 // Add a filegroup target that exposes all sources in the subtree of this package 364 // NOTE: This also means we generate a BUILD file for every Android.bp file (as long as it has at least one module) 365 // 366 // This works because: https://bazel.build/reference/be/functions#exports_files 367 // "As a legacy behaviour, also files mentioned as input to a rule are exported with the 368 // default visibility until the flag --incompatible_no_implicit_file_export is flipped. However, this behavior 369 // should not be relied upon and actively migrated away from." 370 // 371 // TODO(b/198619163): We should change this to export_files(glob(["**/*"])) instead, but doing that causes these errors: 372 // "Error in exports_files: generated label '//external/avb:avbtool' conflicts with existing py_binary rule" 373 // So we need to solve all the "target ... is both a rule and a file" warnings first. 374 for dir := range dirs { 375 buildFileToTargets[dir] = append(buildFileToTargets[dir], BazelTarget{ 376 name: "bp2build_all_srcs", 377 content: `filegroup(name = "bp2build_all_srcs", srcs = glob(["**/*"]))`, 378 ruleClass: "filegroup", 379 }) 380 } 381 } 382 383 return conversionResults{ 384 buildFileToTargets: buildFileToTargets, 385 metrics: metrics, 386 }, errs 387} 388 389func generateBazelTargets(ctx bpToBuildContext, m android.Module) ([]BazelTarget, []error) { 390 var targets []BazelTarget 391 var errs []error 392 for _, m := range m.Bp2buildTargets() { 393 target, err := generateBazelTarget(ctx, m) 394 if err != nil { 395 errs = append(errs, err) 396 return targets, errs 397 } 398 targets = append(targets, target) 399 } 400 return targets, errs 401} 402 403type bp2buildModule interface { 404 TargetName() string 405 TargetPackage() string 406 BazelRuleClass() string 407 BazelRuleLoadLocation() string 408 BazelAttributes() []interface{} 409} 410 411func generateBazelTarget(ctx bpToBuildContext, m bp2buildModule) (BazelTarget, error) { 412 ruleClass := m.BazelRuleClass() 413 bzlLoadLocation := m.BazelRuleLoadLocation() 414 415 // extract the bazel attributes from the module. 416 attrs := m.BazelAttributes() 417 props, err := extractModuleProperties(attrs, true) 418 if err != nil { 419 return BazelTarget{}, err 420 } 421 422 // name is handled in a special manner 423 delete(props.Attrs, "name") 424 425 // Return the Bazel target with rule class and attributes, ready to be 426 // code-generated. 427 attributes := propsToAttributes(props.Attrs) 428 var content string 429 targetName := m.TargetName() 430 if targetName != "" { 431 content = fmt.Sprintf(ruleTargetTemplate, ruleClass, targetName, attributes) 432 } else { 433 content = fmt.Sprintf(unnamedRuleTargetTemplate, ruleClass, attributes) 434 } 435 return BazelTarget{ 436 name: targetName, 437 packageName: m.TargetPackage(), 438 ruleClass: ruleClass, 439 bzlLoadLocation: bzlLoadLocation, 440 content: content, 441 }, nil 442} 443 444// Convert a module and its deps and props into a Bazel macro/rule 445// representation in the BUILD file. 446func generateSoongModuleTarget(ctx bpToBuildContext, m blueprint.Module) (BazelTarget, error) { 447 props, err := getBuildProperties(ctx, m) 448 449 // TODO(b/163018919): DirectDeps can have duplicate (module, variant) 450 // items, if the modules are added using different DependencyTag. Figure 451 // out the implications of that. 452 depLabels := map[string]bool{} 453 if aModule, ok := m.(android.Module); ok { 454 ctx.VisitDirectDeps(aModule, func(depModule blueprint.Module) { 455 depLabels[qualifiedTargetLabel(ctx, depModule)] = true 456 }) 457 } 458 459 for p := range ignoredPropNames { 460 delete(props.Attrs, p) 461 } 462 attributes := propsToAttributes(props.Attrs) 463 464 depLabelList := "[\n" 465 for depLabel := range depLabels { 466 depLabelList += fmt.Sprintf(" %q,\n", depLabel) 467 } 468 depLabelList += " ]" 469 470 targetName := targetNameWithVariant(ctx, m) 471 return BazelTarget{ 472 name: targetName, 473 packageName: ctx.ModuleDir(m), 474 content: fmt.Sprintf( 475 soongModuleTargetTemplate, 476 targetName, 477 ctx.ModuleName(m), 478 canonicalizeModuleType(ctx.ModuleType(m)), 479 ctx.ModuleSubDir(m), 480 depLabelList, 481 attributes), 482 }, err 483} 484 485func getBuildProperties(ctx bpToBuildContext, m blueprint.Module) (BazelAttributes, error) { 486 // TODO: this omits properties for blueprint modules (blueprint_go_binary, 487 // bootstrap_go_binary, bootstrap_go_package), which will have to be handled separately. 488 if aModule, ok := m.(android.Module); ok { 489 return extractModuleProperties(aModule.GetProperties(), false) 490 } 491 492 return BazelAttributes{}, nil 493} 494 495// Generically extract module properties and types into a map, keyed by the module property name. 496func extractModuleProperties(props []interface{}, checkForDuplicateProperties bool) (BazelAttributes, error) { 497 ret := map[string]string{} 498 499 // Iterate over this android.Module's property structs. 500 for _, properties := range props { 501 propertiesValue := reflect.ValueOf(properties) 502 // Check that propertiesValue is a pointer to the Properties struct, like 503 // *cc.BaseLinkerProperties or *java.CompilerProperties. 504 // 505 // propertiesValue can also be type-asserted to the structs to 506 // manipulate internal props, if needed. 507 if isStructPtr(propertiesValue.Type()) { 508 structValue := propertiesValue.Elem() 509 ok, err := extractStructProperties(structValue, 0) 510 if err != nil { 511 return BazelAttributes{}, err 512 } 513 for k, v := range ok { 514 if existing, exists := ret[k]; checkForDuplicateProperties && exists { 515 return BazelAttributes{}, fmt.Errorf( 516 "%s (%v) is present in properties whereas it should be consolidated into a commonAttributes", 517 k, existing) 518 } 519 ret[k] = v 520 } 521 } else { 522 return BazelAttributes{}, 523 fmt.Errorf( 524 "properties must be a pointer to a struct, got %T", 525 propertiesValue.Interface()) 526 } 527 } 528 529 return BazelAttributes{ 530 Attrs: ret, 531 }, nil 532} 533 534func isStructPtr(t reflect.Type) bool { 535 return t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct 536} 537 538// prettyPrint a property value into the equivalent Starlark representation 539// recursively. 540func prettyPrint(propertyValue reflect.Value, indent int, emitZeroValues bool) (string, error) { 541 if !emitZeroValues && isZero(propertyValue) { 542 // A property value being set or unset actually matters -- Soong does set default 543 // values for unset properties, like system_shared_libs = ["libc", "libm", "libdl"] at 544 // https://cs.android.com/android/platform/superproject/+/master:build/soong/cc/linker.go;l=281-287;drc=f70926eef0b9b57faf04c17a1062ce50d209e480 545 // 546 // In Bazel-parlance, we would use "attr.<type>(default = <default 547 // value>)" to set the default value of unset attributes. In the cases 548 // where the bp2build converter didn't set the default value within the 549 // mutator when creating the BazelTargetModule, this would be a zero 550 // value. For those cases, we return an empty string so we don't 551 // unnecessarily generate empty values. 552 return "", nil 553 } 554 555 switch propertyValue.Kind() { 556 case reflect.String: 557 return fmt.Sprintf("\"%v\"", escapeString(propertyValue.String())), nil 558 case reflect.Bool: 559 return starlark_fmt.PrintBool(propertyValue.Bool()), nil 560 case reflect.Int, reflect.Uint, reflect.Int64: 561 return fmt.Sprintf("%v", propertyValue.Interface()), nil 562 case reflect.Ptr: 563 return prettyPrint(propertyValue.Elem(), indent, emitZeroValues) 564 case reflect.Slice: 565 elements := make([]string, 0, propertyValue.Len()) 566 for i := 0; i < propertyValue.Len(); i++ { 567 val, err := prettyPrint(propertyValue.Index(i), indent, emitZeroValues) 568 if err != nil { 569 return "", err 570 } 571 if val != "" { 572 elements = append(elements, val) 573 } 574 } 575 return starlark_fmt.PrintList(elements, indent, func(s string) string { 576 return "%s" 577 }), nil 578 579 case reflect.Struct: 580 // Special cases where the bp2build sends additional information to the codegenerator 581 // by wrapping the attributes in a custom struct type. 582 if attr, ok := propertyValue.Interface().(bazel.Attribute); ok { 583 return prettyPrintAttribute(attr, indent) 584 } else if label, ok := propertyValue.Interface().(bazel.Label); ok { 585 return fmt.Sprintf("%q", label.Label), nil 586 } 587 588 // Sort and print the struct props by the key. 589 structProps, err := extractStructProperties(propertyValue, indent) 590 591 if err != nil { 592 return "", err 593 } 594 595 if len(structProps) == 0 { 596 return "", nil 597 } 598 return starlark_fmt.PrintDict(structProps, indent), nil 599 case reflect.Interface: 600 // TODO(b/164227191): implement pretty print for interfaces. 601 // Interfaces are used for for arch, multilib and target properties. 602 return "", nil 603 default: 604 return "", fmt.Errorf( 605 "unexpected kind for property struct field: %s", propertyValue.Kind()) 606 } 607} 608 609// Converts a reflected property struct value into a map of property names and property values, 610// which each property value correctly pretty-printed and indented at the right nest level, 611// since property structs can be nested. In Starlark, nested structs are represented as nested 612// dicts: https://docs.bazel.build/skylark/lib/dict.html 613func extractStructProperties(structValue reflect.Value, indent int) (map[string]string, error) { 614 if structValue.Kind() != reflect.Struct { 615 return map[string]string{}, fmt.Errorf("Expected a reflect.Struct type, but got %s", structValue.Kind()) 616 } 617 618 var err error 619 620 ret := map[string]string{} 621 structType := structValue.Type() 622 for i := 0; i < structValue.NumField(); i++ { 623 field := structType.Field(i) 624 if shouldSkipStructField(field) { 625 continue 626 } 627 628 fieldValue := structValue.Field(i) 629 if isZero(fieldValue) { 630 // Ignore zero-valued fields 631 continue 632 } 633 634 // if the struct is embedded (anonymous), flatten the properties into the containing struct 635 if field.Anonymous { 636 if field.Type.Kind() == reflect.Ptr { 637 fieldValue = fieldValue.Elem() 638 } 639 if fieldValue.Type().Kind() == reflect.Struct { 640 propsToMerge, err := extractStructProperties(fieldValue, indent) 641 if err != nil { 642 return map[string]string{}, err 643 } 644 for prop, value := range propsToMerge { 645 ret[prop] = value 646 } 647 continue 648 } 649 } 650 651 propertyName := proptools.PropertyNameForField(field.Name) 652 var prettyPrintedValue string 653 prettyPrintedValue, err = prettyPrint(fieldValue, indent+1, false) 654 if err != nil { 655 return map[string]string{}, fmt.Errorf( 656 "Error while parsing property: %q. %s", 657 propertyName, 658 err) 659 } 660 if prettyPrintedValue != "" { 661 ret[propertyName] = prettyPrintedValue 662 } 663 } 664 665 return ret, nil 666} 667 668func isZero(value reflect.Value) bool { 669 switch value.Kind() { 670 case reflect.Func, reflect.Map, reflect.Slice: 671 return value.IsNil() 672 case reflect.Array: 673 valueIsZero := true 674 for i := 0; i < value.Len(); i++ { 675 valueIsZero = valueIsZero && isZero(value.Index(i)) 676 } 677 return valueIsZero 678 case reflect.Struct: 679 valueIsZero := true 680 for i := 0; i < value.NumField(); i++ { 681 valueIsZero = valueIsZero && isZero(value.Field(i)) 682 } 683 return valueIsZero 684 case reflect.Ptr: 685 if !value.IsNil() { 686 return isZero(reflect.Indirect(value)) 687 } else { 688 return true 689 } 690 // Always print bool/strings, if you want a bool/string attribute to be able to take the default value, use a 691 // pointer instead 692 case reflect.Bool, reflect.String: 693 return false 694 default: 695 if !value.IsValid() { 696 return true 697 } 698 zeroValue := reflect.Zero(value.Type()) 699 result := value.Interface() == zeroValue.Interface() 700 return result 701 } 702} 703 704func escapeString(s string) string { 705 s = strings.ReplaceAll(s, "\\", "\\\\") 706 707 // b/184026959: Reverse the application of some common control sequences. 708 // These must be generated literally in the BUILD file. 709 s = strings.ReplaceAll(s, "\t", "\\t") 710 s = strings.ReplaceAll(s, "\n", "\\n") 711 s = strings.ReplaceAll(s, "\r", "\\r") 712 713 return strings.ReplaceAll(s, "\"", "\\\"") 714} 715 716func targetNameWithVariant(c bpToBuildContext, logicModule blueprint.Module) string { 717 name := "" 718 if c.ModuleSubDir(logicModule) != "" { 719 // TODO(b/162720883): Figure out a way to drop the "--" variant suffixes. 720 name = c.ModuleName(logicModule) + "--" + c.ModuleSubDir(logicModule) 721 } else { 722 name = c.ModuleName(logicModule) 723 } 724 725 return strings.Replace(name, "//", "", 1) 726} 727 728func qualifiedTargetLabel(c bpToBuildContext, logicModule blueprint.Module) string { 729 return fmt.Sprintf("//%s:%s", c.ModuleDir(logicModule), targetNameWithVariant(c, logicModule)) 730} 731