Generate a linker configuration file for chroot-based ART testing.

Build `/system/bin/linkerconfig` as part of ART chroot-based target
testing builds, include it in sync'd files, and invoke it in
`tools/buildbot-sync.sh` to generate a linker configuration file for
the chroot environment. Adjust the linker configuration file path
logic in `tools/buildbot-sync.sh` to match the one in top-of-tree
Bionic linker code.

Test: Run ART tests in a chroot environment on an Android R "host device"
Test: Run ART tests in a chroot environment on an Android Q "host device"
Bug: 34729697
Bug: 147737840
Bug: 148171362
Change-Id: I457d8c75988b6a9254b7ac9db3642f40f4271f1d
diff --git a/tools/buildbot-build.sh b/tools/buildbot-build.sh
index 7a7de45..fe5fb84 100755
--- a/tools/buildbot-build.sh
+++ b/tools/buildbot-build.sh
@@ -80,7 +80,12 @@
   make_command+=" libstdc++ "
   make_command+=" ${ANDROID_PRODUCT_OUT#"${ANDROID_BUILD_TOP}/"}/system/etc/public.libraries.txt"
   if [[ -n "$ART_TEST_CHROOT" ]]; then
-    # These targets are needed for the chroot environment.
+    # Targets required to generate a linker configuration on device within the
+    # chroot environment.
+    make_command+=" linkerconfig"
+    make_command+=" llndk.libraries.txt vndksp.libraries.txt vndkcore.libraries.txt"
+    make_command+=" vndkprivate.libraries.txt vndkcorevariant.libraries.txt sanitizer.libraries.txt"
+    # Additional targets needed for the chroot environment.
     make_command+=" crash_dump event-log-tags"
   fi
   # Build the Runtime (Bionic) APEX.
diff --git a/tools/buildbot-cleanup-device.sh b/tools/buildbot-cleanup-device.sh
index ebd6163..97e494a 100755
--- a/tools/buildbot-cleanup-device.sh
+++ b/tools/buildbot-cleanup-device.sh
@@ -32,6 +32,9 @@
   fi
 
   if adb shell test -d "$ART_TEST_CHROOT"; then
+    echo -e "${green}Remove entire /linkerconfig directory from chroot directory${nc}"
+    adb shell rm -rf "$ART_TEST_CHROOT/linkerconfig"
+
     echo -e "${green}Remove entire /system directory from chroot directory${nc}"
     adb shell rm -rf "$ART_TEST_CHROOT/system"
 
diff --git a/tools/buildbot-setup-device.sh b/tools/buildbot-setup-device.sh
index f2bf329..3178209 100755
--- a/tools/buildbot-setup-device.sh
+++ b/tools/buildbot-setup-device.sh
@@ -110,9 +110,6 @@
   for i in $processes; do adb shell kill -9 $i; done
 fi
 
-echo -e "${green}Set sys.linker.use_generated_config to false if file is absent"
-adb shell "test -f /linkerconfig/ld.config.txt || setprop sys.linker.use_generated_config false"
-
 # Chroot environment.
 # ===================
 
@@ -173,4 +170,12 @@
 
   # Create /apex directory in chroot.
   adb shell mkdir -p "$ART_TEST_CHROOT/apex"
+
+  # Create /linkerconfig directory in chroot.
+  adb shell mkdir -p "$ART_TEST_CHROOT/linkerconfig"
+  # Ensure the linker uses a generated linker configuration (we generate this
+  # linker configuration file on device in `tools/buildbot-sync.sh`, as
+  # `/system/bin/linkerconfig` requires some files to be present in
+  # `/system/etc`).
+  adb shell setprop sys.linker.use_generated_config true
 fi
diff --git a/tools/buildbot-sync.sh b/tools/buildbot-sync.sh
index 82b67f4..8a4b06e 100755
--- a/tools/buildbot-sync.sh
+++ b/tools/buildbot-sync.sh
@@ -78,17 +78,87 @@
 # the configuration file expected by the linker (governed by system properties
 # of the "host system").
 
-# Default linker configuration file name/stem.
+# Default linker configuration file name/stem (before Android R).
 ld_config_file_path="/system/etc/ld.config.txt";
 # VNDK-lite linker configuration file name.
 ld_config_vndk_lite_file_path="/system/etc/ld.config.vndk_lite.txt";
 
+# Statically linked `linkerconfig` binary.
+linkerconfig_binary="/system/bin/linkerconfig"
+# Generated linker configuration file path (since Android R).
+ld_generated_config_file_path="/linkerconfig/ld.config.txt"
+# Location of the generated linker configuration file.
+ld_generated_config_file_location=$(dirname "$ld_generated_config_file_path")
+
+# Return the file name passed as argument with the VNDK version of the "host
+# system" inserted before the file name's extension, if applicable. This mimics
+# the logic used in Bionic linker's `Config::get_vndk_version_string`.
+insert_vndk_version_string() {
+  local file_path="$1"
+  local vndk_version=$(adb shell getprop "ro.vndk.version")
+  if [[ -n "$vndk_version" ]] && [[ "$vndk_version" != current ]]; then
+    # Insert the VNDK version after the last period (and add another period).
+    file_path=$(echo "$file_path" \
+      | sed -e "s/^\\(.*\\)\\.\\([^.]\\)/\\1.${vndk_version}.\\2/")
+  fi
+  echo "$file_path"
+}
+
+# Adjust the names of the following files (sync'd to the device with the
+# previous `adb push` command) depending on the VNDK version of the "host
+# system":
+#
+#   /system/etc/llndk.libraries.R.txt
+#   /system/etc/vndkcore.libraries.R.txt
+#   /system/etc/vndkprivate.libraries.R.txt
+#   /system/etc/vndksp.libraries.R.txt
+#
+# Note that `/system/etc/vndkcorevariant.libraries.txt` does not have a version
+# number.
+#
+# See `build/soong/cc/vndk.go` and `packages/modules/vndk/Android.bp` for more
+# information.
+vndk_libraries_txt_file_names="llndk.libraries.txt \
+  vndkcore.libraries.txt \
+  vndkprivate.libraries.txt \
+  vndksp.libraries.txt"
+for file_name in $vndk_libraries_txt_file_names; do
+  pattern="$(basename $file_name .txt)\*.txt"
+  adb shell find "$ART_TEST_CHROOT/system/etc" -maxdepth 1 -name "$pattern" | \
+    while read src_file_name; do
+      dst_file_name="$ART_TEST_CHROOT/system/etc/$(insert_vndk_version_string "$file_name")"
+      if [[ "$src_file_name" != "$dst_file_name" ]]; then
+        echo -e "${green}Renaming VNDK libraries file in chroot environment:" \
+          "\`$src_file_name\` -> \`$dst_file_name\`${nc}"
+        adb shell mv -f "$src_file_name" "$dst_file_name"
+      fi
+  done
+done
+
+echo -e "${green}Generating the linker configuration file on device:" \
+  "\`$ld_generated_config_file_path\`${nc}"
+# Generate the linker configuration file on device.
+adb shell chroot "$ART_TEST_CHROOT" \
+  "$linkerconfig_binary" --target "$ld_generated_config_file_location" || exit 1
+
 # Find linker configuration path name on the "host system".
 #
 # The logic here partly replicates (and simplifies) Bionic's linker logic around
 # configuration file search (see `get_ld_config_file_path` in
 # bionic/linker/linker.cpp).
 get_ld_host_system_config_file_path() {
+  # Use generated linker config if property `sys.linker.use_generated_config` is
+  # set on "host device".
+  local use_generated_linker_config=$(adb shell getprop "sys.linker.use_generated_config" true)
+  if [[ "$use_generated_linker_config" = true ]]; then
+    if adb shell test -f "$ld_generated_config_file_path"; then
+      echo "$ld_generated_config_file_path"
+      return
+    else
+      echo -e "${yellow}Failed to find generated linker configuration from" \
+        "\`$ld_generated_config_file_path\`${nc}" >&2
+    fi
+  fi
   # Check whether the "host device" uses a VNDK-lite linker configuration.
   local vndk_lite=$(adb shell getprop "ro.vndk.lite" false)
   if [[ "$vndk_lite" = true ]]; then
@@ -98,20 +168,10 @@
     fi
   fi
   # Check the "host device"'s VNDK version, if any.
-  local vndk_version=$(adb shell getprop "ro.vndk.version")
-  if [[ -n "$vndk_version" ]] && [[ "$vndk_version" != current ]]; then
-    # Insert the VNDK version after the last period (and add another period).
-    local ld_config_file_vdnk_path=$(echo "$ld_config_file_path" \
-      | sed -e "s/^\\(.*\\)\\.\\([^.]\\)/\\1.${vndk_version}.\\2/")
-    if adb shell test -f "$ld_config_file_vdnk_path"; then
-      echo "$ld_config_file_vdnk_path"
-      return
-    fi
-  else
-    if adb shell test -f "$ld_config_file_path"; then
-      echo "$ld_config_file_path"
-      return
-    fi
+  local ld_config_file_vndk_path=$(insert_vndk_version_string "$ld_config_file_path")
+  if adb shell test -f "$ld_config_file_vndk_path"; then
+    echo "$ld_config_file_vndk_path"
+    return
   fi
   # If all else fails, return the default linker configuration name.
   echo -e "${yellow}Cannot find linker configuration; using default path name:" \
@@ -122,29 +182,17 @@
 
 # Find linker configuration path name on the "guest system".
 #
-# The logic here tries to "guess" the name of the linker configuration file,
-# based on the contents of the build directory.
+# Since Android R, the linker configuration file used in newly built system is
+# generated at boot time on device, and has has a fixed name
+# ("/linkerconfig/ld.config.txt").
 get_ld_guest_system_config_file_path() {
-  if [[ -z "$ANDROID_PRODUCT_OUT" ]]; then
-    echo -e "${red}ANDROID_PRODUCT_OUT environment variable is empty;" \
-      "did you forget to run \`lunch\`${nc}?" >&2
-    exit 1
-  fi
-  local ld_config_file_location="$ANDROID_PRODUCT_OUT/system/etc"
-  local ld_config_file_paths=$(find "$ld_config_file_location" -name "ld.*.txt")
-  local ld_config_file_path_number=$(wc -l <<< "$ld_config_file_paths")
-  if [[ "$ld_config_file_path_number" -eq 0 ]]; then
-    echo -e "${red}No linker configuration file found in \`$ld_config_file_location\`${nc}" >&2
-    exit 1
-  fi
-  if [[ "$ld_config_file_path_number" -gt 1 ]]; then
+  if adb shell test ! -f "$ART_TEST_CHROOT$ld_generated_config_file_path"; then
     echo -e \
-      "${red}More than one linker configuration file found in \`$ld_config_file_location\`:" \
-      "\n${ld_config_file_paths}${nc}" >&2
+      "${red}No generated linker configuration file \`$ld_generated_config_file_path\`" \
+      "found in chroot environment${nc}" >&2
     exit 1
   fi
-  # Strip the build prefix to make the path name relative to the "guest root directory".
-  sed -e "s|^$ANDROID_PRODUCT_OUT||" <<< "$ld_config_file_paths"
+  echo "$ld_generated_config_file_path"
 }
 
 # Adjust the linker configuration file (if needed).