223 lines
7.4 KiB
Python
223 lines
7.4 KiB
Python
import json
|
|
import os
|
|
import shutil
|
|
import subprocess
|
|
import warnings
|
|
from pathlib import Path
|
|
|
|
from ci.boards import Board # type: ignore
|
|
from ci.locked_print import locked_print
|
|
|
|
|
|
def _install_global_package(package: str) -> None:
|
|
# example pio pkg -g -p "https://github.com/maxgerhardt/platform-raspberrypi.git".
|
|
locked_print(f"*** Installing {package} ***")
|
|
cmd_list = [
|
|
"pio",
|
|
"pkg",
|
|
"install",
|
|
"-g",
|
|
"-p",
|
|
package,
|
|
]
|
|
cmd_str = subprocess.list2cmdline(cmd_list)
|
|
locked_print(f"Running command:\n\n{cmd_str}\n\n")
|
|
result = subprocess.run(
|
|
cmd_str,
|
|
shell=True,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.STDOUT,
|
|
text=True,
|
|
check=True,
|
|
)
|
|
locked_print(result.stdout)
|
|
locked_print(f"*** Finished installing {package} ***")
|
|
|
|
|
|
def insert_tool_aliases(meta_json: dict[str, dict]) -> None:
|
|
for board in meta_json.keys():
|
|
aliases: dict[str, str | None] = {}
|
|
cc_path = meta_json[board].get("cc_path")
|
|
cc_path = Path(cc_path) if cc_path else None
|
|
if cc_path:
|
|
# get the prefix of the base name of the compiler.
|
|
cc_base = cc_path.name
|
|
parent = cc_path.parent
|
|
prefix = cc_base.split("gcc")[0]
|
|
suffix = cc_path.suffix
|
|
# create the aliases
|
|
for tool in [
|
|
"gcc",
|
|
"g++",
|
|
"ar",
|
|
"objcopy",
|
|
"objdump",
|
|
"size",
|
|
"nm",
|
|
"ld",
|
|
"as",
|
|
"ranlib",
|
|
"strip",
|
|
"c++filt",
|
|
"readelf",
|
|
"addr2line",
|
|
]:
|
|
name = f"{prefix}{tool}" + suffix
|
|
tool_path = Path(str(parent / name))
|
|
if tool_path.exists():
|
|
aliases[tool] = str(tool_path)
|
|
else:
|
|
aliases[tool] = None
|
|
meta_json[board]["aliases"] = aliases
|
|
|
|
|
|
def remove_readonly(func, path, _):
|
|
"Clear the readonly bit and reattempt the removal"
|
|
if os.name == "nt":
|
|
os.system(f"attrib -r {path}")
|
|
else:
|
|
try:
|
|
os.chmod(path, 0o777)
|
|
except Exception:
|
|
print(f"Error removing readonly attribute from {path}")
|
|
|
|
func(path)
|
|
|
|
|
|
def create_build_dir(
|
|
board: Board,
|
|
defines: list[str],
|
|
no_install_deps: bool,
|
|
extra_packages: list[str],
|
|
build_dir: str | None,
|
|
board_dir: str | None,
|
|
build_flags: list[str] | None,
|
|
extra_scripts: str | None,
|
|
) -> tuple[bool, str]:
|
|
"""Create the build directory for the given board."""
|
|
# filter out "web" board because it's not a real board.
|
|
if board.board_name == "web":
|
|
locked_print(f"Skipping web target for board {board.board_name}")
|
|
return True, ""
|
|
if board.defines:
|
|
defines.extend(board.defines)
|
|
# remove duplicates
|
|
defines = list(set(defines))
|
|
board_name = board.board_name
|
|
real_board_name = board.get_real_board_name()
|
|
locked_print(f"*** Initializing environment for {board_name} ***")
|
|
# builddir = Path(build_dir) / board if build_dir else Path(".build") / board
|
|
build_dir = build_dir or ".build"
|
|
builddir = Path(build_dir) / board_name
|
|
builddir.mkdir(parents=True, exist_ok=True)
|
|
# if lib directory (where FastLED lives) exists, remove it. This is necessary to run on
|
|
# recycled build directories for fastled to update. This is a fast operation.
|
|
srcdir = builddir / "lib"
|
|
if srcdir.exists():
|
|
shutil.rmtree(srcdir, onerror=remove_readonly)
|
|
platformio_ini = builddir / "platformio.ini"
|
|
if platformio_ini.exists():
|
|
try:
|
|
platformio_ini.unlink()
|
|
except OSError as e:
|
|
locked_print(f"Error removing {platformio_ini}: {e}")
|
|
if board_dir:
|
|
dst_dir = builddir / "boards"
|
|
if dst_dir.exists():
|
|
shutil.rmtree(dst_dir)
|
|
shutil.copytree(str(board_dir), str(builddir / "boards"))
|
|
if board.platform_needs_install:
|
|
if board.platform:
|
|
try:
|
|
_install_global_package(board.platform)
|
|
except subprocess.CalledProcessError as e:
|
|
stdout = e.stdout
|
|
return False, stdout
|
|
else:
|
|
warnings.warn("Platform install was specified but no platform was given.")
|
|
|
|
cmd_list = [
|
|
"pio",
|
|
"project",
|
|
"init",
|
|
"--project-dir",
|
|
builddir.as_posix(),
|
|
"--board",
|
|
real_board_name,
|
|
]
|
|
if board.platform:
|
|
cmd_list.append(f"--project-option=platform={board.platform}")
|
|
if board.platform_packages:
|
|
cmd_list.append(f"--project-option=platform_packages={board.platform_packages}")
|
|
if board.framework:
|
|
cmd_list.append(f"--project-option=framework={board.framework}")
|
|
if board.board_build_core:
|
|
cmd_list.append(f"--project-option=board_build.core={board.board_build_core}")
|
|
if board.board_build_filesystem_size:
|
|
cmd_list.append(
|
|
f"--project-option=board_build.filesystem_size={board.board_build_filesystem_size}"
|
|
)
|
|
if build_flags is not None:
|
|
for build_flag in build_flags:
|
|
cmd_list.append(f"--project-option=build_flags={build_flag}")
|
|
if defines:
|
|
build_flags_str = " ".join(f"-D{define}" for define in defines)
|
|
cmd_list.append(f"--project-option=build_flags={build_flags_str}")
|
|
if extra_packages:
|
|
cmd_list.append(f'--project-option=lib_deps={",".join(extra_packages)}')
|
|
if no_install_deps:
|
|
cmd_list.append("--no-install-dependencies")
|
|
if extra_scripts:
|
|
p = Path(extra_scripts)
|
|
cmd_list.append(f"--project-option=extra_scripts={p.resolve()}")
|
|
cmd_str = subprocess.list2cmdline(cmd_list)
|
|
locked_print(f"\n\nRunning command:\n {cmd_str}\n")
|
|
result = subprocess.run(
|
|
cmd_str,
|
|
shell=True,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.STDOUT,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
stdout = result.stdout
|
|
locked_print(result.stdout)
|
|
if result.returncode != 0:
|
|
locked_print(f"*** Error setting up board {board_name} ***")
|
|
return False, stdout
|
|
locked_print(f"*** Finished initializing environment for board {board_name} ***")
|
|
# dumping enviorment variables to help debug.
|
|
# this is the command: pio run --target envdump
|
|
cwd = str(builddir.resolve())
|
|
cmd_list = [
|
|
"pio",
|
|
"project",
|
|
"metadata",
|
|
"--json-output",
|
|
]
|
|
cmd_str = subprocess.list2cmdline(cmd_list)
|
|
stdout = subprocess.run(
|
|
cmd_list,
|
|
cwd=cwd,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.STDOUT,
|
|
text=True,
|
|
check=False,
|
|
).stdout
|
|
|
|
try:
|
|
data = json.loads(stdout)
|
|
# now dump the values to the file at the root of the build directory.
|
|
matadata_json = builddir / "build_info.json"
|
|
try:
|
|
insert_tool_aliases(data)
|
|
formatted = json.dumps(data, indent=4, sort_keys=True)
|
|
with open(matadata_json, "w") as f:
|
|
f.write(formatted)
|
|
except Exception:
|
|
with open(matadata_json, "w") as f:
|
|
f.write(stdout)
|
|
except json.JSONDecodeError:
|
|
msg = f"build_info.json will not be generated because of error because stdout does not look like a json file:\n#### STDOUT ####\n{stdout}\n#### END STDOUT ####\n"
|
|
locked_print(msg)
|
|
return True, stdout
|