File: make-version.py

package info (click to toggle)
rumur 2020.12.20-1
  • links: PTS, VCS
  • area: main
  • in suites: bullseye
  • size: 3,292 kB
  • sloc: cpp: 17,090; ansic: 2,537; objc: 1,542; python: 1,120; sh: 538; yacc: 536; lex: 229; lisp: 15; makefile: 5
file content (142 lines) | stat: -rwxr-xr-x 3,598 bytes parent folder | download
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
#!/usr/bin/env python3

'''
Generate contents of a version.cc.
'''

import os
import re
import subprocess as sp
import sys
from typing import Optional

def last_release() -> str:
  '''
  The version of the last tagged release of Rumur. This will be used as the
  version number if no Git information is available.
  '''
  with open(os.path.join(os.path.dirname(os.path.abspath(__file__)),
      '../../CHANGELOG.rst'), 'rt') as f:
    for line in f:
      m = re.match(r'(v\d{4}\.\d{2}\.\d{2})$', line)
      if m is not None:
        return m.group(1)

  raise Exception('version heading not found in changelog')

def has_git() -> bool:
  '''
  Return True if we are in a Git repository and have Git.
  '''

  # Return False if we don't have Git.
  try:
    sp.check_call(['which', 'git'], stdout=sp.DEVNULL, stderr=sp.DEVNULL)
  except:
    return False

  # Return False if we have no Git repository information.
  if not os.path.exists(os.path.join(os.path.dirname(__file__),
      '..', '..', '.git')):
    return False

  return True

def get_tag() -> Optional[str]:
  '''
  Find the version tag of the current Git commit, e.g. v2020.05.03, if it
  exists.
  '''
  try:
    tag = sp.check_output(['git', 'describe', '--tags'], stderr=sp.DEVNULL)
  except sp.CalledProcessError:
    tag = None

  if tag is not None:
    tag = tag.decode('utf-8', 'replace').strip()
    if re.match(r'v[\d\.]+$', tag) is None:
      # Not a version tag.
      tag = None

  return tag

def get_sha() -> str:
  '''
  Find the hash of the current Git commit.
  '''
  rev = sp.check_output(['git', 'rev-parse', '--verify', 'HEAD'])
  rev = rev.decode('utf-8', 'replace').strip()

  return rev

def is_dirty() -> bool:
  '''
  Determine whether the current working directory has uncommitted changes.
  '''
  dirty = False

  p = sp.run(['git', 'diff', '--exit-code'], stdout=sp.DEVNULL,
    stderr=sp.DEVNULL)
  dirty |= p.returncode != 0

  p = sp.run(['git', 'diff', '--cached', '--exit-code'], stdout=sp.DEVNULL,
    stderr=sp.DEVNULL)
  dirty |= p.returncode != 0

  return dirty

def main(args: [str]) -> int:

  if len(args) != 2 or args[1] == '--help':
    sys.stderr.write(
      f'usage: {args[0]} file\n'
       ' write version information as a C++ source file\n')
    return -1

  # Get the contents of the old version file if it exists.
  old = None
  if os.path.exists(args[1]):
    with open(args[1], 'rt') as f:
      old = f.read()

  version = None

  # first, look for an environment variable that overrides other version sources
  version = os.environ.get('RUMUR_VERSION')

  # second, look for a version tag on the current commit
  if version is None and has_git():
    tag = get_tag()
    if tag is not None:
      version = f'{tag}{" (dirty)" if is_dirty() else ""}'

  # third, look for the commit hash as the version
  if version is None and has_git():
    rev = get_sha()
    assert rev is not None
    version = f'Git commit {rev}{" (dirty)" if is_dirty() else ""}'

  # Finally, fall back to our known release version.
  if version is None:
    version = last_release()

  new =  '#pragma once\n' \
         '\n' \
         'namespace rumur {\n' \
         '\n' \
         'static constexpr const char *get_version() {\n' \
        f'  return "{version}";\n' \
         '}\n' \
         '\n' \
         '}'

  # If the version has changed, update the output. Otherwise we leave the old
  # contents -- and more importantly, the timestamp -- intact.
  if old != new:
    with open(args[1], 'wt') as f:
      f.write(new)

  return 0

if __name__ == '__main__':
  sys.exit(main(sys.argv))