mirror of
https://github.com/m5stack/esp-bsp.git
synced 2026-05-20 11:26:31 -07:00
297 lines
10 KiB
Python
297 lines
10 KiB
Python
# SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
import sys
|
|
import os
|
|
import json
|
|
import shutil
|
|
import argparse
|
|
from zipfile import ZipFile
|
|
from typing import Optional
|
|
|
|
# Directory of all boards
|
|
BOARDS_DIR = "boards/"
|
|
# Other board files, which are same for all boards (there can be used placeholders)
|
|
COMMON_DIR = "common/"
|
|
# JSON file which describing the board (must be in each board directory)
|
|
BOARD_GENFILE = "manifest.json"
|
|
# Components specific folder in specific board folder. Will be copied from boards/BOARD/ directory
|
|
COMPONENTS_SPECIFIC_FILES = "components"
|
|
# Files, which are specific for each board. Will be copied from boards/BOARD/ directory
|
|
BOARD_SPECIFIC_FILES = {"sdkconfig.defaults", "main/idf_component.yml", "partitions.csv"}
|
|
# Generated output directory
|
|
OUT_DIR = "espressif"
|
|
# SquareLine output directory
|
|
SQUARELINE_OUTDIR = "__ui_project_name__"
|
|
# Template of SLB file
|
|
SLB_FILE = {
|
|
"version":"",
|
|
"group":"Espressif",
|
|
"title": "",
|
|
"keywords": "Espressif",
|
|
"width": 0,
|
|
"height": 0,
|
|
"width_min": 0,
|
|
"height_min": 0,
|
|
"width_max": 0,
|
|
"height_max": 0,
|
|
"offset_x": 0,
|
|
"offset_y": 0,
|
|
"rotation": 0,
|
|
"color_depth": "",
|
|
"lvgl_export_path": "",
|
|
"lvgl_include_path": "lvgl.h",
|
|
"supported_lvgl_version": "9.1.*",
|
|
"pattern_match_files": "./CMakeLists.txt",
|
|
"language":"C",
|
|
"ui_export_path":"./main/ui",
|
|
"url":"https://github.com/espressif/esp-bsp",
|
|
"short_description": "",
|
|
"long_description": ""
|
|
}
|
|
|
|
# ANSI terminal codes
|
|
ANSI_RED = '\033[1;31m'
|
|
ANSI_YELLOW = '\033[0;33m'
|
|
ANSI_GREEN = '\033[1;32m'
|
|
ANSI_NORMAL = '\033[0m'
|
|
|
|
|
|
# Print colored message
|
|
def color_print(message, color, newline='\n'): # type: (str, str, Optional[str]) -> None
|
|
""" Print a message to stderr with colored highlighting """
|
|
sys.stderr.write('%s%s%s%s' % (color, message, ANSI_NORMAL, newline))
|
|
sys.stderr.flush()
|
|
|
|
|
|
# Print green text
|
|
def print_ok(message):
|
|
color_print(message, ANSI_GREEN)
|
|
|
|
|
|
# Print red error
|
|
def print_error(message):
|
|
color_print(message, ANSI_RED)
|
|
|
|
|
|
# Remove folder with all inside on specific path
|
|
def remove_folder(path):
|
|
for filename in os.listdir(path):
|
|
new_path = os.path.join(path, filename)
|
|
if os.path.isdir(new_path):
|
|
remove_folder(new_path)
|
|
else:
|
|
os.remove(new_path)
|
|
os.rmdir(path)
|
|
|
|
|
|
# When placeholder is not set in JSON, but find in code, it will be ignored (no error and no substituted)
|
|
class SafeDict(dict):
|
|
def __missing__(self, key):
|
|
return '{' + key + '}'
|
|
|
|
|
|
# Replace placeholder, which are set in JSON
|
|
def replace_placeholders(file, placeholders):
|
|
if os.path.exists(file):
|
|
with open(file, "r") as f:
|
|
file_str = f.read()
|
|
file_str = file_str.format_map(SafeDict(placeholders))
|
|
with open(file, "w") as f:
|
|
f.write(file_str)
|
|
|
|
|
|
# Copy file and replace placeholders in copied file
|
|
def copy_file(src_path, dst_path, placeholders, translate=1):
|
|
print(f" Copying {src_path} to {dst_path}")
|
|
shutil.copyfile(src_path, dst_path)
|
|
if translate:
|
|
replace_placeholders(dst_path, placeholders)
|
|
|
|
|
|
# Copy all files in directory
|
|
def copy_directory(from_dir, to_dir, placeholders, translate=1):
|
|
if not os.path.exists(to_dir):
|
|
os.makedirs(to_dir)
|
|
for filename in os.listdir(from_dir):
|
|
f = os.path.join(from_dir, filename)
|
|
out_f = os.path.join(to_dir, filename)
|
|
if os.path.isdir(f):
|
|
copy_directory(f, out_f, placeholders, translate)
|
|
else:
|
|
copy_file(f, out_f, placeholders, translate)
|
|
|
|
|
|
# Add folder into ZIP archive recursively
|
|
def zip_add_folder(zip_obj, path, outdir):
|
|
for filename in os.listdir(path):
|
|
file = os.path.join(path, filename)
|
|
out_file = os.path.join(outdir, filename)
|
|
out_file_rel = os.path.join(outdir, os.path.basename(file))
|
|
zip_obj.write(file, out_file_rel)
|
|
if os.path.isdir(file):
|
|
zip_add_folder(zip_obj, file, out_file)
|
|
|
|
|
|
# Creates ZIP archive
|
|
def make_zip(board_name, path):
|
|
zip_obj = ZipFile(os.path.join(path, board_name + ".zip"), 'w')
|
|
zip_dir = os.path.join(path, SQUARELINE_OUTDIR)
|
|
zip_add_folder(zip_obj, zip_dir, SQUARELINE_OUTDIR)
|
|
zip_obj.close()
|
|
|
|
|
|
# Check if JSON key exists, if not error and exit the script
|
|
def check_json_key(manifest, key):
|
|
if key not in manifest:
|
|
print_error(f"Missing {key} in manifest JSON file")
|
|
raise SystemExit(1)
|
|
|
|
|
|
# Get version in filename format
|
|
def get_board_version(manifest):
|
|
check_json_key(manifest, "version")
|
|
v = manifest["version"].split('.')
|
|
return f"_v{v[0]}_{v[1]}_{v[2]}"
|
|
|
|
|
|
# Create SLB file by JSON manifest data
|
|
def create_slb_file(output, output_filename, manifest):
|
|
check_json_key(manifest, "name")
|
|
check_json_key(manifest, "version")
|
|
check_json_key(manifest, "mcu")
|
|
check_json_key(manifest, "screen_width")
|
|
check_json_key(manifest, "screen_height")
|
|
check_json_key(manifest, "screen_color_swap")
|
|
check_json_key(manifest, "short_description")
|
|
check_json_key(manifest, "long_description")
|
|
check_json_key(manifest, "supported_lvgl_version")
|
|
|
|
SLB_FILE["version"] = manifest["version"]
|
|
SLB_FILE["title"] = manifest["name"]
|
|
SLB_FILE["keywords"] += ", " + manifest["name"] + ", " + manifest["mcu"]
|
|
SLB_FILE["width"] = manifest["screen_width"]
|
|
SLB_FILE["width_min"] = manifest["screen_width"]
|
|
SLB_FILE["width_max"] = manifest["screen_width"]
|
|
SLB_FILE["height"] = manifest["screen_height"]
|
|
SLB_FILE["height_min"] = manifest["screen_height"]
|
|
SLB_FILE["height_max"] = manifest["screen_height"]
|
|
if manifest["screen_color_swap"]:
|
|
SLB_FILE["color_depth"] = "16 sw"
|
|
else:
|
|
SLB_FILE["color_depth"] = "16"
|
|
SLB_FILE["short_description"] = manifest["short_description"]
|
|
SLB_FILE["long_description"] = manifest["long_description"]
|
|
SLB_FILE["supported_lvgl_version"] = manifest["supported_lvgl_version"]
|
|
|
|
out_slb_json = json.dumps(SLB_FILE, indent=4)
|
|
slb_file = os.path.join(output, output_filename + ".slb")
|
|
with open(slb_file, "w") as f:
|
|
f.write(out_slb_json)
|
|
|
|
print(f" File {output_filename}.slb created.")
|
|
|
|
|
|
# Process of the board generating
|
|
def process_board(board_name, output, dir):
|
|
print(f"Processing board: {dir}")
|
|
|
|
path = os.path.join(dir, BOARD_GENFILE)
|
|
if os.path.exists(path):
|
|
# Parse manifest file
|
|
with open(path, "r") as f:
|
|
manifest = json.loads(f.read())
|
|
|
|
# Get string version for board folder names
|
|
board_ver = get_board_version(manifest)
|
|
output = output + board_ver
|
|
output_filename = board_name
|
|
|
|
check_json_key(manifest, "placeholders")
|
|
placeholders = manifest["placeholders"]
|
|
|
|
# Copy common files
|
|
squareline_dir_path = os.path.join(output, SQUARELINE_OUTDIR)
|
|
copy_directory(COMMON_DIR, squareline_dir_path, placeholders)
|
|
# Copy specific board files
|
|
for file in BOARD_SPECIFIC_FILES:
|
|
board_specific_file = os.path.join(dir, file)
|
|
if os.path.exists(board_specific_file):
|
|
copy_file(board_specific_file, os.path.join(squareline_dir_path, file), placeholders)
|
|
# Copy components, if exists
|
|
components_dir_path = os.path.join(dir, COMPONENTS_SPECIFIC_FILES)
|
|
if os.path.exists(components_dir_path):
|
|
copy_directory(components_dir_path, os.path.join(squareline_dir_path, "components"), placeholders, 0)
|
|
# Copy image
|
|
copy_file(os.path.join(dir, "image.png"), os.path.join(output, output_filename + ".png"), placeholders, 0)
|
|
|
|
create_slb_file(output, output_filename, manifest)
|
|
# Copy LICENSE
|
|
shutil.copyfile(os.path.join(COMMON_DIR, "LICENSE"), os.path.join(output, "LICENSE"))
|
|
make_zip(output_filename, output)
|
|
remove_folder(squareline_dir_path)
|
|
else:
|
|
print_error(f"ERROR: File '{path}' is not exists!")
|
|
raise SystemExit(1)
|
|
|
|
|
|
# Print usage
|
|
def print_help():
|
|
print("Not enough arguments! Usage:")
|
|
print(" gen.py out board")
|
|
print("\tout: path to the generated output")
|
|
print("\tboard (optional): generate only specific board")
|
|
|
|
|
|
# Create gitignore file into output folder
|
|
def create_gitignore(path):
|
|
with open(os.path.join(path, ".gitignore"), "w") as f:
|
|
f.write("*")
|
|
|
|
|
|
def main(outdir, sel_board):
|
|
output_folder = os.path.join(outdir, OUT_DIR)
|
|
if not os.path.exists(outdir):
|
|
os.mkdir(outdir)
|
|
|
|
create_gitignore(outdir)
|
|
|
|
if sel_board:
|
|
# Process only one specific board
|
|
board_name = sel_board
|
|
board_path = BOARDS_DIR + board_name
|
|
output_folder = os.path.join(output_folder, board_name)
|
|
# Remove all in output directory
|
|
if os.path.exists(output_folder):
|
|
shutil.rmtree(output_folder)
|
|
# Process
|
|
process_board(board_name, output_folder, board_path)
|
|
else:
|
|
# Remove all in output directory
|
|
if os.path.exists(output_folder):
|
|
shutil.rmtree(output_folder)
|
|
# loop all boards V8
|
|
for dirname in os.listdir(BOARDS_DIR + "v8/"):
|
|
d = os.path.join(BOARDS_DIR, "v8/" + dirname)
|
|
# checking if it is a file
|
|
if os.path.isdir(d) and "custom" not in dirname:
|
|
process_board(dirname, os.path.join(output_folder, dirname), d)
|
|
# loop all boards V9
|
|
for dirname in os.listdir(BOARDS_DIR + "v9/"):
|
|
d = os.path.join(BOARDS_DIR, "v9/" + dirname)
|
|
# checking if it is a file
|
|
if os.path.isdir(d) and "custom" not in dirname:
|
|
process_board(dirname, os.path.join(output_folder, dirname), d)
|
|
|
|
print_ok(f"Generating done! Output folder: {outdir}")
|
|
|
|
|
|
if __name__ == '__main__':
|
|
parser = argparse.ArgumentParser(description='Generates SquareLine board packages.')
|
|
parser.add_argument("-o", "--outdir", action="store", dest='outdir', default='out',
|
|
help='Output directory for generated files (default: out)')
|
|
parser.add_argument("-b", "--board", action="store", dest='sel_board', default='',
|
|
help='Generate only selected board (default: generates all)')
|
|
|
|
args = parser.parse_args()
|
|
main(args.outdir, args.sel_board)
|