blob: 2e1d9b878272e3bb9646adb8fee64318925789b7 [file]
############################################################################
# SPDX-License-Identifier: Apache-2.0
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership. The
# ASF licenses this file to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance with the
# License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
############################################################################
"""Build manager for NuttX configuration."""
import os
import subprocess
import sys
from pathlib import Path
from typing import Any, Dict, List, Optional
from ntfc.logger import logger
class NuttXBuilder:
"""NuttX configuration builder (CMake only)."""
IMAGE_BIN_STR = "$IMAGE_BIN"
IMAGE_HEX_STR = "$IMAGE_HEX"
def __init__(self, config: Dict[str, Any], rebuild: bool = True):
"""Initialize NuttX builder."""
if not isinstance(config, dict):
raise TypeError("invalid config file type")
self._cfg_values = config
self._rebuild = rebuild
def _run_command(
self, cmd: List[str], env: Any
) -> None: # pragma: no cover
"""Run command."""
subprocess.run(cmd, check=True, env=env)
def _make_dir(self, path: Path) -> None:
"""Create dir."""
os.makedirs(path, exist_ok=True)
def _run_cmake(
self,
source: str,
build: str,
generator: str = "Ninja",
defines: Optional[Dict[str, str]] = None,
env: Optional[Dict[str, str]] = None,
) -> None:
"""Run CMake configure step."""
build_path = Path(build)
self._make_dir(build_path)
# base command
cmd = [
"cmake",
f"-B{build}",
f"-S{source}",
f"-G{generator}",
]
# add -DVAR=value parameters
if defines: # pragma: no cover
for k, v in defines.items():
cmd.append(f"-D{k}={v}")
# merge environment variables
run_env = os.environ.copy()
if env:
run_env.update(env) # pragma: no cover
self._run_command(cmd, env=run_env)
def _run_build(
self, build: str, env: Optional[Dict[str, str]] = None
) -> None:
"""Run the CMake build step."""
build_path = Path(build)
cmd = [
"cmake",
"--build",
str(build_path),
]
run_env = os.environ.copy()
if env:
run_env.update(env) # pragma: no cover
self._run_command(cmd, env=run_env)
def _build_core(
self, core: str, cores: Dict[str, Any], product: str
) -> None:
"""Build single core image."""
if "defconfig" in cores[core]:
build_dir = (
product
+ "-"
+ self._cfg_values[product]["name"]
+ "-"
+ cores[core]["name"]
)
cfg_build_dir = self._cfg_values["config"].get("build_dir", None)
if not cfg_build_dir: # pragma: no cover
print("ERROR: not found build_dir in YAML configuration!")
sys.exit(1)
cfg_cwd = self._cfg_values["config"].get("cwd", None)
if not cfg_cwd: # pragma: no cover
print("ERROR: not found cwd in YAML configuration!")
sys.exit(1)
build_path = os.path.join(cfg_build_dir, build_dir)
build_cfg = cores[core]["defconfig"]
logger.info(
f"build image " f"conf: {build_cfg}, out: {build_path}"
)
nuttx_dir = os.path.join(cfg_cwd, "nuttx")
nuttx_elf_path = os.path.join(build_path, "nuttx")
nuttx_conf_path = os.path.join(build_path, ".config")
already_build = False
if os.path.isfile(nuttx_elf_path) and os.path.isfile(
nuttx_conf_path
):
already_build = True # pragma: no cover
# get defines passed to cmake
defines = {"BOARD_CONFIG": build_cfg}
custom_defines = cores[core].get("dcmake", [])
for d in custom_defines:
defines[d[0]] = d[1] # pragma: no cover
if not already_build or self._rebuild: # pragma: no cover
# configure build
self._run_cmake(
source=nuttx_dir,
build=build_path,
generator="Ninja",
defines=defines,
)
# build
self._run_build(build_path)
# add elf and conf path
cores[core]["elf_path"] = nuttx_elf_path
cores[core]["conf_path"] = nuttx_conf_path
def _reboot_core(
self, core: str, cores: Dict[str, Any]
) -> None: # pragma: no cover
"""Reboot single core."""
reboot_cmd = cores[core].get("reboot", None)
if reboot_cmd:
cmd = reboot_cmd.split()
logger.info(f"reboot core cmd: {cmd}")
self._run_command(cmd, env=None)
def _flash_core(
self, core: str, cores: Dict[str, Any]
) -> None: # pragma: no cover
"""Flash single core image."""
flash_cmd = cores[core].get("flash", None)
if flash_cmd:
img_path = Path(cores[core]["elf_path"])
image_hex = str(img_path.parent) + "/nuttx.hex"
image_bin = str(img_path.parent) + "/nuttx.bin"
flash_cmd = flash_cmd.replace(self.IMAGE_BIN_STR, image_bin)
flash_cmd = flash_cmd.replace(self.IMAGE_HEX_STR, image_hex)
cmd = flash_cmd.split()
logger.info(f"flash image cmd: {cmd}")
self._run_command(cmd, env=None)
def need_build(self) -> bool:
"""Check if we need build something."""
for product in self._cfg_values:
if "product" in product:
cores = self._cfg_values[product]["cores"]
for core in cores:
if cores[core].get("defconfig", None):
return True
return False
def build_all(self) -> None:
"""Build all defconfigs from configuration file."""
for product in self._cfg_values:
if "product" in product:
cores = self._cfg_values[product]["cores"]
for core in cores:
self._build_core(core, cores, product)
def flash_all(self) -> None:
"""Flash all available images."""
for product in self._cfg_values:
if "product" in product:
cores = self._cfg_values[product]["cores"]
for core in cores:
# flash core image
self._flash_core(core, cores)
# reboot after flash
self._reboot_core(core, cores)
def new_conf(self) -> Dict[str, Any]:
"""Get modified YAML config."""
return self._cfg_values