Merge "Show correct line numbers for errors in an $(eval)"
diff --git a/android/allowlists/allowlists.go b/android/allowlists/allowlists.go
index 63fe462..87554a2 100644
--- a/android/allowlists/allowlists.go
+++ b/android/allowlists/allowlists.go
@@ -212,7 +212,9 @@
 		"frameworks/proto_logging/stats":                     Bp2BuildDefaultTrueRecursively,
 
 		"hardware/interfaces":                          Bp2BuildDefaultTrue,
+		"hardware/interfaces/audio/aidl":               Bp2BuildDefaultTrue,
 		"hardware/interfaces/common/aidl":              Bp2BuildDefaultTrue,
+		"hardware/interfaces/common/fmq/aidl":          Bp2BuildDefaultTrue,
 		"hardware/interfaces/configstore/1.0":          Bp2BuildDefaultTrue,
 		"hardware/interfaces/configstore/1.1":          Bp2BuildDefaultTrue,
 		"hardware/interfaces/configstore/utils":        Bp2BuildDefaultTrue,
diff --git a/android/bazel_handler.go b/android/bazel_handler.go
index fc63c3b..c2fd954 100644
--- a/android/bazel_handler.go
+++ b/android/bazel_handler.go
@@ -432,16 +432,26 @@
 	vars := []struct {
 		name string
 		ptr  *string
+
+		// True if the environment variable needs to be tracked so that changes to the variable
+		// cause the ninja file to be regenerated, false otherwise. False should only be set for
+		// environment variables that have no effect on the generated ninja file.
+		track bool
 	}{
-		{"BAZEL_HOME", &paths.homeDir},
-		{"BAZEL_PATH", &paths.bazelPath},
-		{"BAZEL_OUTPUT_BASE", &paths.outputBase},
-		{"BAZEL_WORKSPACE", &paths.workspaceDir},
-		{"BAZEL_METRICS_DIR", &paths.metricsDir},
-		{"BAZEL_DEPS_FILE", &paths.bazelDepsFile},
+		{"BAZEL_HOME", &paths.homeDir, true},
+		{"BAZEL_PATH", &paths.bazelPath, true},
+		{"BAZEL_OUTPUT_BASE", &paths.outputBase, true},
+		{"BAZEL_WORKSPACE", &paths.workspaceDir, true},
+		{"BAZEL_METRICS_DIR", &paths.metricsDir, false},
+		{"BAZEL_DEPS_FILE", &paths.bazelDepsFile, true},
 	}
 	for _, v := range vars {
-		if s := c.Getenv(v.name); len(s) > 1 {
+		if v.track {
+			if s := c.Getenv(v.name); len(s) > 1 {
+				*v.ptr = s
+				continue
+			}
+		} else if s, ok := c.env[v.name]; ok {
 			*v.ptr = s
 		} else {
 			missing = append(missing, v.name)
diff --git a/bp2build/java_library_conversion_test.go b/bp2build/java_library_conversion_test.go
index e37fa62..84c664e 100644
--- a/bp2build/java_library_conversion_test.go
+++ b/bp2build/java_library_conversion_test.go
@@ -685,6 +685,7 @@
         "c.kt",
     ]`,
 			}),
+			MakeNeverlinkDuplicateTarget("kt_jvm_library", "java-lib-1"),
 		},
 	})
 }
@@ -707,6 +708,7 @@
     ]`,
 				"common_srcs": `["c.kt"]`,
 			}),
+			MakeNeverlinkDuplicateTarget("kt_jvm_library", "java-lib-1"),
 		},
 	})
 }
diff --git a/java/base.go b/java/base.go
index 6e42aef..55d77dc 100644
--- a/java/base.go
+++ b/java/base.go
@@ -1488,14 +1488,7 @@
 			}
 			// Dex compilation
 			var dexOutputFile android.OutputPath
-			params := &compileDexParams{
-				flags:         flags,
-				sdkVersion:    j.SdkVersion(ctx),
-				minSdkVersion: j.MinSdkVersion(ctx),
-				classesJar:    implementationAndResourcesJar,
-				jarName:       jarName,
-			}
-			dexOutputFile = j.dexer.compileDex(ctx, params)
+			dexOutputFile = j.dexer.compileDex(ctx, flags, j.MinSdkVersion(ctx), implementationAndResourcesJar, jarName)
 			if ctx.Failed() {
 				return
 			}
diff --git a/java/dex.go b/java/dex.go
index ed1f07b..40ee99d 100644
--- a/java/dex.go
+++ b/java/dex.go
@@ -180,7 +180,7 @@
 		"r8Flags", "zipFlags", "tmpJar", "mergeZipsFlags"}, []string{"implicits"})
 
 func (d *dexer) dexCommonFlags(ctx android.ModuleContext,
-	dexParams *compileDexParams) (flags []string, deps android.Paths) {
+	minSdkVersion android.SdkSpec) (flags []string, deps android.Paths) {
 
 	flags = d.dexProperties.Dxflags
 	// Translate all the DX flags to D8 ones until all the build files have been migrated
@@ -209,11 +209,11 @@
 	// Note: Targets with a min SDK kind of core_platform (e.g., framework.jar) or unspecified (e.g.,
 	// services.jar), are not classified as stable, which is WAI.
 	// TODO(b/232073181): Expand to additional min SDK cases after validation.
-	if !dexParams.sdkVersion.Stable() {
+	if !minSdkVersion.Stable() {
 		flags = append(flags, "--android-platform-build")
 	}
 
-	effectiveVersion, err := dexParams.minSdkVersion.EffectiveVersion(ctx)
+	effectiveVersion, err := minSdkVersion.EffectiveVersion(ctx)
 	if err != nil {
 		ctx.PropertyErrorf("min_sdk_version", "%s", err)
 	}
@@ -317,27 +317,20 @@
 	return r8Flags, r8Deps
 }
 
-type compileDexParams struct {
-	flags         javaBuilderFlags
-	sdkVersion    android.SdkSpec
-	minSdkVersion android.SdkSpec
-	classesJar    android.Path
-	jarName       string
-}
-
-func (d *dexer) compileDex(ctx android.ModuleContext, dexParams *compileDexParams) android.OutputPath {
+func (d *dexer) compileDex(ctx android.ModuleContext, flags javaBuilderFlags, minSdkVersion android.SdkSpec,
+	classesJar android.Path, jarName string) android.OutputPath {
 
 	// Compile classes.jar into classes.dex and then javalib.jar
-	javalibJar := android.PathForModuleOut(ctx, "dex", dexParams.jarName).OutputPath
+	javalibJar := android.PathForModuleOut(ctx, "dex", jarName).OutputPath
 	outDir := android.PathForModuleOut(ctx, "dex")
-	tmpJar := android.PathForModuleOut(ctx, "withres-withoutdex", dexParams.jarName)
+	tmpJar := android.PathForModuleOut(ctx, "withres-withoutdex", jarName)
 
 	zipFlags := "--ignore_missing_files"
 	if proptools.Bool(d.dexProperties.Uncompress_dex) {
 		zipFlags += " -L 0"
 	}
 
-	commonFlags, commonDeps := d.dexCommonFlags(ctx, dexParams)
+	commonFlags, commonDeps := d.dexCommonFlags(ctx, minSdkVersion)
 
 	// Exclude kotlinc generated files when "exclude_kotlinc_generated_files" is set to true.
 	mergeZipsFlags := ""
@@ -354,7 +347,7 @@
 			android.ModuleNameWithPossibleOverride(ctx), "unused.txt")
 		proguardUsageZip := android.PathForModuleOut(ctx, "proguard_usage.zip")
 		d.proguardUsageZip = android.OptionalPathForPath(proguardUsageZip)
-		r8Flags, r8Deps := d.r8Flags(ctx, dexParams.flags)
+		r8Flags, r8Deps := d.r8Flags(ctx, flags)
 		r8Deps = append(r8Deps, commonDeps...)
 		rule := r8
 		args := map[string]string{
@@ -377,12 +370,12 @@
 			Description:     "r8",
 			Output:          javalibJar,
 			ImplicitOutputs: android.WritablePaths{proguardDictionary, proguardUsageZip},
-			Input:           dexParams.classesJar,
+			Input:           classesJar,
 			Implicits:       r8Deps,
 			Args:            args,
 		})
 	} else {
-		d8Flags, d8Deps := d8Flags(dexParams.flags)
+		d8Flags, d8Deps := d8Flags(flags)
 		d8Deps = append(d8Deps, commonDeps...)
 		rule := d8
 		if ctx.Config().UseRBE() && ctx.Config().IsEnvTrue("RBE_D8") {
@@ -392,7 +385,7 @@
 			Rule:        rule,
 			Description: "d8",
 			Output:      javalibJar,
-			Input:       dexParams.classesJar,
+			Input:       classesJar,
 			Implicits:   d8Deps,
 			Args: map[string]string{
 				"d8Flags":        strings.Join(append(commonFlags, d8Flags...), " "),
@@ -404,7 +397,7 @@
 		})
 	}
 	if proptools.Bool(d.dexProperties.Uncompress_dex) {
-		alignedJavalibJar := android.PathForModuleOut(ctx, "aligned", dexParams.jarName).OutputPath
+		alignedJavalibJar := android.PathForModuleOut(ctx, "aligned", jarName).OutputPath
 		TransformZipAlign(ctx, alignedJavalibJar, javalibJar)
 		javalibJar = alignedJavalibJar
 	}
diff --git a/java/dex_test.go b/java/dex_test.go
index cddd4ad..fc6cd0f 100644
--- a/java/dex_test.go
+++ b/java/dex_test.go
@@ -41,7 +41,6 @@
 			name: "core_platform_app",
 			srcs: ["foo.java"],
 			sdk_version: "core_platform",
-			min_sdk_version: "31",
 		}
 
 		java_library {
diff --git a/java/java.go b/java/java.go
index e83678e..6d53e1e 100644
--- a/java/java.go
+++ b/java/java.go
@@ -2005,15 +2005,7 @@
 			j.dexpreopter.uncompressedDex = *j.dexProperties.Uncompress_dex
 
 			var dexOutputFile android.OutputPath
-			dexParams := &compileDexParams{
-				flags:         flags,
-				sdkVersion:    j.SdkVersion(ctx),
-				minSdkVersion: j.MinSdkVersion(ctx),
-				classesJar:    outputFile,
-				jarName:       jarName,
-			}
-
-			dexOutputFile = j.dexer.compileDex(ctx, dexParams)
+			dexOutputFile = j.dexer.compileDex(ctx, flags, j.MinSdkVersion(ctx), outputFile, jarName)
 			if ctx.Failed() {
 				return
 			}
@@ -2764,14 +2756,6 @@
 			Rule_class:        "java_library",
 			Bzl_load_location: "//build/bazel/rules/java:library.bzl",
 		}
-
-		ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: name}, attrs)
-		neverlinkProp := true
-		neverLinkAttrs := &javaLibraryAttributes{
-			Exports:   bazel.MakeSingleLabelListAttribute(bazel.Label{Label: ":" + name}),
-			Neverlink: bazel.BoolAttribute{Value: &neverlinkProp},
-		}
-		ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: name + "-neverlink"}, neverLinkAttrs)
 	} else {
 		attrs.Common_srcs = bazel.MakeLabelListAttribute(android.BazelLabelForModuleSrc(ctx, m.properties.Common_srcs))
 
@@ -2779,10 +2763,16 @@
 			Rule_class:        "kt_jvm_library",
 			Bzl_load_location: "@rules_kotlin//kotlin:jvm_library.bzl",
 		}
-		// TODO (b/244210934): create neverlink-duplicate target once kt_jvm_library supports neverlink attribute
-		ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: name}, attrs)
 	}
 
+	ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: name}, attrs)
+	neverlinkProp := true
+	neverLinkAttrs := &javaLibraryAttributes{
+		Exports:   bazel.MakeSingleLabelListAttribute(bazel.Label{Label: ":" + name}),
+		Neverlink: bazel.BoolAttribute{Value: &neverlinkProp},
+	}
+	ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: name + "-neverlink"}, neverLinkAttrs)
+
 }
 
 type javaBinaryHostAttributes struct {
diff --git a/scripts/run-ckati.sh b/scripts/run-ckati.sh
new file mode 100755
index 0000000..b670c8a
--- /dev/null
+++ b/scripts/run-ckati.sh
@@ -0,0 +1,91 @@
+#! /bin/bash -eu
+
+# Run CKati step separately, tracing given Makefile variables.
+# It is expected that the regular Android null build (`m nothing`)
+# has been run so that $OUT_DIR/soong/Android-${TARGET_PRODUCT}.mk,
+# $OUT_DIR/soong/make_vars-${TARGET_PRODUCT}.mk, etc. files exist.
+#
+# The output file is in JSON format and can be processed with, say,
+# `jq`. For instance, the following invocation outputs all assignment
+# traces concisely:
+#  jq -c  '.assignments[] | (select (.operation == "assign")) | {("n"): .name, ("l"): .value_stack[0]?, ("v"): .value }' out/ckati.trace
+# generates
+#  {"n":"<var1>","l":"<file>:<line>","v":"<value1>"}
+#  ...
+
+function die() { format=$1; shift; printf "$format\n" $@; exit 1; }
+function usage() { die "Usage: %s [-o FILE] VAR ...\n(without -o the output goes to ${outfile})"  ${0##*/}; }
+
+[[ -d build/soong ]] || die "run this script from the top of the Android source tree"
+declare -r out=${OUT_DIR:-out}
+[[ -x ${out}/soong_ui ]] || die "run Android build first"
+: ${TARGET_PRODUCT:?not set, run lunch?}
+: ${TARGET_BUILD_VARIANT:?not set, run lunch?}
+declare -r androidmk=${out}/soong/Android-${TARGET_PRODUCT}.mk
+declare -r makevarsmk=${out}/soong/make_vars-${TARGET_PRODUCT}.mk
+declare -r target_device_dir=$(${out}/soong_ui --dumpvar-mode TARGET_DEVICE_DIR)
+: ${target_device_dir:?cannot find device directory for ${TARGET_PRODUCT}}
+declare -r target_device=$(${out}/soong_ui --dumpvar-mode TARGET_DEVICE)
+: ${target_device:?cannot find target device for ${TARGET_PRODUCT}}
+declare -r timestamp_file=${out}/build_date.txt
+# Files should exist, so ls should succeed:
+ls -1d "$androidmk" "$makevarsmk" "$target_device_dir" "$timestamp_file" >/dev/null
+
+outfile=${out}/ckati.trace
+while getopts "ho:" opt; do
+  case $opt in
+    h) usage ;;
+    o) outfile=$OPTARG ;;
+    ?) usage ;;
+  esac
+done
+
+if (($#>0)); then
+  declare -a tracing=(--variable_assignment_trace_filter="$*" --dump_variable_assignment_trace "$outfile")
+else
+  printf "running ckati without tracing variables\n"
+fi
+
+# Touch one input for ckati, otherwise it will just print
+# 'No need to regenerate ninja file' and exit.
+touch "$androidmk"
+prebuilts/build-tools/linux-x86/bin/ckati \
+  --gen_all_targets \
+  -i \
+  --ignore_optional_include=out/%.P \
+  --ninja \
+  --ninja_dir=out \
+  --ninja_suffix=-${TARGET_PRODUCT} \
+  --no_builtin_rules \
+  --no_ninja_prelude \
+  --regen \
+  --top_level_phony \
+  --use_find_emulator \
+  --use_ninja_phony_output \
+  --use_ninja_symlink_outputs \
+  --werror_find_emulator \
+  --werror_implicit_rules \
+  --werror_overriding_commands \
+  --werror_phony_looks_real \
+  --werror_real_to_phony \
+  --werror_suffix_rules \
+  --werror_writable \
+  --writable out/ \
+  -f build/make/core/main.mk \
+  "${tracing[@]}" \
+  ANDROID_JAVA_HOME=prebuilts/jdk/jdk17/linux-x86 \
+  ASAN_SYMBOLIZER_PATH=$PWD/prebuilts/clang/host/linux-x86/llvm-binutils-stable/llvm-symbolizer \
+  BUILD_DATETIME_FILE="$timestamp_file" \
+  BUILD_HOSTNAME=$(hostname) \
+  BUILD_USERNAME="$USER" \
+  JAVA_HOME=$PWD/prebuilts/jdk/jdk17/linux-x86 \
+  KATI_PACKAGE_MK_DIR="{$out}/target/product/${target_device}/CONFIG/kati_packaging" \
+  OUT_DIR="$out" \
+  PATH="$PWD/prebuilts/build-tools/path/linux-x86:$PWD/${out}/.path" \
+  PYTHONDONTWRITEBYTECODE=1 \
+  SOONG_ANDROID_MK="$androidmk" \
+  SOONG_MAKEVARS_MK="$makevarsmk" \
+  TARGET_BUILD_VARIANT="$TARGET_BUILD_VARIANT" \
+  TARGET_DEVICE_DIR="$target_device_dir" \
+  TARGET_PRODUCT=${TARGET_PRODUCT} \
+  TMPDIR="$PWD/$out/soong/.temp"
diff --git a/soong_ui.bash b/soong_ui.bash
index 49c4b78..7bddb58 100755
--- a/soong_ui.bash
+++ b/soong_ui.bash
@@ -18,34 +18,8 @@
 # that's detected in the Go code, which skips calculating the startup time.
 export TRACE_BEGIN_SOONG=$(date +%s%N)
 
-# Function to find top of the source tree (if $TOP isn't set) by walking up the
-# tree.
-function gettop
-{
-    local TOPFILE=build/soong/root.bp
-    if [ -n "${TOP-}" -a -f "${TOP-}/${TOPFILE}" ] ; then
-        # The following circumlocution ensures we remove symlinks from TOP.
-        (cd $TOP; PWD= /bin/pwd)
-    else
-        if [ -f $TOPFILE ] ; then
-            # The following circumlocution (repeated below as well) ensures
-            # that we record the true directory name and not one that is
-            # faked up with symlink names.
-            PWD= /bin/pwd
-        else
-            local HERE=$PWD
-            T=
-            while [ \( ! \( -f $TOPFILE \) \) -a \( $PWD != "/" \) ]; do
-                \cd ..
-                T=`PWD= /bin/pwd -P`
-            done
-            \cd $HERE
-            if [ -f "$T/$TOPFILE" ]; then
-                echo $T
-            fi
-        fi
-    fi
-}
+source $(cd $(dirname $BASH_SOURCE) &> /dev/null && pwd)/../make/shell_utils.sh
+require_top
 
 # Save the current PWD for use in soong_ui
 export ORIGINAL_PWD=${PWD}
diff --git a/tests/lib.sh b/tests/lib.sh
index ae8875a..24d6d4c 100644
--- a/tests/lib.sh
+++ b/tests/lib.sh
@@ -85,7 +85,7 @@
   create_mock_bazel
   copy_directory build/blueprint
   copy_directory build/soong
-  copy_directory build/make/tools/rbcrun
+  copy_directory build/make
 
   symlink_directory prebuilts/sdk
   symlink_directory prebuilts/go
@@ -163,4 +163,4 @@
   for f in ${test_fns[*]}; do
     $f
   done
-}
\ No newline at end of file
+}
diff --git a/ui/metrics/bazel_metrics_proto/bazel_metrics.pb.go b/ui/metrics/bazel_metrics_proto/bazel_metrics.pb.go
index 760b592..f8b8fd6 100644
--- a/ui/metrics/bazel_metrics_proto/bazel_metrics.pb.go
+++ b/ui/metrics/bazel_metrics_proto/bazel_metrics.pb.go
@@ -97,8 +97,9 @@
 	// E.g. "execution", "analysis", "launch"
 	PhaseName     *string `protobuf:"bytes,1,opt,name=phase_name,json=phaseName,proto3,oneof" json:"phase_name,omitempty"`
 	DurationNanos *int64  `protobuf:"varint,2,opt,name=duration_nanos,json=durationNanos,proto3,oneof" json:"duration_nanos,omitempty"`
-	// What percentage of the build time this phase took
-	PercentageMillis *int32 `protobuf:"varint,3,opt,name=percentage_millis,json=percentageMillis,proto3,oneof" json:"percentage_millis,omitempty"`
+	// What portion of the build time this phase took, with ten-thousandths precision.
+	// E.g., 1111 = 11.11%, 111 = 1.11%
+	PortionOfBuildTime *int32 `protobuf:"varint,3,opt,name=portion_of_build_time,json=portionOfBuildTime,proto3,oneof" json:"portion_of_build_time,omitempty"`
 }
 
 func (x *PhaseTiming) Reset() {
@@ -147,9 +148,9 @@
 	return 0
 }
 
-func (x *PhaseTiming) GetPercentageMillis() int32 {
-	if x != nil && x.PercentageMillis != nil {
-		return *x.PercentageMillis
+func (x *PhaseTiming) GetPortionOfBuildTime() int32 {
+	if x != nil && x.PortionOfBuildTime != nil {
+		return *x.PortionOfBuildTime
 	}
 	return 0
 }
@@ -168,23 +169,23 @@
 	0x52, 0x0c, 0x70, 0x68, 0x61, 0x73, 0x65, 0x54, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x19,
 	0x0a, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x48, 0x00, 0x52,
 	0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x88, 0x01, 0x01, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x74, 0x6f,
-	0x74, 0x61, 0x6c, 0x22, 0xc7, 0x01, 0x0a, 0x0b, 0x50, 0x68, 0x61, 0x73, 0x65, 0x54, 0x69, 0x6d,
+	0x74, 0x61, 0x6c, 0x22, 0xd1, 0x01, 0x0a, 0x0b, 0x50, 0x68, 0x61, 0x73, 0x65, 0x54, 0x69, 0x6d,
 	0x69, 0x6e, 0x67, 0x12, 0x22, 0x0a, 0x0a, 0x70, 0x68, 0x61, 0x73, 0x65, 0x5f, 0x6e, 0x61, 0x6d,
 	0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x09, 0x70, 0x68, 0x61, 0x73, 0x65,
 	0x4e, 0x61, 0x6d, 0x65, 0x88, 0x01, 0x01, 0x12, 0x2a, 0x0a, 0x0e, 0x64, 0x75, 0x72, 0x61, 0x74,
 	0x69, 0x6f, 0x6e, 0x5f, 0x6e, 0x61, 0x6e, 0x6f, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x48,
 	0x01, 0x52, 0x0d, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x61, 0x6e, 0x6f, 0x73,
-	0x88, 0x01, 0x01, 0x12, 0x30, 0x0a, 0x11, 0x70, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x61, 0x67,
-	0x65, 0x5f, 0x6d, 0x69, 0x6c, 0x6c, 0x69, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x48, 0x02,
-	0x52, 0x10, 0x70, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x61, 0x67, 0x65, 0x4d, 0x69, 0x6c, 0x6c,
-	0x69, 0x73, 0x88, 0x01, 0x01, 0x42, 0x0d, 0x0a, 0x0b, 0x5f, 0x70, 0x68, 0x61, 0x73, 0x65, 0x5f,
-	0x6e, 0x61, 0x6d, 0x65, 0x42, 0x11, 0x0a, 0x0f, 0x5f, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f,
-	0x6e, 0x5f, 0x6e, 0x61, 0x6e, 0x6f, 0x73, 0x42, 0x14, 0x0a, 0x12, 0x5f, 0x70, 0x65, 0x72, 0x63,
-	0x65, 0x6e, 0x74, 0x61, 0x67, 0x65, 0x5f, 0x6d, 0x69, 0x6c, 0x6c, 0x69, 0x73, 0x42, 0x2e, 0x5a,
-	0x2c, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x2f, 0x75,
-	0x69, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2f, 0x62, 0x61, 0x7a, 0x65, 0x6c, 0x5f,
-	0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70,
-	0x72, 0x6f, 0x74, 0x6f, 0x33,
+	0x88, 0x01, 0x01, 0x12, 0x36, 0x0a, 0x15, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6f,
+	0x66, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01,
+	0x28, 0x05, 0x48, 0x02, 0x52, 0x12, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6f, 0x6e, 0x4f, 0x66, 0x42,
+	0x75, 0x69, 0x6c, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x88, 0x01, 0x01, 0x42, 0x0d, 0x0a, 0x0b, 0x5f,
+	0x70, 0x68, 0x61, 0x73, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x42, 0x11, 0x0a, 0x0f, 0x5f, 0x64,
+	0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6e, 0x61, 0x6e, 0x6f, 0x73, 0x42, 0x18, 0x0a,
+	0x16, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6f, 0x66, 0x5f, 0x62, 0x75, 0x69,
+	0x6c, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x42, 0x2e, 0x5a, 0x2c, 0x61, 0x6e, 0x64, 0x72, 0x6f,
+	0x69, 0x64, 0x2f, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x2f, 0x75, 0x69, 0x2f, 0x6d, 0x65, 0x74, 0x72,
+	0x69, 0x63, 0x73, 0x2f, 0x62, 0x61, 0x7a, 0x65, 0x6c, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63,
+	0x73, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
 }
 
 var (
diff --git a/ui/metrics/bazel_metrics_proto/bazel_metrics.proto b/ui/metrics/bazel_metrics_proto/bazel_metrics.proto
index e78a6b9..57eed4c 100644
--- a/ui/metrics/bazel_metrics_proto/bazel_metrics.proto
+++ b/ui/metrics/bazel_metrics_proto/bazel_metrics.proto
@@ -26,6 +26,7 @@
   // E.g. "execution", "analysis", "launch"
   optional string phase_name = 1;
   optional int64 duration_nanos = 2;
-  // What percentage of the build time this phase took
-  optional int32 percentage_millis = 3;
+  // What portion of the build time this phase took, with ten-thousandths precision.
+  // E.g., 1111 = 11.11%, 111 = 1.11%
+  optional int32 portion_of_build_time = 3;
 }