| #!/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, 'MODULE.bazel')): | |
| 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() |