Experimental script for compiling after a module update

Adds an experimental script to run on device to recompile jars in the
boot class path and system server class path that are not in APEXes.

Bug: 160683548
Test: adb root; adb shell \
  "set enforce 0; cd /data/local/tmp; /apex/com.android.art/bin/compile_bcp.sh"
Change-Id: I29d6a61508170e4f9fdc04fc384148b4b1f48870
diff --git a/build/apex/Android.bp b/build/apex/Android.bp
index b99742b..3639e13 100644
--- a/build/apex/Android.bp
+++ b/build/apex/Android.bp
@@ -118,12 +118,14 @@
 
 // Tools exclusively for the device APEX derived from art-tools in art/Android.mk.
 art_tools_device_only_binaries = [
+    "compile_bcp.sh",
     // oatdump cannot link with host linux_bionic due to not using clang lld;
     // TODO: Make it work with clang lld.
     "oatdump",
 ]
 // Same, but for only for debug packages.
 art_tools_debug_device_only_binaries = [
+    "compile_bcp.sh",
     // oatdumpd cannot link with host linux_bionic due to not using clang lld;
     // TODO: Make it work with clang lld.
     "oatdumpd",
diff --git a/build/apex/art_apex_test.py b/build/apex/art_apex_test.py
index 2c40244..d959d3f 100755
--- a/build/apex/art_apex_test.py
+++ b/build/apex/art_apex_test.py
@@ -519,6 +519,7 @@
     self._checker.check_file('apex_manifest.pb')
 
     # Check binaries for ART.
+    self._checker.check_executable("compile_bcp.sh")
     self._checker.check_first_executable('dex2oat')
     self._checker.check_executable('dexdump')
     self._checker.check_executable('dexlist')
@@ -613,6 +614,7 @@
     # removed in Android R.
 
     # Check binaries for ART.
+    self._checker.check_executable("compile_bcp.sh")
     self._checker.check_executable('oatdump')
     self._checker.check_multilib_executable('dex2oat')
 
diff --git a/tools/Android.bp b/tools/Android.bp
index 042eb19..f6733ce 100644
--- a/tools/Android.bp
+++ b/tools/Android.bp
@@ -74,3 +74,13 @@
         },
     },
 }
+
+cc_prebuilt_binary {
+    name: "compile_bcp.sh",
+    host_supported: false,
+    srcs: ["compile_bcp.sh"],
+    apex_available: [
+        "com.android.art.debug",
+        "com.android.art.release",
+    ],
+}
diff --git a/tools/compile_bcp.sh b/tools/compile_bcp.sh
new file mode 100755
index 0000000..397456b
--- /dev/null
+++ b/tools/compile_bcp.sh
@@ -0,0 +1,178 @@
+#!/system/bin/sh
+#
+# Copyright (C) 2020 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Script to recompile the non-APEX jars in the boot class path and
+# system server jars for on-device signing following an ART Module
+# update.
+#
+# For testing purposes, prepare Android device using:
+#
+#   $ adb root
+#   $ adb shell setenforce 0
+#
+# TODO: this is script is currently for proof-of-concept. It does not
+# respect many of the available dalvik.vm properties, e.g. affinity,
+# threads, etc nor dalvik.vm.dex2oat-resolve-startup-strings.
+#
+# TODO: some of the system server jars seem to be installed on device
+# for 32-bit and 64-bit, but services.jar artifacts are just one arch.
+# Not sure why both flavors are present. Does this script need to generate
+# both?
+#
+# TODO: Logging failure / error handling.
+
+# Output directory for generated files. Real location is TBD.
+output=$PWD/out
+
+function mkdir_clean() {
+  # mkdir_clean <dir_path>
+  local dir_path=$1
+  rm -rf "${dir_path}"
+  mkdir -p "${dir_path}"
+}
+
+# Determine candidate architectures for this device.
+case `getprop ro.product.cpu.abi` in
+  arm*)
+    arch32=arm
+    arch64=arm64
+    ;;
+  x86*)
+    arch32=x86
+    arch64=x86_64
+    ;;
+  *)
+    echo "Unknown abi"
+    exit -1
+esac
+
+# Determine architectures to use for Zygote. system_server runs under the primary
+# architecture. Prefer dex2oat64 if device supports 64-bits.
+case $(getprop ro.zygote) in
+  zygote32)
+    # The primary architecture is 32-bits.
+    archs="$arch32"
+    systemserver_arch="$arch32"
+    dex2oat=/apex/com.android.art/bin/dex2oat32
+    ;;
+  zygote32_64)
+    # The primary architecture is 32-bits and the secondary is 64-bits.
+    archs="$arch32 $arch64"
+    systemserver_arch="$arch32"
+    dex2oat=/apex/com.android.art/bin/dex2oat64
+    ;;
+  zygote64_32)
+    # The primary architecture is 64-bits and the secondary is 32-bits.
+    archs="$arch32 $arch64"
+    systemserver_arch="$arch64"
+    dex2oat=/apex/com.android.art/bin/dex2oat64
+    ;;
+  zygote64)
+    # Primary architecture is 64-bits.
+    archs="$arch64"
+    systemserver_arch="$arch64"
+    dex2oat=/apex/com.android.art/bin/dex2oat64
+    ;;
+  *)
+    echo "Unknown ro.zygote value"
+    exit -1
+    ;;
+esac
+
+# Determine which boot class path jars to compile.
+device_bcp_list=""
+device_bcp_dex_files=""
+for jar in ${DEX2OATBOOTCLASSPATH//:/ }; do
+  if [[ ${jar} = *com.android.art* ]]; then
+    continue
+  fi
+  device_bcp_list="${device_bcp_list}${device_bcp_list:+:}${jar}"
+  device_bcp_dex_files="$device_bcp_dex_files --dex-file=${jar}"
+done
+
+# Compile the boot class path elements that are present on device.
+for arch in ${archs}; do
+  arch_output="${output}/$arch"
+  mkdir_clean "${arch_output}"
+
+  invocation_dir="${output}/$arch"
+  mkdir_clean "${invocation_dir}"
+
+  echo "Compiling ${device_bcp_list} ($arch)"
+  ${dex2oat} --avoid-storing-invocation \
+    --compiler-filter=speed-profile \
+    --profile-file=/system/etc/boot-image.prof \
+    --dirty-image-objects=/system/etc/dirty-image-objects \
+    --runtime-arg -Xbootclasspath:${DEX2OATBOOTCLASSPATH} \
+    --boot-image=/apex/com.android.art/javalib/boot.art \
+    ${device_bcp_dex_files} \
+    --generate-debug-info \
+    --image-format=lz4hc \
+    --strip \
+    --oat-file=${arch_output}/boot.oat \
+    --image=${arch_output}/boot.art \
+    --android-root=out/empty \
+    --abort-on-hard-verifier-error \
+    --instruction-set=$arch \
+    --generate-mini-debug-info
+done
+
+# Compile system_server and related jars.
+classloader_context=""
+for jar in ${SYSTEMSERVERCLASSPATH//:/ }; do
+  # Skip class path components in APEXes
+  if [[ ${jar} = "/apex"* ]]; then
+    continue
+  fi
+
+  # Add profile if it exists. Only services.jar has a profile in AOSP.
+  stem=$(basename $jar .jar)
+  profile_file=/system/framework/${stem}.jar.prof
+  if [ -f "${profile_file}" ] ; then
+    profile_arg=--profile-file=${profile_file}
+    filter=speed-profile
+  else
+    profile_arg=""
+    filter=speed
+  fi
+
+  # Add updatable boot class path packages file if there is a property for it.
+  updatable_bcp_file=$(getprop dalvik.vm.dex2oat-updatable-bcp-packages-file)
+  if [ "${updatable_bcp_file}" -a -f "${updatable_bcp_file}" ] ; then
+    updatable_bcp_file_arg="--updatable-bcp-packages-file=${updatable_bcp_file}"
+  fi
+
+  echo "Compiling ${jar} (${systemserver_arch} ${filter} PCL[${classloader_context}])"
+  $dex2oat --avoid-storing-invocation \
+           --runtime-arg -Xbootclasspath:${DEX2OATBOOTCLASSPATH} \
+           --class-loader-context=PCL[${classloader_context}] \
+           --boot-image=/apex/com.android.art/javalib/boot.art:${output}/boot-framework.art \
+           --dex-file=${jar} \
+           --oat-file=${arch_output}/${stem}.odex \
+           --app-image-file=${oat_file_dir}/${stem}.art \
+           --android-root=out/empty \
+           --instruction-set=${systemserver_arch} \
+           --abort-on-hard-verifier-error \
+           --compiler-filter=$filter \
+           --generate-mini-debug-info \
+           --compilation-reason=prebuilt \
+           --image-format=lz4 \
+           --resolve-startup-const-strings=true \
+           ${profile_arg} \
+           ${updatable_bcp_file_arg}
+
+  classloader_context=${classloader_context}${classloader_context:+:}${jar}
+done