Merge "Add keep rules for @KeepForWeakReference annotation"
diff --git a/core/Makefile b/core/Makefile
index 94b4803..3246f58 100644
--- a/core/Makefile
+++ b/core/Makefile
@@ -842,10 +842,6 @@
$(call declare-0p-target,$(INSTALLED_FILES_FILE_ROOT))
$(call declare-0p-target,$(INSTALLED_FILES_JSON_ROOT))
-ifeq ($(HOST_OS),linux)
-$(call dist-for-goals, sdk sdk_addon, $(INSTALLED_FILES_FILE_ROOT))
-endif
-
#------------------------------------------------------------------
# dtb
ifdef BOARD_INCLUDE_DTB_IN_BOOTIMG
@@ -877,9 +873,6 @@
$(eval $(call declare-0p-target,$(INSTALLED_FILES_FILE_RAMDISK)))
$(eval $(call declare-0p-target,$(INSTALLED_FILES_JSON_RAMDISK)))
-ifeq ($(HOST_OS),linux)
-$(call dist-for-goals, sdk sdk_addon, $(INSTALLED_FILES_FILE_RAMDISK))
-endif
BUILT_RAMDISK_TARGET := $(PRODUCT_OUT)/ramdisk.img
ifeq ($(BOARD_RAMDISK_USE_LZ4),true)
@@ -3108,10 +3101,6 @@
.PHONY: installed-file-list
installed-file-list: $(INSTALLED_FILES_FILE)
-ifeq ($(HOST_OS),linux)
-$(call dist-for-goals, sdk sdk_addon, $(INSTALLED_FILES_FILE))
-endif
-
systemimage_intermediates := \
$(call intermediates-dir-for,PACKAGING,systemimage)
BUILT_SYSTEMIMAGE := $(systemimage_intermediates)/system.img
@@ -6798,8 +6787,6 @@
# if we don't have a real list, then use "everything"
ifeq ($(strip $(ATREE_FILES)),)
ATREE_FILES := \
- $(ALL_DEFAULT_INSTALLED_MODULES) \
- $(INSTALLED_RAMDISK_TARGET) \
$(ALL_DOCS) \
$(ALL_SDK_FILES)
endif
@@ -6828,18 +6815,7 @@
deps := \
$(OUT_DOCS)/offline-sdk-timestamp \
$(SDK_METADATA_FILES) \
- $(SYMBOLS_ZIP) \
- $(COVERAGE_ZIP) \
- $(APPCOMPAT_ZIP) \
- $(INSTALLED_SYSTEMIMAGE_TARGET) \
- $(INSTALLED_QEMU_SYSTEMIMAGE) \
- $(INSTALLED_QEMU_RAMDISKIMAGE) \
- $(INSTALLED_QEMU_VENDORIMAGE) \
- $(QEMU_VERIFIED_BOOT_PARAMS) \
- $(INSTALLED_USERDATAIMAGE_TARGET) \
- $(INSTALLED_RAMDISK_TARGET) \
- $(INSTALLED_SDK_BUILD_PROP_TARGET) \
- $(INSTALLED_BUILD_PROP_TARGET) \
+ $(INSTALLED_SDK_BUILD_PROP_TARGET) \
$(ATREE_FILES) \
$(sdk_atree_files) \
$(HOST_OUT_EXECUTABLES)/atree \
diff --git a/core/android_manifest.mk b/core/android_manifest.mk
index 254e09b..ff49262 100644
--- a/core/android_manifest.mk
+++ b/core/android_manifest.mk
@@ -87,13 +87,23 @@
endif
endif
+# TODO: Replace this hardcoded list of optional uses-libraries with build logic
+# that propagates optionality via the generated exported-sdk-libs files.
+# Hardcodng doesn't scale and enforces a single choice on each library, while in
+# reality this is a choice of the library users (which may differ).
+my_optional_sdk_lib_names := \
+ android.test.base \
+ android.test.mock \
+ androidx.window.extensions \
+ androidx.window.sidecar
+
$(fixed_android_manifest): PRIVATE_MANIFEST_FIXER_FLAGS := $(my_manifest_fixer_flags)
# These two libs are added as optional dependencies (<uses-library> with
# android:required set to false). This is because they haven't existed in pre-P
# devices, but classes in them were in bootclasspath jars, etc. So making them
# hard dependencies (andriod:required=true) would prevent apps from being
# installed to such legacy devices.
-$(fixed_android_manifest): PRIVATE_OPTIONAL_SDK_LIB_NAMES := android.test.base android.test.mock
+$(fixed_android_manifest): PRIVATE_OPTIONAL_SDK_LIB_NAMES := $(my_optional_sdk_lib_names)
$(fixed_android_manifest): $(MANIFEST_FIXER)
$(fixed_android_manifest): $(main_android_manifest)
echo $(PRIVATE_OPTIONAL_SDK_LIB_NAMES) | tr ' ' '\n' > $(PRIVATE_EXPORTED_SDK_LIBS_FILE).optional
@@ -109,3 +119,5 @@
) \
$< $@
rm $(PRIVATE_EXPORTED_SDK_LIBS_FILE).optional
+
+my_optional_sdk_lib_names :=
diff --git a/core/config.mk b/core/config.mk
index 4db33f1..247103d 100644
--- a/core/config.mk
+++ b/core/config.mk
@@ -226,8 +226,6 @@
BUILD_FUZZ_TEST :=$= $(BUILD_SYSTEM)/fuzz_test.mk
BUILD_NOTICE_FILE :=$= $(BUILD_SYSTEM)/notice_files.mk
-BUILD_HOST_DALVIK_JAVA_LIBRARY :=$= $(BUILD_SYSTEM)/host_dalvik_java_library.mk
-BUILD_HOST_DALVIK_STATIC_JAVA_LIBRARY :=$= $(BUILD_SYSTEM)/host_dalvik_static_java_library.mk
include $(BUILD_SYSTEM)/deprecation.mk
diff --git a/core/definitions.mk b/core/definitions.mk
index 8fe5edb..e424bc2 100644
--- a/core/definitions.mk
+++ b/core/definitions.mk
@@ -3360,8 +3360,6 @@
STATIC_TEST_LIBRARY \
HOST_STATIC_TEST_LIBRARY \
NOTICE_FILE \
- HOST_DALVIK_JAVA_LIBRARY \
- HOST_DALVIK_STATIC_JAVA_LIBRARY \
base_rules \
HEADER_LIBRARY \
HOST_TEST_CONFIG \
diff --git a/core/deprecation.mk b/core/deprecation.mk
index 2b7a869..ed4215e 100644
--- a/core/deprecation.mk
+++ b/core/deprecation.mk
@@ -3,8 +3,6 @@
BUILD_EXECUTABLE \
BUILD_FUZZ_TEST \
BUILD_HEADER_LIBRARY \
- BUILD_HOST_DALVIK_JAVA_LIBRARY \
- BUILD_HOST_DALVIK_STATIC_JAVA_LIBRARY \
BUILD_HOST_JAVA_LIBRARY \
BUILD_HOST_PREBUILT \
BUILD_JAVA_LIBRARY \
@@ -39,6 +37,8 @@
OBSOLETE_BUILD_MODULE_TYPES :=$= \
BUILD_AUX_EXECUTABLE \
BUILD_AUX_STATIC_LIBRARY \
+ BUILD_HOST_DALVIK_JAVA_LIBRARY \
+ BUILD_HOST_DALVIK_STATIC_JAVA_LIBRARY \
BUILD_HOST_FUZZ_TEST \
BUILD_HOST_NATIVE_TEST \
BUILD_HOST_SHARED_TEST_LIBRARY \
diff --git a/core/host_dalvik_java_library.mk b/core/host_dalvik_java_library.mk
deleted file mode 100644
index 5eeb8ac..0000000
--- a/core/host_dalvik_java_library.mk
+++ /dev/null
@@ -1,191 +0,0 @@
-#
-# Copyright (C) 2013 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.
-#
-$(call record-module-type,HOST_DALVIK_JAVA_LIBRARY)
-
-#
-# Rules for building a host dalvik java library. These libraries
-# are meant to be used by a dalvik VM instance running on the host.
-# They will be compiled against libcore and not the host JRE.
-#
-
-ifeq ($(HOST_OS),linux)
-USE_CORE_LIB_BOOTCLASSPATH := true
-
-#######################################
-include $(BUILD_SYSTEM)/host_java_library_common.mk
-#######################################
-
-full_classes_turbine_jar := $(intermediates.COMMON)/classes-turbine.jar
-full_classes_header_jarjar := $(intermediates.COMMON)/classes-header-jarjar.jar
-full_classes_header_jar := $(intermediates.COMMON)/classes-header.jar
-full_classes_compiled_jar := $(intermediates.COMMON)/classes-full-debug.jar
-full_classes_combined_jar := $(intermediates.COMMON)/classes-combined.jar
-full_classes_jarjar_jar := $(intermediates.COMMON)/classes-jarjar.jar
-full_classes_jar := $(intermediates.COMMON)/classes.jar
-built_dex := $(intermediates.COMMON)/classes.dex
-java_source_list_file := $(intermediates.COMMON)/java-source-list
-
-LOCAL_INTERMEDIATE_TARGETS += \
- $(full_classes_turbine_jar) \
- $(full_classes_compiled_jar) \
- $(full_classes_combined_jar) \
- $(full_classes_jarjar_jar) \
- $(full_classes_jar) \
- $(built_dex) \
- $(java_source_list_file)
-
-# See comment in java.mk
-ifndef LOCAL_CHECKED_MODULE
-ifeq ($(LOCAL_IS_STATIC_JAVA_LIBRARY),true)
-LOCAL_CHECKED_MODULE := $(full_classes_compiled_jar)
-else
-LOCAL_CHECKED_MODULE := $(built_dex)
-endif
-endif
-
-#######################################
-include $(BUILD_SYSTEM)/base_rules.mk
-#######################################
-java_sources := $(addprefix $(LOCAL_PATH)/, $(filter %.java,$(LOCAL_SRC_FILES))) \
- $(filter %.java,$(LOCAL_GENERATED_SOURCES))
-all_java_sources := $(java_sources)
-
-include $(BUILD_SYSTEM)/java_common.mk
-
-include $(BUILD_SYSTEM)/sdk_check.mk
-
-$(cleantarget): PRIVATE_CLEAN_FILES += $(intermediates.COMMON)
-
-# List of dependencies for anything that needs all java sources in place
-java_sources_deps := \
- $(java_sources) \
- $(java_resource_sources) \
- $(LOCAL_SRCJARS) \
- $(LOCAL_ADDITIONAL_DEPENDENCIES)
-
-$(java_source_list_file): $(java_sources_deps)
- $(write-java-source-list)
-
-# TODO(b/143658984): goma can't handle the --system argument to javac.
-#$(full_classes_compiled_jar): .KATI_NINJA_POOL := $(GOMA_POOL)
-$(full_classes_compiled_jar): PRIVATE_JAVA_LAYERS_FILE := $(layers_file)
-$(full_classes_compiled_jar): PRIVATE_JAVACFLAGS := $(LOCAL_JAVACFLAGS) $(annotation_processor_flags)
-$(full_classes_compiled_jar): PRIVATE_JAR_EXCLUDE_FILES :=
-$(full_classes_compiled_jar): PRIVATE_JAR_PACKAGES :=
-$(full_classes_compiled_jar): PRIVATE_JAR_EXCLUDE_PACKAGES :=
-$(full_classes_compiled_jar): PRIVATE_SRCJARS := $(LOCAL_SRCJARS)
-$(full_classes_compiled_jar): PRIVATE_SRCJAR_LIST_FILE := $(intermediates.COMMON)/srcjar-list
-$(full_classes_compiled_jar): PRIVATE_SRCJAR_INTERMEDIATES_DIR := $(intermediates.COMMON)/srcjars
-$(full_classes_compiled_jar): \
- $(java_source_list_file) \
- $(java_sources_deps) \
- $(full_java_header_libs) \
- $(full_java_bootclasspath_libs) \
- $(full_java_system_modules_deps) \
- $(annotation_processor_deps) \
- $(NORMALIZE_PATH) \
- $(JAR_ARGS) \
- $(ZIPSYNC) \
- $(SOONG_ZIP) \
- | $(SOONG_JAVAC_WRAPPER)
- $(transform-host-java-to-dalvik-package)
-
-ifneq ($(TURBINE_ENABLED),false)
-
-$(full_classes_turbine_jar): PRIVATE_JAVACFLAGS := $(LOCAL_JAVACFLAGS) $(annotation_processor_flags)
-$(full_classes_turbine_jar): PRIVATE_SRCJARS := $(LOCAL_SRCJARS)
-$(full_classes_turbine_jar): \
- $(java_source_list_file) \
- $(java_sources_deps) \
- $(full_java_header_libs) \
- $(full_java_bootclasspath_libs) \
- $(NORMALIZE_PATH) \
- $(JAR_ARGS) \
- $(ZIPTIME) \
- | $(TURBINE) \
- $(MERGE_ZIPS)
- $(transform-java-to-header.jar)
-
-.KATI_RESTAT: $(full_classes_turbine_jar)
-
-# Run jarjar before generate classes-header.jar if necessary.
-ifneq ($(strip $(LOCAL_JARJAR_RULES)),)
-$(full_classes_header_jarjar): PRIVATE_JARJAR_RULES := $(LOCAL_JARJAR_RULES)
-$(full_classes_header_jarjar): $(full_classes_turbine_jar) $(LOCAL_JARJAR_RULES) | $(JARJAR)
- $(call transform-jarjar)
-else
-full_classes_header_jarjar := $(full_classes_turbine_jar)
-endif
-
-$(eval $(call copy-one-file,$(full_classes_header_jarjar),$(full_classes_header_jar)))
-
-endif # TURBINE_ENABLED != false
-
-$(full_classes_combined_jar): PRIVATE_DONT_DELETE_JAR_META_INF := $(LOCAL_DONT_DELETE_JAR_META_INF)
-$(full_classes_combined_jar): $(full_classes_compiled_jar) \
- $(jar_manifest_file) \
- $(full_static_java_libs) | $(MERGE_ZIPS)
- $(if $(PRIVATE_JAR_MANIFEST), $(hide) sed -e "s/%BUILD_NUMBER%/$(BUILD_NUMBER_FROM_FILE)/" \
- $(PRIVATE_JAR_MANIFEST) > $(dir $@)/manifest.mf)
- $(MERGE_ZIPS) -j --ignore-duplicates $(if $(PRIVATE_JAR_MANIFEST),-m $(dir $@)/manifest.mf) \
- $(if $(PRIVATE_DONT_DELETE_JAR_META_INF),,-stripDir META-INF -zipToNotStrip $<) \
- $@ $< $(PRIVATE_STATIC_JAVA_LIBRARIES)
-
-# Run jarjar if necessary, otherwise just copy the file.
-ifneq ($(strip $(LOCAL_JARJAR_RULES)),)
-$(full_classes_jarjar_jar): PRIVATE_JARJAR_RULES := $(LOCAL_JARJAR_RULES)
-$(full_classes_jarjar_jar): $(full_classes_combined_jar) $(LOCAL_JARJAR_RULES) | $(JARJAR)
- $(call transform-jarjar)
-else
-full_classes_jarjar_jar := $(full_classes_combined_jar)
-endif
-
-$(eval $(call copy-one-file,$(full_classes_jarjar_jar),$(full_classes_jar)))
-
-ifeq ($(LOCAL_IS_STATIC_JAVA_LIBRARY),true)
-# No dex; all we want are the .class files with resources.
-$(LOCAL_BUILT_MODULE) : $(java_resource_sources)
-$(LOCAL_BUILT_MODULE) : $(full_classes_jar)
- @echo "host Static Jar: $(PRIVATE_MODULE) ($@)"
- $(copy-file-to-target)
-
-else # !LOCAL_IS_STATIC_JAVA_LIBRARY
-$(built_dex): PRIVATE_INTERMEDIATES_DIR := $(intermediates.COMMON)
-$(built_dex): PRIVATE_DX_FLAGS := $(LOCAL_DX_FLAGS)
-$(built_dex): $(full_classes_jar) $(DX) $(ZIP2ZIP)
- $(transform-classes.jar-to-dex)
-
-$(LOCAL_BUILT_MODULE): PRIVATE_DEX_FILE := $(built_dex)
-$(LOCAL_BUILT_MODULE): PRIVATE_SOURCE_ARCHIVE := $(full_classes_jarjar_jar)
-$(LOCAL_BUILT_MODULE): $(MERGE_ZIPS) $(SOONG_ZIP) $(ZIP2ZIP)
-$(LOCAL_BUILT_MODULE): $(built_dex) $(java_resource_sources)
- @echo "Host Jar: $(PRIVATE_MODULE) ($@)"
- rm -rf $@.parts
- mkdir -p $@.parts
- $(call create-dex-jar,$@.parts/dex.zip,$(PRIVATE_DEX_FILE))
- $(call extract-resources-jar,$@.parts/res.zip,$(PRIVATE_SOURCE_ARCHIVE))
- $(MERGE_ZIPS) -j $@ $@.parts/dex.zip $@.parts/res.zip
- rm -rf $@.parts
-
-endif # !LOCAL_IS_STATIC_JAVA_LIBRARY
-
-$(LOCAL_INTERMEDIATE_TARGETS): PRIVATE_DEFAULT_APP_TARGET_SDK := $(call module-target-sdk-version)
-$(LOCAL_INTERMEDIATE_TARGETS): PRIVATE_SDK_VERSION := $(call module-sdk-version)
-$(LOCAL_INTERMEDIATE_TARGETS): PRIVATE_MIN_SDK_VERSION := $(call codename-or-sdk-to-sdk,$(call module-min-sdk-version))
-
-USE_CORE_LIB_BOOTCLASSPATH :=
-
-endif
diff --git a/core/host_dalvik_static_java_library.mk b/core/host_dalvik_static_java_library.mk
deleted file mode 100644
index 78faf73..0000000
--- a/core/host_dalvik_static_java_library.mk
+++ /dev/null
@@ -1,28 +0,0 @@
-#
-# Copyright (C) 2013 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.
-#
-$(call record-module-type,HOST_DALVIK_STATIC_JAVA_LIBRARY)
-
-#
-# Rules for building a host dalvik static java library.
-# These libraries will be compiled against libcore and not the host
-# JRE.
-#
-LOCAL_UNINSTALLABLE_MODULE := true
-LOCAL_IS_STATIC_JAVA_LIBRARY := true
-
-include $(BUILD_SYSTEM)/host_dalvik_java_library.mk
-
-LOCAL_IS_STATIC_JAVA_LIBRARY :=
diff --git a/core/main.mk b/core/main.mk
index 171a761..78f38f3 100644
--- a/core/main.mk
+++ b/core/main.mk
@@ -1936,10 +1936,6 @@
sdk: $(ALL_SDK_TARGETS)
$(call dist-for-goals,sdk, \
$(ALL_SDK_TARGETS) \
- $(SYMBOLS_ZIP) \
- $(SYMBOLS_MAPPING) \
- $(COVERAGE_ZIP) \
- $(APPCOMPAT_ZIP) \
$(INSTALLED_BUILD_PROP_TARGET) \
)
endif
diff --git a/core/product_config.mk b/core/product_config.mk
index 35f018d..540289a 100644
--- a/core/product_config.mk
+++ b/core/product_config.mk
@@ -210,7 +210,6 @@
# Dedup, extract product names, etc.
product_paths := $(sort $(product_paths))
all_named_products := $(sort $(call _first,$(product_paths),:))
-all_product_makefiles := $(sort $(call _second,$(product_paths),:))
current_product_makefile := $(call _second,$(filter $(TARGET_PRODUCT):%,$(product_paths)),:)
COMMON_LUNCH_CHOICES := $(sort $(common_lunch_choices))
@@ -273,8 +272,6 @@
############################################################################
current_product_makefile :=
-all_product_makefiles :=
-all_product_configs :=
#############################################################################
# Quick check and assign default values
diff --git a/core/product_config.rbc b/core/product_config.rbc
index f67ba8e..7a5e501 100644
--- a/core/product_config.rbc
+++ b/core/product_config.rbc
@@ -619,6 +619,12 @@
return ""
return input[l-1]
+def _flatten_2d_list(list):
+ result = []
+ for x in list:
+ result += x
+ return result
+
def _dir(paths):
"""Equivalent to the GNU make function $(dir).
@@ -785,7 +791,7 @@
if t == "list":
s = " ".join(s)
elif t != "string":
- fail("Argument to mkstrip must be a string or list.")
+ fail("Argument to mkstrip must be a string or list, got: "+t)
result = ""
was_space = False
for ch in s.strip().elems():
@@ -879,6 +885,7 @@
findstring = _findstring,
first_word = _first_word,
last_word = _last_word,
+ flatten_2d_list = _flatten_2d_list,
inherit = _inherit,
indirect = _indirect,
mk2rbc_error = _mk2rbc_error,
diff --git a/core/tasks/host-unit-tests.mk b/core/tasks/host-unit-tests.mk
index 4453c29..ed2f2a6 100644
--- a/core/tasks/host-unit-tests.mk
+++ b/core/tasks/host-unit-tests.mk
@@ -39,7 +39,7 @@
echo $$shared_lib >> $@-host-libs.list; \
done
grep $(TARGET_OUT_TESTCASES) $@.list > $@-target.list || true
- $(hide) $(SOONG_ZIP) -L 0 -d -o $@ -P host -C $(HOST_OUT) -l $@-host.list \
+ $(hide) $(SOONG_ZIP) -d -o $@ -P host -C $(HOST_OUT) -l $@-host.list \
-P target -C $(PRODUCT_OUT) -l $@-target.list \
-P host/testcases -C $(HOST_OUT) -l $@-host-libs.list
rm -f $@.list $@-host.list $@-target.list $@-host-libs.list
diff --git a/core/version_defaults.mk b/core/version_defaults.mk
index d129aa4..af7d1c0 100644
--- a/core/version_defaults.mk
+++ b/core/version_defaults.mk
@@ -104,7 +104,7 @@
# It must be of the form "YYYY-MM-DD" on production devices.
# It must match one of the Android Security Patch Level strings of the Public Security Bulletins.
# If there is no $PLATFORM_SECURITY_PATCH set, keep it empty.
- PLATFORM_SECURITY_PATCH := 2022-04-05
+ PLATFORM_SECURITY_PATCH := 2022-05-05
endif
.KATI_READONLY := PLATFORM_SECURITY_PATCH
diff --git a/envsetup.sh b/envsetup.sh
index e7b8538..b079d41 100644
--- a/envsetup.sh
+++ b/envsetup.sh
@@ -395,7 +395,7 @@
fi
local completion_files=(
- system/core/adb/adb.bash
+ packages/modules/adb/adb.bash
system/core/fastboot/fastboot.bash
tools/asuite/asuite.sh
)
@@ -404,7 +404,9 @@
# ENVSETUP_NO_COMPLETION=adb # -> disable adb completion
# ENVSETUP_NO_COMPLETION=adb:bit # -> disable adb and bit completion
for f in ${completion_files[*]}; do
- if [ -f "$f" ] && should_add_completion "$f"; then
+ if [ ! -f "$f" ]; then
+ echo "Warning: completion file $f not found"
+ elif should_add_completion "$f"; then
. $f
fi
done
@@ -454,7 +456,7 @@
if $(echo "$1" | grep -q '^-') ; then
# Calls starting with a -- argument are passed directly and the function
# returns with the lunch.py exit code.
- build/make/orchestrator/core/lunch.py "$@"
+ build/build/make/orchestrator/core/lunch.py "$@"
code=$?
if [[ $code -eq 2 ]] ; then
echo 1>&2
@@ -465,7 +467,7 @@
fi
else
# All other calls go through the --lunch variant of lunch.py
- results=($(build/make/orchestrator/core/lunch.py --lunch "$@"))
+ results=($(build/build/make/orchestrator/core/lunch.py --lunch "$@"))
code=$?
if [[ $code -eq 2 ]] ; then
echo 1>&2
@@ -942,6 +944,34 @@
fi
}
+# TODO: Merge into gettop as part of launching multitree
+function multitree_gettop
+{
+ local TOPFILE=build/build/make/core/envsetup.mk
+ if [ -n "$TOP" -a -f "$TOP/$TOPFILE" ] ; then
+ # The following circumlocution ensures we remove symlinks from TOP.
+ (cd "$TOP"; PWD= /bin/pwd)
+ else
+ if [ -f $TOPFILE ] ; then
+ # The following circumlocution (repeated below as well) ensures
+ # that we record the true directory name and not one that is
+ # faked up with symlink names.
+ PWD= /bin/pwd
+ else
+ local HERE=$PWD
+ local T=
+ while [ \( ! \( -f $TOPFILE \) \) -a \( "$PWD" != "/" \) ]; do
+ \cd ..
+ T=`PWD= /bin/pwd -P`
+ done
+ \cd "$HERE"
+ if [ -f "$T/$TOPFILE" ]; then
+ echo "$T"
+ fi
+ fi
+ fi
+}
+
function croot()
{
local T=$(gettop)
@@ -1824,6 +1854,21 @@
_wrap_build $(get_make_command "$@") "$@"
}
+function _multitree_lunch_error()
+{
+ >&2 echo "Couldn't locate the top of the tree. Please run \'source build/envsetup.sh\' and multitree_lunch from the root of your workspace."
+}
+
+function multitree_build()
+{
+ if T="$(multitree_gettop)"; then
+ "$T/build/build/orchestrator/core/orchestrator.py" "$@"
+ else
+ _multitree_lunch_error
+ return 1
+ fi
+}
+
function provision()
{
if [ ! "$ANDROID_PRODUCT_OUT" ]; then
diff --git a/finalize_branch_for_release.sh b/finalize_branch_for_release.sh
index 8587b3a..c942eb2 100755
--- a/finalize_branch_for_release.sh
+++ b/finalize_branch_for_release.sh
@@ -17,8 +17,7 @@
# Update references in the codebase to new API version (TODO)
# ...
-# Adding -j1 option because of file(Android.bp) race condition.
-AIDL_TRANSITIVE_FREEZE=true m aidl-freeze-api -j1
+AIDL_TRANSITIVE_FREEZE=true m aidl-freeze-api
m check-vndk-list || update-vndk-list.sh # for new versions of AIDL interfaces
diff --git a/orchestrator/README b/orchestrator/README
new file mode 100644
index 0000000..ce6f5c3
--- /dev/null
+++ b/orchestrator/README
@@ -0,0 +1,7 @@
+DEMO
+
+from the root of the workspace
+
+ln -fs ../build/build/orchestrator/inner_build/inner_build_demo.py master/.inner_build
+ln -fs ../build/build/orchestrator/inner_build/inner_build_demo.py sc-mainline-prod/.inner_build
+
diff --git a/orchestrator/core/api_assembly.py b/orchestrator/core/api_assembly.py
new file mode 100644
index 0000000..d87a83d
--- /dev/null
+++ b/orchestrator/core/api_assembly.py
@@ -0,0 +1,151 @@
+#!/usr/bin/python3
+#
+# Copyright (C) 2022 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.
+
+import json
+import os
+
+def assemble_apis(inner_trees):
+
+ # Find all of the contributions from the inner tree
+ contribution_files_dict = inner_trees.for_each_tree(api_contribution_files_for_inner_tree)
+
+ # Load and validate the contribution files
+ # TODO: Check timestamps and skip unnecessary work
+ contributions = []
+ for tree_key, filenames in contribution_files_dict.items():
+ for filename in filenames:
+ contribution_data = load_contribution_file(filename)
+ if not contribution_data:
+ continue
+ # TODO: Validate the configs, especially that the domains match what we asked for
+ # from the lunch config.
+ contributions.append(contribution_data)
+
+ # Group contributions by language and API surface
+ stub_libraries = collate_contributions(contributions)
+
+ # Iterate through all of the stub libraries and generate rules to assemble them
+ # and Android.bp/BUILD files to make those available to inner trees.
+ # TODO: Parallelize? Skip unnecessary work?
+ ninja_file = NinjaFile() # TODO: parameters?
+ build_file = BuildFile() # TODO: parameters?
+ for stub_library in stub_libraries:
+ STUB_LANGUAGE_HANDLERS[stub_library.language](ninja_file, build_file, stub_library)
+
+ # TODO: Handle host_executables separately or as a StubLibrary language?
+
+
+def api_contribution_files_for_inner_tree(tree_key, inner_tree, cookie):
+ "Scan an inner_tree's out dir for the api contribution files."
+ directory = inner_tree.out.api_contributions_dir()
+ result = []
+ with os.scandir(directory) as it:
+ for dirent in it:
+ if not dirent.is_file():
+ break
+ if dirent.name.endswith(".json"):
+ result.append(os.path.join(directory, dirent.name))
+ return result
+
+
+def load_contribution_file(filename):
+ "Load and return the API contribution at filename. On error report error and return None."
+ with open(filename) as f:
+ try:
+ return json.load(f)
+ except json.decoder.JSONDecodeError as ex:
+ # TODO: Error reporting
+ raise ex
+
+
+class StubLibraryContribution(object):
+ def __init__(self, api_domain, library_contribution):
+ self.api_domain = api_domain
+ self.library_contribution = library_contribution
+
+
+class StubLibrary(object):
+ def __init__(self, language, api_surface, api_surface_version, name):
+ self.language = language
+ self.api_surface = api_surface
+ self.api_surface_version = api_surface_version
+ self.name = name
+ self.contributions = []
+
+ def add_contribution(self, contrib):
+ self.contributions.append(contrib)
+
+
+def collate_contributions(contributions):
+ """Take the list of parsed API contribution files, and group targets by API Surface, version,
+ language and library name, and return a StubLibrary object for each of those.
+ """
+ grouped = {}
+ for contribution in contributions:
+ for language in STUB_LANGUAGE_HANDLERS.keys():
+ for library in contribution.get(language, []):
+ key = (language, contribution["name"], contribution["version"], library["name"])
+ stub_library = grouped.get(key)
+ if not stub_library:
+ stub_library = StubLibrary(language, contribution["name"],
+ contribution["version"], library["name"])
+ grouped[key] = stub_library
+ stub_library.add_contribution(StubLibraryContribution(
+ contribution["api_domain"], library))
+ return list(grouped.values())
+
+
+def assemble_cc_api_library(ninja_file, build_file, stub_library):
+ print("assembling cc_api_library %s-%s %s from:" % (stub_library.api_surface, stub_library.api_surface_version,
+ stub_library.name))
+ for contrib in stub_library.contributions:
+ print(" %s %s" % (contrib.api_domain, contrib.library_contribution["api"]))
+ # TODO: Implement me
+
+
+def assemble_java_api_library(ninja_file, build_file, stub_library):
+ print("assembling java_api_library %s-%s %s from:" % (stub_library.api_surface, stub_library.api_surface_version,
+ stub_library.name))
+ for contrib in stub_library.contributions:
+ print(" %s %s" % (contrib.api_domain, contrib.library_contribution["api"]))
+ # TODO: Implement me
+
+
+def assemble_resource_api_library(ninja_file, build_file, stub_library):
+ print("assembling resource_api_library %s-%s %s from:" % (stub_library.api_surface, stub_library.api_surface_version,
+ stub_library.name))
+ for contrib in stub_library.contributions:
+ print(" %s %s" % (contrib.api_domain, contrib.library_contribution["api"]))
+ # TODO: Implement me
+
+
+STUB_LANGUAGE_HANDLERS = {
+ "cc_libraries": assemble_cc_api_library,
+ "java_libraries": assemble_java_api_library,
+ "resource_libraries": assemble_resource_api_library,
+}
+
+
+class NinjaFile(object):
+ "Generator for build actions and dependencies."
+ pass
+
+
+class BuildFile(object):
+ "Abstract generator for Android.bp files and BUILD files."
+ pass
+
+
diff --git a/orchestrator/core/api_domain.py b/orchestrator/core/api_domain.py
new file mode 100644
index 0000000..bb7306c
--- /dev/null
+++ b/orchestrator/core/api_domain.py
@@ -0,0 +1,28 @@
+#!/usr/bin/python3
+#
+# Copyright (C) 2022 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.
+
+class ApiDomain(object):
+ def __init__(self, name, tree, product):
+ # Product will be null for modules
+ self.name = name
+ self.tree = tree
+ self.product = product
+
+ def __str__(self):
+ return "ApiDomain(name=\"%s\" tree.root=\"%s\" product=%s)" % (
+ self.name, self.tree.root,
+ "None" if self.product is None else "\"%s\"" % self.product)
+
diff --git a/orchestrator/core/api_export.py b/orchestrator/core/api_export.py
new file mode 100644
index 0000000..2f26b02
--- /dev/null
+++ b/orchestrator/core/api_export.py
@@ -0,0 +1,20 @@
+#!/usr/bin/python3
+#
+# Copyright (C) 2022 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.
+
+def export_apis_from_tree(tree_key, inner_tree, cookie):
+ inner_tree.invoke(["export_api_contributions"])
+
+
diff --git a/orchestrator/core/inner_tree.py b/orchestrator/core/inner_tree.py
new file mode 100644
index 0000000..cdb0d85
--- /dev/null
+++ b/orchestrator/core/inner_tree.py
@@ -0,0 +1,155 @@
+#!/usr/bin/python3
+#
+# Copyright (C) 2022 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.
+
+import os
+import subprocess
+import sys
+import textwrap
+
+class InnerTreeKey(object):
+ """Trees are identified uniquely by their root and the TARGET_PRODUCT they will use to build.
+ If a single tree uses two different prdoucts, then we won't make assumptions about
+ them sharing _anything_.
+ TODO: This is true for soong. It's more likely that bazel could do analysis for two
+ products at the same time in a single tree, so there's an optimization there to do
+ eventually."""
+ def __init__(self, root, product):
+ self.root = root
+ self.product = product
+
+ def __str__(self):
+ return "TreeKey(root=%s product=%s)" % (enquote(self.root), enquote(self.product))
+
+ def __hash__(self):
+ return hash((self.root, self.product))
+
+ def __eq__(self, other):
+ return (self.root == other.root and self.product == other.product)
+
+ def __ne__(self, other):
+ return not self.__eq__(other)
+
+ def __lt__(self, other):
+ return (self.root, self.product) < (other.root, other.product)
+
+ def __le__(self, other):
+ return (self.root, self.product) <= (other.root, other.product)
+
+ def __gt__(self, other):
+ return (self.root, self.product) > (other.root, other.product)
+
+ def __ge__(self, other):
+ return (self.root, self.product) >= (other.root, other.product)
+
+
+class InnerTree(object):
+ def __init__(self, root, product):
+ """Initialize with the inner tree root (relative to the workspace root)"""
+ self.root = root
+ self.product = product
+ self.domains = {}
+ # TODO: Base directory on OUT_DIR
+ self.out = OutDirLayout(os.path.join("out", "trees", root))
+
+ def __str__(self):
+ return "InnerTree(root=%s product=%s domains=[%s])" % (enquote(self.root),
+ enquote(self.product),
+ " ".join([enquote(d) for d in sorted(self.domains.keys())]))
+
+ def invoke(self, args):
+ """Call the inner tree command for this inner tree. Exits on failure."""
+ # TODO: Build time tracing
+
+ # Validate that there is a .inner_build command to run at the root of the tree
+ # so we can print a good error message
+ inner_build_tool = os.path.join(self.root, ".inner_build")
+ if not os.access(inner_build_tool, os.X_OK):
+ sys.stderr.write(("Unable to execute %s. Is there an inner tree or lunch combo"
+ + " misconfiguration?\n") % inner_build_tool)
+ sys.exit(1)
+
+ # TODO: This is where we should set up the shared trees
+
+ # Build the command
+ cmd = [inner_build_tool, "--out_dir", self.out.root()]
+ for domain_name in sorted(self.domains.keys()):
+ cmd.append("--api_domain")
+ cmd.append(domain_name)
+ cmd += args
+
+ # Run the command
+ process = subprocess.run(cmd, shell=False)
+
+ # TODO: Probably want better handling of inner tree failures
+ if process.returncode:
+ sys.stderr.write("Build error in inner tree: %s\nstopping multitree build.\n"
+ % self.root)
+ sys.exit(1)
+
+
+class InnerTrees(object):
+ def __init__(self, trees, domains):
+ self.trees = trees
+ self.domains = domains
+
+ def __str__(self):
+ "Return a debugging dump of this object"
+ return textwrap.dedent("""\
+ InnerTrees {
+ trees: [
+ %(trees)s
+ ]
+ domains: [
+ %(domains)s
+ ]
+ }""" % {
+ "trees": "\n ".join(sorted([str(t) for t in self.trees.values()])),
+ "domains": "\n ".join(sorted([str(d) for d in self.domains.values()])),
+ })
+
+
+ def for_each_tree(self, func, cookie=None):
+ """Call func for each of the inner trees once for each product that will be built in it.
+
+ The calls will be in a stable order.
+
+ Return a map of the InnerTreeKey to any results returned from func().
+ """
+ result = {}
+ for key in sorted(self.trees.keys()):
+ result[key] = func(key, self.trees[key], cookie)
+ return result
+
+
+class OutDirLayout(object):
+ def __init__(self, root):
+ "Initialize with the root of the OUT_DIR for the inner tree."
+ self._root = root
+
+ def root(self):
+ return self._root
+
+ def tree_info_file(self):
+ return os.path.join(self._root, "tree_info.json")
+
+ def api_contributions_dir(self):
+ return os.path.join(self._root, "api_contributions")
+
+
+def enquote(s):
+ return "None" if s is None else "\"%s\"" % s
+
+
diff --git a/orchestrator/core/interrogate.py b/orchestrator/core/interrogate.py
new file mode 100644
index 0000000..9fe769e
--- /dev/null
+++ b/orchestrator/core/interrogate.py
@@ -0,0 +1,29 @@
+#
+# Copyright (C) 2022 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.
+
+import json
+import os
+
+def interrogate_tree(tree_key, inner_tree, cookie):
+ inner_tree.invoke(["describe"])
+
+ info_json_filename = inner_tree.out.tree_info_file()
+
+ # TODO: Error handling
+ with open(info_json_filename) as f:
+ info_json = json.load(f)
+
+ # TODO: Check orchestrator protocol
+
diff --git a/orchestrator/core/lunch.py b/orchestrator/core/lunch.py
index 35dac73..a648478 100755
--- a/orchestrator/core/lunch.py
+++ b/orchestrator/core/lunch.py
@@ -24,8 +24,10 @@
EXIT_STATUS_ERROR = 1
EXIT_STATUS_NEED_HELP = 2
-def FindDirs(path, name, ttl=6):
- """Search at most ttl directories deep inside path for a directory called name."""
+
+def find_dirs(path, name, ttl=6):
+ """Search at most ttl directories deep inside path for a directory called name
+ and yield directories that match."""
# The dance with subdirs is so that we recurse in sorted order.
subdirs = []
with os.scandir(path) as it:
@@ -40,10 +42,10 @@
# Consume filesystem errors, e.g. too many links, permission etc.
pass
for subdir in subdirs:
- yield from FindDirs(os.path.join(path, subdir), name, ttl-1)
+ yield from find_dirs(os.path.join(path, subdir), name, ttl-1)
-def WalkPaths(path, matcher, ttl=10):
+def walk_paths(path, matcher, ttl=10):
"""Do a traversal of all files under path yielding each file that matches
matcher."""
# First look for files, then recurse into directories as needed.
@@ -62,22 +64,22 @@
# Consume filesystem errors, e.g. too many links, permission etc.
pass
for subdir in sorted(subdirs):
- yield from WalkPaths(os.path.join(path, subdir), matcher, ttl-1)
+ yield from walk_paths(os.path.join(path, subdir), matcher, ttl-1)
-def FindFile(path, filename):
+def find_file(path, filename):
"""Return a file called filename inside path, no more than ttl levels deep.
Directories are searched alphabetically.
"""
- for f in WalkPaths(path, lambda x: x == filename):
+ for f in walk_paths(path, lambda x: x == filename):
return f
-def FindConfigDirs(workspace_root):
+def find_config_dirs(workspace_root):
"""Find the configuration files in the well known locations inside workspace_root
- <workspace_root>/build/orchestrator/multitree_combos
+ <workspace_root>/build/build/orchestrator/multitree_combos
(AOSP devices, such as cuttlefish)
<workspace_root>/vendor/**/multitree_combos
@@ -89,29 +91,30 @@
Directories are returned specifically in this order, so that aosp can't be
overridden, but vendor overrides device.
"""
+ # TODO: This is not looking in inner trees correctly.
# TODO: When orchestrator is in its own git project remove the "make/" here
- yield os.path.join(workspace_root, "build/make/orchestrator/multitree_combos")
+ yield os.path.join(workspace_root, "build/build/make/orchestrator/multitree_combos")
dirs = ["vendor", "device"]
for d in dirs:
- yield from FindDirs(os.path.join(workspace_root, d), "multitree_combos")
+ yield from find_dirs(os.path.join(workspace_root, d), "multitree_combos")
-def FindNamedConfig(workspace_root, shortname):
+def find_named_config(workspace_root, shortname):
"""Find the config with the given shortname inside workspace_root.
- Config directories are searched in the order described in FindConfigDirs,
+ Config directories are searched in the order described in find_config_dirs,
and inside those directories, alphabetically."""
filename = shortname + ".mcombo"
- for config_dir in FindConfigDirs(workspace_root):
- found = FindFile(config_dir, filename)
+ for config_dir in find_config_dirs(workspace_root):
+ found = find_file(config_dir, filename)
if found:
return found
return None
-def ParseProductVariant(s):
+def parse_product_variant(s):
"""Split a PRODUCT-VARIANT name, or return None if it doesn't match that pattern."""
split = s.split("-")
if len(split) != 2:
@@ -119,15 +122,15 @@
return split
-def ChooseConfigFromArgs(workspace_root, args):
+def choose_config_from_args(workspace_root, args):
"""Return the config file we should use for the given argument,
or null if there's no file that matches that."""
if len(args) == 1:
# Prefer PRODUCT-VARIANT syntax so if there happens to be a matching
# file we don't match that.
- pv = ParseProductVariant(args[0])
+ pv = parse_product_variant(args[0])
if pv:
- config = FindNamedConfig(workspace_root, pv[0])
+ config = find_named_config(workspace_root, pv[0])
if config:
return (config, pv[1])
return None, None
@@ -139,10 +142,12 @@
class ConfigException(Exception):
+ ERROR_IDENTIFY = "identify"
ERROR_PARSE = "parse"
ERROR_CYCLE = "cycle"
+ ERROR_VALIDATE = "validate"
- def __init__(self, kind, message, locations, line=0):
+ def __init__(self, kind, message, locations=[], line=0):
"""Error thrown when loading and parsing configurations.
Args:
@@ -169,13 +174,13 @@
self.line = line
-def LoadConfig(filename):
+def load_config(filename):
"""Load a config, including processing the inherits fields.
Raises:
ConfigException on errors
"""
- def LoadAndMerge(fn, visited):
+ def load_and_merge(fn, visited):
with open(fn) as f:
try:
contents = json.load(f)
@@ -191,34 +196,74 @@
if parent in visited:
raise ConfigException(ConfigException.ERROR_CYCLE, "Cycle detected in inherits",
visited)
- DeepMerge(inherited_data, LoadAndMerge(parent, [parent,] + visited))
+ deep_merge(inherited_data, load_and_merge(parent, [parent,] + visited))
# Then merge inherited_data into contents, but what's already there will win.
- DeepMerge(contents, inherited_data)
+ deep_merge(contents, inherited_data)
contents.pop("inherits", None)
return contents
- return LoadAndMerge(filename, [filename,])
+ return load_and_merge(filename, [filename,])
-def DeepMerge(merged, addition):
+def deep_merge(merged, addition):
"""Merge all fields of addition into merged. Pre-existing fields win."""
for k, v in addition.items():
if k in merged:
if isinstance(v, dict) and isinstance(merged[k], dict):
- DeepMerge(merged[k], v)
+ deep_merge(merged[k], v)
else:
merged[k] = v
-def Lunch(args):
+def make_config_header(config_file, config, variant):
+ def make_table(rows):
+ maxcols = max([len(row) for row in rows])
+ widths = [0] * maxcols
+ for row in rows:
+ for i in range(len(row)):
+ widths[i] = max(widths[i], len(row[i]))
+ text = []
+ for row in rows:
+ rowtext = []
+ for i in range(len(row)):
+ cell = row[i]
+ rowtext.append(str(cell))
+ rowtext.append(" " * (widths[i] - len(cell)))
+ rowtext.append(" ")
+ text.append("".join(rowtext))
+ return "\n".join(text)
+
+ trees = [("Component", "Path", "Product"),
+ ("---------", "----", "-------")]
+ entry = config.get("system", None)
+ def add_config_tuple(trees, entry, name):
+ if entry:
+ trees.append((name, entry.get("tree"), entry.get("product", "")))
+ add_config_tuple(trees, config.get("system"), "system")
+ add_config_tuple(trees, config.get("vendor"), "vendor")
+ for k, v in config.get("modules", {}).items():
+ add_config_tuple(trees, v, k)
+
+ return """========================================
+TARGET_BUILD_COMBO=%(TARGET_BUILD_COMBO)s
+TARGET_BUILD_VARIANT=%(TARGET_BUILD_VARIANT)s
+
+%(trees)s
+========================================\n""" % {
+ "TARGET_BUILD_COMBO": config_file,
+ "TARGET_BUILD_VARIANT": variant,
+ "trees": make_table(trees),
+ }
+
+
+def do_lunch(args):
"""Handle the lunch command."""
- # Check that we're at the top of a multitree workspace
- # TODO: Choose the right sentinel file
- if not os.path.exists("build/make/orchestrator"):
+ # Check that we're at the top of a multitree workspace by seeing if this script exists.
+ if not os.path.exists("build/build/make/orchestrator/core/lunch.py"):
sys.stderr.write("ERROR: lunch.py must be run from the root of a multi-tree workspace\n")
return EXIT_STATUS_ERROR
# Choose the config file
- config_file, variant = ChooseConfigFromArgs(".", args)
+ config_file, variant = choose_config_from_args(".", args)
if config_file == None:
sys.stderr.write("Can't find lunch combo file for: %s\n" % " ".join(args))
@@ -229,7 +274,7 @@
# Parse the config file
try:
- config = LoadConfig(config_file)
+ config = load_config(config_file)
except ConfigException as ex:
sys.stderr.write(str(ex))
return EXIT_STATUS_ERROR
@@ -244,47 +289,81 @@
sys.stdout.write("%s\n" % config_file)
sys.stdout.write("%s\n" % variant)
+ # Write confirmation message to stderr
+ sys.stderr.write(make_config_header(config_file, config, variant))
+
return EXIT_STATUS_OK
-def FindAllComboFiles(workspace_root):
+def find_all_combo_files(workspace_root):
"""Find all .mcombo files in the prescribed locations in the tree."""
- for dir in FindConfigDirs(workspace_root):
- for file in WalkPaths(dir, lambda x: x.endswith(".mcombo")):
+ for dir in find_config_dirs(workspace_root):
+ for file in walk_paths(dir, lambda x: x.endswith(".mcombo")):
yield file
-def IsFileLunchable(config_file):
+def is_file_lunchable(config_file):
"""Parse config_file, flatten the inheritance, and return whether it can be
used as a lunch target."""
try:
- config = LoadConfig(config_file)
+ config = load_config(config_file)
except ConfigException as ex:
sys.stderr.write("%s" % ex)
return False
return config.get("lunchable", False)
-def FindAllLunchable(workspace_root):
+def find_all_lunchable(workspace_root):
"""Find all mcombo files in the tree (rooted at workspace_root) that when
parsed (and inheritance is flattened) have lunchable: true."""
- for f in [x for x in FindAllComboFiles(workspace_root) if IsFileLunchable(x)]:
+ for f in [x for x in find_all_combo_files(workspace_root) if is_file_lunchable(x)]:
yield f
-def List():
+def load_current_config():
+ """Load, validate and return the config as specified in TARGET_BUILD_COMBO. Throws
+ ConfigException if there is a problem."""
+
+ # Identify the config file
+ config_file = os.environ.get("TARGET_BUILD_COMBO")
+ if not config_file:
+ raise ConfigException(ConfigException.ERROR_IDENTIFY,
+ "TARGET_BUILD_COMBO not set. Run lunch or pass a combo file.")
+
+ # Parse the config file
+ config = load_config(config_file)
+
+ # Validate the config file
+ if not config.get("lunchable", False):
+ raise ConfigException(ConfigException.ERROR_VALIDATE,
+ "Lunch config file (or inherited files) does not have the 'lunchable'"
+ + " flag set, which means it is probably not a complete lunch spec.",
+ [config_file,])
+
+ # TODO: Validate that:
+ # - there are no modules called system or vendor
+ # - everything has all the required files
+
+ variant = os.environ.get("TARGET_BUILD_VARIANT")
+ if not variant:
+ variant = "eng" # TODO: Is this the right default?
+ # Validate variant is user, userdebug or eng
+
+ return config_file, config, variant
+
+def do_list():
"""Handle the --list command."""
- for f in sorted(FindAllLunchable(".")):
+ for f in sorted(find_all_lunchable(".")):
print(f)
-def Print(args):
+def do_print(args):
"""Handle the --print command."""
# Parse args
if len(args) == 0:
config_file = os.environ.get("TARGET_BUILD_COMBO")
if not config_file:
- sys.stderr.write("TARGET_BUILD_COMBO not set. Run lunch or pass a combo file.\n")
+ sys.stderr.write("TARGET_BUILD_COMBO not set. Run lunch before building.\n")
return EXIT_STATUS_NEED_HELP
elif len(args) == 1:
config_file = args[0]
@@ -293,7 +372,7 @@
# Parse the config file
try:
- config = LoadConfig(config_file)
+ config = load_config(config_file)
except ConfigException as ex:
sys.stderr.write(str(ex))
return EXIT_STATUS_ERROR
@@ -309,15 +388,15 @@
return EXIT_STATUS_NEED_HELP
if len(argv) == 2 and argv[1] == "--list":
- List()
+ do_list()
return EXIT_STATUS_OK
if len(argv) == 2 and argv[1] == "--print":
- return Print(argv[2:])
+ return do_print(argv[2:])
return EXIT_STATUS_OK
- if (len(argv) == 2 or len(argv) == 3) and argv[1] == "--lunch":
- return Lunch(argv[2:])
+ if (len(argv) == 3 or len(argv) == 4) and argv[1] == "--lunch":
+ return do_lunch(argv[2:])
sys.stderr.write("Unknown lunch command: %s\n" % " ".join(argv[1:]))
return EXIT_STATUS_NEED_HELP
diff --git a/orchestrator/core/orchestrator.py b/orchestrator/core/orchestrator.py
new file mode 100755
index 0000000..e99c956
--- /dev/null
+++ b/orchestrator/core/orchestrator.py
@@ -0,0 +1,123 @@
+#!/usr/bin/python3
+#
+# Copyright (C) 2022 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.
+
+import os
+import subprocess
+import sys
+
+sys.dont_write_bytecode = True
+import api_assembly
+import api_domain
+import api_export
+import inner_tree
+import interrogate
+import lunch
+
+EXIT_STATUS_OK = 0
+EXIT_STATUS_ERROR = 1
+
+API_DOMAIN_SYSTEM = "system"
+API_DOMAIN_VENDOR = "vendor"
+API_DOMAIN_MODULE = "module"
+
+def process_config(lunch_config):
+ """Returns a InnerTrees object based on the configuration requested in the lunch config."""
+ def add(domain_name, tree_root, product):
+ tree_key = inner_tree.InnerTreeKey(tree_root, product)
+ if tree_key in trees:
+ tree = trees[tree_key]
+ else:
+ tree = inner_tree.InnerTree(tree_root, product)
+ trees[tree_key] = tree
+ domain = api_domain.ApiDomain(domain_name, tree, product)
+ domains[domain_name] = domain
+ tree.domains[domain_name] = domain
+
+ trees = {}
+ domains = {}
+
+ system_entry = lunch_config.get("system")
+ if system_entry:
+ add(API_DOMAIN_SYSTEM, system_entry["tree"], system_entry["product"])
+
+ vendor_entry = lunch_config.get("vendor")
+ if vendor_entry:
+ add(API_DOMAIN_VENDOR, vendor_entry["tree"], vendor_entry["product"])
+
+ for module_name, module_entry in lunch_config.get("modules", []).items():
+ add(module_name, module_entry["tree"], None)
+
+ return inner_tree.InnerTrees(trees, domains)
+
+
+def build():
+ #
+ # Load lunch combo
+ #
+
+ # Read the config file
+ try:
+ config_file, config, variant = lunch.load_current_config()
+ except lunch.ConfigException as ex:
+ sys.stderr.write("%s\n" % ex)
+ return EXIT_STATUS_ERROR
+ sys.stdout.write(lunch.make_config_header(config_file, config, variant))
+
+ # Construct the trees and domains dicts
+ inner_trees = process_config(config)
+
+ #
+ # 1. Interrogate the trees
+ #
+ inner_trees.for_each_tree(interrogate.interrogate_tree)
+ # TODO: Detect bazel-only mode
+
+ #
+ # 2a. API Export
+ #
+ inner_trees.for_each_tree(api_export.export_apis_from_tree)
+
+ #
+ # 2b. API Surface Assembly
+ #
+ api_assembly.assemble_apis(inner_trees)
+
+ #
+ # 3a. API Domain Analysis
+ #
+
+ #
+ # 3b. Final Packaging Rules
+ #
+
+ #
+ # 4. Build Execution
+ #
+
+
+ #
+ # Success!
+ #
+ return EXIT_STATUS_OK
+
+def main(argv):
+ return build()
+
+if __name__ == "__main__":
+ sys.exit(main(sys.argv))
+
+
+# vim: sts=4:ts=4:sw=4
diff --git a/orchestrator/core/test_lunch.py b/orchestrator/core/test_lunch.py
index 3c39493..2d85d05 100755
--- a/orchestrator/core/test_lunch.py
+++ b/orchestrator/core/test_lunch.py
@@ -23,73 +23,73 @@
class TestStringMethods(unittest.TestCase):
def test_find_dirs(self):
- self.assertEqual([x for x in lunch.FindDirs("test/configs", "multitree_combos")], [
+ self.assertEqual([x for x in lunch.find_dirs("test/configs", "multitree_combos")], [
"test/configs/build/make/orchestrator/multitree_combos",
"test/configs/device/aa/bb/multitree_combos",
"test/configs/vendor/aa/bb/multitree_combos"])
def test_find_file(self):
# Finds the one in device first because this is searching from the root,
- # not using FindNamedConfig.
- self.assertEqual(lunch.FindFile("test/configs", "v.mcombo"),
+ # not using find_named_config.
+ self.assertEqual(lunch.find_file("test/configs", "v.mcombo"),
"test/configs/device/aa/bb/multitree_combos/v.mcombo")
def test_find_config_dirs(self):
- self.assertEqual([x for x in lunch.FindConfigDirs("test/configs")], [
+ self.assertEqual([x for x in lunch.find_config_dirs("test/configs")], [
"test/configs/build/make/orchestrator/multitree_combos",
"test/configs/vendor/aa/bb/multitree_combos",
"test/configs/device/aa/bb/multitree_combos"])
def test_find_named_config(self):
# Inside build/orchestrator, overriding device and vendor
- self.assertEqual(lunch.FindNamedConfig("test/configs", "b"),
+ self.assertEqual(lunch.find_named_config("test/configs", "b"),
"test/configs/build/make/orchestrator/multitree_combos/b.mcombo")
# Nested dir inside a combo dir
- self.assertEqual(lunch.FindNamedConfig("test/configs", "nested"),
+ self.assertEqual(lunch.find_named_config("test/configs", "nested"),
"test/configs/build/make/orchestrator/multitree_combos/nested/nested.mcombo")
# Inside vendor, overriding device
- self.assertEqual(lunch.FindNamedConfig("test/configs", "v"),
+ self.assertEqual(lunch.find_named_config("test/configs", "v"),
"test/configs/vendor/aa/bb/multitree_combos/v.mcombo")
# Inside device
- self.assertEqual(lunch.FindNamedConfig("test/configs", "d"),
+ self.assertEqual(lunch.find_named_config("test/configs", "d"),
"test/configs/device/aa/bb/multitree_combos/d.mcombo")
# Make sure we don't look too deep (for performance)
- self.assertIsNone(lunch.FindNamedConfig("test/configs", "too_deep"))
+ self.assertIsNone(lunch.find_named_config("test/configs", "too_deep"))
def test_choose_config_file(self):
# Empty string argument
- self.assertEqual(lunch.ChooseConfigFromArgs("test/configs", [""]),
+ self.assertEqual(lunch.choose_config_from_args("test/configs", [""]),
(None, None))
# A PRODUCT-VARIANT name
- self.assertEqual(lunch.ChooseConfigFromArgs("test/configs", ["v-eng"]),
+ self.assertEqual(lunch.choose_config_from_args("test/configs", ["v-eng"]),
("test/configs/vendor/aa/bb/multitree_combos/v.mcombo", "eng"))
# A PRODUCT-VARIANT name that conflicts with a file
- self.assertEqual(lunch.ChooseConfigFromArgs("test/configs", ["b-eng"]),
+ self.assertEqual(lunch.choose_config_from_args("test/configs", ["b-eng"]),
("test/configs/build/make/orchestrator/multitree_combos/b.mcombo", "eng"))
# A PRODUCT-VARIANT that doesn't exist
- self.assertEqual(lunch.ChooseConfigFromArgs("test/configs", ["z-user"]),
+ self.assertEqual(lunch.choose_config_from_args("test/configs", ["z-user"]),
(None, None))
# An explicit file
- self.assertEqual(lunch.ChooseConfigFromArgs("test/configs",
+ self.assertEqual(lunch.choose_config_from_args("test/configs",
["test/configs/build/make/orchestrator/multitree_combos/b.mcombo", "eng"]),
("test/configs/build/make/orchestrator/multitree_combos/b.mcombo", "eng"))
# An explicit file that doesn't exist
- self.assertEqual(lunch.ChooseConfigFromArgs("test/configs",
+ self.assertEqual(lunch.choose_config_from_args("test/configs",
["test/configs/doesnt_exist.mcombo", "eng"]),
(None, None))
# An explicit file without a variant should fail
- self.assertEqual(lunch.ChooseConfigFromArgs("test/configs",
+ self.assertEqual(lunch.choose_config_from_args("test/configs",
["test/configs/build/make/orchestrator/multitree_combos/b.mcombo"]),
("test/configs/build/make/orchestrator/multitree_combos/b.mcombo", None))
@@ -97,12 +97,12 @@
def test_config_cycles(self):
# Test that we catch cycles
with self.assertRaises(lunch.ConfigException) as context:
- lunch.LoadConfig("test/configs/parsing/cycles/1.mcombo")
+ lunch.load_config("test/configs/parsing/cycles/1.mcombo")
self.assertEqual(context.exception.kind, lunch.ConfigException.ERROR_CYCLE)
def test_config_merge(self):
# Test the merge logic
- self.assertEqual(lunch.LoadConfig("test/configs/parsing/merge/1.mcombo"), {
+ self.assertEqual(lunch.load_config("test/configs/parsing/merge/1.mcombo"), {
"in_1": "1",
"in_1_2": "1",
"merged": {"merged_1": "1",
@@ -119,7 +119,7 @@
})
def test_list(self):
- self.assertEqual(sorted(lunch.FindAllLunchable("test/configs")),
+ self.assertEqual(sorted(lunch.find_all_lunchable("test/configs")),
["test/configs/build/make/orchestrator/multitree_combos/b.mcombo"])
if __name__ == "__main__":
diff --git a/orchestrator/inner_build/common.py b/orchestrator/inner_build/common.py
new file mode 100644
index 0000000..6919e04
--- /dev/null
+++ b/orchestrator/inner_build/common.py
@@ -0,0 +1,56 @@
+#!/usr/bin/python3
+#
+# Copyright (C) 2022 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.
+
+import argparse
+import sys
+
+def _parse_arguments(argv):
+ argv = argv[1:]
+ """Return an argparse options object."""
+ # Top-level parser
+ parser = argparse.ArgumentParser(prog=".inner_build")
+
+ parser.add_argument("--out_dir", action="store", required=True,
+ help="root of the output directory for this inner tree's API contributions")
+
+ parser.add_argument("--api_domain", action="append", required=True,
+ help="which API domains are to be built in this inner tree")
+
+ subparsers = parser.add_subparsers(required=True, dest="command",
+ help="subcommands")
+
+ # inner_build describe command
+ describe_parser = subparsers.add_parser("describe",
+ help="describe the capabilities of this inner tree's build system")
+
+ # create the parser for the "b" command
+ export_parser = subparsers.add_parser("export_api_contributions",
+ help="export the API contributions of this inner tree")
+
+ # Parse the arguments
+ return parser.parse_args(argv)
+
+
+class Commands(object):
+ def Run(self, argv):
+ """Parse the command arguments and call the corresponding subcommand method on
+ this object.
+
+ Throws AttributeError if the method for the command wasn't found.
+ """
+ args = _parse_arguments(argv)
+ return getattr(self, args.command)(args)
+
diff --git a/orchestrator/inner_build/inner_build_demo.py b/orchestrator/inner_build/inner_build_demo.py
new file mode 100755
index 0000000..9aafb4d
--- /dev/null
+++ b/orchestrator/inner_build/inner_build_demo.py
@@ -0,0 +1,143 @@
+#!/usr/bin/python3
+#
+# Copyright (C) 2022 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.
+
+import os
+import sys
+import textwrap
+
+sys.dont_write_bytecode = True
+import common
+
+def mkdirs(path):
+ try:
+ os.makedirs(path)
+ except FileExistsError:
+ pass
+
+
+class InnerBuildSoong(common.Commands):
+ def describe(self, args):
+ mkdirs(args.out_dir)
+
+ with open(os.path.join(args.out_dir, "tree_info.json"), "w") as f:
+ f.write(textwrap.dedent("""\
+ {
+ "requires_ninja": true,
+ "orchestrator_protocol_version": 1
+ }"""))
+
+ def export_api_contributions(self, args):
+ contributions_dir = os.path.join(args.out_dir, "api_contributions")
+ mkdirs(contributions_dir)
+
+ if "system" in args.api_domain:
+ with open(os.path.join(contributions_dir, "public_api-1.json"), "w") as f:
+ # 'name: android' is android.jar
+ f.write(textwrap.dedent("""\
+ {
+ "name": "public_api",
+ "version": 1,
+ "api_domain": "system",
+ "cc_libraries": [
+ {
+ "name": "libhwui",
+ "headers": [
+ {
+ "root": "frameworks/base/libs/hwui/apex/include",
+ "files": [
+ "android/graphics/jni_runtime.h",
+ "android/graphics/paint.h",
+ "android/graphics/matrix.h",
+ "android/graphics/canvas.h",
+ "android/graphics/renderthread.h",
+ "android/graphics/bitmap.h",
+ "android/graphics/region.h"
+ ]
+ }
+ ],
+ "api": [
+ "frameworks/base/libs/hwui/libhwui.map.txt"
+ ]
+ }
+ ],
+ "java_libraries": [
+ {
+ "name": "android",
+ "api": [
+ "frameworks/base/core/api/current.txt"
+ ]
+ }
+ ],
+ "resource_libraries": [
+ {
+ "name": "android",
+ "api": "frameworks/base/core/res/res/values/public.xml"
+ }
+ ],
+ "host_executables": [
+ {
+ "name": "aapt2",
+ "binary": "out/host/bin/aapt2",
+ "runfiles": [
+ "../lib/todo.so"
+ ]
+ }
+ ]
+ }"""))
+ elif "com.android.bionic" in args.api_domain:
+ with open(os.path.join(contributions_dir, "public_api-1.json"), "w") as f:
+ # 'name: android' is android.jar
+ f.write(textwrap.dedent("""\
+ {
+ "name": "public_api",
+ "version": 1,
+ "api_domain": "system",
+ "cc_libraries": [
+ {
+ "name": "libc",
+ "headers": [
+ {
+ "root": "bionic/libc/include",
+ "files": [
+ "stdio.h",
+ "sys/klog.h"
+ ]
+ }
+ ],
+ "api": "bionic/libc/libc.map.txt"
+ }
+ ],
+ "java_libraries": [
+ {
+ "name": "android",
+ "api": [
+ "frameworks/base/libs/hwui/api/current.txt"
+ ]
+ }
+ ]
+ }"""))
+
+
+
+def main(argv):
+ return InnerBuildSoong().Run(argv)
+
+
+if __name__ == "__main__":
+ sys.exit(main(sys.argv))
+
+
+# vim: sts=4:ts=4:sw=4
diff --git a/orchestrator/inner_build/inner_build_soong.py b/orchestrator/inner_build/inner_build_soong.py
new file mode 100755
index 0000000..a653dcc
--- /dev/null
+++ b/orchestrator/inner_build/inner_build_soong.py
@@ -0,0 +1,37 @@
+#!/usr/bin/python3
+#
+# Copyright (C) 2022 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.
+
+import argparse
+import sys
+
+sys.dont_write_bytecode = True
+import common
+
+class InnerBuildSoong(common.Commands):
+ def describe(self, args):
+ pass
+
+
+ def export_api_contributions(self, args):
+ pass
+
+
+def main(argv):
+ return InnerBuildSoong().Run(argv)
+
+
+if __name__ == "__main__":
+ sys.exit(main(sys.argv))
diff --git a/orchestrator/multitree_combos/aosp_cf_arm64_phone.mcombo b/orchestrator/multitree_combos/aosp_cf_arm64_phone.mcombo
new file mode 100644
index 0000000..0790226
--- /dev/null
+++ b/orchestrator/multitree_combos/aosp_cf_arm64_phone.mcombo
@@ -0,0 +1,16 @@
+{
+ "lunchable": true,
+ "system": {
+ "tree": "master",
+ "product": "aosp_cf_arm64_phone"
+ },
+ "vendor": {
+ "tree": "master",
+ "product": "aosp_cf_arm64_phone"
+ },
+ "modules": {
+ "com.android.bionic": {
+ "tree": "sc-mainline-prod"
+ }
+ }
+}
diff --git a/orchestrator/ninja/ninja_syntax.py b/orchestrator/ninja/ninja_syntax.py
new file mode 100644
index 0000000..328c99c
--- /dev/null
+++ b/orchestrator/ninja/ninja_syntax.py
@@ -0,0 +1,172 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2022 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.
+
+from abc import ABC, abstractmethod
+
+from collections.abc import Iterator
+from typing import List
+
+TAB = " "
+
+class Node(ABC):
+ '''An abstract class that can be serialized to a ninja file
+ All other ninja-serializable classes inherit from this class'''
+
+ @abstractmethod
+ def stream(self) -> Iterator[str]:
+ pass
+
+class Variable(Node):
+ '''A ninja variable that can be reused across build actions
+ https://ninja-build.org/manual.html#_variables'''
+
+ def __init__(self, name:str, value:str, indent=0):
+ self.name = name
+ self.value = value
+ self.indent = indent
+
+ def stream(self) -> Iterator[str]:
+ indent = TAB * self.indent
+ yield f"{indent}{self.name} = {self.value}"
+
+class RuleException(Exception):
+ pass
+
+# Ninja rules recognize a limited set of variables
+# https://ninja-build.org/manual.html#ref_rule
+# Keep this list sorted
+RULE_VARIABLES = ["command",
+ "depfile",
+ "deps",
+ "description",
+ "dyndep",
+ "generator",
+ "msvc_deps_prefix",
+ "restat",
+ "rspfile",
+ "rspfile_content"]
+
+class Rule(Node):
+ '''A shorthand for a command line that can be reused
+ https://ninja-build.org/manual.html#_rules'''
+
+ def __init__(self, name:str):
+ self.name = name
+ self.variables = []
+
+ def add_variable(self, name: str, value: str):
+ if name not in RULE_VARIABLES:
+ raise RuleException(f"{name} is not a recognized variable in a ninja rule")
+
+ self.variables.append(Variable(name=name, value=value, indent=1))
+
+ def stream(self) -> Iterator[str]:
+ self._validate_rule()
+
+ yield f"rule {self.name}"
+ # Yield rule variables sorted by `name`
+ for var in sorted(self.variables, key=lambda x: x.name):
+ # variables yield a single item, next() is sufficient
+ yield next(var.stream())
+
+ def _validate_rule(self):
+ # command is a required variable in a ninja rule
+ self._assert_variable_is_not_empty(variable_name="command")
+
+ def _assert_variable_is_not_empty(self, variable_name: str):
+ if not any(var.name == variable_name for var in self.variables):
+ raise RuleException(f"{variable_name} is required in a ninja rule")
+
+class BuildActionException(Exception):
+ pass
+
+class BuildAction(Node):
+ '''Describes the dependency edge between inputs and output
+ https://ninja-build.org/manual.html#_build_statements'''
+
+ def __init__(self, output: str, rule: str, inputs: List[str]=None, implicits: List[str]=None, order_only: List[str]=None):
+ self.output = output
+ self.rule = rule
+ self.inputs = self._as_list(inputs)
+ self.implicits = self._as_list(implicits)
+ self.order_only = self._as_list(order_only)
+ self.variables = []
+
+ def add_variable(self, name: str, value: str):
+ '''Variables limited to the scope of this build action'''
+ self.variables.append(Variable(name=name, value=value, indent=1))
+
+ def stream(self) -> Iterator[str]:
+ self._validate()
+
+ build_statement = f"build {self.output}: {self.rule}"
+ if len(self.inputs) > 0:
+ build_statement += " "
+ build_statement += " ".join(self.inputs)
+ if len(self.implicits) > 0:
+ build_statement += " | "
+ build_statement += " ".join(self.implicits)
+ if len(self.order_only) > 0:
+ build_statement += " || "
+ build_statement += " ".join(self.order_only)
+ yield build_statement
+ # Yield variables sorted by `name`
+ for var in sorted(self.variables, key=lambda x: x.name):
+ # variables yield a single item, next() is sufficient
+ yield next(var.stream())
+
+ def _validate(self):
+ if not self.output:
+ raise BuildActionException("Output is required in a ninja build statement")
+ if not self.rule:
+ raise BuildActionException("Rule is required in a ninja build statement")
+
+ def _as_list(self, list_like):
+ if list_like is None:
+ return []
+ if isinstance(list_like, list):
+ return list_like
+ return [list_like]
+
+class Pool(Node):
+ '''https://ninja-build.org/manual.html#ref_pool'''
+
+ def __init__(self, name: str, depth: int):
+ self.name = name
+ self.depth = Variable(name="depth", value=depth, indent=1)
+
+ def stream(self) -> Iterator[str]:
+ yield f"pool {self.name}"
+ yield next(self.depth.stream())
+
+class Subninja(Node):
+
+ def __init__(self, subninja: str, chDir: str):
+ self.subninja = subninja
+ self.chDir = chDir
+
+ # TODO(spandandas): Update the syntax when aosp/2064612 lands
+ def stream() -> Iterator[str]:
+ yield f"subninja {self.subninja}"
+
+class Line(Node):
+ '''Generic class that can be used for comments/newlines/default_target etc'''
+
+ def __init__(self, value:str):
+ self.value = value
+
+ def stream(self) -> Iterator[str]:
+ yield self.value
diff --git a/orchestrator/ninja/ninja_writer.py b/orchestrator/ninja/ninja_writer.py
new file mode 100644
index 0000000..e3070bb
--- /dev/null
+++ b/orchestrator/ninja/ninja_writer.py
@@ -0,0 +1,55 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2022 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.
+
+from ninja_syntax import Variable, BuildAction, Rule, Pool, Subninja, Line
+
+# TODO: Format the output according to a configurable width variable
+# This will ensure that the generated content fits on a screen and does not
+# require horizontal scrolling
+class Writer:
+
+ def __init__(self, file):
+ self.file = file
+ self.nodes = [] # type Node
+
+ def add_variable(self, variable: Variable):
+ self.nodes.append(variable)
+
+ def add_rule(self, rule: Rule):
+ self.nodes.append(rule)
+
+ def add_build_action(self, build_action: BuildAction):
+ self.nodes.append(build_action)
+
+ def add_pool(self, pool: Pool):
+ self.nodes.append(pool)
+
+ def add_comment(self, comment: str):
+ self.nodes.append(Line(value=f"# {comment}"))
+
+ def add_default(self, default: str):
+ self.nodes.append(Line(value=f"default {default}"))
+
+ def add_newline(self):
+ self.nodes.append(Line(value=""))
+
+ def add_subninja(self, subninja: Subninja):
+ self.nodes.append(subninja)
+
+ def write(self):
+ for node in self.nodes:
+ for line in node.stream():
+ print(line, file=self.file)
diff --git a/orchestrator/ninja/test_ninja_syntax.py b/orchestrator/ninja/test_ninja_syntax.py
new file mode 100644
index 0000000..d922fd2
--- /dev/null
+++ b/orchestrator/ninja/test_ninja_syntax.py
@@ -0,0 +1,107 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2022 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.
+
+import unittest
+
+from ninja_syntax import Variable, Rule, RuleException, BuildAction, BuildActionException, Pool
+
+class TestVariable(unittest.TestCase):
+
+ def test_assignment(self):
+ variable = Variable(name="key", value="value")
+ self.assertEqual("key = value", next(variable.stream()))
+ variable = Variable(name="key", value="value with spaces")
+ self.assertEqual("key = value with spaces", next(variable.stream()))
+ variable = Variable(name="key", value="$some_other_variable")
+ self.assertEqual("key = $some_other_variable", next(variable.stream()))
+
+ def test_indentation(self):
+ variable = Variable(name="key", value="value", indent=0)
+ self.assertEqual("key = value", next(variable.stream()))
+ variable = Variable(name="key", value="value", indent=1)
+ self.assertEqual(" key = value", next(variable.stream()))
+
+class TestRule(unittest.TestCase):
+
+ def test_rulename_comes_first(self):
+ rule = Rule(name="myrule")
+ rule.add_variable("command", "/bin/bash echo")
+ self.assertEqual("rule myrule", next(rule.stream()))
+
+ def test_command_is_a_required_variable(self):
+ rule = Rule(name="myrule")
+ with self.assertRaises(RuleException):
+ next(rule.stream())
+
+ def test_bad_rule_variable(self):
+ rule = Rule(name="myrule")
+ with self.assertRaises(RuleException):
+ rule.add_variable(name="unrecognize_rule_variable", value="value")
+
+ def test_rule_variables_are_indented(self):
+ rule = Rule(name="myrule")
+ rule.add_variable("command", "/bin/bash echo")
+ stream = rule.stream()
+ self.assertEqual("rule myrule", next(stream)) # top-level rule should not be indented
+ self.assertEqual(" command = /bin/bash echo", next(stream))
+
+ def test_rule_variables_are_sorted(self):
+ rule = Rule(name="myrule")
+ rule.add_variable("description", "Adding description before command")
+ rule.add_variable("command", "/bin/bash echo")
+ stream = rule.stream()
+ self.assertEqual("rule myrule", next(stream)) # rule always comes first
+ self.assertEqual(" command = /bin/bash echo", next(stream))
+ self.assertEqual(" description = Adding description before command", next(stream))
+
+class TestBuildAction(unittest.TestCase):
+
+ def test_no_inputs(self):
+ build = BuildAction(output="out", rule="phony")
+ stream = build.stream()
+ self.assertEqual("build out: phony", next(stream))
+ # Empty output
+ build = BuildAction(output="", rule="phony")
+ with self.assertRaises(BuildActionException):
+ next(build.stream())
+ # Empty rule
+ build = BuildAction(output="out", rule="")
+ with self.assertRaises(BuildActionException):
+ next(build.stream())
+
+ def test_inputs(self):
+ build = BuildAction(output="out", rule="cat", inputs=["input1", "input2"])
+ self.assertEqual("build out: cat input1 input2", next(build.stream()))
+ build = BuildAction(output="out", rule="cat", inputs=["input1", "input2"], implicits=["implicits1", "implicits2"], order_only=["order_only1", "order_only2"])
+ self.assertEqual("build out: cat input1 input2 | implicits1 implicits2 || order_only1 order_only2", next(build.stream()))
+
+ def test_variables(self):
+ build = BuildAction(output="out", rule="cat", inputs=["input1", "input2"])
+ build.add_variable(name="myvar", value="myval")
+ stream = build.stream()
+ next(stream)
+ self.assertEqual(" myvar = myval", next(stream))
+
+class TestPool(unittest.TestCase):
+
+ def test_pool(self):
+ pool = Pool(name="mypool", depth=10)
+ stream = pool.stream()
+ self.assertEqual("pool mypool", next(stream))
+ self.assertEqual(" depth = 10", next(stream))
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/orchestrator/ninja/test_ninja_writer.py b/orchestrator/ninja/test_ninja_writer.py
new file mode 100644
index 0000000..703dd4d
--- /dev/null
+++ b/orchestrator/ninja/test_ninja_writer.py
@@ -0,0 +1,54 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2022 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.
+
+import unittest
+
+from io import StringIO
+
+from ninja_writer import Writer
+from ninja_syntax import Variable, Rule, BuildAction
+
+class TestWriter(unittest.TestCase):
+
+ def test_simple_writer(self):
+ with StringIO() as f:
+ writer = Writer(f)
+ writer.add_variable(Variable(name="cflags", value="-Wall"))
+ writer.add_newline()
+ cc = Rule(name="cc")
+ cc.add_variable(name="command", value="gcc $cflags -c $in -o $out")
+ writer.add_rule(cc)
+ writer.add_newline()
+ build_action = BuildAction(output="foo.o", rule="cc", inputs=["foo.c"])
+ writer.add_build_action(build_action)
+ writer.write()
+ self.assertEqual('''cflags = -Wall
+
+rule cc
+ command = gcc $cflags -c $in -o $out
+
+build foo.o: cc foo.c
+''', f.getvalue())
+
+ def test_comment(self):
+ with StringIO() as f:
+ writer = Writer(f)
+ writer.add_comment("This is a comment in a ninja file")
+ writer.write()
+ self.assertEqual("# This is a comment in a ninja file\n", f.getvalue())
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/run.rbc b/tests/run.rbc
index 107be09..c6dfeba 100644
--- a/tests/run.rbc
+++ b/tests/run.rbc
@@ -43,7 +43,7 @@
assert_eq("", rblf.mkstrip(" \n \t "))
assert_eq("a b c", rblf.mkstrip(" a b \n c \t"))
-assert_eq(1, rblf.mkstrip(1))
+assert_eq("1", rblf.mkstrip("1 "))
assert_eq("b1 b2", rblf.mksubst("a", "b", "a1 a2"))
assert_eq(["b1", "x2"], rblf.mksubst("a", "b", ["a1", "x2"]))
@@ -90,6 +90,10 @@
assert_eq("", rblf.last_word(""))
assert_eq("", rblf.last_word([]))
+assert_eq(["foo", "bar"], rblf.flatten_2d_list([["foo", "bar"]]))
+assert_eq(["foo", "bar"], rblf.flatten_2d_list([["foo"], ["bar"]]))
+assert_eq([], rblf.flatten_2d_list([]))
+
assert_eq(
["build/make/tests/board.rbc", "build/make/tests/board_input_vars.rbc"],
rblf.expand_wildcard("build/make/tests/board*.rbc")
diff --git a/tools/releasetools/common.py b/tools/releasetools/common.py
index 374babf..cff7542 100644
--- a/tools/releasetools/common.py
+++ b/tools/releasetools/common.py
@@ -455,6 +455,11 @@
return vabc_enabled
@property
+ def is_android_r(self):
+ system_prop = self.info_dict.get("system.build.prop")
+ return system_prop and system_prop.GetProp("ro.build.version.release") == "11"
+
+ @property
def is_vabc_xor(self):
vendor_prop = self.info_dict.get("vendor.build.prop")
vabc_xor_enabled = vendor_prop and \
diff --git a/tools/releasetools/ota_from_target_files.py b/tools/releasetools/ota_from_target_files.py
index 66e850b..5384699 100755
--- a/tools/releasetools/ota_from_target_files.py
+++ b/tools/releasetools/ota_from_target_files.py
@@ -1068,10 +1068,11 @@
pre_partition_state, post_partition_state):
assert pre_partition_state is not None
partition_timestamps = {}
- for part in pre_partition_state:
- partition_timestamps[part.partition_name] = part.version
for part in post_partition_state:
- partition_timestamps[part.partition_name] = \
+ partition_timestamps[part.partition_name] = part.version
+ for part in pre_partition_state:
+ if part.partition_name in partition_timestamps:
+ partition_timestamps[part.partition_name] = \
max(part.version, partition_timestamps[part.partition_name])
return [
"--partition_timestamps",
@@ -1145,6 +1146,14 @@
logger.info("Either source or target does not support VABC, disabling.")
OPTIONS.disable_vabc = True
+ # Virtual AB Compression was introduced in Androd S.
+ # Later, we backported VABC to Android R. But verity support was not
+ # backported, so if VABC is used and we are on Android R, disable
+ # verity computation.
+ if not OPTIONS.disable_vabc and source_info.is_android_r:
+ OPTIONS.disable_verity_computation = True
+ OPTIONS.disable_fec_computation = True
+
else:
assert "ab_partitions" in OPTIONS.info_dict, \
"META/ab_partitions.txt is required for ab_update."
diff --git a/tools/signapk/src/com/android/signapk/SignApk.java b/tools/signapk/src/com/android/signapk/SignApk.java
index c127dbe..36a220c 100644
--- a/tools/signapk/src/com/android/signapk/SignApk.java
+++ b/tools/signapk/src/com/android/signapk/SignApk.java
@@ -901,7 +901,7 @@
* Tries to load a JSE Provider by class name. This is for custom PrivateKey
* types that might be stored in PKCS#11-like storage.
*/
- private static void loadProviderIfNecessary(String providerClassName) {
+ private static void loadProviderIfNecessary(String providerClassName, String providerArg) {
if (providerClassName == null) {
return;
}
@@ -920,27 +920,41 @@
return;
}
- Constructor<?> constructor = null;
- for (Constructor<?> c : klass.getConstructors()) {
- if (c.getParameterTypes().length == 0) {
- constructor = c;
- break;
+ Constructor<?> constructor;
+ Object o = null;
+ if (providerArg == null) {
+ try {
+ constructor = klass.getConstructor();
+ o = constructor.newInstance();
+ } catch (ReflectiveOperationException e) {
+ e.printStackTrace();
+ System.err.println("Unable to instantiate " + providerClassName
+ + " with a zero-arg constructor");
+ System.exit(1);
+ }
+ } else {
+ try {
+ constructor = klass.getConstructor(String.class);
+ o = constructor.newInstance(providerArg);
+ } catch (ReflectiveOperationException e) {
+ // This is expected from JDK 9+; the single-arg constructor accepting the
+ // configuration has been replaced with a configure(String) method to be invoked
+ // after instantiating the Provider with the zero-arg constructor.
+ try {
+ constructor = klass.getConstructor();
+ o = constructor.newInstance();
+ // The configure method will return either the modified Provider or a new
+ // Provider if this one cannot be configured in-place.
+ o = klass.getMethod("configure", String.class).invoke(o, providerArg);
+ } catch (ReflectiveOperationException roe) {
+ roe.printStackTrace();
+ System.err.println("Unable to instantiate " + providerClassName
+ + " with the provided argument " + providerArg);
+ System.exit(1);
+ }
}
}
- if (constructor == null) {
- System.err.println("No zero-arg constructor found for " + providerClassName);
- System.exit(1);
- return;
- }
- final Object o;
- try {
- o = constructor.newInstance();
- } catch (Exception e) {
- e.printStackTrace();
- System.exit(1);
- return;
- }
if (!(o instanceof Provider)) {
System.err.println("Not a Provider class: " + providerClassName);
System.exit(1);
@@ -1049,6 +1063,7 @@
"[-a <alignment>] " +
"[--align-file-size] " +
"[-providerClass <className>] " +
+ "[-providerArg <configureArg>] " +
"[-loadPrivateKeysFromKeyStore <keyStoreName>]" +
"[-keyStorePin <pin>]" +
"[--min-sdk-version <n>] " +
@@ -1073,6 +1088,7 @@
boolean signWholeFile = false;
String providerClass = null;
+ String providerArg = null;
String keyStoreName = null;
String keyStorePin = null;
int alignment = 4;
@@ -1093,6 +1109,12 @@
}
providerClass = args[++argstart];
++argstart;
+ } else if("-providerArg".equals(args[argstart])) {
+ if (argstart + 1 >= args.length) {
+ usage();
+ }
+ providerArg = args[++argstart];
+ ++argstart;
} else if ("-loadPrivateKeysFromKeyStore".equals(args[argstart])) {
if (argstart + 1 >= args.length) {
usage();
@@ -1153,7 +1175,7 @@
System.exit(2);
}
- loadProviderIfNecessary(providerClass);
+ loadProviderIfNecessary(providerClass, providerArg);
String inputFilename = args[numArgsExcludeV4FilePath - 2];
String outputFilename = args[numArgsExcludeV4FilePath - 1];