Merge "Emit apex_info to target_files META/apex_info.pb"
diff --git a/core/Makefile b/core/Makefile
index 7504687..79255ca 100644
--- a/core/Makefile
+++ b/core/Makefile
@@ -2328,12 +2328,12 @@
# -----------------------------------------------------------------
# vendor debug ramdisk
# Combines vendor ramdisk files and debug ramdisk files to build the vendor debug ramdisk.
-INSTALLED_VENDOR_DEBUG_RAMDISK_TARGET := $(PRODUCT_OUT)/vendor-ramdisk-debug.cpio$(RAMDISK_EXT)
-$(INSTALLED_VENDOR_DEBUG_RAMDISK_TARGET): DEBUG_RAMDISK_FILES := $(INTERNAL_DEBUG_RAMDISK_FILES)
-$(INSTALLED_VENDOR_DEBUG_RAMDISK_TARGET): VENDOR_RAMDISK_DIR := $(TARGET_VENDOR_RAMDISK_OUT)
+INTERNAL_VENDOR_DEBUG_RAMDISK_TARGET := $(call intermediates-dir-for,PACKAGING,vendor_boot-debug)/vendor-ramdisk-debug.cpio$(RAMDISK_EXT)
+$(INTERNAL_VENDOR_DEBUG_RAMDISK_TARGET): DEBUG_RAMDISK_FILES := $(INTERNAL_DEBUG_RAMDISK_FILES)
+$(INTERNAL_VENDOR_DEBUG_RAMDISK_TARGET): VENDOR_RAMDISK_DIR := $(TARGET_VENDOR_RAMDISK_OUT)
ifeq (true,$(BOARD_MOVE_RECOVERY_RESOURCES_TO_VENDOR_BOOT))
-$(INSTALLED_VENDOR_DEBUG_RAMDISK_TARGET): PRIVATE_ADDITIONAL_DIR := $(TARGET_RECOVERY_ROOT_OUT)
+$(INTERNAL_VENDOR_DEBUG_RAMDISK_TARGET): PRIVATE_ADDITIONAL_DIR := $(TARGET_RECOVERY_ROOT_OUT)
endif
INTERNAL_VENDOR_DEBUG_RAMDISK_FILES := $(filter $(TARGET_VENDOR_DEBUG_RAMDISK_OUT)/%, \
@@ -2344,14 +2344,15 @@
# if BOARD_USES_RECOVERY_AS_BOOT is true. Otherwise, it will be $(PRODUCT_OUT)/vendor_debug_ramdisk.
# But the path of $(VENDOR_DEBUG_RAMDISK_DIR) to build the vendor debug ramdisk, is always
# $(PRODUCT_OUT)/vendor_debug_ramdisk.
-$(INSTALLED_VENDOR_DEBUG_RAMDISK_TARGET): VENDOR_DEBUG_RAMDISK_DIR := $(PRODUCT_OUT)/vendor_debug_ramdisk
-$(INSTALLED_VENDOR_DEBUG_RAMDISK_TARGET): $(INTERNAL_VENDOR_RAMDISK_TARGET) $(INSTALLED_DEBUG_RAMDISK_TARGET)
-$(INSTALLED_VENDOR_DEBUG_RAMDISK_TARGET): $(MKBOOTFS) $(INTERNAL_VENDOR_DEBUG_RAMDISK_FILES) | $(COMPRESSION_COMMAND_DEPS)
+$(INTERNAL_VENDOR_DEBUG_RAMDISK_TARGET): DEBUG_RAMDISK_DIR := $(PRODUCT_OUT)/debug_ramdisk
+$(INTERNAL_VENDOR_DEBUG_RAMDISK_TARGET): VENDOR_DEBUG_RAMDISK_DIR := $(PRODUCT_OUT)/vendor_debug_ramdisk
+$(INTERNAL_VENDOR_DEBUG_RAMDISK_TARGET): $(INTERNAL_VENDOR_RAMDISK_TARGET) $(INSTALLED_DEBUG_RAMDISK_TARGET)
+$(INTERNAL_VENDOR_DEBUG_RAMDISK_TARGET): $(MKBOOTFS) $(INTERNAL_VENDOR_DEBUG_RAMDISK_FILES) | $(COMPRESSION_COMMAND_DEPS)
$(call pretty,"Target vendor debug ram disk: $@")
mkdir -p $(TARGET_VENDOR_DEBUG_RAMDISK_OUT)
touch $(TARGET_VENDOR_DEBUG_RAMDISK_OUT)/force_debuggable
$(foreach debug_file,$(DEBUG_RAMDISK_FILES), \
- cp -f $(debug_file) $(subst $(PRODUCT_OUT)/debug_ramdisk,$(PRODUCT_OUT)/vendor_debug_ramdisk,$(debug_file)) &&) true
+ cp -f $(debug_file) $(patsubst $(DEBUG_RAMDISK_DIR)/%,$(VENDOR_DEBUG_RAMDISK_DIR)/%,$(debug_file)) &&) true
$(MKBOOTFS) -d $(TARGET_OUT) $(VENDOR_RAMDISK_DIR) $(VENDOR_DEBUG_RAMDISK_DIR) $(PRIVATE_ADDITIONAL_DIR) | $(COMPRESSION_COMMAND) > $@
INSTALLED_FILES_FILE_VENDOR_DEBUG_RAMDISK := $(PRODUCT_OUT)/installed-files-vendor-ramdisk-debug.txt
@@ -2361,7 +2362,7 @@
# The vendor debug ramdisk will rsync from $(TARGET_VENDOR_RAMDISK_OUT) and $(INTERNAL_DEBUG_RAMDISK_FILES),
# so we have to wait for the vendor debug ramdisk to be built before generating the installed file list.
-$(INSTALLED_FILES_FILE_VENDOR_DEBUG_RAMDISK): $(INSTALLED_VENDOR_DEBUG_RAMDISK_TARGET)
+$(INSTALLED_FILES_FILE_VENDOR_DEBUG_RAMDISK): $(INTERNAL_VENDOR_DEBUG_RAMDISK_TARGET)
$(INSTALLED_FILES_FILE_VENDOR_DEBUG_RAMDISK): $(INTERNAL_VENDOR_DEBUG_RAMDISK_FILES) $(FILESLIST) $(FILESLIST_UTIL)
echo Installed file list: $@
mkdir -p $(dir $@)
@@ -2392,10 +2393,10 @@
endif
# Depends on vendor_boot.img and vendor-ramdisk-debug.cpio.gz to build the new vendor_boot-debug.img
-$(INSTALLED_VENDOR_DEBUG_BOOTIMAGE_TARGET): $(MKBOOTIMG) $(INSTALLED_VENDOR_BOOTIMAGE_TARGET) $(INSTALLED_VENDOR_DEBUG_RAMDISK_TARGET)
+$(INSTALLED_VENDOR_DEBUG_BOOTIMAGE_TARGET): $(MKBOOTIMG) $(INSTALLED_VENDOR_BOOTIMAGE_TARGET) $(INTERNAL_VENDOR_DEBUG_RAMDISK_TARGET)
$(INSTALLED_VENDOR_DEBUG_BOOTIMAGE_TARGET): $(INTERNAL_VENDOR_RAMDISK_FRAGMENT_TARGETS)
$(call pretty,"Target vendor_boot debug image: $@")
- $(MKBOOTIMG) $(INTERNAL_VENDOR_BOOTIMAGE_ARGS) $(BOARD_MKBOOTIMG_ARGS) --vendor_ramdisk $(INSTALLED_VENDOR_DEBUG_RAMDISK_TARGET) $(INTERNAL_VENDOR_RAMDISK_FRAGMENT_ARGS) --vendor_boot $@
+ $(MKBOOTIMG) $(INTERNAL_VENDOR_BOOTIMAGE_ARGS) $(BOARD_MKBOOTIMG_ARGS) --vendor_ramdisk $(INTERNAL_VENDOR_DEBUG_RAMDISK_TARGET) $(INTERNAL_VENDOR_RAMDISK_FRAGMENT_ARGS) --vendor_boot $@
$(call assert-max-image-size,$@,$(BOARD_VENDOR_BOOTIMAGE_PARTITION_SIZE))
$(if $(BOARD_AVB_VENDOR_BOOT_KEY_PATH),$(call test-key-sign-vendor-bootimage,$@))
diff --git a/core/android_soong_config_vars.mk b/core/android_soong_config_vars.mk
index 883f92d..3a0c0f1 100644
--- a/core/android_soong_config_vars.mk
+++ b/core/android_soong_config_vars.mk
@@ -34,3 +34,8 @@
SOONG_CONFIG_art_module += source_build
endif
SOONG_CONFIG_art_module_source_build ?= true
+
+# Apex build mode variables
+ifdef APEX_BUILD_FOR_PRE_S_DEVICES
+$(call add_soong_config_var_value,ANDROID,library_linking_strategy,prefer_static)
+endif
diff --git a/core/clear_vars.mk b/core/clear_vars.mk
index 5f16363..5effac7 100644
--- a/core/clear_vars.mk
+++ b/core/clear_vars.mk
@@ -243,6 +243,7 @@
# lite(default),micro,nano,stream,full,nanopb-c,nanopb-c-enable_malloc,nanopb-c-16bit,nanopb-c-enable_malloc-16bit,nanopb-c-32bit,nanopb-c-enable_malloc-32bit
LOCAL_PROTOC_OPTIMIZE_TYPE:=
LOCAL_PROTO_JAVA_OUTPUT_PARAMS:=
+LOCAL_PROVIDES_USES_LIBRARY:=
LOCAL_R8_FLAG_FILES:=
LOCAL_RECORDED_MODULE_TYPE:=
LOCAL_RENDERSCRIPT_CC:=
diff --git a/core/config_sanitizers.mk b/core/config_sanitizers.mk
index c92cea2..f39b84a 100644
--- a/core/config_sanitizers.mk
+++ b/core/config_sanitizers.mk
@@ -53,6 +53,18 @@
endif
endif
+# Disable global memtag_heap in excluded paths
+ifneq ($(filter memtag_heap, $(my_global_sanitize)),)
+ combined_exclude_paths := $(MEMTAG_HEAP_EXCLUDE_PATHS) \
+ $(PRODUCT_MEMTAG_HEAP_EXCLUDE_PATHS)
+
+ ifneq ($(strip $(foreach dir,$(subst $(comma),$(space),$(combined_exclude_paths)),\
+ $(filter $(dir)%,$(LOCAL_PATH)))),)
+ my_global_sanitize := $(filter-out memtag_heap,$(my_global_sanitize))
+ my_global_sanitize_diag := $(filter-out memtag_heap,$(my_global_sanitize_diag))
+ endif
+endif
+
ifneq ($(my_global_sanitize),)
my_sanitize := $(my_global_sanitize) $(my_sanitize)
endif
@@ -116,6 +128,25 @@
endif
endif
+# Enable memtag_heap in included paths (for Arm64 only).
+ifeq ($(filter memtag_heap, $(my_sanitize)),)
+ ifneq ($(filter arm64,$(TARGET_$(LOCAL_2ND_ARCH_VAR_PREFIX)ARCH)),)
+ combined_sync_include_paths := $(MEMTAG_HEAP_SYNC_INCLUDE_PATHS) \
+ $(PRODUCT_MEMTAG_HEAP_SYNC_INCLUDE_PATHS)
+ combined_async_include_paths := $(MEMTAG_HEAP_ASYNC_INCLUDE_PATHS) \
+ $(PRODUCT_MEMTAG_HEAP_ASYNC_INCLUDE_PATHS)
+
+ ifneq ($(strip $(foreach dir,$(subst $(comma),$(space),$(combined_sync_include_paths)),\
+ $(filter $(dir)%,$(LOCAL_PATH)))),)
+ my_sanitize := memtag_heap $(my_sanitize)
+ my_sanitize_diag := memtag_heap $(my_sanitize)
+ else ifneq ($(strip $(foreach dir,$(subst $(comma),$(space),$(combined_async_include_paths)),\
+ $(filter $(dir)%,$(LOCAL_PATH)))),)
+ my_sanitize := memtag_heap $(my_sanitize)
+ endif
+ endif
+endif
+
# If CFI is disabled globally, remove it from my_sanitize.
ifeq ($(strip $(ENABLE_CFI)),false)
my_sanitize := $(filter-out cfi,$(my_sanitize))
@@ -164,6 +195,7 @@
ifneq ($(filter arm x86 x86_64,$(TARGET_$(LOCAL_2ND_ARCH_VAR_PREFIX)ARCH)),)
my_sanitize := $(filter-out hwaddress,$(my_sanitize))
+ my_sanitize := $(filter-out memtag_heap,$(my_sanitize))
endif
ifneq ($(filter hwaddress,$(my_sanitize)),)
@@ -183,6 +215,20 @@
endif
endif
+ifneq ($(filter memtag_heap,$(my_sanitize)),)
+ # Add memtag ELF note.
+ ifneq ($(filter memtag_heap,$(my_sanitize_diag)),)
+ my_whole_static_libraries += note_memtag_heap_sync
+ else
+ my_whole_static_libraries += note_memtag_heap_async
+ endif
+ # This is all that memtag_heap does - it is not an actual -fsanitize argument.
+ # Remove it from the list.
+ my_sanitize := $(filter-out memtag_heap,$(my_sanitize))
+endif
+
+my_sanitize_diag := $(filter-out memtag_heap,$(my_sanitize_diag))
+
# TSAN is not supported on 32-bit architectures. For non-multilib cases, make
# its use an error. For multilib cases, don't use it for the 32-bit case.
ifneq ($(filter thread,$(my_sanitize)),)
diff --git a/core/dex_preopt_odex_install.mk b/core/dex_preopt_odex_install.mk
index c31d4e8..b74e047 100644
--- a/core/dex_preopt_odex_install.mk
+++ b/core/dex_preopt_odex_install.mk
@@ -220,8 +220,9 @@
$(foreach lib, $(2),\
$(call add_json_map, $(lib)) \
$(eval file := $(filter %/$(lib).jar, $(call module-installed-files,$(lib)))) \
- $(call add_json_str, Host, $(call intermediates-dir-for,JAVA_LIBRARIES,$(lib),,COMMON)/javalib.jar) \
- $(call add_json_str, Device, $(call install-path-to-on-device-path,$(file))) \
+ $(call add_json_str, Host, $(call intermediates-dir-for,JAVA_LIBRARIES,$(lib),,COMMON)/javalib.jar) \
+ $(call add_json_str, Device, $(call install-path-to-on-device-path,$(file))) \
+ $(call add_json_map, Subcontexts, ${$}) $(call end_json_map) \
$(call end_json_map)) \
$(call end_json_map)
@@ -252,6 +253,7 @@
$(call add_json_str, ProfileClassListing, $(if $(my_process_profile),$(LOCAL_DEX_PREOPT_PROFILE)))
$(call add_json_bool, ProfileIsTextListing, $(my_profile_is_text_listing))
$(call add_json_bool, EnforceUsesLibraries, $(LOCAL_ENFORCE_USES_LIBRARIES))
+ $(call add_json_str, ProvidesUsesLibrary, $(firstword $(LOCAL_PROVIDES_USES_LIBRARY) $(LOCAL_MODULE)))
$(call add_json_map, ClassLoaderContexts)
$(call add_json_class_loader_context, any, $(my_dexpreopt_libs))
$(call add_json_class_loader_context, 28, $(my_dexpreopt_libs_compat_28))
diff --git a/core/main.mk b/core/main.mk
index 508ae19..5ea95c8 100644
--- a/core/main.mk
+++ b/core/main.mk
@@ -1463,9 +1463,6 @@
.PHONY: ramdisk_test_harness
ramdisk_test_harness: $(INSTALLED_TEST_HARNESS_RAMDISK_TARGET)
-.PHONY: vendor_ramdisk_debug
-vendor_ramdisk_debug: $(INSTALLED_VENDOR_DEBUG_RAMDISK_TARGET)
-
.PHONY: userdataimage
userdataimage: $(INSTALLED_USERDATAIMAGE_TARGET)
@@ -1545,7 +1542,6 @@
$(INSTALLED_BPTIMAGE_TARGET) \
$(INSTALLED_VENDORIMAGE_TARGET) \
$(INSTALLED_VENDOR_BOOTIMAGE_TARGET) \
- $(INSTALLED_VENDOR_DEBUG_RAMDISK_TARGET) \
$(INSTALLED_VENDOR_DEBUG_BOOTIMAGE_TARGET) \
$(INSTALLED_ODMIMAGE_TARGET) \
$(INSTALLED_VENDOR_DLKMIMAGE_TARGET) \
@@ -1730,7 +1726,6 @@
$(INSTALLED_FILES_JSON_VENDOR_DEBUG_RAMDISK) \
$(INSTALLED_DEBUG_RAMDISK_TARGET) \
$(INSTALLED_DEBUG_BOOTIMAGE_TARGET) \
- $(INSTALLED_VENDOR_DEBUG_RAMDISK_TARGET) \
$(INSTALLED_VENDOR_DEBUG_BOOTIMAGE_TARGET) \
)
$(call dist-for-goals, bootimage_test_harness, \
diff --git a/target/board/BoardConfigGsiCommon.mk b/target/board/BoardConfigGsiCommon.mk
index e34dc23..a2150ad 100644
--- a/target/board/BoardConfigGsiCommon.mk
+++ b/target/board/BoardConfigGsiCommon.mk
@@ -49,6 +49,10 @@
BOARD_GSI_DYNAMIC_PARTITIONS_SIZE := 3221225472
endif
+# TODO(b/123695868, b/146149698):
+# This flag is set by mainline but isn't desired for GSI
+BOARD_BLUETOOTH_BDROID_BUILDCFG_INCLUDE_DIR :=
+
# Enable chain partition for boot, mainly for GKI images.
BOARD_AVB_BOOT_KEY_PATH := external/avb/test/data/testkey_rsa2048.pem
BOARD_AVB_BOOT_ALGORITHM := SHA256_RSA2048
diff --git a/target/product/gsi/current.txt b/target/product/gsi/current.txt
index 9f2b940..2ca6687 100644
--- a/target/product/gsi/current.txt
+++ b/target/product/gsi/current.txt
@@ -19,8 +19,12 @@
LLNDK: libvndksupport.so
LLNDK: libvulkan.so
VNDK-SP: android.hardware.common-V2-ndk_platform.so
+VNDK-SP: android.hardware.common-unstable-ndk_platform.so
VNDK-SP: android.hardware.common.fmq-V1-ndk_platform.so
+VNDK-SP: android.hardware.common.fmq-ndk_platform.so
+VNDK-SP: android.hardware.common.fmq-unstable-ndk_platform.so
VNDK-SP: android.hardware.graphics.common-V2-ndk_platform.so
+VNDK-SP: android.hardware.graphics.common-unstable-ndk_platform.so
VNDK-SP: android.hardware.graphics.common@1.0.so
VNDK-SP: android.hardware.graphics.common@1.1.so
VNDK-SP: android.hardware.graphics.common@1.2.so
@@ -58,7 +62,10 @@
VNDK-SP: libz.so
VNDK-core: android.hardware.audio.common@2.0.so
VNDK-core: android.hardware.authsecret-V1-ndk_platform.so
+VNDK-core: android.hardware.authsecret-ndk_platform.so
+VNDK-core: android.hardware.authsecret-unstable-ndk_platform.so
VNDK-core: android.hardware.automotive.occupant_awareness-V1-ndk_platform.so
+VNDK-core: android.hardware.automotive.occupant_awareness-ndk_platform.so
VNDK-core: android.hardware.configstore-utils.so
VNDK-core: android.hardware.configstore@1.0.so
VNDK-core: android.hardware.configstore@1.1.so
@@ -69,28 +76,50 @@
VNDK-core: android.hardware.graphics.bufferqueue@1.0.so
VNDK-core: android.hardware.graphics.bufferqueue@2.0.so
VNDK-core: android.hardware.health.storage-V1-ndk_platform.so
+VNDK-core: android.hardware.health.storage-ndk_platform.so
+VNDK-core: android.hardware.health.storage-unstable-ndk_platform.so
VNDK-core: android.hardware.identity-V2-ndk_platform.so
+VNDK-core: android.hardware.identity-ndk_platform.so
VNDK-core: android.hardware.keymaster-V2-ndk_platform.so
+VNDK-core: android.hardware.keymaster-ndk_platform.so
VNDK-core: android.hardware.light-V1-ndk_platform.so
+VNDK-core: android.hardware.light-ndk_platform.so
VNDK-core: android.hardware.media.bufferpool@2.0.so
VNDK-core: android.hardware.media.omx@1.0.so
VNDK-core: android.hardware.media@1.0.so
VNDK-core: android.hardware.memtrack-V1-ndk_platform.so
+VNDK-core: android.hardware.memtrack-ndk_platform.so
+VNDK-core: android.hardware.memtrack-unstable-ndk_platform.so
VNDK-core: android.hardware.memtrack@1.0.so
VNDK-core: android.hardware.oemlock-V1-ndk_platform.so
+VNDK-core: android.hardware.oemlock-ndk_platform.so
+VNDK-core: android.hardware.oemlock-unstable-ndk_platform.so
VNDK-core: android.hardware.power-V1-ndk_platform.so
+VNDK-core: android.hardware.power-ndk_platform.so
VNDK-core: android.hardware.rebootescrow-V1-ndk_platform.so
+VNDK-core: android.hardware.rebootescrow-ndk_platform.so
VNDK-core: android.hardware.security.keymint-V1-ndk_platform.so
+VNDK-core: android.hardware.security.keymint-ndk_platform.so
+VNDK-core: android.hardware.security.keymint-unstable-ndk_platform.so
VNDK-core: android.hardware.security.secureclock-V1-ndk_platform.so
+VNDK-core: android.hardware.security.secureclock-ndk_platform.so
+VNDK-core: android.hardware.security.secureclock-unstable-ndk_platform.so
VNDK-core: android.hardware.security.sharedsecret-V1-ndk_platform.so
+VNDK-core: android.hardware.security.sharedsecret-ndk_platform.so
+VNDK-core: android.hardware.security.sharedsecret-unstable-ndk_platform.so
VNDK-core: android.hardware.soundtrigger@2.0-core.so
VNDK-core: android.hardware.soundtrigger@2.0.so
VNDK-core: android.hardware.vibrator-V1-ndk_platform.so
+VNDK-core: android.hardware.vibrator-ndk_platform.so
+VNDK-core: android.hardware.weaver-V1-ndk_platform.so
+VNDK-core: android.hardware.weaver-ndk_platform.so
+VNDK-core: android.hardware.weaver-unstable-ndk_platform.so
VNDK-core: android.hidl.token@1.0-utils.so
VNDK-core: android.hidl.token@1.0.so
VNDK-core: android.system.keystore2-V1-ndk_platform.so
+VNDK-core: android.system.keystore2-ndk_platform.so
+VNDK-core: android.system.keystore2-unstable-ndk_platform.so
VNDK-core: android.system.suspend@1.0.so
-VNDK-core: libadf.so
VNDK-core: libaudioroute.so
VNDK-core: libaudioutils.so
VNDK-core: libbinder.so
diff --git a/target/product/runtime_libart.mk b/target/product/runtime_libart.mk
index 4da8794..e655d51 100644
--- a/target/product/runtime_libart.mk
+++ b/target/product/runtime_libart.mk
@@ -94,3 +94,15 @@
PRODUCT_SYSTEM_PROPERTIES += \
dalvik.vm.minidebuginfo=true \
dalvik.vm.dex2oat-minidebuginfo=true
+
+# Two other device configs are added to IORap besides "ro.iorapd.enable".
+# IORap by default is off and starts when
+# (https://source.corp.google.com/android/system/iorap/iorapd.rc?q=iorapd.rc)
+#
+# * "ro.iorapd.enable" is true excluding unset
+# * One of the device configs is true.
+#
+# "ro.iorapd.enable" has to be set to true, so that iorap can be started.
+PRODUCT_SYSTEM_PROPERTIES += \
+ ro.iorapd.enable?=true
+
diff --git a/tools/product_config/Android.bp b/tools/product_config/Android.bp
new file mode 100644
index 0000000..287ed5a
--- /dev/null
+++ b/tools/product_config/Android.bp
@@ -0,0 +1,23 @@
+java_defaults {
+ name: "product-config-defaults",
+ srcs: ["src/**/*.java"],
+}
+
+java_binary_host {
+ name: "product-config",
+ defaults: ["product-config-defaults"],
+ manifest: "MANIFEST.MF"
+}
+
+java_test_host {
+ name: "product-config-test",
+ defaults: ["product-config-defaults"],
+ srcs: [
+ "test/**/*.java",
+ ],
+ static_libs: [
+ "junit"
+ ],
+ test_suites: ["general-tests"]
+}
+
diff --git a/tools/product_config/MANIFEST.MF b/tools/product_config/MANIFEST.MF
new file mode 100644
index 0000000..db88df3
--- /dev/null
+++ b/tools/product_config/MANIFEST.MF
@@ -0,0 +1,2 @@
+Manifest-Version: 1.0
+Main-Class: com.android.build.config.Main
diff --git a/tools/product_config/TEST_MAPPING b/tools/product_config/TEST_MAPPING
new file mode 100644
index 0000000..d3568f1
--- /dev/null
+++ b/tools/product_config/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name": "product_config_test"
+ }
+ ]
+}
diff --git a/tools/product_config/src/com/android/build/config/ErrorReporter.java b/tools/product_config/src/com/android/build/config/ErrorReporter.java
new file mode 100644
index 0000000..f382b4e
--- /dev/null
+++ b/tools/product_config/src/com/android/build/config/ErrorReporter.java
@@ -0,0 +1,263 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+package com.android.build.config;
+
+import java.lang.reflect.Field;
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Base class for reporting errors.
+ */
+public class ErrorReporter {
+ /**
+ * List of Entries that have occurred.
+ */
+ // Also used as the lock for this object.
+ private final ArrayList<Entry> mEntries = new ArrayList();
+
+ /**
+ * The categories that are for this Errors object.
+ */
+ private Map<Integer, Category> mCategories;
+
+ /**
+ * Whether there has been a warning or an error yet.
+ */
+ private boolean mHadWarningOrError;
+
+ /**
+ * Whether there has been an error yet.
+ */
+ private boolean mHadError;
+
+ /**
+ * Whether errors are errors, warnings or hidden.
+ */
+ public static enum Level {
+ HIDDEN("hidden"),
+ WARNING("warning"),
+ ERROR("error");
+
+ private final String mLabel;
+
+ Level(String label) {
+ mLabel = label;
+ }
+
+ String getLabel() {
+ return mLabel;
+ }
+ }
+
+ /**
+ * The available error codes.
+ */
+ public class Category {
+ private final int mCode;
+ private boolean mIsLevelSettable;
+ private Level mLevel;
+ private String mHelp;
+
+ /**
+ * Construct a Category object.
+ */
+ public Category(int code, boolean isLevelSettable, Level level, String help) {
+ if (!isLevelSettable && level != Level.ERROR) {
+ throw new RuntimeException("Don't have WARNING or HIDDEN without isLevelSettable");
+ }
+ mCode = code;
+ mIsLevelSettable = isLevelSettable;
+ mLevel = level;
+ mHelp = help;
+ }
+
+ /**
+ * Get the numeric code for the Category, which can be used to set the level.
+ */
+ public int getCode() {
+ return mCode;
+ }
+
+ /**
+ * Get whether the level of this Category can be changed.
+ */
+ public boolean isLevelSettable() {
+ return mIsLevelSettable;
+ }
+
+ /**
+ * Set the level of this category.
+ */
+ public void setLevel(Level level) {
+ if (!mIsLevelSettable) {
+ throw new RuntimeException("Can't set level for error " + mCode);
+ }
+ mLevel = level;
+ }
+
+ /**
+ * Return the level, including any overrides.
+ */
+ public Level getLevel() {
+ return mLevel;
+ }
+
+ /**
+ * Return the category's help text.
+ */
+ public String getHelp() {
+ return mHelp;
+ }
+ }
+
+ /**
+ * An instance of an error happening.
+ */
+ public class Entry {
+ private final Category mCategory;
+ private final Position mPosition;
+ private final String mMessage;
+
+ Entry(Category category, Position position, String message) {
+ mCategory = category;
+ mPosition = position;
+ mMessage = message;
+ }
+
+ public Category getCategory() {
+ return mCategory;
+ }
+
+ public Position getPosition() {
+ return mPosition;
+ }
+
+ public String getMessage() {
+ return mMessage;
+ }
+ }
+
+ private void initLocked() {
+ if (mCategories == null) {
+ HashMap<Integer, Category> categories = new HashMap();
+ for (Field field: getClass().getFields()) {
+ if (Category.class.isAssignableFrom(field.getType())) {
+ Category category = null;
+ try {
+ category = (Category)field.get(this);
+ } catch (IllegalAccessException ex) {
+ // Wrap and rethrow, this is always on this class, so it's
+ // our programming error if this happens.
+ throw new RuntimeException("Categories on Errors should be public.", ex);
+ }
+ Category prev = categories.put(category.getCode(), category);
+ if (prev != null) {
+ throw new RuntimeException("Duplicate categories with code "
+ + category.getCode());
+ }
+ }
+ }
+ mCategories = Collections.unmodifiableMap(categories);
+ }
+ }
+
+ /**
+ * Returns a map of the category codes to the categories.
+ */
+ public Map<Integer, Category> getCategories() {
+ synchronized (mEntries) {
+ initLocked();
+ return mCategories;
+ }
+ }
+
+ /**
+ * Add an error with no source position.
+ */
+ public void add(Category category, String message) {
+ add(category, new Position(), message);
+ }
+
+ /**
+ * Add an error.
+ */
+ public void add(Category category, Position pos, String message) {
+ synchronized (mEntries) {
+ initLocked();
+ if (mCategories.get(category.getCode()) != category) {
+ throw new RuntimeException("Errors.Category used from the wrong Errors object.");
+ }
+ mEntries.add(new Entry(category, pos, message));
+ final Level level = category.getLevel();
+ if (level == Level.WARNING || level == Level.ERROR) {
+ mHadWarningOrError = true;
+ }
+ if (level == Level.ERROR) {
+ mHadError = true;
+ }
+ }
+ }
+
+ /**
+ * Returns whether there has been a warning or an error yet.
+ */
+ public boolean hadWarningOrError() {
+ synchronized (mEntries) {
+ return mHadWarningOrError;
+ }
+ }
+
+ /**
+ * Returns whether there has been an error yet.
+ */
+ public boolean hadError() {
+ synchronized (mEntries) {
+ return mHadError;
+ }
+ }
+
+ /**
+ * Returns a list of all entries that were added.
+ */
+ public List<Entry> getEntries() {
+ synchronized (mEntries) {
+ return new ArrayList<Entry>(mEntries);
+ }
+ }
+
+ /**
+ * Prints the errors.
+ */
+ public void printErrors(PrintStream out) {
+ synchronized (mEntries) {
+ for (Entry entry: mEntries) {
+ final Category category = entry.getCategory();
+ final Level level = category.getLevel();
+ if (level == Level.HIDDEN) {
+ continue;
+ }
+ out.println(entry.getPosition() + "[" + level.getLabel() + " "
+ + category.getCode() + "] " + entry.getMessage());
+ }
+ }
+ }
+}
diff --git a/tools/product_config/src/com/android/build/config/Errors.java b/tools/product_config/src/com/android/build/config/Errors.java
new file mode 100644
index 0000000..63792c8
--- /dev/null
+++ b/tools/product_config/src/com/android/build/config/Errors.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+package com.android.build.config;
+
+import java.lang.reflect.Field;
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Error constants and error reporting.
+ * <p>
+ * <b>Naming Convention:</b>
+ * <ul>
+ * <li>ERROR_ for Categories with isLevelSettable false and Level.ERROR
+ * <li>WARNING_ for Categories with isLevelSettable false and default WARNING or HIDDEN
+ * <li>Don't have isLevelSettable true and not ERROR. (The constructor asserts this).
+ * </ul>
+ */
+public class Errors extends ErrorReporter {
+
+ public final Category ERROR_COMMAND_LINE = new Category(1, false, Level.ERROR,
+ "Error on the command line.");
+
+ public final Category WARNING_UNKNOWN_COMMAND_LINE_ERROR = new Category(2, true, Level.HIDDEN,
+ "Passing unknown errors on the command line. Hidden by default for\n"
+ + "forward compatibility.");
+}
diff --git a/tools/product_config/src/com/android/build/config/Main.java b/tools/product_config/src/com/android/build/config/Main.java
new file mode 100644
index 0000000..7669742
--- /dev/null
+++ b/tools/product_config/src/com/android/build/config/Main.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+package com.android.build.config;
+
+public class Main {
+ private final Errors mErrors;
+ private final Options mOptions;
+
+ public Main(Errors errors, Options options) {
+ mErrors = errors;
+ mOptions = options;
+ }
+
+ void run() {
+ System.out.println("Hello World");
+
+ // TODO: Check the build environment to make sure we're running in a real
+ // build environment, e.g. actually inside a source tree, with TARGET_PRODUCT
+ // and TARGET_BUILD_VARIANT defined, etc.
+
+ // TODO: Run kati and extract the variables and convert all that into starlark files.
+
+ // TODO: Run starlark with all the generated ones and the hand written ones.
+
+ // TODO: Get the variables that were defined in starlark and use that to write
+ // out the make, soong and bazel input files.
+ }
+
+ public static void main(String[] args) {
+ Errors errors = new Errors();
+
+ Options options = Options.parse(errors, args);
+ if (errors.hadError()) {
+ Options.printHelp(System.err);
+ System.err.println();
+ errors.printErrors(System.err);
+ System.exit(1);
+ }
+
+ switch (options.getAction()) {
+ case DEFAULT:
+ (new Main(errors, options)).run();
+ errors.printErrors(System.err);
+ return;
+ case HELP:
+ Options.printHelp(System.out);
+ return;
+ }
+ }
+}
diff --git a/tools/product_config/src/com/android/build/config/Options.java b/tools/product_config/src/com/android/build/config/Options.java
new file mode 100644
index 0000000..494b947
--- /dev/null
+++ b/tools/product_config/src/com/android/build/config/Options.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+package com.android.build.config;
+
+import java.io.PrintStream;
+import java.util.TreeMap;
+
+public class Options {
+ public enum Action {
+ DEFAULT,
+ HELP
+ }
+
+ private Action mAction = Action.DEFAULT;
+
+ public Action getAction() {
+ return mAction;
+ }
+
+ public static void printHelp(PrintStream out) {
+ out.println("usage: product_config");
+ out.println();
+ out.println("OPTIONS");
+ out.println(" --hide ERROR_ID Suppress this error.");
+ out.println(" --error ERROR_ID Make this ERROR_ID a fatal error.");
+ out.println(" --help -h This message.");
+ out.println(" --warning ERROR_ID Make this ERROR_ID a warning.");
+ out.println();
+ out.println("ERRORS");
+ out.println(" The following are the errors that can be controlled on the");
+ out.println(" commandline with the --hide --warning --error flags.");
+
+ TreeMap<Integer,Errors.Category> sorted = new TreeMap((new Errors()).getCategories());
+
+ for (final Errors.Category category: sorted.values()) {
+ if (category.isLevelSettable()) {
+ out.println(String.format(" %-3d %s", category.getCode(),
+ category.getHelp().replace("\n", "\n ")));
+ }
+ }
+ }
+
+ static class Parser {
+ private class ParseException extends Exception {
+ public ParseException(String message) {
+ super(message);
+ }
+ }
+
+ private Errors mErrors;
+ private String[] mArgs;
+ private Options mResult = new Options();
+ private int mIndex;
+
+ public Parser(Errors errors, String[] args) {
+ mErrors = errors;
+ mArgs = args;
+ }
+
+ public Options parse() {
+ try {
+ while (mIndex < mArgs.length) {
+ final String arg = mArgs[mIndex];
+
+ if ("--hide".equals(arg)) {
+ handleErrorCode(arg, Errors.Level.HIDDEN);
+ } else if ("--error".equals(arg)) {
+ handleErrorCode(arg, Errors.Level.ERROR);
+ } else if ("--help".equals(arg) || "-h".equals(arg)) {
+ // Help overrides all other commands if there isn't an error, but
+ // we will stop here.
+ if (!mErrors.hadError()) {
+ mResult.mAction = Action.HELP;
+ }
+ return mResult;
+ } else if ("--warning".equals(arg)) {
+ handleErrorCode(arg, Errors.Level.WARNING);
+ } else {
+ throw new ParseException("Unknown command line argument: " + arg);
+ }
+
+ mIndex++;
+ }
+ } catch (ParseException ex) {
+ mErrors.add(mErrors.ERROR_COMMAND_LINE, ex.getMessage());
+ }
+
+ return mResult;
+ }
+
+ private void addWarning(Errors.Category category, String message) {
+ mErrors.add(category, message);
+ }
+
+ private String getNextNonFlagArg() {
+ if (mIndex == mArgs.length - 1) {
+ return null;
+ }
+ if (mArgs[mIndex + 1].startsWith("-")) {
+ return null;
+ }
+ mIndex++;
+ return mArgs[mIndex];
+ }
+
+ private int requireNextNumberArg(String arg) throws ParseException {
+ final String val = getNextNonFlagArg();
+ if (val == null) {
+ throw new ParseException(arg + " requires a numeric argument.");
+ }
+ try {
+ return Integer.parseInt(val);
+ } catch (NumberFormatException ex) {
+ throw new ParseException(arg + " requires a numeric argument. found: " + val);
+ }
+ }
+
+ private void handleErrorCode(String arg, Errors.Level level) throws ParseException {
+ final int code = requireNextNumberArg(arg);
+ final Errors.Category category = mErrors.getCategories().get(code);
+ if (category == null) {
+ mErrors.add(mErrors.WARNING_UNKNOWN_COMMAND_LINE_ERROR,
+ "Unknown error code: " + code);
+ return;
+ }
+ if (!category.isLevelSettable()) {
+ mErrors.add(mErrors.ERROR_COMMAND_LINE, "Can't set level for error " + code);
+ return;
+ }
+ category.setLevel(level);
+ }
+ }
+
+ /**
+ * Parse the arguments and return an options object.
+ * <p>
+ * Updates errors with the hidden / warning / error levels.
+ * <p>
+ * Adds errors encountered to Errors object.
+ */
+ public static Options parse(Errors errors, String[] args) {
+ return (new Parser(errors, args)).parse();
+ }
+}
diff --git a/tools/product_config/src/com/android/build/config/Position.java b/tools/product_config/src/com/android/build/config/Position.java
new file mode 100644
index 0000000..7953942
--- /dev/null
+++ b/tools/product_config/src/com/android/build/config/Position.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+package com.android.build.config;
+
+/**
+ * Position in a source file.
+ */
+public class Position implements Comparable<Position> {
+ /**
+ * Sentinel line number for when there is no known line number.
+ */
+ public static final int NO_LINE = -1;
+
+ private final String mFile;
+ private final int mLine;
+
+ public Position() {
+ mFile = null;
+ mLine = NO_LINE;
+ }
+
+ public Position(String file) {
+ mFile = file;
+ mLine = NO_LINE;
+ }
+
+ public Position(String file, int line) {
+ if (line < NO_LINE) {
+ throw new IllegalArgumentException("Negative line number. file=" + file
+ + " line=" + line);
+ }
+ mFile = file;
+ mLine = line;
+ }
+
+ public int compareTo(Position that) {
+ int result = mFile.compareTo(that.mFile);
+ if (result != 0) {
+ return result;
+ }
+ return mLine - that.mLine;
+ }
+
+ public String getFile() {
+ return mFile;
+ }
+
+ public int getLine() {
+ return mLine;
+ }
+
+ @Override
+ public String toString() {
+ if (mFile == null && mLine == NO_LINE) {
+ return "";
+ } else if (mFile == null && mLine != NO_LINE) {
+ return "<unknown>:" + mLine + ": ";
+ } else if (mFile != null && mLine == NO_LINE) {
+ return mFile + ": ";
+ } else { // if (mFile != null && mLine != NO_LINE)
+ return mFile + ':' + mLine + ": ";
+ }
+ }
+}
diff --git a/tools/product_config/test/com/android/build/config/ErrorReporterTest.java b/tools/product_config/test/com/android/build/config/ErrorReporterTest.java
new file mode 100644
index 0000000..2cde476
--- /dev/null
+++ b/tools/product_config/test/com/android/build/config/ErrorReporterTest.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+package com.android.build.config;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.HashSet;
+import java.util.List;
+
+public class ErrorReporterTest {
+ /**
+ * Test that errors can be recorded and retrieved.
+ */
+ @Test
+ public void testAdding() {
+ TestErrors errors = new TestErrors();
+
+ errors.add(errors.ERROR, new Position("a", 12), "Errrororrrr");
+
+ Assert.assertTrue(errors.hadWarningOrError());
+ Assert.assertTrue(errors.hadError());
+
+ List<TestErrors.Entry> entries = errors.getEntries();
+ Assert.assertEquals(1, entries.size());
+
+ TestErrors.Entry entry = entries.get(0);
+ Assert.assertEquals(errors.ERROR, entry.getCategory());
+ Assert.assertEquals("a", entry.getPosition().getFile());
+ Assert.assertEquals(12, entry.getPosition().getLine());
+ Assert.assertEquals("Errrororrrr", entry.getMessage());
+
+ Assert.assertNotEquals("", errors.getErrorMessages());
+ }
+
+ /**
+ * Test that not adding an error doesn't record errors.
+ */
+ @Test
+ public void testNoError() {
+ TestErrors errors = new TestErrors();
+
+ Assert.assertFalse(errors.hadWarningOrError());
+ Assert.assertFalse(errors.hadError());
+ Assert.assertEquals("", errors.getErrorMessages());
+ }
+
+ /**
+ * Test that not adding a warning doesn't record errors.
+ */
+ @Test
+ public void testWarning() {
+ TestErrors errors = new TestErrors();
+
+ errors.add(errors.WARNING, "Waaaaarninggggg");
+
+ Assert.assertTrue(errors.hadWarningOrError());
+ Assert.assertFalse(errors.hadError());
+ Assert.assertNotEquals("", errors.getErrorMessages());
+ }
+
+ /**
+ * Test that hidden warnings don't report.
+ */
+ @Test
+ public void testHidden() {
+ TestErrors errors = new TestErrors();
+
+ errors.add(errors.HIDDEN, "Hidddeennn");
+
+ Assert.assertFalse(errors.hadWarningOrError());
+ Assert.assertFalse(errors.hadError());
+ Assert.assertEquals("", errors.getErrorMessages());
+ }
+
+ /**
+ * Test changing an error level.
+ */
+ @Test
+ public void testSetLevel() {
+ TestErrors errors = new TestErrors();
+ Assert.assertEquals(TestErrors.Level.ERROR, errors.ERROR.getLevel());
+
+ errors.ERROR.setLevel(TestErrors.Level.WARNING);
+
+ Assert.assertEquals(TestErrors.Level.WARNING, errors.ERROR.getLevel());
+ }
+
+ /**
+ * Test that changing a fixed error fails.
+ */
+ @Test
+ public void testSetLevelFails() {
+ TestErrors errors = new TestErrors();
+ Assert.assertEquals(TestErrors.Level.ERROR, errors.ERROR_FIXED.getLevel());
+
+ boolean exceptionThrown = false;
+ try {
+ errors.ERROR_FIXED.setLevel(TestErrors.Level.WARNING);
+ } catch (RuntimeException ex) {
+ exceptionThrown = true;
+ }
+
+ Assert.assertTrue(exceptionThrown);
+ Assert.assertEquals(TestErrors.Level.ERROR, errors.ERROR_FIXED.getLevel());
+ }
+}
diff --git a/tools/product_config/test/com/android/build/config/OptionsTest.java b/tools/product_config/test/com/android/build/config/OptionsTest.java
new file mode 100644
index 0000000..2c36322
--- /dev/null
+++ b/tools/product_config/test/com/android/build/config/OptionsTest.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+package com.android.build.config;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class OptionsTest {
+ @Test
+ public void testErrorMissingLast() {
+ final Errors errors = new Errors();
+
+ final Options options = Options.parse(errors, new String[] {
+ "--error"
+ });
+
+ Assert.assertNotEquals("", TestErrors.getErrorMessages(errors));
+ Assert.assertEquals(Options.Action.DEFAULT, options.getAction());
+ TestErrors.assertHasEntry(errors.ERROR_COMMAND_LINE, errors);
+ }
+
+ @Test
+ public void testErrorMissingNotLast() {
+ final Errors errors = new Errors();
+
+ final Options options = Options.parse(errors, new String[] {
+ "--error", "--warning", "2"
+ });
+
+ Assert.assertNotEquals("", TestErrors.getErrorMessages(errors));
+ Assert.assertEquals(Options.Action.DEFAULT, options.getAction());
+ TestErrors.assertHasEntry(errors.ERROR_COMMAND_LINE, errors);
+ }
+
+ @Test
+ public void testErrorNotNumeric() {
+ final Errors errors = new Errors();
+
+ final Options options = Options.parse(errors, new String[] {
+ "--error", "notgood"
+ });
+
+ Assert.assertNotEquals("", TestErrors.getErrorMessages(errors));
+ Assert.assertEquals(Options.Action.DEFAULT, options.getAction());
+ TestErrors.assertHasEntry(errors.ERROR_COMMAND_LINE, errors);
+ }
+
+ @Test
+ public void testErrorInvalidError() {
+ final Errors errors = new Errors();
+
+ final Options options = Options.parse(errors, new String[] {
+ "--error", "50000"
+ });
+
+ Assert.assertEquals("", TestErrors.getErrorMessages(errors));
+ Assert.assertEquals(Options.Action.DEFAULT, options.getAction());
+ TestErrors.assertHasEntry(errors.WARNING_UNKNOWN_COMMAND_LINE_ERROR, errors);
+ }
+
+ @Test
+ public void testErrorOne() {
+ final Errors errors = new Errors();
+
+ final Options options = Options.parse(errors, new String[] {
+ "--error", "2"
+ });
+
+ Assert.assertEquals("", TestErrors.getErrorMessages(errors));
+ Assert.assertEquals(Options.Action.DEFAULT, options.getAction());
+ Assert.assertFalse(errors.hadWarningOrError());
+ }
+
+ @Test
+ public void testWarningOne() {
+ final Errors errors = new Errors();
+
+ final Options options = Options.parse(errors, new String[] {
+ "--warning", "2"
+ });
+
+ Assert.assertEquals("", TestErrors.getErrorMessages(errors));
+ Assert.assertEquals(Options.Action.DEFAULT, options.getAction());
+ Assert.assertFalse(errors.hadWarningOrError());
+ }
+
+ @Test
+ public void testHideOne() {
+ final Errors errors = new Errors();
+
+ final Options options = Options.parse(errors, new String[] {
+ "--hide", "2"
+ });
+
+ Assert.assertEquals("", TestErrors.getErrorMessages(errors));
+ Assert.assertEquals(Options.Action.DEFAULT, options.getAction());
+ Assert.assertFalse(errors.hadWarningOrError());
+ }
+}
+
diff --git a/tools/product_config/test/com/android/build/config/TestErrors.java b/tools/product_config/test/com/android/build/config/TestErrors.java
new file mode 100644
index 0000000..dde88b0
--- /dev/null
+++ b/tools/product_config/test/com/android/build/config/TestErrors.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+package com.android.build.config;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * Errors for testing.
+ */
+public class TestErrors extends ErrorReporter {
+
+ public static final int ERROR_CODE = 1;
+
+ public final Category ERROR = new Category(ERROR_CODE, true, Level.ERROR,
+ "An error.");
+
+ public static final int WARNING_CODE = 2;
+
+ public final Category WARNING = new Category(WARNING_CODE, true, Level.WARNING,
+ "A warning.");
+
+ public static final int HIDDEN_CODE = 3;
+
+ public final Category HIDDEN = new Category(HIDDEN_CODE, true, Level.HIDDEN,
+ "A hidden warning.");
+
+ public static final int ERROR_FIXED_CODE = 4;
+
+ public final Category ERROR_FIXED = new Category(ERROR_FIXED_CODE, false, Level.ERROR,
+ "An error that can't have its level changed.");
+
+ public void assertHasEntry(Errors.Category category) {
+ assertHasEntry(category, this);
+ }
+
+ public String getErrorMessages() {
+ return getErrorMessages(this);
+ }
+
+ public static void assertHasEntry(Errors.Category category, ErrorReporter errors) {
+ StringBuilder found = new StringBuilder();
+ for (Errors.Entry entry: errors.getEntries()) {
+ if (entry.getCategory() == category) {
+ return;
+ }
+ found.append(' ');
+ found.append(entry.getCategory().getCode());
+ }
+ throw new AssertionError("No error category " + category.getCode() + " found."
+ + " Found category codes were:" + found);
+ }
+
+ public static String getErrorMessages(ErrorReporter errors) {
+ final ByteArrayOutputStream stream = new ByteArrayOutputStream();
+ try {
+ errors.printErrors(new PrintStream(stream, true, StandardCharsets.UTF_8.name()));
+ } catch (UnsupportedEncodingException ex) {
+ // utf-8 is always supported
+ }
+ return new String(stream.toByteArray(), StandardCharsets.UTF_8);
+ }
+}
+