aboutsummaryrefslogtreecommitdiff
path: root/prepare_upgrade.py
blob: cccd9098b7a2890a923d13f7a2be0ca8d8a64fef (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
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()