platform_build_soong/ui/build/config.go
Cole Faust 583dfb426f Generate build_number.txt only once
build_number.txt was generated in runKati, and runKati is run multiple
times throughout the build. If a build number is not set, then one will
be generated using the current timestamp, meaning that multiple
different build numbers will be used in different phases of the build.

Instead, generate build_number.txt during SetupOutDir(), so that we
only have 1 build number.

This is a resubmission with a change to ensure out/soong is created
before writing the build number file.

Bug: 297269187
Test: edit a bzl file to force bazel to rerun, change the code so that use_fixed_timestamp=true during partition builds, m installclean, m bazel_sandwich, and observe that the bazel/make partitions are byte-for-byte identical
Change-Id: I47abcca166c701bb66a6a7731aecad5818279244
2023-09-28 17:56:43 -07:00

1738 lines
49 KiB
Go

// Copyright 2017 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package build
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"math/rand"
"os"
"os/exec"
"os/user"
"path/filepath"
"runtime"
"strconv"
"strings"
"syscall"
"time"
"android/soong/shared"
"google.golang.org/protobuf/proto"
smpb "android/soong/ui/metrics/metrics_proto"
)
const (
envConfigDir = "vendor/google/tools/soong_config"
jsonSuffix = "json"
)
var (
rbeRandPrefix int
googleProdCredsExistCache bool
)
func init() {
rand.Seed(time.Now().UnixNano())
rbeRandPrefix = rand.Intn(1000)
}
type Config struct{ *configImpl }
type configImpl struct {
// Some targets that are implemented in soong_build
// (bp2build, json-module-graph) are not here and have their own bits below.
arguments []string
goma bool
environ *Environment
distDir string
buildDateTime string
logsPrefix string
// From the arguments
parallel int
keepGoing int
verbose bool
checkbuild bool
dist bool
jsonModuleGraph bool
bp2build bool
queryview bool
reportMkMetrics bool // Collect and report mk2bp migration progress metrics.
soongDocs bool
multitreeBuild bool // This is a multitree build.
skipConfig bool
skipKati bool
skipKatiNinja bool
skipSoong bool
skipNinja bool
skipSoongTests bool
searchApiDir bool // Scan the Android.bp files generated in out/api_surfaces
skipMetricsUpload bool
buildStartedTime int64 // For metrics-upload-only - manually specify a build-started time
buildFromTextStub bool
ensureAllowlistIntegrity bool // For CI builds - make sure modules are mixed-built
bazelExitCode int32 // For b runs - necessary for updating NonZeroExit
besId string // For b runs, to identify the BuildEventService logs
// From the product config
katiArgs []string
ninjaArgs []string
katiSuffix string
targetDevice string
targetDeviceDir string
sandboxConfig *SandboxConfig
// Autodetected
totalRAM uint64
brokenDupRules bool
brokenUsesNetwork bool
brokenNinjaEnvVars []string
pathReplaced bool
bazelProdMode bool
bazelStagingMode bool
// Set by multiproduct_kati
emptyNinjaFile bool
metricsUploader string
bazelForceEnabledModules string
includeTags []string
sourceRootDirs []string
// Data source to write ninja weight list
ninjaWeightListSource NinjaWeightListSource
}
type NinjaWeightListSource uint
const (
// ninja doesn't use weight list.
NOT_USED NinjaWeightListSource = iota
// ninja uses weight list based on previous builds by ninja log
NINJA_LOG
// ninja thinks every task has the same weight.
EVENLY_DISTRIBUTED
// ninja uses an external custom weight list
EXTERNAL_FILE
// ninja uses a prioritized module list from Soong
HINT_FROM_SOONG
// If ninja log exists, use NINJA_LOG, if not, use HINT_FROM_SOONG instead.
// We can assume it is an incremental build if ninja log exists.
DEFAULT
)
const srcDirFileCheck = "build/soong/root.bp"
var buildFiles = []string{"Android.mk", "Android.bp"}
type BuildAction uint
const (
// Builds all of the modules and their dependencies of a specified directory, relative to the root
// directory of the source tree.
BUILD_MODULES_IN_A_DIRECTORY BuildAction = iota
// Builds all of the modules and their dependencies of a list of specified directories. All specified
// directories are relative to the root directory of the source tree.
BUILD_MODULES_IN_DIRECTORIES
// Build a list of specified modules. If none was specified, simply build the whole source tree.
BUILD_MODULES
)
// checkTopDir validates that the current directory is at the root directory of the source tree.
func checkTopDir(ctx Context) {
if _, err := os.Stat(srcDirFileCheck); err != nil {
if os.IsNotExist(err) {
ctx.Fatalf("Current working directory must be the source tree. %q not found.", srcDirFileCheck)
}
ctx.Fatalln("Error verifying tree state:", err)
}
}
func loadEnvConfig(ctx Context, config *configImpl, bc string) error {
if bc == "" {
return nil
}
configDirs := []string{
config.OutDir(),
os.Getenv("ANDROID_BUILD_ENVIRONMENT_CONFIG_DIR"),
envConfigDir,
}
for _, dir := range configDirs {
cfgFile := filepath.Join(os.Getenv("TOP"), dir, fmt.Sprintf("%s.%s", bc, jsonSuffix))
envVarsJSON, err := ioutil.ReadFile(cfgFile)
if err != nil {
continue
}
ctx.Verbosef("Loading config file %v\n", cfgFile)
var envVars map[string]map[string]string
if err := json.Unmarshal(envVarsJSON, &envVars); err != nil {
fmt.Fprintf(os.Stderr, "Env vars config file %s did not parse correctly: %s", cfgFile, err.Error())
continue
}
for k, v := range envVars["env"] {
if os.Getenv(k) != "" {
continue
}
config.environ.Set(k, v)
}
ctx.Verbosef("Finished loading config file %v\n", cfgFile)
break
}
return nil
}
func defaultBazelProdMode(cfg *configImpl) bool {
// Environment flag to disable Bazel for users which experience
// broken bazel-handled builds, or significant performance regressions.
if cfg.IsBazelMixedBuildForceDisabled() {
return false
}
// Darwin-host builds are currently untested with Bazel.
if runtime.GOOS == "darwin" {
return false
}
return true
}
func UploadOnlyConfig(ctx Context, args ...string) Config {
ret := &configImpl{
environ: OsEnvironment(),
sandboxConfig: &SandboxConfig{},
}
ret.parseArgs(ctx, args)
srcDir := absPath(ctx, ".")
bc := os.Getenv("ANDROID_BUILD_ENVIRONMENT_CONFIG")
if err := loadEnvConfig(ctx, ret, bc); err != nil {
ctx.Fatalln("Failed to parse env config files: %v", err)
}
ret.metricsUploader = GetMetricsUploader(srcDir, ret.environ)
return Config{ret}
}
func NewConfig(ctx Context, args ...string) Config {
ret := &configImpl{
environ: OsEnvironment(),
sandboxConfig: &SandboxConfig{},
ninjaWeightListSource: DEFAULT,
}
// Default matching ninja
ret.parallel = runtime.NumCPU() + 2
ret.keepGoing = 1
ret.totalRAM = detectTotalRAM(ctx)
ret.parseArgs(ctx, args)
if ret.ninjaWeightListSource == HINT_FROM_SOONG {
ret.environ.Set("SOONG_GENERATES_NINJA_HINT", "always")
} else if ret.ninjaWeightListSource == DEFAULT {
defaultNinjaWeightListSource := NINJA_LOG
if _, err := os.Stat(filepath.Join(ret.OutDir(), ninjaLogFileName)); errors.Is(err, os.ErrNotExist) {
ctx.Verboseln("$OUT/.ninja_log doesn't exist, use HINT_FROM_SOONG instead")
defaultNinjaWeightListSource = HINT_FROM_SOONG
} else {
ctx.Verboseln("$OUT/.ninja_log exist, use NINJA_LOG")
}
ret.ninjaWeightListSource = defaultNinjaWeightListSource
// soong_build generates ninja hint depending on ninja log existence.
// Set it "depend" to avoid soong re-run due to env variable change.
ret.environ.Set("SOONG_GENERATES_NINJA_HINT", "depend")
}
// Make sure OUT_DIR is set appropriately
if outDir, ok := ret.environ.Get("OUT_DIR"); ok {
ret.environ.Set("OUT_DIR", filepath.Clean(outDir))
} else {
outDir := "out"
if baseDir, ok := ret.environ.Get("OUT_DIR_COMMON_BASE"); ok {
if wd, err := os.Getwd(); err != nil {
ctx.Fatalln("Failed to get working directory:", err)
} else {
outDir = filepath.Join(baseDir, filepath.Base(wd))
}
}
ret.environ.Set("OUT_DIR", outDir)
}
// loadEnvConfig needs to know what the OUT_DIR is, so it should
// be called after we determine the appropriate out directory.
bc := os.Getenv("ANDROID_BUILD_ENVIRONMENT_CONFIG")
if bc != "" {
if err := loadEnvConfig(ctx, ret, bc); err != nil {
ctx.Fatalln("Failed to parse env config files: %v", err)
}
if !ret.canSupportRBE() {
// Explicitly set USE_RBE env variable to false when we cannot run
// an RBE build to avoid ninja local execution pool issues.
ret.environ.Set("USE_RBE", "false")
}
}
if distDir, ok := ret.environ.Get("DIST_DIR"); ok {
ret.distDir = filepath.Clean(distDir)
} else {
ret.distDir = filepath.Join(ret.OutDir(), "dist")
}
if srcDirIsWritable, ok := ret.environ.Get("BUILD_BROKEN_SRC_DIR_IS_WRITABLE"); ok {
ret.sandboxConfig.SetSrcDirIsRO(srcDirIsWritable == "false")
}
ret.environ.Unset(
// We're already using it
"USE_SOONG_UI",
// We should never use GOROOT/GOPATH from the shell environment
"GOROOT",
"GOPATH",
// These should only come from Soong, not the environment.
"CLANG",
"CLANG_CXX",
"CCC_CC",
"CCC_CXX",
// Used by the goma compiler wrapper, but should only be set by
// gomacc
"GOMACC_PATH",
// We handle this above
"OUT_DIR_COMMON_BASE",
// This is handled above too, and set for individual commands later
"DIST_DIR",
// Variables that have caused problems in the past
"BASH_ENV",
"CDPATH",
"DISPLAY",
"GREP_OPTIONS",
"JAVAC",
"NDK_ROOT",
"POSIXLY_CORRECT",
// Drop make flags
"MAKEFLAGS",
"MAKELEVEL",
"MFLAGS",
// Set in envsetup.sh, reset in makefiles
"ANDROID_JAVA_TOOLCHAIN",
// Set by envsetup.sh, but shouldn't be used inside the build because envsetup.sh is optional
"ANDROID_BUILD_TOP",
"ANDROID_HOST_OUT",
"ANDROID_PRODUCT_OUT",
"ANDROID_HOST_OUT_TESTCASES",
"ANDROID_TARGET_OUT_TESTCASES",
"ANDROID_TOOLCHAIN",
"ANDROID_TOOLCHAIN_2ND_ARCH",
"ANDROID_DEV_SCRIPTS",
"ANDROID_EMULATOR_PREBUILTS",
"ANDROID_PRE_BUILD_PATHS",
)
if ret.UseGoma() || ret.ForceUseGoma() {
ctx.Println("Goma for Android has been deprecated and replaced with RBE. See go/rbe_for_android for instructions on how to use RBE.")
ctx.Fatalln("USE_GOMA / FORCE_USE_GOMA flag is no longer supported.")
}
// Tell python not to spam the source tree with .pyc files.
ret.environ.Set("PYTHONDONTWRITEBYTECODE", "1")
tmpDir := absPath(ctx, ret.TempDir())
ret.environ.Set("TMPDIR", tmpDir)
// Always set ASAN_SYMBOLIZER_PATH so that ASAN-based tools can symbolize any crashes
symbolizerPath := filepath.Join("prebuilts/clang/host", ret.HostPrebuiltTag(),
"llvm-binutils-stable/llvm-symbolizer")
ret.environ.Set("ASAN_SYMBOLIZER_PATH", absPath(ctx, symbolizerPath))
// Precondition: the current directory is the top of the source tree
checkTopDir(ctx)
srcDir := absPath(ctx, ".")
if strings.ContainsRune(srcDir, ' ') {
ctx.Println("You are building in a directory whose absolute path contains a space character:")
ctx.Println()
ctx.Printf("%q\n", srcDir)
ctx.Println()
ctx.Fatalln("Directory names containing spaces are not supported")
}
ret.metricsUploader = GetMetricsUploader(srcDir, ret.environ)
if outDir := ret.OutDir(); strings.ContainsRune(outDir, ' ') {
ctx.Println("The absolute path of your output directory ($OUT_DIR) contains a space character:")
ctx.Println()
ctx.Printf("%q\n", outDir)
ctx.Println()
ctx.Fatalln("Directory names containing spaces are not supported")
}
if distDir := ret.RealDistDir(); strings.ContainsRune(distDir, ' ') {
ctx.Println("The absolute path of your dist directory ($DIST_DIR) contains a space character:")
ctx.Println()
ctx.Printf("%q\n", distDir)
ctx.Println()
ctx.Fatalln("Directory names containing spaces are not supported")
}
// Configure Java-related variables, including adding it to $PATH
java8Home := filepath.Join("prebuilts/jdk/jdk8", ret.HostPrebuiltTag())
java9Home := filepath.Join("prebuilts/jdk/jdk9", ret.HostPrebuiltTag())
java11Home := filepath.Join("prebuilts/jdk/jdk11", ret.HostPrebuiltTag())
java17Home := filepath.Join("prebuilts/jdk/jdk17", ret.HostPrebuiltTag())
javaHome := func() string {
if override, ok := ret.environ.Get("OVERRIDE_ANDROID_JAVA_HOME"); ok {
return override
}
if toolchain11, ok := ret.environ.Get("EXPERIMENTAL_USE_OPENJDK11_TOOLCHAIN"); ok && toolchain11 != "true" {
ctx.Fatalln("The environment variable EXPERIMENTAL_USE_OPENJDK11_TOOLCHAIN is no longer supported. An OpenJDK 11 toolchain is now the global default.")
}
if toolchain17, ok := ret.environ.Get("EXPERIMENTAL_USE_OPENJDK17_TOOLCHAIN"); ok && toolchain17 != "true" {
ctx.Fatalln("The environment variable EXPERIMENTAL_USE_OPENJDK17_TOOLCHAIN is no longer supported. An OpenJDK 17 toolchain is now the global default.")
}
return java17Home
}()
absJavaHome := absPath(ctx, javaHome)
ret.configureLocale(ctx)
newPath := []string{filepath.Join(absJavaHome, "bin")}
if path, ok := ret.environ.Get("PATH"); ok && path != "" {
newPath = append(newPath, path)
}
ret.environ.Unset("OVERRIDE_ANDROID_JAVA_HOME")
ret.environ.Set("JAVA_HOME", absJavaHome)
ret.environ.Set("ANDROID_JAVA_HOME", javaHome)
ret.environ.Set("ANDROID_JAVA8_HOME", java8Home)
ret.environ.Set("ANDROID_JAVA9_HOME", java9Home)
ret.environ.Set("ANDROID_JAVA11_HOME", java11Home)
ret.environ.Set("PATH", strings.Join(newPath, string(filepath.ListSeparator)))
// b/286885495, https://bugzilla.redhat.com/show_bug.cgi?id=2227130: some versions of Fedora include patches
// to unzip to enable zipbomb detection that incorrectly handle zip64 and data descriptors and fail on large
// zip files produced by soong_zip. Disable zipbomb detection.
ret.environ.Set("UNZIP_DISABLE_ZIPBOMB_DETECTION", "TRUE")
if ret.MultitreeBuild() {
ret.environ.Set("MULTITREE_BUILD", "true")
}
outDir := ret.OutDir()
buildDateTimeFile := filepath.Join(outDir, "build_date.txt")
if buildDateTime, ok := ret.environ.Get("BUILD_DATETIME"); ok && buildDateTime != "" {
ret.buildDateTime = buildDateTime
} else {
ret.buildDateTime = strconv.FormatInt(time.Now().Unix(), 10)
}
ret.environ.Set("BUILD_DATETIME_FILE", buildDateTimeFile)
if _, ok := ret.environ.Get("BUILD_USERNAME"); !ok {
username := "unknown"
if u, err := user.Current(); err == nil {
username = u.Username
} else {
ctx.Println("Failed to get current user:", err)
}
ret.environ.Set("BUILD_USERNAME", username)
}
if ret.UseRBE() {
for k, v := range getRBEVars(ctx, Config{ret}) {
ret.environ.Set(k, v)
}
}
if ret.BuildFromTextStub() {
// TODO(b/271443071): support hidden api check for from-text stub build
ret.environ.Set("UNSAFE_DISABLE_HIDDENAPI_FLAGS", "true")
}
bpd := ret.BazelMetricsDir()
if err := os.RemoveAll(bpd); err != nil {
ctx.Fatalf("Unable to remove bazel profile directory %q: %v", bpd, err)
}
c := Config{ret}
storeConfigMetrics(ctx, c)
return c
}
// NewBuildActionConfig returns a build configuration based on the build action. The arguments are
// processed based on the build action and extracts any arguments that belongs to the build action.
func NewBuildActionConfig(action BuildAction, dir string, ctx Context, args ...string) Config {
return NewConfig(ctx, getConfigArgs(action, dir, ctx, args)...)
}
// storeConfigMetrics selects a set of configuration information and store in
// the metrics system for further analysis.
func storeConfigMetrics(ctx Context, config Config) {
if ctx.Metrics == nil {
return
}
ctx.Metrics.BuildConfig(buildConfig(config))
s := &smpb.SystemResourceInfo{
TotalPhysicalMemory: proto.Uint64(config.TotalRAM()),
AvailableCpus: proto.Int32(int32(runtime.NumCPU())),
}
ctx.Metrics.SystemResourceInfo(s)
}
func getNinjaWeightListSourceInMetric(s NinjaWeightListSource) *smpb.BuildConfig_NinjaWeightListSource {
switch s {
case NINJA_LOG:
return smpb.BuildConfig_NINJA_LOG.Enum()
case EVENLY_DISTRIBUTED:
return smpb.BuildConfig_EVENLY_DISTRIBUTED.Enum()
case EXTERNAL_FILE:
return smpb.BuildConfig_EXTERNAL_FILE.Enum()
case HINT_FROM_SOONG:
return smpb.BuildConfig_HINT_FROM_SOONG.Enum()
default:
return smpb.BuildConfig_NOT_USED.Enum()
}
}
func buildConfig(config Config) *smpb.BuildConfig {
c := &smpb.BuildConfig{
ForceUseGoma: proto.Bool(config.ForceUseGoma()),
UseGoma: proto.Bool(config.UseGoma()),
UseRbe: proto.Bool(config.UseRBE()),
BazelMixedBuild: proto.Bool(config.BazelBuildEnabled()),
ForceDisableBazelMixedBuild: proto.Bool(config.IsBazelMixedBuildForceDisabled()),
NinjaWeightListSource: getNinjaWeightListSourceInMetric(config.NinjaWeightListSource()),
}
c.Targets = append(c.Targets, config.arguments...)
return c
}
// getConfigArgs processes the command arguments based on the build action and creates a set of new
// arguments to be accepted by Config.
func getConfigArgs(action BuildAction, dir string, ctx Context, args []string) []string {
// The next block of code verifies that the current directory is the root directory of the source
// tree. It then finds the relative path of dir based on the root directory of the source tree
// and verify that dir is inside of the source tree.
checkTopDir(ctx)
topDir, err := os.Getwd()
if err != nil {
ctx.Fatalf("Error retrieving top directory: %v", err)
}
dir, err = filepath.EvalSymlinks(dir)
if err != nil {
ctx.Fatalf("Unable to evaluate symlink of %s: %v", dir, err)
}
dir, err = filepath.Abs(dir)
if err != nil {
ctx.Fatalf("Unable to find absolute path %s: %v", dir, err)
}
relDir, err := filepath.Rel(topDir, dir)
if err != nil {
ctx.Fatalf("Unable to find relative path %s of %s: %v", relDir, topDir, err)
}
// If there are ".." in the path, it's not in the source tree.
if strings.Contains(relDir, "..") {
ctx.Fatalf("Directory %s is not under the source tree %s", dir, topDir)
}
configArgs := args[:]
// If the arguments contains GET-INSTALL-PATH, change the target name prefix from MODULES-IN- to
// GET-INSTALL-PATH-IN- to extract the installation path instead of building the modules.
targetNamePrefix := "MODULES-IN-"
if inList("GET-INSTALL-PATH", configArgs) {
targetNamePrefix = "GET-INSTALL-PATH-IN-"
configArgs = removeFromList("GET-INSTALL-PATH", configArgs)
}
var targets []string
switch action {
case BUILD_MODULES:
// No additional processing is required when building a list of specific modules or all modules.
case BUILD_MODULES_IN_A_DIRECTORY:
// If dir is the root source tree, all the modules are built of the source tree are built so
// no need to find the build file.
if topDir == dir {
break
}
buildFile := findBuildFile(ctx, relDir)
if buildFile == "" {
ctx.Fatalf("Build file not found for %s directory", relDir)
}
targets = []string{convertToTarget(filepath.Dir(buildFile), targetNamePrefix)}
case BUILD_MODULES_IN_DIRECTORIES:
newConfigArgs, dirs := splitArgs(configArgs)
configArgs = newConfigArgs
targets = getTargetsFromDirs(ctx, relDir, dirs, targetNamePrefix)
}
// Tidy only override all other specified targets.
tidyOnly := os.Getenv("WITH_TIDY_ONLY")
if tidyOnly == "true" || tidyOnly == "1" {
configArgs = append(configArgs, "tidy_only")
} else {
configArgs = append(configArgs, targets...)
}
return configArgs
}
// convertToTarget replaces "/" to "-" in dir and pre-append the targetNamePrefix to the target name.
func convertToTarget(dir string, targetNamePrefix string) string {
return targetNamePrefix + strings.ReplaceAll(dir, "/", "-")
}
// hasBuildFile returns true if dir contains an Android build file.
func hasBuildFile(ctx Context, dir string) bool {
for _, buildFile := range buildFiles {
_, err := os.Stat(filepath.Join(dir, buildFile))
if err == nil {
return true
}
if !os.IsNotExist(err) {
ctx.Fatalf("Error retrieving the build file stats: %v", err)
}
}
return false
}
// findBuildFile finds a build file (makefile or blueprint file) by looking if there is a build file
// in the current and any sub directory of dir. If a build file is not found, traverse the path
// up by one directory and repeat again until either a build file is found or reached to the root
// source tree. The returned filename of build file is "Android.mk". If one was not found, a blank
// string is returned.
func findBuildFile(ctx Context, dir string) string {
// If the string is empty or ".", assume it is top directory of the source tree.
if dir == "" || dir == "." {
return ""
}
found := false
for buildDir := dir; buildDir != "."; buildDir = filepath.Dir(buildDir) {
err := filepath.Walk(buildDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if found {
return filepath.SkipDir
}
if info.IsDir() {
return nil
}
for _, buildFile := range buildFiles {
if info.Name() == buildFile {
found = true
return filepath.SkipDir
}
}
return nil
})
if err != nil {
ctx.Fatalf("Error finding Android build file: %v", err)
}
if found {
return filepath.Join(buildDir, "Android.mk")
}
}
return ""
}
// splitArgs iterates over the arguments list and splits into two lists: arguments and directories.
func splitArgs(args []string) (newArgs []string, dirs []string) {
specialArgs := map[string]bool{
"showcommands": true,
"snod": true,
"dist": true,
"checkbuild": true,
}
newArgs = []string{}
dirs = []string{}
for _, arg := range args {
// It's a dash argument if it starts with "-" or it's a key=value pair, it's not a directory.
if strings.IndexRune(arg, '-') == 0 || strings.IndexRune(arg, '=') != -1 {
newArgs = append(newArgs, arg)
continue
}
if _, ok := specialArgs[arg]; ok {
newArgs = append(newArgs, arg)
continue
}
dirs = append(dirs, arg)
}
return newArgs, dirs
}
// getTargetsFromDirs iterates over the dirs list and creates a list of targets to build. If a
// directory from the dirs list does not exist, a fatal error is raised. relDir is related to the
// source root tree where the build action command was invoked. Each directory is validated if the
// build file can be found and follows the format "dir1:target1,target2,...". Target is optional.
func getTargetsFromDirs(ctx Context, relDir string, dirs []string, targetNamePrefix string) (targets []string) {
for _, dir := range dirs {
// The directory may have specified specific modules to build. ":" is the separator to separate
// the directory and the list of modules.
s := strings.Split(dir, ":")
l := len(s)
if l > 2 { // more than one ":" was specified.
ctx.Fatalf("%s not in proper directory:target1,target2,... format (\":\" was specified more than once)", dir)
}
dir = filepath.Join(relDir, s[0])
if _, err := os.Stat(dir); err != nil {
ctx.Fatalf("couldn't find directory %s", dir)
}
// Verify that if there are any targets specified after ":". Each target is separated by ",".
var newTargets []string
if l == 2 && s[1] != "" {
newTargets = strings.Split(s[1], ",")
if inList("", newTargets) {
ctx.Fatalf("%s not in proper directory:target1,target2,... format", dir)
}
}
// If there are specified targets to build in dir, an android build file must exist for the one
// shot build. For the non-targets case, find the appropriate build file and build all the
// modules in dir (or the closest one in the dir path).
if len(newTargets) > 0 {
if !hasBuildFile(ctx, dir) {
ctx.Fatalf("Couldn't locate a build file from %s directory", dir)
}
} else {
buildFile := findBuildFile(ctx, dir)
if buildFile == "" {
ctx.Fatalf("Build file not found for %s directory", dir)
}
newTargets = []string{convertToTarget(filepath.Dir(buildFile), targetNamePrefix)}
}
targets = append(targets, newTargets...)
}
return targets
}
func (c *configImpl) parseArgs(ctx Context, args []string) {
for i := 0; i < len(args); i++ {
arg := strings.TrimSpace(args[i])
if arg == "showcommands" {
c.verbose = true
} else if arg == "--empty-ninja-file" {
c.emptyNinjaFile = true
} else if arg == "--skip-ninja" {
c.skipNinja = true
} else if arg == "--skip-make" {
// TODO(ccross): deprecate this, it has confusing behaviors. It doesn't run kati,
// but it does run a Kati ninja file if the .kati_enabled marker file was created
// by a previous build.
c.skipConfig = true
c.skipKati = true
} else if arg == "--soong-only" {
c.skipKati = true
c.skipKatiNinja = true
} else if arg == "--config-only" {
c.skipKati = true
c.skipKatiNinja = true
c.skipSoong = true
} else if arg == "--skip-config" {
c.skipConfig = true
} else if arg == "--skip-soong-tests" {
c.skipSoongTests = true
} else if arg == "--skip-metrics-upload" {
c.skipMetricsUpload = true
} else if arg == "--mk-metrics" {
c.reportMkMetrics = true
} else if arg == "--multitree-build" {
c.multitreeBuild = true
} else if arg == "--bazel-mode" {
c.bazelProdMode = true
} else if arg == "--bazel-mode-staging" {
c.bazelStagingMode = true
} else if arg == "--search-api-dir" {
c.searchApiDir = true
} else if strings.HasPrefix(arg, "--ninja_weight_source=") {
source := strings.TrimPrefix(arg, "--ninja_weight_source=")
if source == "ninja_log" {
c.ninjaWeightListSource = NINJA_LOG
} else if source == "evenly_distributed" {
c.ninjaWeightListSource = EVENLY_DISTRIBUTED
} else if source == "not_used" {
c.ninjaWeightListSource = NOT_USED
} else if source == "soong" {
c.ninjaWeightListSource = HINT_FROM_SOONG
} else if strings.HasPrefix(source, "file,") {
c.ninjaWeightListSource = EXTERNAL_FILE
filePath := strings.TrimPrefix(source, "file,")
err := validateNinjaWeightList(filePath)
if err != nil {
ctx.Fatalf("Malformed weight list from %s: %s", filePath, err)
}
_, err = copyFile(filePath, filepath.Join(c.OutDir(), ".ninja_weight_list"))
if err != nil {
ctx.Fatalf("Error to copy ninja weight list from %s: %s", filePath, err)
}
} else {
ctx.Fatalf("unknown option for ninja_weight_source: %s", source)
}
} else if arg == "--build-from-text-stub" {
c.buildFromTextStub = true
} else if strings.HasPrefix(arg, "--build-command=") {
buildCmd := strings.TrimPrefix(arg, "--build-command=")
// remove quotations
buildCmd = strings.TrimPrefix(buildCmd, "\"")
buildCmd = strings.TrimSuffix(buildCmd, "\"")
ctx.Metrics.SetBuildCommand([]string{buildCmd})
} else if strings.HasPrefix(arg, "--bazel-force-enabled-modules=") {
c.bazelForceEnabledModules = strings.TrimPrefix(arg, "--bazel-force-enabled-modules=")
} else if strings.HasPrefix(arg, "--build-started-time-unix-millis=") {
buildTimeStr := strings.TrimPrefix(arg, "--build-started-time-unix-millis=")
val, err := strconv.ParseInt(buildTimeStr, 10, 64)
if err == nil {
c.buildStartedTime = val
} else {
ctx.Fatalf("Error parsing build-time-started-unix-millis", err)
}
} else if arg == "--ensure-allowlist-integrity" {
c.ensureAllowlistIntegrity = true
} else if strings.HasPrefix(arg, "--bazel-exit-code=") {
bazelExitCodeStr := strings.TrimPrefix(arg, "--bazel-exit-code=")
val, err := strconv.Atoi(bazelExitCodeStr)
if err == nil {
c.bazelExitCode = int32(val)
} else {
ctx.Fatalf("Error parsing bazel-exit-code", err)
}
} else if strings.HasPrefix(arg, "--bes-id=") {
c.besId = strings.TrimPrefix(arg, "--bes-id=")
} else if len(arg) > 0 && arg[0] == '-' {
parseArgNum := func(def int) int {
if len(arg) > 2 {
p, err := strconv.ParseUint(arg[2:], 10, 31)
if err != nil {
ctx.Fatalf("Failed to parse %q: %v", arg, err)
}
return int(p)
} else if i+1 < len(args) {
p, err := strconv.ParseUint(args[i+1], 10, 31)
if err == nil {
i++
return int(p)
}
}
return def
}
if len(arg) > 1 && arg[1] == 'j' {
c.parallel = parseArgNum(c.parallel)
} else if len(arg) > 1 && arg[1] == 'k' {
c.keepGoing = parseArgNum(0)
} else {
ctx.Fatalln("Unknown option:", arg)
}
} else if k, v, ok := decodeKeyValue(arg); ok && len(k) > 0 {
if k == "OUT_DIR" {
ctx.Fatalln("OUT_DIR may only be set in the environment, not as a command line option.")
}
c.environ.Set(k, v)
} else if arg == "dist" {
c.dist = true
} else if arg == "json-module-graph" {
c.jsonModuleGraph = true
} else if arg == "bp2build" {
c.bp2build = true
} else if arg == "queryview" {
c.queryview = true
} else if arg == "soong_docs" {
c.soongDocs = true
} else {
if arg == "checkbuild" {
c.checkbuild = true
}
c.arguments = append(c.arguments, arg)
}
}
if (!c.bazelProdMode) && (!c.bazelStagingMode) {
c.bazelProdMode = defaultBazelProdMode(c)
}
}
func validateNinjaWeightList(weightListFilePath string) (err error) {
data, err := os.ReadFile(weightListFilePath)
if err != nil {
return
}
lines := strings.Split(strings.TrimSpace(string(data)), "\n")
for _, line := range lines {
fields := strings.Split(line, ",")
if len(fields) != 2 {
return fmt.Errorf("wrong format, each line should have two fields, but '%s'", line)
}
_, err = strconv.Atoi(fields[1])
if err != nil {
return
}
}
return
}
func (c *configImpl) configureLocale(ctx Context) {
cmd := Command(ctx, Config{c}, "locale", "locale", "-a")
output, err := cmd.Output()
var locales []string
if err == nil {
locales = strings.Split(string(output), "\n")
} else {
// If we're unable to list the locales, let's assume en_US.UTF-8
locales = []string{"en_US.UTF-8"}
ctx.Verbosef("Failed to list locales (%q), falling back to %q", err, locales)
}
// gettext uses LANGUAGE, which is passed directly through
// For LANG and LC_*, only preserve the evaluated version of
// LC_MESSAGES
userLang := ""
if lc_all, ok := c.environ.Get("LC_ALL"); ok {
userLang = lc_all
} else if lc_messages, ok := c.environ.Get("LC_MESSAGES"); ok {
userLang = lc_messages
} else if lang, ok := c.environ.Get("LANG"); ok {
userLang = lang
}
c.environ.UnsetWithPrefix("LC_")
if userLang != "" {
c.environ.Set("LC_MESSAGES", userLang)
}
// The for LANG, use C.UTF-8 if it exists (Debian currently, proposed
// for others)
if inList("C.UTF-8", locales) {
c.environ.Set("LANG", "C.UTF-8")
} else if inList("C.utf8", locales) {
// These normalize to the same thing
c.environ.Set("LANG", "C.UTF-8")
} else if inList("en_US.UTF-8", locales) {
c.environ.Set("LANG", "en_US.UTF-8")
} else if inList("en_US.utf8", locales) {
// These normalize to the same thing
c.environ.Set("LANG", "en_US.UTF-8")
} else {
ctx.Fatalln("System doesn't support either C.UTF-8 or en_US.UTF-8")
}
}
func (c *configImpl) Environment() *Environment {
return c.environ
}
func (c *configImpl) Arguments() []string {
return c.arguments
}
func (c *configImpl) SoongBuildInvocationNeeded() bool {
if len(c.Arguments()) > 0 {
// Explicit targets requested that are not special targets like b2pbuild
// or the JSON module graph
return true
}
if !c.JsonModuleGraph() && !c.Bp2Build() && !c.Queryview() && !c.SoongDocs() {
// Command line was empty, the default Ninja target is built
return true
}
// bp2build + dist may be used to dist bp2build logs but does not require SoongBuildInvocation
if c.Dist() && !c.Bp2Build() {
return true
}
// build.ninja doesn't need to be generated
return false
}
func (c *configImpl) OutDir() string {
if outDir, ok := c.environ.Get("OUT_DIR"); ok {
return outDir
}
return "out"
}
func (c *configImpl) DistDir() string {
return c.distDir
}
func (c *configImpl) RealDistDir() string {
return c.distDir
}
func (c *configImpl) NinjaArgs() []string {
if c.skipKati {
return c.arguments
}
return c.ninjaArgs
}
func (c *configImpl) BazelOutDir() string {
return filepath.Join(c.OutDir(), "bazel")
}
func (c *configImpl) bazelOutputBase() string {
return filepath.Join(c.BazelOutDir(), "output")
}
func (c *configImpl) SoongOutDir() string {
return filepath.Join(c.OutDir(), "soong")
}
func (c *configImpl) ApiSurfacesOutDir() string {
return filepath.Join(c.OutDir(), "api_surfaces")
}
func (c *configImpl) PrebuiltOS() string {
switch runtime.GOOS {
case "linux":
return "linux-x86"
case "darwin":
return "darwin-x86"
default:
panic("Unknown GOOS")
}
}
func (c *configImpl) HostToolDir() string {
if c.SkipKatiNinja() {
return filepath.Join(c.SoongOutDir(), "host", c.PrebuiltOS(), "bin")
} else {
return filepath.Join(c.OutDir(), "host", c.PrebuiltOS(), "bin")
}
}
func (c *configImpl) NamedGlobFile(name string) string {
return shared.JoinPath(c.SoongOutDir(), "globs-"+name+".ninja")
}
func (c *configImpl) UsedEnvFile(tag string) string {
if v, ok := c.environ.Get("TARGET_PRODUCT"); ok {
return shared.JoinPath(c.SoongOutDir(), usedEnvFile+"."+v+"."+tag)
}
return shared.JoinPath(c.SoongOutDir(), usedEnvFile+"."+tag)
}
func (c *configImpl) Bp2BuildFilesMarkerFile() string {
return shared.JoinPath(c.SoongOutDir(), "bp2build_files_marker")
}
func (c *configImpl) Bp2BuildWorkspaceMarkerFile() string {
return shared.JoinPath(c.SoongOutDir(), "bp2build_workspace_marker")
}
func (c *configImpl) SoongDocsHtml() string {
return shared.JoinPath(c.SoongOutDir(), "docs/soong_build.html")
}
func (c *configImpl) QueryviewMarkerFile() string {
return shared.JoinPath(c.SoongOutDir(), "queryview.marker")
}
func (c *configImpl) ModuleGraphFile() string {
return shared.JoinPath(c.SoongOutDir(), "module-graph.json")
}
func (c *configImpl) ModuleActionsFile() string {
return shared.JoinPath(c.SoongOutDir(), "module-actions.json")
}
func (c *configImpl) TempDir() string {
return shared.TempDirForOutDir(c.SoongOutDir())
}
func (c *configImpl) FileListDir() string {
return filepath.Join(c.OutDir(), ".module_paths")
}
func (c *configImpl) KatiSuffix() string {
if c.katiSuffix != "" {
return c.katiSuffix
}
panic("SetKatiSuffix has not been called")
}
// Checkbuild returns true if "checkbuild" was one of the build goals, which means that the
// user is interested in additional checks at the expense of build time.
func (c *configImpl) Checkbuild() bool {
return c.checkbuild
}
func (c *configImpl) Dist() bool {
return c.dist
}
func (c *configImpl) JsonModuleGraph() bool {
return c.jsonModuleGraph
}
func (c *configImpl) Bp2Build() bool {
return c.bp2build
}
func (c *configImpl) Queryview() bool {
return c.queryview
}
func (c *configImpl) SoongDocs() bool {
return c.soongDocs
}
func (c *configImpl) IsVerbose() bool {
return c.verbose
}
func (c *configImpl) MultitreeBuild() bool {
return c.multitreeBuild
}
func (c *configImpl) NinjaWeightListSource() NinjaWeightListSource {
return c.ninjaWeightListSource
}
func (c *configImpl) SkipKati() bool {
return c.skipKati
}
func (c *configImpl) SkipKatiNinja() bool {
return c.skipKatiNinja
}
func (c *configImpl) SkipSoong() bool {
return c.skipSoong
}
func (c *configImpl) SkipNinja() bool {
return c.skipNinja
}
func (c *configImpl) SetSkipNinja(v bool) {
c.skipNinja = v
}
func (c *configImpl) SkipConfig() bool {
return c.skipConfig
}
func (c *configImpl) BuildFromTextStub() bool {
return c.buildFromTextStub
}
func (c *configImpl) TargetProduct() string {
if v, ok := c.environ.Get("TARGET_PRODUCT"); ok {
return v
}
panic("TARGET_PRODUCT is not defined")
}
func (c *configImpl) TargetProductOrErr() (string, error) {
if v, ok := c.environ.Get("TARGET_PRODUCT"); ok {
return v, nil
}
return "", fmt.Errorf("TARGET_PRODUCT is not defined")
}
func (c *configImpl) TargetDevice() string {
return c.targetDevice
}
func (c *configImpl) SetTargetDevice(device string) {
c.targetDevice = device
}
func (c *configImpl) TargetBuildVariant() string {
if v, ok := c.environ.Get("TARGET_BUILD_VARIANT"); ok {
return v
}
panic("TARGET_BUILD_VARIANT is not defined")
}
func (c *configImpl) KatiArgs() []string {
return c.katiArgs
}
func (c *configImpl) Parallel() int {
return c.parallel
}
func (c *configImpl) GetSourceRootDirs() []string {
return c.sourceRootDirs
}
func (c *configImpl) SetSourceRootDirs(i []string) {
c.sourceRootDirs = i
}
func (c *configImpl) GetIncludeTags() []string {
return c.includeTags
}
func (c *configImpl) SetIncludeTags(i []string) {
c.includeTags = i
}
func (c *configImpl) GetLogsPrefix() string {
return c.logsPrefix
}
func (c *configImpl) SetLogsPrefix(prefix string) {
c.logsPrefix = prefix
}
func (c *configImpl) HighmemParallel() int {
if i, ok := c.environ.GetInt("NINJA_HIGHMEM_NUM_JOBS"); ok {
return i
}
const minMemPerHighmemProcess = 8 * 1024 * 1024 * 1024
parallel := c.Parallel()
if c.UseRemoteBuild() {
// Ninja doesn't support nested pools, and when remote builds are enabled the total ninja parallelism
// is set very high (i.e. 500). Using a large value here would cause the total number of running jobs
// to be the sum of the sizes of the local and highmem pools, which will cause extra CPU contention.
// Return 1/16th of the size of the local pool, rounding up.
return (parallel + 15) / 16
} else if c.totalRAM == 0 {
// Couldn't detect the total RAM, don't restrict highmem processes.
return parallel
} else if c.totalRAM <= 16*1024*1024*1024 {
// Less than 16GB of ram, restrict to 1 highmem processes
return 1
} else if c.totalRAM <= 32*1024*1024*1024 {
// Less than 32GB of ram, restrict to 2 highmem processes
return 2
} else if p := int(c.totalRAM / minMemPerHighmemProcess); p < parallel {
// If less than 8GB total RAM per process, reduce the number of highmem processes
return p
}
// No restriction on highmem processes
return parallel
}
func (c *configImpl) TotalRAM() uint64 {
return c.totalRAM
}
// ForceUseGoma determines whether we should override Goma deprecation
// and use Goma for the current build or not.
func (c *configImpl) ForceUseGoma() bool {
if v, ok := c.environ.Get("FORCE_USE_GOMA"); ok {
v = strings.TrimSpace(v)
if v != "" && v != "false" {
return true
}
}
return false
}
func (c *configImpl) UseGoma() bool {
if v, ok := c.environ.Get("USE_GOMA"); ok {
v = strings.TrimSpace(v)
if v != "" && v != "false" {
return true
}
}
return false
}
func (c *configImpl) StartGoma() bool {
if !c.UseGoma() {
return false
}
if v, ok := c.environ.Get("NOSTART_GOMA"); ok {
v = strings.TrimSpace(v)
if v != "" && v != "false" {
return false
}
}
return true
}
func (c *configImpl) canSupportRBE() bool {
// Do not use RBE with prod credentials in scenarios when stubby doesn't exist, since
// its unlikely that we will be able to obtain necessary creds without stubby.
authType, _ := c.rbeAuth()
if !c.StubbyExists() && strings.Contains(authType, "use_google_prod_creds") {
return false
}
return true
}
func (c *configImpl) UseRBE() bool {
// These alternate modes of running Soong do not use RBE / reclient.
if c.Bp2Build() || c.Queryview() || c.JsonModuleGraph() {
return false
}
if !c.canSupportRBE() {
return false
}
if v, ok := c.Environment().Get("USE_RBE"); ok {
v = strings.TrimSpace(v)
if v != "" && v != "false" {
return true
}
}
return false
}
func (c *configImpl) BazelBuildEnabled() bool {
return c.bazelProdMode || c.bazelStagingMode
}
func (c *configImpl) StartRBE() bool {
if !c.UseRBE() {
return false
}
if v, ok := c.environ.Get("NOSTART_RBE"); ok {
v = strings.TrimSpace(v)
if v != "" && v != "false" {
return false
}
}
return true
}
func (c *configImpl) rbeProxyLogsDir() string {
for _, f := range []string{"RBE_proxy_log_dir", "FLAG_output_dir"} {
if v, ok := c.environ.Get(f); ok {
return v
}
}
buildTmpDir := shared.TempDirForOutDir(c.SoongOutDir())
return filepath.Join(buildTmpDir, "rbe")
}
func (c *configImpl) rbeCacheDir() string {
for _, f := range []string{"RBE_cache_dir", "FLAG_cache_dir"} {
if v, ok := c.environ.Get(f); ok {
return v
}
}
return shared.JoinPath(c.SoongOutDir(), "rbe")
}
func (c *configImpl) shouldCleanupRBELogsDir() bool {
// Perform a log directory cleanup only when the log directory
// is auto created by the build rather than user-specified.
for _, f := range []string{"RBE_proxy_log_dir", "FLAG_output_dir"} {
if _, ok := c.environ.Get(f); ok {
return false
}
}
return true
}
func (c *configImpl) rbeExecRoot() string {
for _, f := range []string{"RBE_exec_root", "FLAG_exec_root"} {
if v, ok := c.environ.Get(f); ok {
return v
}
}
wd, err := os.Getwd()
if err != nil {
return ""
}
return wd
}
func (c *configImpl) rbeDir() string {
if v, ok := c.environ.Get("RBE_DIR"); ok {
return v
}
return "prebuilts/remoteexecution-client/live/"
}
func (c *configImpl) rbeReproxy() string {
for _, f := range []string{"RBE_re_proxy", "FLAG_re_proxy"} {
if v, ok := c.environ.Get(f); ok {
return v
}
}
return filepath.Join(c.rbeDir(), "reproxy")
}
func (c *configImpl) rbeAuth() (string, string) {
credFlags := []string{
"use_application_default_credentials",
"use_gce_credentials",
"credential_file",
"use_google_prod_creds",
}
for _, cf := range credFlags {
for _, f := range []string{"RBE_" + cf, "FLAG_" + cf} {
if v, ok := c.environ.Get(f); ok {
v = strings.TrimSpace(v)
if v != "" && v != "false" && v != "0" {
return "RBE_" + cf, v
}
}
}
}
return "RBE_use_application_default_credentials", "true"
}
func (c *configImpl) rbeSockAddr(dir string) (string, error) {
maxNameLen := len(syscall.RawSockaddrUnix{}.Path)
base := fmt.Sprintf("reproxy_%v.sock", rbeRandPrefix)
name := filepath.Join(dir, base)
if len(name) < maxNameLen {
return name, nil
}
name = filepath.Join("/tmp", base)
if len(name) < maxNameLen {
return name, nil
}
return "", fmt.Errorf("cannot generate a proxy socket address shorter than the limit of %v", maxNameLen)
}
// IsGooglerEnvironment returns true if the current build is running
// on a Google developer machine and false otherwise.
func (c *configImpl) IsGooglerEnvironment() bool {
cf := "ANDROID_BUILD_ENVIRONMENT_CONFIG"
if v, ok := c.environ.Get(cf); ok {
return v == "googler"
}
return false
}
// GoogleProdCredsExist determine whether credentials exist on the
// Googler machine to use remote execution.
func (c *configImpl) GoogleProdCredsExist() bool {
if googleProdCredsExistCache {
return googleProdCredsExistCache
}
if _, err := exec.Command("/usr/bin/gcertstatus", "-nocheck_ssh").Output(); err != nil {
return false
}
googleProdCredsExistCache = true
return true
}
// UseRemoteBuild indicates whether to use a remote build acceleration system
// to speed up the build.
func (c *configImpl) UseRemoteBuild() bool {
return c.UseGoma() || c.UseRBE()
}
// StubbyExists checks whether the stubby binary exists on the machine running
// the build.
func (c *configImpl) StubbyExists() bool {
if _, err := exec.LookPath("stubby"); err != nil {
return false
}
return true
}
// RemoteParallel controls how many remote jobs (i.e., commands which contain
// gomacc) are run in parallel. Note the parallelism of all other jobs is
// still limited by Parallel()
func (c *configImpl) RemoteParallel() int {
if !c.UseRemoteBuild() {
return 0
}
if i, ok := c.environ.GetInt("NINJA_REMOTE_NUM_JOBS"); ok {
return i
}
return 500
}
func (c *configImpl) SetKatiArgs(args []string) {
c.katiArgs = args
}
func (c *configImpl) SetNinjaArgs(args []string) {
c.ninjaArgs = args
}
func (c *configImpl) SetKatiSuffix(suffix string) {
c.katiSuffix = suffix
}
func (c *configImpl) LastKatiSuffixFile() string {
return filepath.Join(c.OutDir(), "last_kati_suffix")
}
func (c *configImpl) HasKatiSuffix() bool {
return c.katiSuffix != ""
}
func (c *configImpl) KatiEnvFile() string {
return filepath.Join(c.OutDir(), "env"+c.KatiSuffix()+".sh")
}
func (c *configImpl) KatiBuildNinjaFile() string {
return filepath.Join(c.OutDir(), "build"+c.KatiSuffix()+katiBuildSuffix+".ninja")
}
func (c *configImpl) KatiPackageNinjaFile() string {
return filepath.Join(c.OutDir(), "build"+c.KatiSuffix()+katiPackageSuffix+".ninja")
}
func (c *configImpl) SoongVarsFile() string {
targetProduct, err := c.TargetProductOrErr()
if err != nil {
return filepath.Join(c.SoongOutDir(), "soong.variables")
} else {
return filepath.Join(c.SoongOutDir(), "soong."+targetProduct+".variables")
}
}
func (c *configImpl) SoongNinjaFile() string {
targetProduct, err := c.TargetProductOrErr()
if err != nil {
return filepath.Join(c.SoongOutDir(), "build.ninja")
} else {
return filepath.Join(c.SoongOutDir(), "build."+targetProduct+".ninja")
}
}
func (c *configImpl) CombinedNinjaFile() string {
if c.katiSuffix == "" {
return filepath.Join(c.OutDir(), "combined.ninja")
}
return filepath.Join(c.OutDir(), "combined"+c.KatiSuffix()+".ninja")
}
func (c *configImpl) SoongAndroidMk() string {
return filepath.Join(c.SoongOutDir(), "Android-"+c.TargetProduct()+".mk")
}
func (c *configImpl) SoongMakeVarsMk() string {
return filepath.Join(c.SoongOutDir(), "make_vars-"+c.TargetProduct()+".mk")
}
func (c *configImpl) ProductOut() string {
return filepath.Join(c.OutDir(), "target", "product", c.TargetDevice())
}
func (c *configImpl) DevicePreviousProductConfig() string {
return filepath.Join(c.ProductOut(), "previous_build_config.mk")
}
func (c *configImpl) KatiPackageMkDir() string {
return filepath.Join(c.ProductOut(), "obj", "CONFIG", "kati_packaging")
}
func (c *configImpl) hostOutRoot() string {
return filepath.Join(c.OutDir(), "host")
}
func (c *configImpl) HostOut() string {
return filepath.Join(c.hostOutRoot(), c.HostPrebuiltTag())
}
// This probably needs to be multi-valued, so not exporting it for now
func (c *configImpl) hostCrossOut() string {
if runtime.GOOS == "linux" {
return filepath.Join(c.hostOutRoot(), "windows-x86")
} else {
return ""
}
}
func (c *configImpl) HostPrebuiltTag() string {
if runtime.GOOS == "linux" {
return "linux-x86"
} else if runtime.GOOS == "darwin" {
return "darwin-x86"
} else {
panic("Unsupported OS")
}
}
func (c *configImpl) PrebuiltBuildTool(name string) string {
if v, ok := c.environ.Get("SANITIZE_HOST"); ok {
if sanitize := strings.Fields(v); inList("address", sanitize) {
asan := filepath.Join("prebuilts/build-tools", c.HostPrebuiltTag(), "asan/bin", name)
if _, err := os.Stat(asan); err == nil {
return asan
}
}
}
return filepath.Join("prebuilts/build-tools", c.HostPrebuiltTag(), "bin", name)
}
func (c *configImpl) SetBuildBrokenDupRules(val bool) {
c.brokenDupRules = val
}
func (c *configImpl) BuildBrokenDupRules() bool {
return c.brokenDupRules
}
func (c *configImpl) SetBuildBrokenUsesNetwork(val bool) {
c.brokenUsesNetwork = val
}
func (c *configImpl) BuildBrokenUsesNetwork() bool {
return c.brokenUsesNetwork
}
func (c *configImpl) SetBuildBrokenNinjaUsesEnvVars(val []string) {
c.brokenNinjaEnvVars = val
}
func (c *configImpl) BuildBrokenNinjaUsesEnvVars() []string {
return c.brokenNinjaEnvVars
}
func (c *configImpl) SetTargetDeviceDir(dir string) {
c.targetDeviceDir = dir
}
func (c *configImpl) TargetDeviceDir() string {
return c.targetDeviceDir
}
func (c *configImpl) BuildDateTime() string {
return c.buildDateTime
}
func (c *configImpl) MetricsUploaderApp() string {
return c.metricsUploader
}
// LogsDir returns the absolute path to the logs directory where build log and
// metrics files are located. By default, the logs directory is the out
// directory. If the argument dist is specified, the logs directory
// is <dist_dir>/logs.
func (c *configImpl) LogsDir() string {
dir := c.OutDir()
if c.Dist() {
// Always write logs to the real dist dir, even if Bazel is using a rigged dist dir for other files
dir = filepath.Join(c.RealDistDir(), "logs")
}
absDir, err := filepath.Abs(dir)
if err != nil {
fmt.Fprintf(os.Stderr, "\nError making log dir '%s' absolute: %s\n", dir, err.Error())
os.Exit(1)
}
return absDir
}
// BazelMetricsDir returns the <logs dir>/bazel_metrics directory
// where the bazel profiles are located.
func (c *configImpl) BazelMetricsDir() string {
return filepath.Join(c.LogsDir(), "bazel_metrics")
}
// MkFileMetrics returns the file path for make-related metrics.
func (c *configImpl) MkMetrics() string {
return filepath.Join(c.LogsDir(), "mk_metrics.pb")
}
func (c *configImpl) SetEmptyNinjaFile(v bool) {
c.emptyNinjaFile = v
}
func (c *configImpl) EmptyNinjaFile() bool {
return c.emptyNinjaFile
}
func (c *configImpl) IsBazelMixedBuildForceDisabled() bool {
return c.Environment().IsEnvTrue("BUILD_BROKEN_DISABLE_BAZEL")
}
func (c *configImpl) IsPersistentBazelEnabled() bool {
return c.Environment().IsEnvTrue("USE_PERSISTENT_BAZEL")
}
// GetBazeliskBazelVersion returns the Bazel version to use for this build,
// or the empty string if the current canonical prod Bazel should be used.
// This environment variable should only be set to debug the build system.
// The Bazel version, if set, will be passed to Bazelisk, and Bazelisk will
// handle downloading and invoking the correct Bazel binary.
func (c *configImpl) GetBazeliskBazelVersion() string {
value, _ := c.Environment().Get("USE_BAZEL_VERSION")
return value
}
func (c *configImpl) BazelModulesForceEnabledByFlag() string {
return c.bazelForceEnabledModules
}
func (c *configImpl) SkipMetricsUpload() bool {
return c.skipMetricsUpload
}
func (c *configImpl) EnsureAllowlistIntegrity() bool {
return c.ensureAllowlistIntegrity
}
// Returns a Time object if one was passed via a command-line flag.
// Otherwise returns the passed default.
func (c *configImpl) BuildStartedTimeOrDefault(defaultTime time.Time) time.Time {
if c.buildStartedTime == 0 {
return defaultTime
}
return time.UnixMilli(c.buildStartedTime)
}
func (c *configImpl) BazelExitCode() int32 {
return c.bazelExitCode
}
func GetMetricsUploader(topDir string, env *Environment) string {
if p, ok := env.Get("METRICS_UPLOADER"); ok {
metricsUploader := filepath.Join(topDir, p)
if _, err := os.Stat(metricsUploader); err == nil {
return metricsUploader
}
}
return ""
}