Skip to content

Commit 716f826

Browse files
committed
feat: Added automatic resolving of supported IDF and Python versions
1 parent 3308a4a commit 716f826

File tree

4 files changed

+246
-55
lines changed

4 files changed

+246
-55
lines changed

.github/workflows/build-wheels-platforms.yml

Lines changed: 45 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,16 @@ on:
66
workflow_dispatch:
77

88
env:
9-
MIN_IDF_MAJOR_VERSION: ${{ vars.MIN_IDF_MAJOR_VERSION }}
10-
MIN_IDF_MINOR_VERSION: ${{ vars.MIN_IDF_MINOR_VERSION }}
119
GH_TOKEN : ${{ secrets.GITHUB_TOKEN }}
1210

1311
jobs:
12+
get-supported-versions:
13+
name: Get Supported Versions
14+
uses: ./.github/workflows/get-supported-versions.yml
15+
secrets: inherit
16+
1417
build-wheels:
18+
needs: get-supported-versions
1519
name: Build for ${{ matrix.os }} (Python ${{matrix.python-version}})
1620
runs-on: ${{ matrix.os }}
1721
strategy:
@@ -26,10 +30,10 @@ jobs:
2630
- linux-arm64-self-hosted
2731
include:
2832
- os: linux-armv7-self-hosted
29-
CONTAINER: python:3.8-bullseye
33+
CONTAINER: python:${{ needs.get-supported-versions.outputs.oldest_supported_python }}-bullseye
3034
- os: linux-arm64-self-hosted
31-
CONTAINER: python:3.8-bullseye
32-
python-version: ['3.8']
35+
CONTAINER: python:${{ needs.get-supported-versions.outputs.oldest_supported_python }}-bullseye
36+
python-version: ['${{ needs.get-supported-versions.outputs.oldest_supported_python }}']
3337

3438
# Use python container on ARM
3539
container: ${{ matrix.CONTAINER }}
@@ -50,6 +54,30 @@ jobs:
5054
- name: Checkout repository
5155
uses: actions/checkout@v4
5256

57+
- name: Set IDF version environment variables
58+
if: matrix.os != 'windows-latest'
59+
run: |
60+
IDF_VERSION="${{ needs.get-supported-versions.outputs.oldest_supported_idf }}"
61+
# Extract major version (e.g., "v5.1" -> "5")
62+
IDF_MAJOR=$(echo "$IDF_VERSION" | sed 's/v//' | cut -d'.' -f1)
63+
# Extract minor version (e.g., "v5.1" -> "1")
64+
IDF_MINOR=$(echo "$IDF_VERSION" | sed 's/v//' | cut -d'.' -f2)
65+
echo "MIN_IDF_MAJOR_VERSION=$IDF_MAJOR" >> $GITHUB_ENV
66+
echo "MIN_IDF_MINOR_VERSION=$IDF_MINOR" >> $GITHUB_ENV
67+
echo "Setting MIN_IDF_MAJOR_VERSION=$IDF_MAJOR, MIN_IDF_MINOR_VERSION=$IDF_MINOR"
68+
69+
- name: Set IDF version environment variables - Windows
70+
if: matrix.os == 'windows-latest'
71+
run: |
72+
$IDF_VERSION="${{ needs.get-supported-versions.outputs.oldest_supported_idf }}"
73+
# Extract major and minor versions (e.g., "v5.1" -> major="5", minor="1")
74+
$IDF_VERSION_CLEAN = $IDF_VERSION -replace "^v", ""
75+
$IDF_PARTS = $IDF_VERSION_CLEAN -split "\."
76+
$IDF_MAJOR = $IDF_PARTS[0]
77+
$IDF_MINOR = $IDF_PARTS[1]
78+
echo "MIN_IDF_MAJOR_VERSION=$IDF_MAJOR" >> $env:GITHUB_ENV
79+
echo "MIN_IDF_MINOR_VERSION=$IDF_MINOR" >> $env:GITHUB_ENV
80+
echo "Setting MIN_IDF_MAJOR_VERSION=$IDF_MAJOR, MIN_IDF_MINOR_VERSION=$IDF_MINOR"
5381
5482
- name: Setup Python
5583
# Skip setting python on ARM because of missing compatibility: https://github.com/actions/setup-python/issues/108
@@ -112,12 +140,16 @@ jobs:
112140

113141

114142
build-python-version-dependent-wheels:
115-
needs: build-wheels
143+
needs: [get-supported-versions, build-wheels]
116144
name: Build Python version dependendent wheels for IDF
117-
uses: espressif/idf-python-wheels/.github/workflows/build-wheels-python-dependent.yml@main
118-
119-
upload-python-wheels:
120-
needs: [build-wheels, build-python-version-dependent-wheels]
121-
name: Upload Python wheels
122-
uses: espressif/idf-python-wheels/.github/workflows/upload-python-wheels.yml@main
123-
secrets: inherit
145+
uses: ./.github/workflows/build-wheels-python-dependent.yml
146+
with:
147+
supported_python_versions: ${{ needs.get-supported-versions.outputs.supported_python }}
148+
oldest_supported_python: ${{ needs.get-supported-versions.outputs.oldest_supported_python }}
149+
150+
# TODO Uncomment this when we are ready to upload the wheels
151+
#upload-python-wheels:
152+
# needs: [build-wheels, build-python-version-dependent-wheels]
153+
# name: Upload Python wheels
154+
# uses: ./.github/workflows/upload-python-wheels.yml
155+
# secrets: inherit

.github/workflows/build-wheels-python-dependent.yml

Lines changed: 27 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,15 @@ name: Build Python version dependent wheels
22

33
on:
44
workflow_call:
5+
inputs:
6+
supported_python_versions:
7+
description: 'JSON array of supported Python versions'
8+
required: true
9+
type: string
10+
oldest_supported_python:
11+
description: 'Oldest supported Python version to exclude from builds'
12+
required: true
13+
type: string
514

615
jobs:
716
triage:
@@ -17,48 +26,24 @@ jobs:
1726
- macos-latest # MacOS arm64 (M1)
1827
- linux-armv7-self-hosted
1928
- linux-arm64-self-hosted
20-
python-version:
21-
- '3.9'
22-
- '3.10'
23-
- '3.11'
24-
- '3.12'
25-
- '3.13'
26-
include:
27-
- os: linux-armv7-self-hosted
28-
python-version: '3.9'
29-
CONTAINER: 'python:3.9-bullseye'
30-
- os: linux-armv7-self-hosted
31-
python-version: '3.10'
32-
CONTAINER: 'python:3.10-bullseye'
33-
- os: linux-armv7-self-hosted
34-
python-version: '3.11'
35-
CONTAINER: 'python:3.11-bullseye'
36-
- os: linux-armv7-self-hosted
37-
python-version: '3.12'
38-
CONTAINER: 'python:3.12-bullseye'
39-
- os: linux-armv7-self-hosted
40-
python-version: '3.13'
41-
CONTAINER: 'python:3.13-bullseye'
42-
43-
- os: linux-arm64-self-hosted
44-
python-version: '3.9'
45-
CONTAINER: 'python:3.9-bullseye'
46-
- os: linux-arm64-self-hosted
47-
python-version: '3.10'
48-
CONTAINER: 'python:3.10-bullseye'
49-
- os: linux-arm64-self-hosted
50-
python-version: '3.11'
51-
CONTAINER: 'python:3.11-bullseye'
52-
- os: linux-arm64-self-hosted
53-
python-version: '3.12'
54-
CONTAINER: 'python:3.12-bullseye'
55-
- os: linux-arm64-self-hosted
56-
python-version: '3.13'
57-
CONTAINER: 'python:3.13-bullseye'
58-
59-
60-
# Use python container on ARM
61-
container: ${{ matrix.CONTAINER }}
29+
python-version: ${{ fromJson(inputs.supported_python_versions) }}
30+
exclude:
31+
# Exclude oldest supported Python since it's already built in the platform builds
32+
- python-version: ${{ inputs.oldest_supported_python }}
33+
os: windows-latest
34+
- python-version: ${{ inputs.oldest_supported_python }}
35+
os: ubuntu-latest
36+
- python-version: ${{ inputs.oldest_supported_python }}
37+
os: macos-13
38+
- python-version: ${{ inputs.oldest_supported_python }}
39+
os: macos-latest
40+
- python-version: ${{ inputs.oldest_supported_python }}
41+
os: linux-armv7-self-hosted
42+
- python-version: ${{ inputs.oldest_supported_python }}
43+
os: linux-arm64-self-hosted
44+
45+
# Use python container on ARM - dynamically constructed
46+
container: ${{ (matrix.os == 'linux-armv7-self-hosted' || matrix.os == 'linux-arm64-self-hosted') && format('python:{0}-bullseye', matrix.python-version) || null }}
6247

6348
steps:
6449
- name: Checkout repository
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
name: Get Supported Versions
2+
on:
3+
workflow_dispatch: # TODO: Remove
4+
workflow_call:
5+
outputs:
6+
supported_idf:
7+
description: "Array of supported IDF versions"
8+
value: ${{ jobs.triage.outputs.supported_idf }}
9+
supported_python:
10+
description: "Array of supported Python versions"
11+
value: ${{ jobs.triage.outputs.supported_python }}
12+
oldest_supported_python:
13+
description: "Oldest supported Python version"
14+
value: ${{ jobs.triage.outputs.oldest_supported_python }}
15+
16+
jobs:
17+
triage:
18+
name: Get Supported Versions
19+
runs-on: ubuntu-latest
20+
outputs:
21+
supported_idf: ${{ steps.set-arrays.outputs.supported_idf }}
22+
oldest_supported_idf: ${{ steps.set-arrays.outputs.oldest_supported_idf }}
23+
supported_python: ${{ steps.set-arrays.outputs.supported_python }}
24+
oldest_supported_python: ${{ steps.set-arrays.outputs.oldest_supported_python }}
25+
strategy:
26+
fail-fast: false
27+
steps:
28+
- name: Checkout repository
29+
uses: actions/checkout@v5
30+
31+
- name: Run script to generate supported_versions.json
32+
run: |
33+
python supported_versions.py ${{ secrets.IDF_VERSIONS_TXT }}
34+
35+
- name: Set outputs from supported_versions.json
36+
id: set-arrays
37+
run: |
38+
echo "supported_idf=$(jq -c .supported_idf < supported_versions.json)" >> $GITHUB_OUTPUT
39+
echo "oldest_supported_idf=$(jq -c .oldest_supported_idf < supported_versions.json)" >> $GITHUB_OUTPUT
40+
echo "supported_python=$(jq -c .supported_python < supported_versions.json)" >> $GITHUB_OUTPUT
41+
echo "oldest_supported_python=$(jq -r .oldest_supported_python < supported_versions.json)" >> $GITHUB_OUTPUT

supported_versions.py

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
2+
# SPDX-License-Identifier: Apache-2.0
3+
import json
4+
import sys
5+
from datetime import datetime
6+
7+
import requests
8+
from dateutil.relativedelta import relativedelta # TODO check and remove comment pip install python-dateutil
9+
10+
from _helper_functions import print_color
11+
12+
try:
13+
IDF_VERSIONS_TXT = str(sys.argv[1])
14+
except IndexError:
15+
raise SystemExit('Error: IDF versions txt not provided.')
16+
17+
json_data = {
18+
'supported_idf': [],
19+
'oldest_supported_idf': '',
20+
'supported_python': [],
21+
'oldest_supported_python': ''
22+
}
23+
24+
25+
def get_supported_idf_versions():
26+
"""
27+
Fetches the supported versions of the ESP-IDF from the official Espressif's server.
28+
Returns a list of version strings.
29+
"""
30+
url = IDF_VERSIONS_TXT
31+
try:
32+
response = requests.get(url)
33+
response.raise_for_status()
34+
supported_idf_versions = response.text.splitlines()
35+
except requests.RequestException as exc:
36+
raise SystemExit(f'Failed to fetch supported ESP-IDF versions.\nError: {exc}')
37+
38+
supported_idf_versions = [version for version in supported_idf_versions if version.startswith('v')]
39+
supported_idf_versions = [f"{version.split('.')[0]}.{version.split('.')[1]}" for version in supported_idf_versions]
40+
41+
return supported_idf_versions
42+
43+
44+
class IDFRelease():
45+
"""
46+
ESP-IDF Release with important properties
47+
EOL is based on the documentation's statement of 30 months support length:
48+
https://docs.espressif.com/projects/esp-idf/en/latest/esp32/versions.html
49+
"""
50+
def __init__(self, release_tag, published) -> None:
51+
self.release_tag = release_tag
52+
self.published = datetime.strptime(published, '%Y-%m-%dT%H:%M:%SZ').date()
53+
self.eol = self.published + relativedelta(months=+30)
54+
55+
56+
def get_github_idf_releases(supported_idf_versions):
57+
"""
58+
Fetches the releases of the ESP-IDF from the GitHub API.
59+
Returns a list of IDFRelease classes according to supported ESP-IDF versions.
60+
"""
61+
url = 'https://api.github.com/repos/espressif/esp-idf/releases?per_page=100'
62+
try:
63+
response = requests.get(url)
64+
response.raise_for_status()
65+
releases_raw = response.json()
66+
67+
except requests.RequestException as exc:
68+
raise SystemExit(f'Failed to fetch ESP-IDF releases from GitHub.\nError: {exc}')
69+
70+
releases = []
71+
for release in releases_raw:
72+
tag_name = release['tag_name']
73+
if tag_name in supported_idf_versions:
74+
releases.append(IDFRelease(tag_name, release['published_at']))
75+
76+
print_color('Supported ESP-IDF Versions:')
77+
for version in supported_idf_versions:
78+
for release in releases:
79+
if release.release_tag == version:
80+
print_color(f' - {version} ... Published: {release.published} ... EOL: {release.eol}')
81+
json_data['supported_idf'].append(version)
82+
return releases
83+
84+
85+
class PythonRelease():
86+
"""
87+
Python release with important properties
88+
"""
89+
def __init__(self, version, eol_date, is_eol) -> None:
90+
self.version = version
91+
self.eol_date = datetime.strptime(eol_date, '%Y-%m-%d').date()
92+
self.is_eol = is_eol
93+
94+
95+
def get_supported_python_versions():
96+
url = 'https://endoflife.date/api/v1/products/python/'
97+
try:
98+
response = requests.get(url)
99+
response.raise_for_status()
100+
releases_raw = response.json()
101+
releases_raw = releases_raw['result']['releases']
102+
103+
releases = []
104+
for release in releases_raw:
105+
releases.append(PythonRelease(release['name'], release['eolFrom'], release['isEol']))
106+
releases = [release for release in releases if release.eol_date > oldest_idf.published]
107+
return releases
108+
109+
except requests.RequestException as exc:
110+
raise SystemExit(f'Failed to fetch supported Python versions.\nError: {exc}')
111+
112+
113+
supported_idf_versions = get_supported_idf_versions()
114+
github_releases = get_github_idf_releases(supported_idf_versions)
115+
oldest_idf: IDFRelease = min(github_releases, key=lambda rel: rel.published)
116+
json_data['oldest_supported_idf'] = oldest_idf.release_tag
117+
print_color('Oldest supported ESP-IDF release:')
118+
print_color(f' - {oldest_idf.release_tag} (Published on: {oldest_idf.published} ... EOL: {oldest_idf.eol})')
119+
120+
python_releases = get_supported_python_versions()
121+
oldest_python_release = min(python_releases, key=lambda rel: rel.eol_date)
122+
json_data['oldest_supported_python'] = oldest_python_release.version
123+
124+
print_color('Supported Python Versions based on ESP-IDF:')
125+
for item in python_releases:
126+
print_color(f' - Python {item.version}\t... EOL: {item.eol_date} ... EOL Status: {item.is_eol}')
127+
json_data['supported_python'].append(item.version) # type: ignore
128+
129+
print_color('Oldest supported Python version:')
130+
print_color(f' - {oldest_python_release.version} (EOL: {oldest_python_release.eol_date})')
131+
132+
with open('supported_versions.json', 'w') as f:
133+
json.dump(json_data, f, indent=4)

0 commit comments

Comments
 (0)