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
142
143
144
145
146
147
148
149
150
infra / config / scripts / milestones.py [blame]
#!/usr/bin/env vpython3
# Copyright 2020 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Script for updating the active milestones for the chromium project.
To activate a new chromium branch, run the following from the root of
the repo (where MM is the milestone number and BBBB is the branch
number):
```
scripts/chromium/milestones.py activate --milestone MM --branch BBBB
./main.star
```
To deactivate a chromium branch, run the following from the root of the
repo (where MM is the milestone number):
```
scripts/chromium/milestones.py deactivate --milestone MM
./main.star
```
Usage:
milestones.py activate --milestone XX --branch YYYY
milestones.py deactivate --milestone XX
"""
import argparse
import itertools
import json
import os
import re
import sys
INFRA_CONFIG_DIR = os.path.abspath(os.path.join(__file__, '..', '..'))
def parse_args(args=None, *, parser_type=None):
parser_type = parser_type or argparse.ArgumentParser
parser = parser_type(
description='Update the active milestones for the chromium project')
parser.set_defaults(func=None)
parser.add_argument('--milestones-json',
help='Path to the milestones.json file',
default=os.path.join(INFRA_CONFIG_DIR, 'milestones.json'))
subparsers = parser.add_subparsers()
activate_parser = subparsers.add_parser(
'activate', help='Add an additional active milestone')
activate_parser.set_defaults(func=activate_cmd)
activate_parser.add_argument(
'--milestone',
required=True,
help=('The milestone identifier '
'(e.g. the milestone number for standard release channel)'))
activate_parser.add_argument(
'--branch',
required=True,
help='The branch name, must correspond to a ref in refs/branch-heads')
deactivate_parser = subparsers.add_parser(
'deactivate', help='Remove an active milestone')
deactivate_parser.set_defaults(func=deactivate_cmd)
deactivate_parser.add_argument(
'--milestone',
required=True,
help=('The milestone identifier '
'(e.g. the milestone number for standard release channel)'))
args = parser.parse_args(args)
if args.func is None:
parser.error('no sub-command specified')
return args
class MilestonesException(Exception):
pass
_NUMBER_RE = re.compile('([0-9]+)')
def numeric_sort_key(s):
# The capture group in the regex means that the numeric portions are returned,
# odd indices will be the numeric portions of the string (the 0th or last
# element will be empty if the string starts or ends with a number,
# respectively)
pieces = _NUMBER_RE.split(s)
return [
(int(x), x) if is_numeric else x
for x, is_numeric
in zip(pieces, itertools.cycle([False, True]))
]
def add_milestone(milestones, milestone, branch):
if milestone in milestones:
raise MilestonesException(
f'there is already an active milestone with id {milestone!r}: '
f'{milestones[milestone]}')
milestones[milestone] = {
'name': f'm{milestone}',
'project': f'chromium-m{milestone}',
'ref': f'refs/branch-heads/{branch}',
}
milestones = {
k: milestones[k] for k in sorted(milestones, key=numeric_sort_key)
}
return json.dumps(milestones, indent=4) + '\n'
def activate_cmd(args):
with open(args.milestones_json) as f:
milestones = json.load(f)
milestones = add_milestone(milestones, args.milestone, args.branch)
with open(args.milestones_json, 'w') as f:
f.write(milestones)
def remove_milestone(milestones, milestone):
if milestone not in milestones:
raise MilestonesException(
f'{milestone!r} does not refer to an active milestone: '
f'{list(milestones.keys())}')
del milestones[milestone]
milestones = {
k: milestones[k] for k in sorted(milestones, key=numeric_sort_key)
}
return json.dumps(milestones, indent=4) + '\n'
def deactivate_cmd(args):
with open(args.milestones_json) as f:
milestones = json.load(f)
milestones = remove_milestone(milestones, args.milestone)
with open(args.milestones_json, 'w') as f:
f.write(milestones)
def main():
args = parse_args()
try:
args.func(args)
except MilestonesException as e:
print(str(e), file=sys.stderr)
sys.exit(1)
if __name__ == '__main__':
main()