2024-10-01 19:19:35 +00:00
|
|
|
import copy
|
|
|
|
import json
|
|
|
|
from dataclasses import dataclass, field
|
|
|
|
from typing import Any, List, Optional
|
|
|
|
|
|
|
|
|
|
|
|
class Job:
|
|
|
|
@dataclass
|
|
|
|
class Requirements:
|
|
|
|
python: bool = False
|
|
|
|
python_requirements_txt: str = ""
|
|
|
|
|
|
|
|
@dataclass
|
|
|
|
class CacheDigestConfig:
|
|
|
|
include_paths: List[str] = field(default_factory=list)
|
|
|
|
exclude_paths: List[str] = field(default_factory=list)
|
|
|
|
|
|
|
|
@dataclass
|
|
|
|
class Config:
|
|
|
|
# Job Name
|
|
|
|
name: str
|
|
|
|
|
|
|
|
# Machine's label to run job on. For instance [ubuntu-latest] for free gh runner
|
|
|
|
runs_on: List[str]
|
|
|
|
|
|
|
|
# Job Run Command
|
|
|
|
command: str
|
|
|
|
|
|
|
|
# What job requires
|
|
|
|
# May be phony or physical names
|
|
|
|
requires: List[str] = field(default_factory=list)
|
|
|
|
|
|
|
|
# What job provides
|
|
|
|
# May be phony or physical names
|
|
|
|
provides: List[str] = field(default_factory=list)
|
|
|
|
|
|
|
|
job_requirements: Optional["Job.Requirements"] = None
|
|
|
|
|
|
|
|
timeout: int = 1 * 3600
|
|
|
|
|
|
|
|
digest_config: Optional["Job.CacheDigestConfig"] = None
|
|
|
|
|
|
|
|
run_in_docker: str = ""
|
|
|
|
|
|
|
|
run_unless_cancelled: bool = False
|
|
|
|
|
|
|
|
allow_merge_on_failure: bool = False
|
|
|
|
|
|
|
|
parameter: Any = None
|
|
|
|
|
|
|
|
def parametrize(
|
|
|
|
self,
|
|
|
|
parameter: Optional[List[Any]] = None,
|
|
|
|
runs_on: Optional[List[List[str]]] = None,
|
2024-11-15 12:49:28 +00:00
|
|
|
provides: Optional[List[List[str]]] = None,
|
|
|
|
requires: Optional[List[List[str]]] = None,
|
2024-10-01 19:19:35 +00:00
|
|
|
timeout: Optional[List[int]] = None,
|
|
|
|
):
|
|
|
|
assert (
|
|
|
|
parameter or runs_on
|
|
|
|
), "Either :parameter or :runs_on must be non empty list for parametrisation"
|
2024-11-15 12:49:28 +00:00
|
|
|
if runs_on:
|
|
|
|
assert isinstance(runs_on, list) and isinstance(runs_on[0], list)
|
2024-10-01 19:19:35 +00:00
|
|
|
if not parameter:
|
|
|
|
parameter = [None] * len(runs_on)
|
|
|
|
if not runs_on:
|
|
|
|
runs_on = [None] * len(parameter)
|
|
|
|
if not timeout:
|
|
|
|
timeout = [None] * len(parameter)
|
2024-11-15 12:49:28 +00:00
|
|
|
if not provides:
|
|
|
|
provides = [None] * len(parameter)
|
|
|
|
if not requires:
|
|
|
|
requires = [None] * len(parameter)
|
2024-10-01 19:19:35 +00:00
|
|
|
assert (
|
2024-11-15 12:49:28 +00:00
|
|
|
len(parameter)
|
|
|
|
== len(runs_on)
|
|
|
|
== len(timeout)
|
|
|
|
== len(provides)
|
|
|
|
== len(requires)
|
|
|
|
), f"Parametrization lists must be of the same size [{len(parameter)}, {len(runs_on)}, {len(timeout)}, {len(provides)}, {len(requires)}]"
|
2024-10-01 19:19:35 +00:00
|
|
|
|
|
|
|
res = []
|
2024-11-15 12:49:28 +00:00
|
|
|
for parameter_, runs_on_, timeout_, provides_, requires_ in zip(
|
|
|
|
parameter, runs_on, timeout, provides, requires
|
|
|
|
):
|
2024-10-01 19:19:35 +00:00
|
|
|
obj = copy.deepcopy(self)
|
2024-11-15 12:49:28 +00:00
|
|
|
assert (
|
|
|
|
not obj.provides
|
|
|
|
), "Job.Config.provides must be empty for parametrized jobs"
|
2024-10-01 19:19:35 +00:00
|
|
|
if parameter_:
|
|
|
|
obj.parameter = parameter_
|
2024-11-15 12:49:28 +00:00
|
|
|
obj.command = obj.command.format(PARAMETER=parameter_)
|
2024-10-01 19:19:35 +00:00
|
|
|
if runs_on_:
|
|
|
|
obj.runs_on = runs_on_
|
|
|
|
if timeout_:
|
|
|
|
obj.timeout = timeout_
|
2024-11-15 12:49:28 +00:00
|
|
|
if provides_:
|
|
|
|
assert (
|
|
|
|
not obj.provides
|
|
|
|
), "Job.Config.provides must be empty for parametrized jobs"
|
|
|
|
obj.provides = provides_
|
|
|
|
if requires_:
|
|
|
|
assert (
|
|
|
|
not obj.requires
|
|
|
|
), "Job.Config.requires and parametrize(requires=...) are both set"
|
|
|
|
obj.requires = requires_
|
2024-10-01 19:19:35 +00:00
|
|
|
obj.name = obj.get_job_name_with_parameter()
|
|
|
|
res.append(obj)
|
|
|
|
return res
|
|
|
|
|
|
|
|
def get_job_name_with_parameter(self):
|
|
|
|
name, parameter, runs_on = self.name, self.parameter, self.runs_on
|
|
|
|
res = name
|
|
|
|
name_params = []
|
2024-11-15 12:49:28 +00:00
|
|
|
if parameter:
|
|
|
|
if isinstance(parameter, list) or isinstance(parameter, dict):
|
|
|
|
name_params.append(json.dumps(parameter))
|
|
|
|
else:
|
|
|
|
name_params.append(parameter)
|
|
|
|
elif runs_on:
|
2024-10-01 19:19:35 +00:00
|
|
|
assert isinstance(runs_on, list)
|
|
|
|
name_params.append(json.dumps(runs_on))
|
2024-11-15 12:49:28 +00:00
|
|
|
else:
|
|
|
|
assert False
|
2024-10-01 19:19:35 +00:00
|
|
|
if name_params:
|
|
|
|
name_params = [str(param) for param in name_params]
|
|
|
|
res += f" ({', '.join(name_params)})"
|
|
|
|
|
|
|
|
self.name = res
|
|
|
|
return res
|
|
|
|
|
|
|
|
def __repr__(self):
|
|
|
|
return self.name
|