-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Expand file tree
/
Copy pathupdate_versions.py
More file actions
executable file
·141 lines (118 loc) · 5.56 KB
/
update_versions.py
File metadata and controls
executable file
·141 lines (118 loc) · 5.56 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
#!/usr/bin/env python3
"""
This script inserts "versionadded" directives into .rst documents found in the
Help/ directory and module documentation comments found in the Modules/
directory. It can be run from any directory within the CMake repository.
Each file is assigned a CMake version in which it first appears,
according to the git version tags.
Options:
--overwrite Replace existing "versionadded" directives.
Default: existing directives are left unchanged.
--baseline Files present in this tag don't need a version directive.
Default: v3.0.0
--since Files present in this tag will be ignored.
Only newer files will be operated on.
Default: v3.0.0
--next-version The next CMake version, which hasn't been tagged yet.
Default: extracted from Source/CMakeVersion.cmake
"""
import re
import pathlib
import subprocess
import argparse
tag_re = re.compile(r'^v[34]\.(\d+)\.(\d+)(?:-rc(\d+))?$')
path_re = re.compile(r'Help/(?!dev|guide|manual|cpack_|release).*\.rst|Modules/[^/]*\.cmake$')
def git_root():
"""Return the root of the .git repository from the current directory."""
result = subprocess.run(
['git', 'rev-parse', '--show-toplevel'], check=True, universal_newlines=True, capture_output=True)
return pathlib.Path(result.stdout.strip())
def git_tags():
"""Return a list of CMake version tags from the repository."""
result = subprocess.run(['git', 'tag'], check=True, universal_newlines=True, capture_output=True)
return [tag for tag in result.stdout.splitlines() if tag_re.match(tag)]
def git_list_tree(ref):
"""Return a list of help and module files in a given git reference."""
result = subprocess.run(
['git', 'ls-tree', '-r', '--full-name', '--name-only', ref, ':/'],
check=True, universal_newlines=True, capture_output=True)
return [path for path in result.stdout.splitlines() if path_re.match(path)]
def tag_version(tag):
"""Extract a clean CMake version from a git version tag."""
return re.sub(r'^v|\.0(-rc\d+)?$', '', tag)
def tag_sortkey(tag):
"""Sorting key for a git version tag."""
return tuple(int(part or '1000') for part in tag_re.match(tag).groups())
def make_version_map(baseline, since, next_version):
"""Map repository file paths to CMake versions in which they first appear."""
versions = {}
if next_version:
for path in git_list_tree('HEAD'):
versions[path] = next_version
for tag in sorted(git_tags(), key=tag_sortkey, reverse=True):
version = tag_version(tag)
for path in git_list_tree(tag):
versions[path] = version
if baseline:
for path in git_list_tree(baseline):
versions[path] = None
if since:
for path in git_list_tree(since):
versions.pop(path, None)
return versions
cmake_version_re = re.compile(
rb'set\(CMake_VERSION_MAJOR\s+(\d+)\)\s+set\(CMake_VERSION_MINOR\s+(\d+)\)\s+set\(CMake_VERSION_PATCH\s+(\d+)\)', re.S)
def cmake_version(path):
"""Extract the current MAJOR.MINOR CMake version from CMakeVersion.cmake found at `path`."""
match = cmake_version_re.search(path.read_bytes())
major, minor, patch = map(int, match.groups())
minor += patch > 20000000 # nightly version will become the next minor
return f'{major}.{minor}'
stamp_re = re.compile(
rb'(?P<PREFIX>(^|\[\.rst:\r?\n)[^\r\n]+\r?\n[*^\-=#]+(?P<NL>\r?\n))(?P<STAMP>\s*\.\. versionadded::[^\r\n]*\r?\n)?')
stamp_pattern_add = rb'\g<PREFIX>\g<NL>.. versionadded:: VERSION\g<NL>'
stamp_pattern_remove = rb'\g<PREFIX>'
def update_file(path, version, overwrite):
try:
data = path.read_bytes()
except FileNotFoundError as e:
return False
def _replacement(match):
if not overwrite and match.start('STAMP') != -1:
return match.group()
if version:
pattern = stamp_pattern_add.replace(b'VERSION', version.encode('utf-8'))
else:
pattern = stamp_pattern_remove
return match.expand(pattern)
new_data, nrepl = stamp_re.subn(_replacement, data, 1)
if nrepl and new_data != data:
path.write_bytes(new_data)
return True
return False
def update_repo(repo_root, version_map, overwrite):
total = 0
for path, version in version_map.items():
if update_file(repo_root / path, version, overwrite):
print(f"Version {version or '<none>':6} for {path}")
total += 1
print(f"Updated {total} file(s)")
def main():
parser = argparse.ArgumentParser(allow_abbrev=False)
parser.add_argument('--overwrite', action='store_true', help="overwrite existing version tags")
parser.add_argument('--baseline', metavar='TAG', default='v3.0.0',
help="files present in this tag don't need a version directive (default: v3.0.0)")
parser.add_argument('--since', metavar='TAG',
help="apply changes only to files added after this tag")
parser.add_argument('--next-version', metavar='VER',
help="version for files not present in any tag (default: from CMakeVersion.cmake)")
args = parser.parse_args()
try:
repo_root = git_root()
next_version = args.next_version or cmake_version(repo_root / 'Source/CMakeVersion.cmake')
version_map = make_version_map(args.baseline, args.since, next_version)
update_repo(repo_root, version_map, args.overwrite)
except subprocess.CalledProcessError as e:
print(f"Command '{' '.join(e.cmd)}' returned code {e.returncode}:\n{e.stderr.strip()}")
if __name__ == '__main__':
main()