xref: /llvm-project/llvm/utils/release/bump-version.py (revision cb1a3bb29ffcd65c221f017164a83400310a0d4b)
1#!/usr/bin/env python3
2
3# This script bumps the version of LLVM in *all* the different places where
4# it needs to be defined. Which is quite a few.
5
6import sys
7import argparse
8import packaging.version
9from pathlib import Path
10import re
11from typing import Optional
12
13
14class Processor:
15    def __init__(self, args):
16        self.args = args
17
18    def process_line(self, line: str) -> str:
19        raise NotImplementedError()
20
21    def process_file(self, fpath: Path, version: packaging.version.Version) -> None:
22        self.version = version
23        self.major, self.minor, self.patch, self.suffix = (
24            version.major,
25            version.minor,
26            version.micro,
27            version.pre,
28        )
29
30        if self.args.rc:
31            self.suffix = f"-rc{self.args.rc}"
32
33        if self.args.git:
34            self.suffix = "git"
35
36        data = fpath.read_text()
37        new_data = []
38
39        for line in data.splitlines(True):
40            nline = self.process_line(line)
41
42            # Print the failing line just to inform the user.
43            if nline != line:
44                print(f"{fpath.name}: {line.strip()} -> {nline.strip()}")
45
46            new_data.append(nline)
47
48        fpath.write_text("".join(new_data), newline="\n")
49
50    # Return a string from the version class
51    # optionally include the suffix (-rcX)
52    def version_str(
53        self,
54        version: Optional[packaging.version.Version] = None,
55        include_suffix: bool = True,
56    ) -> str:
57        if version is None:
58            version = self.version
59
60        ver = f"{version.major}.{version.minor}.{version.micro}"
61        if include_suffix and version.pre:
62            ver += f"-{version.pre[0]}{version.pre[1]}"
63        return ver
64
65
66# llvm/CMakeLists.txt
67class CMakeProcessor(Processor):
68    def process_line(self, line: str) -> str:
69        nline = line
70
71        # LLVM_VERSION_SUFFIX should be set to -rcX or be blank if we are
72        # building a final version.
73        if "set(LLVM_VERSION_SUFFIX" in line:
74            if self.suffix:
75                nline = re.sub(
76                    r"set\(LLVM_VERSION_SUFFIX(.*)\)",
77                    f"set(LLVM_VERSION_SUFFIX {self.suffix})",
78                    line,
79                )
80            else:
81                nline = re.sub(
82                    r"set\(LLVM_VERSION_SUFFIX(.*)\)", f"set(LLVM_VERSION_SUFFIX)", line
83                )
84
85        # Check the rest of the LLVM_VERSION_ lines.
86        elif "set(LLVM_VERSION_" in line:
87            for c, cver in (
88                ("MAJOR", self.major),
89                ("MINOR", self.minor),
90                ("PATCH", self.patch),
91            ):
92                nline = re.sub(
93                    rf"set\(LLVM_VERSION_{c} (\d+)",
94                    rf"set(LLVM_VERSION_{c} {cver}",
95                    line,
96                )
97                if nline != line:
98                    break
99
100        return nline
101
102
103# GN build system
104class GNIProcessor(Processor):
105    def process_line(self, line: str) -> str:
106        if "llvm_version_" in line:
107            for c, cver in (
108                ("major", self.major),
109                ("minor", self.minor),
110                ("patch", self.patch),
111            ):
112                nline = re.sub(
113                    rf"llvm_version_{c} = \d+", f"llvm_version_{c} = {cver}", line
114                )
115                if nline != line:
116                    return nline
117
118        return line
119
120
121# LIT python file, a simple tuple
122class LitProcessor(Processor):
123    def process_line(self, line: str) -> str:
124        if "__versioninfo__" in line:
125            nline = re.sub(
126                rf"__versioninfo__(.*)\((\d+), (\d+), (\d+)\)",
127                f"__versioninfo__\\1({self.major}, {self.minor}, {self.patch})",
128                line,
129            )
130            return nline
131        return line
132
133
134# Handle libc++ config header
135class LibCXXProcessor(Processor):
136    def process_line(self, line: str) -> str:
137        # match #define _LIBCPP_VERSION 160000 in a relaxed way
138        match = re.match(r".*\s_LIBCPP_VERSION\s+(\d{6})$", line)
139        if match:
140            verstr = f"{str(self.major).zfill(2)}{str(self.minor).zfill(2)}{str(self.patch).zfill(2)}"
141
142            nline = re.sub(
143                rf"_LIBCPP_VERSION (\d+)",
144                f"_LIBCPP_VERSION {verstr}",
145                line,
146            )
147            return nline
148        return line
149
150
151if __name__ == "__main__":
152    parser = argparse.ArgumentParser(
153        usage="Call this script with a version and it will bump the version for you"
154    )
155    parser.add_argument("version", help="Version to bump to, e.g. 15.0.1", default=None)
156    parser.add_argument("--rc", default=None, type=int, help="RC version")
157    parser.add_argument("--git", action="store_true", help="Git version")
158    parser.add_argument(
159        "-s",
160        "--source-root",
161        default=None,
162        help="LLVM source root (/path/llvm-project). Defaults to the llvm-project the script is located in.",
163    )
164
165    args = parser.parse_args()
166
167    if args.rc and args.git:
168        raise RuntimeError("Can't specify --git and --rc at the same time!")
169
170    verstr = args.version
171
172    # parse the version string with distutils.
173    # note that -rc will end up as version.pre here
174    # since it's a prerelease
175    version = packaging.version.parse(verstr)
176
177    # Find llvm-project root
178    source_root = Path(__file__).resolve().parents[3]
179
180    if args.source_root:
181        source_root = Path(args.source_root).resolve()
182
183    files_to_update = (
184        # Main CMakeLists.
185        (source_root / "cmake" / "Modules" / "LLVMVersion.cmake", CMakeProcessor(args)),
186        # Lit configuration
187        (
188            "llvm/utils/lit/lit/__init__.py",
189            LitProcessor(args),
190        ),
191        # mlgo-utils configuration
192        (
193            "llvm/utils/mlgo-utils/mlgo/__init__.py",
194            LitProcessor(args),
195        ),
196        # GN build system
197        (
198            "llvm/utils/gn/secondary/llvm/version.gni",
199            GNIProcessor(args),
200        ),
201        (
202            "libcxx/include/__config",
203            LibCXXProcessor(args),
204        ),
205    )
206
207    for f, processor in files_to_update:
208        processor.process_file(source_root / Path(f), version)
209