add chart of version support to version.rst

This commit is contained in:
martin.gano 2020-09-08 15:11:16 +02:00
parent 9e099f97d9
commit 7a9abe3ec5
9 changed files with 267 additions and 364 deletions

View File

@ -46,7 +46,7 @@ variables:
CI_TARGET_TEST_CONFIG_FILE: "$CI_PROJECT_DIR/tools/ci/config/target-test.yml"
# Versioned esp-idf-doc env image to use for all document building jobs
ESP_IDF_DOC_ENV_IMAGE: "$CI_DOCKER_REGISTRY/esp-idf-doc-env:v5"
ESP_IDF_DOC_ENV_IMAGE: "$CI_DOCKER_REGISTRY/esp-idf-doc-env:v6"
# before each job, we need to check if this job is filtered by bot stage/job filter

View File

@ -25,7 +25,7 @@ reserved for features which are needed to meet particular regulatory
requirements or standards for new products, and which carry a very low risk of
introducing regressions.)
During the Maintenace period, the version is still supported but only bugfixes
During the Maintenance period, the version is still supported but only bugfixes
for high severity issues or security issues will be applied.
Using an “In Service” version is recommended when starting a new project.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

View File

@ -54,6 +54,7 @@ extensions = ['breathe',
'sphinxcontrib.nwdiag',
'sphinxcontrib.rackdiag',
'sphinxcontrib.packetdiag',
'sphinxcontrib.cairosvgconverter',
'extensions.html_redirects',
'extensions.toctree_filter',

View File

@ -70,7 +70,49 @@ As a general guideline:
- If possible, periodically update the project to a new major or minor ESP-IDF version (for example, once a year.) The update process should be straightforward for Minor updates, but may require some planning and checking of the release notes for Major updates.
- Always plan to update to a newer release before the release you are using becomes End of Life.
.. image:: ../chart.png
Each ESP-IDF major and minor release (V4.1, V4.2, etc) is supported for
30 months after the initial stable release date.
Supported means that the ESP-IDF team will continue to apply bug fixes,
security fixes, etc to the release branch on GitHub, and periodically
make new bugfix releases as needed.
Support period is divided into "Service" and "Maintenance" period:
+-------------+--------------+---------------------------------------+
| Period | Duration | Recommended for new projects? |
+=============+==============+=======================================+
| Service | 12 months | Yes |
+-------------+--------------+---------------------------------------+
| Maintenance | 18 months | No |
+-------------+--------------+---------------------------------------+
During the Service period, bugfixes releases are more frequent. In some cases,
support for new features may be added during the Service period (this is
reserved for features which are needed to meet particular regulatory
requirements or standards for new products, and which carry a very low risk of
introducing regressions.)
During the Maintenance period, the version is still supported but only bugfixes
for high severity issues or security issues will be applied.
Using an “In Service” version is recommended when starting a new project.
Users are encouraged to upgrade all projects to a newer ESP-IDF release before
the support period finishes and the release becomes End of Life (EOL). It is our
policy to not continue fixing bugs in End of Life releases.
Pre-release versions (betas, previews, `-rc` and `-dev` versions, etc)
are not covered by any support period. Sometimes a particular feature is
marked as "Preview" in a release, which means it is also not covered
by the support period.
The ESP-IDF Programming Guide has information about the
`different versions of ESP-IDF <https://docs.espressif.com/projects/esp-idf/en/latest/versions.html>`_
(major, minor, bugfix, etc).
.. image:: https://dl.espressif.com/dl/esp-idf/support-periods.svg
Checking the Current Version

216
docs/generate_chart.py Normal file
View File

@ -0,0 +1,216 @@
#!/usr/bin/env python
# Copyright 2020 Espressif Systems (Shanghai) PTE LTD
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import argparse
import datetime as dt
import json
import numpy as np
import requests
import matplotlib.dates
import matplotlib.patches as mpatches
import matplotlib.pyplot as plt
from matplotlib.dates import MONTHLY, DateFormatter, RRuleLocator, rrulewrapper
from dateutil import parser
from dateutil.relativedelta import relativedelta
class Version(object):
def __init__(self, version_name, explicit_start_date, explicit_end_date, explicit_end_service_date=None):
self.version_name = version_name
self._start_date = parser.parse(explicit_start_date)
self._end_of_life_date = parser.parse(explicit_end_date)
self._end_service_date = parser.parse(
explicit_end_service_date) if explicit_end_service_date is not None else self.compute_end_service_date()
self.start_date_matplotlib_format = matplotlib.dates.date2num(self._start_date)
self.end_of_life_date_matplotlib_format = matplotlib.dates.date2num(self._end_of_life_date)
self.end_service_date_matplotlib_format = matplotlib.dates.date2num(self._end_service_date)
@staticmethod
def add_months(source_date, months):
return source_date + relativedelta(months=+months)
def get_start_date(self):
return self._start_date
def get_end_of_life_date(self):
return self._end_of_life_date
def get_end_service_date(self):
return self._end_service_date
def compute_end_service_date(self):
return self.add_months(self._start_date, 12)
class ChartVersions(object):
def __init__(self, url=None, filename=None):
self._releases = self._get_releases_from_url(url=url, filename=filename)
self.sorted_releases_supported = sorted(self.filter_old_versions(self._releases), key=lambda x: x.version_name,
reverse=True)
def get_releases_as_json(self):
return {
x.version_name: {
"start_date": x.get_start_date().strftime("%Y-%m-%d"),
"end_service": x.get_end_service_date().strftime("%Y-%m-%d"),
"end_date": x.get_end_of_life_date().strftime("%Y-%m-%d")
} for x in self.sorted_releases_supported
}
@staticmethod
def parse_chart_releases_from_js(js_as_string):
return json.loads(js_as_string[js_as_string.find("RELEASES: ") + len("RELEASES: "):js_as_string.rfind("};")])
def _get_all_version_from_url(self, url=None, filename=None):
releases_file = requests.get(url).text if url is not None else "".join(open(filename).readlines())
return self.parse_chart_releases_from_js(releases_file)
def _get_releases_from_url(self, url=None, filename=None):
all_versions = self._get_all_version_from_url(url, filename)
return [
Version(version_name=x,
explicit_start_date=all_versions[x]['start_date'],
explicit_end_date=all_versions[x]['end_date'] if 'end_date' in all_versions[x].keys() else None,
explicit_end_service_date=all_versions[x]['end_service'] if 'end_service' in all_versions[
x].keys() else None)
for x in all_versions.keys()
]
@staticmethod
def filter_old_versions(versions):
return list(
filter(lambda x: x.get_end_of_life_date() >= dt.datetime.now(x.get_end_of_life_date().tzinfo), versions))
@staticmethod
def months_timedelta(datetime_1, datetime2):
datetime_1, datetime2 = (datetime2, datetime_1) if datetime_1 > datetime2 else (datetime_1, datetime2)
return (datetime2.year * 12 + datetime2.month) - (datetime_1.year * 12 + datetime_1.month)
@staticmethod
def find_next_multiple_of_power_two(number, initial=3):
"""
Computes the next multiple of the number by some power of two.
>>> ChartVersions.find_next_multiple_of_power_two(7, 3)
12
"""
msb = number.bit_length()
return 3 if number <= 1 else initial << msb - 2 << (1 & number >> msb - 2)
def find_nearest_multiple_of_power_two(self, number, initial=3, prefer_next=False):
next_num = self.find_next_multiple_of_power_two(number=number - 1, initial=initial)
previous_num = next_num >> 1
return next_num if abs(next_num - number) < (abs(previous_num - number) + int(prefer_next)) else previous_num
def create_chart(self,
figure_size=(41.8330013267, 16.7332005307),
subplot=111,
step_size=0.5,
bar_height=0.3,
version_alpha=0.8,
lts_service_color='darkred',
lts_maintenance_color='red',
bar_align='center',
date_interval=None,
output_chart_name='docs/chart',
output_chart_extension='.png',
months_surrounding_chart=4,
service_period_label='Service period (Recommended for new designs)',
maintenance_period_text='Maintenance period'):
fig = plt.figure(figsize=figure_size)
ax = fig.add_subplot(subplot)
labels_count = len(self.sorted_releases_supported)
pos = np.arange(step_size, labels_count * step_size + step_size, step_size)
for release, i in zip(self.sorted_releases_supported, range(labels_count)):
start_date = release.start_date_matplotlib_format
end_of_service_date = release.end_service_date_matplotlib_format
end_date = release.end_of_life_date_matplotlib_format
ax.barh((i * step_size) + step_size, (end_of_service_date or end_date) - start_date, left=start_date,
height=bar_height, align=bar_align,
color=lts_service_color,
alpha=version_alpha,
edgecolor=lts_service_color)
if end_of_service_date is not None:
ax.barh((i * step_size) + step_size, end_date - end_of_service_date, left=end_of_service_date,
height=bar_height, align=bar_align,
color=lts_maintenance_color, alpha=version_alpha, edgecolor=lts_maintenance_color)
ax.set_ylim(bottom=0, ymax=labels_count * step_size + step_size)
max_ax_date = Version.add_months(
max(self.sorted_releases_supported,
key=lambda version: version.get_end_of_life_date().replace(tzinfo=None)).get_end_of_life_date(),
months_surrounding_chart + 1).replace(day=1)
min_ax_date = Version.add_months(
min(self.sorted_releases_supported,
key=lambda version: version.get_start_date().replace(tzinfo=None)).get_start_date(),
-months_surrounding_chart).replace(day=1)
x_ax_interval = date_interval or self.find_nearest_multiple_of_power_two(
self.months_timedelta(max_ax_date, min_ax_date) // 10)
ax.set_xlim(xmin=min_ax_date, xmax=max_ax_date)
ax.grid(color='g', linestyle=':')
ax.xaxis_date()
rule = rrulewrapper(MONTHLY, interval=x_ax_interval)
loc = RRuleLocator(rule)
formatter = DateFormatter("%b %Y")
ax.xaxis.set_major_locator(loc)
ax.xaxis.set_major_formatter(formatter)
x_labels = ax.get_xticklabels()
plt.ylabel('ESP-IDF Release', size=12)
ax.invert_yaxis()
fig.autofmt_xdate()
darkred_patch = mpatches.Patch(color=lts_service_color, label=service_period_label)
red_patch = mpatches.Patch(color=lts_maintenance_color, label=maintenance_period_text)
plt.setp(plt.yticks(pos, map(lambda x: x.version_name, self.sorted_releases_supported))[1], rotation=0,
fontsize=10, family='Tahoma')
plt.setp(x_labels, rotation=30, fontsize=11, family='Tahoma')
plt.legend(handles=[darkred_patch, red_patch], prop={'size': 10, 'family': 'Tahoma'},
bbox_to_anchor=(1.01, 1.165), loc='upper right')
fig.set_size_inches(11, 5, forward=True)
plt.savefig(output_chart_name + output_chart_extension, bbox_inches='tight')
print("Saved into " + output_chart_name + output_chart_extension)
if __name__ == '__main__':
arg_parser = argparse.ArgumentParser(
description="Create chart of version support. Set the url or filename with versions."
"If you set both filename and url the script will prefer filename.")
arg_parser.add_argument("--url", metavar="URL", default="https://dl.espressif.com/dl/esp-idf/idf_versions.js")
arg_parser.add_argument("--filename",
help="Set the name of the source file, if is set, the script ignores the url.")
arg_parser.add_argument("--output-format", help="Set the output format of the image.", default="svg")
arg_parser.add_argument("--output-file", help="Set the name of the output file.", default="docs/chart")
args = arg_parser.parse_args()
ChartVersions(url=args.url if args.filename is None else None, filename=args.filename).create_chart(
output_chart_extension="." + args.output_format.lower()[-3:], output_chart_name=args.output_file)

View File

@ -1,6 +1,10 @@
# This is a list of python packages used to generate documentation. This file is used with pip:
# pip install --user -r requirements.txt
#
# matplotlib is currently required only by the script generate_chart.py
matplotlib==3.3.1 ; python_version>="3"
matplotlib==2.0.1 ; python_version=="2.7"
cairosvg==2.4.2 # required by sphinxcontrib-svg2pdfconverter[CairoSVG]
sphinx==2.3.1
breathe==4.14.1
sphinx-copybutton==0.3.0
@ -10,6 +14,7 @@ sphinxcontrib-seqdiag==2.0.0
sphinxcontrib-actdiag==2.0.0
sphinxcontrib-nwdiag==2.0.0
sphinxcontrib-wavedrom==2.0.0
sphinxcontrib-svg2pdfconverter[CairoSVG]==1.1.0
nwdiag==2.0.0
recommonmark
future>=0.16.0 # for ../tools/gen_esp_err_to_name.py

View File

@ -1,265 +0,0 @@
#!/usr/bin/env python
import datetime as dt
import requests
import json
import os
import re
import matplotlib.dates
import matplotlib.font_manager as font_manager
import matplotlib.patches as mpatches
import matplotlib.pyplot as plt
import numpy as np
from dateutil import parser
from dateutil.relativedelta import relativedelta
from matplotlib.dates import WEEKLY, DateFormatter, RRuleLocator, rrulewrapper
class Version(object):
def __init__(self, version_name,
explicit_start_date=None,
explicit_end_date=None,
explicit_end_service_date=None,
is_lts=None):
self.version_name = version_name
self.is_lts = is_lts or self.is_version_lts()
self.is_major_minor = Version.is_minor_major_version(self.version_name)
self._start_date = parser.parse(
explicit_start_date) if explicit_start_date is not None else self._retrieve_start_date()
self._end_of_life_date = parser.parse(
explicit_end_date) if explicit_end_date is not None else self._retrieve_end_of_life_date()
self._end_service_date = parser.parse(
explicit_end_service_date) if explicit_end_service_date is not None else self.get_end_service_date()
self.start_date_matplotlib_format = matplotlib.dates.date2num(self._start_date)
self.end_of_life_date_matplotlib_format = matplotlib.dates.date2num(self._end_of_life_date)
self.end_service_date_matplotlib_format = matplotlib.dates.date2num(
self._end_service_date) if self._end_service_date is not None else None
@staticmethod
def get_config(config_path):
return json.load(open(config_path))
@staticmethod
def add_months(source_date, months):
return source_date + relativedelta(months=+months)
@staticmethod
def is_minor_major_version(version_name):
return True if len(version_name.split(".")) <= 2 else False
def is_version_lts(self):
version = self.version_name
return version >= 'v4.1'
def get_start_date(self):
return self._start_date
def get_end_of_life_date(self):
return self._end_of_life_date
def _retrieve_start_date(self):
return parser.parse(os.popen("git log -1 --format=%ai " + self.version_name))
def _retrieve_end_of_life_date(self):
return self.add_months(self._start_date, 30 if self.is_lts else 18)
def get_end_service_date(self):
return self.add_months(self._start_date, 12)
class ChartVersions(object):
def __init__(self):
self.all_versions = self.get_all_versions_from_git()
self._releases = self._get_releases_from_url(url="https://dl.espressif.com/dl/esp-idf/idf_versions.js")
self._patches = self._get_patches_from_url(url="https://dl.espressif.com/dl/esp-idf/idf_versions.js")
self.sorted_releases_supported = sorted(self.filter_old_versions(self._releases), key=lambda x: x.version_name)
self.patches_supported = self.filter_old_versions(self._patches)
# TODO test higher versions
# self.sorted_releases_supported.append(Version(version_name='v4.1', explicit_start_date="28-9-2020"))
# self.sorted_releases_supported.append(Version(version_name='v4.2', explicit_start_date="28-5-2021"))
def get_releases_as_json(self):
return {
x.version_name: {
"start_date": x.get_start_date().strftime("%Y-%m-%d"),
"end_date": x.get_end_of_life_date().strftime("%Y-%m-%d"),
"is_lts": x.is_lts
} for x in self.sorted_releases_supported + self.patches_supported
}
@staticmethod
def parse_chart_releases_from_js(js_as_string):
return json.loads(js_as_string[js_as_string.find("RELEASES: ") + len("RELEASES: "):js_as_string.rfind("};")])
def _get_all_version_from_url(self, url=None, filename=None):
releases_file = requests.get(url).text if url is not None else "".join(open(filename).readlines())
return self.parse_chart_releases_from_js(releases_file)
def _get_releases_from_url(self, url=None, filename=None):
all_versions = self._get_all_version_from_url(url, filename)
return [
Version(version_name=x,
explicit_start_date=all_versions[x]['start_date'],
explicit_end_date=all_versions[x]['end_date'] if 'end_date' in all_versions[x].keys() else None,
explicit_end_service_date=all_versions[x]['end_service'] if 'end_service' in all_versions[x].keys() else None)
for x in all_versions.keys() if Version.is_minor_major_version(x)
]
def _get_patches_from_url(self, url=None, filename=None):
all_versions = self._get_all_version_from_url(url, filename)
return [
Version(version_name=x,
explicit_start_date=all_versions[x]['start_date'],
explicit_end_date=all_versions[x]['end_date'] if 'end_date' in all_versions[x].keys() else None,
is_lts=all_versions[x]['new_policy'] if 'new_policy' in all_versions[x].keys() else None)
for x in all_versions.keys() if not Version.is_minor_major_version(x)
]
def _get_releases(self, all_versions):
return [Version(release) for release in self._get_releases_names(all_versions)]
def _get_patches(self, all_versions):
return [Version(patch, explicit_end_date=self.get_super_end_date_as_string(patch),
is_lts=self.get_super_lts_state(patch)) for patch in self._get_patches_names(all_versions)]
@staticmethod
def _get_releases_names(all_versions):
return list(filter(lambda x: Version.is_minor_major_version(x), all_versions))
@staticmethod
def _get_patches_names(all_versions):
return list(filter(lambda x: not Version.is_minor_major_version(x), all_versions))
def get_super_version(self, patch):
return list(filter(lambda x: x.version_name == self.get_super_version_name(patch), self._releases))[0]
def get_super_end_date_as_string(self, patch):
return self.get_super_version(patch).get_end_of_life_date().strftime("%m/%d/%Y, %H:%M:%S")
def get_super_lts_state(self, patch):
return self.get_super_version(patch).is_lts
@staticmethod
def get_all_versions_from_git():
"""
:returns: list of string variables meaning name of the versions or patches (e.g. v4.1, v3.3.1, etc.)
"""
all_git_tags = os.popen("git tag")
stable_releases_regex = "(?:^|)(v\d+\.\d+(?:\.\d+){0,1})(?=\n|$)"
results = [re.match(stable_releases_regex, line) for line in all_git_tags]
all_versions = [regex_result.group(1) for regex_result in results if regex_result is not None]
return all_versions
@staticmethod
def filter_old_versions(versions):
return list(
filter(lambda x: x.get_end_of_life_date() >= dt.datetime.now(x.get_end_of_life_date().tzinfo), versions))
@staticmethod
def get_super_version_name(version_name):
return ".".join(version_name.split(".")[:2])
def create_chart(self,
figure_size=(20, 8),
subplot=111,
step_size=0.5,
bar_height=0.3,
version_alpha=0.8,
patch_width=1,
lts_service_color='darkred',
lts_maintenance_color='red',
bar_align='center',
patch_color='black',
patch_alpha=1,
date_interval=10,
output_chart_name='docs/chart',
output_chart_extension='.png',
months_surrounding_chart=5):
fig = plt.figure(figsize=figure_size)
ax = fig.add_subplot(subplot)
labels_count = len(self.sorted_releases_supported)
pos = np.arange(step_size, labels_count * step_size + step_size, step_size)
mapping_releases_index = {release.version_name: i for release, i in
zip(self.sorted_releases_supported, range(labels_count))}
for release, i in zip(self.sorted_releases_supported, range(labels_count)):
start_date = release.start_date_matplotlib_format
end_of_service_date = release.end_service_date_matplotlib_format
end_date = release.end_of_life_date_matplotlib_format
ax.barh((i * step_size) + step_size, (end_of_service_date or end_date) - start_date, left=start_date,
height=bar_height, align=bar_align,
color=lts_service_color,
alpha=version_alpha,
edgecolor=lts_service_color)
if end_of_service_date is not None:
ax.barh((i * step_size) + step_size, end_date - end_of_service_date, left=end_of_service_date,
height=bar_height, align=bar_align,
color=lts_maintenance_color, alpha=version_alpha, edgecolor=lts_maintenance_color)
for patch, i in zip(self.patches_supported, range(len(self.patches_supported))):
start_date = patch.start_date_matplotlib_format
ax.barh(mapping_releases_index[self.get_super_version_name(patch.version_name)] * step_size + step_size,
patch_width, left=start_date, height=bar_height, align=bar_align, color=patch_color,
alpha=patch_alpha,
edgecolor=patch_color)
ax.text(start_date - 5, mapping_releases_index[
self.get_super_version_name(patch.version_name)] * step_size + step_size - 0.2,
patch.version_name.split('.')[-1],
fontsize='xx-small', color='darkred')
plt.setp(plt.yticks(pos, map(lambda x: x.version_name, self.sorted_releases_supported))[1], fontsize=14)
ax.set_ylim(bottom=0, ymax=labels_count * step_size + step_size)
ax.set_xlim(
xmin=Version.add_months(
min(self.sorted_releases_supported,
key=lambda version: version.get_start_date().replace(tzinfo=None)).get_start_date(),
-months_surrounding_chart),
xmax=Version.add_months(
max(self.sorted_releases_supported,
key=lambda version: version.get_end_of_life_date().replace(tzinfo=None)).get_end_of_life_date(),
months_surrounding_chart))
ax.grid(color='g', linestyle=':')
ax.xaxis_date()
rule = rrulewrapper(WEEKLY, interval=date_interval)
loc = RRuleLocator(rule)
formatter = DateFormatter("%d-%b '%y")
ax.xaxis.set_major_locator(loc)
ax.xaxis.set_major_formatter(formatter)
x_labels = ax.get_xticklabels()
plt.setp(x_labels, rotation=30, fontsize=10)
ax.legend(loc=1, prop=font_manager.FontProperties(size='small'))
ax.invert_yaxis()
fig.autofmt_xdate()
darkred_patch = mpatches.Patch(color='darkred', label='Service period (Recommended for new designs)')
red_patch = mpatches.Patch(color='red', label='Maintenance period')
plt.legend(handles=[darkred_patch, red_patch], prop={'size': 6})
fig.set_size_inches(10, 5, forward=True)
plt.savefig(output_chart_name + output_chart_extension)
plt.show()
if __name__ == '__main__':
ChartVersions().create_chart()

View File

@ -1,96 +0,0 @@
var DOCUMENTATION_VERSIONS = {
DEFAULTS: {
has_targets: false,
supported_targets: ["esp32"]
},
VERSIONS: [{
name: "latest",
has_targets: true,
supported_targets: ["esp32", "esp32s2"]
},
{
name: "v4.1-rc",
pre_release: true
},
{
name: "v4.0.1",
old: false
},
{
name: "v4.0",
old: true
},
{
name: "v3.3.2"
},
{
name: "v3.3.1",
old: true
},
{
name: "v3.3",
old: true
},
{
name: "v3.2.3",
old: false
},
{
name: "v3.1.7",
old: false
},
{
name: "v3.1.6",
old: true
},
{
name: "v3.0.9",
old: true
},
{
name: "release-v4.2",
pre_release: true,
has_targets: true,
supported_targets: ["esp32", "esp32s2"]
},
{
name: "release-v4.1",
pre_release: true
},
{
name: "release-v4.0",
pre_release: true
},
{
name: "release-v3.3",
pre_release: true
},
{
name: "release-v3.2",
pre_release: true
},
{
name: "release-v3.1",
pre_release: true
}
],
RELEASES: {
"v3.1": {
"start_date": "2018-09-07",
"end_date": "2020-10-31"
},
"v3.2": {
"start_date": "2019-04-11",
"end_date": "2020-10-31"
},
"v3.3": {
"start_date": "2019-09-05",
"end_date": "2022-03-31"
},
"v4.0": {
"start_date": "2020-02-11",
"end_service": "2021-02-11",
"end_date": "2021-10-31"
}
}
};