blob: 268838edf0f93812c836473a55d7da7a49a9518b [file] [log] [blame]
#!/usr/bin/env python3
#
# Copyright (C) 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.
import argparse
import os
import re
import subprocess
import sys
# Color codes
RED = '\033[0;31m'
GREEN = '\033[0;32m'
YELLOW = '\033[0;33m'
NC = '\033[0m' # No Color
def info(msg):
print(f"{GREEN}INFO:{NC} {msg}")
def warn(msg):
print(f"{YELLOW}WARN:{NC} {msg}")
def error(msg):
print(f"{RED}ERROR:{NC} {msg}", file=sys.stderr)
sys.exit(1)
def prompt(msg):
print(f"{YELLOW}ACTION:{NC} {msg}")
input("Press Enter to continue...")
def confirm(msg):
reply = input(f"{msg} [y/N] ").lower().strip()
if reply not in ['y', 'yes']:
error("Aborted by user.")
def run_cmd(*args, check=True):
info(f"Running command: {' '.join(args)}")
subprocess.run(args, check=check)
def run_cmd_output(*args, check=True):
"""Runs a command and returns its stdout."""
info(f"Running command: {' '.join(args)}")
# Using text=True to get stdout/stderr as strings
result = subprocess.run(args, check=check, capture_output=True, text=True)
return result.stdout.strip()
def cmd_succeeded(*args):
"""Runs a command and returns True if it succeeds."""
return subprocess.run(args, capture_output=True).returncode == 0
def get_current_branch():
"""Returns the current git branch."""
return run_cmd_output('git', 'rev-parse', '--abbrev-ref', 'HEAD')
def local_branch_exists(branch):
"""Checks if a local git branch exists."""
return cmd_succeeded('git', 'show-ref', '--verify', '--quiet',
f'refs/heads/{branch}')
def remote_branch_exists(branch):
"""Checks if a remote git branch exists."""
info(f"Checking for remote branch '{branch}'...")
return cmd_succeeded('git', 'ls-remote', '--exit-code', '--heads', 'origin',
branch)
def tag_exists(tag):
"""Checks if a local git tag exists."""
return cmd_succeeded('git', 'show-ref', '--verify', '--quiet',
f'refs/tags/{tag}')
def remote_tag_exists(tag):
"""Checks if a remote git tag exists."""
info(f"Checking for remote tag '{tag}'...")
return cmd_succeeded('git', 'ls-remote', '--exit-code', '--tags', 'origin',
tag)
def create_major_release(version, major_version):
info(f"--- Creating a new major version: {version} ---")
prompt("Please check that no release-blockers are open: "
"http://b/savedsearches/5776355")
prompt("Please trigger all builds on LUCI and wait for their success: "
"https://luci-scheduler.appspot.com/jobs/perfetto")
prompt(f"Please update the CHANGELOG: rename the 'Unreleased' entry to "
f"'{version}'.")
info("Building to test CHANGELOG parsing...")
run_cmd('tools/ninja', '-C', 'out/linux_clang_release')
info("Checking 'perfetto --version' output...")
version_output = subprocess.check_output(
['out/linux_clang_release/perfetto', '--version']).decode('utf-8')
info(f"Output: {version_output}")
if version not in version_output:
error(f"Version check failed. Expected '{version}' to be in the output.")
info("Version check successful.")
prompt("Please upload the CHANGELOG change for review and submit it on the "
"main branch.")
branch_name = f"releases/v{major_version}.x"
info(f"Setting up release branch '{branch_name}'...")
run_cmd('git', 'fetch', 'origin')
info(f"Creating/updating remote branch '{branch_name}' from 'origin/main'...")
run_cmd('git', 'push', 'origin', '--no-verify',
f'origin/main:refs/heads/{branch_name}')
run_cmd('git', 'fetch', 'origin')
if get_current_branch() == branch_name:
warn(f"Already on branch '{branch_name}'.")
elif local_branch_exists(branch_name):
warn(f"Local branch '{branch_name}' already exists. Checking it out.")
run_cmd('git', 'checkout', branch_name)
else:
info(f"Creating and checking out local branch '{branch_name}'.")
run_cmd('git', 'checkout', '-b', branch_name, '-t', f'origin/{branch_name}')
info("Major release setup complete.")
def create_minor_release(version, major_version):
info(f"--- Bumping to a new minor version: {version} ---")
branch_name = f"releases/v{major_version}.x"
info(f"Checking out branch '{branch_name}'...")
run_cmd('git', 'fetch', 'origin')
if get_current_branch() == branch_name:
warn(f"Already on branch '{branch_name}'.")
elif local_branch_exists(branch_name):
warn(f"Local branch '{branch_name}' already exists. Checking it out.")
run_cmd('git', 'checkout', branch_name)
else:
info(f"Creating and checking out local branch '{branch_name}'.")
run_cmd('git', 'checkout', '-b', branch_name, '-t', f'origin/{branch_name}')
prompt("Please merge or cherry-pick the desired commits for the new release.")
prompt(f"Please update the CHANGELOG with a dedicated entry for '{version}'.")
info("Minor release setup complete.")
def build_and_tag_release(version, major_version):
info(f"--- Building and tagging the release: {version} ---")
confirm("Have all changes for the release been merged into the release "
"branch?")
info("Generating and committing amalgamated source files...")
run_cmd('tools/gen_amalgamated', '--output', 'sdk/perfetto')
run_cmd('git', 'add', 'sdk/perfetto.cc', 'sdk/perfetto.h')
# Only commit if there are any changes.
if subprocess.run(['git', 'diff', '--cached', '--quiet']).returncode == 1:
run_cmd('git', 'commit', '-m', f'Amalgamated source for {version}')
else:
warn("No changes in amalgamated source to commit.")
info("Checking that the SDK example code works...")
original_dir = os.getcwd()
try:
os.chdir('examples/sdk')
run_cmd('cmake', '-B', 'build')
run_cmd('cmake', '--build', 'build')
finally:
os.chdir(original_dir)
info("SDK example build successful.")
prompt("Please upload the new release for review (e.g., by running "
"'gh pr create').")
prompt("Once the release CL has been reviewed and landed, press Enter to "
"continue.")
info("Tagging the release...")
run_cmd('git', 'pull')
info("Checking git status...")
run_cmd('git', 'status')
prompt("Please verify that your branch is up to date with "
f"'origin/releases/v{major_version}.x' and has no divergence.")
if tag_exists(version):
warn(f"Tag '{version}' already exists locally.")
else:
run_cmd('git', 'tag', '-a', '-m', f'Perfetto {version}', version)
if remote_tag_exists(version):
warn(f"Tag '{version}' already exists on remote.")
else:
run_cmd('git', 'push', 'origin', version)
info("Build and tag complete.")
def create_github_release(version):
info(f"--- Creating GitHub release with prebuilts: {version} ---")
prompt("Wait for LUCI builds to complete successfully: "
"https://luci-scheduler.appspot.com/jobs/perfetto")
info("Packaging prebuilts for GitHub release...")
run_cmd('tools/package-prebuilts-for-github-release', version)
prompt(f"Please check that all 10 prebuilt zips are present in "
f"'/tmp/perfetto-prebuilts-{version}'.")
prompt(f"""
Please create a new GitHub release:
1. Open https://github.com/google/perfetto/releases/new
2. Choose Tag: {version}
3. Release title: Perfetto {version}
4. Describe release: Copy the CHANGELOG, wrapping it in triple backticks.
5. Attach binaries: Attach the ten .zip files from the previous step.
""")
info("Rolling prebuilts...")
run_cmd('tools/roll-prebuilts', version)
prompt("Please upload the prebuilt roll CL for review.")
info("Phew, you're done!")
def main():
parser = argparse.ArgumentParser(
description="Automates the Perfetto SDK release process.")
parser.add_argument(
'version',
metavar='vX.Y',
help="The new version number for the release (e.g., v16.0 or v16.1).")
args = parser.parse_args()
version = args.version
match = re.match(r'v(\d+)\.(\d+)', version)
if not match:
error("Invalid version format. Please use 'vX.Y' (e.g., v16.0 or v16.1).")
return
major_version, minor_version = map(int, match.groups())
if minor_version == 0:
create_major_release(version, major_version)
else:
create_minor_release(version, major_version)
build_and_tag_release(version, major_version)
create_github_release(version)
if __name__ == '__main__':
main()