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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
|
#!/usr/bin/env python3
# This is a variant of the `workspace_status.py` script that in addition to
# plain `git describe` implements a few heuristics to arrive at more to the
# point stamps for directories. But due to the implemented heuristics, it will
# typically take longer to run (especially if you use lots of plugins that
# come without tags) and might slow down your development cycle when used
# as default.
#
# To use it, simply add
#
# --workspace_status_command="python3 ./tools/workspace_status_release.py"
#
# to your bazel command. So for example instead of
#
# bazel build release.war
#
# use
#
# bazel build --workspace_status_command="python3 ./tools/workspace_status_release.py" release.war
#
# Alternatively, you can add
#
# build --workspace_status_command="python3 ./tools/workspace_status_release.py"
#
# to `.bazelrc` in your home directory.
#
# If the script exits with non-zero code, it's considered as a failure
# and the output will be discarded.
from __future__ import print_function
import os
import subprocess
import sys
import re
ROOT = os.path.abspath(__file__)
while not os.path.exists(os.path.join(ROOT, 'WORKSPACE')):
ROOT = os.path.dirname(ROOT)
REVISION_CMD = ['git', 'describe', '--always', '--dirty']
def run(command):
try:
return subprocess.check_output(command).strip().decode("utf-8")
except OSError as err:
print('could not invoke %s: %s' % (command[0], err), file=sys.stderr)
sys.exit(1)
except subprocess.CalledProcessError:
# ignore "not a git repository error" to report unknown version
return None
def revision_with_match(pattern=None, prefix=False, all_refs=False,
return_unmatched=False):
"""Return a description of the current commit
Keyword arguments:
pattern -- (Default: None) Use only refs that match this pattern.
prefix -- (Default: False) If True, the pattern is considered a prefix
and does not require an exact match.
all_refs -- (Default: False) If True, consider all refs, not just tags.
return_unmatched -- (Default: False) If False and a pattern is given that
cannot be matched, return the empty string. If True, return
the unmatched description nonetheless.
"""
command = REVISION_CMD[:]
if pattern:
command += ['--match', pattern + ('*' if prefix else '')]
if all_refs:
command += ['--all', '--long']
description = run(command)
if pattern and not return_unmatched and not description.startswith(pattern):
return ''
return description
def branch_with_match(pattern):
for ref_kind in ['origin/', 'gerrit/', '']:
description = revision_with_match(ref_kind + pattern, all_refs=True,
return_unmatched=True)
for cutoff in ['heads/', 'remotes/', ref_kind]:
if description.startswith(cutoff):
description = description[len(cutoff):]
if description.startswith(pattern):
return description
return ''
def revision(template=None):
if template:
# We use the version `v2.16.19-1-gec686a6352` as running example for the
# below comments. First, we split into ['v2', '16', '19']
parts = template.split('-')[0].split('.')
# Although we have releases with version tags containing 4 numbers, we
# treat only the first three numbers for simplicity. See discussion on
# Ib1681b2730cf2c443a3cb55fe6e282f6484e18de.
if len(parts) >= 3:
# Match for v2.16.19
version_part = '.'.join(parts[0:3])
description = revision_with_match(version_part)
if description:
return description
if len(parts) >= 2:
version_part = '.'.join(parts[0:2])
# Match for v2.16.*
description = revision_with_match(version_part + '.', prefix=True)
if description:
return description
# Match for v2.16
description = revision_with_match(version_part)
if description.startswith(version_part):
return description
if template.startswith('v'):
# Match for stable-2.16 branches
branch = 'stable-' + version_part[1:]
description = branch_with_match(branch)
if description:
return description
# None of the template based methods worked out, so we're falling back to
# generic matches.
# Match for master branch
description = branch_with_match('master')
if description:
return description
# Match for anything that looks like a version tag
description = revision_with_match('v[0-9].', return_unmatched=True)
if description.startswith('v'):
return description
# Still no good tag, so we re-try without any matching
return revision_with_match()
# prints the stamps for the current working directory
def print_stamps_for_cwd(name, template):
workspace_status_script = os.path.join(
'tools', 'workspace_status_release.py')
if os.path.isfile(workspace_status_script):
# directory has own workspace_status_command, so we use stamps from that
for line in run(["python3", workspace_status_script]).split('\n'):
if re.search("^STABLE_[a-zA-Z0-9().:@/_ -]*$", line):
print(line)
else:
# directory lacks own workspace_status_command, so we create a default
# stamp
v = revision(template)
print('STABLE_BUILD_%s_LABEL %s' % (name.upper(),
v if v else 'unknown'))
# os.chdir is different from plain `cd` in shells in that it follows symlinks
# and does not update the PWD environment. So when using os.chdir to change into
# a symlinked directory from gerrit's `plugins` or `modules` directory, we
# cannot recover gerrit's directory. This prevents the plugins'/modules'
# `workspace_status_release.py` scripts to detect the name they were symlinked
# as (E.g.: it-* plugins sometimes get linked in more than once under different
# names) and to detect gerrit's root directory. To work around this problem, we
# mimic the `cd` of ordinary shells. By using this function, symlink information
# is preserved in the `PWD` environment variable (as it is for example also done
# in bash) and plugin/module `workspace_status_release.py` scripts can pick up
# the needed information from there.
def cd(absolute_path):
os.environ['PWD'] = absolute_path
os.chdir(absolute_path)
def print_stamps():
cd(ROOT)
gerrit_version = revision()
print("STABLE_BUILD_GERRIT_LABEL %s" % gerrit_version)
for kind in ['modules', 'plugins']:
kind_dir = os.path.join(ROOT, kind)
for d in os.listdir(kind_dir) if os.path.isdir(kind_dir) else []:
p = os.path.join(kind_dir, d)
if os.path.isdir(p):
cd(p)
name = os.path.basename(p)
print_stamps_for_cwd(name, gerrit_version)
if __name__ == '__main__':
print_stamps()
|