1
1
#!env python3
2
2
"""Action body."""
3
+
3
4
import json
4
5
import os
5
6
import re
7
+ from pathlib import Path
8
+ from typing import Any
6
9
7
10
from actions_toolkit import core
8
11
22
25
IMPLICIT_SKIP_EXPLODE = "0"
23
26
24
27
28
+ def sort_human (data : list [str ]) -> list [str ]:
29
+ """Sort a list using human logic, so 'py39' comes before 'py311'."""
30
+
31
+ def convert (text : str ) -> str | float :
32
+ return float (text ) if text .isdigit () else text
33
+
34
+ def alphanumeric (key : str ) -> list [str | float ]:
35
+ return [convert (c ) for c in re .split (r"([-+]?\d*\\.?\d*)" , key )]
36
+
37
+ data .sort (key = alphanumeric )
38
+ return data
39
+
40
+
25
41
def add_job (result : dict [str , dict [str , str ]], name : str , data : dict [str , str ]) -> None :
26
42
"""Adds a new job to the list of generated jobs."""
27
43
if name in result :
@@ -31,22 +47,54 @@ def add_job(result: dict[str, dict[str, str]], name: str, data: dict[str, str])
31
47
result [name ] = data
32
48
33
49
50
+ def get_platforms () -> list [str ]:
51
+ """Retrieve effective list of platforms."""
52
+ platforms = []
53
+ for v in core .get_input ("platforms" , required = False ).split ("," ):
54
+ platform , run_on = v .split (":" ) if ":" in v else (v , None )
55
+ if not platform :
56
+ continue
57
+ if run_on :
58
+ core .debug (
59
+ f"Add platform '{ platform } ' with run_on={ run_on } to known platforms" ,
60
+ )
61
+ PLATFORM_MAP [platform ] = run_on
62
+ platforms .append (platform )
63
+ return platforms
64
+
65
+
66
+ def produce_output (output : dict [str , Any ]) -> None :
67
+ """Produce the output."""
68
+ if "TEST_GITHUB_OUTPUT_JSON" in os .environ :
69
+ with Path (os .environ ["TEST_GITHUB_OUTPUT_JSON" ]).open (
70
+ "w" ,
71
+ encoding = "utf-8" ,
72
+ ) as f :
73
+ json .dump (output , f )
74
+ for key , value in output .items ():
75
+ core .set_output (key , value )
76
+
77
+
34
78
# loop list staring with given item
35
79
# pylint: disable=too-many-locals,too-many-branches
36
- def main () -> None : # noqa: C901,PLR0912
80
+ def main () -> None : # noqa: C901,PLR0912,PLR0915
37
81
"""Main."""
38
82
# print all env vars starting with INPUT_
39
83
for k , v in os .environ .items ():
40
84
if k .startswith ("INPUT_" ):
41
85
core .info (f"Env var { k } ={ v } " )
42
86
try :
43
87
other_names = core .get_input ("other_names" , required = False ).split ("\n " )
44
- platforms = core .get_input ("platforms" , required = False ).split ("," )
88
+ platforms = get_platforms ()
89
+ core .info (f"Effective platforms: { platforms } " )
90
+ core .info (f"Platform map: { PLATFORM_MAP } " )
91
+
45
92
min_python = core .get_input ("min_python" ) or IMPLICIT_MIN_PYTHON
46
93
max_python = core .get_input ("max_python" ) or IMPLICIT_MAX_PYTHON
47
94
default_python = core .get_input ("default_python" ) or IMPLICIT_DEFAULT_PYTHON
48
95
skip_explode = int (core .get_input ("skip_explode" ) or IMPLICIT_SKIP_EXPLODE )
49
96
strategies = {}
97
+
50
98
for platform in PLATFORM_MAP :
51
99
strategies [platform ] = core .get_input (platform , required = False )
52
100
@@ -60,7 +108,15 @@ def main() -> None: # noqa: C901,PLR0912
60
108
KNOWN_PYTHONS .index (min_python ) : (KNOWN_PYTHONS .index (max_python ) + 1 )
61
109
]
62
110
python_flavours = len (python_names )
63
- core .debug ("..." )
111
+
112
+ def sort_key (s : str ) -> tuple [int , str ]:
113
+ """Sorts longer strings first."""
114
+ return - len (s ), s
115
+
116
+ # we put longer names first in order to pick the most specific platforms
117
+ platform_names_sorted = sorted (PLATFORM_MAP .keys (), key = sort_key )
118
+ core .info (f"Known platforms sorted: { platform_names_sorted } " )
119
+
64
120
for line in other_names :
65
121
name , _ = line .split (":" , 1 ) if ":" in line else (line , f"tox -e { line } " )
66
122
commands = _ .split (";" )
@@ -70,7 +126,7 @@ def main() -> None: # noqa: C901,PLR0912
70
126
if match :
71
127
py_version = match .groups ()[0 ]
72
128
env_python = f"{ py_version [0 ]} .{ py_version [1 :]} "
73
- for platform_name in PLATFORM_MAP :
129
+ for platform_name in platform_names_sorted :
74
130
if platform_name in name :
75
131
break
76
132
else :
@@ -93,7 +149,7 @@ def main() -> None: # noqa: C901,PLR0912
93
149
if not skip_explode :
94
150
for platform in platforms :
95
151
for i , python in enumerate (python_names ):
96
- py_name = re .sub (r"[^0-9] " , "" , python .strip ("." ))
152
+ py_name = re .sub (r"\D " , "" , python .strip ("." ))
97
153
suffix = "" if platform == IMPLICIT_PLATFORM else f"-{ platform } "
98
154
if strategies [platform ] == "minmax" and (
99
155
i not in (0 , python_flavours - 1 )
@@ -111,7 +167,7 @@ def main() -> None: # noqa: C901,PLR0912
111
167
)
112
168
113
169
core .info (f"Generated { len (result )} matrix entries." )
114
- names = sorted ( result .keys ())
170
+ names = sort_human ( list ( result .keys () ))
115
171
core .info (f"Job names: { ', ' .join (names )} " )
116
172
matrix_include = []
117
173
matrix_include = [
@@ -120,26 +176,13 @@ def main() -> None: # noqa: C901,PLR0912
120
176
core .info (
121
177
f"Matrix jobs ordered by their name: { json .dumps (matrix_include , indent = 2 )} " ,
122
178
)
123
-
124
- core . set_output ( "matrix" , { "include" : matrix_include } )
179
+ output = { "matrix" : { "include" : matrix_include }}
180
+ produce_output ( output )
125
181
126
182
# pylint: disable=broad-exception-caught
127
183
except Exception as exc : # noqa: BLE001
128
184
core .set_failed (f"Action failed due to { exc } " )
129
185
130
186
131
187
if __name__ == "__main__" :
132
- # only used for local testing, emulating use from github actions
133
- if os .getenv ("GITHUB_ACTIONS" ) is None :
134
- os .environ ["INPUT_DEFAULT_PYTHON" ] = "3.10"
135
- os .environ ["INPUT_LINUX" ] = "full"
136
- os .environ ["INPUT_MACOS" ] = "minmax"
137
- os .environ ["INPUT_MAX_PYTHON" ] = "3.13"
138
- os .environ ["INPUT_MIN_PYTHON" ] = "3.8"
139
- os .environ ["INPUT_OTHER_NAMES" ] = (
140
- "lint\n pkg\n py313-devel\n all-macos:tox -e unit;tox -e integration"
141
- )
142
- os .environ ["INPUT_PLATFORMS" ] = "linux,macos" # macos and windows
143
- os .environ ["INPUT_SKIP_EXPLODE" ] = "0"
144
- os .environ ["INPUT_WINDOWS" ] = "minmax"
145
188
main ()
0 commit comments