xref: /openbmc/openbmc/poky/documentation/set_versions.py (revision c9537f57ab488bf5d90132917b0184e2527970a5)
1#!/usr/bin/env python3
2#
3# Add version information to poky.yaml based upon current git branch/tags
4# Also generate the list of available manuals (releases.rst file)
5#
6# Copyright Linux Foundation
7# Author: Richard Purdie <richard.purdie@linuxfoundation.org>
8# Author: Quentin Schulz <foss@0leil.net>
9#
10# SPDX-License-Identifier: MIT
11#
12
13
14import subprocess
15import collections
16import sys
17import os
18import itertools
19import re
20
21ourversion = None
22if len(sys.argv) == 2:
23    ourversion = sys.argv[1]
24
25ourversion = None
26if len(sys.argv) == 2:
27    ourversion = sys.argv[1]
28
29activereleases = ["walnascar", "scarthgap", "kirkstone"]
30devbranch = "whinlatter"
31ltsseries = ["scarthgap", "kirkstone"]
32
33# used by run-docs-builds to get the default page
34if ourversion == "getlatest":
35    print(activereleases[0])
36    sys.exit(0)
37
38release_series = collections.OrderedDict()
39release_series["whinlatter"] = "5.3"
40release_series["walnascar"] = "5.2"
41release_series["styhead"] = "5.1"
42release_series["scarthgap"] = "5.0"
43release_series["nanbield"] = "4.3"
44release_series["mickledore"] = "4.2"
45release_series["langdale"] = "4.1"
46release_series["kirkstone"] = "4.0"
47release_series["honister"] = "3.4"
48release_series["hardknott"] = "3.3"
49release_series["gatesgarth"] = "3.2"
50release_series["dunfell"] = "3.1"
51release_series["zeus"] = "3.0"
52release_series["warrior"] = "2.7"
53release_series["thud"] = "2.6"
54release_series["sumo"] = "2.5"
55release_series["rocko"] = "2.4"
56release_series["pyro"] = "2.3"
57release_series["morty"] = "2.2"
58release_series["krogoth"] = "2.1"
59release_series["jethro"] = "2.0"
60release_series["jethro-pre"] = "1.9"
61release_series["fido"] = "1.8"
62release_series["dizzy"] = "1.7"
63release_series["daisy"] = "1.6"
64release_series["dora"] = "1.5"
65release_series["dylan"] = "1.4"
66release_series["danny"] = "1.3"
67release_series["denzil"] = "1.2"
68release_series["edison"] = "1.1"
69release_series["bernard"] = "1.0"
70release_series["laverne"] = "0.9"
71
72
73bitbake_mapping = {
74    "whinlatter" : "2.14",
75    "walnascar" : "2.12",
76    "styhead" : "2.10",
77    "scarthgap" : "2.8",
78    "nanbield" : "2.6",
79    "mickledore" : "2.4",
80    "langdale" : "2.2",
81    "kirkstone" : "2.0",
82    "honister" : "1.52",
83    "hardknott" : "1.50",
84    "gatesgarth" : "1.48",
85    "dunfell" : "1.46",
86}
87
88# 3.4 onwards doesn't have poky version
89# Early 3.4 release docs do reference it though
90poky_mapping = {
91    "3.4" : "26.0",
92    "3.3" : "25.0",
93    "3.2" : "24.0",
94    "3.1" : "23.0",
95}
96
97ourseries = None
98ourbranch = None
99bitbakeversion = None
100docconfver = None
101
102# Test tags exist and inform the user to fetch if not
103try:
104    subprocess.run(["git", "show", "yocto-%s" % release_series[activereleases[0]]], stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True)
105except subprocess.CalledProcessError:
106    sys.exit("Please run 'git fetch --tags' before building the documentation")
107
108# Try and figure out what we are
109tags = subprocess.run(["git", "tag", "--points-at", "HEAD"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True).stdout
110for t in tags.split():
111    if t.startswith("yocto-"):
112        ourversion = t[6:]
113
114if ourversion:
115    # We're a tagged release
116    components = ourversion.split(".")
117    baseversion = components[0] + "." + components[1]
118    docconfver = ourversion
119    for i in release_series:
120        if release_series[i] == baseversion:
121            ourseries = i
122            ourbranch = i
123            if i in bitbake_mapping:
124                bitbakeversion = bitbake_mapping[i]
125else:
126    # We're floating on a branch
127    branch = subprocess.run(["git", "branch", "--show-current"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True).stdout.strip()
128    ourbranch = branch
129    if branch != "master" and branch not in release_series:
130        # We're not on a known release branch so we have to guess. Compare the numbers of commits
131        # from each release branch and assume the smallest number of commits is the one we're based off
132        possible_branch = None
133        branch_count = 0
134        for b in itertools.chain(release_series.keys(), ["master"]):
135            result = subprocess.run(["git", "log", "--format=oneline",  "HEAD..origin/" + b], stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)
136            if result.returncode == 0:
137                count = result.stdout.count('\n')
138                if not possible_branch or count < branch_count:
139                    print("Branch %s has count %s" % (b, count))
140                    possible_branch = b
141                    branch_count = count
142        if possible_branch:
143            branch = possible_branch
144        else:
145            branch = "master"
146        print("Nearest release branch estimated to be %s" % branch)
147    if branch == "master":
148        ourseries = devbranch
149        docconfver = "dev"
150        bitbakeversion = "dev"
151    elif branch in release_series:
152        ourseries = branch
153        if branch in bitbake_mapping:
154            bitbakeversion = bitbake_mapping[branch]
155    else:
156        sys.exit("Unknown series for branch %s" % branch)
157
158    previoustags = subprocess.run(["git", "tag", "--merged", "HEAD"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True).stdout
159    previoustags = [t[6:] for t in previoustags.split() if t.startswith("yocto-" + release_series[ourseries])]
160    futuretags = subprocess.run(["git", "tag", "--merged", ourbranch], stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True).stdout
161    futuretags = [t[6:] for t in futuretags.split() if t.startswith("yocto-" + release_series[ourseries])]
162
163    # Append .999 against the last known version
164    if len(previoustags) != len(futuretags):
165        ourversion = previoustags[-1] + ".999"
166    else:
167        ourversion = release_series[ourseries] + ".999"
168    if not docconfver:
169        docconfver = ourversion
170
171series = [k for k in release_series]
172previousseries = series[series.index(ourseries)+1:] or [""]
173lastlts = [k for k in previousseries if k in ltsseries] or "dunfell"
174
175latestreltag = subprocess.run(["git", "describe", "--abbrev=0", "--tags", "--match", "yocto-*"], capture_output=True, text=True).stdout
176latestreltag = latestreltag.strip()
177if latestreltag:
178    if latestreltag.startswith("yocto-"):
179        latesttag = latestreltag[6:]
180else:
181    # fallback on the calculated version
182    print("Did not find a tag with 'git describe', falling back to %s" % ourversion)
183    latestreltag = "yocto-" + ourversion
184    latesttag = ourversion
185
186print("Version calculated to be %s" % ourversion)
187print("Latest release tag found is %s" % latestreltag)
188print("Release series calculated to be %s" % ourseries)
189
190replacements = {
191    "DISTRO" : ourversion,
192    "DISTRO_LATEST_TAG": latesttag,
193    "DISTRO_NAME_NO_CAP" : ourseries,
194    "DISTRO_NAME" : ourseries.capitalize(),
195    "DISTRO_NAME_NO_CAP_MINUS_ONE" : previousseries[0],
196    "DISTRO_NAME_NO_CAP_LTS" : lastlts[0],
197    "YOCTO_DOC_VERSION" : ourversion,
198    "DOCCONF_VERSION" : docconfver,
199    "BITBAKE_SERIES" : bitbakeversion,
200}
201
202if release_series[ourseries] in poky_mapping:
203    pokyversion = poky_mapping[release_series[ourseries]]
204    if ourversion != release_series[ourseries]:
205        pokyversion = pokyversion + "." + ourversion.rsplit(".", 1)[1]
206    else:
207        pokyversion = pokyversion + ".0"
208    replacements["POKYVERSION"] = pokyversion
209
210if os.path.exists("poky.yaml.in"):
211    with open("poky.yaml.in", "r") as r, open("poky.yaml", "w") as w:
212        lines = r.readlines()
213        for line in lines:
214            data = line.split(":")
215            k = data[0].strip()
216            if k in replacements:
217                w.write("%s : \"%s\"\n" % (k, replacements[k]))
218            else:
219                w.write(line)
220
221    print("poky.yaml generated from poky.yaml.in")
222
223
224# In the switcher list of versions we display:
225#  - latest dev
226#  - latest stable release
227#  - latest LTS
228#  - latest for each releases listed as active
229#  - latest doc version in current series
230#  - current doc version
231# (with duplicates removed)
232
233versions = []
234with open("sphinx-static/switchers.js.in", "r") as r, open("sphinx-static/switchers.js", "w") as w:
235    lines = r.readlines()
236    for line in lines:
237        if "ALL_RELEASES_PLACEHOLDER" in line:
238            w.write(str(list(release_series.keys())))
239            continue
240        if "VERSIONS_PLACEHOLDER" in line:
241            w.write("    'dev': { 'title': 'Unstable (dev)', 'obsolete': false,},\n")
242            for branch in activereleases + ([ourseries] if ourseries not in activereleases else []):
243                if branch == devbranch:
244                    continue
245                branch_versions = subprocess.run('git tag --list yocto-%s*' % (release_series[branch]), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True).stdout.split()
246                branch_versions = sorted([v.replace("yocto-" +  release_series[branch] + ".", "").replace("yocto-" +  release_series[branch], "0") for v in branch_versions], key=int)
247                if not branch_versions:
248                    continue
249                version = release_series[branch]
250                if branch_versions[-1] != "0":
251                    version = version + "." + branch_versions[-1]
252                versions.append(version)
253                w.write("    '%s': {'title': '%s (%s)', 'obsolete': %s,},\n" % (version, branch.capitalize(), version, str(branch not in activereleases).lower()))
254            if ourversion not in versions and ourseries != devbranch:
255                w.write("    '%s': {'title': '%s (%s)', 'obsolete': %s,},\n" % (ourversion, ourseries.capitalize(), ourversion, str(ourseries not in activereleases).lower()))
256        else:
257            w.write(line)
258
259print("switchers.js generated from switchers.js.in")
260
261# generate releases.rst
262
263# list missing tags in yocto-docs
264missing_tags = [
265        'yocto-0.9',
266        'yocto-1.0', 'yocto-1.0.1',
267        'yocto-1.1', 'yocto-1.1.1',
268        'yocto-1.2',
269        'yocto-1.4.4', 'yocto-1.4.5',
270        'yocto-1.5', 'yocto-1.5.2', 'yocto-1.5.3', 'yocto-1.5.4',
271        'yocto-1.6', 'yocto-1.6.1', 'yocto-1.6.2',
272        'yocto-1.7', 'yocto-1.7.1',
273        'yocto-1.9',
274        'yocto-2.5.3',
275        'yocto-3.1', 'yocto-3.1.1', 'yocto-3.1.2', 'yocto-3.1.3',
276        ]
277
278semver = re.compile(r'yocto-(\d+)\.(\d+)(?:\.)?(\d*)')
279
280# git is able to properly order semver versions but not python
281# instead of adding a dependency on semver module, let's convert the version
282# into a decimal number, e.g. 11.23.1 will be 112301 and 1.5 will be 010500 so
283# it can be used as a key for the sorting algorithm.
284# This can be removed once all the old tags are re-created.
285def tag_to_semver_like(v):
286    v_semver = semver.search(v)
287    v_maj, v_min, v_patch = v_semver.groups('0')
288    return int("{:0>2}{:0>2}{:0>2}".format(v_maj, v_min, v_patch), 10)
289
290yocto_tags = subprocess.run(["git", "tag", "--list", "--sort=version:refname", "yocto-*"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True).stdout
291yocto_tags = sorted(yocto_tags.split() + missing_tags, key=tag_to_semver_like)
292tags = [tag[6:] for tag in yocto_tags]
293
294with open('releases.rst', 'w') as f:
295    f.write('===========================\n')
296    f.write(' Supported Release Manuals\n')
297    f.write('===========================\n')
298    f.write('\n')
299
300    for activerelease in activereleases:
301        title = "Release Series %s (%s)" % (release_series[activerelease], activerelease)
302        f.write('*' * len(title) + '\n')
303        f.write(title + '\n')
304        f.write('*' * len(title) + '\n')
305        f.write('\n')
306
307        for tag in tags:
308            if tag == release_series[activerelease] or tag.startswith('%s.' % release_series[activerelease]):
309                f.write('- :yocto_docs:`%s Documentation </%s>`\n' % (tag, tag))
310        f.write('\n')
311
312    f.write('==========================\n')
313    f.write(' Outdated Release Manuals\n')
314    f.write('==========================\n')
315    f.write('\n')
316
317    for series in release_series:
318        if series == devbranch or series in activereleases:
319            continue
320
321        if series == "jethro-pre":
322            continue
323
324        title = "Release Series %s (%s)" % (release_series[series], series)
325        f.write('*' * len(title) + '\n')
326        f.write(title + '\n')
327        f.write('*' * len(title) + '\n')
328        f.write('\n')
329        if series == "jethro":
330            f.write('- :yocto_docs:`1.9 Documentation </1.9>`\n')
331        for tag in tags:
332            if tag == release_series[series] or tag.startswith('%s.' % release_series[series]):
333                f.write('- :yocto_docs:`%s Documentation </%s>`\n' % (tag, tag))
334        f.write('\n')
335
336
337