11#!env python3
22"""Action body."""
3+
34import json
45import os
56import re
7+ from pathlib import Path
8+ from typing import Any
69
710from actions_toolkit import core
811
2225IMPLICIT_SKIP_EXPLODE = "0"
2326
2427
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+
2541def add_job (result : dict [str , dict [str , str ]], name : str , data : dict [str , str ]) -> None :
2642 """Adds a new job to the list of generated jobs."""
2743 if name in result :
@@ -31,22 +47,54 @@ def add_job(result: dict[str, dict[str, str]], name: str, data: dict[str, str])
3147 result [name ] = data
3248
3349
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+
3478# loop list staring with given item
3579# pylint: disable=too-many-locals,too-many-branches
36- def main () -> None : # noqa: C901,PLR0912
80+ def main () -> None : # noqa: C901,PLR0912,PLR0915
3781 """Main."""
3882 # print all env vars starting with INPUT_
3983 for k , v in os .environ .items ():
4084 if k .startswith ("INPUT_" ):
4185 core .info (f"Env var { k } ={ v } " )
4286 try :
4387 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+
4592 min_python = core .get_input ("min_python" ) or IMPLICIT_MIN_PYTHON
4693 max_python = core .get_input ("max_python" ) or IMPLICIT_MAX_PYTHON
4794 default_python = core .get_input ("default_python" ) or IMPLICIT_DEFAULT_PYTHON
4895 skip_explode = int (core .get_input ("skip_explode" ) or IMPLICIT_SKIP_EXPLODE )
4996 strategies = {}
97+
5098 for platform in PLATFORM_MAP :
5199 strategies [platform ] = core .get_input (platform , required = False )
52100
@@ -60,7 +108,15 @@ def main() -> None: # noqa: C901,PLR0912
60108 KNOWN_PYTHONS .index (min_python ) : (KNOWN_PYTHONS .index (max_python ) + 1 )
61109 ]
62110 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+
64120 for line in other_names :
65121 name , _ = line .split (":" , 1 ) if ":" in line else (line , f"tox -e { line } " )
66122 commands = _ .split (";" )
@@ -70,7 +126,7 @@ def main() -> None: # noqa: C901,PLR0912
70126 if match :
71127 py_version = match .groups ()[0 ]
72128 env_python = f"{ py_version [0 ]} .{ py_version [1 :]} "
73- for platform_name in PLATFORM_MAP :
129+ for platform_name in platform_names_sorted :
74130 if platform_name in name :
75131 break
76132 else :
@@ -93,7 +149,7 @@ def main() -> None: # noqa: C901,PLR0912
93149 if not skip_explode :
94150 for platform in platforms :
95151 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 ("." ))
97153 suffix = "" if platform == IMPLICIT_PLATFORM else f"-{ platform } "
98154 if strategies [platform ] == "minmax" and (
99155 i not in (0 , python_flavours - 1 )
@@ -111,7 +167,7 @@ def main() -> None: # noqa: C901,PLR0912
111167 )
112168
113169 core .info (f"Generated { len (result )} matrix entries." )
114- names = sorted ( result .keys ())
170+ names = sort_human ( list ( result .keys () ))
115171 core .info (f"Job names: { ', ' .join (names )} " )
116172 matrix_include = []
117173 matrix_include = [
@@ -120,26 +176,13 @@ def main() -> None: # noqa: C901,PLR0912
120176 core .info (
121177 f"Matrix jobs ordered by their name: { json .dumps (matrix_include , indent = 2 )} " ,
122178 )
123-
124- core . set_output ( "matrix" , { "include" : matrix_include } )
179+ output = { "matrix" : { "include" : matrix_include }}
180+ produce_output ( output )
125181
126182 # pylint: disable=broad-exception-caught
127183 except Exception as exc : # noqa: BLE001
128184 core .set_failed (f"Action failed due to { exc } " )
129185
130186
131187if __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"
145188 main ()
0 commit comments