Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
287 changes: 125 additions & 162 deletions amphimixis/configurator.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
import pickle
from os import path
from platform import machine as local_arch
from typing import Any
from typing import Any, SupportsInt

import yaml

from amphimixis.build_systems import build_systems_dict, runners_dict
from amphimixis.general import IUI, DummyRunner, NullUI, general, tools
from amphimixis.general import IUI, DummyRunner, MachineInfo, NullUI, general, tools
from amphimixis.general.constants import ANALYZED_FILE_NAME
from amphimixis.laboratory_assistant import LaboratoryAssistant
from amphimixis.logger import setup_logger
Expand Down Expand Up @@ -54,46 +54,34 @@ def parse_config(
with open(config_file_path, "r", encoding="UTF-8") as file:
input_config = yaml.safe_load(file)

build_system: str | None = str(input_config.get("build_system")).lower()
if build_system not in build_systems_dict:
if not (build_system := _get_analyzed_build_system()):
_logger.error("Did not find any proper build_system")
ui.mark_failed("Config: no build system found")
return False
runner_name = str(input_config.get("runner", None)).lower()
if (
runner_name
and runner_name in runners_dict
and runners_dict[runner_name] in build_systems_dict[build_system][1]
build_system: str | None = str(input_config.get("build_system")).lower()
if build_system not in build_systems_dict:
if not (build_system := _get_analyzed_build_system()):
_logger.error("Did not find any proper build_system")
ui.mark_failed("Config: no build system found")
return False
runner_name = str(input_config.get("runner", None)).lower()
if (
runner_name
and runner_name in runners_dict
and runners_dict[runner_name] in build_systems_dict[build_system][1]
):
runner = runners_dict[runner_name](project, ui)
elif len(build_systems_dict[build_system][1]) > 0:
runner = build_systems_dict[build_system][1][0](project, ui)
else: # if build system doesn't have runners (like Make)
runner = DummyRunner()
project.build_system = build_systems_dict[build_system][0](project, runner, ui)

for build in input_config["builds"]:
if not _create_build(
project,
input_config,
build,
ui,
):
runner = runners_dict[runner_name](project, ui)
elif len(build_systems_dict[build_system][1]) > 0:
runner = build_systems_dict[build_system][1][0](project, ui)
else: # if build system doesn't have runners (like Make)
runner = DummyRunner()
project.build_system = build_systems_dict[build_system][0](project, runner, ui)

for build in input_config["builds"]:
(
executables,
build_machine_info,
run_machine_info,
recipe_info,
) = _configure_build(input_config, build)

if build_machine_info == {} or run_machine_info == {} or recipe_info == {}:
return False

if not _create_build(
project,
build_machine_info,
run_machine_info,
recipe_info,
executables,
ui,
):
ui.mark_failed("Failed to create build")
return False
ui.mark_failed("Failed to create build")
return False

config_path = tools.project_name(project) + ".project"
with open(config_path, "wb") as file:
Expand All @@ -103,77 +91,44 @@ def parse_config(
return True


def _configure_build(input_config: dict[str, Any], build: dict[str, Any]) -> tuple[
list[str],
str | dict[str, int | str],
str | dict[str, int | str],
dict[str, Any],
]:
"""Function to configure fields of a single build"""

executables = build.get("executables", [])

build_machine_info = build["build_machine"]
if str(build_machine_info).isdecimal():
build_machine_info = _get_by_id(
input_config["platforms"], build["build_machine"]
)

run_machine_info = build["run_machine"]
if str(run_machine_info).isdecimal():
run_machine_info = _get_by_id(input_config["platforms"], build["run_machine"])

recipe_info = _get_by_id(input_config["recipes"], build["recipe_id"])

return (
executables,
build_machine_info,
run_machine_info,
recipe_info,
)


def _create_build( # pylint: disable=R0913,R0914,R0917
project: general.Project,
build_machine_info: str | dict[str, int | str],
run_machine_info: str | dict[str, int | str],
recipe_info: dict[str, Any],
executables: list[str],
input_config: dict[str, Any],
build_dict: dict[str, Any],
ui: IUI = NullUI(),
) -> bool:
"""Function to create a new build and save its configuration to a Pickle file"""

id_name_build_machine = (
build_machine_info
if isinstance(build_machine_info, str)
else build_machine_info["id"]
)
id_name_run_machine = (
run_machine_info
if isinstance(run_machine_info, str)
else run_machine_info["id"]
)
build_machine_id = str(build_dict["build_machine"])
run_machine_id = str(build_dict["run_machine"])
recipe_id = str(build_dict["recipe_id"])
executables = build_dict.get("executables", [])

build_name = _generate_build_name(
str(id_name_build_machine), str(id_name_run_machine), recipe_info["id"]
build_dict["build_machine"], build_dict["run_machine"], build_dict["recipe_id"]
)

if isinstance(build_machine_info, str):
if (
build_machine := LaboratoryAssistant.find_platform(build_machine_info)
) is None:
msg = f"Build '{build_name}': unknown build machine: '{build_machine_info}'"
_logger.fatal(msg)
raise ValueError(msg)
build_machine: MachineInfo
if dict_from_input := _get_by_id(input_config["platforms"], build_machine_id):
build_machine = create_machine(dict_from_input)
elif machine_from_la := LaboratoryAssistant.find_platform(build_machine_id):
build_machine = machine_from_la
else:
build_machine = create_machine(build_machine_info)

if isinstance(run_machine_info, str):
if (run_machine := LaboratoryAssistant.find_platform(run_machine_info)) is None:
msg = f"Build '{build_name}': unknown run machine: '{run_machine_info}'"
_logger.fatal(msg)
raise ValueError(msg)
msg = f"Build '{build_name}': unknown build machine: '{build_machine_id}'"
_logger.fatal(msg)
raise ValueError(msg)

run_machine: MachineInfo
if dict_from_input := _get_by_id(input_config["platforms"], run_machine_id):
run_machine = create_machine(dict_from_input)
elif machine_from_la := LaboratoryAssistant.find_platform(run_machine_id):
run_machine = machine_from_la
else:
run_machine = create_machine(run_machine_info)
msg = f"Build '{build_name}': unknown run machine: '{run_machine_id}'"
_logger.fatal(msg)
raise ValueError(msg)

recipe_info = _get_by_id(input_config["recipes"], recipe_id)

if not _has_valid_arch(project, run_machine, ui):
return False
Expand All @@ -186,20 +141,24 @@ def _create_build( # pylint: disable=R0913,R0914,R0917
if sysroot is None and toolchain is not None:
sysroot = toolchain.sysroot

config_flags = recipe_info.get("config_flags", "")
compiler_flags = create_flags(recipe_info.get("compiler_flags"))
jobs = recipe_info.get("jobs", None)
config_flags = recipe_info.get("config_flags")
compiler_flags = create_flags(recipe_info.get("compiler_flags")) # type: ignore
jobs = recipe_info.get("jobs")
if not isinstance(jobs, SupportsInt | None):
msg = f"Build '{build_name}': invalid jobs: '{jobs}'"
_logger.fatal(msg)
raise ValueError(msg)

build = general.Build(
build_machine,
run_machine,
build_name,
executables,
toolchain,
sysroot,
str(sysroot) if sysroot else None,
compiler_flags,
config_flags,
jobs,
str(config_flags) if config_flags else None,
int(jobs) if jobs else None,
)

project.builds.append(build)
Expand All @@ -213,15 +172,13 @@ def _generate_build_name(build_id: str, run_id: str, recipe_id: str) -> str:
return f"{build_id}_{run_id}_{recipe_id}"


def _get_by_id(items: list[dict[str, str]], target_id: int) -> dict[str, str]:
def _get_by_id(
items: list[dict[str, str | int]], target_id: str
) -> dict[str, str | int]:
"""Function to find item in dict by id"""

for item in items:
id_ = item["id"]
if not isinstance(id_, int):
msg = f"Error: not integer id '{id_}' of item: {item}"
_logger.fatal(msg)
raise ValueError(msg)
id_ = str(item["id"])
if id_ == target_id:
return item

Expand All @@ -242,27 +199,28 @@ def _has_valid_arch(
local_arch().lower(),
)
return False
return True

# remote case
ui.update_message("Config", "Checking remote architecture for validity...")
shell = Shell(project, machine, ui).connect()
error_code, stdout, _ = shell.run("uname -m")
if error_code != 0:
_logger.error(
"An error occured during reading remote machine arch, check remote machine"
)
ui.mark_failed("Config: failed to check remote architecture")
return False

else:
ui.update_message("Config", "Checking remote architecture for validity...")
shell = Shell(project, machine, ui).connect()
error_code, stdout, _ = shell.run("uname -m")
if error_code != 0:
_logger.error(
"An error occured during reading remote machine arch, check remote machine"
)
ui.mark_failed("Config: failed to check remote architecture")
return False

remote_arch = stdout[0][0]
if machine.arch.lower() not in remote_arch.lower():
_logger.error(
"Invalid remote machine arch: %s, remote machine is %s",
machine.arch.name.lower(),
remote_arch.lower(),
)
ui.mark_failed("Config: invalid remote architecture")
return False
remote_arch = stdout[0][0]
if machine.arch.lower() not in remote_arch.lower():
_logger.error(
"Invalid remote machine arch: %s, remote machine is %s",
machine.arch.name.lower(),
remote_arch.lower(),
)
ui.mark_failed("Config: invalid remote architecture")
return False

return True

Expand All @@ -283,27 +241,30 @@ def _get_analyzed_build_system() -> str | None:

with open(ANALYZED_FILE_NAME, "r", encoding="UTF-8") as file:
analyzed = yaml.safe_load(file)
if analyzed:
if not isinstance(analyzed, dict):
raise TypeError("Incorrect analyzer output file")

if "build_systems" not in analyzed:
raise TypeError("Missing 'build_systems' key in analyzer output file")
if not analyzed:
return None

build_systems = analyzed["build_systems"]
if not isinstance(build_systems, list) or not build_systems:
raise TypeError("'Build_systems' must be a non-empty list")
if not isinstance(analyzed, dict):
raise TypeError("Incorrect analyzer output file")

if not isinstance(build_systems[0], str):
raise TypeError("Incorrect build systems list")
if "build_systems" not in analyzed:
raise TypeError("Missing 'build_systems' key in analyzer output file")

build_system = build_systems[0].lower()
if (
build_system in build_systems_dict
): # take first (in priority) found build system
return build_system
build_systems = analyzed["build_systems"]
if not isinstance(build_systems, list) or not build_systems:
raise TypeError("'Build_systems' must be a non-empty list")

return None
if not isinstance(build_systems[0], str):
raise TypeError("Incorrect build systems list")

build_system = build_systems[0].lower()
if (
build_system in build_systems_dict
): # take first (in priority) found build system
return build_system

return None


def create_machine(machine_info: dict[str, int | str]) -> general.MachineInfo:
Expand All @@ -328,25 +289,27 @@ def create_machine(machine_info: dict[str, int | str]) -> general.MachineInfo:


def create_toolchain(
toolchain_dict: dict[str, str] | str,
toolchain_dict: dict[str, str | int] | str | int,
) -> general.Toolchain | None:
"""Function to create a new toolchain"""

if isinstance(toolchain_dict, str):
return LaboratoryAssistant.find_toolchain_by_name(toolchain_dict)
if isinstance(toolchain_dict, str | int):
return LaboratoryAssistant.find_toolchain_by_name(str(toolchain_dict))

toolchain = general.Toolchain()
for attr in toolchain_dict:
if attr.lower() in general.ToolchainAttrs:
toolchain.set(general.ToolchainAttrs(attr.lower()), toolchain_dict[attr])
toolchain.set(
general.ToolchainAttrs(attr.lower()), str(toolchain_dict[attr])
)

elif attr.lower() in general.CompilerFlagsAttrs:
toolchain.set(
general.CompilerFlagsAttrs(attr.lower()), toolchain_dict[attr]
general.CompilerFlagsAttrs(attr.lower()), str(toolchain_dict[attr])
)

elif attr.lower() == "sysroot":
toolchain.sysroot = toolchain_dict[attr]
toolchain.sysroot = str(toolchain_dict[attr])

else:
_logger.info("Unknown toolchain attribute: %s, skipping...", attr.lower())
Expand All @@ -360,17 +323,17 @@ def create_flags(
"""Function to create new flags"""

if compiler_flags_dict is None:
return compiler_flags_dict
return None

compiler_flags = general.CompilerFlags()
for flag in compiler_flags_dict:
if flag.lower() in general.CompilerFlagsAttrs:
compiler_flags.set(
general.CompilerFlagsAttrs(flag.lower()), compiler_flags_dict[flag]
)
else:
for key, value in compiler_flags_dict.items():
flag = key.lower()
if flag not in general.CompilerFlagsAttrs:
_logger.info(
"Unknown compiler flag attribute: %s, skipping...", flag.lower()
)
continue

compiler_flags.set(general.CompilerFlagsAttrs(flag), value)

return compiler_flags
Loading