Merge tag 'android-13.0.0_r52' into int/13/fp3

Android 13.0.0 Release 52 (TQ3A.230605.012)

* tag 'android-13.0.0_r52':
  Add ktfmt 0.43 prebuilt
  Replace prepare_upgrade.sh by a Python equivalent
  Create a prepare_upgrade.sh script to help for upgrades

Change-Id: Ic950de4b70a996069d9d8007e039b3135c1050bc
diff --git a/ktfmt.jar b/ktfmt.jar
new file mode 100644
index 0000000..4d39e41
--- /dev/null
+++ b/ktfmt.jar
Binary files differ
diff --git a/ktfmt.py b/ktfmt.py
index 3d337bb..0443795 100755
--- a/ktfmt.py
+++ b/ktfmt.py
@@ -39,6 +39,11 @@
       help='The file containing the Kotlin files and directories that should be included/excluded, generated using generate_includes_file.py.'
   )
   parser.add_argument(
+      '--jar',
+      default='',
+      help='The path to the ktfmt jar.'
+  )
+  parser.add_argument(
       'files',
       nargs='*',
       help='The files to format or check. If --include_file is specified, only the files at their intersection will be formatted/checked.'
@@ -91,8 +96,7 @@
   ktfmt_args += kt_files
 
   dir = os.path.normpath(os.path.dirname(__file__))
-  ktfmt_jar = os.path.join(
-      dir, '../../prebuilts/build-tools/common/framework/ktfmt.jar')
+  ktfmt_jar = args.jar if args.jar else os.path.join(dir, 'ktfmt.jar')
 
   ktlint_env = os.environ.copy()
   ktlint_env['JAVA_CMD'] = 'java'
diff --git a/prepare_upgrade.py b/prepare_upgrade.py
new file mode 100755
index 0000000..cccd909
--- /dev/null
+++ b/prepare_upgrade.py
@@ -0,0 +1,257 @@
+#!/usr/bin/env python3
+
+#
+# Copyright 2023, 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 prepare an update to a new version of ktfmt."""
+
+import subprocess
+import os
+import sys
+import re
+import shutil
+import argparse
+import textwrap
+
+tmp_dir = "/tmp/ktfmt"
+zip_path = os.path.join(tmp_dir, "common.zip")
+jar_path = os.path.join(tmp_dir, "framework/ktfmt.jar")
+copy_path = os.path.join(tmp_dir, "copy.jar")
+
+
+def main():
+  parser = argparse.ArgumentParser(
+      description="Prepare a repository for the upgrade of ktfmt to a new version."
+  )
+  parser.add_argument(
+      "--build_id",
+      required=True,
+      help="The build ID of aosp-build-tools-release with the new version of ktfmt"
+  )
+  parser.add_argument(
+      "--bug_id",
+      required=True,
+      help="The bug ID associated to each CL generated by this tool")
+  parser.add_argument(
+      "--repo",
+      required=True,
+      help="The relative path of the repository to upgrade, e.g. 'frameworks/base/'"
+  )
+  args = parser.parse_args()
+
+  build_id = args.build_id
+  bug_id = args.bug_id
+  repo_relative_path = args.repo
+
+  build_top = os.environ["ANDROID_BUILD_TOP"]
+  repo_absolute_path = os.path.join(build_top, repo_relative_path)
+
+  print("Preparing upgrade of ktfmt from build", build_id)
+  os.chdir(repo_absolute_path)
+  check_workspace_clean()
+  check_branches()
+
+  print("Downloading ktfmt.jar from aosp-build-tools-release")
+  download_jar(build_id)
+
+  print(f"Creating local branch ktfmt_update1")
+  run_cmd(["repo", "start", "ktfmt_update1"])
+
+  includes_file = find_includes_file(repo_relative_path)
+  if includes_file:
+    update_includes_file(build_top, includes_file, bug_id)
+  else:
+    print("No includes file found, skipping first CL")
+
+  print(f"Creating local branch ktfmt_update2")
+  run_cmd(["repo", "start", "--head", "ktfmt_update2"])
+  format_files(build_top, includes_file, repo_absolute_path, bug_id)
+
+  print("Done. You can now submit the generated CL(s), if any.")
+
+
+def run_cmd(cmd):
+  result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+  if result.returncode != 0:
+    print("Error running command: {}".format(" ".join(cmd)))
+    print("Output: {}".format(result.stderr.decode()))
+    sys.exit(1)
+  return result.stdout.decode("utf-8")
+
+
+def is_workspace_clean():
+  return run_cmd(["git", "status", "--porcelain"]) == ""
+
+
+def check_workspace_clean():
+  if not is_workspace_clean():
+    print(
+        "The current repository contains uncommitted changes, please run this script in a clean workspace"
+    )
+    sys.exit(1)
+
+
+def check_branches():
+  result = run_cmd(["git", "branch"])
+  if "ktfmt_update1" in result or "ktfmt_update2" in result:
+    print(
+        "Branches ktfmt_update1 or ktfmt_update2 already exist, you should delete them before running this script"
+    )
+    sys.exit(1)
+
+
+def download_jar(build_id):
+  cmd = [
+      "/google/data/ro/projects/android/fetch_artifact", "--branch",
+      "aosp-build-tools-release", "--bid", build_id, "--target", "linux",
+      "build-common-prebuilts.zip", zip_path
+  ]
+  run_cmd(cmd)
+  cmd = ["unzip", "-q", "-o", "-d", tmp_dir, zip_path]
+  run_cmd(cmd)
+
+  if not os.path.isfile(jar_path):
+    print("Error: {} is not readable".format(jar_path))
+    sys.exit(1)
+
+
+def find_includes_file(repo_relative_path):
+  with open("PREUPLOAD.cfg") as f:
+    includes_line = [line for line in f if "ktfmt.py" in line][0].split(" ")
+    if "-i" not in includes_line:
+      return None
+
+    index = includes_line.index("-i") + 1
+    includes_file = includes_line[index][len("${REPO_ROOT}/") +
+                                         len(repo_relative_path):]
+    if not os.path.isfile(includes_file):
+      print("Error: {} does not exist or is not a file".format(includes_file))
+      sys.exit(1)
+    return includes_file
+
+
+def get_included_folders(includes_file):
+  with open(includes_file) as f:
+    return [line[1:] for line in f.read().splitlines() if line.startswith("+")]
+
+
+def update_includes_file(build_top, includes_file, bug_id):
+  included_folders = get_included_folders(includes_file)
+  cmd = [
+      f"{build_top}/external/ktfmt/generate_includes_file.py",
+      f"--output={includes_file}"
+  ] + included_folders
+  print(f"Updating {includes_file} with the command: {cmd}")
+  run_cmd(cmd)
+
+  if is_workspace_clean():
+    print(f"No change were made to {includes_file}, skipping first CL")
+  else:
+    print(f"Creating first CL with update of {includes_file}")
+    create_first_cl(bug_id)
+
+
+def create_first_cl(bug_id):
+  sha1sum = get_sha1sum(jar_path)
+  change_id = f"I{sha1sum}"
+  command = " ".join(sys.argv)
+  cl_message = textwrap.dedent(f"""
+  Regenerate include file for ktfmt upgrade
+
+  This CL was generated automatically from the following command:
+
+  $ {command}
+
+  This CL regenerates the inclusion file with the current version of ktfmt
+  so that it is up-to-date with files currently formatted or ignored by
+  ktfmt.
+
+  Bug: {bug_id}
+  Test: Presubmits
+  Change-Id: {change_id}
+  Merged-In: {change_id}
+  """)
+
+  run_cmd(["git", "add", "--all"])
+  run_cmd(["git", "commit", "-m", cl_message])
+
+
+def get_sha1sum(file):
+  output = run_cmd(["sha1sum", file])
+  regex = re.compile(r"[a-f0-9]{40}")
+  match = regex.search(output)
+  if not match:
+    print(f"sha1sum not found in output: {output}")
+    sys.exit(1)
+  return match.group()
+
+
+def format_files(build_top, includes_file, repo_absolute_path, bug_id):
+  if (includes_file):
+    included_folders = get_included_folders(includes_file)
+    cmd = [
+        f"{build_top}/external/ktfmt/ktfmt.py", "-i", includes_file, "--jar",
+        jar_path
+    ] + included_folders
+  else:
+    cmd = [
+        f"{build_top}/external/ktfmt/ktfmt.py", "--jar", jar_path,
+        repo_absolute_path
+    ]
+
+  print(
+      f"Formatting the files that are already formatted with the command: {cmd}"
+  )
+  run_cmd(cmd)
+
+  if is_workspace_clean():
+    print("All files were already properly formatted, skipping second CL")
+  else:
+    print("Creating second CL that formats all files")
+    create_second_cl(bug_id)
+
+
+def create_second_cl(bug_id):
+  # Append 'ktfmt_update' at the end of a copy of the jar file to get
+  # a different sha1sum.
+  shutil.copyfile(jar_path, copy_path)
+  with open(copy_path, "a") as file_object:
+    file_object.write("ktfmt_update")
+
+  sha1sum = get_sha1sum(copy_path)
+  change_id = f"I{sha1sum}"
+  command = " ".join(sys.argv)
+  cl_message = textwrap.dedent(f"""
+  Format files with the upcoming version of ktfmt
+
+  This CL was generated automatically from the following command:
+
+  $ {command}
+
+  This CL formats all files already correctly formatted with the upcoming
+  version of ktfmt.
+
+  Bug: {bug_id}
+  Test: Presubmits
+  Change-Id: {change_id}
+  Merged-In: {change_id}
+  """)
+
+  run_cmd(["git", "add", "--all"])
+  run_cmd(["git", "commit", "-m", cl_message])
+
+
+if __name__ == "__main__":
+  main()