diff --git a/llvm/utils/release/bump-version.py b/llvm/utils/release/bump-version.py new file mode 100755 --- /dev/null +++ b/llvm/utils/release/bump-version.py @@ -0,0 +1,290 @@ +#!/usr/bin/env python3 + +# This script bumps the version of LLVM in *all* the different places where +# it needs to be defined. Which is quite a few. + +import sys +import argparse +import packaging.version +from pathlib import Path +import re +from typing import Optional + + +class Processor: + def process_line(self, line: str) -> str: + raise NotImplementedError() + + def process_file(self, fpath: Path, version: packaging.version.Version) -> None: + self.version = version + self.major, self.minor, self.patch, self.suffix = ( + version.major, + version.minor, + version.micro, + version.pre, + ) + data = fpath.read_text() + new_data = [] + + for line in data.splitlines(True): + nline = self.process_line(line) + + # Print the failing line just to inform the user. + if nline != line: + print(f"{fpath.name}: {line.strip()} -> {nline.strip()}") + + new_data.append(nline) + + fpath.write_text("".join(new_data), newline="\n") + + # Return a string from the version class + # optionally include the suffix (-rcX) + def version_str( + self, + version: Optional[packaging.version.Version] = None, + include_suffix: bool = True, + ) -> str: + if version is None: + version = self.version + + ver = f"{version.major}.{version.minor}.{version.micro}" + if include_suffix and version.pre: + ver += f"-{version.pre[0]}{version.pre[1]}" + return ver + + +# llvm/CMakeLists.txt +class CMakeProcessor(Processor): + def process_line(self, line: str) -> str: + nline = line + + # LLVM_VERSION_SUFFIX should be set to -rcX or be blank if we are + # building a final version. + if "set(LLVM_VERSION_SUFFIX" in line: + if self.suffix: + nline = re.sub( + r"set\(LLVM_VERSION_SUFFIX(.*)\)", + f"set(LLVM_VERSION_SUFFIX -{self.suffix[0]}{self.suffix[1]})", + line, + ) + else: + nline = re.sub( + r"set\(LLVM_VERSION_SUFFIX(.*)\)", f"set(LLVM_VERSION_SUFFIX)", line + ) + + # Check the rest of the LLVM_VERSION_ lines. + elif "set(LLVM_VERSION_" in line: + for c, cver in ( + ("MAJOR", self.major), + ("MINOR", self.minor), + ("PATCH", self.patch), + ): + nline = re.sub( + fr"set\(LLVM_VERSION_{c} (\d+)", + fr"set(LLVM_VERSION_{c} {cver}", + line, + ) + if nline != line: + break + + return nline + + +# Process the many bazel files. +class BazelProcessor(Processor): + def process_line(self, line: str) -> str: + # This maches the CLANG_VERSION line of clang/Config/config.h + nline = line + if "CLANG_VERSION " in line: + nline = re.sub( + r"#define CLANG_VERSION (.*)'", + f"#define CLANG_VERSION {self.version_str(include_suffix=False)}'", + line, + ) + # Match version strings of LLVM, Clang and LLD overlay headers + elif ( + "LLVM_VERSION_STRING" in line + or "CLANG_VERSION_STRING" in line + or "LLD_VERSION_STRING" in line + ): + nline = re.sub( + r"#define (LLVM|CLANG|LLD)_VERSION_STRING ([\\\"]+)[0-9\.rcgit-]+([\\\"]+)", + rf"#define \g<1>_VERSION_STRING \g<2>{self.version_str()}\g<3>", + line, + ) + # Match the split out MAJOR/MINOR/PATCH versions of LLVM and Clang overlay headers + # in LLVM the define is called _PATCH and in clang it's called _PATCHLEVEL + elif "LLVM_VERSION_" in line or "CLANG_VERSION_" in line: + for c, cver in ( + ("(MAJOR)", self.major), + ("(MINOR)", self.minor), + ("(PATCH|PATCHLEVEL)", self.patch), + ): + nline = re.sub( + fr"(LLVM|CLANG)_VERSION_{c} \d+", + rf"\g<1>_VERSION_\g<2> {cver}", + line, + ) + if nline != line: + break + # Match the BACKEND_PACKAGE_STRING in clang/config.h + elif "BACKEND_PACKAGE_STRING" in line: + nline = re.sub( + r'#define BACKEND_PACKAGE_STRING "LLVM ([0-9\.rcgit-]+)"', + f'#define BACKEND_PACKAGE_STRING "LLVM {self.version_str()}"', + line, + ) + + return nline + + +# GN build system +class GNIProcessor(Processor): + def process_line(self, line: str) -> str: + if "llvm_version_" in line: + for c, cver in ( + ("major", self.major), + ("minor", self.minor), + ("patch", self.patch), + ): + nline = re.sub( + fr"llvm_version_{c} = \d+", f"llvm_version_{c} = {cver}", line + ) + if nline != line: + return nline + + return line + + +# LIT python file, a simple tuple +class LitProcessor(Processor): + def process_line(self, line: str) -> str: + if "__versioninfo__" in line: + nline = re.sub( + fr"__versioninfo__(.*)\((\d+), (\d+), (\d+)\)", + f"__versioninfo__\\1({self.major}, {self.minor}, {self.patch})", + line, + ) + return nline + return line + + +class LibCXXProcessor(Processor): + def process_line(self, line: str) -> str: + if "define _LIBCPP_VERSION " in line: + + # Before libc++ 16.x the version field was + # XXYZZ and after 16 it's XXYYZZ - figure out + # what we are dealing with by counting the numbers + t = line[line.rfind(' ')+1:].strip() + verstr = "" + if len(t) == 6: + # XXYYZZ + verstr = f"{str(self.major).zfill(2)}{str(self.minor).zfill(2)}{str(self.patch).zfill(2)}" + else: + # XXYZZ + verstr = f"{str(self.major).zfill(2)}{self.minor}{str(self.patch).zfill(2)}" + nline = re.sub( + fr"_LIBCPP_VERSION (\d+)", + f"_LIBCPP_VERSION {verstr}", + line, + ) + return nline + return line + + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + usage="Call this script with a version and it will bump the version for you" + ) + parser.add_argument("version", help="Version to bump to, i.e. 15.0.1", default=None) + parser.add_argument("--rc", default=None, type=int, help="RC version") + parser.add_argument("-s", "--source-root", default=None, help="Source Root") + + args = parser.parse_args() + + verstr = args.version + if args.rc: + verstr += f"-rc{args.rc}" + + # parse the version string with distutils. + # note that -rc will end up as version.pre here + # since it's a prerelease + version = packaging.version.parse(verstr) + + # Find llvm-project root + source_root = Path(__file__).resolve().parents[3] + + if args.source_root: + source_root = Path(args.source_root).resolve() + + files_to_update = ( + # Main CMakeLists. + (source_root / "llvm" / "CMakeLists.txt", CMakeProcessor()), + # Lit configuration + ( + source_root / "llvm" / "utils" / "lit" / "lit" / "__init__.py", + LitProcessor(), + ), + # GN build system + ( + source_root + / "llvm" + / "utils" + / "gn" + / "secondary" + / "llvm" + / "version.gni", + GNIProcessor(), + ), + # Bazel build system + ( + source_root + / "utils" + / "bazel" + / "llvm-project-overlay" + / "llvm" + / "include" + / "llvm" + / "Config" + / "llvm-config.h", + BazelProcessor(), + ), + ( + source_root + / "utils" + / "bazel" + / "llvm-project-overlay" + / "clang" + / "BUILD.bazel", + BazelProcessor(), + ), + ( + source_root + / "utils" + / "bazel" + / "llvm-project-overlay" + / "clang" + / "include" + / "clang" + / "Config" + / "config.h", + BazelProcessor(), + ), + ( + source_root + / "utils" + / "bazel" + / "llvm-project-overlay" + / "lld" + / "BUILD.bazel", + BazelProcessor(), + ), + ( + source_root / "libcxx" / "include" / "__config", + LibCXXProcessor(), + ), + ) + + for f, processor in files_to_update: + processor.process_file(f, version)