rules_intellij/tools/bump_sdk.py (111 lines of code) (raw):
import sys
import re
import os
import subprocess
import urllib.request
import urllib.error
import tempfile
import time
from typing import List, Dict, Tuple
from concurrent.futures import ThreadPoolExecutor, as_completed
from logging import getLogger, basicConfig, INFO
from threading import Lock
# Setup logging
basicConfig(level=INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = getLogger(__name__)
def replace_variables(content: str, intellij_version: str, plugin_repository_version: str, intellij_repository_version: str, intellij_repository: str) -> str:
content = re.sub(rf'SDK_{intellij_version}_PLUGIN_REPOSITORY_VERSION = "[^"]*"', f'SDK_{intellij_version}_PLUGIN_REPOSITORY_VERSION = "{plugin_repository_version}"', content)
content = re.sub(rf'SDK_{intellij_version}_INTELLIJ_REPOSITORY_VERSION = "[^"]*"', f'SDK_{intellij_version}_INTELLIJ_REPOSITORY_VERSION = "{intellij_repository_version}"', content)
content = re.sub(rf'SDK_{intellij_version}_INTELLIJ_REPOSITORY = "[^"]*"', f'SDK_{intellij_version}_INTELLIJ_REPOSITORY = "{intellij_repository}"', content)
return content
def parse_url(url_template: str, variables: Dict[str, str]) -> str:
"""
Parse and format a URL template using provided variables, handling both two and three positional arguments.
"""
try:
if url_template.count('%') == 2:
return url_template % (
variables.get("SDK_{intellij_version}_PLUGIN_REPOSITORY_VERSION".format(intellij_version=intellij_version), ''),
variables.get("SDK_{intellij_version}_PLUGIN_REPOSITORY_VERSION".format(intellij_version=intellij_version), '')
)
elif url_template.count('%') == 3:
return url_template % (
variables.get("SDK_{intellij_version}_INTELLIJ_REPOSITORY".format(intellij_version=intellij_version), ''),
variables.get("SDK_{intellij_version}_INTELLIJ_REPOSITORY_VERSION".format(intellij_version=intellij_version), ''),
variables.get("SDK_{intellij_version}_INTELLIJ_REPOSITORY_VERSION".format(intellij_version=intellij_version), '')
)
else:
raise ValueError(f"Unexpected number of format specifiers in URL template: {url_template}")
except KeyError as e:
raise ValueError(f"Missing variable {e} for URL template: {url_template}")
except TypeError as e:
raise ValueError(f"Error formatting URL template {url_template}: {e}")
def download_file(url: str, temp_dir: str) -> str:
file_path = os.path.join(temp_dir, os.path.basename(url))
logger.info(f"Starting download: {os.path.basename(url)}")
start_time = time.time()
def reporthook(block_num: int, block_size: int, total_size: int):
nonlocal start_time
if total_size > 0:
percent = (block_num * block_size) / total_size * 100
if time.time() - start_time >= 1:
logger.info(f"Downloading {os.path.basename(url)}: {percent:.2f}% complete")
start_time = time.time()
try:
urllib.request.urlretrieve(url, file_path, reporthook)
logger.info(f"Finished download: {os.path.basename(url)}")
return file_path
except urllib.error.HTTPError as e:
logger.error(f"HTTPError for {os.path.basename(url)}: {e.code} {e.reason}")
return ""
except urllib.error.URLError as e:
logger.error(f"URLError for {os.path.basename(url)}: {e.reason}")
return ""
def compute_checksum(file_path: str) -> str:
result = subprocess.run(['sha256sum', file_path], capture_output=True, text=True, check=True)
return result.stdout.split()[0]
def update_checksums(content: str, temp_dir: str, intellij_version: str) -> str:
urls: List[Tuple[str, str]] = re.findall(rf'(\w+_{intellij_version}_\w*URL) = "([^"]*)"', content)
variables: Dict[str, str] = {
f'SDK_{intellij_version}_PLUGIN_REPOSITORY_VERSION': plugin_repository_version,
f'SDK_{intellij_version}_INTELLIJ_REPOSITORY_VERSION': intellij_repository_version,
f'SDK_{intellij_version}_INTELLIJ_REPOSITORY': intellij_repository,
}
download_futures: Dict[Future, Tuple[str, str]] = {}
with ThreadPoolExecutor(max_workers=8) as executor:
for var_name, url_template in urls:
try:
url = parse_url(url_template, variables)
future = executor.submit(download_file, url, temp_dir)
download_futures[future] = (var_name, url)
except ValueError as e:
logger.error(f"Error parsing URL for {var_name}: {e}")
for future in as_completed(download_futures):
var_name, url = download_futures[future]
file_path = future.result()
if file_path:
checksum = compute_checksum(file_path)
logger.info(f"Download completed for {os.path.basename(file_path)}. SHA256: {checksum}")
checksum_var_name = var_name.replace('URL', 'SHA')
content = re.sub(rf'{checksum_var_name} = "[^"]*"', f'{checksum_var_name} = "{checksum}"', content)
else:
logger.warning(f"[{var_name}] Download failed for {url}, skipping checksum update.")
return content
def main(module_file_path: str, intellij_version: str, plugin_repository_version: str, intellij_repository_version: str, intellij_repository: str) -> None:
logger.info(f"Reading {module_file_path} file.")
with open(module_file_path, 'r') as file:
content = file.read()
logger.info("Replacing variables in MODULE.bazel content.")
content = replace_variables(content, intellij_version, plugin_repository_version, intellij_repository_version, intellij_repository)
with tempfile.TemporaryDirectory() as temp_dir:
logger.info("Updating checksums for URLs.")
content = update_checksums(content, temp_dir, intellij_version)
logger.info(f"Writing updated content back to {module_file_path}.")
with open(module_file_path, 'w') as file:
file.write(content)
if __name__ == "__main__":
if len(sys.argv) != 6:
print("Usage: script.py <module_file_path> <intellij_version> <plugin_repository_version> <intellij_repository_version> <intellij_repository>")
sys.exit(1)
module_file_path: str = sys.argv[1]
intellij_version: str = sys.argv[2]
plugin_repository_version: str = sys.argv[3]
intellij_repository_version: str = sys.argv[4]
intellij_repository: str = sys.argv[5]
main(module_file_path, intellij_version, plugin_repository_version, intellij_repository_version, intellij_repository)