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 android 16 17import ( 18 "bytes" 19 "fmt" 20 "os" 21 "os/exec" 22 "path" 23 "path/filepath" 24 "runtime" 25 "sort" 26 "strings" 27 "sync" 28 29 "android/soong/android/allowlists" 30 "android/soong/bazel/cquery" 31 "android/soong/shared" 32 "android/soong/starlark_fmt" 33 34 "github.com/google/blueprint" 35 "github.com/google/blueprint/metrics" 36 37 "android/soong/bazel" 38) 39 40var ( 41 _ = pctx.HostBinToolVariable("bazelBuildRunfilesTool", "build-runfiles") 42 buildRunfilesRule = pctx.AndroidStaticRule("bazelBuildRunfiles", blueprint.RuleParams{ 43 Command: "${bazelBuildRunfilesTool} ${in} ${outDir}", 44 Depfile: "", 45 Description: "", 46 CommandDeps: []string{"${bazelBuildRunfilesTool}"}, 47 }, "outDir") 48 allowedBazelEnvironmentVars = []string{ 49 // clang-tidy 50 "ALLOW_LOCAL_TIDY_TRUE", 51 "DEFAULT_TIDY_HEADER_DIRS", 52 "TIDY_TIMEOUT", 53 "WITH_TIDY", 54 "WITH_TIDY_FLAGS", 55 "TIDY_EXTERNAL_VENDOR", 56 57 "SKIP_ABI_CHECKS", 58 "UNSAFE_DISABLE_APEX_ALLOWED_DEPS_CHECK", 59 "AUTO_ZERO_INITIALIZE", 60 "AUTO_PATTERN_INITIALIZE", 61 "AUTO_UNINITIALIZE", 62 "USE_CCACHE", 63 "LLVM_NEXT", 64 "ALLOW_UNKNOWN_WARNING_OPTION", 65 66 "UNBUNDLED_BUILD_TARGET_SDK_WITH_API_FINGERPRINT", 67 68 // Overrides the version in the apex_manifest.json. The version is unique for 69 // each branch (internal, aosp, mainline releases, dessert releases). This 70 // enables modules built on an older branch to be installed against a newer 71 // device for development purposes. 72 "OVERRIDE_APEX_MANIFEST_DEFAULT_VERSION", 73 } 74) 75 76func init() { 77 RegisterMixedBuildsMutator(InitRegistrationContext) 78} 79 80func RegisterMixedBuildsMutator(ctx RegistrationContext) { 81 ctx.FinalDepsMutators(func(ctx RegisterMutatorsContext) { 82 ctx.BottomUp("mixed_builds_prep", mixedBuildsPrepareMutator).Parallel() 83 }) 84} 85 86func mixedBuildsPrepareMutator(ctx BottomUpMutatorContext) { 87 if m := ctx.Module(); m.Enabled() { 88 if mixedBuildMod, ok := m.(MixedBuildBuildable); ok { 89 queueMixedBuild := mixedBuildMod.IsMixedBuildSupported(ctx) && MixedBuildsEnabled(ctx) 90 if queueMixedBuild { 91 mixedBuildMod.QueueBazelCall(ctx) 92 } else if _, ok := ctx.Config().bazelForceEnabledModules[m.Name()]; ok { 93 // TODO(b/273910287) - remove this once --ensure_allowlist_integrity is added 94 ctx.ModuleErrorf("Attempted to force enable an unready module: %s. Did you forget to Bp2BuildDefaultTrue its directory?\n", m.Name()) 95 } 96 } 97 } 98} 99 100type cqueryRequest interface { 101 // Name returns a string name for this request type. Such request type names must be unique, 102 // and must only consist of alphanumeric characters. 103 Name() string 104 105 // StarlarkFunctionBody returns a starlark function body to process this request type. 106 // The returned string is the body of a Starlark function which obtains 107 // all request-relevant information about a target and returns a string containing 108 // this information. 109 // The function should have the following properties: 110 // - The arguments are `target` (a configured target) and `id_string` (the label + configuration). 111 // - The return value must be a string. 112 // - The function body should not be indented outside of its own scope. 113 StarlarkFunctionBody() string 114} 115 116// Portion of cquery map key to describe target configuration. 117type configKey struct { 118 arch string 119 osType OsType 120 apexKey ApexConfigKey 121} 122 123type ApexConfigKey struct { 124 WithinApex bool 125 ApexSdkVersion string 126} 127 128func (c ApexConfigKey) String() string { 129 return fmt.Sprintf("%s_%s", withinApexToString(c.WithinApex), c.ApexSdkVersion) 130} 131 132func withinApexToString(withinApex bool) string { 133 if withinApex { 134 return "within_apex" 135 } 136 return "" 137} 138 139func (c configKey) String() string { 140 return fmt.Sprintf("%s::%s::%s", c.arch, c.osType, c.apexKey) 141} 142 143// Map key to describe bazel cquery requests. 144type cqueryKey struct { 145 label string 146 requestType cqueryRequest 147 configKey configKey 148} 149 150func makeCqueryKey(label string, cqueryRequest cqueryRequest, cfgKey configKey) cqueryKey { 151 if strings.HasPrefix(label, "//") { 152 // Normalize Bazel labels to specify main repository explicitly. 153 label = "@" + label 154 } 155 return cqueryKey{label, cqueryRequest, cfgKey} 156} 157 158func (c cqueryKey) String() string { 159 return fmt.Sprintf("cquery(%s,%s,%s)", c.label, c.requestType.Name(), c.configKey) 160} 161 162type invokeBazelContext interface { 163 GetEventHandler() *metrics.EventHandler 164} 165 166// BazelContext is a context object useful for interacting with Bazel during 167// the course of a build. Use of Bazel to evaluate part of the build graph 168// is referred to as a "mixed build". (Some modules are managed by Soong, 169// some are managed by Bazel). To facilitate interop between these build 170// subgraphs, Soong may make requests to Bazel and evaluate their responses 171// so that Soong modules may accurately depend on Bazel targets. 172type BazelContext interface { 173 // Add a cquery request to the bazel request queue. All queued requests 174 // will be sent to Bazel on a subsequent invocation of InvokeBazel. 175 QueueBazelRequest(label string, requestType cqueryRequest, cfgKey configKey) 176 177 // ** Cquery Results Retrieval Functions 178 // The below functions pertain to retrieving cquery results from a prior 179 // InvokeBazel function call and parsing the results. 180 181 // Returns result files built by building the given bazel target label. 182 GetOutputFiles(label string, cfgKey configKey) ([]string, error) 183 184 // Returns the results of GetOutputFiles and GetCcObjectFiles in a single query (in that order). 185 GetCcInfo(label string, cfgKey configKey) (cquery.CcInfo, error) 186 187 // Returns the executable binary resultant from building together the python sources 188 // TODO(b/232976601): Remove. 189 GetPythonBinary(label string, cfgKey configKey) (string, error) 190 191 // Returns the results of the GetApexInfo query (including output files) 192 GetApexInfo(label string, cfgkey configKey) (cquery.ApexInfo, error) 193 194 // Returns the results of the GetCcUnstrippedInfo query 195 GetCcUnstrippedInfo(label string, cfgkey configKey) (cquery.CcUnstrippedInfo, error) 196 197 // ** end Cquery Results Retrieval Functions 198 199 // Issues commands to Bazel to receive results for all cquery requests 200 // queued in the BazelContext. The ctx argument is optional and is only 201 // used for performance data collection 202 InvokeBazel(config Config, ctx invokeBazelContext) error 203 204 // Returns true if Bazel handling is enabled for the module with the given name. 205 // Note that this only implies "bazel mixed build" allowlisting. The caller 206 // should independently verify the module is eligible for Bazel handling 207 // (for example, that it is MixedBuildBuildable). 208 IsModuleNameAllowed(moduleName string, withinApex bool) bool 209 210 IsModuleDclaAllowed(moduleName string) bool 211 212 // Returns the bazel output base (the root directory for all bazel intermediate outputs). 213 OutputBase() string 214 215 // Returns build statements which should get registered to reflect Bazel's outputs. 216 BuildStatementsToRegister() []*bazel.BuildStatement 217 218 // Returns the depsets defined in Bazel's aquery response. 219 AqueryDepsets() []bazel.AqueryDepset 220} 221 222type bazelRunner interface { 223 createBazelCommand(config Config, paths *bazelPaths, runName bazel.RunName, command bazelCommand, extraFlags ...string) *exec.Cmd 224 issueBazelCommand(bazelCmd *exec.Cmd, eventHandler *metrics.EventHandler) (output string, errorMessage string, error error) 225} 226 227type bazelPaths struct { 228 homeDir string 229 bazelPath string 230 outputBase string 231 workspaceDir string 232 soongOutDir string 233 metricsDir string 234 bazelDepsFile string 235} 236 237// A context object which tracks queued requests that need to be made to Bazel, 238// and their results after the requests have been made. 239type mixedBuildBazelContext struct { 240 bazelRunner 241 paths *bazelPaths 242 // cquery requests that have not yet been issued to Bazel. This list is maintained 243 // in a sorted state, and is guaranteed to have no duplicates. 244 requests []cqueryKey 245 requestMutex sync.Mutex // requests can be written in parallel 246 247 results map[cqueryKey]string // Results of cquery requests after Bazel invocations 248 249 // Build statements which should get registered to reflect Bazel's outputs. 250 buildStatements []*bazel.BuildStatement 251 252 // Depsets which should be used for Bazel's build statements. 253 depsets []bazel.AqueryDepset 254 255 // Per-module allowlist/denylist functionality to control whether analysis of 256 // modules are handled by Bazel. For modules which do not have a Bazel definition 257 // (or do not sufficiently support bazel handling via MixedBuildBuildable), 258 // this allowlist will have no effect, even if the module is explicitly allowlisted here. 259 // Per-module denylist to opt modules out of bazel handling. 260 bazelDisabledModules map[string]bool 261 // Per-module allowlist to opt modules in to bazel handling. 262 bazelEnabledModules map[string]bool 263 // DCLA modules are enabled when used in apex. 264 bazelDclaEnabledModules map[string]bool 265 // If true, modules are bazel-enabled by default, unless present in bazelDisabledModules. 266 modulesDefaultToBazel bool 267 268 targetProduct string 269 targetBuildVariant string 270} 271 272var _ BazelContext = &mixedBuildBazelContext{} 273 274// A bazel context to use when Bazel is disabled. 275type noopBazelContext struct{} 276 277var _ BazelContext = noopBazelContext{} 278 279// A bazel context to use for tests. 280type MockBazelContext struct { 281 OutputBaseDir string 282 283 LabelToOutputFiles map[string][]string 284 LabelToCcInfo map[string]cquery.CcInfo 285 LabelToPythonBinary map[string]string 286 LabelToApexInfo map[string]cquery.ApexInfo 287 LabelToCcBinary map[string]cquery.CcUnstrippedInfo 288 289 BazelRequests map[string]bool 290} 291 292func (m MockBazelContext) QueueBazelRequest(label string, requestType cqueryRequest, cfgKey configKey) { 293 key := BuildMockBazelContextRequestKey(label, requestType, cfgKey.arch, cfgKey.osType, cfgKey.apexKey) 294 if m.BazelRequests == nil { 295 m.BazelRequests = make(map[string]bool) 296 } 297 m.BazelRequests[key] = true 298} 299 300func (m MockBazelContext) GetOutputFiles(label string, _ configKey) ([]string, error) { 301 result, ok := m.LabelToOutputFiles[label] 302 if !ok { 303 return []string{}, fmt.Errorf("no target with label %q in LabelToOutputFiles", label) 304 } 305 return result, nil 306} 307 308func (m MockBazelContext) GetCcInfo(label string, cfgKey configKey) (cquery.CcInfo, error) { 309 result, ok := m.LabelToCcInfo[label] 310 if !ok { 311 key := BuildMockBazelContextResultKey(label, cfgKey.arch, cfgKey.osType, cfgKey.apexKey) 312 result, ok = m.LabelToCcInfo[key] 313 if !ok { 314 return cquery.CcInfo{}, fmt.Errorf("no target with label %q in LabelToCcInfo", label) 315 } 316 } 317 return result, nil 318} 319 320func (m MockBazelContext) GetPythonBinary(label string, _ configKey) (string, error) { 321 result, ok := m.LabelToPythonBinary[label] 322 if !ok { 323 return "", fmt.Errorf("no target with label %q in LabelToPythonBinary", label) 324 } 325 return result, nil 326} 327 328func (m MockBazelContext) GetApexInfo(label string, _ configKey) (cquery.ApexInfo, error) { 329 result, ok := m.LabelToApexInfo[label] 330 if !ok { 331 return cquery.ApexInfo{}, fmt.Errorf("no target with label %q in LabelToApexInfo", label) 332 } 333 return result, nil 334} 335 336func (m MockBazelContext) GetCcUnstrippedInfo(label string, _ configKey) (cquery.CcUnstrippedInfo, error) { 337 result, ok := m.LabelToCcBinary[label] 338 if !ok { 339 return cquery.CcUnstrippedInfo{}, fmt.Errorf("no target with label %q in LabelToCcBinary", label) 340 } 341 return result, nil 342} 343 344func (m MockBazelContext) InvokeBazel(_ Config, _ invokeBazelContext) error { 345 panic("unimplemented") 346} 347 348func (m MockBazelContext) IsModuleNameAllowed(_ string, _ bool) bool { 349 return true 350} 351 352func (m MockBazelContext) IsModuleDclaAllowed(_ string) bool { 353 return true 354} 355 356func (m MockBazelContext) OutputBase() string { return m.OutputBaseDir } 357 358func (m MockBazelContext) BuildStatementsToRegister() []*bazel.BuildStatement { 359 return []*bazel.BuildStatement{} 360} 361 362func (m MockBazelContext) AqueryDepsets() []bazel.AqueryDepset { 363 return []bazel.AqueryDepset{} 364} 365 366var _ BazelContext = MockBazelContext{} 367 368func BuildMockBazelContextRequestKey(label string, request cqueryRequest, arch string, osType OsType, apexKey ApexConfigKey) string { 369 cfgKey := configKey{ 370 arch: arch, 371 osType: osType, 372 apexKey: apexKey, 373 } 374 375 return strings.Join([]string{label, request.Name(), cfgKey.String()}, "_") 376} 377 378func BuildMockBazelContextResultKey(label string, arch string, osType OsType, apexKey ApexConfigKey) string { 379 cfgKey := configKey{ 380 arch: arch, 381 osType: osType, 382 apexKey: apexKey, 383 } 384 385 return strings.Join([]string{label, cfgKey.String()}, "_") 386} 387 388func (bazelCtx *mixedBuildBazelContext) QueueBazelRequest(label string, requestType cqueryRequest, cfgKey configKey) { 389 key := makeCqueryKey(label, requestType, cfgKey) 390 bazelCtx.requestMutex.Lock() 391 defer bazelCtx.requestMutex.Unlock() 392 393 // Insert key into requests, maintaining the sort, and only if it's not duplicate. 394 keyString := key.String() 395 foundEqual := false 396 notLessThanKeyString := func(i int) bool { 397 s := bazelCtx.requests[i].String() 398 v := strings.Compare(s, keyString) 399 if v == 0 { 400 foundEqual = true 401 } 402 return v >= 0 403 } 404 targetIndex := sort.Search(len(bazelCtx.requests), notLessThanKeyString) 405 if foundEqual { 406 return 407 } 408 409 if targetIndex == len(bazelCtx.requests) { 410 bazelCtx.requests = append(bazelCtx.requests, key) 411 } else { 412 bazelCtx.requests = append(bazelCtx.requests[:targetIndex+1], bazelCtx.requests[targetIndex:]...) 413 bazelCtx.requests[targetIndex] = key 414 } 415} 416 417func (bazelCtx *mixedBuildBazelContext) GetOutputFiles(label string, cfgKey configKey) ([]string, error) { 418 key := makeCqueryKey(label, cquery.GetOutputFiles, cfgKey) 419 if rawString, ok := bazelCtx.results[key]; ok { 420 bazelOutput := strings.TrimSpace(rawString) 421 422 return cquery.GetOutputFiles.ParseResult(bazelOutput), nil 423 } 424 return nil, fmt.Errorf("no bazel response found for %v", key) 425} 426 427func (bazelCtx *mixedBuildBazelContext) GetCcInfo(label string, cfgKey configKey) (cquery.CcInfo, error) { 428 key := makeCqueryKey(label, cquery.GetCcInfo, cfgKey) 429 if rawString, ok := bazelCtx.results[key]; ok { 430 bazelOutput := strings.TrimSpace(rawString) 431 return cquery.GetCcInfo.ParseResult(bazelOutput) 432 } 433 return cquery.CcInfo{}, fmt.Errorf("no bazel response found for %v", key) 434} 435 436func (bazelCtx *mixedBuildBazelContext) GetPythonBinary(label string, cfgKey configKey) (string, error) { 437 key := makeCqueryKey(label, cquery.GetPythonBinary, cfgKey) 438 if rawString, ok := bazelCtx.results[key]; ok { 439 bazelOutput := strings.TrimSpace(rawString) 440 return cquery.GetPythonBinary.ParseResult(bazelOutput), nil 441 } 442 return "", fmt.Errorf("no bazel response found for %v", key) 443} 444 445func (bazelCtx *mixedBuildBazelContext) GetApexInfo(label string, cfgKey configKey) (cquery.ApexInfo, error) { 446 key := makeCqueryKey(label, cquery.GetApexInfo, cfgKey) 447 if rawString, ok := bazelCtx.results[key]; ok { 448 return cquery.GetApexInfo.ParseResult(strings.TrimSpace(rawString)) 449 } 450 return cquery.ApexInfo{}, fmt.Errorf("no bazel response found for %v", key) 451} 452 453func (bazelCtx *mixedBuildBazelContext) GetCcUnstrippedInfo(label string, cfgKey configKey) (cquery.CcUnstrippedInfo, error) { 454 key := makeCqueryKey(label, cquery.GetCcUnstrippedInfo, cfgKey) 455 if rawString, ok := bazelCtx.results[key]; ok { 456 return cquery.GetCcUnstrippedInfo.ParseResult(strings.TrimSpace(rawString)) 457 } 458 return cquery.CcUnstrippedInfo{}, fmt.Errorf("no bazel response for %s", key) 459} 460 461func (n noopBazelContext) QueueBazelRequest(_ string, _ cqueryRequest, _ configKey) { 462 panic("unimplemented") 463} 464 465func (n noopBazelContext) GetOutputFiles(_ string, _ configKey) ([]string, error) { 466 panic("unimplemented") 467} 468 469func (n noopBazelContext) GetCcInfo(_ string, _ configKey) (cquery.CcInfo, error) { 470 panic("unimplemented") 471} 472 473func (n noopBazelContext) GetPythonBinary(_ string, _ configKey) (string, error) { 474 panic("unimplemented") 475} 476 477func (n noopBazelContext) GetApexInfo(_ string, _ configKey) (cquery.ApexInfo, error) { 478 panic("unimplemented") 479} 480 481func (n noopBazelContext) GetCcUnstrippedInfo(_ string, _ configKey) (cquery.CcUnstrippedInfo, error) { 482 //TODO implement me 483 panic("implement me") 484} 485 486func (n noopBazelContext) InvokeBazel(_ Config, _ invokeBazelContext) error { 487 panic("unimplemented") 488} 489 490func (m noopBazelContext) OutputBase() string { 491 return "" 492} 493 494func (n noopBazelContext) IsModuleNameAllowed(_ string, _ bool) bool { 495 return false 496} 497 498func (n noopBazelContext) IsModuleDclaAllowed(_ string) bool { 499 return false 500} 501 502func (m noopBazelContext) BuildStatementsToRegister() []*bazel.BuildStatement { 503 return []*bazel.BuildStatement{} 504} 505 506func (m noopBazelContext) AqueryDepsets() []bazel.AqueryDepset { 507 return []bazel.AqueryDepset{} 508} 509 510func addToStringSet(set map[string]bool, items []string) { 511 for _, item := range items { 512 set[item] = true 513 } 514} 515 516func GetBazelEnabledAndDisabledModules(buildMode SoongBuildMode, forceEnabled map[string]struct{}) (map[string]bool, map[string]bool) { 517 disabledModules := map[string]bool{} 518 enabledModules := map[string]bool{} 519 520 switch buildMode { 521 case BazelProdMode: 522 addToStringSet(enabledModules, allowlists.ProdMixedBuildsEnabledList) 523 for enabledAdHocModule := range forceEnabled { 524 enabledModules[enabledAdHocModule] = true 525 } 526 case BazelStagingMode: 527 // Staging mode includes all prod modules plus all staging modules. 528 addToStringSet(enabledModules, allowlists.ProdMixedBuildsEnabledList) 529 addToStringSet(enabledModules, allowlists.StagingMixedBuildsEnabledList) 530 for enabledAdHocModule := range forceEnabled { 531 enabledModules[enabledAdHocModule] = true 532 } 533 case BazelDevMode: 534 addToStringSet(disabledModules, allowlists.MixedBuildsDisabledList) 535 default: 536 panic("Expected BazelProdMode, BazelStagingMode, or BazelDevMode") 537 } 538 return enabledModules, disabledModules 539} 540 541func GetBazelEnabledModules(buildMode SoongBuildMode) []string { 542 enabledModules, disabledModules := GetBazelEnabledAndDisabledModules(buildMode, nil) 543 enabledList := make([]string, 0, len(enabledModules)) 544 for module := range enabledModules { 545 if !disabledModules[module] { 546 enabledList = append(enabledList, module) 547 } 548 } 549 sort.Strings(enabledList) 550 return enabledList 551} 552 553func NewBazelContext(c *config) (BazelContext, error) { 554 if c.BuildMode != BazelProdMode && c.BuildMode != BazelStagingMode && c.BuildMode != BazelDevMode { 555 return noopBazelContext{}, nil 556 } 557 558 enabledModules, disabledModules := GetBazelEnabledAndDisabledModules(c.BuildMode, c.BazelModulesForceEnabledByFlag()) 559 560 paths := bazelPaths{ 561 soongOutDir: c.soongOutDir, 562 } 563 var missing []string 564 vars := []struct { 565 name string 566 ptr *string 567 568 // True if the environment variable needs to be tracked so that changes to the variable 569 // cause the ninja file to be regenerated, false otherwise. False should only be set for 570 // environment variables that have no effect on the generated ninja file. 571 track bool 572 }{ 573 {"BAZEL_HOME", &paths.homeDir, true}, 574 {"BAZEL_PATH", &paths.bazelPath, true}, 575 {"BAZEL_OUTPUT_BASE", &paths.outputBase, true}, 576 {"BAZEL_WORKSPACE", &paths.workspaceDir, true}, 577 {"BAZEL_METRICS_DIR", &paths.metricsDir, false}, 578 {"BAZEL_DEPS_FILE", &paths.bazelDepsFile, true}, 579 } 580 for _, v := range vars { 581 if v.track { 582 if s := c.Getenv(v.name); len(s) > 1 { 583 *v.ptr = s 584 continue 585 } 586 } else if s, ok := c.env[v.name]; ok { 587 *v.ptr = s 588 } else { 589 missing = append(missing, v.name) 590 } 591 } 592 if len(missing) > 0 { 593 return nil, fmt.Errorf("missing required env vars to use bazel: %s", missing) 594 } 595 596 targetBuildVariant := "user" 597 if c.Eng() { 598 targetBuildVariant = "eng" 599 } else if c.Debuggable() { 600 targetBuildVariant = "userdebug" 601 } 602 targetProduct := "unknown" 603 if c.HasDeviceProduct() { 604 targetProduct = c.DeviceProduct() 605 } 606 dclaMixedBuildsEnabledList := []string{} 607 if c.BuildMode == BazelProdMode { 608 dclaMixedBuildsEnabledList = allowlists.ProdDclaMixedBuildsEnabledList 609 } else if c.BuildMode == BazelStagingMode { 610 dclaMixedBuildsEnabledList = append(allowlists.ProdDclaMixedBuildsEnabledList, 611 allowlists.StagingDclaMixedBuildsEnabledList...) 612 } 613 dclaEnabledModules := map[string]bool{} 614 addToStringSet(dclaEnabledModules, dclaMixedBuildsEnabledList) 615 return &mixedBuildBazelContext{ 616 bazelRunner: &builtinBazelRunner{c.UseBazelProxy, absolutePath(c.outDir)}, 617 paths: &paths, 618 modulesDefaultToBazel: c.BuildMode == BazelDevMode, 619 bazelEnabledModules: enabledModules, 620 bazelDisabledModules: disabledModules, 621 bazelDclaEnabledModules: dclaEnabledModules, 622 targetProduct: targetProduct, 623 targetBuildVariant: targetBuildVariant, 624 }, nil 625} 626 627func (p *bazelPaths) BazelMetricsDir() string { 628 return p.metricsDir 629} 630 631func (context *mixedBuildBazelContext) IsModuleNameAllowed(moduleName string, withinApex bool) bool { 632 if context.bazelDisabledModules[moduleName] { 633 return false 634 } 635 if context.bazelEnabledModules[moduleName] { 636 return true 637 } 638 if withinApex && context.IsModuleDclaAllowed(moduleName) { 639 return true 640 } 641 642 return context.modulesDefaultToBazel 643} 644 645func (context *mixedBuildBazelContext) IsModuleDclaAllowed(moduleName string) bool { 646 return context.bazelDclaEnabledModules[moduleName] 647} 648 649func pwdPrefix() string { 650 // Darwin doesn't have /proc 651 if runtime.GOOS != "darwin" { 652 return "PWD=/proc/self/cwd" 653 } 654 return "" 655} 656 657type bazelCommand struct { 658 command string 659 // query or label 660 expression string 661} 662 663type mockBazelRunner struct { 664 bazelCommandResults map[bazelCommand]string 665 // use *exec.Cmd as a key to get the bazelCommand, the map will be used in issueBazelCommand() 666 // Register createBazelCommand() invocations. Later, an 667 // issueBazelCommand() invocation can be mapped to the *exec.Cmd instance 668 // and then to the expected result via bazelCommandResults 669 tokens map[*exec.Cmd]bazelCommand 670 commands []bazelCommand 671 extraFlags []string 672} 673 674func (r *mockBazelRunner) createBazelCommand(_ Config, _ *bazelPaths, _ bazel.RunName, 675 command bazelCommand, extraFlags ...string) *exec.Cmd { 676 r.commands = append(r.commands, command) 677 r.extraFlags = append(r.extraFlags, strings.Join(extraFlags, " ")) 678 cmd := &exec.Cmd{} 679 if r.tokens == nil { 680 r.tokens = make(map[*exec.Cmd]bazelCommand) 681 } 682 r.tokens[cmd] = command 683 return cmd 684} 685 686func (r *mockBazelRunner) issueBazelCommand(bazelCmd *exec.Cmd, _ *metrics.EventHandler) (string, string, error) { 687 if command, ok := r.tokens[bazelCmd]; ok { 688 return r.bazelCommandResults[command], "", nil 689 } 690 return "", "", nil 691} 692 693type builtinBazelRunner struct { 694 useBazelProxy bool 695 outDir string 696} 697 698// Issues the given bazel command with given build label and additional flags. 699// Returns (stdout, stderr, error). The first and second return values are strings 700// containing the stdout and stderr of the run command, and an error is returned if 701// the invocation returned an error code. 702func (r *builtinBazelRunner) issueBazelCommand(bazelCmd *exec.Cmd, eventHandler *metrics.EventHandler) (string, string, error) { 703 if r.useBazelProxy { 704 eventHandler.Begin("client_proxy") 705 defer eventHandler.End("client_proxy") 706 proxyClient := bazel.NewProxyClient(r.outDir) 707 // Omit the arg containing the Bazel binary, as that is handled by the proxy 708 // server. 709 bazelFlags := bazelCmd.Args[1:] 710 // TODO(b/270989498): Refactor these functions to not take exec.Cmd, as its 711 // not actually executed for client proxying. 712 resp, err := proxyClient.IssueCommand(bazel.CmdRequest{bazelFlags, bazelCmd.Env}) 713 714 if err != nil { 715 return "", "", err 716 } 717 if len(resp.ErrorString) > 0 { 718 return "", "", fmt.Errorf(resp.ErrorString) 719 } 720 return resp.Stdout, resp.Stderr, nil 721 } else { 722 eventHandler.Begin("bazel command") 723 defer eventHandler.End("bazel command") 724 stderr := &bytes.Buffer{} 725 bazelCmd.Stderr = stderr 726 if output, err := bazelCmd.Output(); err != nil { 727 return "", string(stderr.Bytes()), 728 fmt.Errorf("bazel command failed: %s\n---command---\n%s\n---env---\n%s\n---stderr---\n%s---", 729 err, bazelCmd, strings.Join(bazelCmd.Env, "\n"), stderr) 730 } else { 731 return string(output), string(stderr.Bytes()), nil 732 } 733 } 734} 735 736func (r *builtinBazelRunner) createBazelCommand(config Config, paths *bazelPaths, runName bazel.RunName, command bazelCommand, 737 extraFlags ...string) *exec.Cmd { 738 cmdFlags := []string{ 739 "--output_base=" + absolutePath(paths.outputBase), 740 command.command, 741 command.expression, 742 // TODO(asmundak): is it needed in every build? 743 "--profile=" + shared.BazelMetricsFilename(paths, runName), 744 745 // We don't need to set --host_platforms because it's set in bazelrc files 746 // that the bazel shell script wrapper passes 747 748 // Explicitly disable downloading rules (such as canonical C++ and Java rules) from the network. 749 "--experimental_repository_disable_download", 750 751 // Suppress noise 752 "--ui_event_filters=-INFO", 753 "--noshow_progress", 754 "--norun_validations", 755 } 756 cmdFlags = append(cmdFlags, extraFlags...) 757 758 bazelCmd := exec.Command(paths.bazelPath, cmdFlags...) 759 bazelCmd.Dir = absolutePath(paths.syntheticWorkspaceDir()) 760 extraEnv := []string{ 761 "HOME=" + paths.homeDir, 762 pwdPrefix(), 763 "BUILD_DIR=" + absolutePath(paths.soongOutDir), 764 // Make OUT_DIR absolute here so build/bazel/bin/bazel uses the correct 765 // OUT_DIR at <root>/out, instead of <root>/out/soong/workspace/out. 766 "OUT_DIR=" + absolutePath(paths.outDir()), 767 // Disables local host detection of gcc; toolchain information is defined 768 // explicitly in BUILD files. 769 "BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1", 770 } 771 for _, envvar := range allowedBazelEnvironmentVars { 772 val := config.Getenv(envvar) 773 if val == "" { 774 continue 775 } 776 extraEnv = append(extraEnv, fmt.Sprintf("%s=%s", envvar, val)) 777 } 778 bazelCmd.Env = append(os.Environ(), extraEnv...) 779 780 return bazelCmd 781} 782 783func printableCqueryCommand(bazelCmd *exec.Cmd) string { 784 outputString := strings.Join(bazelCmd.Env, " ") + " \"" + strings.Join(bazelCmd.Args, "\" \"") + "\"" 785 return outputString 786 787} 788 789func (context *mixedBuildBazelContext) mainBzlFileContents() []byte { 790 // TODO(cparsons): Define configuration transitions programmatically based 791 // on available archs. 792 contents := ` 793##################################################### 794# This file is generated by soong_build. Do not edit. 795##################################################### 796def _config_node_transition_impl(settings, attr): 797 if attr.os == "android" and attr.arch == "target": 798 target = "{PRODUCT}-{VARIANT}" 799 else: 800 target = "{PRODUCT}-{VARIANT}_%s_%s" % (attr.os, attr.arch) 801 apex_name = "" 802 if attr.within_apex: 803 # //build/bazel/rules/apex:apex_name has to be set to a non_empty value, 804 # otherwise //build/bazel/rules/apex:non_apex will be true and the 805 # "-D__ANDROID_APEX__" compiler flag will be missing. Apex_name is used 806 # in some validation on bazel side which don't really apply in mixed 807 # build because soong will do the work, so we just set it to a fixed 808 # value here. 809 apex_name = "dcla_apex" 810 outputs = { 811 "//command_line_option:platforms": "@soong_injection//product_config_platforms/products/{PRODUCT}-{VARIANT}:%s" % target, 812 "@//build/bazel/rules/apex:within_apex": attr.within_apex, 813 "@//build/bazel/rules/apex:min_sdk_version": attr.apex_sdk_version, 814 "@//build/bazel/rules/apex:apex_name": apex_name, 815 } 816 817 return outputs 818 819_config_node_transition = transition( 820 implementation = _config_node_transition_impl, 821 inputs = [], 822 outputs = [ 823 "//command_line_option:platforms", 824 "@//build/bazel/rules/apex:within_apex", 825 "@//build/bazel/rules/apex:min_sdk_version", 826 "@//build/bazel/rules/apex:apex_name", 827 ], 828) 829 830def _passthrough_rule_impl(ctx): 831 return [DefaultInfo(files = depset(ctx.files.deps))] 832 833config_node = rule( 834 implementation = _passthrough_rule_impl, 835 attrs = { 836 "arch" : attr.string(mandatory = True), 837 "os" : attr.string(mandatory = True), 838 "within_apex" : attr.bool(default = False), 839 "apex_sdk_version" : attr.string(mandatory = True), 840 "deps" : attr.label_list(cfg = _config_node_transition, allow_files = True), 841 "_allowlist_function_transition": attr.label(default = "@bazel_tools//tools/allowlists/function_transition_allowlist"), 842 }, 843) 844 845 846# Rule representing the root of the build, to depend on all Bazel targets that 847# are required for the build. Building this target will build the entire Bazel 848# build tree. 849mixed_build_root = rule( 850 implementation = _passthrough_rule_impl, 851 attrs = { 852 "deps" : attr.label_list(), 853 }, 854) 855 856def _phony_root_impl(ctx): 857 return [] 858 859# Rule to depend on other targets but build nothing. 860# This is useful as follows: building a target of this rule will generate 861# symlink forests for all dependencies of the target, without executing any 862# actions of the build. 863phony_root = rule( 864 implementation = _phony_root_impl, 865 attrs = {"deps" : attr.label_list()}, 866) 867` 868 869 productReplacer := strings.NewReplacer( 870 "{PRODUCT}", context.targetProduct, 871 "{VARIANT}", context.targetBuildVariant) 872 873 return []byte(productReplacer.Replace(contents)) 874} 875 876func (context *mixedBuildBazelContext) mainBuildFileContents() []byte { 877 // TODO(cparsons): Map label to attribute programmatically; don't use hard-coded 878 // architecture mapping. 879 formatString := ` 880# This file is generated by soong_build. Do not edit. 881load(":main.bzl", "config_node", "mixed_build_root", "phony_root") 882 883%s 884 885mixed_build_root(name = "buildroot", 886 deps = [%s], 887 testonly = True, # Unblocks testonly deps. 888) 889 890phony_root(name = "phonyroot", 891 deps = [":buildroot"], 892 testonly = True, # Unblocks testonly deps. 893) 894` 895 configNodeFormatString := ` 896config_node(name = "%s", 897 arch = "%s", 898 os = "%s", 899 within_apex = %s, 900 apex_sdk_version = "%s", 901 deps = [%s], 902 testonly = True, # Unblocks testonly deps. 903) 904` 905 906 configNodesSection := "" 907 908 labelsByConfig := map[string][]string{} 909 910 for _, val := range context.requests { 911 labelString := fmt.Sprintf("\"@%s\"", val.label) 912 configString := getConfigString(val) 913 labelsByConfig[configString] = append(labelsByConfig[configString], labelString) 914 } 915 916 // Configs need to be sorted to maintain determinism of the BUILD file. 917 sortedConfigs := make([]string, 0, len(labelsByConfig)) 918 for val := range labelsByConfig { 919 sortedConfigs = append(sortedConfigs, val) 920 } 921 sort.Slice(sortedConfigs, func(i, j int) bool { return sortedConfigs[i] < sortedConfigs[j] }) 922 923 allLabels := []string{} 924 for _, configString := range sortedConfigs { 925 labels := labelsByConfig[configString] 926 configTokens := strings.Split(configString, "|") 927 if len(configTokens) < 2 { 928 panic(fmt.Errorf("Unexpected config string format: %s", configString)) 929 } 930 archString := configTokens[0] 931 osString := configTokens[1] 932 withinApex := "False" 933 apexSdkVerString := "" 934 targetString := fmt.Sprintf("%s_%s", osString, archString) 935 if len(configTokens) > 2 { 936 targetString += "_" + configTokens[2] 937 if configTokens[2] == withinApexToString(true) { 938 withinApex = "True" 939 } 940 } 941 if len(configTokens) > 3 { 942 targetString += "_" + configTokens[3] 943 apexSdkVerString = configTokens[3] 944 } 945 allLabels = append(allLabels, fmt.Sprintf("\":%s\"", targetString)) 946 labelsString := strings.Join(labels, ",\n ") 947 configNodesSection += fmt.Sprintf(configNodeFormatString, targetString, archString, osString, withinApex, apexSdkVerString, 948 labelsString) 949 } 950 951 return []byte(fmt.Sprintf(formatString, configNodesSection, strings.Join(allLabels, ",\n "))) 952} 953 954func indent(original string) string { 955 result := "" 956 for _, line := range strings.Split(original, "\n") { 957 result += " " + line + "\n" 958 } 959 return result 960} 961 962// Returns the file contents of the buildroot.cquery file that should be used for the cquery 963// expression in order to obtain information about buildroot and its dependencies. 964// The contents of this file depend on the mixedBuildBazelContext's requests; requests are enumerated 965// and grouped by their request type. The data retrieved for each label depends on its 966// request type. 967func (context *mixedBuildBazelContext) cqueryStarlarkFileContents() []byte { 968 requestTypeToCqueryIdEntries := map[cqueryRequest][]string{} 969 requestTypes := []cqueryRequest{} 970 for _, val := range context.requests { 971 cqueryId := getCqueryId(val) 972 mapEntryString := fmt.Sprintf("%q : True", cqueryId) 973 if _, seenKey := requestTypeToCqueryIdEntries[val.requestType]; !seenKey { 974 requestTypes = append(requestTypes, val.requestType) 975 } 976 requestTypeToCqueryIdEntries[val.requestType] = 977 append(requestTypeToCqueryIdEntries[val.requestType], mapEntryString) 978 } 979 labelRegistrationMapSection := "" 980 functionDefSection := "" 981 mainSwitchSection := "" 982 983 mapDeclarationFormatString := ` 984%s = { 985 %s 986} 987` 988 functionDefFormatString := ` 989def %s(target, id_string): 990%s 991` 992 mainSwitchSectionFormatString := ` 993 if id_string in %s: 994 return id_string + ">>" + %s(target, id_string) 995` 996 997 for _, requestType := range requestTypes { 998 labelMapName := requestType.Name() + "_Labels" 999 functionName := requestType.Name() + "_Fn" 1000 labelRegistrationMapSection += fmt.Sprintf(mapDeclarationFormatString, 1001 labelMapName, 1002 strings.Join(requestTypeToCqueryIdEntries[requestType], ",\n ")) 1003 functionDefSection += fmt.Sprintf(functionDefFormatString, 1004 functionName, 1005 indent(requestType.StarlarkFunctionBody())) 1006 mainSwitchSection += fmt.Sprintf(mainSwitchSectionFormatString, 1007 labelMapName, functionName) 1008 } 1009 1010 formatString := ` 1011# This file is generated by soong_build. Do not edit. 1012 1013{LABEL_REGISTRATION_MAP_SECTION} 1014 1015{FUNCTION_DEF_SECTION} 1016 1017def get_arch(target): 1018 # TODO(b/199363072): filegroups and file targets aren't associated with any 1019 # specific platform architecture in mixed builds. This is consistent with how 1020 # Soong treats filegroups, but it may not be the case with manually-written 1021 # filegroup BUILD targets. 1022 buildoptions = build_options(target) 1023 1024 if buildoptions == None: 1025 # File targets do not have buildoptions. File targets aren't associated with 1026 # any specific platform architecture in mixed builds, so use the host. 1027 return "x86_64|linux" 1028 platforms = buildoptions["//command_line_option:platforms"] 1029 if len(platforms) != 1: 1030 # An individual configured target should have only one platform architecture. 1031 # Note that it's fine for there to be multiple architectures for the same label, 1032 # but each is its own configured target. 1033 fail("expected exactly 1 platform for " + str(target.label) + " but got " + str(platforms)) 1034 platform_name = platforms[0].name 1035 if platform_name == "host": 1036 return "HOST" 1037 if not platform_name.startswith("{TARGET_PRODUCT}-{TARGET_BUILD_VARIANT}"): 1038 fail("expected platform name of the form '{TARGET_PRODUCT}-{TARGET_BUILD_VARIANT}_android_<arch>' or '{TARGET_PRODUCT}-{TARGET_BUILD_VARIANT}_linux_<arch>', but was " + str(platforms)) 1039 platform_name = platform_name.removeprefix("{TARGET_PRODUCT}-{TARGET_BUILD_VARIANT}").removeprefix("_") 1040 config_key = "" 1041 if not platform_name: 1042 config_key = "target|android" 1043 elif platform_name.startswith("android_"): 1044 config_key = platform_name.removeprefix("android_") + "|android" 1045 elif platform_name.startswith("linux_"): 1046 config_key = platform_name.removeprefix("linux_") + "|linux" 1047 else: 1048 fail("expected platform name of the form '{TARGET_PRODUCT}-{TARGET_BUILD_VARIANT}_android_<arch>' or '{TARGET_PRODUCT}-{TARGET_BUILD_VARIANT}_linux_<arch>', but was " + str(platforms)) 1049 1050 within_apex = buildoptions.get("//build/bazel/rules/apex:within_apex") 1051 apex_sdk_version = buildoptions.get("//build/bazel/rules/apex:min_sdk_version") 1052 1053 if within_apex: 1054 config_key += "|within_apex" 1055 if apex_sdk_version != None and len(apex_sdk_version) > 0: 1056 config_key += "|" + apex_sdk_version 1057 1058 return config_key 1059 1060def format(target): 1061 id_string = str(target.label) + "|" + get_arch(target) 1062 1063 # TODO(b/248106697): Remove once Bazel is updated to always normalize labels. 1064 if id_string.startswith("//"): 1065 id_string = "@" + id_string 1066 1067 {MAIN_SWITCH_SECTION} 1068 1069 # This target was not requested via cquery, and thus must be a dependency 1070 # of a requested target. 1071 return id_string + ">>NONE" 1072` 1073 replacer := strings.NewReplacer( 1074 "{TARGET_PRODUCT}", context.targetProduct, 1075 "{TARGET_BUILD_VARIANT}", context.targetBuildVariant, 1076 "{LABEL_REGISTRATION_MAP_SECTION}", labelRegistrationMapSection, 1077 "{FUNCTION_DEF_SECTION}", functionDefSection, 1078 "{MAIN_SWITCH_SECTION}", mainSwitchSection) 1079 1080 return []byte(replacer.Replace(formatString)) 1081} 1082 1083// Returns a path containing build-related metadata required for interfacing 1084// with Bazel. Example: out/soong/bazel. 1085func (p *bazelPaths) intermediatesDir() string { 1086 return filepath.Join(p.soongOutDir, "bazel") 1087} 1088 1089// Returns the path where the contents of the @soong_injection repository live. 1090// It is used by Soong to tell Bazel things it cannot over the command line. 1091func (p *bazelPaths) injectedFilesDir() string { 1092 return filepath.Join(p.soongOutDir, bazel.SoongInjectionDirName) 1093} 1094 1095// Returns the path of the synthetic Bazel workspace that contains a symlink 1096// forest composed the whole source tree and BUILD files generated by bp2build. 1097func (p *bazelPaths) syntheticWorkspaceDir() string { 1098 return filepath.Join(p.soongOutDir, "workspace") 1099} 1100 1101// Returns the path to the top level out dir ($OUT_DIR). 1102func (p *bazelPaths) outDir() string { 1103 return filepath.Dir(p.soongOutDir) 1104} 1105 1106const buildrootLabel = "@soong_injection//mixed_builds:buildroot" 1107 1108var ( 1109 cqueryCmd = bazelCommand{"cquery", fmt.Sprintf("deps(%s, 2)", buildrootLabel)} 1110 aqueryCmd = bazelCommand{"aquery", fmt.Sprintf("deps(%s)", buildrootLabel)} 1111 buildCmd = bazelCommand{"build", "@soong_injection//mixed_builds:phonyroot"} 1112) 1113 1114// Issues commands to Bazel to receive results for all cquery requests 1115// queued in the BazelContext. 1116func (context *mixedBuildBazelContext) InvokeBazel(config Config, ctx invokeBazelContext) error { 1117 eventHandler := ctx.GetEventHandler() 1118 eventHandler.Begin("bazel") 1119 defer eventHandler.End("bazel") 1120 1121 if metricsDir := context.paths.BazelMetricsDir(); metricsDir != "" { 1122 if err := os.MkdirAll(metricsDir, 0777); err != nil { 1123 return err 1124 } 1125 } 1126 context.results = make(map[cqueryKey]string) 1127 if err := context.runCquery(config, ctx); err != nil { 1128 return err 1129 } 1130 if err := context.runAquery(config, ctx); err != nil { 1131 return err 1132 } 1133 if err := context.generateBazelSymlinks(config, ctx); err != nil { 1134 return err 1135 } 1136 1137 // Clear requests. 1138 context.requests = []cqueryKey{} 1139 return nil 1140} 1141 1142func (context *mixedBuildBazelContext) runCquery(config Config, ctx invokeBazelContext) error { 1143 eventHandler := ctx.GetEventHandler() 1144 eventHandler.Begin("cquery") 1145 defer eventHandler.End("cquery") 1146 soongInjectionPath := absolutePath(context.paths.injectedFilesDir()) 1147 mixedBuildsPath := filepath.Join(soongInjectionPath, "mixed_builds") 1148 if _, err := os.Stat(mixedBuildsPath); os.IsNotExist(err) { 1149 err = os.MkdirAll(mixedBuildsPath, 0777) 1150 if err != nil { 1151 return err 1152 } 1153 } 1154 if err := writeFileBytesIfChanged(filepath.Join(soongInjectionPath, "WORKSPACE.bazel"), []byte{}, 0666); err != nil { 1155 return err 1156 } 1157 if err := writeFileBytesIfChanged(filepath.Join(mixedBuildsPath, "main.bzl"), context.mainBzlFileContents(), 0666); err != nil { 1158 return err 1159 } 1160 if err := writeFileBytesIfChanged(filepath.Join(mixedBuildsPath, "BUILD.bazel"), context.mainBuildFileContents(), 0666); err != nil { 1161 return err 1162 } 1163 cqueryFileRelpath := filepath.Join(context.paths.injectedFilesDir(), "buildroot.cquery") 1164 if err := writeFileBytesIfChanged(absolutePath(cqueryFileRelpath), context.cqueryStarlarkFileContents(), 0666); err != nil { 1165 return err 1166 } 1167 1168 extraFlags := []string{"--output=starlark", "--starlark:file=" + absolutePath(cqueryFileRelpath)} 1169 if Bool(config.productVariables.ClangCoverage) { 1170 extraFlags = append(extraFlags, "--collect_code_coverage") 1171 } 1172 1173 cqueryCommandWithFlag := context.createBazelCommand(config, context.paths, bazel.CqueryBuildRootRunName, cqueryCmd, extraFlags...) 1174 cqueryOutput, cqueryErrorMessage, cqueryErr := context.issueBazelCommand(cqueryCommandWithFlag, eventHandler) 1175 if cqueryErr != nil { 1176 return cqueryErr 1177 } 1178 cqueryCommandPrint := fmt.Sprintf("cquery command line:\n %s \n\n\n", printableCqueryCommand(cqueryCommandWithFlag)) 1179 if err := os.WriteFile(filepath.Join(soongInjectionPath, "cquery.out"), []byte(cqueryCommandPrint+cqueryOutput), 0666); err != nil { 1180 return err 1181 } 1182 cqueryResults := map[string]string{} 1183 for _, outputLine := range strings.Split(cqueryOutput, "\n") { 1184 if strings.Contains(outputLine, ">>") { 1185 splitLine := strings.SplitN(outputLine, ">>", 2) 1186 cqueryResults[splitLine[0]] = splitLine[1] 1187 } 1188 } 1189 for _, val := range context.requests { 1190 if cqueryResult, ok := cqueryResults[getCqueryId(val)]; ok { 1191 context.results[val] = cqueryResult 1192 } else { 1193 return fmt.Errorf("missing result for bazel target %s. query output: [%s], cquery err: [%s]", 1194 getCqueryId(val), cqueryOutput, cqueryErrorMessage) 1195 } 1196 } 1197 return nil 1198} 1199 1200func writeFileBytesIfChanged(path string, contents []byte, perm os.FileMode) error { 1201 oldContents, err := os.ReadFile(path) 1202 if err != nil || !bytes.Equal(contents, oldContents) { 1203 err = os.WriteFile(path, contents, perm) 1204 } 1205 return nil 1206} 1207 1208func (context *mixedBuildBazelContext) runAquery(config Config, ctx invokeBazelContext) error { 1209 eventHandler := ctx.GetEventHandler() 1210 eventHandler.Begin("aquery") 1211 defer eventHandler.End("aquery") 1212 // Issue an aquery command to retrieve action information about the bazel build tree. 1213 // 1214 // Use jsonproto instead of proto; actual proto parsing would require a dependency on Bazel's 1215 // proto sources, which would add a number of unnecessary dependencies. 1216 extraFlags := []string{"--output=proto", "--include_file_write_contents"} 1217 if Bool(config.productVariables.ClangCoverage) { 1218 extraFlags = append(extraFlags, "--collect_code_coverage") 1219 paths := make([]string, 0, 2) 1220 if p := config.productVariables.NativeCoveragePaths; len(p) > 0 { 1221 for i := range p { 1222 // TODO(b/259404593) convert path wildcard to regex values 1223 if p[i] == "*" { 1224 p[i] = ".*" 1225 } 1226 } 1227 paths = append(paths, JoinWithPrefixAndSeparator(p, "+", ",")) 1228 } 1229 if p := config.productVariables.NativeCoverageExcludePaths; len(p) > 0 { 1230 paths = append(paths, JoinWithPrefixAndSeparator(p, "-", ",")) 1231 } 1232 if len(paths) > 0 { 1233 extraFlags = append(extraFlags, "--instrumentation_filter="+strings.Join(paths, ",")) 1234 } 1235 } 1236 aqueryOutput, _, err := context.issueBazelCommand(context.createBazelCommand(config, context.paths, bazel.AqueryBuildRootRunName, aqueryCmd, 1237 extraFlags...), eventHandler) 1238 if err != nil { 1239 return err 1240 } 1241 context.buildStatements, context.depsets, err = bazel.AqueryBuildStatements([]byte(aqueryOutput), eventHandler) 1242 return err 1243} 1244 1245func (context *mixedBuildBazelContext) generateBazelSymlinks(config Config, ctx invokeBazelContext) error { 1246 eventHandler := ctx.GetEventHandler() 1247 eventHandler.Begin("symlinks") 1248 defer eventHandler.End("symlinks") 1249 // Issue a build command of the phony root to generate symlink forests for dependencies of the 1250 // Bazel build. This is necessary because aquery invocations do not generate this symlink forest, 1251 // but some of symlinks may be required to resolve source dependencies of the build. 1252 _, _, err := context.issueBazelCommand(context.createBazelCommand(config, context.paths, bazel.BazelBuildPhonyRootRunName, buildCmd), eventHandler) 1253 return err 1254} 1255 1256func (context *mixedBuildBazelContext) BuildStatementsToRegister() []*bazel.BuildStatement { 1257 return context.buildStatements 1258} 1259 1260func (context *mixedBuildBazelContext) AqueryDepsets() []bazel.AqueryDepset { 1261 return context.depsets 1262} 1263 1264func (context *mixedBuildBazelContext) OutputBase() string { 1265 return context.paths.outputBase 1266} 1267 1268// Singleton used for registering BUILD file ninja dependencies (needed 1269// for correctness of builds which use Bazel. 1270func BazelSingleton() Singleton { 1271 return &bazelSingleton{} 1272} 1273 1274type bazelSingleton struct{} 1275 1276func (c *bazelSingleton) GenerateBuildActions(ctx SingletonContext) { 1277 // bazelSingleton is a no-op if mixed-soong-bazel-builds are disabled. 1278 if !ctx.Config().IsMixedBuildsEnabled() { 1279 return 1280 } 1281 1282 // Add ninja file dependencies for files which all bazel invocations require. 1283 bazelBuildList := absolutePath(filepath.Join( 1284 filepath.Dir(ctx.Config().moduleListFile), "bazel.list")) 1285 ctx.AddNinjaFileDeps(bazelBuildList) 1286 1287 data, err := os.ReadFile(bazelBuildList) 1288 if err != nil { 1289 ctx.Errorf(err.Error()) 1290 } 1291 files := strings.Split(strings.TrimSpace(string(data)), "\n") 1292 for _, file := range files { 1293 ctx.AddNinjaFileDeps(file) 1294 } 1295 1296 for _, depset := range ctx.Config().BazelContext.AqueryDepsets() { 1297 var outputs []Path 1298 var orderOnlies []Path 1299 for _, depsetDepHash := range depset.TransitiveDepSetHashes { 1300 otherDepsetName := bazelDepsetName(depsetDepHash) 1301 outputs = append(outputs, PathForPhony(ctx, otherDepsetName)) 1302 } 1303 for _, artifactPath := range depset.DirectArtifacts { 1304 pathInBazelOut := PathForBazelOut(ctx, artifactPath) 1305 if artifactPath == "bazel-out/volatile-status.txt" { 1306 // See https://bazel.build/docs/user-manual#workspace-status 1307 orderOnlies = append(orderOnlies, pathInBazelOut) 1308 } else { 1309 outputs = append(outputs, pathInBazelOut) 1310 } 1311 } 1312 thisDepsetName := bazelDepsetName(depset.ContentHash) 1313 ctx.Build(pctx, BuildParams{ 1314 Rule: blueprint.Phony, 1315 Outputs: []WritablePath{PathForPhony(ctx, thisDepsetName)}, 1316 Implicits: outputs, 1317 OrderOnly: orderOnlies, 1318 }) 1319 } 1320 1321 executionRoot := path.Join(ctx.Config().BazelContext.OutputBase(), "execroot", "__main__") 1322 bazelOutDir := path.Join(executionRoot, "bazel-out") 1323 for index, buildStatement := range ctx.Config().BazelContext.BuildStatementsToRegister() { 1324 // nil build statements are a valid case where we do not create an action because it is 1325 // unnecessary or handled by other processing 1326 if buildStatement == nil { 1327 continue 1328 } 1329 if len(buildStatement.Command) > 0 { 1330 rule := NewRuleBuilder(pctx, ctx) 1331 createCommand(rule.Command(), buildStatement, executionRoot, bazelOutDir, ctx) 1332 desc := fmt.Sprintf("%s: %s", buildStatement.Mnemonic, buildStatement.OutputPaths) 1333 rule.Build(fmt.Sprintf("bazel %d", index), desc) 1334 continue 1335 } 1336 // Certain actions returned by aquery (for instance FileWrite) do not contain a command 1337 // and thus require special treatment. If BuildStatement were an interface implementing 1338 // buildRule(ctx) function, the code here would just call it. 1339 // Unfortunately, the BuildStatement is defined in 1340 // the 'bazel' package, which cannot depend on 'android' package where ctx is defined, 1341 // because this would cause circular dependency. So, until we move aquery processing 1342 // to the 'android' package, we need to handle special cases here. 1343 switch buildStatement.Mnemonic { 1344 case "FileWrite", "SourceSymlinkManifest": 1345 out := PathForBazelOut(ctx, buildStatement.OutputPaths[0]) 1346 WriteFileRuleVerbatim(ctx, out, buildStatement.FileContents) 1347 case "SymlinkTree": 1348 // build-runfiles arguments are the manifest file and the target directory 1349 // where it creates the symlink tree according to this manifest (and then 1350 // writes the MANIFEST file to it). 1351 outManifest := PathForBazelOut(ctx, buildStatement.OutputPaths[0]) 1352 outManifestPath := outManifest.String() 1353 if !strings.HasSuffix(outManifestPath, "MANIFEST") { 1354 panic("the base name of the symlink tree action should be MANIFEST, got " + outManifestPath) 1355 } 1356 outDir := filepath.Dir(outManifestPath) 1357 ctx.Build(pctx, BuildParams{ 1358 Rule: buildRunfilesRule, 1359 Output: outManifest, 1360 Inputs: []Path{PathForBazelOut(ctx, buildStatement.InputPaths[0])}, 1361 Description: "symlink tree for " + outDir, 1362 Args: map[string]string{ 1363 "outDir": outDir, 1364 }, 1365 }) 1366 default: 1367 panic(fmt.Sprintf("unhandled build statement: %v", buildStatement)) 1368 } 1369 } 1370} 1371 1372// Register bazel-owned build statements (obtained from the aquery invocation). 1373func createCommand(cmd *RuleBuilderCommand, buildStatement *bazel.BuildStatement, executionRoot string, bazelOutDir string, ctx BuilderContext) { 1374 // executionRoot is the action cwd. 1375 cmd.Text(fmt.Sprintf("cd '%s' &&", executionRoot)) 1376 1377 // Remove old outputs, as some actions might not rerun if the outputs are detected. 1378 if len(buildStatement.OutputPaths) > 0 { 1379 cmd.Text("rm -rf") // -r because outputs can be Bazel dir/tree artifacts. 1380 for _, outputPath := range buildStatement.OutputPaths { 1381 cmd.Text(fmt.Sprintf("'%s'", outputPath)) 1382 } 1383 cmd.Text("&&") 1384 } 1385 1386 for _, pair := range buildStatement.Env { 1387 // Set per-action env variables, if any. 1388 cmd.Flag(pair.Key + "=" + pair.Value) 1389 } 1390 1391 // The actual Bazel action. 1392 if len(buildStatement.Command) > 16*1024 { 1393 commandFile := PathForBazelOut(ctx, buildStatement.OutputPaths[0]+".sh") 1394 WriteFileRule(ctx, commandFile, buildStatement.Command) 1395 1396 cmd.Text("bash").Text(buildStatement.OutputPaths[0] + ".sh").Implicit(commandFile) 1397 } else { 1398 cmd.Text(buildStatement.Command) 1399 } 1400 1401 for _, outputPath := range buildStatement.OutputPaths { 1402 cmd.ImplicitOutput(PathForBazelOut(ctx, outputPath)) 1403 } 1404 for _, inputPath := range buildStatement.InputPaths { 1405 cmd.Implicit(PathForBazelOut(ctx, inputPath)) 1406 } 1407 for _, inputDepsetHash := range buildStatement.InputDepsetHashes { 1408 otherDepsetName := bazelDepsetName(inputDepsetHash) 1409 cmd.Implicit(PathForPhony(ctx, otherDepsetName)) 1410 } 1411 1412 if depfile := buildStatement.Depfile; depfile != nil { 1413 // The paths in depfile are relative to `executionRoot`. 1414 // Hence, they need to be corrected by replacing "bazel-out" 1415 // with the full `bazelOutDir`. 1416 // Otherwise, implicit outputs and implicit inputs under "bazel-out/" 1417 // would be deemed missing. 1418 // (Note: The regexp uses a capture group because the version of sed 1419 // does not support a look-behind pattern.) 1420 replacement := fmt.Sprintf(`&& sed -i'' -E 's@(^|\s|")bazel-out/@\1%s/@g' '%s'`, 1421 bazelOutDir, *depfile) 1422 cmd.Text(replacement) 1423 cmd.ImplicitDepFile(PathForBazelOut(ctx, *depfile)) 1424 } 1425 1426 for _, symlinkPath := range buildStatement.SymlinkPaths { 1427 cmd.ImplicitSymlinkOutput(PathForBazelOut(ctx, symlinkPath)) 1428 } 1429} 1430 1431func getCqueryId(key cqueryKey) string { 1432 return key.label + "|" + getConfigString(key) 1433} 1434 1435func getConfigString(key cqueryKey) string { 1436 arch := key.configKey.arch 1437 if len(arch) == 0 || arch == "common" { 1438 if key.configKey.osType.Class == Device { 1439 // For the generic Android, the expected result is "target|android", which 1440 // corresponds to the product_variable_config named "android_target" in 1441 // build/bazel/platforms/BUILD.bazel. 1442 arch = "target" 1443 } else { 1444 // Use host platform, which is currently hardcoded to be x86_64. 1445 arch = "x86_64" 1446 } 1447 } 1448 osName := key.configKey.osType.Name 1449 if len(osName) == 0 || osName == "common_os" || osName == "linux_glibc" || osName == "linux_musl" { 1450 // Use host OS, which is currently hardcoded to be linux. 1451 osName = "linux" 1452 } 1453 keyString := arch + "|" + osName 1454 if key.configKey.apexKey.WithinApex { 1455 keyString += "|" + withinApexToString(key.configKey.apexKey.WithinApex) 1456 } 1457 1458 if len(key.configKey.apexKey.ApexSdkVersion) > 0 { 1459 keyString += "|" + key.configKey.apexKey.ApexSdkVersion 1460 } 1461 1462 return keyString 1463} 1464 1465func GetConfigKey(ctx BaseModuleContext) configKey { 1466 return configKey{ 1467 // use string because Arch is not a valid key in go 1468 arch: ctx.Arch().String(), 1469 osType: ctx.Os(), 1470 } 1471} 1472 1473func GetConfigKeyApexVariant(ctx BaseModuleContext, apexKey *ApexConfigKey) configKey { 1474 configKey := GetConfigKey(ctx) 1475 1476 if apexKey != nil { 1477 configKey.apexKey = ApexConfigKey{ 1478 WithinApex: apexKey.WithinApex, 1479 ApexSdkVersion: apexKey.ApexSdkVersion, 1480 } 1481 } 1482 1483 return configKey 1484} 1485 1486func bazelDepsetName(contentHash string) string { 1487 return fmt.Sprintf("bazel_depset_%s", contentHash) 1488} 1489 1490func EnvironmentVarsFile(config Config) string { 1491 return fmt.Sprintf(bazel.GeneratedBazelFileWarning+` 1492_env = %s 1493 1494env = _env 1495`, 1496 starlark_fmt.PrintStringList(allowedBazelEnvironmentVars, 0), 1497 ) 1498} 1499