]> granicus.if.org Git - graphviz/blob - gen_version.py
gxl2gv: dedupe 'openFile' implementation
[graphviz] / gen_version.py
1 #!/usr/bin/env python3
2
3 """
4 Generate version
5
6 Release version entry format : <major>.<minor>.<patch>
7
8 Stable release version output format     : <major>.<minor>.<patch>
9 Development release version output format: <major>.<minor>.<patch>~dev.<YYYYmmdd.HHMM>
10
11 The patch version of a development release should be the same as the
12 next stable release patch number. The string "~dev." and the
13 committer date will be added.
14
15 Example sequence:
16
17 Entry version   Entry collection          Output
18 2.44.1          stable                 => 2.44.1
19 2.44.2          development            => 2.44.2~dev.20200704.1652
20 2.44.2          stable                 => 2.44.2
21 2.44.3          development            => 2.44.3~dev.20200824.1337
22 """
23
24 import argparse
25 from datetime import datetime
26 import os
27 from pathlib import Path
28 import re
29 import subprocess
30 import sys
31 from typing import Tuple
32
33 CHANGELOG = Path(__file__).parent / "CHANGELOG.md"
34 assert CHANGELOG.exists(), "CHANGELOG.md file missing"
35
36 def get_version() -> Tuple[int, int, int, str]:
37   """
38   Derive a Graphviz version from the changelog information.
39
40   Returns a tuple of major version, minor version, patch version,
41   "stable"/"development".
42   """
43
44   # is this a development revision (as opposed to a stable release)?
45   is_development = False
46
47   with open(CHANGELOG, encoding="utf-8") as f:
48     for line in f:
49
50       # is this a version heading?
51       m = re.match(r"## \[(?P<heading>[^\]]*)\]", line)
52       if m is None:
53         continue
54       heading = m.group("heading")
55
56       # is this the leading unreleased heading of a development version?
57       UNRELEASED_PREFIX = "Unreleased ("
58       if heading.startswith(UNRELEASED_PREFIX):
59         is_development = True
60         heading = heading[len(UNRELEASED_PREFIX):]
61
62       # extract the version components
63       m = re.match(r"(?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)", heading)
64       if m is None:
65         raise RuntimeError("non-version ## heading encountered before seeing a "
66                            "version heading")
67
68       major = int(m.group("major"))
69       minor = int(m.group("minor"))
70       patch = int(m.group("patch"))
71       break
72
73     else:
74       # we read the whole changelog without finding a version
75       raise RuntimeError("no version found")
76
77   if is_development:
78     coll = "development"
79   else:
80     coll = "stable"
81
82   return major, minor, patch, coll
83
84 graphviz_date_format = "%Y%m%d.%H%M"
85 iso_date_format = "%Y-%m-%d %H:%M:%S"
86
87 parser = argparse.ArgumentParser(description="Generate Graphviz version.")
88 parser.add_argument("--committer-date-iso",
89                     dest="date_format",
90                     action="store_const",
91                     const=iso_date_format,
92                     help="Print ISO formatted committer date in UTC instead of version"
93 )
94 parser.add_argument("--committer-date-graphviz",
95                     dest="date_format",
96                     action="store_const",
97                     const=graphviz_date_format,
98                     help="Print graphviz special formatted committer date in UTC "
99                     "instead of version"
100 )
101 parser.add_argument("--major",
102                     dest="component",
103                     action="store_const",
104                     const="major",
105                     help="Print major version")
106 parser.add_argument("--minor",
107                     dest="component",
108                     action="store_const",
109                     const="minor",
110                     help="Print minor version")
111 parser.add_argument("--patch",
112                     dest="component",
113                     action="store_const",
114                     const="patch",
115                     help="Print patch version")
116 parser.add_argument("--definition",
117                     action="store_true",
118                     help="Print a C-style preprocessor #define")
119 parser.add_argument("--output",
120                     type=argparse.FileType("wt", encoding="ascii"),
121                     default=sys.stdout,
122                     help="Path to write result to")
123
124 args = parser.parse_args()
125
126 date_format = args.date_format or graphviz_date_format
127
128 major_version, minor_version, patch_version, collection = get_version()
129
130 if collection == "development":
131   patch_version = f"{patch_version}~dev"
132 else:
133   patch_version = str(patch_version)
134
135 if not patch_version.isnumeric() or args.date_format:
136   os.environ["TZ"] = "UTC"
137   try:
138     committer_date = datetime.strptime(
139         subprocess.check_output(
140             [
141                 "git",
142                 "log",
143                 "-n",
144                 "1",
145                 "--format=%cd",
146                 "--date=format-local:%Y-%m-%d %H:%M:%S"
147             ],
148             cwd=os.path.abspath(os.path.dirname(__file__)),
149             universal_newlines=True,
150         ).strip(),
151         "%Y-%m-%d %H:%M:%S",
152     ).strftime(date_format)
153   except (subprocess.CalledProcessError, FileNotFoundError):
154     sys.stderr.write("Warning: build not started in a Git clone, or Git is not "
155                      "installed: setting version date to 0.\n")
156     committer_date = "0"
157
158 if not patch_version.isnumeric():
159   # Non-numerical patch version; add committer date
160   patch_version += f".{committer_date}"
161
162 if args.date_format:
163   if args.definition:
164     args.output.write(f'#define BUILDDATE "{committer_date}"\n')
165   else:
166     args.output.write(f"{committer_date}\n")
167 elif args.component == "major":
168   if args.definition:
169     args.output.write(f'#define VERSION_MAJOR "{major_version}"\n')
170   else:
171     args.output.write(f"{major_version}\n")
172 elif args.component == "minor":
173   if args.definition:
174     args.output.write(f'#define VERSION_MINOR "{minor_version}"\n')
175   else:
176     args.output.write(f"{minor_version}\n")
177 elif args.component == "patch":
178   if args.definition:
179     args.output.write(f'#define VERSION_PATCH "{patch_version}"\n')
180   else:
181     args.output.write(f"{patch_version}\n")
182 else:
183   if args.definition:
184     args.output.write(f'#define VERSION "{major_version}.{minor_version}.'
185                       f'{patch_version}"\n')
186   else:
187     args.output.write(f"{major_version}.{minor_version}.{patch_version}\n")