Merge "Don't force color diagnostics outside of ninja"
diff --git a/CleanSpec.mk b/CleanSpec.mk
index 64d84e3..19cb651 100644
--- a/CleanSpec.mk
+++ b/CleanSpec.mk
@@ -371,6 +371,9 @@
$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/build.prop)
+# $(PRODUCT_OUT)/recovery/root/sdcard goes from symlink to folder.
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/recovery/root/sdcard)
+
# ************************************************
# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
# ************************************************
diff --git a/core/Makefile b/core/Makefile
index aee792a..18135d3 100644
--- a/core/Makefile
+++ b/core/Makefile
@@ -695,6 +695,19 @@
$(hide) zip -qjX $@ $<
$(remove-timestamps-from-package)
+# Carry the public key for update_engine if it's a non-Brillo target that
+# uses the AB updater. We use the same key as otacerts but in RSA public key
+# format.
+ifeq ($(AB_OTA_UPDATER),true)
+ifeq ($(BRILLO),)
+ALL_DEFAULT_INSTALLED_MODULES += $(TARGET_OUT_ETC)/update_engine/update-payload-key.pub.pem
+$(TARGET_OUT_ETC)/update_engine/update-payload-key.pub.pem: $(addsuffix .x509.pem,$(DEFAULT_KEY_CERT_PAIR))
+ $(hide) rm -f $@
+ $(hide) mkdir -p $(dir $@)
+ $(hide) openssl x509 -pubkey -noout -in $< > $@
+endif
+endif
+
.PHONY: otacerts
otacerts: $(TARGET_OUT_ETC)/security/otacerts.zip
@@ -920,9 +933,9 @@
define build-recoveryimage-target
@echo ----- Making recovery image ------
$(hide) mkdir -p $(TARGET_RECOVERY_OUT)
- $(hide) mkdir -p $(TARGET_RECOVERY_ROOT_OUT)/etc $(TARGET_RECOVERY_ROOT_OUT)/tmp
+ $(hide) mkdir -p $(TARGET_RECOVERY_ROOT_OUT)/etc $(TARGET_RECOVERY_ROOT_OUT)/sdcard $(TARGET_RECOVERY_ROOT_OUT)/tmp
@echo Copying baseline ramdisk...
- $(hide) rsync -a --exclude=etc $(TARGET_ROOT_OUT) $(TARGET_RECOVERY_OUT) # "cp -Rf" fails to overwrite broken symlinks on Mac.
+ $(hide) rsync -a --exclude=etc --exclude=sdcard $(TARGET_ROOT_OUT) $(TARGET_RECOVERY_OUT) # "cp -Rf" fails to overwrite broken symlinks on Mac.
@echo Modifying ramdisk contents...
$(hide) rm -f $(TARGET_RECOVERY_ROOT_OUT)/init*.rc
$(hide) cp -f $(recovery_initrc) $(TARGET_RECOVERY_ROOT_OUT)/
@@ -1425,11 +1438,10 @@
$(HOST_OUT_EXECUTABLES)/img2simg \
$(HOST_OUT_EXECUTABLES)/boot_signer \
$(HOST_OUT_EXECUTABLES)/fec \
+ $(HOST_OUT_EXECUTABLES)/brillo_update_payload \
+ $(HOST_OUT_EXECUTABLES)/lib/shflags/shflags \
$(HOST_OUT_EXECUTABLES)/delta_generator
-OTATOOLS += \
- system/update_engine/scripts/brillo_update_payload
-
# Shared libraries.
OTATOOLS += \
$(HOST_LIBRARY_PATH)/libc++$(HOST_SHLIB_SUFFIX) \
@@ -1712,8 +1724,11 @@
@# If breakpad symbols have been generated, add them to the zip.
$(hide) $(ACP) -r $(TARGET_OUT_BREAKPAD) $(zip_root)/BREAKPAD
endif
- @# Zip everything up, preserving symlinks
- $(hide) (cd $(zip_root) && zip -qryX ../$(notdir $@) .)
+ @# Zip everything up, preserving symlinks and placing META/ files first to
+ @# help early validation of the .zip file while uploading it.
+ $(hide) (cd $(zip_root) && \
+ zip -qryX ../$(notdir $@) ./META && \
+ zip -qryXu ../$(notdir $@) .)
@# Run fs_config on all the system, vendor, boot ramdisk,
@# and recovery ramdisk files in the zip, and save the output
$(hide) zipinfo -1 $@ | awk 'BEGIN { FS="SYSTEM/" } /^SYSTEM\// {print "system/" $$2}' | $(HOST_OUT_EXECUTABLES)/fs_config -C -D $(TARGET_OUT) -S $(SELINUX_FC) > $(zip_root)/META/filesystem_config.txt
diff --git a/core/binary.mk b/core/binary.mk
index 4cb62b3..d8b6816 100644
--- a/core/binary.mk
+++ b/core/binary.mk
@@ -449,7 +449,6 @@
ifeq ($(LOCAL_CPP_EXTENSION),)
LOCAL_CPP_EXTENSION := .cpp
endif
-$(LOCAL_INTERMEDIATE_TARGETS): PRIVATE_CPP_EXTENSION := $(LOCAL_CPP_EXTENSION)
# Certain modules like libdl have to have symbols resolved at runtime and blow
# up if --no-undefined is passed to the linker.
@@ -504,6 +503,34 @@
endif
####################################################
+## Keep track of src -> obj mapping
+####################################################
+
+my_tracked_gen_files :=
+my_tracked_src_files :=
+
+###########################################################
+## Stuff source generated from one-off tools
+###########################################################
+$(my_generated_sources): PRIVATE_MODULE := $(my_register_name)
+
+my_gen_sources_copy := $(patsubst $(generated_sources_dir)/%,$(intermediates)/%,$(filter $(generated_sources_dir)/%,$(my_generated_sources)))
+
+$(my_gen_sources_copy): $(intermediates)/% : $(generated_sources_dir)/% | $(ACP)
+ @echo "Copy: $@"
+ $(copy-file-to-target)
+
+my_generated_sources := $(patsubst $(generated_sources_dir)/%,$(intermediates)/%,$(my_generated_sources))
+
+# Generated sources that will actually produce object files.
+# Other files (like headers) are allowed in LOCAL_GENERATED_SOURCES,
+# since other compiled sources may depend on them, and we set up
+# the dependencies.
+my_gen_src_files := $(filter %.c %$(LOCAL_CPP_EXTENSION) %.S %.s %.o,$(my_generated_sources))
+
+ALL_GENERATED_SOURCES += $(my_generated_sources)
+
+####################################################
## Compile RenderScript with reflected C++
####################################################
@@ -563,7 +590,7 @@
$(transform-renderscripts-to-cpp-and-bc)
# include the dependency files (.d/.P) generated by llvm-rs-cc.
--include $(bc_dep_files:%.d=%.P)
+$(call include-depfile,$(RenderScript_file_stamp).P,$(RenderScript_file_stamp))
LOCAL_INTERMEDIATE_TARGETS += $(RenderScript_file_stamp)
@@ -571,6 +598,8 @@
$(renderscript_intermediate)/ScriptC_,$(patsubst %.fs,%.cpp, $(patsubst %.rs,%.cpp, \
$(notdir $(renderscript_sources)))))
+$(call track-src-file-gen,$(renderscript_sources),$(rs_generated_cpps))
+
# This is just a dummy rule to make sure gmake doesn't skip updating the dependents.
$(rs_generated_cpps) : $(RenderScript_file_stamp)
@echo "Updated RS generated cpp file $@."
@@ -583,21 +612,6 @@
###########################################################
-## Stuff source generated from one-off tools
-###########################################################
-$(my_generated_sources): PRIVATE_MODULE := $(my_register_name)
-
-my_gen_sources_copy := $(patsubst $(generated_sources_dir)/%,$(intermediates)/%,$(filter $(generated_sources_dir)/%,$(my_generated_sources)))
-
-$(my_gen_sources_copy): $(intermediates)/% : $(generated_sources_dir)/% | $(ACP)
- @echo "Copy: $@"
- $(copy-file-to-target)
-
-my_generated_sources := $(patsubst $(generated_sources_dir)/%,$(intermediates)/%,$(my_generated_sources))
-
-ALL_GENERATED_SOURCES += $(my_generated_sources)
-
-###########################################################
## Compile the .proto files to .cc (or .c) and then to .o
###########################################################
proto_sources := $(filter %.proto,$(my_src_files))
@@ -626,6 +640,7 @@
proto_generated_headers := $(patsubst %.pb$(my_proto_source_suffix),%.pb.h, $(proto_generated_sources))
proto_generated_objects := $(addprefix $(proto_generated_obj_dir)/, \
$(patsubst %.proto,%.pb.o,$(proto_sources_fullpath)))
+$(call track-src-file-obj,$(proto_sources),$(proto_generated_objects))
# Ensure the transform-proto-to-cc rule is only defined once in multilib build.
ifndef $(my_prefix)_$(LOCAL_MODULE_CLASS)_$(LOCAL_MODULE)_proto_defined
@@ -686,6 +701,9 @@
dbus_service_config := $(filter %dbus-service-config.json,$(my_src_files))
dbus_service_config_path := $(addprefix $(LOCAL_PATH)/,$(dbus_service_config))
+# Mark these source files as not producing objects
+$(call track-src-file-obj,$(dbus_definitions) $(dbus_service_config),)
+
dbus_gen_dir := $(generated_sources_dir)/dbus_bindings
ifdef LOCAL_DBUS_PROXY_PREFIX
@@ -734,24 +752,22 @@
aidl_gen_cpp :=
ifneq ($(aidl_src),)
+# Use the intermediates directory to avoid writing our own .cpp -> .o rules.
aidl_gen_cpp_root := $(intermediates)/aidl-generated/src
aidl_gen_include_root := $(intermediates)/aidl-generated/include
-aidl_gen_cpp := $(patsubst %.aidl,%$(LOCAL_CPP_EXTENSION),$(aidl_src))
-aidl_gen_cpp := $(addprefix $(aidl_gen_cpp_root)/,$(aidl_gen_cpp))
+# Multi-architecture builds have distinct intermediates directories.
+# Thus we'll actually generate source for each architecture.
+$(foreach s,$(aidl_src),\
+ $(eval $(call define-aidl-cpp-rule,$(s),$(aidl_gen_cpp_root),aidl_gen_cpp)))
+$(foreach cpp,$(aidl_gen_cpp), \
+ $(call include-depfile,$(addsuffix .aidl.P,$(basename $(cpp))),$(cpp)))
+$(call track-src-file-gen,$(aidl_src),$(aidl_gen_cpp))
-# TODO(wiley): we could pass down a flag here to only generate the server or
-# client side of the binder interface.
$(aidl_gen_cpp) : PRIVATE_MODULE := $(LOCAL_MODULE)
$(aidl_gen_cpp) : PRIVATE_HEADER_OUTPUT_DIR := $(aidl_gen_include_root)
$(aidl_gen_cpp) : PRIVATE_AIDL_FLAGS := $(addprefix -I,$(LOCAL_AIDL_INCLUDES))
-# Multi-architecture builds have distinct intermediates directories.
-# Define rules for both architectures.
-$(aidl_gen_cpp) : $(aidl_gen_cpp_root)/%$(LOCAL_CPP_EXTENSION) : $(LOCAL_PATH)/%.aidl $(AIDL_CPP)
- $(transform-aidl-to-cpp)
--include $(addsuffix .P,$(basename $(aidl_gen_cpp)))
-
# Add generated headers to include paths.
my_c_includes += $(aidl_gen_include_root)
my_export_c_include_dirs += $(aidl_gen_include_root)
@@ -761,79 +777,61 @@
endif # $(aidl_src) non-empty
###########################################################
-## YACC: Compile .y and .yy files to .cpp and the to .o.
+## YACC: Compile .y/.yy files to .c/.cpp and then to .o.
###########################################################
y_yacc_sources := $(filter %.y,$(my_src_files))
-y_yacc_cpps := $(addprefix \
- $(intermediates)/,$(y_yacc_sources:.y=$(LOCAL_CPP_EXTENSION)))
+y_yacc_cs := $(addprefix \
+ $(intermediates)/,$(y_yacc_sources:.y=.c))
+ifneq ($(y_yacc_cs),)
+$(y_yacc_cs): $(intermediates)/%.c: \
+ $(TOPDIR)$(LOCAL_PATH)/%.y \
+ $(my_additional_dependencies)
+ $(call transform-y-to-c-or-cpp)
+$(call track-src-file-gen,$(y_yacc_sources),$(y_yacc_cs))
+
+my_generated_sources += $(y_yacc_cs)
+endif
yy_yacc_sources := $(filter %.yy,$(my_src_files))
yy_yacc_cpps := $(addprefix \
$(intermediates)/,$(yy_yacc_sources:.yy=$(LOCAL_CPP_EXTENSION)))
-
-yacc_cpps := $(y_yacc_cpps) $(yy_yacc_cpps)
-yacc_headers := $(yacc_cpps:$(LOCAL_CPP_EXTENSION)=.h)
-yacc_objects := $(yacc_cpps:$(LOCAL_CPP_EXTENSION)=.o)
-
-ifneq ($(strip $(y_yacc_cpps)),)
-$(y_yacc_cpps): $(intermediates)/%$(LOCAL_CPP_EXTENSION): \
- $(TOPDIR)$(LOCAL_PATH)/%.y \
- $(lex_cpps) $(my_additional_dependencies)
- $(call transform-y-to-cpp,$(PRIVATE_CPP_EXTENSION))
-$(yacc_headers): $(intermediates)/%.h: $(intermediates)/%$(LOCAL_CPP_EXTENSION)
-endif
-
-ifneq ($(strip $(yy_yacc_cpps)),)
+ifneq ($(yy_yacc_cpps),)
$(yy_yacc_cpps): $(intermediates)/%$(LOCAL_CPP_EXTENSION): \
$(TOPDIR)$(LOCAL_PATH)/%.yy \
- $(lex_cpps) $(my_additional_dependencies)
- $(call transform-y-to-cpp,$(PRIVATE_CPP_EXTENSION))
-$(yacc_headers): $(intermediates)/%.h: $(intermediates)/%$(LOCAL_CPP_EXTENSION)
-endif
+ $(my_additional_dependencies)
+ $(call transform-y-to-c-or-cpp)
+$(call track-src-file-gen,$(yy_yacc_sources),$(yy_yacc_cpps))
-ifneq ($(strip $(yacc_cpps)),)
-$(yacc_objects): PRIVATE_ARM_MODE := $(normal_objects_mode)
-$(yacc_objects): PRIVATE_ARM_CFLAGS := $(normal_objects_cflags)
-$(yacc_objects): $(intermediates)/%.o: $(intermediates)/%$(LOCAL_CPP_EXTENSION)
- $(transform-$(PRIVATE_HOST)cpp-to-o)
+my_generated_sources += $(yy_yacc_cpps)
endif
###########################################################
-## LEX: Compile .l and .ll files to .cpp and then to .o.
+## LEX: Compile .l/.ll files to .c/.cpp and then to .o.
###########################################################
l_lex_sources := $(filter %.l,$(my_src_files))
-l_lex_cpps := $(addprefix \
- $(intermediates)/,$(l_lex_sources:.l=$(LOCAL_CPP_EXTENSION)))
+l_lex_cs := $(addprefix \
+ $(intermediates)/,$(l_lex_sources:.l=.c))
+ifneq ($(l_lex_cs),)
+$(l_lex_cs): $(intermediates)/%.c: \
+ $(TOPDIR)$(LOCAL_PATH)/%.l
+ $(transform-l-to-c-or-cpp)
+$(call track-src-file-gen,$(l_lex_sources),$(l_lex_cs))
+
+my_generated_sources += $(l_lex_cs)
+endif
ll_lex_sources := $(filter %.ll,$(my_src_files))
ll_lex_cpps := $(addprefix \
$(intermediates)/,$(ll_lex_sources:.ll=$(LOCAL_CPP_EXTENSION)))
-
-lex_cpps := $(l_lex_cpps) $(ll_lex_cpps)
-lex_objects := $(lex_cpps:$(LOCAL_CPP_EXTENSION)=.o)
-
-ifneq ($(strip $(l_lex_cpps)),)
-$(l_lex_cpps): $(intermediates)/%$(LOCAL_CPP_EXTENSION): \
- $(TOPDIR)$(LOCAL_PATH)/%.l
- $(transform-l-to-cpp)
-endif
-
-ifneq ($(strip $(ll_lex_cpps)),)
+ifneq ($(ll_lex_cpps),)
$(ll_lex_cpps): $(intermediates)/%$(LOCAL_CPP_EXTENSION): \
$(TOPDIR)$(LOCAL_PATH)/%.ll
- $(transform-l-to-cpp)
-endif
+ $(transform-l-to-c-or-cpp)
+$(call track-src-file-gen,$(ll_lex_sources),$(ll_lex_cpps))
-ifneq ($(strip $(lex_cpps)),)
-$(lex_objects): PRIVATE_ARM_MODE := $(normal_objects_mode)
-$(lex_objects): PRIVATE_ARM_CFLAGS := $(normal_objects_cflags)
-$(lex_objects): $(intermediates)/%.o: \
- $(intermediates)/%$(LOCAL_CPP_EXTENSION) \
- $(my_additional_dependencies) \
- $(yacc_headers)
- $(transform-$(PRIVATE_HOST)cpp-to-o)
+my_generated_sources += $(ll_lex_cpps)
endif
###########################################################
@@ -846,6 +844,7 @@
dotdot_arm_sources := $(filter ../%,$(cpp_arm_sources))
cpp_arm_sources := $(filter-out ../%,$(cpp_arm_sources))
cpp_arm_objects := $(addprefix $(intermediates)/,$(cpp_arm_sources:$(LOCAL_CPP_EXTENSION)=.o))
+$(call track-src-file-obj,$(patsubst %,%.arm,$(cpp_arm_sources)),$(cpp_arm_objects))
# For source files starting with ../, we remove all the ../ in the object file path,
# to avoid object file escaping the intermediate directory.
@@ -854,6 +853,7 @@
$(eval $(call compile-dotdot-cpp-file,$(s),\
$(yacc_cpps) $(proto_generated_headers) $(my_additional_dependencies),\
dotdot_arm_objects)))
+$(call track-src-file-obj,$(patsubst %,%.arm,$(dotdot_arm_sources)),$(dotdot_arm_objects))
dotdot_sources := $(filter ../%$(LOCAL_CPP_EXTENSION),$(my_src_files))
dotdot_objects :=
@@ -861,9 +861,11 @@
$(eval $(call compile-dotdot-cpp-file,$(s),\
$(yacc_cpps) $(proto_generated_headers) $(my_additional_dependencies),\
dotdot_objects)))
+$(call track-src-file-obj,$(dotdot_sources),$(dotdot_objects))
cpp_normal_sources := $(filter-out ../%,$(filter %$(LOCAL_CPP_EXTENSION),$(my_src_files)))
cpp_normal_objects := $(addprefix $(intermediates)/,$(cpp_normal_sources:$(LOCAL_CPP_EXTENSION)=.o))
+$(call track-src-file-obj,$(cpp_normal_sources),$(cpp_normal_objects))
$(dotdot_arm_objects) $(cpp_arm_objects): PRIVATE_ARM_MODE := $(arm_objects_mode)
$(dotdot_arm_objects) $(cpp_arm_objects): PRIVATE_ARM_CFLAGS := $(arm_objects_cflags)
@@ -889,6 +891,7 @@
gen_cpp_sources := $(filter %$(LOCAL_CPP_EXTENSION),$(my_generated_sources))
gen_cpp_objects := $(gen_cpp_sources:%$(LOCAL_CPP_EXTENSION)=%.o)
+$(call track-gen-file-obj,$(gen_cpp_sources),$(gen_cpp_objects))
ifneq ($(strip $(gen_cpp_objects)),)
# Compile all generated files as thumb.
@@ -909,6 +912,7 @@
gen_S_sources := $(filter %.S,$(my_generated_sources))
gen_S_objects := $(gen_S_sources:%.S=%.o)
+$(call track-gen-file-obj,$(gen_S_sources),$(gen_S_objects))
ifneq ($(strip $(gen_S_sources)),)
$(gen_S_objects): $(intermediates)/%.o: $(intermediates)/%.S \
@@ -919,6 +923,7 @@
gen_s_sources := $(filter %.s,$(my_generated_sources))
gen_s_objects := $(gen_s_sources:%.s=%.o)
+$(call track-gen-file-obj,$(gen_s_sources),$(gen_s_objects))
ifneq ($(strip $(gen_s_objects)),)
$(gen_s_objects): $(intermediates)/%.o: $(intermediates)/%.s \
@@ -928,6 +933,7 @@
endif
gen_asm_objects := $(gen_S_objects) $(gen_s_objects)
+$(gen_asm_objects): PRIVATE_ARM_CFLAGS := $(normal_objects_cflags)
###########################################################
## o: Include generated .o files in output.
@@ -943,6 +949,7 @@
dotdot_arm_sources := $(filter ../%,$(c_arm_sources))
c_arm_sources := $(filter-out ../%,$(c_arm_sources))
c_arm_objects := $(addprefix $(intermediates)/,$(c_arm_sources:.c=.o))
+$(call track-src-file-obj,$(patsubst %,%.arm,$(c_arm_sources)),$(c_arm_objects))
# For source files starting with ../, we remove all the ../ in the object file path,
# to avoid object file escaping the intermediate directory.
@@ -951,6 +958,7 @@
$(eval $(call compile-dotdot-c-file,$(s),\
$(yacc_cpps) $(proto_generated_headers) $(my_additional_dependencies),\
dotdot_arm_objects)))
+$(call track-src-file-obj,$(patsubst %,%.arm,$(dotdot_arm_sources)),$(dotdot_arm_objects))
dotdot_sources := $(filter ../%.c, $(my_src_files))
dotdot_objects :=
@@ -958,9 +966,11 @@
$(eval $(call compile-dotdot-c-file,$(s),\
$(yacc_cpps) $(proto_generated_headers) $(my_additional_dependencies),\
dotdot_objects)))
+$(call track-src-file-obj,$(dotdot_sources),$(dotdot_objects))
c_normal_sources := $(filter-out ../%,$(filter %.c,$(my_src_files)))
c_normal_objects := $(addprefix $(intermediates)/,$(c_normal_sources:.c=.o))
+$(call track-src-file-obj,$(c_normal_sources),$(c_normal_objects))
$(dotdot_arm_objects) $(c_arm_objects): PRIVATE_ARM_MODE := $(arm_objects_mode)
$(dotdot_arm_objects) $(c_arm_objects): PRIVATE_ARM_CFLAGS := $(arm_objects_cflags)
@@ -984,6 +994,7 @@
gen_c_sources := $(filter %.c,$(my_generated_sources))
gen_c_objects := $(gen_c_sources:%.c=%.o)
+$(call track-gen-file-obj,$(gen_c_sources),$(gen_c_objects))
ifneq ($(strip $(gen_c_objects)),)
# Compile all generated files as thumb.
@@ -1032,12 +1043,14 @@
dotdot_sources := $(filter ../%,$(asm_sources_S))
asm_sources_S := $(filter-out ../%,$(asm_sources_S))
asm_objects_S := $(addprefix $(intermediates)/,$(asm_sources_S:.S=.o))
+$(call track-src-file-obj,$(asm_sources_S),$(asm_objects_S))
dotdot_objects_S :=
$(foreach s,$(dotdot_sources),\
$(eval $(call compile-dotdot-s-file,$(s),\
$(my_additional_dependencies),\
dotdot_objects_S)))
+$(call track-src-file-obj,$(dotdot_sources),$(dotdot_objects_S))
ifneq ($(strip $(asm_objects_S)),)
$(asm_objects_S): $(intermediates)/%.o: $(TOPDIR)$(LOCAL_PATH)/%.S \
@@ -1050,12 +1063,14 @@
dotdot_sources := $(filter ../%,$(asm_sources_s))
asm_sources_s := $(filter-out ../%,$(asm_sources_s))
asm_objects_s := $(addprefix $(intermediates)/,$(asm_sources_s:.s=.o))
+$(call track-src-file-obj,$(asm_sources_s),$(asm_objects_s))
dotdot_objects_s :=
$(foreach s,$(dotdot_sources),\
$(eval $(call compile-dotdot-s-file-no-deps,$(s),\
$(my_additional_dependencies),\
dotdot_objects_s)))
+$(call track-src-file-obj,$(dotdot_sources),$(dotdot_objects_s))
ifneq ($(strip $(asm_objects_s)),)
$(asm_objects_s): $(intermediates)/%.o: $(TOPDIR)$(LOCAL_PATH)/%.s \
@@ -1064,6 +1079,7 @@
endif
asm_objects := $(dotdot_objects_S) $(dotdot_objects_s) $(asm_objects_S) $(asm_objects_s)
+$(asm_objects): PRIVATE_ARM_CFLAGS := $(normal_objects_cflags)
# .asm for x86/x86_64 needs to be compiled with yasm.
@@ -1073,6 +1089,7 @@
$(asm_objects_asm): $(intermediates)/%.o: $(TOPDIR)$(LOCAL_PATH)/%.asm \
$(my_additional_dependencies)
$(transform-asm-to-o)
+$(call track-src-file-obj,$(asm_sources_asm),$(asm_objects_asm))
asm_objects += $(asm_objects_asm)
endif
@@ -1127,6 +1144,11 @@
## Common object handling.
###########################################################
+my_unused_src_files := $(filter-out $(logtags_sources) $(my_tracked_src_files),$(my_src_files) $(my_gen_src_files))
+ifneq ($(my_unused_src_files),)
+ $(warning $(LOCAL_MODULE_MAKEFILE): $(LOCAL_MODULE): Unused source files: $(my_unused_src_files))
+endif
+
# some rules depend on asm_objects being first. If your code depends on
# being first, it's reasonable to require it to be assembly
normal_objects := \
@@ -1138,13 +1160,31 @@
$(gen_c_objects) \
$(objc_objects) \
$(objcpp_objects) \
- $(yacc_objects) \
- $(lex_objects) \
- $(proto_generated_objects) \
- $(addprefix $(TOPDIR)$(LOCAL_PATH)/,$(LOCAL_PREBUILT_OBJ_FILES))
+ $(proto_generated_objects)
+
+new_order_normal_objects := $(foreach f,$(my_src_files),$(my_src_file_obj_$(f)))
+new_order_normal_objects += $(foreach f,$(my_gen_src_files),$(my_src_file_obj_$(f)))
+
+ifneq ($(sort $(normal_objects)),$(sort $(new_order_normal_objects)))
+$(warning $(LOCAL_MODULE_MAKEFILE) Internal build system warning: New object list does not match old)
+$(info Only in old: $(filter-out $(new_order_normal_objects),$(sort $(normal_objects))))
+$(info Only in new: $(filter-out $(normal_objects),$(sort $(new_order_normal_objects))))
+endif
+
+ifeq ($(BINARY_OBJECTS_ORDER),soong)
+normal_objects := $(new_order_normal_objects)
+endif
+
+normal_objects += $(addprefix $(TOPDIR)$(LOCAL_PATH)/,$(LOCAL_PREBUILT_OBJ_FILES))
all_objects := $(normal_objects) $(gen_o_objects)
+# Cleanup file tracking
+$(foreach f,$(my_tracked_gen_files),$(eval my_src_file_gen_$(s):=))
+my_tracked_gen_files :=
+$(foreach f,$(my_tracked_src_files),$(eval my_src_file_obj_$(s):=))
+my_tracked_src_files :=
+
my_c_includes += $(TOPDIR)$(LOCAL_PATH) $(intermediates) $(generated_sources_dir)
ifndef LOCAL_SDK_VERSION
diff --git a/core/clang/arm.mk b/core/clang/arm.mk
index e66aa6c..4053bb2 100644
--- a/core/clang/arm.mk
+++ b/core/clang/arm.mk
@@ -29,8 +29,7 @@
-fno-partial-inlining \
-fno-strict-volatile-bitfields \
-fno-tree-copy-prop \
- -fno-tree-loop-optimize \
- -Wa,--noexecstack
+ -fno-tree-loop-optimize
define subst-clang-incompatible-arm-flags
$(subst -march=armv5te,-march=armv5t,\
diff --git a/core/clang/arm64.mk b/core/clang/arm64.mk
index ab395b3..cad7321 100644
--- a/core/clang/arm64.mk
+++ b/core/clang/arm64.mk
@@ -13,8 +13,7 @@
-frerun-cse-after-loop \
-frename-registers \
-fno-strict-volatile-bitfields \
- -fno-align-jumps \
- -Wa,--noexecstack
+ -fno-align-jumps
# We don't have any arm64 flags to substitute yet.
define subst-clang-incompatible-arm64-flags
diff --git a/core/clang/config.mk b/core/clang/config.mk
index 842d47d..c184a87 100644
--- a/core/clang/config.mk
+++ b/core/clang/config.mk
@@ -13,6 +13,16 @@
CLANG_TBLGEN := $(BUILD_OUT_EXECUTABLES)/clang-tblgen$(BUILD_EXECUTABLE_SUFFIX)
LLVM_TBLGEN := $(BUILD_OUT_EXECUTABLES)/llvm-tblgen$(BUILD_EXECUTABLE_SUFFIX)
+# RenderScript-specific tools
+# These are tied to the version of LLVM directly in external/, so they might
+# trail the host prebuilts being used for the rest of the build process.
+RS_LLVM_PREBUILTS_VERSION := 3.8
+RS_LLVM_PREBUILTS_BASE := prebuilts/clang/host
+RS_LLVM_PREBUILTS_PATH := $(RS_LLVM_PREBUILTS_BASE)/$(BUILD_OS)-x86/$(RS_LLVM_PREBUILTS_VERSION)/bin
+RS_CLANG := $(RS_LLVM_PREBUILTS_PATH)/clang$(BUILD_EXECUTABLE_SUFFIX)
+RS_LLVM_AS := $(RS_LLVM_PREBUILTS_PATH)/llvm-as$(BUILD_EXECUTABLE_SUFFIX)
+RS_LLVM_LINK := $(RS_LLVM_PREBUILTS_PATH)/llvm-link$(BUILD_EXECUTABLE_SUFFIX)
+
# Clang flags for all host or target rules
CLANG_CONFIG_EXTRA_ASFLAGS :=
CLANG_CONFIG_EXTRA_CFLAGS :=
diff --git a/core/cleanbuild.mk b/core/cleanbuild.mk
index 06ceecb..f7722d2 100644
--- a/core/cleanbuild.mk
+++ b/core/cleanbuild.mk
@@ -122,9 +122,17 @@
mkdir -p $(dir $(clean_steps_file)) && \
echo "CURRENT_CLEAN_BUILD_VERSION := $(INTERNAL_CLEAN_BUILD_VERSION)" > \
$(clean_steps_file) ;\
- echo "CURRENT_CLEAN_STEPS := $(INTERNAL_CLEAN_STEPS)" >> \
- $(clean_steps_file) \
+ echo "CURRENT_CLEAN_STEPS := $(wordlist 1,500,$(INTERNAL_CLEAN_STEPS))" >> $(clean_steps_file) \
)
+define -cs-write-clean-steps-if-arg1-not-empty
+$(if $(1),$(shell echo "CURRENT_CLEAN_STEPS += $(1)" >> $(clean_steps_file)))
+endef
+$(call -cs-write-clean-steps-if-arg1-not-empty,$(wordlist 501,1000,$(INTERNAL_CLEAN_STEPS)))
+$(call -cs-write-clean-steps-if-arg1-not-empty,$(wordlist 1001,1500,$(INTERNAL_CLEAN_STEPS)))
+$(call -cs-write-clean-steps-if-arg1-not-empty,$(wordlist 1501,2000,$(INTERNAL_CLEAN_STEPS)))
+$(call -cs-write-clean-steps-if-arg1-not-empty,$(wordlist 2001,2500,$(INTERNAL_CLEAN_STEPS)))
+$(call -cs-write-clean-steps-if-arg1-not-empty,$(wordlist 2501,3000,$(INTERNAL_CLEAN_STEPS)))
+$(call -cs-write-clean-steps-if-arg1-not-empty,$(wordlist 3001,99999,$(INTERNAL_CLEAN_STEPS)))
endif
CURRENT_CLEAN_BUILD_VERSION :=
diff --git a/core/combo/HOST_CROSS_windows-x86.mk b/core/combo/HOST_CROSS_windows-x86.mk
index 1ac1059..b6cbebc 100644
--- a/core/combo/HOST_CROSS_windows-x86.mk
+++ b/core/combo/HOST_CROSS_windows-x86.mk
@@ -32,8 +32,8 @@
$(combo_var_prefix)GLOBAL_CFLAGS += -D__STDC_FORMAT_MACROS -D__STDC_CONSTANT_MACROS
# Use C99-compliant printf functions (%zd).
$(combo_var_prefix)GLOBAL_CFLAGS += -D__USE_MINGW_ANSI_STDIO=1
-# Admit to using >= Win2K. Both are needed because of <_mingw.h>.
-$(combo_var_prefix)GLOBAL_CFLAGS += -D_WIN32_WINNT=0x0500 -DWINVER=0x0500
+# Admit to using >= Vista. Both are needed because of <_mingw.h>.
+$(combo_var_prefix)GLOBAL_CFLAGS += -D_WIN32_WINNT=0x0600 -DWINVER=0x0600
# Get 64-bit off_t and related functions.
$(combo_var_prefix)GLOBAL_CFLAGS += -D_FILE_OFFSET_BITS=64
diff --git a/core/config.mk b/core/config.mk
index 29de233..0f19dea 100644
--- a/core/config.mk
+++ b/core/config.mk
@@ -406,6 +406,8 @@
DX := $(HOST_OUT_EXECUTABLES)/dx
MAINDEXCLASSES := $(HOST_OUT_EXECUTABLES)/mainDexClasses
+USE_PREBUILT_SDK_TOOLS_IN_PLACE := true
+
# Override the definitions above for unbundled and PDK builds
ifneq (,$(TARGET_BUILD_APPS)$(filter true,$(TARGET_BUILD_PDK)))
prebuilt_sdk_tools := prebuilts/sdk/tools
@@ -529,8 +531,6 @@
# Tool to merge AndroidManifest.xmls
ANDROID_MANIFEST_MERGER := java -classpath prebuilts/devtools/tools/lib/manifest-merger.jar com.android.manifmerger.Main merge
-YACC_HEADER_SUFFIX:= .hpp
-
COLUMN:= column
# We may not have the right JAVA_HOME/PATH set up yet when this is run from envsetup.sh.
@@ -606,7 +606,7 @@
GLOBAL_CPPFLAGS_NO_OVERRIDE :=
# list of flags to turn specific warnings in to errors
-TARGET_ERROR_FLAGS := -Werror=return-type -Werror=non-virtual-dtor -Werror=address -Werror=sequence-point
+TARGET_ERROR_FLAGS := -Werror=return-type -Werror=non-virtual-dtor -Werror=address -Werror=sequence-point -Werror=date-time
# We run gcc/clang with PWD=/proc/self/cwd to remove the $TOP
# from the debug output. That way two builds in two different
diff --git a/core/definitions.mk b/core/definitions.mk
index a6218cc..a0b2976 100644
--- a/core/definitions.mk
+++ b/core/definitions.mk
@@ -142,11 +142,27 @@
endef
###########################################################
+## Remove any makefiles that are being handled by soong
+###########################################################
+ifeq ($(USE_SOONG),true)
+define filter-soong-makefiles
+$(foreach mk,$(1),\
+ $(if $(wildcard $(patsubst %/Android.mk,%/Android.bp,$(mk))),\
+ $(info skipping $(mk) ...),\
+ $(mk)))
+endef
+else
+define filter-soong-makefiles
+$(1)
+endef
+endif
+
+###########################################################
## Retrieve a list of all makefiles immediately below some directory
###########################################################
define all-makefiles-under
-$(sort $(wildcard $(1)/*/Android.mk))
+$(sort $(call filter-soong-makefiles,$(wildcard $(1)/*/Android.mk)))
endef
###########################################################
@@ -157,8 +173,9 @@
# $(1): directory to search under
# Ignores $(1)/Android.mk
define first-makefiles-under
-$(shell build/tools/findleaves.py $(FIND_LEAVES_EXCLUDES) \
- --mindepth=2 $(1) Android.mk)
+$(call filter-soong-makefiles,\
+ $(shell build/tools/findleaves.py $(FIND_LEAVES_EXCLUDES) \
+ --mindepth=2 $(1) Android.mk))
endef
###########################################################
@@ -178,7 +195,8 @@
# $(1): List of directories to look for under this directory
define all-named-subdir-makefiles
-$(sort $(wildcard $(addsuffix /Android.mk, $(addprefix $(call my-dir)/,$(1)))))
+$(sort $(call filter-soong-makefiles,\
+ $(wildcard $(addsuffix /Android.mk, $(addprefix $(call my-dir)/,$(1))))))
endef
###########################################################
@@ -856,7 +874,7 @@
endif
###########################################################
-## Commands for munging the dependency files GCC generates
+## Commands for munging the dependency files the compiler generates
###########################################################
# $(1): the input .d file
# $(2): the output .P file
@@ -872,10 +890,61 @@
endef
###########################################################
+## Commands for including the dependency files the compiler generates
+###########################################################
+# $(1): the .P file
+# $(2): the main build target
+ifeq ($(BUILDING_WITH_NINJA),true)
+define include-depfile
+$(eval $(2) : .KATI_DEPFILE := $1)
+endef
+else
+define include-depfile
+$(eval -include $1)
+endef
+endif
+
+###########################################################
+## Track source files compiled to objects
+###########################################################
+# $(1): list of sources
+# $(2): list of matching objects
+define track-src-file-obj
+$(eval $(call _track-src-file-obj,$(1)))
+endef
+define _track-src-file-obj
+i := w
+$(foreach s,$(1),
+my_tracked_src_files += $(s)
+my_src_file_obj_$(s) := $$(word $$(words $$(i)),$$(2))
+i += w)
+endef
+
+# $(1): list of sources
+# $(2): list of matching generated sources
+define track-src-file-gen
+$(eval $(call _track-src-file-gen,$(2)))
+endef
+define _track-src-file-gen
+i := w
+$(foreach s,$(1),
+my_tracked_gen_files += $(s)
+my_src_file_gen_$(s) := $$(word $$(words $$(i)),$$(1))
+i += w)
+endef
+
+# $(1): list of generated sources
+# $(2): list of matching objects
+define track-gen-file-obj
+$(call track-src-file-obj,$(foreach f,$(1),\
+ $(or $(my_src_file_gen_$(f)),$(f))),$(2))
+endef
+
+###########################################################
## Commands for running lex
###########################################################
-define transform-l-to-cpp
+define transform-l-to-c-or-cpp
@echo "Lex: $(PRIVATE_MODULE) <= $<"
@mkdir -p $(dir $@)
$(hide) $(LEX) -o$@ $<
@@ -884,20 +953,14 @@
###########################################################
## Commands for running yacc
##
-## Because the extension of c++ files can change, the
-## extension must be specified in $1.
-## E.g, "$(call transform-y-to-cpp,.cpp)"
###########################################################
-define transform-y-to-cpp
+define transform-y-to-c-or-cpp
@echo "Yacc: $(PRIVATE_MODULE) <= $<"
@mkdir -p $(dir $@)
-$(YACC) $(PRIVATE_YACCFLAGS) -o $@ $<
-touch $(@:$1=$(YACC_HEADER_SUFFIX))
-echo '#ifndef '$(@F:$1=_h) > $(@:$1=.h)
-echo '#define '$(@F:$1=_h) >> $(@:$1=.h)
-cat $(@:$1=$(YACC_HEADER_SUFFIX)) >> $(@:$1=.h)
-echo '#endif' >> $(@:$1=.h)
+$(YACC) $(PRIVATE_YACCFLAGS) \
+ --defines=$(basename $@).h \
+ -o $@ $<
endef
###########################################################
@@ -942,6 +1005,24 @@
## Commands to compile RenderScript to C++
###########################################################
+## Merge multiple .d files generated by llvm-rs-cc. This is necessary
+## because ninja can handle only a single depfile per build target.
+## We assume .d files start with two targets and their prerequisites
+## follow. The first line is for the stamp file and the second line is
+## for .bc file. There's no way to let ninja know dependencies to .bc
+## files, so we give up build targets for .bc files. As we write the
+## .stamp file as the target by ourselves, the sed script removes the
+## first two lines and append a backslash to the last line to
+## concatenate contents of multiple files.
+# $(1): .d files to be merged
+# $(2): merged .d file
+define _merge-renderscript-d
+$(hide) echo '$@: $(backslash)' > $2
+$(foreach d,$1, \
+ $(hide) sed '1d; 2d; s/\( \\\)\?$$/ \\/' $d >> $2$(newline))
+$(hide) echo >> $2
+endef
+
define transform-renderscripts-to-cpp-and-bc
@echo "RenderScript: $(PRIVATE_MODULE) <= $(PRIVATE_RS_SOURCE_FILES)"
$(hide) rm -rf $(PRIVATE_RS_OUTPUT_DIR)
@@ -955,8 +1036,8 @@
$(PRIVATE_RS_FLAGS) \
$(addprefix -I , $(PRIVATE_RS_INCLUDES)) \
$(PRIVATE_RS_SOURCE_FILES)
- $(foreach d,$(PRIVATE_DEP_FILES),\
- $(call transform-d-to-p-args,$(d),$(d:%.d=%.P))$(newline))
+$(call _merge-renderscript-d,$(PRIVATE_DEP_FILES),$@.d)
+$(call transform-d-to-p-args,$@.d,$@.P)
$(hide) mkdir -p $(dir $@)
$(hide) touch $@
endef
@@ -977,10 +1058,22 @@
@mkdir -p $(dir $@)
@mkdir -p $(PRIVATE_HEADER_OUTPUT_DIR)
@echo "Generating C++ from AIDL: $(PRIVATE_MODULE) <= $<"
-$(hide) $(AIDL_CPP) -d$(basename $@).P $(PRIVATE_AIDL_FLAGS) \
+$(hide) $(AIDL_CPP) -d$(basename $@).aidl.P $(PRIVATE_AIDL_FLAGS) \
$< $(PRIVATE_HEADER_OUTPUT_DIR) $@
endef
+## Given a .aidl file path generate the rule to compile it a .cpp file.
+# $(1): a .aidl source file
+# $(2): a directory to place the generated .cpp files in
+# $(3): name of a variable to add the path to the generated source file to
+#
+# You must call this with $(eval).
+define define-aidl-cpp-rule
+define-aidl-cpp-rule-src := $(patsubst %.aidl,%$(LOCAL_CPP_EXTENSION),$(subst ../,dotdot/,$(addprefix $(2)/,$(1))))
+$$(define-aidl-cpp-rule-src) : $(LOCAL_PATH)/$(1) $(AIDL_CPP)
+ $$(transform-aidl-to-cpp)
+$(3) += $$(define-aidl-cpp-rule-src)
+endef
###########################################################
## Commands for running java-event-log-tags.py
diff --git a/core/droiddoc.mk b/core/droiddoc.mk
index f791884..1b65611 100644
--- a/core/droiddoc.mk
+++ b/core/droiddoc.mk
@@ -209,7 +209,7 @@
\@$(PRIVATE_SRC_LIST_FILE) \
-J-Xmx1024m \
-XDignore.symbol.file \
- $(if $(EXPERIMENTAL_USE_JAVA8),-Xdoclint:none) \
+ $(if $(LEGACY_USE_JAVA7),,-Xdoclint:none) \
$(PRIVATE_PROFILING_OPTIONS) \
$(addprefix -classpath ,$(PRIVATE_CLASSPATH)) \
$(addprefix -bootclasspath ,$(PRIVATE_BOOTCLASSPATH)) \
diff --git a/core/envsetup.mk b/core/envsetup.mk
index 050d445..46c1463 100644
--- a/core/envsetup.mk
+++ b/core/envsetup.mk
@@ -154,6 +154,10 @@
ifeq ($(TARGET_ARCH),)
$(error TARGET_ARCH not defined by board config: $(board_config_mk))
endif
+ifneq ($(MALLOC_IMPL),)
+ $(warning *** Unsupported option MALLOC_IMPL defined by board config: $(board_config_mk).)
+ $(error Use `MALLOC_SVELTE := true` to configure jemalloc for low-memory)
+endif
TARGET_DEVICE_DIR := $(patsubst %/,%,$(dir $(board_config_mk)))
board_config_mk :=
@@ -426,3 +430,7 @@
ifeq ($(PRINT_BUILD_CONFIG),)
PRINT_BUILD_CONFIG := true
endif
+
+ifeq ($(USE_CLANG_PLATFORM_BUILD),)
+USE_CLANG_PLATFORM_BUILD := true
+endif
diff --git a/core/java.mk b/core/java.mk
index a0ab4c5..4680c19 100644
--- a/core/java.mk
+++ b/core/java.mk
@@ -309,7 +309,8 @@
$(AIDL) \
$(aidl_preprocess_import)
$(transform-aidl-to-java)
--include $(aidl_java_sources:%.java=%.P)
+$(foreach java,$(aidl_java_sources), \
+ $(call include-depfile,$(java:%.java=%.P),$(java)))
else
aidl_java_sources :=
diff --git a/core/main.mk b/core/main.mk
index d2f8414..549c825 100644
--- a/core/main.mk
+++ b/core/main.mk
@@ -57,6 +57,9 @@
BUILD_SYSTEM := $(TOPDIR)build/core
+# Ensure JAVA_NOT_REQUIRED is not set externally.
+JAVA_NOT_REQUIRED := false
+
# This is the default target. It must be the first declared target.
.PHONY: droid
DEFAULT_GOAL := droid
@@ -144,7 +147,7 @@
# Include the google-specific config
-include vendor/google/build/config.mk
-VERSION_CHECK_SEQUENCE_NUMBER := 5
+VERSION_CHECK_SEQUENCE_NUMBER := 6
-include $(OUT_DIR)/versions_checked.mk
ifneq ($(VERSION_CHECK_SEQUENCE_NUMBER),$(VERSIONS_CHECKED))
@@ -177,22 +180,23 @@
$(error Directory names containing spaces not supported)
endif
+ifeq ($(JAVA_NOT_REQUIRED), false)
java_version_str := $(shell unset _JAVA_OPTIONS && java -version 2>&1)
javac_version_str := $(shell unset _JAVA_OPTIONS && javac -version 2>&1)
-# Check for the correct version of java, should be 1.7 by
-# default, and 1.8 if EXPERIMENTAL_USE_JAVA8 is set
-ifneq ($(EXPERIMENTAL_USE_JAVA8),)
+# Check for the correct version of java, should be 1.8 by
+# default and only 1.7 if LEGACY_USE_JAVA7 is set.
+ifeq ($(LEGACY_USE_JAVA7),) # if LEGACY_USE_JAVA7 == ''
required_version := "1.8.x"
required_javac_version := "1.8"
java_version := $(shell echo '$(java_version_str)' | grep '[ "]1\.8[\. "$$]')
javac_version := $(shell echo '$(javac_version_str)' | grep '[ "]1\.8[\. "$$]')
-else # default
+else
required_version := "1.7.x"
required_javac_version := "1.7"
java_version := $(shell echo '$(java_version_str)' | grep '^java .*[ "]1\.7[\. "$$]')
javac_version := $(shell echo '$(javac_version_str)' | grep '[ "]1\.7[\. "$$]')
-endif # if EXPERIMENTAL_USE_JAVA8
+endif # if LEGACY_USE_JAVA7 == ''
ifeq ($(strip $(java_version)),)
$(info ************************************************************)
@@ -210,7 +214,7 @@
# Check for the current JDK.
#
-# For Java 1.7, we require OpenJDK on linux and Oracle JDK on Mac OS.
+# For Java 1.7/1.8, we require OpenJDK on linux and Oracle JDK on Mac OS.
requires_openjdk := false
ifeq ($(BUILD_OS),linux)
requires_openjdk := true
@@ -256,6 +260,7 @@
$(error stop)
endif
+endif # if JAVA_NOT_REQUIRED
ifndef BUILD_EMULATOR
# Emulator binaries are now provided under prebuilts/android-emulator/
@@ -534,11 +539,12 @@
# --mindepth=2 makes the prunes not work.
subdir_makefiles := \
$(shell build/tools/findleaves.py $(FIND_LEAVES_EXCLUDES) $(subdirs) Android.mk)
+
ifeq ($(USE_SOONG),true)
-subdir_makefiles := $(SOONG_ANDROID_MK) $(subdir_makefiles)
+subdir_makefiles := $(SOONG_ANDROID_MK) $(call filter-soong-makefiles,$(subdir_makefiles))
endif
-$(foreach mk, $(subdir_makefiles), $(info including $(mk) ...)$(eval include $(mk)))
+$(foreach mk, $(subdir_makefiles),$(info including $(mk) ...)$(eval include $(mk)))
endif # dont_bother
diff --git a/core/ninja.mk b/core/ninja.mk
index 750aae5..2878514 100644
--- a/core/ninja.mk
+++ b/core/ninja.mk
@@ -1,6 +1,15 @@
+NINJA ?= prebuilts/ninja/$(HOST_PREBUILT_TAG)/ninja
+
+ifeq ($(USE_SOONG),true)
+USE_SOONG_FOR_KATI := true
+endif
+
+ifeq ($(USE_SOONG_FOR_KATI),true)
+include $(BUILD_SYSTEM)/soong.mk
+else
KATI ?= $(HOST_OUT_EXECUTABLES)/ckati
MAKEPARALLEL ?= $(HOST_OUT_EXECUTABLES)/makeparallel
-NINJA ?= prebuilts/ninja/$(HOST_PREBUILT_TAG)/ninja
+endif
KATI_OUTPUT_PATTERNS := $(OUT_DIR)/build%.ninja $(OUT_DIR)/ninja%.sh
@@ -119,8 +128,6 @@
endif
ifeq ($(USE_SOONG),true)
-include $(BUILD_SYSTEM)/soong.mk
-
COMBINED_BUILD_NINJA := $(OUT_DIR)/combined$(KATI_NINJA_SUFFIX).ninja
$(COMBINED_BUILD_NINJA): $(KATI_BUILD_NINJA) $(SOONG_ANDROID_MK)
@@ -147,6 +154,7 @@
@echo Running kati to generate build$(KATI_NINJA_SUFFIX).ninja...
+$(hide) $(KATI_MAKEPARALLEL) $(KATI) --ninja --ninja_dir=$(OUT_DIR) --ninja_suffix=$(KATI_NINJA_SUFFIX) --regen --ignore_dirty=$(OUT_DIR)/% --no_ignore_dirty=$(SOONG_ANDROID_MK) --ignore_optional_include=$(OUT_DIR)/%.P --detect_android_echo $(KATI_FIND_EMULATOR) -f build/core/main.mk $(KATI_GOALS) --gen_all_targets BUILDING_WITH_NINJA=true SOONG_ANDROID_MK=$(SOONG_ANDROID_MK)
+ifneq ($(USE_SOONG_FOR_KATI),true)
KATI_CXX := $(CLANG_CXX) $(CLANG_HOST_GLOBAL_CFLAGS) $(CLANG_HOST_GLOBAL_CPPFLAGS)
KATI_LD := $(CLANG_CXX) $(CLANG_HOST_GLOBAL_LDFLAGS)
# Build static ckati. Unfortunately Mac OS X doesn't officially support static exectuables.
@@ -168,6 +176,7 @@
MAKEPARALLEL_INTERMEDIATES_PATH := $(HOST_OUT_INTERMEDIATES)/EXECUTABLES/makeparallel_intermediates
MAKEPARALLEL_BIN_PATH := $(HOST_OUT_EXECUTABLES)
include build/tools/makeparallel/Makefile
+endif
.PHONY: FORCE
FORCE:
diff --git a/core/prebuilt_internal.mk b/core/prebuilt_internal.mk
index 601a79c..6d9ded4 100644
--- a/core/prebuilt_internal.mk
+++ b/core/prebuilt_internal.mk
@@ -26,11 +26,14 @@
else
ifdef LOCAL_SRC_FILES_$($(my_prefix)$(LOCAL_2ND_ARCH_VAR_PREFIX)ARCH)
my_prebuilt_src_file := $(LOCAL_PATH)/$(LOCAL_SRC_FILES_$($(my_prefix)$(LOCAL_2ND_ARCH_VAR_PREFIX)ARCH))
+ LOCAL_SRC_FILES_$($(my_prefix)$(LOCAL_2ND_ARCH_VAR_PREFIX)ARCH)) :=
else
ifdef LOCAL_SRC_FILES_$(my_32_64_bit_suffix)
my_prebuilt_src_file := $(LOCAL_PATH)/$(LOCAL_SRC_FILES_$(my_32_64_bit_suffix))
+ LOCAL_SRC_FILES_$(my_32_64_bit_suffix) :=
else
my_prebuilt_src_file := $(LOCAL_PATH)/$(LOCAL_SRC_FILES)
+ LOCAL_SRC_FILES :=
endif
endif
endif
diff --git a/core/soong.mk b/core/soong.mk
index aab26de..646c68e 100644
--- a/core/soong.mk
+++ b/core/soong.mk
@@ -4,6 +4,9 @@
SOONG_ANDROID_MK := $(SOONG_OUT_DIR)/Android.mk
SOONG_VARIABLES := $(SOONG_OUT_DIR)/soong.variables
SOONG_IN_MAKE := $(SOONG_OUT_DIR)/.soong.in_make
+SOONG_HOST_EXECUTABLES := $(SOONG_OUT_DIR)/host/$(HOST_PREBUILT_TAG)/bin
+KATI := $(SOONG_HOST_EXECUTABLES)/ckati
+MAKEPARALLEL := $(SOONG_HOST_EXECUTABLES)/makeparallel
ifeq (,$(filter /%,$(SOONG_OUT_DIR)))
SOONG_TOP_RELPATH := $(shell python -c "import os; print os.path.relpath('$(TOP)', '$(SOONG_OUT_DIR)')")
@@ -57,4 +60,7 @@
# Build an Android.mk listing all soong outputs as prebuilts
$(SOONG_ANDROID_MK): $(SOONG) $(SOONG_VARIABLES) $(SOONG_IN_MAKE) FORCE
- $(hide) $(SOONG) $(SOONG_BUILD_NINJA) $(NINJA_ARGS)
+ $(hide) $(SOONG) $(KATI) $(MAKEPARALLEL) $(NINJA_ARGS)
+
+$(KATI): $(SOONG_ANDROID_MK)
+$(MAKEPARALLEL): $(SOONG_ANDROID_MK)
diff --git a/core/static_java_library.mk b/core/static_java_library.mk
index 9b7b46a..d956124 100644
--- a/core/static_java_library.mk
+++ b/core/static_java_library.mk
@@ -23,6 +23,10 @@
LOCAL_IS_STATIC_JAVA_LIBRARY := true
LOCAL_MODULE_CLASS := JAVA_LIBRARIES
+#################################
+include $(BUILD_SYSTEM)/configure_local_jack.mk
+#################################
+
# Hack to build static Java library with Android resource
# See bug 5714516
all_resources :=
@@ -59,10 +63,6 @@
LOCAL_PROGUARD_FLAGS := $(addprefix -include ,$(proguard_options_file)) $(LOCAL_PROGUARD_FLAGS)
-#################################
-include $(BUILD_SYSTEM)/configure_local_jack.mk
-#################################
-
ifdef LOCAL_JACK_ENABLED
ifndef LOCAL_JACK_PROGUARD_FLAGS
LOCAL_JACK_PROGUARD_FLAGS := $(LOCAL_PROGUARD_FLAGS)
diff --git a/envsetup.sh b/envsetup.sh
index f266f1a..61b9ff0 100644
--- a/envsetup.sh
+++ b/envsetup.sh
@@ -1426,7 +1426,8 @@
fi
if [ ! "$JAVA_HOME" ]; then
- if [ ! "$EXPERIMENTAL_USE_JAVA8" ]; then
+ if [ -n "$LEGACY_USE_JAVA7" ]; then
+ echo Warning: Support for JDK 7 will be dropped. Switch to JDK 8.
case `uname -s` in
Darwin)
export JAVA_HOME=$(/usr/libexec/java_home -v 1.7)
diff --git a/target/board/generic/sepolicy/property_contexts b/target/board/generic/sepolicy/property_contexts
index 152f0c8..142b062 100644
--- a/target/board/generic/sepolicy/property_contexts
+++ b/target/board/generic/sepolicy/property_contexts
@@ -1,5 +1,5 @@
qemu. u:object_r:qemu_prop:s0
-emu. u:object_r:qemu_prop:s0
-emulator. u:object_r:qemu_prop:s0
-radio.noril u:object_r:radio_noril_prop:s0
-opengles. u:object_r:opengles_prop:s0
+ro.emu. u:object_r:qemu_prop:s0
+ro.emulator. u:object_r:qemu_prop:s0
+ro.radio.noril u:object_r:radio_noril_prop:s0
+ro.opengles. u:object_r:opengles_prop:s0
diff --git a/target/product/embedded.mk b/target/product/embedded.mk
index 2428585..71456dc 100644
--- a/target/product/embedded.mk
+++ b/target/product/embedded.mk
@@ -22,6 +22,7 @@
adbd \
atrace \
bootanimation \
+ bootstat \
debuggerd \
dumpstate \
dumpsys \
@@ -41,6 +42,7 @@
libGLESv3 \
libbinder \
libc \
+ libc_malloc_debug \
libcutils \
libdl \
libgui \
diff --git a/target/product/security/README b/target/product/security/README
index 24f984c..15f2e93 100644
--- a/target/product/security/README
+++ b/target/product/security/README
@@ -1,3 +1,14 @@
+For detailed information on key types and image signing, please see:
+
+https://source.android.com/devices/tech/ota/sign_builds.html
+
+The test keys in this directory are used in development only and should
+NEVER be used to sign packages in publicly released images (as that would
+open a major security hole).
+
+key generation
+--------------
+
The following commands were used to generate the test key pairs:
development/tools/make_key testkey '/C=US/ST=California/L=Mountain View/O=Android/OU=Android/CN=Android/emailAddress=android@android.com'
@@ -5,18 +16,6 @@
development/tools/make_key shared '/C=US/ST=California/L=Mountain View/O=Android/OU=Android/CN=Android/emailAddress=android@android.com'
development/tools/make_key media '/C=US/ST=California/L=Mountain View/O=Android/OU=Android/CN=Android/emailAddress=android@android.com'
-The following standard test keys are currently included:
-
-testkey -- a generic key for packages that do not otherwise specify a key.
-platform -- a test key for packages that are part of the core platform.
-shared -- a test key for things that are shared in the home/contacts process.
-media -- a test key for packages that are part of the media/download system.
-
-These test keys are used strictly in development, and should never be assumed
-to convey any sort of validity. When $BUILD_SECURE=true, the code should not
-honor these keys in any context.
-
-
signing using the openssl commandline (for boot/system images)
--------------------------------------------------------------
@@ -28,7 +27,12 @@
extracting public keys for embedding
------------------------------------
-it's a Java tool
-but it generates C code
-take a look at commands/recovery/Android.mk
-you'll see it running $(HOST_OUT_JAVA_LIBRARIES)/dumpkey.jar
+
+dumpkey.jar is a Java tool that takes an x.509 certificate in PEM format as
+input and prints a C structure to standard output:
+
+ $ java -jar out/host/linux-x86/framework/dumpkey.jar build/target/product/security/testkey.x509.pem
+ {64,0xc926ad21,{1795090719,2141396315,950055447,2581568430,4268923165,1920809988,546586521,3498997798,1776797858,3740060814,1805317999,1429410244,129622599,1422441418,1783893377,1222374759,2563319927,323993566,28517732,609753416,1826472888,215237850,4261642700,4049082591,3228462402,774857746,154822455,2497198897,2758199418,3019015328,2794777644,87251430,2534927978,120774784,571297800,3695899472,2479925187,3811625450,3401832990,2394869647,3267246207,950095497,555058928,414729973,1136544882,3044590084,465547824,4058146728,2731796054,1689838846,3890756939,1048029507,895090649,247140249,178744550,3547885223,3165179243,109881576,3944604415,1044303212,3772373029,2985150306,3737520932,3599964420},{3437017481,3784475129,2800224972,3086222688,251333580,2131931323,512774938,325948880,2657486437,2102694287,3820568226,792812816,1026422502,2053275343,2800889200,3113586810,165549746,4273519969,4065247892,1902789247,772932719,3941848426,3652744109,216871947,3164400649,1942378755,3996765851,1055777370,964047799,629391717,2232744317,3910558992,191868569,2758883837,3682816752,2997714732,2702529250,3570700455,3776873832,3924067546,3555689545,2758825434,1323144535,61311905,1997411085,376844204,213777604,4077323584,9135381,1625809335,2804742137,2952293945,1117190829,4237312782,1825108855,3013147971,1111251351,2568837572,1684324211,2520978805,367251975,810756730,2353784344,1175080310}}
+
+This is called by build/core/Makefile to incorporate the OTA signing keys
+into the recovery image.
diff --git a/tools/droiddoc/templates-ds/class.cs b/tools/droiddoc/templates-ds/class.cs
index 23e57ab..ffd8dcd 100644
--- a/tools/droiddoc/templates-ds/class.cs
+++ b/tools/droiddoc/templates-ds/class.cs
@@ -184,9 +184,9 @@
<tr class="<?cs if:count % #2 ?>alt-color<?cs /if ?> api apilevel-<?cs var:method.since ?>" >
<td class="jd-typecol"><nobr>
<?cs var:method.abstract ?>
- <?cs var:method.synchronized ?>
- <?cs var:method.final ?>
+ <?cs var:method.default ?>
<?cs var:method.static ?>
+ <?cs var:method.final ?>
<?cs call:type_link(method.generic) ?>
<?cs call:type_link(method.returnType) ?></nobr>
</td>
@@ -531,11 +531,11 @@
<div class="jd-details api apilevel-<?cs var:method.since ?>">
<h4 class="jd-details-title">
<span class="normal">
- <?cs var:method.scope ?>
- <?cs var:method.static ?>
- <?cs var:method.final ?>
- <?cs var:method.abstract ?>
- <?cs var:method.synchronized ?>
+ <?cs var:method.scope ?>
+ <?cs var:method.abstract ?>
+ <?cs var:method.default ?>
+ <?cs var:method.static ?>
+ <?cs var:method.final ?>
<?cs call:type_link(method.returnType) ?>
</span>
<span class="sympad"><?cs var:method.name ?></span>
diff --git a/tools/droiddoc/templates-ndk/class.cs b/tools/droiddoc/templates-ndk/class.cs
index 7aa99f9..e55ac31 100644
--- a/tools/droiddoc/templates-ndk/class.cs
+++ b/tools/droiddoc/templates-ndk/class.cs
@@ -188,9 +188,9 @@
<tr class="<?cs if:count % #2 ?>alt-color<?cs /if ?> api apilevel-<?cs var:method.since ?>" >
<td class="jd-typecol"><nobr>
<?cs var:method.abstract ?>
- <?cs var:method.synchronized ?>
- <?cs var:method.final ?>
+ <?cs var:method.default ?>
<?cs var:method.static ?>
+ <?cs var:method.final ?>
<?cs call:type_link(method.generic) ?>
<?cs call:type_link(method.returnType) ?></nobr>
</td>
@@ -554,11 +554,11 @@
<div class="jd-details api apilevel-<?cs var:method.since ?>">
<h4 class="jd-details-title">
<span class="normal">
- <?cs var:method.scope ?>
- <?cs var:method.static ?>
- <?cs var:method.final ?>
- <?cs var:method.abstract ?>
- <?cs var:method.synchronized ?>
+ <?cs var:method.scope ?>
+ <?cs var:method.abstract ?>
+ <?cs var:method.default ?>
+ <?cs var:method.static ?>
+ <?cs var:method.final ?>
<?cs call:type_link(method.returnType) ?>
</span>
<span class="sympad"><?cs var:method.name ?></span>
diff --git a/tools/droiddoc/templates-sac/class.cs b/tools/droiddoc/templates-sac/class.cs
index 4003ff5..98633fb 100644
--- a/tools/droiddoc/templates-sac/class.cs
+++ b/tools/droiddoc/templates-sac/class.cs
@@ -185,9 +185,9 @@
<tr class="<?cs if:count % #2 ?>alt-color<?cs /if ?> api apilevel-<?cs var:method.since ?>" >
<td class="jd-typecol"><nobr>
<?cs var:method.abstract ?>
- <?cs var:method.synchronized ?>
- <?cs var:method.final ?>
+ <?cs var:method.default ?>
<?cs var:method.static ?>
+ <?cs var:method.final ?>
<?cs call:type_link(method.generic) ?>
<?cs call:type_link(method.returnType) ?></nobr>
</td>
@@ -535,11 +535,11 @@
<div class="jd-details api apilevel-<?cs var:method.since ?>">
<h4 class="jd-details-title">
<span class="normal">
- <?cs var:method.scope ?>
- <?cs var:method.static ?>
- <?cs var:method.final ?>
- <?cs var:method.abstract ?>
- <?cs var:method.synchronized ?>
+ <?cs var:method.scope ?>
+ <?cs var:method.abstract ?>
+ <?cs var:method.default ?>
+ <?cs var:method.static ?>
+ <?cs var:method.final ?>
<?cs call:type_link(method.returnType) ?>
</span>
<span class="sympad"><?cs var:method.name ?></span>
diff --git a/tools/droiddoc/templates-sac/footer.cs b/tools/droiddoc/templates-sac/footer.cs
index e38374a..e43adb0 100644
--- a/tools/droiddoc/templates-sac/footer.cs
+++ b/tools/droiddoc/templates-sac/footer.cs
@@ -2,7 +2,7 @@
<style>.feedback { float: right !Important }</style>
<div class="feedback">
<a href="#" class="button" onclick=" try {
- userfeedback.api.startFeedback({'productId':'715571','authuser':'1'});return false;}catch(e){}">Feedback on Site</a>
+ userfeedback.api.startFeedback({'productId':'715571','authuser':'1'});return false;}catch(e){}">Site Feedback</a>
</div>
<div id="copyright">
<?cs call:custom_cc_copyright() ?>
diff --git a/tools/droiddoc/templates-sdk-dev/class.cs b/tools/droiddoc/templates-sdk-dev/class.cs
index 693eaed..47e826b 100644
--- a/tools/droiddoc/templates-sdk-dev/class.cs
+++ b/tools/droiddoc/templates-sdk-dev/class.cs
@@ -190,9 +190,9 @@
<tr class="<?cs if:count % #2 ?>alt-color<?cs /if ?> api apilevel-<?cs var:method.since ?>" >
<td class="jd-typecol"><nobr>
<?cs var:method.abstract ?>
- <?cs var:method.synchronized ?>
- <?cs var:method.final ?>
+ <?cs var:method.default ?>
<?cs var:method.static ?>
+ <?cs var:method.final ?>
<?cs call:type_link(method.generic) ?>
<?cs call:type_link(method.returnType) ?></nobr>
</td>
@@ -556,11 +556,11 @@
<div class="jd-details api apilevel-<?cs var:method.since ?>">
<h4 class="jd-details-title">
<span class="normal">
- <?cs var:method.scope ?>
- <?cs var:method.static ?>
- <?cs var:method.final ?>
- <?cs var:method.abstract ?>
- <?cs var:method.synchronized ?>
+ <?cs var:method.scope ?>
+ <?cs var:method.abstract ?>
+ <?cs var:method.default ?>
+ <?cs var:method.static ?>
+ <?cs var:method.final ?>
<?cs call:type_link(method.returnType) ?>
</span>
<span class="sympad"><?cs var:method.name ?></span>
diff --git a/tools/droiddoc/templates-sdk/class.cs b/tools/droiddoc/templates-sdk/class.cs
index 44eae97..8312b25 100644
--- a/tools/droiddoc/templates-sdk/class.cs
+++ b/tools/droiddoc/templates-sdk/class.cs
@@ -188,9 +188,9 @@
<tr class="<?cs if:count % #2 ?>alt-color<?cs /if ?> api apilevel-<?cs var:method.since ?>" >
<td class="jd-typecol"><nobr>
<?cs var:method.abstract ?>
- <?cs var:method.synchronized ?>
- <?cs var:method.final ?>
+ <?cs var:method.default ?>
<?cs var:method.static ?>
+ <?cs var:method.final ?>
<?cs call:type_link(method.generic) ?>
<?cs call:type_link(method.returnType) ?></nobr>
</td>
@@ -554,11 +554,11 @@
<div class="jd-details api apilevel-<?cs var:method.since ?>">
<h4 class="jd-details-title">
<span class="normal">
- <?cs var:method.scope ?>
- <?cs var:method.static ?>
- <?cs var:method.final ?>
- <?cs var:method.abstract ?>
- <?cs var:method.synchronized ?>
+ <?cs var:method.scope ?>
+ <?cs var:method.abstract ?>
+ <?cs var:method.default ?>
+ <?cs var:method.static ?>
+ <?cs var:method.final ?>
<?cs call:type_link(method.returnType) ?>
</span>
<span class="sympad"><?cs var:method.name ?></span>
diff --git a/tools/makeparallel/Android.bp b/tools/makeparallel/Android.bp
new file mode 100644
index 0000000..cb81817
--- /dev/null
+++ b/tools/makeparallel/Android.bp
@@ -0,0 +1,26 @@
+// Copyright 2016 Google Inc. All rights reserved
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+cc_binary_host {
+ name: "makeparallel",
+ srcs: [
+ "makeparallel.cpp",
+ ],
+ cflags: ["-Wall", "-Werror"],
+ target: {
+ linux: {
+ host_ldlibs: ["-lrt", "-lpthread"],
+ },
+ },
+}
diff --git a/tools/makeparallel/makeparallel.cpp b/tools/makeparallel/makeparallel.cpp
index 576fe8d..cf125fa 100644
--- a/tools/makeparallel/makeparallel.cpp
+++ b/tools/makeparallel/makeparallel.cpp
@@ -343,7 +343,7 @@
// child
int ret = execvp(path, args.data());
if (ret < 0) {
- error(errno, errno, "exec failed");
+ error(errno, errno, "exec %s failed", path);
}
abort();
}
diff --git a/tools/releasetools/blockimgdiff.py b/tools/releasetools/blockimgdiff.py
index 1a5b93d..1d338ee 100644
--- a/tools/releasetools/blockimgdiff.py
+++ b/tools/releasetools/blockimgdiff.py
@@ -613,12 +613,15 @@
def_cmd = stashes[idx][1]
assert (idx, sr) in def_cmd.stash_before
def_cmd.stash_before.remove((idx, sr))
- new_blocks += sr.size()
+ # Add up blocks that violates space limit and print total number to
+ # screen later.
+ new_blocks += cmd.tgt_ranges.size()
cmd.ConvertToNew()
- print(" Total %d blocks are packed as new blocks due to insufficient "
- "cache size." % (new_blocks,))
+ num_of_bytes = new_blocks * self.tgt.blocksize
+ print(" Total %d blocks (%d bytes) are packed as new blocks due to "
+ "insufficient cache size." % (new_blocks, num_of_bytes))
def ComputePatches(self, prefix):
print("Reticulating splines...")
@@ -987,29 +990,36 @@
too many blocks (greater than MAX_BLOCKS_PER_DIFF_TRANSFER), we split it
into smaller pieces by getting multiple Transfer()s.
- The downside is that after splitting, we can no longer use imgdiff but
- only bsdiff."""
-
- MAX_BLOCKS_PER_DIFF_TRANSFER = 1024
+ The downside is that after splitting, we may increase the package size
+ since the split pieces don't align well. According to our experiments,
+ 1/8 of the cache size as the per-piece limit appears to be optimal.
+ Compared to the fixed 1024-block limit, it reduces the overall package
+ size by 30% volantis, and 20% for angler and bullhead."""
# We care about diff transfers only.
if style != "diff" or not split:
Transfer(tgt_name, src_name, tgt_ranges, src_ranges, style, by_id)
return
+ pieces = 0
+ cache_size = common.OPTIONS.cache_size
+ split_threshold = 0.125
+ max_blocks_per_transfer = int(cache_size * split_threshold /
+ self.tgt.blocksize)
+
# Change nothing for small files.
- if (tgt_ranges.size() <= MAX_BLOCKS_PER_DIFF_TRANSFER and
- src_ranges.size() <= MAX_BLOCKS_PER_DIFF_TRANSFER):
+ if (tgt_ranges.size() <= max_blocks_per_transfer and
+ src_ranges.size() <= max_blocks_per_transfer):
Transfer(tgt_name, src_name, tgt_ranges, src_ranges, style, by_id)
return
- pieces = 0
- while (tgt_ranges.size() > MAX_BLOCKS_PER_DIFF_TRANSFER and
- src_ranges.size() > MAX_BLOCKS_PER_DIFF_TRANSFER):
+ while (tgt_ranges.size() > max_blocks_per_transfer and
+ src_ranges.size() > max_blocks_per_transfer):
tgt_split_name = "%s-%d" % (tgt_name, pieces)
src_split_name = "%s-%d" % (src_name, pieces)
- tgt_first = tgt_ranges.first(MAX_BLOCKS_PER_DIFF_TRANSFER)
- src_first = src_ranges.first(MAX_BLOCKS_PER_DIFF_TRANSFER)
+ tgt_first = tgt_ranges.first(max_blocks_per_transfer)
+ src_first = src_ranges.first(max_blocks_per_transfer)
+
Transfer(tgt_split_name, src_split_name, tgt_first, src_first, style,
by_id)
diff --git a/tools/releasetools/ota_from_target_files.py b/tools/releasetools/ota_from_target_files.py
index d0636b6..a567760 100755
--- a/tools/releasetools/ota_from_target_files.py
+++ b/tools/releasetools/ota_from_target_files.py
@@ -109,6 +109,7 @@
import multiprocessing
import os
+import subprocess
import tempfile
import zipfile
@@ -1078,6 +1079,124 @@
WriteMetadata(metadata, output_zip)
+def WriteABOTAPackageWithBrilloScript(target_file, output_file,
+ source_file=None):
+ """Generate an Android OTA package that has A/B update payload."""
+
+ # Setup signing keys.
+ if OPTIONS.package_key is None:
+ OPTIONS.package_key = OPTIONS.info_dict.get(
+ "default_system_dev_certificate",
+ "build/target/product/security/testkey")
+
+ # A/B updater expects key in RSA format.
+ cmd = ["openssl", "pkcs8",
+ "-in", OPTIONS.package_key + OPTIONS.private_key_suffix,
+ "-inform", "DER", "-nocrypt"]
+ rsa_key = common.MakeTempFile(prefix="key-", suffix=".key")
+ cmd.extend(["-out", rsa_key])
+ p1 = common.Run(cmd, stdout=subprocess.PIPE)
+ p1.wait()
+ assert p1.returncode == 0, "openssl pkcs8 failed"
+
+ # Stage the output zip package for signing.
+ temp_zip_file = tempfile.NamedTemporaryFile()
+ output_zip = zipfile.ZipFile(temp_zip_file, "w",
+ compression=zipfile.ZIP_DEFLATED)
+
+ # Metadata to comply with Android OTA package format.
+ oem_props = OPTIONS.info_dict.get("oem_fingerprint_properties", None)
+ oem_dict = None
+ if oem_props:
+ if OPTIONS.oem_source is None:
+ raise common.ExternalError("OEM source required for this build")
+ oem_dict = common.LoadDictionaryFromLines(
+ open(OPTIONS.oem_source).readlines())
+
+ metadata = {
+ "post-build": CalculateFingerprint(oem_props, oem_dict,
+ OPTIONS.info_dict),
+ "pre-device": GetOemProperty("ro.product.device", oem_props, oem_dict,
+ OPTIONS.info_dict),
+ "post-timestamp": GetBuildProp("ro.build.date.utc", OPTIONS.info_dict),
+ }
+
+ if source_file is not None:
+ metadata["pre-build"] = CalculateFingerprint(oem_props, oem_dict,
+ OPTIONS.source_info_dict)
+
+ # 1. Generate payload.
+ payload_file = common.MakeTempFile(prefix="payload-", suffix=".bin")
+ cmd = ["brillo_update_payload", "generate",
+ "--payload", payload_file,
+ "--target_image", target_file]
+ if source_file is not None:
+ cmd.extend(["--source_image", source_file])
+ p1 = common.Run(cmd, stdout=subprocess.PIPE)
+ p1.wait()
+ assert p1.returncode == 0, "brillo_update_payload generate failed"
+
+ # 2. Generate hashes of the payload and metadata files.
+ payload_sig_file = common.MakeTempFile(prefix="sig-", suffix=".bin")
+ metadata_sig_file = common.MakeTempFile(prefix="sig-", suffix=".bin")
+ cmd = ["brillo_update_payload", "hash",
+ "--unsigned_payload", payload_file,
+ "--signature_size", "256",
+ "--metadata_hash_file", metadata_sig_file,
+ "--payload_hash_file", payload_sig_file]
+ p1 = common.Run(cmd, stdout=subprocess.PIPE)
+ p1.wait()
+ assert p1.returncode == 0, "brillo_update_payload hash failed"
+
+ # 3. Sign the hashes and insert them back into the payload file.
+ signed_payload_sig_file = common.MakeTempFile(prefix="signed-sig-",
+ suffix=".bin")
+ signed_metadata_sig_file = common.MakeTempFile(prefix="signed-sig-",
+ suffix=".bin")
+ # 3a. Sign the payload hash.
+ cmd = ["openssl", "pkeyutl", "-sign",
+ "-inkey", rsa_key,
+ "-pkeyopt", "digest:sha256",
+ "-in", payload_sig_file,
+ "-out", signed_payload_sig_file]
+ p1 = common.Run(cmd, stdout=subprocess.PIPE)
+ p1.wait()
+ assert p1.returncode == 0, "openssl sign payload failed"
+
+ # 3b. Sign the metadata hash.
+ cmd = ["openssl", "pkeyutl", "-sign",
+ "-inkey", rsa_key,
+ "-pkeyopt", "digest:sha256",
+ "-in", metadata_sig_file,
+ "-out", signed_metadata_sig_file]
+ p1 = common.Run(cmd, stdout=subprocess.PIPE)
+ p1.wait()
+ assert p1.returncode == 0, "openssl sign metadata failed"
+
+ # 3c. Insert the signatures back into the payload file.
+ signed_payload_file = common.MakeTempFile(prefix="signed-payload-",
+ suffix=".bin")
+ cmd = ["brillo_update_payload", "sign",
+ "--unsigned_payload", payload_file,
+ "--payload", signed_payload_file,
+ "--signature_size", "256",
+ "--metadata_signature_file", signed_metadata_sig_file,
+ "--payload_signature_file", signed_payload_sig_file]
+ p1 = common.Run(cmd, stdout=subprocess.PIPE)
+ p1.wait()
+ assert p1.returncode == 0, "brillo_update_payload sign failed"
+
+ # Add the signed payload file into the zip.
+ common.ZipWrite(output_zip, signed_payload_file, arcname="payload.bin",
+ compress_type=zipfile.ZIP_STORED)
+ WriteMetadata(metadata, output_zip)
+
+ # Sign the whole package to comply with the Android OTA package format.
+ common.ZipClose(output_zip)
+ SignOutput(temp_zip_file.name, output_file)
+ temp_zip_file.close()
+
+
class FileDifference(object):
def __init__(self, partition, source_zip, target_zip, output_zip):
self.deferred_patch_list = None
@@ -1683,6 +1802,37 @@
common.Usage(__doc__)
sys.exit(1)
+ # Load the dict file from the zip directly to have a peek at the OTA type.
+ # For packages using A/B update, unzipping is not needed.
+ input_zip = zipfile.ZipFile(args[0], "r")
+ OPTIONS.info_dict = common.LoadInfoDict(input_zip)
+ common.ZipClose(input_zip)
+
+ ab_update = OPTIONS.info_dict.get("ab_update") == "true"
+
+ if ab_update:
+ if OPTIONS.incremental_source is not None:
+ OPTIONS.target_info_dict = OPTIONS.info_dict
+ source_zip = zipfile.ZipFile(OPTIONS.incremental_source, "r")
+ OPTIONS.source_info_dict = common.LoadInfoDict(source_zip)
+ common.ZipClose(source_zip)
+
+ if OPTIONS.verbose:
+ print "--- target info ---"
+ common.DumpInfoDict(OPTIONS.info_dict)
+
+ if OPTIONS.incremental_source is not None:
+ print "--- source info ---"
+ common.DumpInfoDict(OPTIONS.source_info_dict)
+
+ WriteABOTAPackageWithBrilloScript(
+ target_file=args[0],
+ output_file=args[1],
+ source_file=OPTIONS.incremental_source)
+
+ print "done."
+ return
+
if OPTIONS.extra_script is not None:
OPTIONS.extra_script = open(OPTIONS.extra_script).read()
@@ -1714,9 +1864,7 @@
if OPTIONS.device_specific is not None:
OPTIONS.device_specific = os.path.abspath(OPTIONS.device_specific)
- ab_update = OPTIONS.info_dict.get("ab_update") == "true"
-
- if OPTIONS.info_dict.get("no_recovery") == "true" and not ab_update:
+ if OPTIONS.info_dict.get("no_recovery") == "true":
raise common.ExternalError(
"--- target build has specified no recovery ---")
@@ -1740,7 +1888,7 @@
# Non A/B OTAs rely on /cache partition to store temporary files.
cache_size = OPTIONS.info_dict.get("cache_size", None)
- if cache_size is None and not ab_update:
+ if cache_size is None:
print "--- can't determine the cache partition size ---"
OPTIONS.cache_size = cache_size
@@ -1750,11 +1898,7 @@
# Generate a full OTA.
elif OPTIONS.incremental_source is None:
- if ab_update:
- # TODO: Pending for b/25715402.
- pass
- else:
- WriteFullOTAPackage(input_zip, output_zip)
+ WriteFullOTAPackage(input_zip, output_zip)
# Generate an incremental OTA. It will fall back to generate a full OTA on
# failure unless no_fallback_to_full is specified.
diff --git a/tools/signapk/Android.mk b/tools/signapk/Android.mk
index c2cf572..ac217c7 100644
--- a/tools/signapk/Android.mk
+++ b/tools/signapk/Android.mk
@@ -26,6 +26,7 @@
include $(BUILD_HOST_JAVA_LIBRARY)
ifeq ($(TARGET_BUILD_APPS),)
+ifeq ($(BRILLO),)
# The post-build signing tools need signapk.jar and its shared libraries,
# but we don't need this if we're just doing unbundled apps.
my_dist_files := $(LOCAL_INSTALLED_MODULE) \
@@ -34,3 +35,4 @@
$(call dist-for-goals,droidcore,$(my_dist_files))
my_dist_files :=
endif
+endif
diff --git a/tools/signapk/src/com/android/signapk/ApkSignerV2.java b/tools/signapk/src/com/android/signapk/ApkSignerV2.java
new file mode 100644
index 0000000..ee5e72e
--- /dev/null
+++ b/tools/signapk/src/com/android/signapk/ApkSignerV2.java
@@ -0,0 +1,730 @@
+/*
+ * Copyright (C) 2016 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.signapk;
+
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.security.DigestException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.KeyFactory;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.Signature;
+import java.security.SignatureException;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509Certificate;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.MGF1ParameterSpec;
+import java.security.spec.PSSParameterSpec;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * APK Signature Scheme v2 signer.
+ *
+ * <p>APK Signature Scheme v2 is a whole-file signature scheme which aims to protect every single
+ * bit of the APK, as opposed to the JAR Signature Scheme which protects only the names and
+ * uncompressed contents of ZIP entries.
+ */
+public abstract class ApkSignerV2 {
+ /*
+ * The two main goals of APK Signature Scheme v2 are:
+ * 1. Detect any unauthorized modifications to the APK. This is achieved by making the signature
+ * cover every byte of the APK being signed.
+ * 2. Enable much faster signature and integrity verification. This is achieved by requiring
+ * only a minimal amount of APK parsing before the signature is verified, thus completely
+ * bypassing ZIP entry decompression and by making integrity verification parallelizable by
+ * employing a hash tree.
+ *
+ * The generated signature block is wrapped into an APK Signing Block and inserted into the
+ * original APK immediately before the start of ZIP Central Directory. This is to ensure that
+ * JAR and ZIP parsers continue to work on the signed APK. The APK Signing Block is designed for
+ * extensibility. For example, a future signature scheme could insert its signatures there as
+ * well. The contract of the APK Signing Block is that all contents outside of the block must be
+ * protected by signatures inside the block.
+ */
+
+ public static final int SIGNATURE_RSA_PSS_WITH_SHA256 = 0x0101;
+ public static final int SIGNATURE_RSA_PSS_WITH_SHA512 = 0x0102;
+ public static final int SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256 = 0x0103;
+ public static final int SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512 = 0x0104;
+ public static final int SIGNATURE_ECDSA_WITH_SHA256 = 0x0201;
+ public static final int SIGNATURE_ECDSA_WITH_SHA512 = 0x0202;
+ public static final int SIGNATURE_DSA_WITH_SHA256 = 0x0301;
+ public static final int SIGNATURE_DSA_WITH_SHA512 = 0x0302;
+
+ /**
+ * {@code .SF} file header section attribute indicating that the APK is signed not just with
+ * JAR signature scheme but also with APK Signature Scheme v2 or newer. This attribute
+ * facilitates v2 signature stripping detection.
+ *
+ * <p>The attribute contains a comma-separated set of signature scheme IDs.
+ */
+ public static final String SF_ATTRIBUTE_ANDROID_APK_SIGNED_NAME = "X-Android-APK-Signed";
+ // TODO: Adjust the value when signing scheme finalized.
+ public static final String SF_ATTRIBUTE_ANDROID_APK_SIGNED_VALUE = "1234567890";
+
+ private static final int CONTENT_DIGEST_CHUNKED_SHA256 = 0;
+ private static final int CONTENT_DIGEST_CHUNKED_SHA512 = 1;
+
+ private static final int CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES = 1024 * 1024;
+
+ private static final byte[] APK_SIGNING_BLOCK_MAGIC =
+ new byte[] {
+ 0x41, 0x50, 0x4b, 0x20, 0x53, 0x69, 0x67, 0x20,
+ 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x20, 0x34, 0x32,
+ };
+ private static final int APK_SIGNATURE_SCHEME_V2_BLOCK_ID = 0x7109871a;
+
+ private ApkSignerV2() {}
+
+ /**
+ * Signer configuration.
+ */
+ public static final class SignerConfig {
+ /** Private key. */
+ public PrivateKey privateKey;
+
+ /**
+ * Certificates, with the first certificate containing the public key corresponding to
+ * {@link #privateKey}.
+ */
+ public List<X509Certificate> certificates;
+
+ /**
+ * List of signature algorithms with which to sign (see {@code SIGNATURE_...} constants).
+ */
+ public List<Integer> signatureAlgorithms;
+ }
+
+ /**
+ * Signs the provided APK using APK Signature Scheme v2 and returns the signed APK as a list of
+ * consecutive chunks.
+ *
+ * <p>NOTE: To enable APK signature verifier to detect v2 signature stripping, header sections
+ * of META-INF/*.SF files of APK being signed must contain the
+ * {@code X-Android-APK-Signed: true} attribute.
+ *
+ * @param inputApk contents of the APK to be signed. The APK starts at the current position
+ * of the buffer and ends at the limit of the buffer.
+ * @param signerConfigs signer configurations, one for each signer.
+ *
+ * @throws ApkParseException if the APK cannot be parsed.
+ * @throws InvalidKeyException if a signing key is not suitable for this signature scheme or
+ * cannot be used in general.
+ * @throws SignatureException if an error occurs when computing digests of generating
+ * signatures.
+ */
+ public static ByteBuffer[] sign(
+ ByteBuffer inputApk,
+ List<SignerConfig> signerConfigs)
+ throws ApkParseException, InvalidKeyException, SignatureException {
+ // Slice/create a view in the inputApk to make sure that:
+ // 1. inputApk is what's between position and limit of the original inputApk, and
+ // 2. changes to position, limit, and byte order are not reflected in the original.
+ ByteBuffer originalInputApk = inputApk;
+ inputApk = originalInputApk.slice();
+ inputApk.order(ByteOrder.LITTLE_ENDIAN);
+
+ // Locate ZIP End of Central Directory (EoCD), Central Directory, and check that Central
+ // Directory is immediately followed by the ZIP End of Central Directory.
+ int eocdOffset = ZipUtils.findZipEndOfCentralDirectoryRecord(inputApk);
+ if (eocdOffset == -1) {
+ throw new ApkParseException("Failed to locate ZIP End of Central Directory");
+ }
+ if (ZipUtils.isZip64EndOfCentralDirectoryLocatorPresent(inputApk, eocdOffset)) {
+ throw new ApkParseException("ZIP64 format not supported");
+ }
+ inputApk.position(eocdOffset);
+ long centralDirSizeLong = ZipUtils.getZipEocdCentralDirectorySizeBytes(inputApk);
+ if (centralDirSizeLong > Integer.MAX_VALUE) {
+ throw new ApkParseException(
+ "ZIP Central Directory size out of range: " + centralDirSizeLong);
+ }
+ int centralDirSize = (int) centralDirSizeLong;
+ long centralDirOffsetLong = ZipUtils.getZipEocdCentralDirectoryOffset(inputApk);
+ if (centralDirOffsetLong > Integer.MAX_VALUE) {
+ throw new ApkParseException(
+ "ZIP Central Directory offset in file out of range: " + centralDirOffsetLong);
+ }
+ int centralDirOffset = (int) centralDirOffsetLong;
+ int expectedEocdOffset = centralDirOffset + centralDirSize;
+ if (expectedEocdOffset < centralDirOffset) {
+ throw new ApkParseException(
+ "ZIP Central Directory extent too large. Offset: " + centralDirOffset
+ + ", size: " + centralDirSize);
+ }
+ if (eocdOffset != expectedEocdOffset) {
+ throw new ApkParseException(
+ "ZIP Central Directory not immeiately followed by ZIP End of"
+ + " Central Directory. CD end: " + expectedEocdOffset
+ + ", EoCD start: " + eocdOffset);
+ }
+
+ // Create ByteBuffers holding the contents of everything before ZIP Central Directory,
+ // ZIP Central Directory, and ZIP End of Central Directory.
+ inputApk.clear();
+ ByteBuffer beforeCentralDir = getByteBuffer(inputApk, centralDirOffset);
+ ByteBuffer centralDir = getByteBuffer(inputApk, eocdOffset - centralDirOffset);
+ // Create a copy of End of Central Directory because we'll need modify its contents later.
+ byte[] eocdBytes = new byte[inputApk.remaining()];
+ inputApk.get(eocdBytes);
+ ByteBuffer eocd = ByteBuffer.wrap(eocdBytes);
+ eocd.order(inputApk.order());
+
+ // Figure which which digests to use for APK contents.
+ Set<Integer> contentDigestAlgorithms = new HashSet<>();
+ for (SignerConfig signerConfig : signerConfigs) {
+ for (int signatureAlgorithm : signerConfig.signatureAlgorithms) {
+ contentDigestAlgorithms.add(
+ getSignatureAlgorithmContentDigestAlgorithm(signatureAlgorithm));
+ }
+ }
+
+ // Compute digests of APK contents.
+ Map<Integer, byte[]> contentDigests; // digest algorithm ID -> digest
+ try {
+ contentDigests =
+ computeContentDigests(
+ contentDigestAlgorithms,
+ new ByteBuffer[] {beforeCentralDir, centralDir, eocd});
+ } catch (DigestException e) {
+ throw new SignatureException("Failed to compute digests of APK", e);
+ }
+
+ // Sign the digests and wrap the signatures and signer info into an APK Signing Block.
+ ByteBuffer apkSigningBlock =
+ ByteBuffer.wrap(generateApkSigningBlock(signerConfigs, contentDigests));
+
+ // Update Central Directory Offset in End of Central Directory Record. Central Directory
+ // follows the APK Signing Block and thus is shifted by the size of the APK Signing Block.
+ centralDirOffset += apkSigningBlock.remaining();
+ eocd.clear();
+ ZipUtils.setZipEocdCentralDirectoryOffset(eocd, centralDirOffset);
+
+ // Follow the Java NIO pattern for ByteBuffer whose contents have been consumed.
+ originalInputApk.position(originalInputApk.limit());
+
+ // Reset positions (to 0) and limits (to capacity) in the ByteBuffers below to follow the
+ // Java NIO pattern for ByteBuffers which are ready for their contents to be read by caller.
+ // Contrary to the name, this does not clear the contents of these ByteBuffer.
+ beforeCentralDir.clear();
+ centralDir.clear();
+ eocd.clear();
+
+ // Insert APK Signing Block immediately before the ZIP Central Directory.
+ return new ByteBuffer[] {
+ beforeCentralDir,
+ apkSigningBlock,
+ centralDir,
+ eocd,
+ };
+ }
+
+ private static Map<Integer, byte[]> computeContentDigests(
+ Set<Integer> digestAlgorithms,
+ ByteBuffer[] contents) throws DigestException {
+ // For each digest algorithm the result is computed as follows:
+ // 1. Each segment of contents is split into consecutive chunks of 1 MB in size.
+ // The final chunk will be shorter iff the length of segment is not a multiple of 1 MB.
+ // No chunks are produced for empty (zero length) segments.
+ // 2. The digest of each chunk is computed over the concatenation of byte 0xa5, the chunk's
+ // length in bytes (uint32 little-endian) and the chunk's contents.
+ // 3. The output digest is computed over the concatenation of the byte 0x5a, the number of
+ // chunks (uint32 little-endian) and the concatenation of digests of chunks of all
+ // segments in-order.
+
+ int chunkCount = 0;
+ for (ByteBuffer input : contents) {
+ chunkCount += getChunkCount(input.remaining(), CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES);
+ }
+
+ final Map<Integer, byte[]> digestsOfChunks = new HashMap<>(digestAlgorithms.size());
+ for (int digestAlgorithm : digestAlgorithms) {
+ int digestOutputSizeBytes = getContentDigestAlgorithmOutputSizeBytes(digestAlgorithm);
+ byte[] concatenationOfChunkCountAndChunkDigests =
+ new byte[5 + chunkCount * digestOutputSizeBytes];
+ concatenationOfChunkCountAndChunkDigests[0] = 0x5a;
+ setUnsignedInt32LittleEngian(
+ chunkCount, concatenationOfChunkCountAndChunkDigests, 1);
+ digestsOfChunks.put(digestAlgorithm, concatenationOfChunkCountAndChunkDigests);
+ }
+
+ int chunkIndex = 0;
+ byte[] chunkContentPrefix = new byte[5];
+ chunkContentPrefix[0] = (byte) 0xa5;
+ // Optimization opportunity: digests of chunks can be computed in parallel.
+ for (ByteBuffer input : contents) {
+ while (input.hasRemaining()) {
+ int chunkSize =
+ Math.min(input.remaining(), CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES);
+ final ByteBuffer chunk = getByteBuffer(input, chunkSize);
+ for (int digestAlgorithm : digestAlgorithms) {
+ String jcaAlgorithmName =
+ getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm);
+ MessageDigest md;
+ try {
+ md = MessageDigest.getInstance(jcaAlgorithmName);
+ } catch (NoSuchAlgorithmException e) {
+ throw new DigestException(
+ jcaAlgorithmName + " MessageDigest not supported", e);
+ }
+ // Reset position to 0 and limit to capacity. Position would've been modified
+ // by the preceding iteration of this loop. NOTE: Contrary to the method name,
+ // this does not modify the contents of the chunk.
+ chunk.clear();
+ setUnsignedInt32LittleEngian(chunk.remaining(), chunkContentPrefix, 1);
+ md.update(chunkContentPrefix);
+ md.update(chunk);
+ byte[] concatenationOfChunkCountAndChunkDigests =
+ digestsOfChunks.get(digestAlgorithm);
+ int expectedDigestSizeBytes =
+ getContentDigestAlgorithmOutputSizeBytes(digestAlgorithm);
+ int actualDigestSizeBytes =
+ md.digest(
+ concatenationOfChunkCountAndChunkDigests,
+ 5 + chunkIndex * expectedDigestSizeBytes,
+ expectedDigestSizeBytes);
+ if (actualDigestSizeBytes != expectedDigestSizeBytes) {
+ throw new DigestException(
+ "Unexpected output size of " + md.getAlgorithm()
+ + " digest: " + actualDigestSizeBytes);
+ }
+ }
+ chunkIndex++;
+ }
+ }
+
+ Map<Integer, byte[]> result = new HashMap<>(digestAlgorithms.size());
+ for (Map.Entry<Integer, byte[]> entry : digestsOfChunks.entrySet()) {
+ int digestAlgorithm = entry.getKey();
+ byte[] concatenationOfChunkCountAndChunkDigests = entry.getValue();
+ String jcaAlgorithmName = getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm);
+ MessageDigest md;
+ try {
+ md = MessageDigest.getInstance(jcaAlgorithmName);
+ } catch (NoSuchAlgorithmException e) {
+ throw new DigestException(jcaAlgorithmName + " MessageDigest not supported", e);
+ }
+ result.put(digestAlgorithm, md.digest(concatenationOfChunkCountAndChunkDigests));
+ }
+ return result;
+ }
+
+ private static final int getChunkCount(int inputSize, int chunkSize) {
+ return (inputSize + chunkSize - 1) / chunkSize;
+ }
+
+ private static void setUnsignedInt32LittleEngian(int value, byte[] result, int offset) {
+ result[offset] = (byte) (value & 0xff);
+ result[offset + 1] = (byte) ((value >> 8) & 0xff);
+ result[offset + 2] = (byte) ((value >> 16) & 0xff);
+ result[offset + 3] = (byte) ((value >> 24) & 0xff);
+ }
+
+ private static byte[] generateApkSigningBlock(
+ List<SignerConfig> signerConfigs,
+ Map<Integer, byte[]> contentDigests) throws InvalidKeyException, SignatureException {
+ byte[] apkSignatureSchemeV2Block =
+ generateApkSignatureSchemeV2Block(signerConfigs, contentDigests);
+ return generateApkSigningBlock(apkSignatureSchemeV2Block);
+ }
+
+ private static byte[] generateApkSigningBlock(byte[] apkSignatureSchemeV2Block) {
+ // FORMAT:
+ // uint64: size (excluding this field)
+ // repeated ID-value pairs:
+ // uint64: size (excluding this field)
+ // uint32: ID
+ // (size - 4) bytes: value
+ // uint64: size (same as the one above)
+ // uint128: magic
+
+ int resultSize =
+ 8 // size
+ + 8 + 4 + apkSignatureSchemeV2Block.length // v2Block as ID-value pair
+ + 8 // size
+ + 16 // magic
+ ;
+ ByteBuffer result = ByteBuffer.allocate(resultSize);
+ result.order(ByteOrder.LITTLE_ENDIAN);
+ long blockSizeFieldValue = resultSize - 8;
+ result.putLong(blockSizeFieldValue);
+
+ long pairSizeFieldValue = 4 + apkSignatureSchemeV2Block.length;
+ result.putLong(pairSizeFieldValue);
+ result.putInt(APK_SIGNATURE_SCHEME_V2_BLOCK_ID);
+ result.put(apkSignatureSchemeV2Block);
+
+ result.putLong(blockSizeFieldValue);
+ result.put(APK_SIGNING_BLOCK_MAGIC);
+
+ return result.array();
+ }
+
+ private static byte[] generateApkSignatureSchemeV2Block(
+ List<SignerConfig> signerConfigs,
+ Map<Integer, byte[]> contentDigests) throws InvalidKeyException, SignatureException {
+ // FORMAT:
+ // * length-prefixed sequence of length-prefixed signer blocks.
+
+ List<byte[]> signerBlocks = new ArrayList<>(signerConfigs.size());
+ int signerNumber = 0;
+ for (SignerConfig signerConfig : signerConfigs) {
+ signerNumber++;
+ byte[] signerBlock;
+ try {
+ signerBlock = generateSignerBlock(signerConfig, contentDigests);
+ } catch (InvalidKeyException e) {
+ throw new InvalidKeyException("Signer #" + signerNumber + " failed", e);
+ } catch (SignatureException e) {
+ throw new SignatureException("Signer #" + signerNumber + " failed", e);
+ }
+ signerBlocks.add(signerBlock);
+ }
+
+ return encodeAsSequenceOfLengthPrefixedElements(
+ new byte[][] {
+ encodeAsSequenceOfLengthPrefixedElements(signerBlocks),
+ });
+ }
+
+ private static byte[] generateSignerBlock(
+ SignerConfig signerConfig,
+ Map<Integer, byte[]> contentDigests) throws InvalidKeyException, SignatureException {
+ if (signerConfig.certificates.isEmpty()) {
+ throw new SignatureException("No certificates configured for signer");
+ }
+ PublicKey publicKey = signerConfig.certificates.get(0).getPublicKey();
+
+ byte[] encodedPublicKey = encodePublicKey(publicKey);
+
+ V2SignatureSchemeBlock.SignedData signedData = new V2SignatureSchemeBlock.SignedData();
+ try {
+ signedData.certificates = encodeCertificates(signerConfig.certificates);
+ } catch (CertificateEncodingException e) {
+ throw new SignatureException("Failed to encode certificates", e);
+ }
+
+ List<Pair<Integer, byte[]>> digests =
+ new ArrayList<>(signerConfig.signatureAlgorithms.size());
+ for (int signatureAlgorithm : signerConfig.signatureAlgorithms) {
+ int contentDigestAlgorithm =
+ getSignatureAlgorithmContentDigestAlgorithm(signatureAlgorithm);
+ byte[] contentDigest = contentDigests.get(contentDigestAlgorithm);
+ if (contentDigest == null) {
+ throw new RuntimeException(
+ getContentDigestAlgorithmJcaDigestAlgorithm(contentDigestAlgorithm)
+ + " content digest for "
+ + getSignatureAlgorithmJcaSignatureAlgorithm(signatureAlgorithm)
+ + " not computed");
+ }
+ digests.add(Pair.create(signatureAlgorithm, contentDigest));
+ }
+ signedData.digests = digests;
+
+ V2SignatureSchemeBlock.Signer signer = new V2SignatureSchemeBlock.Signer();
+ // FORMAT:
+ // * length-prefixed sequence of length-prefixed digests:
+ // * uint32: signature algorithm ID
+ // * length-prefixed bytes: digest of contents
+ // * length-prefixed sequence of certificates:
+ // * length-prefixed bytes: X.509 certificate (ASN.1 DER encoded).
+ // * length-prefixed sequence of length-prefixed additional attributes:
+ // * uint32: ID
+ // * (length - 4) bytes: value
+ signer.signedData = encodeAsSequenceOfLengthPrefixedElements(new byte[][] {
+ encodeAsSequenceOfLengthPrefixedPairsOfIntAndLengthPrefixedBytes(signedData.digests),
+ encodeAsSequenceOfLengthPrefixedElements(signedData.certificates),
+ // additional attributes
+ new byte[0],
+ });
+ signer.publicKey = encodedPublicKey;
+ signer.signatures = new ArrayList<>();
+ for (int signatureAlgorithm : signerConfig.signatureAlgorithms) {
+ Pair<String, ? extends AlgorithmParameterSpec> signatureParams =
+ getSignatureAlgorithmJcaSignatureAlgorithm(signatureAlgorithm);
+ String jcaSignatureAlgorithm = signatureParams.getFirst();
+ AlgorithmParameterSpec jcaSignatureAlgorithmParams = signatureParams.getSecond();
+ byte[] signatureBytes;
+ try {
+ Signature signature = Signature.getInstance(jcaSignatureAlgorithm);
+ signature.initSign(signerConfig.privateKey);
+ if (jcaSignatureAlgorithmParams != null) {
+ signature.setParameter(jcaSignatureAlgorithmParams);
+ }
+ signature.update(signer.signedData);
+ signatureBytes = signature.sign();
+ } catch (InvalidKeyException e) {
+ throw new InvalidKeyException("Failed sign using " + jcaSignatureAlgorithm, e);
+ } catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException
+ | SignatureException e) {
+ throw new SignatureException("Failed sign using " + jcaSignatureAlgorithm, e);
+ }
+
+ try {
+ Signature signature = Signature.getInstance(jcaSignatureAlgorithm);
+ signature.initVerify(publicKey);
+ if (jcaSignatureAlgorithmParams != null) {
+ signature.setParameter(jcaSignatureAlgorithmParams);
+ }
+ signature.update(signer.signedData);
+ if (!signature.verify(signatureBytes)) {
+ throw new SignatureException("Signature did not verify");
+ }
+ } catch (InvalidKeyException e) {
+ throw new InvalidKeyException("Failed to verify generated " + jcaSignatureAlgorithm
+ + " signature using public key from certificate", e);
+ } catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException
+ | SignatureException e) {
+ throw new SignatureException("Failed to verify generated " + jcaSignatureAlgorithm
+ + " signature using public key from certificate", e);
+ }
+
+ signer.signatures.add(Pair.create(signatureAlgorithm, signatureBytes));
+ }
+
+ // FORMAT:
+ // * length-prefixed signed data
+ // * length-prefixed sequence of length-prefixed signatures:
+ // * uint32: signature algorithm ID
+ // * length-prefixed bytes: signature of signed data
+ // * length-prefixed bytes: public key (X.509 SubjectPublicKeyInfo, ASN.1 DER encoded)
+ return encodeAsSequenceOfLengthPrefixedElements(
+ new byte[][] {
+ signer.signedData,
+ encodeAsSequenceOfLengthPrefixedPairsOfIntAndLengthPrefixedBytes(
+ signer.signatures),
+ signer.publicKey,
+ });
+ }
+
+ private static final class V2SignatureSchemeBlock {
+ private static final class Signer {
+ public byte[] signedData;
+ public List<Pair<Integer, byte[]>> signatures;
+ public byte[] publicKey;
+ }
+
+ private static final class SignedData {
+ public List<Pair<Integer, byte[]>> digests;
+ public List<byte[]> certificates;
+ }
+ }
+
+ private static byte[] encodePublicKey(PublicKey publicKey) throws InvalidKeyException {
+ byte[] encodedPublicKey = null;
+ if ("X.509".equals(publicKey.getFormat())) {
+ encodedPublicKey = publicKey.getEncoded();
+ }
+ if (encodedPublicKey == null) {
+ try {
+ encodedPublicKey =
+ KeyFactory.getInstance(publicKey.getAlgorithm())
+ .getKeySpec(publicKey, X509EncodedKeySpec.class)
+ .getEncoded();
+ } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
+ throw new InvalidKeyException(
+ "Failed to obtain X.509 encoded form of public key " + publicKey
+ + " of class " + publicKey.getClass().getName(),
+ e);
+ }
+ }
+ if ((encodedPublicKey == null) || (encodedPublicKey.length == 0)) {
+ throw new InvalidKeyException(
+ "Failed to obtain X.509 encoded form of public key " + publicKey
+ + " of class " + publicKey.getClass().getName());
+ }
+ return encodedPublicKey;
+ }
+
+ public static List<byte[]> encodeCertificates(List<X509Certificate> certificates)
+ throws CertificateEncodingException {
+ List<byte[]> result = new ArrayList<>();
+ for (X509Certificate certificate : certificates) {
+ result.add(certificate.getEncoded());
+ }
+ return result;
+ }
+
+ private static byte[] encodeAsSequenceOfLengthPrefixedElements(List<byte[]> sequence) {
+ return encodeAsSequenceOfLengthPrefixedElements(
+ sequence.toArray(new byte[sequence.size()][]));
+ }
+
+ private static byte[] encodeAsSequenceOfLengthPrefixedElements(byte[][] sequence) {
+ int payloadSize = 0;
+ for (byte[] element : sequence) {
+ payloadSize += 4 + element.length;
+ }
+ ByteBuffer result = ByteBuffer.allocate(payloadSize);
+ result.order(ByteOrder.LITTLE_ENDIAN);
+ for (byte[] element : sequence) {
+ result.putInt(element.length);
+ result.put(element);
+ }
+ return result.array();
+ }
+
+ private static byte[] encodeAsSequenceOfLengthPrefixedPairsOfIntAndLengthPrefixedBytes(
+ List<Pair<Integer, byte[]>> sequence) {
+ int resultSize = 0;
+ for (Pair<Integer, byte[]> element : sequence) {
+ resultSize += 12 + element.getSecond().length;
+ }
+ ByteBuffer result = ByteBuffer.allocate(resultSize);
+ result.order(ByteOrder.LITTLE_ENDIAN);
+ for (Pair<Integer, byte[]> element : sequence) {
+ byte[] second = element.getSecond();
+ result.putInt(8 + second.length);
+ result.putInt(element.getFirst());
+ result.putInt(second.length);
+ result.put(second);
+ }
+ return result.array();
+ }
+
+ /**
+ * Relative <em>get</em> method for reading {@code size} number of bytes from the current
+ * position of this buffer.
+ *
+ * <p>This method reads the next {@code size} bytes at this buffer's current position,
+ * returning them as a {@code ByteBuffer} with start set to 0, limit and capacity set to
+ * {@code size}, byte order set to this buffer's byte order; and then increments the position by
+ * {@code size}.
+ */
+ private static ByteBuffer getByteBuffer(ByteBuffer source, int size) {
+ if (size < 0) {
+ throw new IllegalArgumentException("size: " + size);
+ }
+ int originalLimit = source.limit();
+ int position = source.position();
+ int limit = position + size;
+ if ((limit < position) || (limit > originalLimit)) {
+ throw new BufferUnderflowException();
+ }
+ source.limit(limit);
+ try {
+ ByteBuffer result = source.slice();
+ result.order(source.order());
+ source.position(limit);
+ return result;
+ } finally {
+ source.limit(originalLimit);
+ }
+ }
+
+ private static Pair<String, ? extends AlgorithmParameterSpec>
+ getSignatureAlgorithmJcaSignatureAlgorithm(int sigAlgorithm) {
+ switch (sigAlgorithm) {
+ case SIGNATURE_RSA_PSS_WITH_SHA256:
+ return Pair.create(
+ "SHA256withRSA/PSS",
+ new PSSParameterSpec(
+ "SHA-256", "MGF1", MGF1ParameterSpec.SHA256, 256 / 8, 1));
+ case SIGNATURE_RSA_PSS_WITH_SHA512:
+ return Pair.create(
+ "SHA512withRSA/PSS",
+ new PSSParameterSpec(
+ "SHA-512", "MGF1", MGF1ParameterSpec.SHA512, 512 / 8, 1));
+ case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256:
+ return Pair.create("SHA256withRSA", null);
+ case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512:
+ return Pair.create("SHA512withRSA", null);
+ case SIGNATURE_ECDSA_WITH_SHA256:
+ return Pair.create("SHA256withECDSA", null);
+ case SIGNATURE_ECDSA_WITH_SHA512:
+ return Pair.create("SHA512withECDSA", null);
+ case SIGNATURE_DSA_WITH_SHA256:
+ return Pair.create("SHA256withDSA", null);
+ case SIGNATURE_DSA_WITH_SHA512:
+ return Pair.create("SHA512withDSA", null);
+ default:
+ throw new IllegalArgumentException(
+ "Unknown signature algorithm: 0x"
+ + Long.toHexString(sigAlgorithm & 0xffffffff));
+ }
+ }
+
+ private static int getSignatureAlgorithmContentDigestAlgorithm(int sigAlgorithm) {
+ switch (sigAlgorithm) {
+ case SIGNATURE_RSA_PSS_WITH_SHA256:
+ case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256:
+ case SIGNATURE_ECDSA_WITH_SHA256:
+ case SIGNATURE_DSA_WITH_SHA256:
+ return CONTENT_DIGEST_CHUNKED_SHA256;
+ case SIGNATURE_RSA_PSS_WITH_SHA512:
+ case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512:
+ case SIGNATURE_ECDSA_WITH_SHA512:
+ case SIGNATURE_DSA_WITH_SHA512:
+ return CONTENT_DIGEST_CHUNKED_SHA512;
+ default:
+ throw new IllegalArgumentException(
+ "Unknown signature algorithm: 0x"
+ + Long.toHexString(sigAlgorithm & 0xffffffff));
+ }
+ }
+
+ private static String getContentDigestAlgorithmJcaDigestAlgorithm(int digestAlgorithm) {
+ switch (digestAlgorithm) {
+ case CONTENT_DIGEST_CHUNKED_SHA256:
+ return "SHA-256";
+ case CONTENT_DIGEST_CHUNKED_SHA512:
+ return "SHA-512";
+ default:
+ throw new IllegalArgumentException(
+ "Unknown content digest algorthm: " + digestAlgorithm);
+ }
+ }
+
+ private static int getContentDigestAlgorithmOutputSizeBytes(int digestAlgorithm) {
+ switch (digestAlgorithm) {
+ case CONTENT_DIGEST_CHUNKED_SHA256:
+ return 256 / 8;
+ case CONTENT_DIGEST_CHUNKED_SHA512:
+ return 512 / 8;
+ default:
+ throw new IllegalArgumentException(
+ "Unknown content digest algorthm: " + digestAlgorithm);
+ }
+ }
+
+ /**
+ * Indicates that APK file could not be parsed.
+ */
+ public static class ApkParseException extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ public ApkParseException(String message) {
+ super(message);
+ }
+
+ public ApkParseException(String message, Throwable cause) {
+ super(message, cause);
+ }
+ }
+}
diff --git a/tools/signapk/src/com/android/signapk/Pair.java b/tools/signapk/src/com/android/signapk/Pair.java
new file mode 100644
index 0000000..e4a6c92
--- /dev/null
+++ b/tools/signapk/src/com/android/signapk/Pair.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2016 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.signapk;
+
+/**
+ * Pair of two elements.
+ */
+public final class Pair<A, B> {
+ private final A mFirst;
+ private final B mSecond;
+
+ private Pair(A first, B second) {
+ mFirst = first;
+ mSecond = second;
+ }
+
+ public static <A, B> Pair<A, B> create(A first, B second) {
+ return new Pair<A, B>(first, second);
+ }
+
+ public A getFirst() {
+ return mFirst;
+ }
+
+ public B getSecond() {
+ return mSecond;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((mFirst == null) ? 0 : mFirst.hashCode());
+ result = prime * result + ((mSecond == null) ? 0 : mSecond.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ @SuppressWarnings("rawtypes")
+ Pair other = (Pair) obj;
+ if (mFirst == null) {
+ if (other.mFirst != null) {
+ return false;
+ }
+ } else if (!mFirst.equals(other.mFirst)) {
+ return false;
+ }
+ if (mSecond == null) {
+ if (other.mSecond != null) {
+ return false;
+ }
+ } else if (!mSecond.equals(other.mSecond)) {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/tools/signapk/src/com/android/signapk/SignApk.java b/tools/signapk/src/com/android/signapk/SignApk.java
index 5afb8d1..8f40220 100644
--- a/tools/signapk/src/com/android/signapk/SignApk.java
+++ b/tools/signapk/src/com/android/signapk/SignApk.java
@@ -51,13 +51,16 @@
import java.io.OutputStream;
import java.io.PrintStream;
import java.lang.reflect.Constructor;
+import java.nio.ByteBuffer;
import java.security.DigestOutputStream;
import java.security.GeneralSecurityException;
+import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyFactory;
import java.security.MessageDigest;
import java.security.PrivateKey;
import java.security.Provider;
+import java.security.PublicKey;
import java.security.Security;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateFactory;
@@ -67,8 +70,11 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.List;
import java.util.Locale;
import java.util.Map;
+import java.util.TimeZone;
import java.util.TreeMap;
import java.util.jar.Attributes;
import java.util.jar.JarEntry;
@@ -101,7 +107,8 @@
/**
* Command line tool to sign JAR files (including APKs and OTA updates) in a way
* compatible with the mincrypt verifier, using EC or RSA keys and SHA1 or
- * SHA-256 (see historical note).
+ * SHA-256 (see historical note). The tool can additionally sign APKs using
+ * APK Signature Scheme v2.
*/
class SignApk {
private static final String CERT_SF_NAME = "META-INF/CERT.SF";
@@ -115,15 +122,28 @@
private static final int USE_SHA1 = 1;
private static final int USE_SHA256 = 2;
+ /** Digest algorithm used when signing the APK using APK Signature Scheme v2. */
+ private static final String APK_SIG_SCHEME_V2_DIGEST_ALGORITHM = "SHA-256";
+
+ /**
+ * Minimum Android SDK API Level which accepts JAR signatures which use SHA-256. Older platform
+ * versions accept only SHA-1 signatures.
+ */
+ private static final int MIN_API_LEVEL_FOR_SHA256_JAR_SIGNATURES = 18;
+
/**
* Return one of USE_SHA1 or USE_SHA256 according to the signature
* algorithm specified in the cert.
*/
- private static int getDigestAlgorithm(X509Certificate cert) {
+ private static int getDigestAlgorithm(X509Certificate cert, int minSdkVersion) {
String sigAlg = cert.getSigAlgName().toUpperCase(Locale.US);
- if ("SHA1WITHRSA".equals(sigAlg) ||
- "MD5WITHRSA".equals(sigAlg)) { // see "HISTORICAL NOTE" above.
- return USE_SHA1;
+ if ("SHA1WITHRSA".equals(sigAlg) || "MD5WITHRSA".equals(sigAlg)) {
+ // see "HISTORICAL NOTE" above.
+ if (minSdkVersion < MIN_API_LEVEL_FOR_SHA256_JAR_SIGNATURES) {
+ return USE_SHA1;
+ } else {
+ return USE_SHA256;
+ }
} else if (sigAlg.startsWith("SHA256WITH")) {
return USE_SHA256;
} else {
@@ -133,10 +153,11 @@
}
/** Returns the expected signature algorithm for this key type. */
- private static String getSignatureAlgorithm(X509Certificate cert) {
+ private static String getSignatureAlgorithm(X509Certificate cert, int minSdkVersion) {
String keyType = cert.getPublicKey().getAlgorithm().toUpperCase(Locale.US);
if ("RSA".equalsIgnoreCase(keyType)) {
- if (getDigestAlgorithm(cert) == USE_SHA256) {
+ if ((minSdkVersion >= MIN_API_LEVEL_FOR_SHA256_JAR_SIGNATURES)
+ || (getDigestAlgorithm(cert, minSdkVersion) == USE_SHA256)) {
return "SHA256withRSA";
} else {
return "SHA1withRSA";
@@ -309,10 +330,24 @@
Attributes attr = null;
if (input != null) attr = input.getAttributes(name);
attr = attr != null ? new Attributes(attr) : new Attributes();
+ // Remove any previously computed digests from this entry's attributes.
+ for (Iterator<Object> i = attr.keySet().iterator(); i.hasNext();) {
+ Object key = i.next();
+ if (!(key instanceof Attributes.Name)) {
+ continue;
+ }
+ String attributeNameLowerCase =
+ ((Attributes.Name) key).toString().toLowerCase(Locale.US);
+ if (attributeNameLowerCase.endsWith("-digest")) {
+ i.remove();
+ }
+ }
+ // Add SHA-1 digest if requested
if (md_sha1 != null) {
attr.putValue("SHA1-Digest",
new String(Base64.encode(md_sha1.digest()), "ASCII"));
}
+ // Add SHA-256 digest if requested
if (md_sha256 != null) {
attr.putValue("SHA-256-Digest",
new String(Base64.encode(md_sha256.digest()), "ASCII"));
@@ -388,12 +423,22 @@
/** Write a .SF file with a digest of the specified manifest. */
private static void writeSignatureFile(Manifest manifest, OutputStream out,
- int hash)
+ int hash, boolean additionallySignedUsingAnApkSignatureScheme)
throws IOException, GeneralSecurityException {
Manifest sf = new Manifest();
Attributes main = sf.getMainAttributes();
main.putValue("Signature-Version", "1.0");
main.putValue("Created-By", "1.0 (Android SignApk)");
+ if (additionallySignedUsingAnApkSignatureScheme) {
+ // Add APK Signature Scheme v2 signature stripping protection.
+ // This attribute indicates that this APK is supposed to have been signed using one or
+ // more APK-specific signature schemes in addition to the standard JAR signature scheme
+ // used by this code. APK signature verifier should reject the APK if it does not
+ // contain a signature for the signature scheme the verifier prefers out of this set.
+ main.putValue(
+ ApkSignerV2.SF_ATTRIBUTE_ANDROID_APK_SIGNED_NAME,
+ ApkSignerV2.SF_ATTRIBUTE_ANDROID_APK_SIGNED_VALUE);
+ }
MessageDigest md = MessageDigest.getInstance(
hash == USE_SHA256 ? "SHA256" : "SHA1");
@@ -418,7 +463,7 @@
print.flush();
Attributes sfAttr = new Attributes();
- sfAttr.putValue(hash == USE_SHA256 ? "SHA-256-Digest" : "SHA1-Digest-Manifest",
+ sfAttr.putValue(hash == USE_SHA256 ? "SHA-256-Digest" : "SHA1-Digest",
new String(Base64.encode(md.digest()), "ASCII"));
sf.getEntries().put(entry.getKey(), sfAttr);
}
@@ -438,7 +483,7 @@
/** Sign data and write the digital signature to 'out'. */
private static void writeSignatureBlock(
- CMSTypedData data, X509Certificate publicKey, PrivateKey privateKey,
+ CMSTypedData data, X509Certificate publicKey, PrivateKey privateKey, int minSdkVersion,
OutputStream out)
throws IOException,
CertificateEncodingException,
@@ -449,8 +494,9 @@
JcaCertStore certs = new JcaCertStore(certList);
CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
- ContentSigner signer = new JcaContentSignerBuilder(getSignatureAlgorithm(publicKey))
- .build(privateKey);
+ ContentSigner signer =
+ new JcaContentSignerBuilder(getSignatureAlgorithm(publicKey, minSdkVersion))
+ .build(privateKey);
gen.addSignerInfoGenerator(
new JcaSignerInfoGeneratorBuilder(
new JcaDigestCalculatorProviderBuilder()
@@ -631,21 +677,25 @@
}
private static class CMSSigner implements CMSTypedData {
- private JarFile inputJar;
- private File publicKeyFile;
- private X509Certificate publicKey;
- private PrivateKey privateKey;
- private OutputStream outputStream;
+ private final JarFile inputJar;
+ private final File publicKeyFile;
+ private final X509Certificate publicKey;
+ private final PrivateKey privateKey;
+ private final long timestamp;
+ private final int minSdkVersion;
+ private final OutputStream outputStream;
private final ASN1ObjectIdentifier type;
private WholeFileSignerOutputStream signer;
public CMSSigner(JarFile inputJar, File publicKeyFile,
- X509Certificate publicKey, PrivateKey privateKey,
- OutputStream outputStream) {
+ X509Certificate publicKey, PrivateKey privateKey, long timestamp,
+ int minSdkVersion, OutputStream outputStream) {
this.inputJar = inputJar;
this.publicKeyFile = publicKeyFile;
this.publicKey = publicKey;
this.privateKey = privateKey;
+ this.timestamp = timestamp;
+ this.minSdkVersion = minSdkVersion;
this.outputStream = outputStream;
this.type = new ASN1ObjectIdentifier(CMSObjectIdentifiers.data.getId());
}
@@ -670,7 +720,7 @@
signer = new WholeFileSignerOutputStream(out, outputStream);
JarOutputStream outputJar = new JarOutputStream(signer);
- int hash = getDigestAlgorithm(publicKey);
+ int hash = getDigestAlgorithm(publicKey, minSdkVersion);
// Assume the certificate is valid for at least an hour.
long timestamp = publicKey.getNotBefore().getTime() + 3600L * 1000;
@@ -682,6 +732,9 @@
signFile(manifest,
new X509Certificate[]{ publicKey },
new PrivateKey[]{ privateKey },
+ timestamp,
+ minSdkVersion,
+ false, // Don't sign using APK Signature Scheme v2
outputJar);
signer.notifyClosing();
@@ -698,7 +751,7 @@
CertificateEncodingException,
OperatorCreationException,
CMSException {
- SignApk.writeSignatureBlock(this, publicKey, privateKey, temp);
+ SignApk.writeSignatureBlock(this, publicKey, privateKey, minSdkVersion, temp);
}
public WholeFileSignerOutputStream getSigner() {
@@ -708,9 +761,10 @@
private static void signWholeFile(JarFile inputJar, File publicKeyFile,
X509Certificate publicKey, PrivateKey privateKey,
+ long timestamp, int minSdkVersion,
OutputStream outputStream) throws Exception {
CMSSigner cmsOut = new CMSSigner(inputJar, publicKeyFile,
- publicKey, privateKey, outputStream);
+ publicKey, privateKey, timestamp, minSdkVersion, outputStream);
ByteArrayOutputStream temp = new ByteArrayOutputStream();
@@ -776,10 +830,11 @@
private static void signFile(Manifest manifest,
X509Certificate[] publicKey, PrivateKey[] privateKey,
+ long timestamp,
+ int minSdkVersion,
+ boolean additionallySignedUsingAnApkSignatureScheme,
JarOutputStream outputJar)
throws Exception {
- // Assume the certificate is valid for at least an hour.
- long timestamp = publicKey[0].getNotBefore().getTime() + 3600L * 1000;
// MANIFEST.MF
JarEntry je = new JarEntry(JarFile.MANIFEST_NAME);
@@ -795,7 +850,11 @@
je.setTime(timestamp);
outputJar.putNextEntry(je);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
- writeSignatureFile(manifest, baos, getDigestAlgorithm(publicKey[k]));
+ writeSignatureFile(
+ manifest,
+ baos,
+ getDigestAlgorithm(publicKey[k], minSdkVersion),
+ additionallySignedUsingAnApkSignatureScheme);
byte[] signedData = baos.toByteArray();
outputJar.write(signedData);
@@ -807,7 +866,7 @@
je.setTime(timestamp);
outputJar.putNextEntry(je);
writeSignatureBlock(new CMSProcessableByteArray(signedData),
- publicKey[k], privateKey[k], outputJar);
+ publicKey[k], privateKey[k], minSdkVersion, outputJar);
}
}
@@ -863,10 +922,89 @@
Security.insertProviderAt((Provider) o, 1);
}
+ /**
+ * Converts the provided lists of private keys, their X.509 certificates, and digest algorithms
+ * into a list of APK Signature Scheme v2 {@code SignerConfig} instances.
+ */
+ public static List<ApkSignerV2.SignerConfig> createV2SignerConfigs(
+ PrivateKey[] privateKeys, X509Certificate[] certificates, String[] digestAlgorithms)
+ throws InvalidKeyException {
+ if (privateKeys.length != certificates.length) {
+ throw new IllegalArgumentException(
+ "The number of private keys must match the number of certificates: "
+ + privateKeys.length + " vs" + certificates.length);
+ }
+ List<ApkSignerV2.SignerConfig> result = new ArrayList<>(privateKeys.length);
+ for (int i = 0; i < privateKeys.length; i++) {
+ PrivateKey privateKey = privateKeys[i];
+ X509Certificate certificate = certificates[i];
+ PublicKey publicKey = certificate.getPublicKey();
+ String keyAlgorithm = privateKey.getAlgorithm();
+ if (!keyAlgorithm.equalsIgnoreCase(publicKey.getAlgorithm())) {
+ throw new InvalidKeyException(
+ "Key algorithm of private key #" + (i + 1) + " does not match key"
+ + " algorithm of public key #" + (i + 1) + ": " + keyAlgorithm
+ + " vs " + publicKey.getAlgorithm());
+ }
+ ApkSignerV2.SignerConfig signerConfig = new ApkSignerV2.SignerConfig();
+ signerConfig.privateKey = privateKey;
+ signerConfig.certificates = Collections.singletonList(certificate);
+ List<Integer> signatureAlgorithms = new ArrayList<>(digestAlgorithms.length);
+ for (String digestAlgorithm : digestAlgorithms) {
+ try {
+ signatureAlgorithms.add(
+ getV2SignatureAlgorithm(keyAlgorithm, digestAlgorithm));
+ } catch (IllegalArgumentException e) {
+ throw new InvalidKeyException(
+ "Unsupported key and digest algorithm combination for signer #"
+ + (i + 1),
+ e);
+ }
+ }
+ signerConfig.signatureAlgorithms = signatureAlgorithms;
+ result.add(signerConfig);
+ }
+ return result;
+ }
+
+ private static int getV2SignatureAlgorithm(String keyAlgorithm, String digestAlgorithm) {
+ if ("SHA-256".equalsIgnoreCase(digestAlgorithm)) {
+ if ("RSA".equalsIgnoreCase(keyAlgorithm)) {
+ // Use RSASSA-PKCS1-v1_5 signature scheme instead of RSASSA-PSS to guarantee
+ // deterministic signatures which make life easier for OTA updates (fewer files
+ // changed when deterministic signature schemes are used).
+ return ApkSignerV2.SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256;
+ } else if ("EC".equalsIgnoreCase(keyAlgorithm)) {
+ return ApkSignerV2.SIGNATURE_ECDSA_WITH_SHA256;
+ } else if ("DSA".equalsIgnoreCase(keyAlgorithm)) {
+ return ApkSignerV2.SIGNATURE_DSA_WITH_SHA256;
+ } else {
+ throw new IllegalArgumentException("Unsupported key algorithm: " + keyAlgorithm);
+ }
+ } else if ("SHA-512".equalsIgnoreCase(digestAlgorithm)) {
+ if ("RSA".equalsIgnoreCase(keyAlgorithm)) {
+ // Use RSASSA-PKCS1-v1_5 signature scheme instead of RSASSA-PSS to guarantee
+ // deterministic signatures which make life easier for OTA updates (fewer files
+ // changed when deterministic signature schemes are used).
+ return ApkSignerV2.SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512;
+ } else if ("EC".equalsIgnoreCase(keyAlgorithm)) {
+ return ApkSignerV2.SIGNATURE_ECDSA_WITH_SHA512;
+ } else if ("DSA".equalsIgnoreCase(keyAlgorithm)) {
+ return ApkSignerV2.SIGNATURE_DSA_WITH_SHA512;
+ } else {
+ throw new IllegalArgumentException("Unsupported key algorithm: " + keyAlgorithm);
+ }
+ } else {
+ throw new IllegalArgumentException("Unsupported digest algorithm: " + digestAlgorithm);
+ }
+ }
+
private static void usage() {
System.err.println("Usage: signapk [-w] " +
"[-a <alignment>] " +
"[-providerClass <className>] " +
+ "[--min-sdk-version <n>] " +
+ "[--disable-v2] " +
"publickey.x509[.pem] privatekey.pk8 " +
"[publickey2.x509[.pem] privatekey2.pk8 ...] " +
"input.jar output.jar");
@@ -887,6 +1025,8 @@
boolean signWholeFile = false;
String providerClass = null;
int alignment = 4;
+ int minSdkVersion = 0;
+ boolean signUsingApkSignatureSchemeV2 = true;
int argstart = 0;
while (argstart < args.length && args[argstart].startsWith("-")) {
@@ -902,6 +1042,18 @@
} else if ("-a".equals(args[argstart])) {
alignment = Integer.parseInt(args[++argstart]);
++argstart;
+ } else if ("--min-sdk-version".equals(args[argstart])) {
+ String minSdkVersionString = args[++argstart];
+ try {
+ minSdkVersion = Integer.parseInt(minSdkVersionString);
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException(
+ "--min-sdk-version must be a decimal number: " + minSdkVersionString);
+ }
+ ++argstart;
+ } else if ("--disable-v2".equals(args[argstart])) {
+ signUsingApkSignatureSchemeV2 = false;
+ ++argstart;
} else {
usage();
}
@@ -931,17 +1083,19 @@
for (int i = 0; i < numKeys; ++i) {
int argNum = argstart + i*2;
publicKey[i] = readPublicKey(new File(args[argNum]));
- hashes |= getDigestAlgorithm(publicKey[i]);
+ hashes |= getDigestAlgorithm(publicKey[i], minSdkVersion);
}
} catch (IllegalArgumentException e) {
System.err.println(e);
System.exit(1);
}
- // Set the ZIP file timestamp to the starting valid time
- // of the 0th certificate plus one hour (to match what
- // we've historically done).
- long timestamp = publicKey[0].getNotBefore().getTime() + 3600L * 1000;
+ // Set all ZIP file timestamps to Jan 1 2009 00:00:00.
+ long timestamp = 1230768000000L;
+ // The Java ZipEntry API we're using converts milliseconds since epoch into MS-DOS
+ // timestamp using the current timezone. We thus adjust the milliseconds since epoch
+ // value to end up with MS-DOS timestamp of Jan 1 2009 00:00:00.
+ timestamp -= TimeZone.getDefault().getOffset(timestamp);
PrivateKey[] privateKey = new PrivateKey[numKeys];
for (int i = 0; i < numKeys; ++i) {
@@ -952,25 +1106,59 @@
outputFile = new FileOutputStream(outputFilename);
-
+ // NOTE: Signing currently recompresses any compressed entries using Deflate (default
+ // compression level for OTA update files and maximum compession level for APKs).
if (signWholeFile) {
SignApk.signWholeFile(inputJar, firstPublicKeyFile,
- publicKey[0], privateKey[0], outputFile);
+ publicKey[0], privateKey[0],
+ timestamp, minSdkVersion,
+ outputFile);
} else {
- JarOutputStream outputJar = new JarOutputStream(outputFile);
-
- // For signing .apks, use the maximum compression to make
- // them as small as possible (since they live forever on
- // the system partition). For OTA packages, use the
- // default compression level, which is much much faster
- // and produces output that is only a tiny bit larger
- // (~0.1% on full OTA packages I tested).
+ // Generate, in memory, an APK signed using standard JAR Signature Scheme.
+ ByteArrayOutputStream v1SignedApkBuf = new ByteArrayOutputStream();
+ JarOutputStream outputJar = new JarOutputStream(v1SignedApkBuf);
+ // Use maximum compression for compressed entries because the APK lives forever on
+ // the system partition.
outputJar.setLevel(9);
-
Manifest manifest = addDigestsToManifest(inputJar, hashes);
copyFiles(manifest, inputJar, outputJar, timestamp, alignment);
- signFile(manifest, publicKey, privateKey, outputJar);
+ signFile(
+ manifest,
+ publicKey, privateKey,
+ timestamp, minSdkVersion, signUsingApkSignatureSchemeV2,
+ outputJar);
outputJar.close();
+ ByteBuffer v1SignedApk = ByteBuffer.wrap(v1SignedApkBuf.toByteArray());
+ v1SignedApkBuf.reset();
+
+ ByteBuffer[] outputChunks;
+ if (signUsingApkSignatureSchemeV2) {
+ // Additionally sign the APK using the APK Signature Scheme v2.
+ ByteBuffer apkContents = v1SignedApk;
+ List<ApkSignerV2.SignerConfig> signerConfigs =
+ createV2SignerConfigs(
+ privateKey,
+ publicKey,
+ new String[] {APK_SIG_SCHEME_V2_DIGEST_ALGORITHM});
+ outputChunks = ApkSignerV2.sign(apkContents, signerConfigs);
+ } else {
+ // Output the JAR-signed APK as is.
+ outputChunks = new ByteBuffer[] {v1SignedApk};
+ }
+
+ // This assumes outputChunks are array-backed. To avoid this assumption, the
+ // code could be rewritten to use FileChannel.
+ for (ByteBuffer outputChunk : outputChunks) {
+ outputFile.write(
+ outputChunk.array(),
+ outputChunk.arrayOffset() + outputChunk.position(),
+ outputChunk.remaining());
+ outputChunk.position(outputChunk.limit());
+ }
+
+ outputFile.close();
+ outputFile = null;
+ return;
}
} catch (Exception e) {
e.printStackTrace();
diff --git a/tools/signapk/src/com/android/signapk/ZipUtils.java b/tools/signapk/src/com/android/signapk/ZipUtils.java
new file mode 100644
index 0000000..b9a17ca
--- /dev/null
+++ b/tools/signapk/src/com/android/signapk/ZipUtils.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2016 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.signapk;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * Assorted ZIP format helpers.
+ *
+ * <p>NOTE: Most helper methods operating on {@code ByteBuffer} instances expect that the byte
+ * order of these buffers is little-endian.
+ */
+public abstract class ZipUtils {
+ private ZipUtils() {}
+
+ private static final int ZIP_EOCD_REC_MIN_SIZE = 22;
+ private static final int ZIP_EOCD_REC_SIG = 0x06054b50;
+ private static final int ZIP_EOCD_CENTRAL_DIR_SIZE_FIELD_OFFSET = 12;
+ private static final int ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_OFFSET = 16;
+ private static final int ZIP_EOCD_COMMENT_LENGTH_FIELD_OFFSET = 20;
+
+ private static final int ZIP64_EOCD_LOCATOR_SIZE = 20;
+ private static final int ZIP64_EOCD_LOCATOR_SIG = 0x07064b50;
+
+ private static final int UINT32_MAX_VALUE = 0xffff;
+
+ /**
+ * Returns the position at which ZIP End of Central Directory record starts in the provided
+ * buffer or {@code -1} if the record is not present.
+ *
+ * <p>NOTE: Byte order of {@code zipContents} must be little-endian.
+ */
+ public static int findZipEndOfCentralDirectoryRecord(ByteBuffer zipContents) {
+ assertByteOrderLittleEndian(zipContents);
+
+ // ZIP End of Central Directory (EOCD) record is located at the very end of the ZIP archive.
+ // The record can be identified by its 4-byte signature/magic which is located at the very
+ // beginning of the record. A complication is that the record is variable-length because of
+ // the comment field.
+ // The algorithm for locating the ZIP EOCD record is as follows. We search backwards from
+ // end of the buffer for the EOCD record signature. Whenever we find a signature, we check
+ // the candidate record's comment length is such that the remainder of the record takes up
+ // exactly the remaining bytes in the buffer. The search is bounded because the maximum
+ // size of the comment field is 65535 bytes because the field is an unsigned 32-bit number.
+
+ int archiveSize = zipContents.capacity();
+ if (archiveSize < ZIP_EOCD_REC_MIN_SIZE) {
+ System.out.println("File size smaller than EOCD min size");
+ return -1;
+ }
+ int maxCommentLength = Math.min(archiveSize - ZIP_EOCD_REC_MIN_SIZE, UINT32_MAX_VALUE);
+ int eocdWithEmptyCommentStartPosition = archiveSize - ZIP_EOCD_REC_MIN_SIZE;
+ for (int expectedCommentLength = 0; expectedCommentLength < maxCommentLength;
+ expectedCommentLength++) {
+ int eocdStartPos = eocdWithEmptyCommentStartPosition - expectedCommentLength;
+ if (zipContents.getInt(eocdStartPos) == ZIP_EOCD_REC_SIG) {
+ int actualCommentLength =
+ getUnsignedInt16(
+ zipContents, eocdStartPos + ZIP_EOCD_COMMENT_LENGTH_FIELD_OFFSET);
+ if (actualCommentLength == expectedCommentLength) {
+ return eocdStartPos;
+ }
+ }
+ }
+
+ return -1;
+ }
+
+ /**
+ * Returns {@code true} if the provided buffer contains a ZIP64 End of Central Directory
+ * Locator.
+ *
+ * <p>NOTE: Byte order of {@code zipContents} must be little-endian.
+ */
+ public static final boolean isZip64EndOfCentralDirectoryLocatorPresent(
+ ByteBuffer zipContents, int zipEndOfCentralDirectoryPosition) {
+ assertByteOrderLittleEndian(zipContents);
+
+ // ZIP64 End of Central Directory Locator immediately precedes the ZIP End of Central
+ // Directory Record.
+
+ int locatorPosition = zipEndOfCentralDirectoryPosition - ZIP64_EOCD_LOCATOR_SIZE;
+ if (locatorPosition < 0) {
+ return false;
+ }
+
+ return zipContents.getInt(locatorPosition) == ZIP64_EOCD_LOCATOR_SIG;
+ }
+
+ /**
+ * Returns the offset of the start of the ZIP Central Directory in the archive.
+ *
+ * <p>NOTE: Byte order of {@code zipEndOfCentralDirectory} must be little-endian.
+ */
+ public static long getZipEocdCentralDirectoryOffset(ByteBuffer zipEndOfCentralDirectory) {
+ assertByteOrderLittleEndian(zipEndOfCentralDirectory);
+ return getUnsignedInt32(
+ zipEndOfCentralDirectory,
+ zipEndOfCentralDirectory.position() + ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_OFFSET);
+ }
+
+ /**
+ * Sets the offset of the start of the ZIP Central Directory in the archive.
+ *
+ * <p>NOTE: Byte order of {@code zipEndOfCentralDirectory} must be little-endian.
+ */
+ public static void setZipEocdCentralDirectoryOffset(
+ ByteBuffer zipEndOfCentralDirectory, long offset) {
+ assertByteOrderLittleEndian(zipEndOfCentralDirectory);
+ setUnsignedInt32(
+ zipEndOfCentralDirectory,
+ zipEndOfCentralDirectory.position() + ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_OFFSET,
+ offset);
+ }
+
+ /**
+ * Returns the size (in bytes) of the ZIP Central Directory.
+ *
+ * <p>NOTE: Byte order of {@code zipEndOfCentralDirectory} must be little-endian.
+ */
+ public static long getZipEocdCentralDirectorySizeBytes(ByteBuffer zipEndOfCentralDirectory) {
+ assertByteOrderLittleEndian(zipEndOfCentralDirectory);
+ return getUnsignedInt32(
+ zipEndOfCentralDirectory,
+ zipEndOfCentralDirectory.position() + ZIP_EOCD_CENTRAL_DIR_SIZE_FIELD_OFFSET);
+ }
+
+ private static void assertByteOrderLittleEndian(ByteBuffer buffer) {
+ if (buffer.order() != ByteOrder.LITTLE_ENDIAN) {
+ throw new IllegalArgumentException("ByteBuffer byte order must be little endian");
+ }
+ }
+
+ private static int getUnsignedInt16(ByteBuffer buffer, int offset) {
+ return buffer.getShort(offset) & 0xffff;
+ }
+
+ private static long getUnsignedInt32(ByteBuffer buffer, int offset) {
+ return buffer.getInt(offset) & 0xffffffffL;
+ }
+
+ private static void setUnsignedInt32(ByteBuffer buffer, int offset, long value) {
+ if ((value < 0) || (value > 0xffffffffL)) {
+ throw new IllegalArgumentException("uint32 value of out range: " + value);
+ }
+ buffer.putInt(buffer.position() + offset, (int) value);
+ }
+}