blob: b3e72ff76aba4698f0a1e8c5fd4701cc2652bad2 [file] [log] [blame]
David Ostrovsky2b5fe092021-03-03 11:52:30 +01001#!/usr/bin/env python3
Christian Aistleitner95bfeba2020-06-11 11:29:50 +02002
3# This is a variant of the `workspace_status.py` script that in addition to
4# plain `git describe` implements a few heuristics to arrive at more to the
5# point stamps for directories. But due to the implemented heuristics, it will
6# typically take longer to run (especially if you use lots of plugins that
7# come without tags) and might slow down your development cycle when used
8# as default.
9#
10# To use it, simply add
11#
David Ostrovsky2b5fe092021-03-03 11:52:30 +010012# --workspace_status_command="python3 ./tools/workspace_status_release.py"
Christian Aistleitner95bfeba2020-06-11 11:29:50 +020013#
14# to your bazel command. So for example instead of
15#
16# bazel build release.war
17#
18# use
19#
David Ostrovsky2b5fe092021-03-03 11:52:30 +010020# bazel build --workspace_status_command="python3 ./tools/workspace_status_release.py" release.war
Christian Aistleitner95bfeba2020-06-11 11:29:50 +020021#
22# Alternatively, you can add
23#
David Ostrovsky2b5fe092021-03-03 11:52:30 +010024# build --workspace_status_command="python3 ./tools/workspace_status_release.py"
Christian Aistleitner95bfeba2020-06-11 11:29:50 +020025#
26# to `.bazelrc` in your home directory.
27#
28# If the script exits with non-zero code, it's considered as a failure
29# and the output will be discarded.
30
31from __future__ import print_function
32import os
33import subprocess
34import sys
35import re
36
37ROOT = os.path.abspath(__file__)
38while not os.path.exists(os.path.join(ROOT, 'WORKSPACE')):
39 ROOT = os.path.dirname(ROOT)
40REVISION_CMD = ['git', 'describe', '--always', '--dirty']
41
42
43def run(command):
44 try:
45 return subprocess.check_output(command).strip().decode("utf-8")
46 except OSError as err:
47 print('could not invoke %s: %s' % (command[0], err), file=sys.stderr)
48 sys.exit(1)
49 except subprocess.CalledProcessError:
50 # ignore "not a git repository error" to report unknown version
51 return None
52
53
54def revision_with_match(pattern=None, prefix=False, all_refs=False,
55 return_unmatched=False):
56 """Return a description of the current commit
57
58 Keyword arguments:
59 pattern -- (Default: None) Use only refs that match this pattern.
60 prefix -- (Default: False) If True, the pattern is considered a prefix
61 and does not require an exact match.
62 all_refs -- (Default: False) If True, consider all refs, not just tags.
63 return_unmatched -- (Default: False) If False and a pattern is given that
64 cannot be matched, return the empty string. If True, return
65 the unmatched description nonetheless.
66 """
67
68 command = REVISION_CMD[:]
69 if pattern:
70 command += ['--match', pattern + ('*' if prefix else '')]
71 if all_refs:
72 command += ['--all', '--long']
73
74 description = run(command)
75
76 if pattern and not return_unmatched and not description.startswith(pattern):
77 return ''
78 return description
79
80
81def branch_with_match(pattern):
82 for ref_kind in ['origin/', 'gerrit/', '']:
83 description = revision_with_match(ref_kind + pattern, all_refs=True,
84 return_unmatched=True)
85 for cutoff in ['heads/', 'remotes/', ref_kind]:
86 if description.startswith(cutoff):
87 description = description[len(cutoff):]
88 if description.startswith(pattern):
89 return description
90 return ''
91
92
93def revision(template=None):
94 if template:
95 # We use the version `v2.16.19-1-gec686a6352` as running example for the
96 # below comments. First, we split into ['v2', '16', '19']
97 parts = template.split('-')[0].split('.')
98
99 # Although we have releases with version tags containing 4 numbers, we
100 # treat only the first three numbers for simplicity. See discussion on
101 # Ib1681b2730cf2c443a3cb55fe6e282f6484e18de.
102
103 if len(parts) >= 3:
104 # Match for v2.16.19
105 version_part = '.'.join(parts[0:3])
106 description = revision_with_match(version_part)
107 if description:
108 return description
109
110 if len(parts) >= 2:
111 version_part = '.'.join(parts[0:2])
112
113 # Match for v2.16.*
114 description = revision_with_match(version_part + '.', prefix=True)
115 if description:
116 return description
117
118 # Match for v2.16
119 description = revision_with_match(version_part)
120 if description.startswith(version_part):
121 return description
122
123 if template.startswith('v'):
124 # Match for stable-2.16 branches
125 branch = 'stable-' + version_part[1:]
126 description = branch_with_match(branch)
127 if description:
128 return description
129
130 # None of the template based methods worked out, so we're falling back to
131 # generic matches.
132
133 # Match for master branch
134 description = branch_with_match('master')
135 if description:
136 return description
137
138 # Match for anything that looks like a version tag
139 description = revision_with_match('v[0-9].', return_unmatched=True)
140 if description.startswith('v'):
141 return description
142
143 # Still no good tag, so we re-try without any matching
144 return revision_with_match()
145
146
147# prints the stamps for the current working directory
148def print_stamps_for_cwd(name, template):
149 workspace_status_script = os.path.join(
150 'tools', 'workspace_status_release.py')
151 if os.path.isfile(workspace_status_script):
152 # directory has own workspace_status_command, so we use stamps from that
David Ostrovsky2b5fe092021-03-03 11:52:30 +0100153 for line in run(["python3", workspace_status_script]).split('\n'):
Christian Aistleitner95bfeba2020-06-11 11:29:50 +0200154 if re.search("^STABLE_[a-zA-Z0-9().:@/_ -]*$", line):
155 print(line)
156 else:
157 # directory lacks own workspace_status_command, so we create a default
158 # stamp
159 v = revision(template)
160 print('STABLE_BUILD_%s_LABEL %s' % (name.upper(),
161 v if v else 'unknown'))
162
163
164# os.chdir is different from plain `cd` in shells in that it follows symlinks
165# and does not update the PWD environment. So when using os.chdir to change into
166# a symlinked directory from gerrit's `plugins` or `modules` directory, we
167# cannot recover gerrit's directory. This prevents the plugins'/modules'
168# `workspace_status_release.py` scripts to detect the name they were symlinked
169# as (E.g.: it-* plugins sometimes get linked in more than once under different
170# names) and to detect gerrit's root directory. To work around this problem, we
171# mimic the `cd` of ordinary shells. By using this function, symlink information
172# is preserved in the `PWD` environment variable (as it is for example also done
173# in bash) and plugin/module `workspace_status_release.py` scripts can pick up
174# the needed information from there.
175def cd(absolute_path):
176 os.environ['PWD'] = absolute_path
177 os.chdir(absolute_path)
178
179
180def print_stamps():
181 cd(ROOT)
182 gerrit_version = revision()
183 print("STABLE_BUILD_GERRIT_LABEL %s" % gerrit_version)
184 for kind in ['modules', 'plugins']:
185 kind_dir = os.path.join(ROOT, kind)
186 for d in os.listdir(kind_dir) if os.path.isdir(kind_dir) else []:
187 p = os.path.join(kind_dir, d)
188 if os.path.isdir(p):
189 cd(p)
190 name = os.path.basename(p)
191 print_stamps_for_cwd(name, gerrit_version)
192
193
194if __name__ == '__main__':
195 print_stamps()