2022-08-29 17:57:17 +02:00
|
|
|
#!/usr/bin/env python3
|
2020-09-11 19:33:56 -04:00
|
|
|
import argparse
|
2022-03-03 11:42:05 -05:00
|
|
|
import subprocess
|
2020-09-11 19:33:56 -04:00
|
|
|
import shutil
|
2022-04-20 20:06:11 -04:00
|
|
|
import glob
|
2022-03-31 21:04:25 -04:00
|
|
|
import math
|
2020-09-11 19:33:56 -04:00
|
|
|
import sys
|
|
|
|
|
import re
|
|
|
|
|
import os
|
2020-08-25 21:02:02 -04:00
|
|
|
|
2024-08-02 16:14:37 -04:00
|
|
|
IDF_PATH = os.getenv("IDF_PATH")
|
|
|
|
|
if not IDF_PATH:
|
2024-07-29 19:34:05 -04:00
|
|
|
exit("IDF_PATH is not defined. Are you running inside esp-idf environment?")
|
|
|
|
|
|
2022-08-24 16:29:11 -04:00
|
|
|
TARGETS = ["odroid-go"] # We just need to specify the default, the others are discovered below
|
2022-11-03 16:25:19 -04:00
|
|
|
for t in glob.glob("components/retro-go/targets/*/config.h"):
|
|
|
|
|
TARGETS.append(os.path.basename(os.path.dirname(t)))
|
2022-04-20 20:06:11 -04:00
|
|
|
|
|
|
|
|
DEFAULT_TARGET = os.getenv("RG_TOOL_TARGET", TARGETS[0])
|
2021-09-24 16:50:27 -04:00
|
|
|
DEFAULT_BAUD = os.getenv("RG_TOOL_BAUD", "1152000")
|
|
|
|
|
DEFAULT_PORT = os.getenv("RG_TOOL_PORT", "COM3")
|
2022-03-16 18:16:54 -04:00
|
|
|
PROJECT_NAME = os.getenv("PROJECT_NAME", "Retro-Go") # os.path.basename(os.getcwd()).title()
|
2022-03-14 00:19:22 -04:00
|
|
|
PROJECT_ICON = os.getenv("PROJECT_ICON", "icon.raw")
|
2022-03-31 21:04:25 -04:00
|
|
|
PROJECT_APPS = {}
|
2022-01-31 07:53:00 -05:00
|
|
|
try:
|
2022-03-14 00:19:22 -04:00
|
|
|
PROJECT_VER = os.getenv("PROJECT_VER") or subprocess.check_output(
|
|
|
|
|
"git describe --tags --abbrev=5 --dirty --always", shell=True
|
|
|
|
|
).decode().rstrip()
|
2022-01-31 07:53:00 -05:00
|
|
|
except:
|
2022-03-14 00:19:22 -04:00
|
|
|
PROJECT_VER = "unknown"
|
2020-08-25 21:02:02 -04:00
|
|
|
|
2024-08-02 15:39:17 -04:00
|
|
|
if os.name == 'nt':
|
|
|
|
|
IDF_PY = os.path.join(IDF_PATH, "tools", "idf.py")
|
|
|
|
|
IDF_MONITOR_PY = os.path.join(IDF_PATH, "tools", "idf_monitor.py")
|
|
|
|
|
ESPTOOL_PY = os.path.join(IDF_PATH, "components", "esptool_py", "esptool", "esptool.py")
|
|
|
|
|
PARTTOOL_PY = os.path.join(IDF_PATH, "components", "partition_table", "parttool.py")
|
|
|
|
|
GEN_ESP32PART_PY = os.path.join(IDF_PATH, "components", "partition_table", "gen_esp32part.py")
|
|
|
|
|
else:
|
|
|
|
|
IDF_PY = "idf.py"
|
|
|
|
|
IDF_MONITOR_PY = "idf_monitor.py"
|
|
|
|
|
ESPTOOL_PY = "esptool.py"
|
|
|
|
|
PARTTOOL_PY = "parttool.py"
|
|
|
|
|
GEN_ESP32PART_PY = "gen_esp32part.py"
|
2024-07-29 19:34:05 -04:00
|
|
|
MKFW_PY = os.path.join("tools", "mkfw.py")
|
|
|
|
|
|
2022-03-31 21:04:25 -04:00
|
|
|
if os.path.exists("rg_config.py"):
|
|
|
|
|
with open("rg_config.py", "rb") as f:
|
|
|
|
|
exec(f.read())
|
2022-03-14 00:19:22 -04:00
|
|
|
|
2020-09-11 19:33:56 -04:00
|
|
|
|
2024-01-30 18:32:55 -05:00
|
|
|
def run(cmd, cwd=None, check=True):
|
|
|
|
|
print(f"Running command: {' '.join(cmd)}")
|
2024-08-02 15:39:17 -04:00
|
|
|
if os.name == 'nt' and cmd[0].endswith(".py"):
|
|
|
|
|
return subprocess.run(["python", *cmd], shell=True, cwd=cwd, check=check)
|
|
|
|
|
return subprocess.run(cmd, shell=False, cwd=cwd, check=check)
|
2024-01-30 18:32:55 -05:00
|
|
|
|
|
|
|
|
|
2024-07-29 20:56:50 -04:00
|
|
|
def build_firmware(output_file, apps, fw_format="odroid-go", fatsize=0):
|
2022-04-20 20:06:11 -04:00
|
|
|
print("Building firmware with: %s\n" % " ".join(apps))
|
2024-07-29 20:56:50 -04:00
|
|
|
args = [MKFW_PY, output_file, f"{PROJECT_NAME} {PROJECT_VER}", PROJECT_ICON]
|
2021-09-13 20:38:10 -04:00
|
|
|
|
2022-11-03 16:25:19 -04:00
|
|
|
if fw_format == "esplay":
|
2021-09-13 20:38:10 -04:00
|
|
|
args.append("--esplay")
|
|
|
|
|
|
2022-04-20 20:06:11 -04:00
|
|
|
for app in apps:
|
|
|
|
|
part = PROJECT_APPS[app]
|
|
|
|
|
args += [str(part[0]), str(part[1]), str(part[2]), app, os.path.join(app, "build", app + ".bin")]
|
2020-08-25 21:02:02 -04:00
|
|
|
|
2024-01-30 18:32:55 -05:00
|
|
|
run(args)
|
2020-08-25 21:02:02 -04:00
|
|
|
|
|
|
|
|
|
2024-07-29 20:56:50 -04:00
|
|
|
def build_image(output_file, apps, img_format="esp32", fatsize=0):
|
2022-04-20 20:06:11 -04:00
|
|
|
print("Building image with: %s\n" % " ".join(apps))
|
2022-03-31 21:04:25 -04:00
|
|
|
image_data = bytearray(b"\xFF" * 0x10000)
|
|
|
|
|
table_ota = 0
|
|
|
|
|
table_csv = [
|
|
|
|
|
"nvs, data, nvs, 36864, 16384",
|
|
|
|
|
"otadata, data, ota, 53248, 8192",
|
|
|
|
|
"phy_init, data, phy, 61440, 4096",
|
|
|
|
|
]
|
|
|
|
|
|
2022-04-20 20:06:11 -04:00
|
|
|
for app in apps:
|
|
|
|
|
with open(os.path.join(app, "build", app + ".bin"), "rb") as f:
|
2022-03-31 21:04:25 -04:00
|
|
|
data = f.read()
|
2024-01-23 13:17:05 -05:00
|
|
|
part_size = max(PROJECT_APPS[app][2], math.ceil(len(data) / 0x10000) * 0x10000)
|
2022-04-20 20:06:11 -04:00
|
|
|
table_csv.append("%s, app, ota_%d, %d, %d" % (app, table_ota, len(image_data), part_size))
|
2022-03-31 21:04:25 -04:00
|
|
|
table_ota += 1
|
|
|
|
|
image_data += data + b"\xFF" * (part_size - len(data))
|
|
|
|
|
|
2024-06-29 20:06:34 +02:00
|
|
|
if fatsize:
|
2024-07-29 19:49:34 -04:00
|
|
|
# Use "vfs" label, same as MicroPython, in case the storage is to be shared with a MicroPython install
|
2024-07-29 20:45:39 -04:00
|
|
|
table_csv.append("vfs, data, fat, %d, %s" % (len(image_data), fatsize))
|
2024-06-29 20:06:34 +02:00
|
|
|
|
2024-01-23 17:21:00 -05:00
|
|
|
print("Generating partition table...")
|
|
|
|
|
with open("partitions.csv", "w") as f:
|
|
|
|
|
f.write("\n".join(table_csv))
|
2024-07-29 19:34:05 -04:00
|
|
|
run([GEN_ESP32PART_PY, "partitions.csv", "partitions.bin"])
|
2024-01-23 17:21:00 -05:00
|
|
|
with open("partitions.bin", "rb") as f:
|
|
|
|
|
table_bin = f.read()
|
|
|
|
|
|
|
|
|
|
print("Building bootloader...")
|
2024-07-29 19:34:05 -04:00
|
|
|
run([IDF_PY, "bootloader"], cwd=os.path.join(os.getcwd(), list(apps)[0]))
|
2024-01-30 18:32:55 -05:00
|
|
|
with open(os.path.join(os.getcwd(), list(apps)[0], "build", "bootloader", "bootloader.bin"), "rb") as f:
|
2024-01-23 17:21:00 -05:00
|
|
|
bootloader_bin = f.read()
|
2022-03-31 21:04:25 -04:00
|
|
|
|
2024-01-23 12:19:10 -05:00
|
|
|
if img_format == "esp32s3":
|
2024-01-23 13:58:57 -05:00
|
|
|
image_data[0x0000:0x0000+len(bootloader_bin)] = bootloader_bin
|
2024-01-23 12:19:10 -05:00
|
|
|
image_data[0x8000:0x8000+len(table_bin)] = table_bin
|
|
|
|
|
else:
|
|
|
|
|
image_data[0x1000:0x1000+len(bootloader_bin)] = bootloader_bin
|
|
|
|
|
image_data[0x8000:0x8000+len(table_bin)] = table_bin
|
|
|
|
|
|
2024-07-29 20:56:50 -04:00
|
|
|
with open(output_file, "wb") as f:
|
2022-03-31 21:04:25 -04:00
|
|
|
f.write(image_data)
|
|
|
|
|
|
2024-07-29 20:56:50 -04:00
|
|
|
print("\nSaved image '%s' (%d bytes)\n" % (output_file, len(image_data)))
|
2024-07-29 20:22:49 -04:00
|
|
|
|
|
|
|
|
|
2022-04-20 20:06:11 -04:00
|
|
|
def clean_app(app):
|
|
|
|
|
print("Cleaning up app '%s'..." % app)
|
2021-06-19 11:33:11 -04:00
|
|
|
try:
|
2022-04-20 20:06:11 -04:00
|
|
|
os.unlink(os.path.join(app, "sdkconfig"))
|
|
|
|
|
os.unlink(os.path.join(app, "sdkconfig.old"))
|
2021-09-13 20:38:10 -04:00
|
|
|
except:
|
|
|
|
|
pass
|
|
|
|
|
try:
|
2022-04-20 20:06:11 -04:00
|
|
|
shutil.rmtree(os.path.join(app, "build"))
|
2021-06-19 11:33:11 -04:00
|
|
|
except:
|
|
|
|
|
pass
|
|
|
|
|
print("Done.\n")
|
2020-08-25 21:02:02 -04:00
|
|
|
|
|
|
|
|
|
2024-03-18 18:34:38 -04:00
|
|
|
def build_app(app, device_type, with_profiling=False, no_networking=False, is_release=False):
|
2020-09-11 19:33:56 -04:00
|
|
|
# To do: clean up if any of the flags changed since last build
|
2022-04-20 20:06:11 -04:00
|
|
|
print("Building app '%s'" % app)
|
2024-07-29 19:34:05 -04:00
|
|
|
args = [IDF_PY, "app"]
|
2024-08-24 13:37:34 -04:00
|
|
|
args.append(f"-DRG_PROJECT_APP={app}")
|
|
|
|
|
args.append(f"-DRG_PROJECT_VER={PROJECT_VER}")
|
|
|
|
|
args.append(f"-DRG_BUILD_TARGET=RG_TARGET_{re.sub(r'[^A-Z0-9]', '_', device_type.upper())}")
|
2024-07-01 14:14:33 -04:00
|
|
|
args.append(f"-DRG_BUILD_RELEASE={1 if is_release else 0}")
|
2024-01-29 17:09:39 -05:00
|
|
|
args.append(f"-DRG_ENABLE_PROFILING={1 if with_profiling else 0}")
|
|
|
|
|
args.append(f"-DRG_ENABLE_NETWORKING={0 if no_networking else 1}")
|
2024-10-19 13:39:49 -04:00
|
|
|
with open("partitions.csv", "w") as f:
|
|
|
|
|
f.write("# This table isn't used, it's just needed to avoid esp-idf build failures.\n")
|
|
|
|
|
f.write("dummy, app, ota_0, 65536, 3145728\n")
|
2024-01-30 18:32:55 -05:00
|
|
|
run(args, cwd=os.path.join(os.getcwd(), app))
|
2024-01-23 13:58:57 -05:00
|
|
|
print("Done.\n")
|
2020-08-25 21:02:02 -04:00
|
|
|
|
|
|
|
|
|
2024-01-24 13:24:55 -05:00
|
|
|
def flash_app(app, port, baudrate=1152000):
|
|
|
|
|
os.putenv("ESPTOOL_CHIP", os.getenv("IDF_TARGET", "auto"))
|
2024-08-23 14:52:59 -04:00
|
|
|
os.putenv("ESPTOOL_BAUD", str(baudrate))
|
2024-01-24 13:24:55 -05:00
|
|
|
os.putenv("ESPTOOL_PORT", port)
|
|
|
|
|
if not os.path.exists("partitions.bin"):
|
|
|
|
|
print("Reading device's partition table...")
|
2024-07-29 19:34:05 -04:00
|
|
|
run([ESPTOOL_PY, "read_flash", "0x8000", "0x1000", "partitions.bin"], check=False)
|
|
|
|
|
run([GEN_ESP32PART_PY, "partitions.bin"], check=False)
|
2024-01-24 13:24:55 -05:00
|
|
|
app_bin = os.path.join(app, "build", app + ".bin")
|
|
|
|
|
print(f"Flashing '{app_bin}' to port {port}")
|
2024-07-29 19:34:05 -04:00
|
|
|
run([PARTTOOL_PY, "--partition-table-file", "partitions.bin", "write_partition", "--partition-name", app, "--input", app_bin])
|
2024-01-24 13:24:55 -05:00
|
|
|
|
|
|
|
|
|
2024-07-29 20:29:40 -04:00
|
|
|
def flash_image(image_file, port, baudrate=1152000):
|
|
|
|
|
os.putenv("ESPTOOL_CHIP", os.getenv("IDF_TARGET", "auto"))
|
2024-08-23 14:52:59 -04:00
|
|
|
os.putenv("ESPTOOL_BAUD", str(baudrate))
|
2024-07-29 20:29:40 -04:00
|
|
|
os.putenv("ESPTOOL_PORT", port)
|
|
|
|
|
print(f"Flashing image file '{image_file}' to {port}")
|
|
|
|
|
run([ESPTOOL_PY, "write_flash", "--flash_size", "detect", "0x0", image_file])
|
|
|
|
|
|
|
|
|
|
|
2022-04-20 20:06:11 -04:00
|
|
|
def monitor_app(app, port, baudrate=115200):
|
2024-01-23 15:10:10 -05:00
|
|
|
print(f"Starting monitor for app {app} on port {port}")
|
2024-07-28 13:24:01 -04:00
|
|
|
elf_file = os.path.join(os.getcwd(), app, "build", app + ".elf")
|
|
|
|
|
if os.path.exists(elf_file):
|
2024-07-29 19:34:05 -04:00
|
|
|
run([IDF_MONITOR_PY, "--port", port, elf_file])
|
2024-07-28 13:24:01 -04:00
|
|
|
else: # We must pass a file to idf_monitor.py but it doesn't have to be valid with -d
|
2024-07-29 19:34:05 -04:00
|
|
|
run([IDF_MONITOR_PY, "--port", port, "-d", sys.argv[0]])
|
2020-08-25 21:02:02 -04:00
|
|
|
|
|
|
|
|
|
2020-08-27 16:40:45 -04:00
|
|
|
parser = argparse.ArgumentParser(description="Retro-Go build tool")
|
|
|
|
|
parser.add_argument(
|
2020-09-11 19:41:51 -04:00
|
|
|
# To do: Learn to use subcommands instead...
|
2024-07-29 20:22:49 -04:00
|
|
|
"command", choices=["build-fw", "build-img", "release", "build", "clean", "flash", "monitor", "run", "profile", "install"],
|
2020-08-27 16:40:45 -04:00
|
|
|
)
|
|
|
|
|
parser.add_argument(
|
2021-06-19 11:33:11 -04:00
|
|
|
"apps", nargs="*", default="all", choices=["all"] + list(PROJECT_APPS.keys())
|
2020-08-27 16:40:45 -04:00
|
|
|
)
|
2020-09-11 19:33:56 -04:00
|
|
|
parser.add_argument(
|
2022-04-20 20:06:11 -04:00
|
|
|
"--target", default=DEFAULT_TARGET, choices=set(TARGETS), help="Device to target"
|
2020-09-11 19:33:56 -04:00
|
|
|
)
|
2020-09-09 16:43:17 -04:00
|
|
|
parser.add_argument(
|
2024-01-12 18:29:24 -05:00
|
|
|
"--no-networking", action="store_const", const=True, help="Build without networking support"
|
2020-09-09 16:43:17 -04:00
|
|
|
)
|
2020-08-27 16:40:45 -04:00
|
|
|
parser.add_argument(
|
2021-09-24 12:26:08 -04:00
|
|
|
"--port", default=DEFAULT_PORT, help="Serial port to use for flash and monitor"
|
|
|
|
|
)
|
|
|
|
|
parser.add_argument(
|
|
|
|
|
"--baud", default=DEFAULT_BAUD, help="Serial baudrate to use for flashing"
|
2020-08-27 16:40:45 -04:00
|
|
|
)
|
2024-06-29 20:06:34 +02:00
|
|
|
parser.add_argument(
|
|
|
|
|
"--fatsize", help="Add FAT storage partition of provided size (500K, 5M,...) to the built image."
|
|
|
|
|
)
|
2020-08-27 16:40:45 -04:00
|
|
|
args = parser.parse_args()
|
2022-10-01 15:11:19 -04:00
|
|
|
|
2022-11-03 16:25:19 -04:00
|
|
|
command = args.command
|
|
|
|
|
apps = [app for app in PROJECT_APPS.keys() if app in args.apps or "all" in args.apps]
|
|
|
|
|
|
2022-10-01 15:11:19 -04:00
|
|
|
|
2024-01-13 15:15:16 -05:00
|
|
|
if os.path.exists(f"components/retro-go/targets/{args.target}/sdkconfig"):
|
2024-01-23 15:10:10 -05:00
|
|
|
os.putenv("SDKCONFIG_DEFAULTS", os.path.abspath(f"components/retro-go/targets/{args.target}/sdkconfig"))
|
2024-01-13 15:15:16 -05:00
|
|
|
|
2024-01-18 16:13:44 -05:00
|
|
|
if os.path.exists(f"components/retro-go/targets/{args.target}/env.py"):
|
|
|
|
|
with open(f"components/retro-go/targets/{args.target}/env.py", "rb") as f:
|
|
|
|
|
exec(f.read())
|
2020-08-27 16:40:45 -04:00
|
|
|
|
2024-01-23 17:21:00 -05:00
|
|
|
try:
|
2024-07-29 20:22:49 -04:00
|
|
|
if command in ["build-fw", "build-img", "release", "install"] and "launcher" not in apps:
|
2024-01-23 17:21:00 -05:00
|
|
|
print("\nWARNING: The launcher is mandatory for those apps and will be included!\n")
|
|
|
|
|
apps.insert(0, "launcher")
|
2020-09-09 16:43:17 -04:00
|
|
|
|
2024-01-23 17:21:00 -05:00
|
|
|
if command in ["clean", "release"]:
|
|
|
|
|
print("=== Step: Cleaning ===\n")
|
|
|
|
|
for app in apps:
|
|
|
|
|
clean_app(app)
|
2021-06-19 11:33:11 -04:00
|
|
|
|
2024-07-29 20:22:49 -04:00
|
|
|
if command in ["build", "build-fw", "build-img", "release", "run", "profile", "install"]:
|
2024-01-23 17:21:00 -05:00
|
|
|
print("=== Step: Building ===\n")
|
|
|
|
|
for app in apps:
|
2024-03-18 18:34:38 -04:00
|
|
|
build_app(app, args.target, command == "profile", args.no_networking, command == "release")
|
2020-08-25 21:02:02 -04:00
|
|
|
|
2024-01-23 17:21:00 -05:00
|
|
|
if command in ["build-fw", "release"]:
|
|
|
|
|
print("=== Step: Packing ===\n")
|
2024-07-29 20:56:50 -04:00
|
|
|
fw_file = ("%s_%s_%s.fw" % (PROJECT_NAME, PROJECT_VER, args.target)).lower()
|
|
|
|
|
build_firmware(fw_file, apps, os.getenv("FW_FORMAT"), args.fatsize)
|
2022-03-19 21:17:44 -04:00
|
|
|
|
2024-07-29 20:22:49 -04:00
|
|
|
if command in ["build-img", "release", "install"]:
|
2024-01-23 17:21:00 -05:00
|
|
|
print("=== Step: Packing ===\n")
|
2024-07-29 20:56:50 -04:00
|
|
|
img_file = ("%s_%s_%s.img" % (PROJECT_NAME, PROJECT_VER, args.target)).lower()
|
|
|
|
|
build_image(img_file, apps, os.getenv("IMG_FORMAT", os.getenv("IDF_TARGET")), args.fatsize)
|
2024-07-29 20:22:49 -04:00
|
|
|
|
|
|
|
|
if command in ["install"]:
|
|
|
|
|
print("=== Step: Flashing entire image to device ===\n")
|
|
|
|
|
# Should probably show a warning here and ask for confirmation...
|
2024-07-29 20:56:50 -04:00
|
|
|
flash_image(img_file, args.port, args.baud)
|
2020-08-25 21:02:02 -04:00
|
|
|
|
2024-01-23 17:21:00 -05:00
|
|
|
if command in ["flash", "run", "profile"]:
|
|
|
|
|
print("=== Step: Flashing ===\n")
|
2024-01-24 13:24:55 -05:00
|
|
|
try: os.unlink("partitions.bin")
|
|
|
|
|
except: pass
|
2022-03-03 11:42:05 -05:00
|
|
|
for app in apps:
|
2024-01-24 13:24:55 -05:00
|
|
|
flash_app(app, args.port, args.baud)
|
2020-09-09 16:43:17 -04:00
|
|
|
|
2024-01-23 17:21:00 -05:00
|
|
|
if command in ["monitor", "run", "profile"]:
|
|
|
|
|
print("=== Step: Monitoring ===\n")
|
2024-07-28 13:24:01 -04:00
|
|
|
monitor_app(apps[0] if len(apps) else "none", args.port)
|
2024-01-23 17:21:00 -05:00
|
|
|
|
2024-01-23 19:38:48 -05:00
|
|
|
print("All done!")
|
2024-01-23 17:21:00 -05:00
|
|
|
|
|
|
|
|
except KeyboardInterrupt as e:
|
|
|
|
|
exit("\n")
|
2020-08-25 21:02:02 -04:00
|
|
|
|
2024-01-23 17:21:00 -05:00
|
|
|
except Exception as e:
|
|
|
|
|
exit(f"\nTask failed: {e}")
|