Skip to content

Commit

Permalink
add chart of version support to version.rst
Browse files Browse the repository at this point in the history
  • Loading branch information
martin.gano committed Sep 9, 2020
1 parent 9e099f9 commit 7a9abe3
Show file tree
Hide file tree
Showing 9 changed files with 267 additions and 364 deletions.
2 changes: 1 addition & 1 deletion .gitlab-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion SUPPORT_POLICY.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Binary file removed docs/chart.png
Binary file not shown.
1 change: 1 addition & 0 deletions docs/conf_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
'sphinxcontrib.nwdiag',
'sphinxcontrib.rackdiag',
'sphinxcontrib.packetdiag',
'sphinxcontrib.cairosvgconverter',

'extensions.html_redirects',
'extensions.toctree_filter',
Expand Down
44 changes: 43 additions & 1 deletion docs/en/versions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
216 changes: 216 additions & 0 deletions docs/generate_chart.py
Original file line number Diff line number Diff line change
@@ -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)
5 changes: 5 additions & 0 deletions docs/requirements.txt
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand Down
Loading

0 comments on commit 7a9abe3

Please sign in to comment.