|
| 1 | +#!/usr/bin/env python3 |
| 2 | +""" |
| 3 | +Bump version in VERSION file for ROCprofiler-SDK. |
| 4 | +
|
| 5 | +Supports semantic versioning (MAJOR.MINOR.PATCH format). |
| 6 | +""" |
| 7 | + |
| 8 | +import argparse |
| 9 | +import re |
| 10 | +import sys |
| 11 | +from pathlib import Path |
| 12 | +from typing import Tuple |
| 13 | + |
| 14 | + |
| 15 | +class VersionBumper: |
| 16 | + """Handle version file reading, parsing, and bumping.""" |
| 17 | + |
| 18 | + VERSION_PATTERN = re.compile(r'^(\d+)\.(\d+)\.(\d+)$') |
| 19 | + |
| 20 | + def __init__(self, version_file: Path): |
| 21 | + """ |
| 22 | + Initialize version bumper. |
| 23 | +
|
| 24 | + Args: |
| 25 | + version_file: Path to VERSION file |
| 26 | + """ |
| 27 | + self.version_file = version_file |
| 28 | + |
| 29 | + def read_version(self) -> Tuple[int, int, int]: |
| 30 | + """ |
| 31 | + Read and parse current version from file. |
| 32 | +
|
| 33 | + Returns: |
| 34 | + Tuple of (major, minor, patch) |
| 35 | +
|
| 36 | + Raises: |
| 37 | + ValueError: If version format is invalid |
| 38 | + FileNotFoundError: If version file doesn't exist |
| 39 | + """ |
| 40 | + if not self.version_file.exists(): |
| 41 | + raise FileNotFoundError(f"Version file not found: {self.version_file}") |
| 42 | + |
| 43 | + version_text = self.version_file.read_text().strip() |
| 44 | + match = self.VERSION_PATTERN.match(version_text) |
| 45 | + |
| 46 | + if not match: |
| 47 | + raise ValueError( |
| 48 | + f"Invalid version format: {version_text}. " |
| 49 | + f"Expected MAJOR.MINOR.PATCH" |
| 50 | + ) |
| 51 | + |
| 52 | + major = int(match.group(1)) |
| 53 | + minor = int(match.group(2)) |
| 54 | + patch = int(match.group(3)) |
| 55 | + |
| 56 | + return (major, minor, patch) |
| 57 | + |
| 58 | + def write_version(self, major: int, minor: int, patch: int): |
| 59 | + """ |
| 60 | + Write version to file. |
| 61 | +
|
| 62 | + Args: |
| 63 | + major: Major version number |
| 64 | + minor: Minor version number |
| 65 | + patch: Patch version number |
| 66 | + """ |
| 67 | + version_str = f"{major}.{minor}.{patch}\n" |
| 68 | + self.version_file.write_text(version_str) |
| 69 | + |
| 70 | + def bump_major(self) -> Tuple[int, int, int]: |
| 71 | + """ |
| 72 | + Bump major version, reset minor and patch to 0. |
| 73 | +
|
| 74 | + Returns: |
| 75 | + New version tuple |
| 76 | + """ |
| 77 | + major, minor, patch = self.read_version() |
| 78 | + return (major + 1, 0, 0) |
| 79 | + |
| 80 | + def bump_minor(self) -> Tuple[int, int, int]: |
| 81 | + """ |
| 82 | + Bump minor version, reset patch to 0. |
| 83 | +
|
| 84 | + Returns: |
| 85 | + New version tuple |
| 86 | + """ |
| 87 | + major, minor, patch = self.read_version() |
| 88 | + return (major, minor + 1, 0) |
| 89 | + |
| 90 | + def bump_patch(self) -> Tuple[int, int, int]: |
| 91 | + """ |
| 92 | + Bump patch version. |
| 93 | +
|
| 94 | + Returns: |
| 95 | + New version tuple |
| 96 | + """ |
| 97 | + major, minor, patch = self.read_version() |
| 98 | + return (major, minor, patch + 1) |
| 99 | + |
| 100 | + def bump(self, bump_type: str) -> Tuple[int, int, int]: |
| 101 | + """ |
| 102 | + Bump version based on type. |
| 103 | +
|
| 104 | + Args: |
| 105 | + bump_type: One of 'major', 'minor', 'patch' |
| 106 | +
|
| 107 | + Returns: |
| 108 | + New version tuple |
| 109 | +
|
| 110 | + Raises: |
| 111 | + ValueError: If bump_type is invalid |
| 112 | + """ |
| 113 | + bump_type = bump_type.lower() |
| 114 | + |
| 115 | + if bump_type == 'major': |
| 116 | + new_version = self.bump_major() |
| 117 | + elif bump_type == 'minor': |
| 118 | + new_version = self.bump_minor() |
| 119 | + elif bump_type == 'patch': |
| 120 | + new_version = self.bump_patch() |
| 121 | + else: |
| 122 | + raise ValueError( |
| 123 | + f"Invalid bump type: {bump_type}. " |
| 124 | + f"Expected 'major', 'minor', or 'patch'" |
| 125 | + ) |
| 126 | + |
| 127 | + # Write new version |
| 128 | + self.write_version(*new_version) |
| 129 | + |
| 130 | + return new_version |
| 131 | + |
| 132 | + def get_version_string(self, version: Tuple[int, int, int] = None) -> str: |
| 133 | + """ |
| 134 | + Get version as string. |
| 135 | +
|
| 136 | + Args: |
| 137 | + version: Version tuple, or None to read current version |
| 138 | +
|
| 139 | + Returns: |
| 140 | + Version string in MAJOR.MINOR.PATCH format |
| 141 | + """ |
| 142 | + if version is None: |
| 143 | + version = self.read_version() |
| 144 | + |
| 145 | + return f"{version[0]}.{version[1]}.{version[2]}" |
| 146 | + |
| 147 | + |
| 148 | +def main(): |
| 149 | + """Main entry point.""" |
| 150 | + parser = argparse.ArgumentParser( |
| 151 | + description='Bump version in VERSION file' |
| 152 | + ) |
| 153 | + parser.add_argument( |
| 154 | + '--version-file', |
| 155 | + type=Path, |
| 156 | + required=True, |
| 157 | + help='Path to VERSION file' |
| 158 | + ) |
| 159 | + parser.add_argument( |
| 160 | + '--bump-type', |
| 161 | + choices=['major', 'minor', 'patch'], |
| 162 | + required=True, |
| 163 | + help='Type of version bump' |
| 164 | + ) |
| 165 | + parser.add_argument( |
| 166 | + '--dry-run', |
| 167 | + action='store_true', |
| 168 | + help='Show what would be done without making changes' |
| 169 | + ) |
| 170 | + |
| 171 | + args = parser.parse_args() |
| 172 | + |
| 173 | + try: |
| 174 | + bumper = VersionBumper(args.version_file) |
| 175 | + |
| 176 | + # Get current version |
| 177 | + current_version = bumper.read_version() |
| 178 | + current_str = bumper.get_version_string(current_version) |
| 179 | + |
| 180 | + # Calculate new version |
| 181 | + if args.bump_type == 'major': |
| 182 | + new_version = bumper.bump_major() |
| 183 | + elif args.bump_type == 'minor': |
| 184 | + new_version = bumper.bump_minor() |
| 185 | + elif args.bump_type == 'patch': |
| 186 | + new_version = bumper.bump_patch() |
| 187 | + |
| 188 | + new_str = bumper.get_version_string(new_version) |
| 189 | + |
| 190 | + # Output results |
| 191 | + print(f"Current version: {current_str}") |
| 192 | + print(f"New version: {new_str}") |
| 193 | + print(f"Bump type: {args.bump_type}") |
| 194 | + |
| 195 | + # Perform bump (unless dry run) |
| 196 | + if args.dry_run: |
| 197 | + print("Dry run - no changes made") |
| 198 | + else: |
| 199 | + bumper.write_version(*new_version) |
| 200 | + print(f"Version file updated: {args.version_file}") |
| 201 | + |
| 202 | + # Set output for GitHub Actions |
| 203 | + # https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#setting-an-output-parameter |
| 204 | + print(f"::set-output name=old_version::{current_str}") |
| 205 | + print(f"::set-output name=new_version::{new_str}") |
| 206 | + |
| 207 | + except (FileNotFoundError, ValueError) as e: |
| 208 | + print(f"Error: {e}", file=sys.stderr) |
| 209 | + sys.exit(1) |
| 210 | + |
| 211 | + |
| 212 | +if __name__ == '__main__': |
| 213 | + main() |
0 commit comments