|
| 1 | +#!/usr/bin/env python3 |
| 2 | +""" |
| 3 | +Version management utility for LibFake package. |
| 4 | +Use this script to update version numbers across the entire project. |
| 5 | +""" |
| 6 | + |
| 7 | +import os |
| 8 | +import re |
| 9 | +import argparse |
| 10 | +import sys |
| 11 | +from pathlib import Path |
| 12 | + |
| 13 | + |
| 14 | +def get_current_version(): |
| 15 | + """Get the current version from _version.py""" |
| 16 | + version_file = Path(__file__).parent / "src" / "libfake" / "_version.py" |
| 17 | + |
| 18 | + if not version_file.exists(): |
| 19 | + raise FileNotFoundError(f"Version file not found: {version_file}") |
| 20 | + |
| 21 | + with open(version_file, "r", encoding="utf-8") as f: |
| 22 | + content = f.read() |
| 23 | + |
| 24 | + # Extract version components |
| 25 | + major_match = re.search(r"VERSION_MAJOR\s*=\s*(\d+)", content) |
| 26 | + minor_match = re.search(r"VERSION_MINOR\s*=\s*(\d+)", content) |
| 27 | + patch_match = re.search(r"VERSION_PATCH\s*=\s*(\d+)", content) |
| 28 | + suffix_match = re.search(r'VERSION_SUFFIX\s*=\s*["\']([^"\']*)["\']', content) |
| 29 | + |
| 30 | + if not all([major_match, minor_match, patch_match, suffix_match]): |
| 31 | + raise ValueError("Could not parse version components from _version.py") |
| 32 | + |
| 33 | + major = int(major_match.group(1)) |
| 34 | + minor = int(minor_match.group(1)) |
| 35 | + patch = int(patch_match.group(1)) |
| 36 | + suffix = suffix_match.group(1) |
| 37 | + |
| 38 | + return major, minor, patch, suffix |
| 39 | + |
| 40 | + |
| 41 | +def update_version(major, minor, patch, suffix=""): |
| 42 | + """Update the version in _version.py""" |
| 43 | + version_file = Path(__file__).parent / "src" / "libcrypto" / "_version.py" |
| 44 | + |
| 45 | + with open(version_file, "r", encoding="utf-8") as f: |
| 46 | + content = f.read() |
| 47 | + |
| 48 | + # Update version components |
| 49 | + content = re.sub(r"VERSION_MAJOR\s*=\s*\d+", f"VERSION_MAJOR = {major}", content) |
| 50 | + content = re.sub(r"VERSION_MINOR\s*=\s*\d+", f"VERSION_MINOR = {minor}", content) |
| 51 | + content = re.sub(r"VERSION_PATCH\s*=\s*\d+", f"VERSION_PATCH = {patch}", content) |
| 52 | + content = re.sub( |
| 53 | + r'VERSION_SUFFIX\s*=\s*["\'][^"\']*["\']', |
| 54 | + f'VERSION_SUFFIX = "{suffix}"', |
| 55 | + content, |
| 56 | + ) |
| 57 | + |
| 58 | + with open(version_file, "w", encoding="utf-8") as f: |
| 59 | + f.write(content) |
| 60 | + |
| 61 | + # Calculate version string |
| 62 | + if suffix: |
| 63 | + version_str = f"{major}.{minor}.{patch}{suffix}" |
| 64 | + else: |
| 65 | + version_str = f"{major}.{minor}.{patch}" |
| 66 | + |
| 67 | + return version_str |
| 68 | + |
| 69 | + |
| 70 | +def validate_version_consistency(): |
| 71 | + """Check if all files use the same version""" |
| 72 | + from src.libfake._version import __version__ |
| 73 | + |
| 74 | + issues = [] |
| 75 | + |
| 76 | + # Check if import works |
| 77 | + try: |
| 78 | + from src.libfake import __version__ as init_version |
| 79 | + |
| 80 | + if __version__ != init_version: |
| 81 | + issues.append( |
| 82 | + f"__init__.py version mismatch: {init_version} != {__version__}" |
| 83 | + ) |
| 84 | + except ImportError as e: |
| 85 | + issues.append(f"Could not import version from __init__.py: {e}") |
| 86 | + |
| 87 | + return issues |
| 88 | + |
| 89 | + |
| 90 | +def bump_version(component, suffix=""): |
| 91 | + """Bump version component (major, minor, patch)""" |
| 92 | + major, minor, patch, current_suffix = get_current_version() |
| 93 | + |
| 94 | + if component == "major": |
| 95 | + major += 1 |
| 96 | + minor = 0 |
| 97 | + patch = 0 |
| 98 | + elif component == "minor": |
| 99 | + minor += 1 |
| 100 | + patch = 0 |
| 101 | + elif component == "patch": |
| 102 | + patch += 1 |
| 103 | + else: |
| 104 | + raise ValueError(f"Invalid component: {component}") |
| 105 | + |
| 106 | + return update_version(major, minor, patch, suffix) |
| 107 | + |
| 108 | + |
| 109 | +def main(): |
| 110 | + """Main function for version management.""" |
| 111 | + parser = argparse.ArgumentParser( |
| 112 | + description="LibFake Version Management Tool", |
| 113 | + formatter_class=argparse.RawDescriptionHelpFormatter, |
| 114 | + epilog=""" |
| 115 | +Examples: |
| 116 | + python version_manager.py --show Show current version |
| 117 | + python version_manager.py --set 1.2.3 Set specific version |
| 118 | + python version_manager.py --bump patch Bump patch version |
| 119 | + python version_manager.py --bump minor Bump minor version |
| 120 | + python version_manager.py --bump major Bump major version |
| 121 | + python version_manager.py --set 2.0.0 --suffix rc1 Set version with suffix |
| 122 | + python version_manager.py --check Check version consistency |
| 123 | + """, |
| 124 | + ) |
| 125 | + |
| 126 | + group = parser.add_mutually_exclusive_group(required=True) |
| 127 | + group.add_argument("--show", "-s", action="store_true", help="Show current version") |
| 128 | + group.add_argument("--set", type=str, help="Set specific version (e.g., 1.2.3)") |
| 129 | + group.add_argument( |
| 130 | + "--bump", |
| 131 | + "-b", |
| 132 | + choices=["major", "minor", "patch"], |
| 133 | + help="Bump version component", |
| 134 | + ) |
| 135 | + group.add_argument( |
| 136 | + "--check", |
| 137 | + "-c", |
| 138 | + action="store_true", |
| 139 | + help="Check version consistency across files", |
| 140 | + ) |
| 141 | + |
| 142 | + parser.add_argument( |
| 143 | + "--suffix", type=str, default="", help="Version suffix (e.g., a1, b1, rc1)" |
| 144 | + ) |
| 145 | + |
| 146 | + args = parser.parse_args() |
| 147 | + |
| 148 | + try: |
| 149 | + if args.show: |
| 150 | + major, minor, patch, suffix = get_current_version() |
| 151 | + if suffix: |
| 152 | + version_str = f"{major}.{minor}.{patch}{suffix}" |
| 153 | + else: |
| 154 | + version_str = f"{major}.{minor}.{patch}" |
| 155 | + |
| 156 | + print(f"Current version: {version_str}") |
| 157 | + print( |
| 158 | + f"Components: major={major}, minor={minor}, patch={patch}, suffix='{suffix}'" |
| 159 | + ) |
| 160 | + |
| 161 | + elif args.set: |
| 162 | + # Parse version string |
| 163 | + version_parts = args.set.split(".") |
| 164 | + if len(version_parts) != 3: |
| 165 | + raise ValueError( |
| 166 | + "Version must be in format major.minor.patch (e.g., 1.2.3)" |
| 167 | + ) |
| 168 | + |
| 169 | + try: |
| 170 | + major = int(version_parts[0]) |
| 171 | + minor = int(version_parts[1]) |
| 172 | + patch = int(version_parts[2]) |
| 173 | + except ValueError: |
| 174 | + raise ValueError("Version components must be integers") |
| 175 | + |
| 176 | + new_version = update_version(major, minor, patch, args.suffix) |
| 177 | + print(f"Version updated to: {new_version}") |
| 178 | + |
| 179 | + elif args.bump: |
| 180 | + new_version = bump_version(args.bump, args.suffix) |
| 181 | + print(f"Version bumped to: {new_version}") |
| 182 | + |
| 183 | + elif args.check: |
| 184 | + issues = validate_version_consistency() |
| 185 | + if issues: |
| 186 | + print("Version consistency issues found:") |
| 187 | + for issue in issues: |
| 188 | + print(f" ❌ {issue}") |
| 189 | + sys.exit(1) |
| 190 | + else: |
| 191 | + major, minor, patch, suffix = get_current_version() |
| 192 | + if suffix: |
| 193 | + version_str = f"{major}.{minor}.{patch}{suffix}" |
| 194 | + else: |
| 195 | + version_str = f"{major}.{minor}.{patch}" |
| 196 | + print(f"✅ All versions are consistent: {version_str}") |
| 197 | + |
| 198 | + except Exception as e: |
| 199 | + print(f"Error: {e}", file=sys.stderr) |
| 200 | + sys.exit(1) |
| 201 | + |
| 202 | + |
| 203 | +if __name__ == "__main__": |
| 204 | + main() |
0 commit comments