Merge "Use non-next NetworkStack"
diff --git a/core/Makefile b/core/Makefile
index e9340f7..accb05a 100644
--- a/core/Makefile
+++ b/core/Makefile
@@ -17,6 +17,52 @@
 SYSTEM_DLKM_NOTICE_DEPS :=
 
 # -----------------------------------------------------------------
+# Release Config Flags
+
+# Create a summary file of build flags for each partition
+# $(1): build flags json file
+# $(2): flag names
+define generate-partition-build-flag-file
+$(eval $(strip $(1)): PRIVATE_OUT := $(strip $(1)))
+$(eval $(strip $(1)): PRIVATE_FLAG_NAMES := $(strip $(2)))
+$(strip $(1)):
+	mkdir -p $$(dir $$(PRIVATE_OUT))
+	( \
+		echo '{' ; \
+		echo 'flags: [' ; \
+		$$(foreach flag, $$(PRIVATE_FLAG_NAMES), \
+			printf '  { "name": "%s", "value": "%s", ' \
+					'$$(flag)' \
+					'$$(_ALL_RELEASE_FLAGS.$$(flag).VALUE)' \
+					; \
+			printf '"set": "%s", "default": "%s", "declared": "%s", }' \
+					'$$(_ALL_RELEASE_FLAGS.$$(flag).SET_IN)' \
+					'$$(_ALL_RELEASE_FLAGS.$$(flag).DEFAULT)' \
+					'$$(_ALL_RELEASE_FLAGS.$$(flag).DECLARED_IN)' \
+					; \
+			printf '$$(if $$(filter $$(lastword $$(PRIVATE_FLAG_NAMES)),$$(flag)),,$$(comma))\n' ; \
+		) \
+		echo "]" ; \
+		echo "}" \
+	) >> $$(PRIVATE_OUT)
+endef
+
+$(foreach partition, $(_FLAG_PARTITIONS), \
+	$(eval BUILD_FLAG_SUMMARIES.$(partition) \
+			:= $(TARGET_OUT_FLAGS)/$(partition)/etc/build_flags.json) \
+	$(eval $(call generate-partition-build-flag-file, \
+				$(BUILD_FLAG_SUMMARIES.$(partition)), \
+				$(_ALL_RELEASE_FLAGS.PARTITIONS.$(partition)) \
+            ) \
+    ) \
+)
+
+# TODO: Remove
+.PHONY: flag-files
+flag-files: $(foreach partition, $(_FLAG_PARTITIONS), \
+		$(TARGET_OUT_FLAGS)/$(partition)/etc/build_flags.json)
+
+# -----------------------------------------------------------------
 # Define rules to copy PRODUCT_COPY_FILES defined by the product.
 # PRODUCT_COPY_FILES contains words like <source file>:<dest file>[:<owner>].
 # <dest file> is relative to $(PRODUCT_OUT), so it should look like,
@@ -5589,6 +5635,8 @@
 .PHONY: fastboot_info
 fastboot_info: $(INSTALLED_FASTBOOT_INFO_TARGET)
 
+droidcore-unbundled: $(INSTALLED_FASTBOOT_INFO_TARGET)
+
 $(call declare-0p-target,$(INSTALLED_MISC_INFO_TARGET))
 
 .PHONY: misc_info
@@ -5910,6 +5958,7 @@
 	    $(LPMAKE) \
 	    $(SELINUX_FC) \
 	    $(INSTALLED_MISC_INFO_TARGET) \
+	    $(INSTALLED_FASTBOOT_INFO_TARGET) \
 	    $(APKCERTS_FILE) \
 	    $(SOONG_APEX_KEYS_FILE) \
 	    $(SOONG_ZIP) \
@@ -6128,6 +6177,9 @@
 	$(hide) echo "$(PRODUCT_OTA_PUBLIC_KEYS)" > $(zip_root)/META/otakeys.txt
 	$(hide) cp $(SELINUX_FC) $(zip_root)/META/file_contexts.bin
 	$(hide) cp $(INSTALLED_MISC_INFO_TARGET) $(zip_root)/META/misc_info.txt
+ifneq ($(INSTALLED_FASTBOOT_INFO_TARGET),)
+	$(hide) cp $(INSTALLED_FASTBOOT_INFO_TARGET) $(zip_root)/META/fastboot-info.txt
+endif
 ifneq ($(PRODUCT_SYSTEM_BASE_FS_PATH),)
 	$(hide) cp $(PRODUCT_SYSTEM_BASE_FS_PATH) \
 	  $(zip_root)/META/$(notdir $(PRODUCT_SYSTEM_BASE_FS_PATH))
diff --git a/core/all_versions.bzl b/core/all_versions.bzl
new file mode 100644
index 0000000..33da673
--- /dev/null
+++ b/core/all_versions.bzl
@@ -0,0 +1,23 @@
+# Copyright (C) 2023 The Android Open Source Project
+#
+# 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.
+
+_all_versions = ["OPR1", "OPD1", "OPD2", "OPM1", "OPM2", "PPR1", "PPD1", "PPD2", "PPM1", "PPM2", "QPR1"] + [
+    version + subversion
+    for version in ["Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"]
+    for subversion in ["P1A", "P1B", "P2A", "P2B", "D1A", "D1B", "D2A", "D2B", "Q1A", "Q1B", "Q2A", "Q2B", "Q3A", "Q3B"]
+]
+
+variables_to_export_to_make = {
+    "ALL_VERSIONS": _all_versions,
+}
diff --git a/core/board_config.mk b/core/board_config.mk
index 7969b25..bdfb279 100644
--- a/core/board_config.mk
+++ b/core/board_config.mk
@@ -256,7 +256,7 @@
   endif
 
   $(shell build/soong/scripts/update_out $(OUT_DIR)/rbc/rbc_board_config_results.mk \
-    $(OUT_DIR)/rbcrun $(OUT_DIR)/rbc/boardlauncher.rbc)
+    $(OUT_DIR)/rbcrun --mode=rbc $(OUT_DIR)/rbc/boardlauncher.rbc)
   ifneq ($(.SHELLSTATUS),0)
     $(error board configuration runner failed: $(.SHELLSTATUS))
   endif
diff --git a/core/dex_preopt_odex_install.mk b/core/dex_preopt_odex_install.mk
index cb16321..7165bea 100644
--- a/core/dex_preopt_odex_install.mk
+++ b/core/dex_preopt_odex_install.mk
@@ -84,12 +84,13 @@
 ifndef LOCAL_DEX_PREOPT_GENERATE_PROFILE
   # If LOCAL_DEX_PREOPT_GENERATE_PROFILE is not defined, default it based on the existence of the
   # profile class listing. TODO: Use product specific directory here.
-  my_classes_directory := $(PRODUCT_DEX_PREOPT_PROFILE_DIR)
-  LOCAL_DEX_PREOPT_PROFILE := $(my_classes_directory)/$(LOCAL_MODULE).prof
+  ifdef PRODUCT_DEX_PREOPT_PROFILE_DIR
+    LOCAL_DEX_PREOPT_PROFILE := $(PRODUCT_DEX_PREOPT_PROFILE_DIR)/$(LOCAL_MODULE).prof
 
-  ifneq (,$(wildcard $(LOCAL_DEX_PREOPT_PROFILE)))
-    my_process_profile := true
-    my_profile_is_text_listing :=
+    ifneq (,$(wildcard $(LOCAL_DEX_PREOPT_PROFILE)))
+      my_process_profile := true
+      my_profile_is_text_listing :=
+    endif
   endif
 else
   my_process_profile := $(LOCAL_DEX_PREOPT_GENERATE_PROFILE)
diff --git a/core/envsetup.mk b/core/envsetup.mk
index 7dd9b12..8887ddc 100644
--- a/core/envsetup.mk
+++ b/core/envsetup.mk
@@ -24,14 +24,30 @@
 #$(warning $(call find_and_earlier,A B C,C))
 #$(warning $(call find_and_earlier,A B C,D))
 
-define version-list
-$(1)P1A $(1)P1B $(1)P2A $(1)P2B $(1)D1A $(1)D1B $(1)D2A $(1)D2B $(1)Q1A $(1)Q1B $(1)Q2A $(1)Q2B $(1)Q3A $(1)Q3B
+# Runs the starlark file given in $(1), and sets all the variables in its top-level
+# variables_to_export_to_make variable as make variables.
+#
+# In order to avoid running starlark every time the stamp file is checked, we use
+# $(KATI_shell_no_rerun). Then, to make sure that we actually do rerun kati when
+# modifying the starlark files, we add the starlark files to the kati stamp file with
+# $(KATI_extra_file_deps).
+define run-starlark
+$(eval _starlark_results := $(OUT_DIR)/starlark_results/$(subst /,_,$(1)).mk)
+$(KATI_shell_no_rerun mkdir -p $(OUT_DIR)/starlark_results && $(OUT_DIR)/rbcrun --mode=make $(1) >$(_starlark_results) && touch -t 200001010000 $(_starlark_results))
+$(if $(filter-out 0,$(.SHELLSTATUS)),$(error Starlark failed to run))
+$(eval include $(_starlark_results))
+$(KATI_extra_file_deps $(LOADED_STARLARK_FILES))
+$(eval LOADED_STARLARK_FILES :=)
+$(eval _starlark_results :=)
 endef
 
-PREV_VERSIONS := OPR1 OPD1 OPD2 OPM1 OPM2 PPR1 PPD1 PPD2 PPM1 PPM2 QPR1
-ALL_VERSIONS := Q R S T U V W X Y Z
-ALL_VERSIONS := $(PREV_VERSIONS) $(foreach v,$(ALL_VERSIONS),$(call version-list,$(v)))
-PREV_VERSIONS :=
+# ---------------------------------------------------------------
+# Release config
+include $(BUILD_SYSTEM)/release_config.mk
+
+# ---------------------------------------------------------------
+# defines ALL_VERSIONS
+$(call run-starlark,build/make/core/all_versions.bzl)
 
 # Filters ALL_VERSIONS down to the range [$1, $2], and errors if $1 > $2 or $3 is
 # not in [$1, $2]
@@ -339,6 +355,7 @@
   RBC_PRODUCT_CONFIG \
   RBC_BOARD_CONFIG \
   SOONG_% \
+  TARGET_RELEASE \
   TOPDIR \
   TRACE_BEGIN_SOONG \
   USER)
@@ -553,6 +570,8 @@
 TARGET_OUT_NOTICE_FILES := $(TARGET_OUT_INTERMEDIATES)/NOTICE_FILES
 TARGET_OUT_FAKE := $(PRODUCT_OUT)/fake_packages
 TARGET_OUT_TESTCASES := $(PRODUCT_OUT)/testcases
+TARGET_OUT_FLAGS := $(TARGET_OUT_INTERMEDIATES)/FLAGS
+
 .KATI_READONLY := \
   TARGET_OUT_EXECUTABLES \
   TARGET_OUT_OPTIONAL_EXECUTABLES \
@@ -566,7 +585,8 @@
   TARGET_OUT_ETC \
   TARGET_OUT_NOTICE_FILES \
   TARGET_OUT_FAKE \
-  TARGET_OUT_TESTCASES
+  TARGET_OUT_TESTCASES \
+  TARGET_OUT_FLAGS
 
 ifeq ($(SANITIZE_LITE),true)
 # When using SANITIZE_LITE, APKs must not be packaged with sanitized libraries, as they will not
diff --git a/core/product_config.mk b/core/product_config.mk
index 6a27613..01ad030 100644
--- a/core/product_config.mk
+++ b/core/product_config.mk
@@ -247,7 +247,7 @@
   endif
 
   $(shell build/soong/scripts/update_out $(OUT_DIR)/rbc/rbc_product_config_results.mk \
-    $(OUT_DIR)/rbcrun $(OUT_DIR)/rbc/launcher.rbc)
+    $(OUT_DIR)/rbcrun --mode=rbc $(OUT_DIR)/rbc/launcher.rbc)
   ifneq ($(.SHELLSTATUS),0)
     $(error product configuration runner failed: $(.SHELLSTATUS))
   endif
diff --git a/core/release_config.mk b/core/release_config.mk
new file mode 100644
index 0000000..0ad87fc
--- /dev/null
+++ b/core/release_config.mk
@@ -0,0 +1,198 @@
+# Copyright (C) 2023 The Android Open Source Project
+#
+# 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.
+
+# Partitions that get build system flag summaries
+_FLAG_PARTITIONS := system vendor system_ext product
+
+# All possible release flags. Defined in the flags.mk files
+# throughout the tree
+_ALL_RELEASE_FLAGS :=
+
+# -----------------------------------------------------------------
+# Choose the flag files
+# Do this first, because we're going to unset TARGET_RELEASE before
+# including anyone, so they don't start making conditionals based on it.
+
+# If this is a google source tree, restrict it to only the one file
+# which has OWNERS control.  If it isn't let others define their own.
+config_map_files := build/make/release/release_config_map.mk \
+    $(if $(wildcard vendor/google/release/release_config_map.mk), \
+        vendor/google/release/release_config_map.mk, \
+        $(sort \
+            $(wildcard device/*/release/release_config_map.mk) \
+            $(wildcard device/*/*/release/release_config_map.mk) \
+            $(wildcard vendor/*/release/release_config_map.mk) \
+            $(wildcard vendor/*/*/release/release_config_map.mk) \
+        ) \
+    )
+
+# $1 config name
+# $2 release config files
+define declare-release-config
+    $(eval # No duplicates)
+    $(if $(filter $(_all_release_configs), $(strip $(1))), \
+        $(error declare-release-config: config $(strip $(1)) declared in: $(_included) Previously declared here: $(_all_release_configs.$(strip $(1)).DECLARED_IN)) \
+    )
+    $(eval # Must have release config files)
+    $(if $(strip $(2)),,  \
+        $(error declare-release-config: config $(strip $(1)) must have release config files) \
+    )
+    $(eval _all_release_configs := $(sort $(_all_release_configs) $(strip $(1))))
+    $(eval _all_release_configs.$(strip $(1)).DECLARED_IN := $(_included))
+    $(eval _all_release_configs.$(strip $(1)).FILES := $(strip $(2)))
+endef
+
+# Include the config map files
+$(foreach f, $(config_map_files), \
+    $(eval _included := $(f)) \
+    $(eval include $(f)) \
+)
+
+# If TARGET_RELEASE is set, fail if there is no matching release config
+# If it isn't set, no release config files will be included and all flags
+# will get their default values.
+ifneq ($(TARGET_RELEASE),)
+ifeq ($(filter $(_all_release_configs), $(TARGET_RELEASE)),)
+    $(error No release config found for TARGET_RELEASE: $(TARGET_RELEASE))
+else
+    # Choose flag files
+    # Don't sort this, use it in the order they gave us.
+    _release_config_files := $(_all_release_configs.$(TARGET_RELEASE).FILES)
+endif
+else
+# Useful for finding scripts etc that aren't passing or setting TARGET_RELEASE
+ifneq ($(FAIL_IF_NO_RELEASE_CONFIG),)
+    $(error FAIL_IF_NO_RELEASE_CONFIG was set and TARGET_RELEASE was not)
+endif
+_release_config_files :=
+endif
+
+# Unset variables so they can't use it
+define declare-release-config
+$(error declare-release-config can only be called from inside release_config_map.mk files)
+endef
+
+# TODO: Remove this check after enough people have sourced lunch that we don't
+# need to worry about it trying to do get_build_vars TARGET_RELEASE. Maybe after ~9/2023
+ifneq ($(CALLED_FROM_SETUP),true)
+define TARGET_RELEASE
+$(error TARGET_RELEASE may not be accessed directly. Use individual flags.)
+endef
+else
+TARGET_RELEASE:=
+endif
+.KATI_READONLY := TARGET_RELEASE
+
+$(foreach config, $(_all_release_configs), \
+    $(eval _all_release_configs.$(config).DECLARED_IN:= ) \
+    $(eval _all_release_configs.$(config).FILES:= ) \
+)
+_all_release_configs:=
+config_map_files:=
+
+# -----------------------------------------------------------------
+# Declare the flags
+
+# $1 partition(s)
+# $2 flag name. Must start with RELEASE_
+# $3 default. True or false
+define declare-build-flag
+    $(if $(filter-out all $(_FLAG_PARTITIONS), $(strip $(1))), \
+        $(error declare-build-flag: invalid partitions: $(strip $(1))) \
+    )
+    $(if $(and $(filter all,$(strip $(1))),$(filter-out all, $(strip $(1)))), \
+        $(error declare-build-flag: "all" can't be combined with other partitions: $(strip $(1))), \
+        $(eval declare-build-flag.partition := $(_FLAG_PARTITIONS)) \
+    )
+    $(if $(filter-out RELEASE_%, $(strip $(2))), \
+        $(error declare-build-flag: Release flag names must start with RELEASE_: $(strip $(2))) \
+    )
+    $(eval _ALL_RELEASE_FLAGS += $(strip $(2)))
+    $(foreach partition, $(declare-build-flag.partition), \
+        $(eval _ALL_RELEASE_FLAGS.PARTITIONS.$(partition) := $(sort \
+            $(_ALL_RELEASE_FLAGS.PARTITIONS.$(partition)) $(strip $(2)))) \
+    )
+    $(eval _ALL_RELEASE_FLAGS.$(strip $(2)).PARTITIONS := $(declare-build-flag.partition))
+    $(eval _ALL_RELEASE_FLAGS.$(strip $(2)).DEFAULT := $(strip $(3)))
+    $(eval _ALL_RELEASE_FLAGS.$(strip $(2)).DECLARED_IN := $(_included))
+    $(eval _ALL_RELEASE_FLAGS.$(strip $(2)).VALUE := $(strip $(3)))
+    $(eval _ALL_RELEASE_FLAGS.$(strip $(2)).SET_IN := $(_included))
+    $(eval declare-build-flag.partition:=)
+endef
+
+
+# Choose the files
+# If this is a google source tree, restrict it to only the one file
+# which has OWNERS control.  If it isn't let others define their own.
+flag_declaration_files := build/make/release/flags.mk \
+    $(if $(wildcard vendor/google/release/flags.mk), \
+        vendor/google/release/flags.mk, \
+        $(sort \
+            $(wildcard device/*/release/flags.mk) \
+            $(wildcard device/*/*/release/flags.mk) \
+            $(wildcard vendor/*/release/flags.mk) \
+            $(wildcard vendor/*/*/release/flags.mk) \
+        ) \
+    )
+
+# Include the files
+$(foreach f, $(flag_declaration_files), \
+    $(eval _included := $(f)) \
+    $(eval include $(f)) \
+)
+
+# Don't let anyone declare build flags after here
+define declare-build-flag
+$(error declare-build-flag can only be called from inside flag definition files.)
+endef
+
+# No more flags from here on
+.KATI_READONLY := _ALL_RELEASE_FLAGS
+
+# -----------------------------------------------------------------
+# Set the flags
+
+# $(1): Flag name. Must start with RELEASE_ and have been defined by declare-build-flag
+# $(2): Value. True or false
+define set-build-flag
+    $(if $(filter-out $(_ALL_RELEASE_FLAGS), $(strip $(1))), \
+        $(error set-build-flag: Undeclared build flag: $(strip $(1))) \
+    )
+    $(eval _ALL_RELEASE_FLAGS.$(strip $(1)).VALUE := $(strip $(2)))
+    $(eval _ALL_RELEASE_FLAGS.$(strip $(1)).SET_IN := $(_included))
+endef
+
+# Include the files (if there are any)
+$(foreach f, $(_release_config_files), \
+    $(eval _included := $(f)) \
+    $(eval include $(f)) \
+)
+
+# Don't let anyone declare build flags after here
+define set-build-flag
+$(error set-build-flag can only be called from inside release config files.)
+endef
+
+# Set the flag values, and don't allow any one to modify them.
+$(foreach flag, $(_ALL_RELEASE_FLAGS), \
+    $(eval $(flag) := $(_ALL_RELEASE_FLAGS.$(flag).VALUE)) \
+    $(eval .KATI_READONLY := $(flag)) \
+)
+
+# -----------------------------------------------------------------
+# Clear out vars
+flag_declaration_files:=
+flag_files:=
+_included:=
+_release_config_files:=
diff --git a/core/tasks/tools/compatibility.mk b/core/tasks/tools/compatibility.mk
index c770b34..8ae2a9a 100644
--- a/core/tasks/tools/compatibility.mk
+++ b/core/tasks/tools/compatibility.mk
@@ -44,10 +44,16 @@
 
 # The JDK to package into the test suite zip file.  Always package the linux JDK.
 test_suite_jdk_dir := $(ANDROID_JAVA_HOME)/../linux-x86
+ifndef test_suite_jdk_files
+  # This file gets included many times, so make sure we only run the $(shell) once.
+  # Otherwise it will slow down every build due to all copies of it being rerun when kati
+  # checks the stamp file.
+  test_suite_jdk_files :=$= $(shell find $(test_suite_jdk_dir) -type f | sort)
+endif
 test_suite_jdk := $(call intermediates-dir-for,PACKAGING,$(test_suite_name)_jdk,HOST)/jdk.zip
 $(test_suite_jdk): PRIVATE_JDK_DIR := $(test_suite_jdk_dir)
 $(test_suite_jdk): PRIVATE_SUBDIR := $(test_suite_subdir)
-$(test_suite_jdk): $(shell find $(test_suite_jdk_dir) -type f | sort)
+$(test_suite_jdk): $(test_suite_jdk_files)
 $(test_suite_jdk): $(SOONG_ZIP)
 	$(SOONG_ZIP) -o $@ -P $(PRIVATE_SUBDIR)/jdk -C $(PRIVATE_JDK_DIR) -D $(PRIVATE_JDK_DIR) -sha256
 
diff --git a/envsetup.sh b/envsetup.sh
index 916344c..ef48249 100644
--- a/envsetup.sh
+++ b/envsetup.sh
@@ -842,7 +842,7 @@
     export TARGET_PRODUCT=$(get_build_var TARGET_PRODUCT)
     export TARGET_BUILD_VARIANT=$(get_build_var TARGET_BUILD_VARIANT)
     if [ -n "$release" ]; then
-      export TARGET_RELEASE=$(get_build_var TARGET_RELEASE)
+      export TARGET_RELEASE=$release
     else
       unset TARGET_RELEASE
     fi
diff --git a/release/flags.mk b/release/flags.mk
new file mode 100644
index 0000000..eb97dc1
--- /dev/null
+++ b/release/flags.mk
@@ -0,0 +1,23 @@
+# Copyright (C) 2023 The Android Open Source Project
+#
+# 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.
+
+# This file defines the build system flags that can be set based on the
+# release configuration.  If at all possible, use aconfig flags instead.
+# This is for things that must be decided at compile time.
+
+# Flag names should be alphabetical by flag name.
+
+$(call declare-build-flag, system, RELEASE_THE_FIRST_FLAG, true)
+$(call declare-build-flag, system, RELEASE_THE_SECOND_FLAG, true)
+
diff --git a/release/release_config_map.mk b/release/release_config_map.mk
new file mode 100644
index 0000000..190cb89
--- /dev/null
+++ b/release/release_config_map.mk
@@ -0,0 +1,16 @@
+# Copyright (C) 2023 The Android Open Source Project
+#
+# 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.
+
+# AOSP doesn't define any release configs yet.
+
diff --git a/tools/aconfig/protos/aconfig.proto b/tools/aconfig/protos/aconfig.proto
index a3b1fec..9d36a9e 100644
--- a/tools/aconfig/protos/aconfig.proto
+++ b/tools/aconfig/protos/aconfig.proto
@@ -12,8 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License
 
-// This is the schema definition for of Aconfig files. Modifications need to be
-// either backwards compatible, or include updates to all Aconfig files in the
+// This is the schema definition for aconfig files. Modifications need to be
+// either backwards compatible, or include updates to all aconfig files in the
 // Android tree.
 
 syntax = "proto2";
@@ -32,40 +32,33 @@
   READ_WRITE = 2;
 }
 
-// aconfig input messages: configuration and override data
+// aconfig input messages: flag declarations and values
 
-message flag_value {
-  required flag_state state = 1;
-  required flag_permission permission = 2;
-  optional uint32 since = 3;
-}
-
-message flag_definition {
+message flag_declaration {
   required string name = 1;
   required string description = 2;
-  repeated flag_value value = 3;
 };
 
-message namespace {
+message flag_declarations {
   required string namespace = 1;
-  repeated flag_definition flag = 2;
+  repeated flag_declaration flag = 2;
 };
 
-message flag_override {
+message flag_value {
   required string namespace = 1;
   required string name = 2;
   required flag_state state = 3;
   required flag_permission permission = 4;
 };
 
-message flag_overrides {
-  repeated flag_override flag_override = 1;
+message flag_values {
+  repeated flag_value flag_value = 1;
 };
 
-// aconfig output messages: parsed and verified configuration and override data
+// aconfig output messages: parsed and verified flag declarations and values
 
 message tracepoint {
-  // path to config or override file releative to $TOP
+  // path to declaration or value file relative to $TOP
   required string source = 1;
   required flag_state state = 2;
   required flag_permission permission = 3;
diff --git a/tools/aconfig/src/aconfig.rs b/tools/aconfig/src/aconfig.rs
index 8fe82b6..b09648d 100644
--- a/tools/aconfig/src/aconfig.rs
+++ b/tools/aconfig/src/aconfig.rs
@@ -20,9 +20,8 @@
 
 use crate::cache::{Cache, Item, Tracepoint};
 use crate::protos::{
-    ProtoFlagDefinition, ProtoFlagDefinitionValue, ProtoFlagOverride, ProtoFlagOverrides,
-    ProtoFlagPermission, ProtoFlagState, ProtoNamespace, ProtoParsedFlag, ProtoParsedFlags,
-    ProtoTracepoint,
+    ProtoFlagDeclaration, ProtoFlagDeclarations, ProtoFlagPermission, ProtoFlagState,
+    ProtoFlagValue, ProtoFlagValues, ProtoParsedFlag, ProtoParsedFlags, ProtoTracepoint,
 };
 
 #[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone, Copy)]
@@ -80,112 +79,43 @@
 }
 
 #[derive(Debug, PartialEq, Eq)]
-pub struct Value {
-    state: FlagState,
-    permission: Permission,
-    since: Option<u32>,
-}
-
-#[allow(dead_code)] // only used in unit tests
-impl Value {
-    pub fn new(state: FlagState, permission: Permission, since: u32) -> Value {
-        Value { state, permission, since: Some(since) }
-    }
-
-    pub fn default(state: FlagState, permission: Permission) -> Value {
-        Value { state, permission, since: None }
-    }
-}
-
-impl TryFrom<ProtoFlagDefinitionValue> for Value {
-    type Error = Error;
-
-    fn try_from(proto: ProtoFlagDefinitionValue) -> Result<Self, Self::Error> {
-        let Some(proto_state) = proto.state else {
-            return Err(anyhow!("missing 'state' field"));
-        };
-        let state = proto_state.try_into()?;
-        let Some(proto_permission) = proto.permission else {
-            return Err(anyhow!("missing 'permission' field"));
-        };
-        let permission = proto_permission.try_into()?;
-        Ok(Value { state, permission, since: proto.since })
-    }
-}
-
-#[derive(Debug, PartialEq, Eq)]
-pub struct Flag {
+pub struct FlagDeclaration {
     pub name: String,
     pub description: String,
-
-    // ordered by Value.since; guaranteed to contain at least one item (the default value, with
-    // since == None)
-    pub values: Vec<Value>,
 }
 
-impl Flag {
+impl FlagDeclaration {
     #[allow(dead_code)] // only used in unit tests
-    pub fn try_from_text_proto(text_proto: &str) -> Result<Flag> {
-        let proto: ProtoFlagDefinition = crate::protos::try_from_text_proto(text_proto)
+    pub fn try_from_text_proto(text_proto: &str) -> Result<FlagDeclaration> {
+        let proto: ProtoFlagDeclaration = crate::protos::try_from_text_proto(text_proto)
             .with_context(|| text_proto.to_owned())?;
         proto.try_into()
     }
-
-    pub fn resolve(&self, build_id: u32) -> (FlagState, Permission) {
-        let mut state = self.values[0].state;
-        let mut permission = self.values[0].permission;
-        for candidate in self.values.iter().skip(1) {
-            let since = candidate.since.expect("invariant: non-defaults values have Some(since)");
-            if since <= build_id {
-                state = candidate.state;
-                permission = candidate.permission;
-            }
-        }
-        (state, permission)
-    }
 }
 
-impl TryFrom<ProtoFlagDefinition> for Flag {
+impl TryFrom<ProtoFlagDeclaration> for FlagDeclaration {
     type Error = Error;
 
-    fn try_from(proto: ProtoFlagDefinition) -> Result<Self, Self::Error> {
+    fn try_from(proto: ProtoFlagDeclaration) -> Result<Self, Self::Error> {
         let Some(name) = proto.name else {
             return Err(anyhow!("missing 'name' field"));
         };
         let Some(description) = proto.description else {
             return Err(anyhow!("missing 'description' field"));
         };
-        if proto.value.is_empty() {
-            return Err(anyhow!("missing 'value' field"));
-        }
-
-        let mut values: Vec<Value> = vec![];
-        for proto_value in proto.value.into_iter() {
-            let v: Value = proto_value.try_into()?;
-            if values.iter().any(|w| v.since == w.since) {
-                let msg = match v.since {
-                    None => format!("flag {}: multiple default values", name),
-                    Some(x) => format!("flag {}: multiple values for since={}", name, x),
-                };
-                return Err(anyhow!(msg));
-            }
-            values.push(v);
-        }
-        values.sort_by_key(|v| v.since);
-
-        Ok(Flag { name, description, values })
+        Ok(FlagDeclaration { name, description })
     }
 }
 
 #[derive(Debug, PartialEq, Eq)]
-pub struct Namespace {
+pub struct FlagDeclarations {
     pub namespace: String,
-    pub flags: Vec<Flag>,
+    pub flags: Vec<FlagDeclaration>,
 }
 
-impl Namespace {
-    pub fn try_from_text_proto(text_proto: &str) -> Result<Namespace> {
-        let proto: ProtoNamespace = crate::protos::try_from_text_proto(text_proto)
+impl FlagDeclarations {
+    pub fn try_from_text_proto(text_proto: &str) -> Result<FlagDeclarations> {
+        let proto: ProtoFlagDeclarations = crate::protos::try_from_text_proto(text_proto)
             .with_context(|| text_proto.to_owned())?;
         let Some(namespace) = proto.namespace else {
             return Err(anyhow!("missing 'namespace' field"));
@@ -194,35 +124,35 @@
         for proto_flag in proto.flag.into_iter() {
             flags.push(proto_flag.try_into()?);
         }
-        Ok(Namespace { namespace, flags })
+        Ok(FlagDeclarations { namespace, flags })
     }
 }
 
 #[derive(Debug, PartialEq, Eq)]
-pub struct Override {
+pub struct FlagValue {
     pub namespace: String,
     pub name: String,
     pub state: FlagState,
     pub permission: Permission,
 }
 
-impl Override {
+impl FlagValue {
     #[allow(dead_code)] // only used in unit tests
-    pub fn try_from_text_proto(text_proto: &str) -> Result<Override> {
-        let proto: ProtoFlagOverride = crate::protos::try_from_text_proto(text_proto)?;
+    pub fn try_from_text_proto(text_proto: &str) -> Result<FlagValue> {
+        let proto: ProtoFlagValue = crate::protos::try_from_text_proto(text_proto)?;
         proto.try_into()
     }
 
-    pub fn try_from_text_proto_list(text_proto: &str) -> Result<Vec<Override>> {
-        let proto: ProtoFlagOverrides = crate::protos::try_from_text_proto(text_proto)?;
-        proto.flag_override.into_iter().map(|proto_flag| proto_flag.try_into()).collect()
+    pub fn try_from_text_proto_list(text_proto: &str) -> Result<Vec<FlagValue>> {
+        let proto: ProtoFlagValues = crate::protos::try_from_text_proto(text_proto)?;
+        proto.flag_value.into_iter().map(|proto_flag| proto_flag.try_into()).collect()
     }
 }
 
-impl TryFrom<ProtoFlagOverride> for Override {
+impl TryFrom<ProtoFlagValue> for FlagValue {
     type Error = Error;
 
-    fn try_from(proto: ProtoFlagOverride) -> Result<Self, Self::Error> {
+    fn try_from(proto: ProtoFlagValue) -> Result<Self, Self::Error> {
         let Some(namespace) = proto.namespace else {
             return Err(anyhow!("missing 'namespace' field"));
         };
@@ -237,7 +167,7 @@
             return Err(anyhow!("missing 'permission' field"));
         };
         let permission = proto_permission.try_into()?;
-        Ok(Override { namespace, name, state, permission })
+        Ok(FlagValue { namespace, name, state, permission })
     }
 }
 
@@ -282,29 +212,16 @@
 
     #[test]
     fn test_flag_try_from_text_proto() {
-        let expected = Flag {
+        let expected = FlagDeclaration {
             name: "1234".to_owned(),
             description: "Description of the flag".to_owned(),
-            values: vec![
-                Value::default(FlagState::Disabled, Permission::ReadOnly),
-                Value::new(FlagState::Enabled, Permission::ReadWrite, 8),
-            ],
         };
 
         let s = r#"
         name: "1234"
         description: "Description of the flag"
-        value {
-            state: DISABLED
-            permission: READ_ONLY
-        }
-        value {
-            state: ENABLED
-            permission: READ_WRITE
-            since: 8
-        }
         "#;
-        let actual = Flag::try_from_text_proto(s).unwrap();
+        let actual = FlagDeclaration::try_from_text_proto(s).unwrap();
 
         assert_eq!(expected, actual);
     }
@@ -313,52 +230,24 @@
     fn test_flag_try_from_text_proto_bad_input() {
         let s = r#"
         name: "a"
-        description: "Description of the flag"
         "#;
-        let error = Flag::try_from_text_proto(s).unwrap_err();
-        assert_eq!(format!("{:?}", error), "missing 'value' field");
-
-        let s = r#"
-        description: "Description of the flag"
-        value {
-            state: ENABLED
-            permission: READ_ONLY
-        }
-        "#;
-        let error = Flag::try_from_text_proto(s).unwrap_err();
+        let error = FlagDeclaration::try_from_text_proto(s).unwrap_err();
         assert!(format!("{:?}", error).contains("Message not initialized"));
 
         let s = r#"
-        name: "a"
         description: "Description of the flag"
-        value {
-            state: ENABLED
-            permission: READ_ONLY
-        }
-        value {
-            state: ENABLED
-            permission: READ_ONLY
-        }
         "#;
-        let error = Flag::try_from_text_proto(s).unwrap_err();
-        assert_eq!(format!("{:?}", error), "flag a: multiple default values");
+        let error = FlagDeclaration::try_from_text_proto(s).unwrap_err();
+        assert!(format!("{:?}", error).contains("Message not initialized"));
     }
 
     #[test]
     fn test_namespace_try_from_text_proto() {
-        let expected = Namespace {
+        let expected = FlagDeclarations {
             namespace: "ns".to_owned(),
             flags: vec![
-                Flag {
-                    name: "a".to_owned(),
-                    description: "A".to_owned(),
-                    values: vec![Value::default(FlagState::Enabled, Permission::ReadOnly)],
-                },
-                Flag {
-                    name: "b".to_owned(),
-                    description: "B".to_owned(),
-                    values: vec![Value::default(FlagState::Disabled, Permission::ReadWrite)],
-                },
+                FlagDeclaration { name: "a".to_owned(), description: "A".to_owned() },
+                FlagDeclaration { name: "b".to_owned(), description: "B".to_owned() },
             ],
         };
 
@@ -367,28 +256,20 @@
         flag {
             name: "a"
             description: "A"
-            value {
-                state: ENABLED
-                permission: READ_ONLY
-            }
         }
         flag {
             name: "b"
             description: "B"
-            value {
-                state: DISABLED
-                permission: READ_WRITE
-            }
         }
         "#;
-        let actual = Namespace::try_from_text_proto(s).unwrap();
+        let actual = FlagDeclarations::try_from_text_proto(s).unwrap();
 
         assert_eq!(expected, actual);
     }
 
     #[test]
-    fn test_override_try_from_text_proto_list() {
-        let expected = Override {
+    fn test_flag_declaration_try_from_text_proto_list() {
+        let expected = FlagValue {
             namespace: "ns".to_owned(),
             name: "1234".to_owned(),
             state: FlagState::Enabled,
@@ -401,32 +282,8 @@
         state: ENABLED
         permission: READ_ONLY
         "#;
-        let actual = Override::try_from_text_proto(s).unwrap();
+        let actual = FlagValue::try_from_text_proto(s).unwrap();
 
         assert_eq!(expected, actual);
     }
-
-    #[test]
-    fn test_flag_resolve() {
-        let flag = Flag {
-            name: "a".to_owned(),
-            description: "A".to_owned(),
-            values: vec![
-                Value::default(FlagState::Disabled, Permission::ReadOnly),
-                Value::new(FlagState::Disabled, Permission::ReadWrite, 10),
-                Value::new(FlagState::Enabled, Permission::ReadOnly, 20),
-                Value::new(FlagState::Enabled, Permission::ReadWrite, 30),
-            ],
-        };
-        assert_eq!((FlagState::Disabled, Permission::ReadOnly), flag.resolve(0));
-        assert_eq!((FlagState::Disabled, Permission::ReadOnly), flag.resolve(9));
-        assert_eq!((FlagState::Disabled, Permission::ReadWrite), flag.resolve(10));
-        assert_eq!((FlagState::Disabled, Permission::ReadWrite), flag.resolve(11));
-        assert_eq!((FlagState::Disabled, Permission::ReadWrite), flag.resolve(19));
-        assert_eq!((FlagState::Enabled, Permission::ReadOnly), flag.resolve(20));
-        assert_eq!((FlagState::Enabled, Permission::ReadOnly), flag.resolve(21));
-        assert_eq!((FlagState::Enabled, Permission::ReadOnly), flag.resolve(29));
-        assert_eq!((FlagState::Enabled, Permission::ReadWrite), flag.resolve(30));
-        assert_eq!((FlagState::Enabled, Permission::ReadWrite), flag.resolve(10_000));
-    }
 }
diff --git a/tools/aconfig/src/cache.rs b/tools/aconfig/src/cache.rs
index 4b46c42..3ecadc9 100644
--- a/tools/aconfig/src/cache.rs
+++ b/tools/aconfig/src/cache.rs
@@ -14,13 +14,16 @@
  * limitations under the License.
  */
 
-use anyhow::{anyhow, Result};
+use anyhow::{anyhow, bail, ensure, Result};
 use serde::{Deserialize, Serialize};
 use std::io::{Read, Write};
 
-use crate::aconfig::{Flag, FlagState, Override, Permission};
+use crate::aconfig::{FlagDeclaration, FlagState, FlagValue, Permission};
 use crate::commands::Source;
 
+const DEFAULT_FLAG_STATE: FlagState = FlagState::Disabled;
+const DEFAULT_FLAG_PERMISSION: Permission = Permission::ReadWrite;
+
 #[derive(Serialize, Deserialize, Debug)]
 pub struct Tracepoint {
     pub source: Source,
@@ -44,14 +47,13 @@
 
 #[derive(Serialize, Deserialize, Debug)]
 pub struct Cache {
-    build_id: u32,
     namespace: String,
     items: Vec<Item>,
 }
 
 impl Cache {
-    pub fn new(build_id: u32, namespace: String) -> Cache {
-        Cache { build_id, namespace, items: vec![] }
+    pub fn new(namespace: String) -> Cache {
+        Cache { namespace, items: vec![] }
     }
 
     pub fn read_from_reader(reader: impl Read) -> Result<Cache> {
@@ -62,40 +64,51 @@
         serde_json::to_writer(writer, self).map_err(|e| e.into())
     }
 
-    pub fn add_flag(&mut self, source: Source, flag: Flag) -> Result<()> {
-        if self.items.iter().any(|item| item.name == flag.name) {
+    pub fn add_flag_declaration(
+        &mut self,
+        source: Source,
+        declaration: FlagDeclaration,
+    ) -> Result<()> {
+        if self.items.iter().any(|item| item.name == declaration.name) {
             return Err(anyhow!(
-                "failed to add flag {} from {}: flag already defined",
-                flag.name,
+                "failed to declare flag {} from {}: flag already declared",
+                declaration.name,
                 source,
             ));
         }
-        let (state, permission) = flag.resolve(self.build_id);
         self.items.push(Item {
             namespace: self.namespace.clone(),
-            name: flag.name.clone(),
-            description: flag.description,
-            state,
-            permission,
-            trace: vec![Tracepoint { source, state, permission }],
+            name: declaration.name.clone(),
+            description: declaration.description,
+            state: DEFAULT_FLAG_STATE,
+            permission: DEFAULT_FLAG_PERMISSION,
+            trace: vec![Tracepoint {
+                source,
+                state: DEFAULT_FLAG_STATE,
+                permission: DEFAULT_FLAG_PERMISSION,
+            }],
         });
         Ok(())
     }
 
-    pub fn add_override(&mut self, source: Source, override_: Override) -> Result<()> {
-        if override_.namespace != self.namespace {
-            // TODO: print warning?
-            return Ok(());
-        }
-        let Some(existing_item) = self.items.iter_mut().find(|item| item.name == override_.name) else {
-            return Err(anyhow!("failed to override flag {}: unknown flag", override_.name));
+    pub fn add_flag_value(&mut self, source: Source, value: FlagValue) -> Result<()> {
+        ensure!(
+            value.namespace == self.namespace,
+            "failed to set values for flag {}/{} from {}: expected namespace {}",
+            value.namespace,
+            value.name,
+            source,
+            self.namespace
+        );
+        let Some(existing_item) = self.items.iter_mut().find(|item| item.name == value.name) else {
+            bail!("failed to set values for flag {}/{} from {}: flag not declared", value.namespace, value.name, source);
         };
-        existing_item.state = override_.state;
-        existing_item.permission = override_.permission;
+        existing_item.state = value.state;
+        existing_item.permission = value.permission;
         existing_item.trace.push(Tracepoint {
             source,
-            state: override_.state,
-            permission: override_.permission,
+            state: value.state,
+            permission: value.permission,
         });
         Ok(())
     }
@@ -112,49 +125,41 @@
 #[cfg(test)]
 mod tests {
     use super::*;
-    use crate::aconfig::{FlagState, Permission, Value};
+    use crate::aconfig::{FlagState, Permission};
 
     #[test]
-    fn test_add_flag() {
-        let mut cache = Cache::new(1, "ns".to_string());
+    fn test_add_flag_declaration() {
+        let mut cache = Cache::new("ns".to_string());
         cache
-            .add_flag(
+            .add_flag_declaration(
                 Source::File("first.txt".to_string()),
-                Flag {
-                    name: "foo".to_string(),
-                    description: "desc".to_string(),
-                    values: vec![Value::default(FlagState::Enabled, Permission::ReadOnly)],
-                },
+                FlagDeclaration { name: "foo".to_string(), description: "desc".to_string() },
             )
             .unwrap();
         let error = cache
-            .add_flag(
+            .add_flag_declaration(
                 Source::File("second.txt".to_string()),
-                Flag {
-                    name: "foo".to_string(),
-                    description: "desc".to_string(),
-                    values: vec![Value::default(FlagState::Disabled, Permission::ReadOnly)],
-                },
+                FlagDeclaration { name: "foo".to_string(), description: "desc".to_string() },
             )
             .unwrap_err();
         assert_eq!(
             &format!("{:?}", error),
-            "failed to add flag foo from second.txt: flag already defined"
+            "failed to declare flag foo from second.txt: flag already declared"
         );
     }
 
     #[test]
-    fn test_add_override() {
+    fn test_add_flag_value() {
         fn check(cache: &Cache, name: &str, expected: (FlagState, Permission)) -> bool {
             let item = cache.iter().find(|&item| item.name == name).unwrap();
             item.state == expected.0 && item.permission == expected.1
         }
 
-        let mut cache = Cache::new(1, "ns".to_string());
+        let mut cache = Cache::new("ns".to_string());
         let error = cache
-            .add_override(
+            .add_flag_value(
                 Source::Memory,
-                Override {
+                FlagValue {
                     namespace: "ns".to_string(),
                     name: "foo".to_string(),
                     state: FlagState::Enabled,
@@ -162,39 +167,36 @@
                 },
             )
             .unwrap_err();
-        assert_eq!(&format!("{:?}", error), "failed to override flag foo: unknown flag");
+        assert_eq!(
+            &format!("{:?}", error),
+            "failed to set values for flag ns/foo from <memory>: flag not declared"
+        );
 
         cache
-            .add_flag(
+            .add_flag_declaration(
                 Source::File("first.txt".to_string()),
-                Flag {
-                    name: "foo".to_string(),
-                    description: "desc".to_string(),
-                    values: vec![Value::default(FlagState::Enabled, Permission::ReadOnly)],
-                },
+                FlagDeclaration { name: "foo".to_string(), description: "desc".to_string() },
             )
             .unwrap();
-        dbg!(&cache);
-        assert!(check(&cache, "foo", (FlagState::Enabled, Permission::ReadOnly)));
+        assert!(check(&cache, "foo", (DEFAULT_FLAG_STATE, DEFAULT_FLAG_PERMISSION)));
 
         cache
-            .add_override(
+            .add_flag_value(
                 Source::Memory,
-                Override {
+                FlagValue {
                     namespace: "ns".to_string(),
                     name: "foo".to_string(),
                     state: FlagState::Disabled,
-                    permission: Permission::ReadWrite,
+                    permission: Permission::ReadOnly,
                 },
             )
             .unwrap();
-        dbg!(&cache);
-        assert!(check(&cache, "foo", (FlagState::Disabled, Permission::ReadWrite)));
+        assert!(check(&cache, "foo", (FlagState::Disabled, Permission::ReadOnly)));
 
         cache
-            .add_override(
+            .add_flag_value(
                 Source::Memory,
-                Override {
+                FlagValue {
                     namespace: "ns".to_string(),
                     name: "foo".to_string(),
                     state: FlagState::Enabled,
@@ -205,17 +207,18 @@
         assert!(check(&cache, "foo", (FlagState::Enabled, Permission::ReadWrite)));
 
         // different namespace -> no-op
-        cache
-            .add_override(
+        let error = cache
+            .add_flag_value(
                 Source::Memory,
-                Override {
+                FlagValue {
                     namespace: "some-other-namespace".to_string(),
                     name: "foo".to_string(),
                     state: FlagState::Enabled,
                     permission: Permission::ReadOnly,
                 },
             )
-            .unwrap();
+            .unwrap_err();
+        assert_eq!(&format!("{:?}", error), "failed to set values for flag some-other-namespace/foo from <memory>: expected namespace ns");
         assert!(check(&cache, "foo", (FlagState::Enabled, Permission::ReadWrite)));
     }
 }
diff --git a/tools/aconfig/src/codegen_java.rs b/tools/aconfig/src/codegen_java.rs
index 9d52cce..bbf1272 100644
--- a/tools/aconfig/src/codegen_java.rs
+++ b/tools/aconfig/src/codegen_java.rs
@@ -20,13 +20,9 @@
 
 use crate::aconfig::{FlagState, Permission};
 use crate::cache::{Cache, Item};
+use crate::commands::OutputFile;
 
-pub struct GeneratedFile {
-    pub file_content: String,
-    pub file_name: String,
-}
-
-pub fn generate_java_code(cache: &Cache) -> Result<GeneratedFile> {
+pub fn generate_java_code(cache: &Cache) -> Result<OutputFile> {
     let class_elements: Vec<ClassElement> = cache.iter().map(create_class_element).collect();
     let readwrite = class_elements.iter().any(|item| item.readwrite);
     let namespace = uppercase_first_letter(
@@ -35,8 +31,9 @@
     let context = Context { namespace: namespace.clone(), readwrite, class_elements };
     let mut template = TinyTemplate::new();
     template.add_template("java_code_gen", include_str!("../templates/java.template"))?;
-    let file_content = template.render("java_code_gen", &context)?;
-    Ok(GeneratedFile { file_content, file_name: format!("{}.java", namespace) })
+    let contents = template.render("java_code_gen", &context)?;
+    let path = ["com", "android", "internal", "aconfig", &(namespace + ".java")].iter().collect();
+    Ok(OutputFile { contents: contents.into(), path })
 }
 
 #[derive(Serialize)]
@@ -87,56 +84,67 @@
 #[cfg(test)]
 mod tests {
     use super::*;
-    use crate::aconfig::{Flag, Value};
+    use crate::aconfig::{FlagDeclaration, FlagValue};
     use crate::commands::Source;
 
     #[test]
     fn test_generate_java_code() {
         let namespace = "TeSTFlaG";
-        let mut cache = Cache::new(1, namespace.to_string());
+        let mut cache = Cache::new(namespace.to_string());
         cache
-            .add_flag(
+            .add_flag_declaration(
                 Source::File("test.txt".to_string()),
-                Flag {
+                FlagDeclaration {
                     name: "test".to_string(),
                     description: "buildtime enable".to_string(),
-                    values: vec![Value::default(FlagState::Enabled, Permission::ReadOnly)],
                 },
             )
             .unwrap();
         cache
-            .add_flag(
+            .add_flag_declaration(
                 Source::File("test2.txt".to_string()),
-                Flag {
+                FlagDeclaration {
                     name: "test2".to_string(),
                     description: "runtime disable".to_string(),
-                    values: vec![Value::default(FlagState::Disabled, Permission::ReadWrite)],
                 },
             )
             .unwrap();
-        let expect_content = "package com.android.aconfig;
+        cache
+            .add_flag_value(
+                Source::Memory,
+                FlagValue {
+                    namespace: namespace.to_string(),
+                    name: "test".to_string(),
+                    state: FlagState::Disabled,
+                    permission: Permission::ReadOnly,
+                },
+            )
+            .unwrap();
+        let expect_content = r#"package com.android.internal.aconfig;
 
         import android.provider.DeviceConfig;
 
         public final class Testflag {
 
             public static boolean test() {
-                return true;
+                return false;
             }
 
             public static boolean test2() {
                 return DeviceConfig.getBoolean(
-                    \"Testflag\",
-                    \"test2__test2\",
+                    "Testflag",
+                    "test2__test2",
                     false
                 );
             }
 
         }
-        ";
-        let expected_file_name = format!("{}.java", uppercase_first_letter(namespace));
-        let generated_file = generate_java_code(&cache).unwrap();
-        assert_eq!(expected_file_name, generated_file.file_name);
-        assert_eq!(expect_content.replace(' ', ""), generated_file.file_content.replace(' ', ""));
+        "#;
+        let file = generate_java_code(&cache).unwrap();
+        assert_eq!("com/android/internal/aconfig/Testflag.java", file.path.to_str().unwrap());
+        assert_eq!(
+            expect_content.replace(' ', ""),
+            String::from_utf8(file.contents).unwrap().replace(' ', "")
+        );
     }
 }
diff --git a/tools/aconfig/src/commands.rs b/tools/aconfig/src/commands.rs
index 1487e72..475d9b8 100644
--- a/tools/aconfig/src/commands.rs
+++ b/tools/aconfig/src/commands.rs
@@ -20,10 +20,11 @@
 use serde::{Deserialize, Serialize};
 use std::fmt;
 use std::io::Read;
+use std::path::PathBuf;
 
-use crate::aconfig::{Namespace, Override};
+use crate::aconfig::{FlagDeclarations, FlagValue};
 use crate::cache::Cache;
-use crate::codegen_java::{generate_java_code, GeneratedFile};
+use crate::codegen_java::generate_java_code;
 use crate::protos::ProtoParsedFlags;
 
 #[derive(Serialize, Deserialize, Clone, Debug)]
@@ -47,45 +48,50 @@
     pub reader: Box<dyn Read>,
 }
 
-pub fn create_cache(
-    build_id: u32,
-    namespace: &str,
-    aconfigs: Vec<Input>,
-    overrides: Vec<Input>,
-) -> Result<Cache> {
-    let mut cache = Cache::new(build_id, namespace.to_owned());
+pub struct OutputFile {
+    pub path: PathBuf, // relative to some root directory only main knows about
+    pub contents: Vec<u8>,
+}
 
-    for mut input in aconfigs {
+pub fn create_cache(
+    namespace: &str,
+    declarations: Vec<Input>,
+    values: Vec<Input>,
+) -> Result<Cache> {
+    let mut cache = Cache::new(namespace.to_owned());
+
+    for mut input in declarations {
         let mut contents = String::new();
         input.reader.read_to_string(&mut contents)?;
-        let ns = Namespace::try_from_text_proto(&contents)
+        let dec_list = FlagDeclarations::try_from_text_proto(&contents)
             .with_context(|| format!("Failed to parse {}", input.source))?;
         ensure!(
-            namespace == ns.namespace,
+            namespace == dec_list.namespace,
             "Failed to parse {}: expected namespace {}, got {}",
             input.source,
             namespace,
-            ns.namespace
+            dec_list.namespace
         );
-        for flag in ns.flags.into_iter() {
-            cache.add_flag(input.source.clone(), flag)?;
+        for d in dec_list.flags.into_iter() {
+            cache.add_flag_declaration(input.source.clone(), d)?;
         }
     }
 
-    for mut input in overrides {
+    for mut input in values {
         let mut contents = String::new();
         input.reader.read_to_string(&mut contents)?;
-        let overrides = Override::try_from_text_proto_list(&contents)
+        let values_list = FlagValue::try_from_text_proto_list(&contents)
             .with_context(|| format!("Failed to parse {}", input.source))?;
-        for override_ in overrides {
-            cache.add_override(input.source.clone(), override_)?;
+        for v in values_list {
+            // TODO: warn about flag values that do not take effect?
+            let _ = cache.add_flag_value(input.source.clone(), v);
         }
     }
 
     Ok(cache)
 }
 
-pub fn generate_code(cache: &Cache) -> Result<GeneratedFile> {
+pub fn generate_code(cache: &Cache) -> Result<OutputFile> {
     generate_java_code(cache)
 }
 
@@ -132,31 +138,23 @@
         flag {
             name: "a"
             description: "Description of a"
-            value {
-                state: ENABLED
-                permission: READ_WRITE
-            }
         }
         flag {
             name: "b"
             description: "Description of b"
-            value {
-                state: ENABLED
-                permission: READ_ONLY
-            }
         }
         "#;
-        let aconfigs = vec![Input { source: Source::Memory, reader: Box::new(s.as_bytes()) }];
+        let declarations = vec![Input { source: Source::Memory, reader: Box::new(s.as_bytes()) }];
         let o = r#"
-        flag_override {
+        flag_value {
             namespace: "ns"
             name: "a"
             state: DISABLED
             permission: READ_ONLY
         }
         "#;
-        let overrides = vec![Input { source: Source::Memory, reader: Box::new(o.as_bytes()) }];
-        create_cache(1, "ns", aconfigs, overrides).unwrap()
+        let values = vec![Input { source: Source::Memory, reader: Box::new(o.as_bytes()) }];
+        create_cache("ns", declarations, values).unwrap()
     }
 
     #[test]
@@ -194,12 +192,12 @@
         assert_eq!(item.namespace(), "ns");
         assert_eq!(item.name(), "b");
         assert_eq!(item.description(), "Description of b");
-        assert_eq!(item.state(), ProtoFlagState::ENABLED);
-        assert_eq!(item.permission(), ProtoFlagPermission::READ_ONLY);
+        assert_eq!(item.state(), ProtoFlagState::DISABLED);
+        assert_eq!(item.permission(), ProtoFlagPermission::READ_WRITE);
         let mut tp = ProtoTracepoint::new();
         tp.set_source("<memory>".to_string());
-        tp.set_state(ProtoFlagState::ENABLED);
-        tp.set_permission(ProtoFlagPermission::READ_ONLY);
+        tp.set_state(ProtoFlagState::DISABLED);
+        tp.set_permission(ProtoFlagPermission::READ_WRITE);
         assert_eq!(item.trace, vec![tp]);
     }
 }
diff --git a/tools/aconfig/src/main.rs b/tools/aconfig/src/main.rs
index f29186a..513e313 100644
--- a/tools/aconfig/src/main.rs
+++ b/tools/aconfig/src/main.rs
@@ -16,11 +16,12 @@
 
 //! `aconfig` is a build time tool to manage build time configurations, such as feature flags.
 
-use anyhow::Result;
+use anyhow::{anyhow, ensure, Result};
 use clap::{builder::ArgAction, builder::EnumValueParser, Arg, ArgMatches, Command};
 use std::fs;
 use std::io;
 use std::io::Write;
+use std::path::{Path, PathBuf};
 
 mod aconfig;
 mod cache;
@@ -29,22 +30,16 @@
 mod protos;
 
 use crate::cache::Cache;
-use commands::{Input, Source};
+use commands::{Input, OutputFile, Source};
 
 fn cli() -> Command {
     Command::new("aconfig")
         .subcommand_required(true)
         .subcommand(
             Command::new("create-cache")
-                .arg(
-                    Arg::new("build-id")
-                        .long("build-id")
-                        .value_parser(clap::value_parser!(u32))
-                        .required(true),
-                )
                 .arg(Arg::new("namespace").long("namespace").required(true))
-                .arg(Arg::new("aconfig").long("aconfig").action(ArgAction::Append))
-                .arg(Arg::new("override").long("override").action(ArgAction::Append))
+                .arg(Arg::new("declarations").long("declarations").action(ArgAction::Append))
+                .arg(Arg::new("values").long("values").action(ArgAction::Append))
                 .arg(Arg::new("cache").long("cache").required(true)),
         )
         .subcommand(
@@ -74,15 +69,30 @@
     Ok(opened_files)
 }
 
+fn write_output_file_realtive_to_dir(root: &Path, output_file: &OutputFile) -> Result<()> {
+    ensure!(
+        root.is_dir(),
+        "output directory {} does not exist or is not a directory",
+        root.display()
+    );
+    let path = root.join(output_file.path.clone());
+    let parent = path
+        .parent()
+        .ok_or(anyhow!("unable to locate parent of output file {}", path.display()))?;
+    fs::create_dir_all(parent)?;
+    let mut file = fs::File::create(path)?;
+    file.write_all(&output_file.contents)?;
+    Ok(())
+}
+
 fn main() -> Result<()> {
     let matches = cli().get_matches();
     match matches.subcommand() {
         Some(("create-cache", sub_matches)) => {
-            let build_id = *sub_matches.get_one::<u32>("build-id").unwrap();
             let namespace = sub_matches.get_one::<String>("namespace").unwrap();
-            let aconfigs = open_zero_or_more_files(sub_matches, "aconfig")?;
-            let overrides = open_zero_or_more_files(sub_matches, "override")?;
-            let cache = commands::create_cache(build_id, namespace, aconfigs, overrides)?;
+            let declarations = open_zero_or_more_files(sub_matches, "declarations")?;
+            let values = open_zero_or_more_files(sub_matches, "values")?;
+            let cache = commands::create_cache(namespace, declarations, values)?;
             let path = sub_matches.get_one::<String>("cache").unwrap();
             let file = fs::File::create(path)?;
             cache.write_to_writer(file)?;
@@ -91,12 +101,9 @@
             let path = sub_matches.get_one::<String>("cache").unwrap();
             let file = fs::File::open(path)?;
             let cache = Cache::read_from_reader(file)?;
-            let out = sub_matches.get_one::<String>("out").unwrap();
+            let dir = PathBuf::from(sub_matches.get_one::<String>("out").unwrap());
             let generated_file = commands::generate_code(&cache).unwrap();
-            fs::write(
-                format!("{}/{}", out, generated_file.file_name),
-                generated_file.file_content,
-            )?;
+            write_output_file_realtive_to_dir(&dir, &generated_file)?;
         }
         Some(("dump", sub_matches)) => {
             let path = sub_matches.get_one::<String>("cache").unwrap();
diff --git a/tools/aconfig/src/protos.rs b/tools/aconfig/src/protos.rs
index 5965a09..cb75692 100644
--- a/tools/aconfig/src/protos.rs
+++ b/tools/aconfig/src/protos.rs
@@ -28,19 +28,16 @@
 
 // ---- When building with the Android tool-chain ----
 #[cfg(not(feature = "cargo"))]
-pub use aconfig_protos::aconfig::Namespace as ProtoNamespace;
+pub use aconfig_protos::aconfig::Flag_declaration as ProtoFlagDeclaration;
 
 #[cfg(not(feature = "cargo"))]
-pub use aconfig_protos::aconfig::Flag_value as ProtoFlagDefinitionValue;
+pub use aconfig_protos::aconfig::Flag_declarations as ProtoFlagDeclarations;
 
 #[cfg(not(feature = "cargo"))]
-pub use aconfig_protos::aconfig::Flag_definition as ProtoFlagDefinition;
+pub use aconfig_protos::aconfig::Flag_value as ProtoFlagValue;
 
 #[cfg(not(feature = "cargo"))]
-pub use aconfig_protos::aconfig::Flag_overrides as ProtoFlagOverrides;
-
-#[cfg(not(feature = "cargo"))]
-pub use aconfig_protos::aconfig::Flag_override as ProtoFlagOverride;
+pub use aconfig_protos::aconfig::Flag_values as ProtoFlagValues;
 
 #[cfg(not(feature = "cargo"))]
 pub use aconfig_protos::aconfig::Flag_permission as ProtoFlagPermission;
@@ -62,19 +59,16 @@
 include!(concat!(env!("OUT_DIR"), "/aconfig_proto/mod.rs"));
 
 #[cfg(feature = "cargo")]
-pub use aconfig::Namespace as ProtoNamespace;
+pub use aconfig::Flag_declaration as ProtoFlagDeclaration;
 
 #[cfg(feature = "cargo")]
-pub use aconfig::Flag_value as ProtoFlagDefinitionValue;
+pub use aconfig::Flag_declarations as ProtoFlagDeclarations;
 
 #[cfg(feature = "cargo")]
-pub use aconfig::Flag_definition as ProtoFlagDefinition;
+pub use aconfig::Flag_value as ProtoFlagValue;
 
 #[cfg(feature = "cargo")]
-pub use aconfig::Flag_overrides as ProtoFlagOverrides;
-
-#[cfg(feature = "cargo")]
-pub use aconfig::Flag_override as ProtoFlagOverride;
+pub use aconfig::Flag_values as ProtoFlagValues;
 
 #[cfg(feature = "cargo")]
 pub use aconfig::Flag_permission as ProtoFlagPermission;
diff --git a/tools/aconfig/templates/java.template b/tools/aconfig/templates/java.template
index 3854579..ebcd607 100644
--- a/tools/aconfig/templates/java.template
+++ b/tools/aconfig/templates/java.template
@@ -1,4 +1,4 @@
-package com.android.aconfig;
+package com.android.internal.aconfig;
 {{ if readwrite }}
 import android.provider.DeviceConfig;
 {{ endif }}
diff --git a/tools/rbcrun/Android.bp b/tools/rbcrun/Android.bp
index fcc33ef..4fab858 100644
--- a/tools/rbcrun/Android.bp
+++ b/tools/rbcrun/Android.bp
@@ -34,6 +34,7 @@
     pkgPath: "rbcrun",
     deps: [
         "go-starlark-starlark",
+        "go-starlark-starlarkjson",
         "go-starlark-starlarkstruct",
         "go-starlark-starlarktest",
     ],
diff --git a/tools/rbcrun/host.go b/tools/rbcrun/host.go
index f2fda4e..a0fb9e1 100644
--- a/tools/rbcrun/host.go
+++ b/tools/rbcrun/host.go
@@ -24,13 +24,19 @@
 	"strings"
 
 	"go.starlark.net/starlark"
+	"go.starlark.net/starlarkjson"
 	"go.starlark.net/starlarkstruct"
 )
 
-const callerDirKey = "callerDir"
+type ExecutionMode int
+const (
+	ExecutionModeRbc ExecutionMode = iota
+	ExecutionModeMake ExecutionMode = iota
+)
 
-var LoadPathRoot = "."
-var shellPath string
+const callerDirKey = "callerDir"
+const shellKey = "shell"
+const executionModeKey = "executionMode"
 
 type modentry struct {
 	globals starlark.StringDict
@@ -39,20 +45,66 @@
 
 var moduleCache = make(map[string]*modentry)
 
-var builtins starlark.StringDict
+var rbcBuiltins starlark.StringDict = starlark.StringDict{
+	"struct":   starlark.NewBuiltin("struct", starlarkstruct.Make),
+	// To convert find-copy-subdir and product-copy-files-by pattern
+	"rblf_find_files": starlark.NewBuiltin("rblf_find_files", find),
+	// To convert makefile's $(shell cmd)
+	"rblf_shell": starlark.NewBuiltin("rblf_shell", shell),
+	// Output to stderr
+	"rblf_log": starlark.NewBuiltin("rblf_log", log),
+	// To convert makefile's $(wildcard foo*)
+	"rblf_wildcard": starlark.NewBuiltin("rblf_wildcard", wildcard),
+}
 
-func moduleName2AbsPath(moduleName string, callerDir string) (string, error) {
-	path := moduleName
-	if ix := strings.LastIndex(path, ":"); ix >= 0 {
-		path = path[0:ix] + string(os.PathSeparator) + path[ix+1:]
+var makeBuiltins starlark.StringDict = starlark.StringDict{
+	"struct":   starlark.NewBuiltin("struct", starlarkstruct.Make),
+	"json": starlarkjson.Module,
+}
+
+// Takes a module name (the first argument to the load() function) and returns the path
+// it's trying to load, stripping out leading //, and handling leading :s.
+func cleanModuleName(moduleName string, callerDir string) (string, error) {
+	if strings.Count(moduleName, ":") > 1 {
+		return "", fmt.Errorf("at most 1 colon must be present in starlark path: %s", moduleName)
 	}
-	if strings.HasPrefix(path, "//") {
-		return filepath.Abs(filepath.Join(LoadPathRoot, path[2:]))
+
+	// We don't have full support for external repositories, but at least support skylib's dicts.
+	if moduleName == "@bazel_skylib//lib:dicts.bzl" {
+		return "external/bazel-skylib/lib/dicts.bzl", nil
+	}
+
+	localLoad := false
+	if strings.HasPrefix(moduleName, "@//") {
+		moduleName = moduleName[3:]
+	} else if strings.HasPrefix(moduleName, "//") {
+		moduleName = moduleName[2:]
 	} else if strings.HasPrefix(moduleName, ":") {
-		return filepath.Abs(filepath.Join(callerDir, path[1:]))
+		moduleName = moduleName[1:]
+		localLoad = true
 	} else {
-		return filepath.Abs(path)
+		return "", fmt.Errorf("load path must start with // or :")
 	}
+
+	if ix := strings.LastIndex(moduleName, ":"); ix >= 0 {
+		moduleName = moduleName[:ix] + string(os.PathSeparator) + moduleName[ix+1:]
+	}
+
+	if filepath.Clean(moduleName) != moduleName {
+		return "", fmt.Errorf("load path must be clean, found: %s, expected: %s", moduleName, filepath.Clean(moduleName))
+	}
+	if strings.HasPrefix(moduleName, "../") {
+		return "", fmt.Errorf("load path must not start with ../: %s", moduleName)
+	}
+	if strings.HasPrefix(moduleName, "/") {
+		return "", fmt.Errorf("load path starts with /, use // for a absolute path: %s", moduleName)
+	}
+
+	if localLoad {
+		return filepath.Join(callerDir, moduleName), nil
+	}
+
+	return moduleName, nil
 }
 
 // loader implements load statement. The format of the loaded module URI is
@@ -61,14 +113,18 @@
 // The presence of `|symbol` indicates that the loader should return a single 'symbol'
 // bound to None if file is missing.
 func loader(thread *starlark.Thread, module string) (starlark.StringDict, error) {
-	pipePos := strings.LastIndex(module, "|")
-	mustLoad := pipePos < 0
+	mode := thread.Local(executionModeKey).(ExecutionMode)
 	var defaultSymbol string
-	if !mustLoad {
-		defaultSymbol = module[pipePos+1:]
-		module = module[:pipePos]
+	mustLoad := true
+	if mode == ExecutionModeRbc {
+		pipePos := strings.LastIndex(module, "|")
+		mustLoad = pipePos < 0
+		if !mustLoad {
+			defaultSymbol = module[pipePos+1:]
+			module = module[:pipePos]
+		}
 	}
-	modulePath, err := moduleName2AbsPath(module, thread.Local(callerDirKey).(string))
+	modulePath, err := cleanModuleName(module, thread.Local(callerDirKey).(string))
 	if err != nil {
 		return nil, err
 	}
@@ -100,8 +156,17 @@
 			}
 
 			childThread.SetLocal(callerDirKey, filepath.Dir(modulePath))
-			globals, err := starlark.ExecFile(childThread, modulePath, nil, builtins)
-			e = &modentry{globals, err}
+			childThread.SetLocal(shellKey, thread.Local(shellKey))
+			childThread.SetLocal(executionModeKey, mode)
+			if mode == ExecutionModeRbc {
+				globals, err := starlark.ExecFile(childThread, modulePath, nil, rbcBuiltins)
+				e = &modentry{globals, err}
+			} else if mode == ExecutionModeMake {
+				globals, err := starlark.ExecFile(childThread, modulePath, nil, makeBuiltins)
+				e = &modentry{globals, err}
+			} else {
+				return nil, fmt.Errorf("unknown executionMode %d", mode)
+			}
 		} else {
 			e = &modentry{starlark.StringDict{defaultSymbol: starlark.None}, nil}
 		}
@@ -189,12 +254,13 @@
 // its output the same way as Make's $(shell ) function. The end-of-lines
 // ("\n" or "\r\n") are replaced with " " in the result, and the trailing
 // end-of-line is removed.
-func shell(_ *starlark.Thread, b *starlark.Builtin, args starlark.Tuple,
+func shell(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple,
 	kwargs []starlark.Tuple) (starlark.Value, error) {
 	var command string
 	if err := starlark.UnpackPositionalArgs(b.Name(), args, kwargs, 1, &command); err != nil {
 		return starlark.None, err
 	}
+	shellPath := thread.Local(shellKey).(string)
 	if shellPath == "" {
 		return starlark.None,
 			fmt.Errorf("cannot run shell, /bin/sh is missing (running on Windows?)")
@@ -245,45 +311,68 @@
 	return starlark.None, nil
 }
 
-func setup() {
-	// Create the symbols that aid makefile conversion. See README.md
-	builtins = starlark.StringDict{
-		"struct":   starlark.NewBuiltin("struct", starlarkstruct.Make),
-		// To convert find-copy-subdir and product-copy-files-by pattern
-		"rblf_find_files": starlark.NewBuiltin("rblf_find_files", find),
-		// To convert makefile's $(shell cmd)
-		"rblf_shell": starlark.NewBuiltin("rblf_shell", shell),
-		// Output to stderr
-		"rblf_log": starlark.NewBuiltin("rblf_log", log),
-		// To convert makefile's $(wildcard foo*)
-		"rblf_wildcard": starlark.NewBuiltin("rblf_wildcard", wildcard),
-	}
-
-	// NOTE(asmundak): OS-specific. Behave similar to Linux `system` call,
-	// which always uses /bin/sh to run the command
-	shellPath = "/bin/sh"
-	if _, err := os.Stat(shellPath); err != nil {
-		shellPath = ""
-	}
-}
-
 // Parses, resolves, and executes a Starlark file.
 // filename and src parameters are as for starlark.ExecFile:
 // * filename is the name of the file to execute,
 //   and the name that appears in error messages;
 // * src is an optional source of bytes to use instead of filename
 //   (it can be a string, or a byte array, or an io.Reader instance)
-func Run(filename string, src interface{}) error {
-	setup()
+// Returns the top-level starlark variables, the list of starlark files loaded, and an error
+func Run(filename string, src interface{}, mode ExecutionMode) (starlark.StringDict, []string, error) {
+	// NOTE(asmundak): OS-specific. Behave similar to Linux `system` call,
+	// which always uses /bin/sh to run the command
+	shellPath := "/bin/sh"
+	if _, err := os.Stat(shellPath); err != nil {
+		shellPath = ""
+	}
+
 	mainThread := &starlark.Thread{
 		Name:  "main",
-		Print: func(_ *starlark.Thread, msg string) { fmt.Println(msg) },
+		Print: func(_ *starlark.Thread, msg string) {
+			if mode == ExecutionModeRbc {
+				// In rbc mode, rblf_log is used to print to stderr
+				fmt.Println(msg)
+			} else if mode == ExecutionModeMake {
+				fmt.Fprintln(os.Stderr, msg)
+			}
+		},
 		Load:  loader,
 	}
-	absPath, err := filepath.Abs(filename)
-	if err == nil {
-		mainThread.SetLocal(callerDirKey, filepath.Dir(absPath))
-		_, err = starlark.ExecFile(mainThread, absPath, src, builtins)
+	filename, err := filepath.Abs(filename)
+	if err != nil {
+		return nil, nil, err
 	}
-	return err
+	if wd, err := os.Getwd(); err == nil {
+		filename, err = filepath.Rel(wd, filename)
+		if err != nil {
+			return nil, nil, err
+		}
+		if strings.HasPrefix(filename, "../") {
+			return nil, nil, fmt.Errorf("path could not be made relative to workspace root: %s", filename)
+		}
+	} else {
+		return nil, nil, err
+	}
+
+	// Add top-level file to cache for cycle detection purposes
+	moduleCache[filename] = nil
+
+	var results starlark.StringDict
+	mainThread.SetLocal(callerDirKey, filepath.Dir(filename))
+	mainThread.SetLocal(shellKey, shellPath)
+	mainThread.SetLocal(executionModeKey, mode)
+	if mode == ExecutionModeRbc {
+		results, err = starlark.ExecFile(mainThread, filename, src, rbcBuiltins)
+	} else if mode == ExecutionModeMake {
+		results, err = starlark.ExecFile(mainThread, filename, src, makeBuiltins)
+	} else {
+		return results, nil, fmt.Errorf("unknown executionMode %d", mode)
+	}
+	loadedStarlarkFiles := make([]string, 0, len(moduleCache))
+	for file := range moduleCache {
+		loadedStarlarkFiles = append(loadedStarlarkFiles, file)
+	}
+	sort.Strings(loadedStarlarkFiles)
+
+	return results, loadedStarlarkFiles, err
 }
diff --git a/tools/rbcrun/host_test.go b/tools/rbcrun/host_test.go
index e109c02..10cac62 100644
--- a/tools/rbcrun/host_test.go
+++ b/tools/rbcrun/host_test.go
@@ -54,7 +54,6 @@
 
 // Common setup for the tests: create thread, change to the test directory
 func testSetup(t *testing.T) *starlark.Thread {
-	setup()
 	thread := &starlark.Thread{
 		Load: func(thread *starlark.Thread, module string) (starlark.StringDict, error) {
 			if module == "assert.star" {
@@ -78,7 +77,6 @@
 	// In order to use "assert.star" from go/starlark.net/starlarktest in the tests, provide:
 	//  * load function that handles "assert.star"
 	//  * starlarktest.DataFile function that finds its location
-	setup()
 	if err := os.Chdir(dataDir()); err != nil {
 		t.Fatal(err)
 	}
@@ -92,7 +90,9 @@
 	starlarktest.SetReporter(thread, t)
 	_, thisSrcFile, _, _ := runtime.Caller(0)
 	filename := filepath.Join(filepath.Dir(thisSrcFile), starFile)
-	if _, err := starlark.ExecFile(thread, filename, nil, builtins); err != nil {
+	thread.SetLocal(executionModeKey, ExecutionModeRbc)
+	thread.SetLocal(shellKey, "/bin/sh")
+	if _, err := starlark.ExecFile(thread, filename, nil, rbcBuiltins); err != nil {
 		if err, ok := err.(*starlark.EvalError); ok {
 			t.Fatal(err.Backtrace())
 		}
@@ -103,7 +103,7 @@
 func TestFileOps(t *testing.T) {
 	// TODO(asmundak): convert this to use exerciseStarlarkTestFile
 	thread := testSetup(t)
-	if _, err := starlark.ExecFile(thread, "file_ops.star", nil, builtins); err != nil {
+	if _, err := starlark.ExecFile(thread, "file_ops.star", nil, rbcBuiltins); err != nil {
 		if err, ok := err.(*starlark.EvalError); ok {
 			t.Fatal(err.Backtrace())
 		}
@@ -122,9 +122,12 @@
 		}
 	}
 	dir := dataDir()
+	if err := os.Chdir(filepath.Dir(dir)); err != nil {
+		t.Fatal(err)
+	}
 	thread.SetLocal(callerDirKey, dir)
-	LoadPathRoot = filepath.Dir(dir)
-	if _, err := starlark.ExecFile(thread, "load.star", nil, builtins); err != nil {
+	thread.SetLocal(executionModeKey, ExecutionModeRbc)
+	if _, err := starlark.ExecFile(thread, "testdata/load.star", nil, rbcBuiltins); err != nil {
 		if err, ok := err.(*starlark.EvalError); ok {
 			t.Fatal(err.Backtrace())
 		}
diff --git a/tools/rbcrun/rbcrun/rbcrun.go b/tools/rbcrun/rbcrun/rbcrun.go
index 8dd0f46..b5182f0 100644
--- a/tools/rbcrun/rbcrun/rbcrun.go
+++ b/tools/rbcrun/rbcrun/rbcrun.go
@@ -17,18 +17,22 @@
 import (
 	"flag"
 	"fmt"
-	"go.starlark.net/starlark"
 	"os"
 	"rbcrun"
+	"regexp"
+	"strings"
+
+	"go.starlark.net/starlark"
 )
 
 var (
+	modeFlag  = flag.String("mode", "", "the general behavior of rbcrun. Can be \"rbc\" or \"make\". Required.")
 	rootdir  = flag.String("d", ".", "the value of // for load paths")
 	perfFile = flag.String("perf", "", "save performance data")
+	identifierRe = regexp.MustCompile("[a-zA-Z_][a-zA-Z0-9_]*")
 )
 
-func main() {
-	flag.Parse()
+func getEntrypointStarlarkFile() string {
 	filename := ""
 
 	for _, arg := range flag.Args() {
@@ -42,8 +46,108 @@
 		flag.Usage()
 		os.Exit(1)
 	}
-	if stat, err := os.Stat(*rootdir); os.IsNotExist(err) || !stat.IsDir() {
-		quit("%s is not a directory\n", *rootdir)
+	return filename
+}
+
+func getMode() rbcrun.ExecutionMode {
+	switch *modeFlag {
+	case "rbc":
+		return rbcrun.ExecutionModeRbc
+	case "make":
+		return rbcrun.ExecutionModeMake
+	case "":
+		quit("-mode flag is required.")
+	default:
+		quit("Unknown -mode value %q, expected 1 of \"rbc\", \"make\"", *modeFlag)
+	}
+	return rbcrun.ExecutionModeMake
+}
+
+var makeStringReplacer = strings.NewReplacer("#", "\\#", "$", "$$")
+
+func cleanStringForMake(s string) (string, error) {
+	if strings.ContainsAny(s, "\\\n") {
+		// \\ in make is literally \\, not a single \, so we can't allow them.
+		// \<newline> in make will produce a space, not a newline.
+		return "", fmt.Errorf("starlark strings exported to make cannot contain backslashes or newlines")
+	}
+	return makeStringReplacer.Replace(s), nil
+}
+
+func getValueInMakeFormat(value starlark.Value, allowLists bool) (string, error) {
+	switch v := value.(type) {
+	case starlark.String:
+		if cleanedValue, err := cleanStringForMake(v.GoString()); err == nil {
+			return cleanedValue, nil
+		} else {
+			return "", err
+		}
+	case starlark.Int:
+		return v.String(), nil
+	case *starlark.List:
+		if !allowLists {
+			return "", fmt.Errorf("nested lists are not allowed to be exported from starlark to make, flatten the list in starlark first")
+		}
+		result := ""
+		for i := 0; i < v.Len(); i++ {
+			value, err := getValueInMakeFormat(v.Index(i), false)
+			if err != nil {
+				return "", err
+			}
+			if i > 0 {
+				result += " "
+			}
+			result += value
+		}
+		return result, nil
+	default:
+		return "", fmt.Errorf("only starlark strings, ints, and lists of strings/ints can be exported to make. Please convert all other types in starlark first. Found type: %s", value.Type())
+	}
+}
+
+func printVarsInMakeFormat(globals starlark.StringDict) error {
+	// We could just directly export top level variables by name instead of going through
+	// a variables_to_export_to_make dictionary, but that wouldn't allow for exporting a
+	// runtime-defined number of variables to make. This can be important because dictionaries
+	// in make are often represented by a unique variable for every key in the dictionary.
+	variablesValue, ok := globals["variables_to_export_to_make"]
+	if !ok {
+		return fmt.Errorf("expected top-level starlark file to have a \"variables_to_export_to_make\" variable")
+	}
+	variables, ok := variablesValue.(*starlark.Dict)
+	if !ok {
+		return fmt.Errorf("expected variables_to_export_to_make to be a dict, got %s", variablesValue.Type())
+	}
+
+	for _, varTuple := range variables.Items() {
+		varNameStarlark, ok := varTuple.Index(0).(starlark.String)
+		if !ok {
+			return fmt.Errorf("all keys in variables_to_export_to_make must be strings, but got %q", varTuple.Index(0).Type())
+		}
+		varName := varNameStarlark.GoString()
+		if !identifierRe.MatchString(varName) {
+			return fmt.Errorf("all variables at the top level starlark file must be valid c identifiers, but got %q", varName)
+		}
+		if varName == "LOADED_STARLARK_FILES" {
+			return fmt.Errorf("the name LOADED_STARLARK_FILES is reserved for use by the starlark interpreter")
+		}
+		valueMake, err := getValueInMakeFormat(varTuple.Index(1), true)
+		if err != nil {
+			return err
+		}
+		// The :=$= is special Kati syntax that means "set and make readonly"
+		fmt.Printf("%s :=$= %s\n", varName, valueMake)
+	}
+	return nil
+}
+
+func main() {
+	flag.Parse()
+	filename := getEntrypointStarlarkFile()
+	mode := getMode()
+
+	if os.Chdir(*rootdir) != nil {
+		quit("could not chdir to %s\n", *rootdir)
 	}
 	if *perfFile != "" {
 		pprof, err := os.Create(*perfFile)
@@ -55,8 +159,7 @@
 			quit("%s\n", err)
 		}
 	}
-	rbcrun.LoadPathRoot = *rootdir
-	err := rbcrun.Run(filename, nil)
+	variables, loadedStarlarkFiles, err := rbcrun.Run(filename, nil, mode)
 	rc := 0
 	if *perfFile != "" {
 		if err2 := starlark.StopProfile(); err2 != nil {
@@ -71,6 +174,12 @@
 			quit("%s\n", err)
 		}
 	}
+	if mode == rbcrun.ExecutionModeMake {
+		if err := printVarsInMakeFormat(variables); err != nil {
+			quit("%s\n", err)
+		}
+		fmt.Printf("LOADED_STARLARK_FILES := %s\n", strings.Join(loadedStarlarkFiles, " "))
+	}
 	os.Exit(rc)
 }
 
diff --git a/tools/rbcrun/testdata/module1.star b/tools/rbcrun/testdata/module1.star
index be04f75..02919a0 100644
--- a/tools/rbcrun/testdata/module1.star
+++ b/tools/rbcrun/testdata/module1.star
@@ -2,6 +2,6 @@
 load("assert.star", "assert")
 
 # Make sure that builtins are defined for the loaded module, too
-assert.true(rblf_wildcard("module1.star"))
-assert.true(not rblf_wildcard("no_such file"))
+assert.true(rblf_wildcard("testdata/module1.star"))
+assert.true(not rblf_wildcard("testdata/no_such file"))
 test = "module1"
diff --git a/tools/releasetools/add_img_to_target_files.py b/tools/releasetools/add_img_to_target_files.py
index ac3271b..8d660f8 100644
--- a/tools/releasetools/add_img_to_target_files.py
+++ b/tools/releasetools/add_img_to_target_files.py
@@ -65,7 +65,7 @@
 import ota_metadata_pb2
 import rangelib
 import sparse_img
-
+from concurrent.futures import ThreadPoolExecutor
 from apex_utils import GetApexInfoFromTargetFiles
 from common import ZipDelete, PARTITIONS_WITH_CARE_MAP, ExternalError, RunAndCheckOutput, IsSparseImage, MakeTempFile, ZipWrite
 
@@ -1083,8 +1083,15 @@
       ("system_dlkm", has_system_dlkm, AddSystemDlkm, []),
       ("system_other", has_system_other, AddSystemOther, []),
   )
-  for call in add_partition_calls:
-    add_partition(*call)
+  # If output_zip exists, each add_partition_calls writes bytes to the same output_zip,
+  # which is not thread-safe. So, run them in serial if output_zip exists.
+  if output_zip:
+    for call in add_partition_calls:
+      add_partition(*call)
+  else:
+    with ThreadPoolExecutor(max_workers=len(add_partition_calls)) as executor:
+      for future in [executor.submit(add_partition, *call) for call in add_partition_calls]:
+        future.result()
 
   AddApexInfo(output_zip)
 
diff --git a/tools/releasetools/img_from_target_files.py b/tools/releasetools/img_from_target_files.py
index f8bdd81..7c27ef7 100755
--- a/tools/releasetools/img_from_target_files.py
+++ b/tools/releasetools/img_from_target_files.py
@@ -118,6 +118,7 @@
 
   entries = [
       'OTA/android-info.txt:android-info.txt',
+      'META/fastboot-info.txt:fastboot-info.txt',
   ]
   with zipfile.ZipFile(input_file) as input_zip:
     namelist = input_zip.namelist()