blob: 8128101744efb329061f6fe9930fa4f5c875c4d3 [file] [log] [blame]
"""A utility to stamp files with build numbers."""
import argparse
import datetime
import json
import os
import re
import shutil
import stat
import sys
from tools.adt.idea.studio import utils
def _read_status_file(info_file):
with open(info_file) as f:
ret = {}
for line in f.read().splitlines():
parts = line.split(" ", 2)
ret[parts[0]] = parts[1]
return ret
def _get_build_id(build_info):
label = build_info["BUILD_EMBED_LABEL"]
return label if label else "SNAPSHOT"
def _format_build_date(build_version):
timestamp = build_version["BUILD_TIMESTAMP"]
time = datetime.datetime.fromtimestamp(int(timestamp))
return time.strftime("%Y%m%d%H%M")
def _stamp_app_info(version_file, build_txt, micro, patch, full, eap, content):
build_version = _read_status_file(version_file)
build = utils.read_file(build_txt)
build_date = _format_build_date(build_version)
content = content.replace("__BUILD__", build[3:]) # Without the product code, e.g. 'AI-'
content = content.replace("__BUILD_DATE__", build_date)
content = content.replace("__BUILD_NUMBER__", build)
version_prop = '(<version[^/]* %s=")([^"]*)(")'
content = re.sub(version_prop % "micro", '\\g<1>%s\\g<3>' % micro, content)
content = re.sub(version_prop % "patch", '\\g<1>%s\\g<3>' % patch, content)
content = re.sub(version_prop % "full", '\\g<1>%s\\g<3>' % full, content)
# Changing the EAP bit requires rebuilding IntelliJ prebuilts (see b/338090219),
# so here we just assert that the existing value is what we expect.
platform_is_eap = re.search(version_prop % "eap", content).group(2)
if eap != platform_is_eap:
sys.exit(f"ERROR: IntelliJ Platform was built with EAP={platform_is_eap}, but the Bazel build "
f"expects EAP={eap} based on the Studio release version (see b/338090219 for details)")
return content
def _stamp_product_info(info_file, build_txt, added_plugins, content):
build_info = _read_status_file(info_file)
build_number = utils.read_file(build_txt)
bid = _get_build_id(build_info)
json_data = json.loads(content)
json_data["buildNumber"] = json_data["buildNumber"].replace("__BUILD_NUMBER__", bid)
json_data["version"] = build_number
# Add metadata for non-platform plugins built by Bazel.
for plugin_id, *plugin_files in added_plugins:
classpath_jars = [f for f in plugin_files if re.fullmatch(r"plugins/[^/]*/lib/[^/]*\.jar", f)]
if len(classpath_jars) == 0:
sys.exit(f"ERROR: plugin '{plugin_id}' has no classpath jars?")
json_data["bundledPlugins"].append(plugin_id)
json_data["layout"].append(
{
"name": plugin_id,
"kind": "plugin",
"classPath": sorted(classpath_jars),
}
)
return json.dumps(json_data, indent=2)
def _overwrite_plugin_version(build_txt, content):
"""Stamps a plugin.xml with the build ids."""
build_number = utils.read_file(build_txt)
build = build_number[3:] # removes the AI- prefix
api_version = ".".join(build.split(".")[:3]) # first 3 components form the IntelliJ API version
content = re.sub("<version>.*</version>", "<version>%s</version>" % build, content, 1)
content = re.sub("<idea-version\\s+since-build=\"\\d+\\.\\d+\"\\s+until-build=\"\\d+\\.\\d+\"",
"<idea-version since-build=\"%s\" until-build=\"%s\"" % (api_version, api_version),
content, 1)
content = re.sub("<idea-version\\s+since-build=\"\\d+\\.\\d+\"",
"<idea-version since-build=\"%s\"" % api_version,
content, 1)
anchor = "</id>" if "</id>" in content else "</name>"
if "<version>" not in content:
content = re.sub(anchor, "%s\n <version>%s</version>" %(anchor, build), content)
if "<idea-version since-build" not in content:
content = re.sub(anchor, "%s\n <idea-version since-build=\"%s\" until-build=\"%s\"/>" % (anchor, api_version, api_version), content)
return content
def _overwrite_since_until_builds(build_txt, content):
build_number = utils.read_file(build_txt)
build = build_number[3:] # removes the AI- prefix
major_api_version = build.split(".")[0]
tag = "<idea-version since-build=\"%s\" until-build=\"%s.*\"/>" % (major_api_version, major_api_version)
if "<idea-version" in content:
content = re.sub("<idea-version.*/>", tag, content, 1)
else:
anchor = "</id>" if "</id>" in content else "</name>"
content = re.sub(anchor, "%s\n %s" % (anchor, tag), content)
return content
def _replace_build_number(content, info_file):
build_info = _read_status_file(info_file)
bid = _get_build_id(build_info)
return content.replace("__BUILD_NUMBER__", bid)
def _replace_selector(content, selector):
return content.replace("_ANDROID_STUDIO_SYSTEM_SELECTOR_", selector)
def _read_file(file, entry = None, optional_entry = False):
if entry:
return utils.read_zip_entry(file, entry, optional_entry)
else:
return utils.read_file(file)
def _write_file(src, dst, data, entry = None):
if entry:
if data is None:
shutil.copy(src, dst)
else:
utils.change_zip_entry(src, entry, data, dst)
else:
utils.write_file(dst, data)
def main(argv):
parser = argparse.ArgumentParser(fromfile_prefix_chars="@")
parser.add_argument(
"--stamp",
nargs=2,
metavar=("src", "dst"),
dest="stamp",
help="Stamps file <src> and saves it in <dst> performing the indicated actions.")
parser.add_argument(
"--entry",
dest="entry",
help="Whether to treat the input and output as zips and replace this entry in it.")
parser.add_argument(
"--optional_entry",
action="store_true",
help="Replaces json")
parser.add_argument(
"--replace_build_number",
action="store_true",
help="Replaces __BUILD_NUMBER__ on the given file using --info_file.")
parser.add_argument(
"--replace_selector",
help="Replaces _ANDROID_STUDIO_SYSTEM_SELECTOR_ with the given value")
parser.add_argument(
"--stamp_app_info",
action="store_true",
help="Replaces xml")
parser.add_argument(
"--stamp_product_info",
action="store_true",
help="Replaces json")
parser.add_argument(
"--added_plugin",
dest="added_plugins",
nargs="+",
action="append",
default=[],
help="Plugin ID + plugin files, to be listed in product-info.json")
parser.add_argument(
"--overwrite_plugin_version",
action="store_true",
help="Whether to set the <version> and <idea-version> tags for this plugin.")
parser.add_argument(
"--overwrite_since_until_builds",
action="store_true",
help="Whether to set the <idea-version> tags to the major version (ie. 242 to 242.*).")
parser.add_argument(
"--build_txt",
default="",
required = "--stamp_app_info" in sys.argv or "--stamp_product_info" in sys.argv or "--overwrite_plugin_version" in sys.argv,
dest="build_txt",
help="The path to the build.txt file.")
parser.add_argument(
"--version_micro",
default="",
dest="version_micro",
required = "--stamp_app_info" in sys.argv,
help="The 'micro' part of the version number: major.minor.micro.patch")
parser.add_argument(
"--version_patch",
default="",
dest="version_patch",
required = "--stamp_app_info" in sys.argv,
help="The 'patch' part of the version number: major.minor.micro.patch")
parser.add_argument(
"--version_full",
default="",
dest="version_full",
required = "--stamp_app_info" in sys.argv,
help="The descriptive form of the version. Can use {0} to refer to version components, like \"{0}.{1} Canary 2\"")
parser.add_argument(
"--eap",
default="",
dest="eap",
required = "--stamp_app_info" in sys.argv,
help="Whether this build is a canary/eap build")
parser.add_argument(
"--info_file",
default="",
dest="info_file",
required = "--replace_build_number" in sys.argv or "--stamp_product_info" in sys.argv,
help="Path to the bazel build info file (bazel-out/stable-status.txt).")
parser.add_argument(
"--version_file",
default="",
dest="version_file",
required = "--stamp_app_info" in sys.argv,
help="Path to the bazel version file (bazel-out/volatile-status.txt).")
args = parser.parse_args(argv)
content = _read_file(args.stamp[0], args.entry, args.optional_entry)
if content:
if args.replace_build_number:
content = _replace_build_number(content, args.info_file)
if args.replace_selector:
content= _replace_selector(content, args.replace_selector)
if args.stamp_app_info:
content = _stamp_app_info(args.version_file, args.build_txt, args.version_micro, args.version_patch, args.version_full, args.eap, content)
if args.overwrite_plugin_version:
content = _overwrite_plugin_version(args.build_txt, content)
if args.overwrite_since_until_builds:
content = _overwrite_since_until_builds(args.build_txt, content)
if args.stamp_product_info:
content = _stamp_product_info(args.info_file, args.build_txt, args.added_plugins, content)
_write_file(args.stamp[0], args.stamp[1], content, args.entry)
if __name__ == "__main__":
main(sys.argv[1:])