| #!/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() |