initial commit

This commit is contained in:
shell committed 2026-10-11 21:48:59 +08:00
commit b44ce7bd77
2716 files changed
+559005

No files matched your search

+333
View File
@@ -0,0 +1,333 @@
#!/usr/bin/env python3
import argparse
import random
import os
import sys
import time
import subprocess
import shlex
from pathlib import Path
from multiprocessing import Pool
import build_utils
STATUS_OK = "\033[32mOK\033[0m"
STATUS_FAILED = "\033[31mFailed\033[0m"
STATUS_SKIPPED = "\033[33mSkipped\033[0m"
RET_OK = 0
RET_FAILED = 1
RET_SKIPPED = 2
build_format = '| {:30} | {:40} | {:16} | {:5} |'
build_separator = '-' * 95
build_status = [STATUS_OK, STATUS_FAILED, STATUS_SKIPPED]
verbose = False
parallel_jobs = os.cpu_count()
# CI board control lists (used when running under CI)
ci_skip_boards = {
'rp2040': [
'adafruit_feather_rp2040_usb_host',
'adafruit_fruit_jam',
'adafruit_metro_rp2350',
'feather_rp2040_max3421',
'pico_sdk',
'raspberry_pi_pico_w',
],
}
ci_preferred_boards = {
'rp2040': ['raspberry_pi_pico'],
'samd2x_l2x': ['metro_m0_express'],
'samd5x_e5x': ['metro_m4_express'],
'stm32h7': ['stm32h743eval']
}
# -----------------------------
# Helper
# -----------------------------
def run_cmd(cmd):
if isinstance(cmd, str):
raise TypeError("run_cmd expects a list/tuple of args, not a string")
args = cmd
cmd_display = " ".join(args)
r = subprocess.run(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
title = f'Command Error: {cmd_display}'
if r.returncode != 0:
# print build output if failed
if os.getenv('GITHUB_ACTIONS'):
print(f"::group::{title}")
print(r.stdout.decode("utf-8"))
print(f"::endgroup::")
else:
print(title)
print(r.stdout.decode("utf-8"))
elif verbose:
print(cmd_display)
print(r.stdout.decode("utf-8"))
return r
def find_family(board):
bsp_dir = Path("hw/bsp")
for family_dir in bsp_dir.iterdir():
if family_dir.is_dir():
board_dir = family_dir / 'boards' / board
if board_dir.exists():
return family_dir.name
return None
def get_examples(family):
all_examples = []
for d in os.scandir("examples"):
if d.is_dir() and 'cmake' not in d.name and 'build_system' not in d.name:
for entry in os.scandir(d.path):
if entry.is_dir() and 'cmake' not in entry.name:
if family != 'espressif' or 'freertos' in entry.name:
all_examples.append(d.name + '/' + entry.name)
if family == 'espressif':
all_examples.append('device/board_test')
all_examples.append('device/video_capture')
all_examples.append('host/device_info')
all_examples.sort()
return all_examples
def print_build_result(board, build_target, status, duration):
if isinstance(duration, (int, float)):
duration = "{:.2f}s".format(duration)
print(build_format.format(board, build_target, build_status[status], duration))
# -----------------------------
# CMake
# -----------------------------
def cmake_board(board, build_args, build_name, build_cflags, build_targets):
ret = [0, 0, 0]
start_time = time.monotonic()
build_dir = f'cmake-build/cmake-build-{build_name or board}'
build_flags = []
if build_cflags:
build_flags.append('-DCFLAGS_CLI=' + ' '.join(build_cflags))
family = find_family(board)
if family == 'espressif':
# for espressif, we have to build example individually
all_examples = get_examples(family)
for example in all_examples:
if build_utils.skip_example(example, board):
ret[2] += 1
else:
rcmd = run_cmd([
'idf.py', '-C', f'examples/{example}', '-B', f'{build_dir}/{example}', '-GNinja',
f'-DBOARD={board}', *build_flags, 'build'
])
ret[0 if rcmd.returncode == 0 else 1] += 1
else:
rcmd = run_cmd(['cmake', 'examples', '-B', build_dir, '-GNinja',
f'-DBOARD={board}', '-DCMAKE_BUILD_TYPE=MinSizeRel', '-DLINKERMAP_OPTION=-q -f tinyusb/src',
*build_args, *build_flags])
if rcmd.returncode == 0:
cmd = ["cmake", "--build", build_dir, '--parallel', str(parallel_jobs)]
for target in build_targets:
rcmd = run_cmd(cmd + ['--target', target])
if rcmd.returncode != 0:
break
ret[0 if rcmd.returncode == 0 else 1] += 1
print_build_result(board, ','.join(build_targets), 0 if ret[1] == 0 else 1, time.monotonic() - start_time)
return ret
# -----------------------------
# Make
# -----------------------------
def make_one_example(example, board, make_option, build_targets):
# Check if board is skipped
if build_utils.skip_example(example, board):
print_build_result(board, example, 2, '-')
r = 2
else:
start_time = time.monotonic()
make_cmd = ["make", "-C", f"examples/{example}", f"BOARD={board}", '-j', str(parallel_jobs)]
if make_option:
make_cmd += shlex.split(make_option)
r = 0
for target in build_targets:
build_result = run_cmd(make_cmd + [target])
if build_result.returncode != 0:
r = 1
break
print_build_result(board, example, r, time.monotonic() - start_time)
ret = [0, 0, 0]
ret[r] = 1
return ret
def make_board(board, build_args, build_targets):
print(build_separator)
family = find_family(board);
all_examples = get_examples(family)
start_time = time.monotonic()
ret = [0, 0, 0]
if family == 'espressif' or family == 'rp2040':
# espressif and rp2040 do not support make, use cmake instead
final_status = 2
else:
with Pool(processes=os.cpu_count()) as pool:
pool_args = list((map(lambda e, b=board, o=f"{build_args}", t=build_targets: [e, b, o, t], all_examples)))
r = pool.starmap(make_one_example, pool_args)
# sum all element of same index (column sum)
ret = list(map(sum, list(zip(*r))))
final_status = 0 if ret[1] == 0 else 1
print_build_result(board, 'all', final_status, time.monotonic() - start_time)
return ret
# -----------------------------
# Build Family
# -----------------------------
def build_boards_list(boards, build_defines, build_system, build_name, build_cflags, build_targets):
ret = [0, 0, 0]
for b in boards:
r = [0, 0, 0]
if build_system == 'cmake':
build_args = [f'-D{d}' for d in build_defines]
r = cmake_board(b, build_args, build_name, build_cflags, build_targets)
elif build_system == 'make':
build_args = ' '.join(f'{d}' for d in build_defines)
r = make_board(b, build_args, build_targets)
ret[0] += r[0]
ret[1] += r[1]
ret[2] += r[2]
return ret
def get_family_boards(family, one_random, one_first):
"""Get list of boards for a family.
Args:
family: Family name
one_random: If True, return only one random board
one_first: If True, return only the first board (alphabetical)
Returns:
List of board names
"""
skip_list = []
preferred_list = []
if os.getenv('GITHUB_ACTIONS') or os.getenv('CIRCLECI'):
skip_list = ci_skip_boards.get(family, [])
preferred_list = ci_preferred_boards.get(family, [])
all_boards = []
for entry in os.scandir(f"hw/bsp/{family}/boards"):
if entry.is_dir() and entry.name not in skip_list:
all_boards.append(entry.name)
if not all_boards:
print(f"No boards found for family '{family}'")
return []
all_boards.sort()
# If only-one flags are set, honor select list first, then pick first or random
if one_first or one_random:
if preferred_list:
return [preferred_list[0]]
if one_first:
return [all_boards[0]]
if one_random:
return [random.choice(all_boards)]
return all_boards
# -----------------------------
# Main
# -----------------------------
def main():
global verbose
global parallel_jobs
parser = argparse.ArgumentParser()
parser.add_argument('families', nargs='*', default=[], help='Families to build')
parser.add_argument('-b', '--board', action='append', default=[], help='Boards to build')
parser.add_argument('-t', '--toolchain', default='gcc', help='Toolchain to use, default is gcc')
parser.add_argument('-s', '--build-system', default='cmake', help='Build system to use, default is cmake')
parser.add_argument('-D', '--define-symbol', action='append', default=[], help='Define to pass to build system')
parser.add_argument('--build-name', default=None,
help='Override build dir name (cmake-build-<name>); default is the board name. Used for HIL variants.')
parser.add_argument('--cflag', action='append', default=[],
help='Raw compiler flag appended to CFLAGS_CLI, e.g. --cflag=-DCFG_TUD_DWC2_DMA_ENABLE=1 (repeatable)')
parser.add_argument('--one-random', action='store_true', default=False,
help='Build only one random board of each specified family')
parser.add_argument('--one-first', action='store_true', default=False,
help='Build only the first board (alphabetical) of each specified family')
parser.add_argument('-j', '--jobs', type=int, default=os.cpu_count(), help='Number of jobs to run in parallel')
parser.add_argument('-T', '--target', action='append', default=[],
help='Build target to use, may be specified multiple times (default: all)')
parser.add_argument('-v', '--verbose', action='store_true', help='Verbose output')
args = parser.parse_args()
families = args.families
boards = args.board
toolchain = args.toolchain
build_system = args.build_system
build_defines = args.define_symbol
build_name = args.build_name
build_cflags = args.cflag
one_random = args.one_random
one_first = args.one_first
build_targets = args.target if args.target else ['all']
verbose = args.verbose
parallel_jobs = args.jobs
build_defines.append(f'TOOLCHAIN={toolchain}')
if len(families) == 0 and len(boards) == 0:
print("Please specify families or board to build")
return 1
# --build-name renames the single shared build dir, so building more than one
# board with it would clobber/mix artifacts
if build_name and (len(families) > 0 or len(boards) != 1):
print("--build-name requires exactly one board (-b) and no families")
return 1
print(build_separator)
print(build_format.format('Board', 'Target', '\033[39mResult\033[0m', 'Time'))
total_time = time.monotonic()
# get all families
all_families = []
if 'all' in families:
for entry in os.scandir("hw/bsp"):
if entry.is_dir() and entry.name != 'espressif' and os.path.isfile(entry.path + "/family.cmake"):
all_families.append(entry.name)
else:
all_families = list(families)
all_families.sort()
# get boards from families and append to boards list
all_boards = list(boards)
for f in all_families:
all_boards.extend(get_family_boards(f, one_random, one_first))
# build all boards
result = build_boards_list(all_boards, build_defines, build_system, build_name, build_cflags, build_targets)
total_time = time.monotonic() - total_time
print(build_separator)
print(f"Build Summary: {result[0]} {STATUS_OK}, {result[1]} {STATUS_FAILED} and took {total_time:.2f}s")
print(build_separator)
return result[1]
if __name__ == '__main__':
sys.exit(main())
+50
View File
@@ -0,0 +1,50 @@
#!/usr/bin/env python3
"""Build the TinyUSB Sphinx documentation locally.
Thin wrapper around `sphinx-build` so a manual doc build is one command.
`conf.py` auto-collects example READMEs, so no extra steps are needed.
python3 tools/build_doc.py # build docs/_build/
python3 tools/build_doc.py -c -W -o # clean, fail on warnings, open result
"""
import argparse
import shutil
import subprocess
import sys
import webbrowser
from pathlib import Path
TOP = Path(__file__).parent.parent.resolve()
DOCS = TOP / "docs"
BUILD = DOCS / "_build"
def main():
p = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
p.add_argument("-c", "--clean", action="store_true", help="remove docs/_build first")
p.add_argument("-W", "--strict", action="store_true", help="treat warnings as errors")
p.add_argument("-o", "--open", action="store_true", help="open the built docs in a browser")
args = p.parse_args()
if args.clean and BUILD.exists():
shutil.rmtree(BUILD)
cmd = ["sphinx-build", "-b", "html"]
if args.strict:
cmd.append("-W")
cmd += [str(DOCS), str(BUILD)]
print("+", " ".join(cmd))
rc = subprocess.call(cmd)
if rc != 0:
return rc
index = BUILD / "index.html"
print(f"\nDocs built: {index}")
if args.open:
webbrowser.open(index.as_uri())
return 0
if __name__ == "__main__":
sys.exit(main())
+98
View File
@@ -0,0 +1,98 @@
#!/usr/bin/env python3
import subprocess
import pathlib
import re
build_format = '| {:29} | {:30} | {:18} | {:7} | {:6} | {:6} |'
SUCCEEDED = "\033[32msucceeded\033[0m"
FAILED = "\033[31mfailed\033[0m"
SKIPPED = "\033[33mskipped\033[0m"
def skip_example(example, board):
ex_dir = pathlib.Path('examples/') / example
bsp = pathlib.Path("hw/bsp")
# board within family
board_dir = list(bsp.glob("*/boards/" + board))
if not board_dir:
# Skip unknown boards
return True
board_dir = list(board_dir)[0]
family_dir = board_dir.parent.parent
family = family_dir.name
# family.mk
family_mk = family_dir / "family.mk"
if not family_mk.exists():
family_mk = family_dir / "family.cmake"
mk_contents = family_mk.read_text()
# Find the mcu, first in family mk then board mk
if "CFG_TUSB_MCU=OPT_MCU_" not in mk_contents:
board_mk = board_dir / "board.mk"
if not board_mk.exists():
board_mk = board_dir / "board.cmake"
mk_contents = board_mk.read_text()
mcu = "NONE"
if family == "espressif":
for line in mk_contents.splitlines():
match = re.search(r'set\(IDF_TARGET\s+"([^"]+)"\)', line)
if match:
mcu = match.group(1).upper()
break
else:
for token in mk_contents.split():
if "CFG_TUSB_MCU=OPT_MCU_" in token:
# Strip " because cmake files has them.
token = token.strip("\"")
_, opt_mcu = token.split("=")
mcu = opt_mcu[len("OPT_MCU_"):]
if mcu != "NONE":
break
# Skip all OPT_MCU_NONE these are WIP port
if mcu == "NONE":
return True
max3421_enabled = False
for line in mk_contents.splitlines():
if "MAX3421_HOST=1" in line or 'MAX3421_HOST 1' in line:
max3421_enabled = True
break
skip_file = ex_dir / "skip.txt"
only_file = ex_dir / "only.txt"
if skip_file.exists():
skips = skip_file.read_text().split()
if ("mcu:" + mcu in skips or
"board:" + board in skips or
"family:" + family in skips):
return True
if only_file.exists():
onlys = only_file.read_text().split()
if not ("mcu:" + mcu in onlys or
("mcu:MAX3421" in onlys and max3421_enabled) or
"board:" + board in onlys or
"family:" + family in onlys):
return True
return False
def build_size(make_cmd):
size_output = subprocess.run(make_cmd + ' size', shell=True, stdout=subprocess.PIPE).stdout.decode("utf-8").splitlines()
for i, l in enumerate(size_output):
text_title = 'text data bss dec'
if text_title in l:
size_list = size_output[i+1].split('\t')
flash_size = int(size_list[0])
sram_size = int(size_list[1]) + int(size_list[2])
return (flash_size, sram_size)
return (0, 0)
Whitespace-only changes.
+17
View File
@@ -0,0 +1,17 @@
attch
busses
dout
endianess
fro
hsi
inout
mot
ore
pris
ptd
ser
sie
synopsys
te
thre
tre
+46
View File
@@ -0,0 +1,46 @@
#!/usr/bin/env python3
import argparse
import random
import os
import sys
import time
import subprocess
from pathlib import Path
from multiprocessing import Pool
from weakref import finalize
def print_carray(f, payload):
while len(payload) > 0:
f.write('\n ')
f.write(', '.join('0x{:02x}'.format(x) for x in payload[0:16]))
f.write(',')
payload = payload[16:]
f.write('\n')
def main():
parser = argparse.ArgumentParser(description='Convert binary files to C array format')
parser.add_argument('files', nargs='+', help='Binary files to convert')
args = parser.parse_args()
files = args.files
for fin_name in files:
if not os.path.isfile(fin_name):
print(f"File {fin_name} does not exist")
continue
with open(fin_name, 'rb') as fin:
contents = fin.read()
fout_name = fin_name + '.h'
with open(fout_name, 'w') as fout:
print(f"Converting {fin_name} to {fout_name}")
fout.write(f'enum {{ BINDATA_LEN = {len(contents)} }};\n')
fout.write(f'const size_t bindata_len = BINDATA_LEN;\n')
fout.write(f'const uint8_t bindata[] __attribute__((aligned(16))) = {{')
print_carray(fout, contents)
fout.write('};\n')
if __name__ == '__main__':
sys.exit(main())
+117
View File
@@ -0,0 +1,117 @@
#!/usr/bin/env python3
import re
import pandas as pd
from tabulate import tabulate
from pathlib import Path
from get_deps import deps_all
# TOP is tinyusb root dir
TOP = Path(__file__).parent.parent.resolve()
# -----------------------------------------
# Dependencies
# -----------------------------------------
def gen_deps_doc():
deps_rst = Path(TOP) / "docs/reference/dependencies.rst"
df = pd.DataFrame.from_dict(deps_all, orient='index', columns=['Repo', 'Commit', 'Required by'])
df = df[['Repo', 'Commit', 'Required by']].sort_index()
df = df.rename_axis("Local Path")
outstr = f"""\
************
Dependencies
************
MCU low-level peripheral drivers and external libraries for building TinyUSB examples
{tabulate(df, headers="keys", tablefmt='rst')}
"""
with deps_rst.open('w') as f:
f.write(outstr)
# -----------------------------------------
# Dependencies
# -----------------------------------------
def extract_metadata(file_path):
metadata = {}
try:
with open(file_path, 'r') as file:
content = file.read()
# Match metadata block
match = re.search(r'/\*\s*metadata:(.*?)\*/', content, re.DOTALL)
if match:
block = match.group(1)
# Extract key-value pairs
for line in block.splitlines():
key_value = re.match(r'\s*(\w+):\s*(.+)', line)
if key_value:
key, value = key_value.groups()
metadata[key] = value.strip()
except FileNotFoundError:
pass
return metadata
def gen_boards_doc():
# 'Manufacturer' : { 'Board' }
vendor_data = {}
# 'Board' : [ 'Name', 'Family', 'url', 'note' ]
all_boards = {}
# extract metadata from family.c
for family_dir in sorted((Path(TOP) / "hw/bsp").iterdir()):
if family_dir.is_dir():
family_c = family_dir / "family.c"
if not family_c.exists():
family_c = family_dir / "boards/family.c"
f_meta = extract_metadata(family_c)
if not f_meta:
continue
manuf = f_meta.get('manufacturer', '')
if manuf not in vendor_data:
vendor_data[manuf] = {}
# extract metadata from board.h
for board_dir in sorted((family_dir / "boards").iterdir()):
if board_dir.is_dir():
b_meta = extract_metadata(board_dir / "board.h")
if not b_meta:
continue
b_entry = [
b_meta.get('name', ''),
family_dir.name,
b_meta.get('url', ''),
b_meta.get('note', '')
]
vendor_data[manuf][board_dir.name] = b_entry
boards_rst = Path(TOP) / "docs/reference/boards.rst"
with boards_rst.open('w') as f:
title = f"""\
****************
Supported Boards
****************
The board support code is only used for self-contained examples and testing. It is not used when TinyUSB is part of a larger project.
It is responsible for getting the MCU started and the USB peripheral clocked with minimal of on-board devices
- One LED : for status
- One Button : to get input from user
- One UART : needed for logging with LOGGER=uart, maybe required for host/dual examples
Following boards are supported"""
f.write(title)
for manuf, boards in sorted(vendor_data.items()):
f.write(f"\n\n{manuf}\n")
f.write(f"{'-' * len(manuf)}\n\n")
df = pd.DataFrame.from_dict(boards, orient='index', columns=['Name', 'Family', 'URL', 'Note'])
df = df.rename_axis("Board")
f.write(tabulate(df, headers="keys", tablefmt='rst'))
# -----------------------------------------
# Main
# -----------------------------------------
if __name__ == "__main__":
gen_deps_doc()
gen_boards_doc()
+137
View File
@@ -0,0 +1,137 @@
#!/usr/bin/env python3
import os
import json
from pathlib import Path
def main():
board_list = []
board_list_esp = []
# Find all board.cmake files, exclude espressif
for root, dirs, files in os.walk("hw/bsp"):
for file in files:
if file == "board.cmake" and "espressif" not in root:
board_list.append(os.path.basename(root))
# Find all espressif boards
for root, dirs, files in os.walk("hw/bsp/espressif"):
for file in files:
if file == "board.cmake":
board_list_esp.append(os.path.basename(root))
print('Generating presets for the following boards:')
print(board_list)
# Generate the presets
presets = {}
presets['version'] = 6
# Configure presets
presets['configurePresets'] = [
{"name": "default",
"hidden": True,
"description": r"Configure preset for the ${presetName} board",
"generator": "Ninja Multi-Config",
"binaryDir": r"${sourceDir}/cmake-build-${presetName}",
"cacheVariables": {
"CMAKE_DEFAULT_BUILD_TYPE": "RelWithDebInfo",
"BOARD": r"${presetName}"
}},
{"name": "default single config",
"hidden": True,
"description": r"Configure preset for the ${presetName} board",
"generator": "Ninja",
"binaryDir": r"${sourceDir}/cmake-build-${presetName}",
"cacheVariables": {
"BOARD": r"${presetName}"
}}]
# Add non-espressif boards
presets['configurePresets'].extend(
sorted(
[
{
'name': board,
'inherits': 'default'
}
for board in board_list
], key=lambda x: x['name']
)
)
# Add espressif boards with single config generator
presets['configurePresets'].extend(
sorted(
[
{
'name': board,
'inherits': 'default single config'
}
for board in board_list_esp
], key=lambda x: x['name']
)
)
# Combine all boards
board_list.extend(board_list_esp)
# Build presets
# no inheritance since 'name' doesn't support macro expansion
presets['buildPresets'] = sorted(
[
{
'name': board,
'description': "Build preset for the " + board + " board",
'configurePreset': board
}
for board in board_list
], key=lambda x: x['name']
)
# Workflow presets
presets['workflowPresets'] = sorted(
[
{
"name": board,
"steps": [
{
"type": "configure",
"name": board
},
{
"type": "build",
"name": board
}
]
}
for board in board_list
], key=lambda x: x['name']
)
path_boardpresets = "hw/bsp/BoardPresets.json"
with open(path_boardpresets, "w") as f:
f.write('{}\n'.format(json.dumps(presets, indent=2)))
# Generate presets for examples
presets = {
"version": 6,
"include": [
]
}
example_list = []
for root, dirs, files in os.walk("examples"):
for file in files:
# Filter out ESP-IDF CMakeLists.txt in src folder
if file == "CMakeLists.txt" and os.path.basename(root) != 'src':
presets['include'] = [os.path.relpath(path_boardpresets, root).replace(os.sep, '/')]
with open(os.path.join(root, 'CMakePresets.json'), 'w') as f:
f.write('{}\n'.format(json.dumps(presets, indent=2)))
example_list.append(os.path.basename(root))
print('Generating presets for the following examples:')
print(example_list)
if __name__ == "__main__":
main()
+411
View File
@@ -0,0 +1,411 @@
#!/usr/bin/env python3
import argparse
import sys
import subprocess
from pathlib import Path
from multiprocessing import Pool
# Mandatory Dependencies that is always fetched
# path, url, commit, family (Alphabet sorted by path)
deps_mandatory = {
'lib/fatfs': ['https://github.com/abbrev/fatfs.git',
'30ca13c62615df0d2e9104ab41256985b96590c1',
'all'],
'lib/FreeRTOS-Kernel': ['https://github.com/FreeRTOS/FreeRTOS-Kernel.git',
'9b777ae5c5b8e9e456065a00294d1e5f5f9facf5',
'all'],
'lib/lwip': ['https://github.com/lwip-tcpip/lwip.git',
'159e31b689577dbf69cf0683bbaffbd71fa5ee10',
'all'],
'lib/threadx': ['https://github.com/eclipse-threadx/threadx.git',
'4b6e8100d932a3a67b34c6eb17f84f3bffb9e2ae',
'all'],
'tools/linkermap': ['https://github.com/hathach/linkermap.git',
'8e1f440fa15c567aceb5aa0d14f6d18c329cc67f',
'all'],
'tools/uf2': ['https://github.com/microsoft/uf2.git',
'c594542b2faa01cc33a2b97c9fbebc38549df80a',
'all'],
}
# Optional Dependencies per MCU
# path, url, commit, family (Alphabet sorted by path)
deps_optional = {
'hw/mcu/allwinner': ['https://github.com/hathach/allwinner_driver.git',
'8e5e89e8e132c0fd90e72d5422e5d3d68232b756',
'fc100s'],
'hw/mcu/analog/msdk' : ['https://github.com/analogdevicesinc/msdk.git',
'b20b398d3e5e2007594e54a74ba3d2a2e50ddd75',
'maxim'],
'hw/mcu/bridgetek/ft9xx/ft90x-sdk': ['https://github.com/BRTSG-FOSS/ft90x-sdk.git',
'03f74eac84645178fdde7f2e5ca9acdcb7bd9dcd',
'ft9xx'],
'hw/mcu/broadcom': ['https://github.com/adafruit/broadcom-peripherals.git',
'08370086080759ed54ac1136d62d2ad24c6fa267',
'broadcom_32bit broadcom_64bit'],
'hw/mcu/gd/nuclei-sdk': ['https://github.com/Nuclei-Software/nuclei-sdk.git',
'7eb7bfa9ea4fbeacfafe1d5f77d5a0e6ed3922e7',
'gd32vf103'],
'hw/mcu/infineon/mtb-xmclib-cat3': ['https://github.com/Infineon/mtb-xmclib-cat3.git',
'daf5500d03cba23e68c2f241c30af79cd9d63880',
'xmc4000'],
'hw/mcu/microchip': ['https://github.com/hathach/microchip_driver.git',
'9e8b37e307d8404033bb881623a113931e1edf27',
'sam3x samd11 samd21 samd51 samd5x_e5x same5x same7x samd2x_l2x samg'],
'hw/mcu/mindmotion/mm32sdk': ['https://github.com/hathach/mm32sdk.git',
'b93e856211060ae825216c6a1d6aa347ec758843',
'mm32'],
'hw/mcu/nordic/nrfx': ['https://github.com/NordicSemiconductor/nrfx.git',
'11f57e578c7feea13f21c79ea0efab2630ac68c7',
'nrf'],
'hw/mcu/nuvoton': ['https://github.com/majbthrd/nuc_driver.git',
'2204191ec76283371419fbcec207da02e1bc22fa',
'nuc100_120 nuc121_125 nuc126 nuc505'],
'hw/mcu/nxp/lpcopen': ['https://github.com/hathach/nxp_lpcopen.git',
'b41cf930e65c734d8ec6de04f1d57d46787c76ae',
'lpc11 lpc13 lpc15 lpc17 lpc18 lpc40 lpc43'],
'hw/mcu/nxp/mcuxsdk-core': ['https://github.com/nxp-mcuxpresso/mcuxsdk-core',
'0c5c6b16deb211110e06bde896cdff59ab213e16',
'imxrt kinetis_k32l lpc51 lpc55 mcx'],
'hw/mcu/nxp/mcux-sdk': ['https://github.com/nxp-mcuxpresso/mcux-sdk',
'a1bdae309a14ec95a4f64a96d3315a4f89c397c6',
'kinetis_k kinetis_kl lpc54 rw61x'],
'hw/mcu/nxp/mcux-devices-kinetis': ['https://github.com/nxp-mcuxpresso/mcux-devices-kinetis',
'98a155e666c54f396e528ec3131f27a5d5b71f76',
'kinetis_k32l'],
'hw/mcu/nxp/mcux-devices-lpc': ['https://github.com/nxp-mcuxpresso/mcux-devices-lpc',
'8096b783ec09d0d1c8629025a5f9d8e7df26e520',
'lpc51 lpc55'],
'hw/mcu/nxp/mcux-devices-mcx': ['https://github.com/nxp-mcuxpresso/mcux-devices-mcx',
'ada1c97c761123ec0c179bb9bb9f744bf9a11475',
'mcx'],
'hw/mcu/nxp/mcux-devices-rt': ['https://github.com/nxp-mcuxpresso/mcux-devices-rt',
'dba2b523c9df61f3330bd186242f8210a8e47c45',
'imxrt'],
'hw/mcu/raspberry_pi/FreeRTOS-Kernel': ['https://github.com/raspberrypi/FreeRTOS-Kernel.git',
'4f7299d6ea746b27a9dd19e87af568e34bd65b15',
'rp2040'],
'hw/mcu/raspberry_pi/Pico-PIO-USB': ['https://github.com/sekigon-gonnoc/Pico-PIO-USB.git',
'675543bcc9baa8170f868ab7ba316d418dbcf41f',
'rp2040'],
'hw/mcu/renesas/fsp': ['https://github.com/renesas/fsp.git',
'edcc97d684b6f716728a60d7a6fea049d9870bd6',
'ra'],
'hw/mcu/renesas/rx': ['https://github.com/kkitayam/rx_device.git',
'706b4e0cf485605c32351e2f90f5698267996023',
'rx'],
'hw/mcu/silabs/cmsis-dfp-efm32gg12b': ['https://github.com/cmsis-packs/cmsis-dfp-efm32gg12b.git',
'f1c31b7887669cb230b3ea63f9b56769078960bc',
'efm32'],
'hw/mcu/sony/cxd56/spresense-exported-sdk': ['https://github.com/sonydevworld/spresense-exported-sdk.git',
'2ec2a1538362696118dc3fdf56f33dacaf8f4067',
'spresense'],
'hw/mcu/st/cmsis_device_c0': ['https://github.com/STMicroelectronics/cmsis_device_c0.git',
'517611273f835ffe95318947647bc1408f69120d',
'stm32c0'],
'hw/mcu/st/cmsis_device_f0': ['https://github.com/STMicroelectronics/cmsis_device_f0.git',
'cbb5da5d48b4b5f2efacdc2f033be30f9d29889f',
'stm32f0'],
'hw/mcu/st/cmsis_device_f1': ['https://github.com/STMicroelectronics/cmsis_device_f1.git',
'c8e9a4a4f16b6d2cb2a2083cbe5161025280fb22',
'stm32f1'],
'hw/mcu/st/cmsis_device_f2': ['https://github.com/STMicroelectronics/cmsis_device_f2.git',
'49321f1e4d2bd3e65687b37f2652a28ea7983674',
'stm32f2'],
'hw/mcu/st/cmsis_device_f3': ['https://github.com/STMicroelectronics/cmsis_device_f3.git',
'5558e64e3675a1e1fcb1c71f468c7c407c1b1134',
'stm32f3'],
'hw/mcu/st/cmsis_device_f4': ['https://github.com/STMicroelectronics/cmsis_device_f4.git',
'3c77349ce04c8af401454cc51f85ea9a50e34fc1',
'stm32f4'],
'hw/mcu/st/cmsis_device_f7': ['https://github.com/STMicroelectronics/cmsis_device_f7.git',
'2352e888e821aa0f4fe549bd5ea81d29c67a3222',
'stm32f7'],
'hw/mcu/st/cmsis_device_g0': ['https://github.com/STMicroelectronics/cmsis_device_g0.git',
'f484fe852535f913a02ee79787eafa74dd7f9488',
'stm32g0'],
'hw/mcu/st/cmsis_device_g4': ['https://github.com/STMicroelectronics/cmsis_device_g4.git',
'7c39c32593b03764aaa57531588b8bf7cdd443a5',
'stm32g4'],
'hw/mcu/st/cmsis_device_h7': ['https://github.com/STMicroelectronics/cmsis_device_h7.git',
'45b818cab6ee2806e3a27c80e330957223424392',
'stm32h7'],
'hw/mcu/st/cmsis_device_h7rs': ['https://github.com/STMicroelectronics/cmsis_device_h7rs.git',
'57ea11f70ebf1850e1048989d665c9070f0bb863',
'stm32h7rs'],
'hw/mcu/st/cmsis_device_h5': ['https://github.com/STMicroelectronics/cmsis_device_h5.git',
'884b8dc78e41cbfca008363342b17f4a9e8641f7',
'stm32h5'],
'hw/mcu/st/cmsis_device_l0': ['https://github.com/STMicroelectronics/cmsis_device_l0.git',
'7b7ae8cd71437331e1d7824f157d00c7bb4a5044',
'stm32l0'],
'hw/mcu/st/cmsis_device_l1': ['https://github.com/STMicroelectronics/cmsis_device_l1.git',
'a23ade4ccf14012085fedf862e33a536ab7ed8be',
'stm32l1'],
'hw/mcu/st/cmsis_device_l4': ['https://github.com/STMicroelectronics/cmsis_device_l4.git',
'a2530753e86dd326a75467d28feb92e2ba7d0df2',
'stm32l4'],
'hw/mcu/st/cmsis_device_l5': ['https://github.com/STMicroelectronics/cmsis_device_l5.git',
'7d9a51481f0e6c376e62c3c849e6caf652c66482',
'stm32l5'],
'hw/mcu/st/cmsis_device_n6': ['https://github.com/STMicroelectronics/cmsis-device-n6.git',
'7bcdc944fbf7cf5928d3c1d14054ca13261d33ec',
'stm32n6'],
'hw/mcu/st/cmsis-device-u0': ['https://github.com/STMicroelectronics/cmsis-device-u0.git',
'e3a627c6a5bc4eb2388e1885a95cc155e1672253',
'stm32u0'],
'hw/mcu/st/cmsis_device_u5': ['https://github.com/STMicroelectronics/cmsis_device_u5.git',
'6e67187dec98035893692ab2923914cb5f4e0117',
'stm32u5'],
'hw/mcu/st/cmsis_device_wb': ['https://github.com/STMicroelectronics/cmsis_device_wb.git',
'cda2cb9fc4a5232ab18efece0bb06b0b60910083',
'stm32wb'],
'hw/mcu/st/cmsis-device-wba': ['https://github.com/STMicroelectronics/cmsis-device-wba.git',
'647d8522e5fd15049e9a1cc30ed19d85e5911eaf',
'stm32wba'],
'hw/mcu/st/stm32c5xx-dfp': ['https://github.com/STMicroelectronics/stm32c5xx-dfp.git',
'6d0940882511d9430f83af9bd3da6bcb77f79239',
'stm32c5'],
'hw/mcu/st/stm32-mfxstm32l152': ['https://github.com/STMicroelectronics/stm32-mfxstm32l152.git',
'7f4389efee9c6a655b55e5df3fceef5586b35f9b',
'stm32h7'],
'hw/mcu/st/stm32-tcpp0203': ['https://github.com/STMicroelectronics/stm32-tcpp0203.git',
'9918655bff176ac3046ccf378b5c7bbbc6a38d15',
'stm32h5 stm32h7rs stm32n6'],
'hw/mcu/st/stm32c0xx_hal_driver': ['https://github.com/STMicroelectronics/stm32c0xx_hal_driver.git',
'c283b143bef6bdaacf64240ee6f15eb61dad6125',
'stm32c0'],
'hw/mcu/st/stm32f0xx_hal_driver': ['https://github.com/STMicroelectronics/stm32f0xx_hal_driver.git',
'94399697cb5eeaf8511b81b7f50dc62f0a5a3f6c',
'stm32f0'],
'hw/mcu/st/stm32f1xx_hal_driver': ['https://github.com/STMicroelectronics/stm32f1xx_hal_driver.git',
'18074e3e5ecad0b380a5cf5a9131fe4b5ed1b2b7',
'stm32f1'],
'hw/mcu/st/stm32f2xx_hal_driver': ['https://github.com/STMicroelectronics/stm32f2xx_hal_driver.git',
'ae7b47fe41cf75ccaf65cbf8ee8749b18ba0e0f3',
'stm32f2'],
'hw/mcu/st/stm32f3xx_hal_driver': ['https://github.com/STMicroelectronics/stm32f3xx_hal_driver.git',
'e098c8c8ce6f426bcee7db3a37c0932ea881eb0b',
'stm32f3'],
'hw/mcu/st/stm32f4xx_hal_driver': ['https://github.com/STMicroelectronics/stm32f4xx_hal_driver.git',
'b6f0ed3829f3829eb358a2e7417d80bba1a42db7',
'stm32f4'],
'hw/mcu/st/stm32f7xx_hal_driver': ['https://github.com/STMicroelectronics/stm32f7xx_hal_driver.git',
'e1446fa12ffda80ea1016faf349e45b2047fff12',
'stm32f7'],
'hw/mcu/st/stm32g0xx_hal_driver': ['https://github.com/STMicroelectronics/stm32g0xx_hal_driver.git',
'a248a9e484d58943b46c68f6c49b4b276778bd59',
'stm32g0'],
'hw/mcu/st/stm32g4xx_hal_driver': ['https://github.com/STMicroelectronics/stm32g4xx_hal_driver.git',
'10138a41749ea62d53ecab65b2bc2a950acc04d2',
'stm32g4'],
'hw/mcu/st/stm32h7xx_hal_driver': ['https://github.com/STMicroelectronics/stm32h7xx_hal_driver.git',
'dbfb749f229e1aa89e50b54229ca87766e180d2d',
'stm32h7'],
'hw/mcu/st/stm32h7rsxx_hal_driver': ['https://github.com/STMicroelectronics/stm32h7rsxx-hal-driver.git',
'9e83b95ae0f70faa067eddce2da617d180937f9b',
'stm32h7rs'],
'hw/mcu/st/stm32h5xx_hal_driver': ['https://github.com/STMicroelectronics/stm32h5xx_hal_driver.git',
'3c84eaa6000ab620be01afbcfba2735389afe09b',
'stm32h5'],
'hw/mcu/st/stm32l0xx_hal_driver': ['https://github.com/STMicroelectronics/stm32l0xx_hal_driver.git',
'65da4cd8a10ad859ec8d9cd71f3f6c50735bd473',
'stm32l0'],
'hw/mcu/st/stm32l1xx_hal_driver': ['https://github.com/STMicroelectronics/stm32l1xx_hal_driver.git',
'54f0b7568ce2acb33d090c70c897ee32229c1d32',
'stm32l1'],
'hw/mcu/st/stm32l4xx_hal_driver': ['https://github.com/STMicroelectronics/stm32l4xx_hal_driver.git',
'3e039bbf62f54bbd834d578185521cff80596efe',
'stm32l4'],
'hw/mcu/st/stm32l5xx_hal_driver': ['https://github.com/STMicroelectronics/stm32l5xx_hal_driver.git',
'3340b9a597bcf75cc173345a90a74aa2a4a37510',
'stm32l5'],
'hw/mcu/st/stm32n6xx_hal_driver': ['https://github.com/STMicroelectronics/stm32n6xx-hal-driver.git',
'bc6c41f8f67d61b47af26695d0bf67762a000666',
'stm32n6'],
'hw/mcu/st/stm32u0xx_hal_driver': ['https://github.com/STMicroelectronics/stm32u0xx-hal-driver.git',
'cbfb5ac654256445237fd32b3587ac6a238d24f1',
'stm32u0'],
'hw/mcu/st/stm32u5xx_hal_driver': ['https://github.com/STMicroelectronics/stm32u5xx_hal_driver.git',
'2c5e2568fbdb1900a13ca3b2901fdd302cac3444',
'stm32u5'],
'hw/mcu/st/stm32wbxx_hal_driver': ['https://github.com/STMicroelectronics/stm32wbxx_hal_driver.git',
'd60dd46996876506f1d2e9abd6b1cc110c8004cd',
'stm32wb'],
'hw/mcu/st/stm32wbaxx_hal_driver': ['https://github.com/STMicroelectronics/stm32wbaxx_hal_driver.git',
'9442fbb71f855ff2e64fbf662b7726beba511a24',
'stm32wba'],
'hw/mcu/st/stm32c5xx-drivers': ['https://github.com/STMicroelectronics/stm32c5xx-drivers.git',
'79b901285a7efeaf87c4c25db81d24cb5d8c9465',
'stm32c5'],
'hw/mcu/ti': ['https://github.com/hathach/ti_driver.git',
'083944907e7d08fcb1f614b47598ce45935b8da1',
'msp430 msp432e4 tm4c'],
'hw/mcu/wch/ch32v103': ['https://github.com/openwch/ch32v103.git',
'7578cae0b21f86dd053a1f781b2fc6ab99d0ec17',
'ch32v10x'],
'hw/mcu/wch/ch32v20x': ['https://github.com/openwch/ch32v20x.git',
'c4c38f507e258a4e69b059ccc2dc27dde33cea1b',
'ch32v20x'],
'hw/mcu/wch/ch32v307': ['https://github.com/openwch/ch32v307.git',
'184f21b852cb95eed58e86e901837bc9fff68775',
'ch32v30x'],
'hw/mcu/wch/ch32f20x': ['https://github.com/openwch/ch32f20x.git',
'77c4095087e5ed2c548ec9058e655d0b8757663b',
'ch32f20x'],
'hw/mcu/wch/ch583': ['https://github.com/openwch/ch583.git',
'bd508ad7ceed48377619837051412a651952857f',
'ch583'],
'hw/mcu/artery/at32f403a_407': ['https://github.com/ArteryTek/AT32F403A_407_Firmware_Library.git',
'f2cb360c3d28fada76b374308b8c4c61d37a090b',
'at32f403a_407'],
'hw/mcu/artery/at32f415': ['https://github.com/ArteryTek/AT32F415_Firmware_Library.git',
'716f545aa1290ff144ccf023a8e797b951e1bc8e',
'at32f415'],
'hw/mcu/artery/at32f435_437': ['https://github.com/ArteryTek/AT32F435_437_Firmware_Library.git',
'25439cc6650a8ae0345934e8707a5f38c7ae41f8',
'at32f435_437'],
'hw/mcu/artery/at32f423': ['https://github.com/ArteryTek/AT32F423_Firmware_Library.git',
'2afa7f12852e57a9e8aab3a892c641e1a8635a18',
'at32f423'],
'hw/mcu/artery/at32f402_405': ['https://github.com/ArteryTek/AT32F402_405_Firmware_Library.git',
'4424515c2663e82438654e0947695295df2abdfe',
'at32f402_405'],
'hw/mcu/artery/at32f425': ['https://github.com/ArteryTek/AT32F425_Firmware_Library.git',
'620233e1357d5c1b7e2bde6b9dd5196822b91817',
'at32f425'],
'hw/mcu/artery/at32f413': ['https://github.com/ArteryTek/AT32F413_Firmware_Library.git',
'f6fe62dfec9fd40c5b63d92fc5ef2c2b5e77a450',
'at32f413'],
'hw/mcu/artery/at32f45x': ['https://github.com/ArteryTek/AT32F45x_Firmware_Library.git',
'3d4a1b38be8ebac292e2350ca53bc4bfa4430233',
'at32f45x'],
'hw/mcu/hpmicro/hpm_sdk': ['https://github.com/hpmicro/hpm_sdk',
'8d2af741ecc4aaa82d7ee395dc1ce25d7070c3ff',
'hpmicro'],
'lib/CMSIS_5': ['https://github.com/ARM-software/CMSIS_5.git',
'2b7495b8535bdcb306dac29b9ded4cfb679d7e5c',
'kinetis_k kinetis_kl lpc54 rw61x mm32 msp432e4 nrf samd2x_l2x '
'lpc11 lpc13 lpc15 lpc17 lpc18 lpc40 lpc43 '
'stm32c0 stm32f0 stm32f1 stm32f2 stm32f3 stm32f4 stm32f7 stm32g0 stm32g4 stm32h5 '
'stm32h7 stm32h7rs stm32l0 stm32l1 stm32l4 stm32l5 stm32u0 stm32u5 stm32wb stm32wba '
'sam3x samd11 samd21 samd2x_l2x samd51 samd5x_e5x same5x same7x samg '
'tm4c '],
'lib/CMSIS_6': ['https://github.com/ARM-software/CMSIS_6.git',
'6f0a58d01aa9bd2feba212097f9afe7acd991d52',
'imxrt kinetis_k32l ra stm32n6 lpc51 lpc55 mcx stm32c5'],
'lib/sct_neopixel': ['https://github.com/gsteiert/sct_neopixel.git',
'e73e04ca63495672d955f9268e003cffe168fcd8',
'lpc55'],
}
# Files to remove after cloning to avoid conflicts with TinyUSB's custom versions
deps_remove_files = {
'lib/fatfs': ['source/ffconf.h'],
}
# combined 2 deps
deps_all = {**deps_mandatory, **deps_optional}
# TOP is tinyusb root dir
TOP = Path(__file__).parent.parent.resolve()
def run_cmd(cmd):
r = subprocess.run(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
title = f'Command Error: {cmd}'
if r.returncode != 0:
print(title)
print(r.stdout.decode("utf-8"))
return r
def get_a_dep(d):
if d not in deps_all.keys():
print('{} is not found in dependency list')
return 1
url = deps_all[d][0]
commit = deps_all[d][1]
families = deps_all[d][2]
print(f'cloning {d} with {url}')
p = Path(TOP / d)
git_cmd = f"git -C {p}"
# Init git deps if not existed
if not p.exists():
p.mkdir(parents=True)
run_cmd(f"{git_cmd} init")
run_cmd(f"{git_cmd} remote add origin {url}")
head = None
else:
# Check if commit is already fetched
result = run_cmd(f"{git_cmd} rev-parse HEAD")
head = result.stdout.decode("utf-8").splitlines()[0]
run_cmd(f"{git_cmd} reset --hard")
if commit != head:
run_cmd(f"{git_cmd} fetch --depth 1 origin {commit}")
run_cmd(f"{git_cmd} checkout FETCH_HEAD")
# Remove files that conflict with TinyUSB's custom versions
if d in deps_remove_files:
for f in deps_remove_files[d]:
fp = p / f
if fp.exists():
fp.unlink()
return 0
def find_family(board):
bsp_dir = Path(TOP / "hw/bsp")
for family_dir in bsp_dir.iterdir():
if family_dir.is_dir():
board_dir = family_dir / 'boards' / board
if board_dir.exists():
return family_dir.name
return None
def main():
parser = argparse.ArgumentParser()
parser.add_argument('families', nargs='*', default=[], help='Families to fetch')
parser.add_argument('-b', '--board', action='append', default=[], help='Boards to fetch')
parser.add_argument('-D', '--define', action='append', default=[], help='Have no effect')
parser.add_argument('-f1', '--build-flags-on', action='append', default=[], help='Have no effect')
parser.add_argument('--build-name', default=None, help='Have no effect')
parser.add_argument('--cflag', action='append', default=[], help='Have no effect')
args = parser.parse_args()
families = args.families
boards = args.board
status = 0
deps = []
if 'all' in families:
deps.extend(deps_optional.keys())
else:
families = list(families)
if boards is not None:
for b in boards:
f = find_family(b)
if f is not None:
families.append(f)
for f in families:
for d in deps_optional:
if d not in deps and f in deps_optional[d][2].split():
deps.append(d)
if len(deps) == 0:
print('WARN: no additional dependencies found for given boards or families')
deps.extend(deps_mandatory.keys())
with Pool() as pool:
status = sum(pool.map(get_a_dep, deps))
return status
if __name__ == "__main__":
sys.exit(main())
+87
View File
@@ -0,0 +1,87 @@
#!/usr/bin/env python3
import os
import sys
import xml.dom.minidom as XML
import glob
def Main():
# Read base configuration
base = ""
with open("iar_template.ipcf") as f:
base = f.read()
# Enumerate all device/host examples
dir_1 = os.listdir("../examples")
for dir_2 in dir_1:
if os.path.isdir("../examples/{}".format(dir_2)):
print(dir_2)
examples = os.listdir("../examples/{}".format(dir_2))
for example in examples:
if os.path.isdir("../examples/{}/{}".format(dir_2, example)):
print("../examples/{}/{}".format(dir_2, example))
conf = XML.parseString(base)
files = conf.getElementsByTagName("files")[0]
inc = conf.getElementsByTagName("includePath")[0]
# Add bsp inc
path = conf.createElement('path')
path_txt = conf.createTextNode("$TUSB_DIR$/hw")
path.appendChild(path_txt)
inc.appendChild(path)
# Add board.c/.h
grp = conf.createElement('group')
grp.setAttribute("name", "bsp")
path = conf.createElement('path')
path_txt = conf.createTextNode("$TUSB_DIR$/hw/bsp/board.c")
path.appendChild(path_txt)
grp.appendChild(path)
files.appendChild(grp)
# Add example's .c/.h
grp = conf.createElement('group')
grp.setAttribute("name", "example")
for file in os.listdir("../examples/{}/{}/src".format(dir_2, example)):
if file.endswith(".c") or file.endswith(".h"):
path = conf.createElement('path')
path.setAttribute("copyTo", "$PROJ_DIR$/{}".format(file))
path_txt = conf.createTextNode("$TUSB_DIR$/examples/{0}/{1}/src/{2}".format(dir_2, example, file))
path.appendChild(path_txt)
grp.appendChild(path)
files.appendChild(grp)
cfg_str = conf.toprettyxml()
cfg_str = '\n'.join([s for s in cfg_str.splitlines() if s.strip()])
#print(cfg_str)
with open("../examples/{0}/{1}/iar_{1}.ipcf".format(dir_2, example), 'w') as f:
f.write(cfg_str)
def ListPath(path, blacklist=[]):
# Get all .c files
files = glob.glob(f'../{path}/**/*.c', recursive=True)
files.extend(glob.glob(f'../{path}/**/*.h', recursive=True))
# Filter
files = [x for x in files if all(y not in x for y in blacklist)]
# Get common dir list
dirs = []
for file in files:
dir = os.path.dirname(file)
if dir not in dirs:
dirs.append(dir)
# Print .c grouped by dir
for dir in dirs:
print('<group name="' + dir.replace('../', '').replace('\\','/') + '">')
for file in files:
if os.path.dirname(file) == dir:
print(' <path>$TUSB_DIR$/' + file.replace('../','').replace('\\','/')+'</path>')
print('</group>')
def List():
ListPath('src', [ 'template.c' ])
ListPath('lib/SEGGER_RTT')
if __name__ == "__main__":
if os.path.dirname(os.getcwd()) != 'tools':
os.chdir('tools')
if (len(sys.argv) > 1):
if (sys.argv[1] == 'l'):
List()
else:
Main()
+288
View File
@@ -0,0 +1,288 @@
<?xml version="1.0"?>
<iarProjectConnection name="tinyusb" oneShot="true">
<includePath>
<path>$TUSB_DIR$/src</path>
<path>$TUSB_DIR$/lib/SEGGER_RTT/RTT</path>
<path>$TUSB_DIR$/lib/SEGGER_RTT/Config</path>
<path>$PROJ_DIR$</path>
</includePath>
<files>
<group name="src">
<path>$TUSB_DIR$/src/tusb.c</path>
<path>$TUSB_DIR$/src/tusb.h</path>
<path>$TUSB_DIR$/src/tusb_option.h</path>
</group>
<group name="src/class/audio">
<path>$TUSB_DIR$/src/class/audio/audio_device.c</path>
<path>$TUSB_DIR$/src/class/audio/audio.h</path>
<path>$TUSB_DIR$/src/class/audio/audio_device.h</path>
</group>
<group name="src/class/bth">
<path>$TUSB_DIR$/src/class/bth/bth_device.c</path>
<path>$TUSB_DIR$/src/class/bth/bth_device.h</path>
</group>
<group name="src/class/cdc">
<path>$TUSB_DIR$/src/class/cdc/cdc_device.c</path>
<path>$TUSB_DIR$/src/class/cdc/cdc_host.c</path>
<path>$TUSB_DIR$/src/class/cdc/cdc_rndis_host.c</path>
<path>$TUSB_DIR$/src/class/cdc/cdc.h</path>
<path>$TUSB_DIR$/src/class/cdc/cdc_device.h</path>
<path>$TUSB_DIR$/src/class/cdc/cdc_host.h</path>
<path>$TUSB_DIR$/src/class/cdc/cdc_rndis.h</path>
<path>$TUSB_DIR$/src/class/cdc/cdc_rndis_host.h</path>
</group>
<group name="src/class/dfu">
<path>$TUSB_DIR$/src/class/dfu/dfu_device.c</path>
<path>$TUSB_DIR$/src/class/dfu/dfu_rt_device.c</path>
<path>$TUSB_DIR$/src/class/dfu/dfu.h</path>
<path>$TUSB_DIR$/src/class/dfu/dfu_device.h</path>
<path>$TUSB_DIR$/src/class/dfu/dfu_rt_device.h</path>
</group>
<group name="src/class/hid">
<path>$TUSB_DIR$/src/class/hid/hid_device.c</path>
<path>$TUSB_DIR$/src/class/hid/hid_host.c</path>
<path>$TUSB_DIR$/src/class/hid/hid.h</path>
<path>$TUSB_DIR$/src/class/hid/hid_device.h</path>
<path>$TUSB_DIR$/src/class/hid/hid_host.h</path>
</group>
<group name="src/class/midi">
<path>$TUSB_DIR$/src/class/midi/midi_device.c</path>
<path>$TUSB_DIR$/src/class/midi/midi.h</path>
<path>$TUSB_DIR$/src/class/midi/midi_device.h</path>
</group>
<group name="src/class/msc">
<path>$TUSB_DIR$/src/class/msc/msc_device.c</path>
<path>$TUSB_DIR$/src/class/msc/msc_host.c</path>
<path>$TUSB_DIR$/src/class/msc/msc.h</path>
<path>$TUSB_DIR$/src/class/msc/msc_device.h</path>
<path>$TUSB_DIR$/src/class/msc/msc_host.h</path>
</group>
<group name="src/class/mtp">
<path>$TUSB_DIR$/src/class/mtp/mtp_device.c</path>
<path>$TUSB_DIR$/src/class/mtp/mtp.h</path>
<path>$TUSB_DIR$/src/class/mtp/mtp_device.h</path>
</group>
<group name="src/class/net">
<path>$TUSB_DIR$/src/class/net/ecm_rndis_device.c</path>
<path>$TUSB_DIR$/src/class/net/ncm_device.c</path>
<path>$TUSB_DIR$/src/class/net/ncm.h</path>
<path>$TUSB_DIR$/src/class/net/net_device.h</path>
</group>
<group name="src/class/printer">
<path>$TUSB_DIR$/src/class/printer/printer_device.c</path>
<path>$TUSB_DIR$/src/class/printer/printer.h</path>
<path>$TUSB_DIR$/src/class/printer/printer_device.h</path>
</group>
<group name="src/class/usbtmc">
<path>$TUSB_DIR$/src/class/usbtmc/usbtmc_device.c</path>
<path>$TUSB_DIR$/src/class/usbtmc/usbtmc.h</path>
<path>$TUSB_DIR$/src/class/usbtmc/usbtmc_device.h</path>
</group>
<group name="src/class/vendor">
<path>$TUSB_DIR$/src/class/vendor/vendor_device.c</path>
<path>$TUSB_DIR$/src/class/vendor/vendor_host.c</path>
<path>$TUSB_DIR$/src/class/vendor/vendor_device.h</path>
<path>$TUSB_DIR$/src/class/vendor/vendor_host.h</path>
</group>
<group name="src/class/video">
<path>$TUSB_DIR$/src/class/video/video_device.c</path>
<path>$TUSB_DIR$/src/class/video/video.h</path>
<path>$TUSB_DIR$/src/class/video/video_device.h</path>
</group>
<group name="src/common">
<path>$TUSB_DIR$/src/common/tusb_fifo.c</path>
<path>$TUSB_DIR$/src/common/tusb_common.h</path>
<path>$TUSB_DIR$/src/common/tusb_compiler.h</path>
<path>$TUSB_DIR$/src/common/tusb_debug.h</path>
<path>$TUSB_DIR$/src/common/tusb_fifo.h</path>
<path>$TUSB_DIR$/src/common/tusb_mcu.h</path>
<path>$TUSB_DIR$/src/common/tusb_private.h</path>
<path>$TUSB_DIR$/src/common/tusb_types.h</path>
<path>$TUSB_DIR$/src/common/tusb_verify.h</path>
</group>
<group name="src/device">
<path>$TUSB_DIR$/src/device/usbd.c</path>
<path>$TUSB_DIR$/src/device/usbd_control.c</path>
<path>$TUSB_DIR$/src/device/dcd.h</path>
<path>$TUSB_DIR$/src/device/usbd.h</path>
<path>$TUSB_DIR$/src/device/usbd_pvt.h</path>
</group>
<group name="src/host">
<path>$TUSB_DIR$/src/host/hub.c</path>
<path>$TUSB_DIR$/src/host/usbh.c</path>
<path>$TUSB_DIR$/src/host/hcd.h</path>
<path>$TUSB_DIR$/src/host/hub.h</path>
<path>$TUSB_DIR$/src/host/usbh.h</path>
<path>$TUSB_DIR$/src/host/usbh_pvt.h</path>
</group>
<group name="src/portable/analog/max3421">
<path>$TUSB_DIR$/src/portable/analog/max3421/hcd_max3421.c</path>
</group>
<group name="src/portable/bridgetek/ft9xx">
<path>$TUSB_DIR$/src/portable/bridgetek/ft9xx/dcd_ft9xx.c</path>
</group>
<group name="src/portable/chipidea/ci_fs">
<path>$TUSB_DIR$/src/portable/chipidea/ci_fs/dcd_ci_fs.c</path>
<path>$TUSB_DIR$/src/portable/chipidea/ci_fs/ci_fs_kinetis.h</path>
<path>$TUSB_DIR$/src/portable/chipidea/ci_fs/ci_fs_mcx.h</path>
<path>$TUSB_DIR$/src/portable/chipidea/ci_fs/ci_fs_type.h</path>
</group>
<group name="src/portable/chipidea/ci_hs">
<path>$TUSB_DIR$/src/portable/chipidea/ci_hs/dcd_ci_hs.c</path>
<path>$TUSB_DIR$/src/portable/chipidea/ci_hs/hcd_ci_hs.c</path>
<path>$TUSB_DIR$/src/portable/chipidea/ci_hs/ci_hs_imxrt.h</path>
<path>$TUSB_DIR$/src/portable/chipidea/ci_hs/ci_hs_lpc18_43.h</path>
<path>$TUSB_DIR$/src/portable/chipidea/ci_hs/ci_hs_mcx.h</path>
<path>$TUSB_DIR$/src/portable/chipidea/ci_hs/ci_hs_type.h</path>
</group>
<group name="src/portable/dialog/da146xx">
<path>$TUSB_DIR$/src/portable/dialog/da146xx/dcd_da146xx.c</path>
</group>
<group name="src/portable/ehci">
<path>$TUSB_DIR$/src/portable/ehci/ehci.c</path>
<path>$TUSB_DIR$/src/portable/ehci/ehci.h</path>
<path>$TUSB_DIR$/src/portable/ehci/ehci_api.h</path>
</group>
<group name="src/portable/mentor/musb">
<path>$TUSB_DIR$/src/portable/mentor/musb/dcd_musb.c</path>
<path>$TUSB_DIR$/src/portable/mentor/musb/hcd_musb.c</path>
<path>$TUSB_DIR$/src/portable/mentor/musb/musb_msp432e.h</path>
<path>$TUSB_DIR$/src/portable/mentor/musb/musb_tm4c.h</path>
<path>$TUSB_DIR$/src/portable/mentor/musb/musb_type.h</path>
</group>
<group name="src/portable/microchip/pic">
<path>$TUSB_DIR$/src/portable/microchip/pic/dcd_pic.c</path>
</group>
<group name="src/portable/microchip/pic32mz">
<path>$TUSB_DIR$/src/portable/microchip/pic32mz/dcd_pic32mz.c</path>
<path>$TUSB_DIR$/src/portable/microchip/pic32mz/usbhs_registers.h</path>
</group>
<group name="src/portable/microchip/samd">
<path>$TUSB_DIR$/src/portable/microchip/samd/dcd_samd.c</path>
</group>
<group name="src/portable/microchip/samg">
<path>$TUSB_DIR$/src/portable/microchip/samg/dcd_samg.c</path>
</group>
<group name="src/portable/microchip/samx7x">
<path>$TUSB_DIR$/src/portable/microchip/samx7x/dcd_samx7x.c</path>
<path>$TUSB_DIR$/src/portable/microchip/samx7x/common_usb_regs.h</path>
</group>
<group name="src/portable/mindmotion/mm32">
<path>$TUSB_DIR$/src/portable/mindmotion/mm32/dcd_mm32f327x_otg.c</path>
</group>
<group name="src/portable/nordic/nrf5x">
<path>$TUSB_DIR$/src/portable/nordic/nrf5x/dcd_nrf5x.c</path>
</group>
<group name="src/portable/nuvoton/nuc120">
<path>$TUSB_DIR$/src/portable/nuvoton/nuc120/dcd_nuc120.c</path>
</group>
<group name="src/portable/nuvoton/nuc121">
<path>$TUSB_DIR$/src/portable/nuvoton/nuc121/dcd_nuc121.c</path>
</group>
<group name="src/portable/nuvoton/nuc505">
<path>$TUSB_DIR$/src/portable/nuvoton/nuc505/dcd_nuc505.c</path>
</group>
<group name="src/portable/nxp/khci">
<path>$TUSB_DIR$/src/portable/nxp/khci/dcd_khci.c</path>
<path>$TUSB_DIR$/src/portable/nxp/khci/hcd_khci.c</path>
</group>
<group name="src/portable/nxp/lpc17_40">
<path>$TUSB_DIR$/src/portable/nxp/lpc17_40/dcd_lpc17_40.c</path>
<path>$TUSB_DIR$/src/portable/nxp/lpc17_40/hcd_lpc17_40.c</path>
<path>$TUSB_DIR$/src/portable/nxp/lpc17_40/dcd_lpc17_40.h</path>
</group>
<group name="src/portable/nxp/lpc_ip3511">
<path>$TUSB_DIR$/src/portable/nxp/lpc_ip3511/dcd_lpc_ip3511.c</path>
</group>
<group name="src/portable/ohci">
<path>$TUSB_DIR$/src/portable/ohci/ohci.c</path>
<path>$TUSB_DIR$/src/portable/ohci/ohci.h</path>
</group>
<group name="src/portable/raspberrypi/pio_usb">
<path>$TUSB_DIR$/src/portable/raspberrypi/pio_usb/dcd_pio_usb.c</path>
<path>$TUSB_DIR$/src/portable/raspberrypi/pio_usb/hcd_pio_usb.c</path>
</group>
<group name="src/portable/raspberrypi/rp2040">
<path>$TUSB_DIR$/src/portable/raspberrypi/rp2040/dcd_rp2040.c</path>
<path>$TUSB_DIR$/src/portable/raspberrypi/rp2040/hcd_rp2040.c</path>
<path>$TUSB_DIR$/src/portable/raspberrypi/rp2040/rp2040_usb.c</path>
<path>$TUSB_DIR$/src/portable/raspberrypi/rp2040/rp2040_usb.h</path>
</group>
<group name="src/portable/renesas/rusb2">
<path>$TUSB_DIR$/src/portable/renesas/rusb2/dcd_rusb2.c</path>
<path>$TUSB_DIR$/src/portable/renesas/rusb2/hcd_rusb2.c</path>
<path>$TUSB_DIR$/src/portable/renesas/rusb2/rusb2_common.c</path>
<path>$TUSB_DIR$/src/portable/renesas/rusb2/rusb2_ra.h</path>
<path>$TUSB_DIR$/src/portable/renesas/rusb2/rusb2_rx.h</path>
<path>$TUSB_DIR$/src/portable/renesas/rusb2/rusb2_type.h</path>
</group>
<group name="src/portable/sony/cxd56">
<path>$TUSB_DIR$/src/portable/sony/cxd56/dcd_cxd56.c</path>
</group>
<group name="src/portable/st/stm32_fsdev">
<path>$TUSB_DIR$/src/portable/st/stm32_fsdev/dcd_stm32_fsdev.c</path>
<path>$TUSB_DIR$/src/portable/st/stm32_fsdev/hcd_stm32_fsdev.c</path>
<path>$TUSB_DIR$/src/portable/st/stm32_fsdev/fsdev_common.c</path>
<path>$TUSB_DIR$/src/portable/st/stm32_fsdev/fsdev_common.h</path>
</group>
<group name="src/portable/st/typec">
<path>$TUSB_DIR$/src/portable/st/typec/typec_stm32.c</path>
</group>
<group name="src/portable/sunxi">
<path>$TUSB_DIR$/src/portable/sunxi/dcd_sunxi_musb.c</path>
<path>$TUSB_DIR$/src/portable/sunxi/musb_def.h</path>
</group>
<group name="src/portable/synopsys/dwc2">
<path>$TUSB_DIR$/src/portable/synopsys/dwc2/dcd_dwc2.c</path>
<path>$TUSB_DIR$/src/portable/synopsys/dwc2/dwc2_bcm.h</path>
<path>$TUSB_DIR$/src/portable/synopsys/dwc2/dwc2_efm32.h</path>
<path>$TUSB_DIR$/src/portable/synopsys/dwc2/dwc2_esp32.h</path>
<path>$TUSB_DIR$/src/portable/synopsys/dwc2/dwc2_gd32.h</path>
<path>$TUSB_DIR$/src/portable/synopsys/dwc2/dwc2_stm32.h</path>
<path>$TUSB_DIR$/src/portable/synopsys/dwc2/dwc2_type.h</path>
<path>$TUSB_DIR$/src/portable/synopsys/dwc2/dwc2_xmc.h</path>
</group>
<group name="src/portable/ti/msp430x5xx">
<path>$TUSB_DIR$/src/portable/ti/msp430x5xx/dcd_msp430x5xx.c</path>
</group>
<group name="src/portable/valentyusb/eptri">
<path>$TUSB_DIR$/src/portable/valentyusb/eptri/dcd_eptri.c</path>
<path>$TUSB_DIR$/src/portable/valentyusb/eptri/dcd_eptri.h</path>
</group>
<group name="src/portable/wch">
<path>$TUSB_DIR$/src/portable/wch/dcd_ch32_usbfs.c</path>
<path>$TUSB_DIR$/src/portable/wch/dcd_ch32_usbhs.c</path>
<path>$TUSB_DIR$/src/portable/wch/ch32_usbhs_reg.h</path>
</group>
<group name="src/typec">
<path>$TUSB_DIR$/src/typec/usbc.c</path>
<path>$TUSB_DIR$/src/typec/pd_types.h</path>
<path>$TUSB_DIR$/src/typec/tcd.h</path>
<path>$TUSB_DIR$/src/typec/usbc.h</path>
</group>
<group name="src/class/cdc/serial">
<path>$TUSB_DIR$/src/class/cdc/serial/ch34x.h</path>
<path>$TUSB_DIR$/src/class/cdc/serial/cp210x.h</path>
<path>$TUSB_DIR$/src/class/cdc/serial/ftdi_sio.h</path>
</group>
<group name="src/osal">
<path>$TUSB_DIR$/src/osal/osal.h</path>
<path>$TUSB_DIR$/src/osal/osal_freertos.h</path>
<path>$TUSB_DIR$/src/osal/osal_mynewt.h</path>
<path>$TUSB_DIR$/src/osal/osal_none.h</path>
<path>$TUSB_DIR$/src/osal/osal_pico.h</path>
<path>$TUSB_DIR$/src/osal/osal_rtthread.h</path>
<path>$TUSB_DIR$/src/osal/osal_rtx4.h</path>
</group>
<group name="lib/SEGGER_RTT/RTT">
<path>$TUSB_DIR$/lib/SEGGER_RTT/RTT/SEGGER_RTT.c</path>
<path>$TUSB_DIR$/lib/SEGGER_RTT/RTT/SEGGER_RTT_printf.c</path>
<path>$TUSB_DIR$/lib/SEGGER_RTT/RTT/SEGGER_RTT.h</path>
</group>
<group name="lib/SEGGER_RTT/Config">
<path>$TUSB_DIR$/lib/SEGGER_RTT/Config/SEGGER_RTT_Conf.h</path>
</group>
</files>
</iarProjectConnection>
+69
View File
@@ -0,0 +1,69 @@
#!/usr/bin/env python3
import re
import gen_doc
import gen_presets
version = '0.21.0'
print('version {}'.format(version))
ver_id = version.split('.')
###################
# src/tusb_option.h
###################
f_option_h = 'src/tusb_option.h'
with open(f_option_h) as f:
fdata = f.read()
fdata = re.sub(r'(#define TUSB_VERSION_MAJOR *) \d+', r"\1 {}".format(ver_id[0]), fdata)
fdata = re.sub(r'(#define TUSB_VERSION_MINOR *) \d+', r"\1 {}".format(ver_id[1]), fdata)
fdata = re.sub(r'(#define TUSB_VERSION_REVISION *) \d+', r"\1 {}".format(ver_id[2]), fdata)
# Write the file out again
with open(f_option_h, 'w') as f:
f.write(fdata)
###################
# repository.yml
###################
f_repository_yml = 'repository.yml'
with open(f_repository_yml) as f:
fdata = f.read()
if fdata.find(version) < 0:
fdata = re.sub(r'("0-latest"): "\d+\.\d+\.\d+"', r'"{}": "{}"\n \1: "{}"'.format(version, version, version), fdata)
with open(f_repository_yml, 'w') as f:
f.write(fdata)
###################
# library.json
###################
f_library_json = 'library.json'
with open(f_library_json) as f:
fdata = f.read()
fdata = re.sub(r'( {4}"version":) "\d+\.\d+\.\d+"', rf'\1 "{version}"', fdata)
with open(f_library_json, 'w') as f:
f.write(fdata)
###################
# sonar-project.properties
###################
f_sonar_properties = 'sonar-project.properties'
with open(f_sonar_properties) as f:
fdata = f.read()
fdata = re.sub(r'(sonar\.projectVersion=)\d+\.\d+\.\d+', r'\g<1>{}'.format(version), fdata)
with open(f_sonar_properties, 'w') as f:
f.write(fdata)
# gen docs
gen_doc.gen_deps_doc()
gen_doc.gen_boards_doc()
# gen presets
gen_presets.main()
###################
# docs/changelog/
###################
print("Add docs/changelog/{}.md and list it at the top of docs/changelog/index.rst".format(version))
+662
View File
@@ -0,0 +1,662 @@
#!/usr/bin/env python3
"""Calculate average sizes from bloaty CSV or TinyUSB metrics JSON outputs."""
import argparse
import csv
import glob
import io
import json
import os
import sys
from collections import defaultdict
def expand_files(file_patterns):
"""Expand file patterns (globs) to list of files.
Args:
file_patterns: List of file paths or glob patterns
Returns:
List of expanded file paths
"""
expanded = []
for pattern in file_patterns:
if '*' in pattern or '?' in pattern:
expanded.extend(glob.glob(pattern))
else:
expanded.append(pattern)
return expanded
def parse_bloaty_csv(csv_text, filters=None):
"""Parse bloaty CSV text and return normalized JSON data structure."""
filters = filters or []
reader = csv.DictReader(io.StringIO(csv_text))
size_by_unit = defaultdict(int)
symbols_by_unit: dict[str, defaultdict[str, int]] = defaultdict(lambda: defaultdict(int))
sections_by_unit: dict[str, defaultdict[str, int]] = defaultdict(lambda: defaultdict(int))
for row in reader:
compile_unit = row.get("compileunits") or row.get("compileunit") or row.get("path")
if compile_unit is None:
continue
if str(compile_unit).upper() == "TOTAL":
continue
if filters and not any(filt in compile_unit for filt in filters):
continue
try:
vmsize = int(row.get("vmsize", 0))
except ValueError:
continue
size_by_unit[compile_unit] += vmsize
symbol_name = row.get("symbols", "")
if symbol_name:
symbols_by_unit[compile_unit][symbol_name] += vmsize
section_name = row.get("sections") or row.get("section")
if section_name and vmsize:
sections_by_unit[compile_unit][section_name] += vmsize
files = []
for unit_path, total_size in size_by_unit.items():
symbols = [
{"name": sym, "size": sz}
for sym, sz in sorted(symbols_by_unit[unit_path].items(), key=lambda x: x[1], reverse=True)
]
sections = {sec: sz for sec, sz in sections_by_unit[unit_path].items() if sz}
files.append(
{
"file": os.path.basename(unit_path) or unit_path,
"path": unit_path,
"size": total_size,
"symbols": symbols,
"sections": sections,
}
)
total_all = sum(size_by_unit.values())
return {"files": files, "TOTAL": total_all}
def combine_files(input_files, filters=None):
"""Combine multiple metrics inputs (bloaty CSV or metrics JSON) into a single data set."""
filters = filters or []
all_json_data = {"file_list": [], "data": []}
for fin in input_files:
if not os.path.exists(fin):
print(f"Warning: {fin} not found, skipping", file=sys.stderr)
continue
try:
if fin.endswith(".json"):
with open(fin, "r", encoding="utf-8") as f:
json_data = json.load(f)
if filters:
json_data["files"] = [
f
for f in json_data.get("files", [])
if f.get("path") and any(filt in f["path"] for filt in filters)
]
elif fin.endswith(".csv"):
with open(fin, "r", encoding="utf-8") as f:
csv_text = f.read()
json_data = parse_bloaty_csv(csv_text, filters)
else:
if fin.endswith(".elf"):
print(f"Warning: {fin} is an ELF; please run bloaty with --csv output first. Skipping.",
file=sys.stderr)
else:
print(f"Warning: {fin} is not a supported CSV or JSON metrics input. Skipping.",
file=sys.stderr)
continue
# Drop any fake TOTAL entries that slipped in as files
json_data["files"] = [
f for f in json_data.get("files", [])
if str(f.get("file", "")).upper() != "TOTAL"
]
all_json_data["file_list"].append(fin)
all_json_data["data"].append(json_data)
except Exception as e: # pragma: no cover - defensive
print(f"Warning: Failed to analyze {fin}: {e}", file=sys.stderr)
continue
return all_json_data
def compute_avg(all_json_data):
"""Compute average sizes from combined json_data.
Args:
all_json_data: Dictionary with file_list and data from combine_files()
Returns:
json_average: Dictionary with averaged size data
"""
if not all_json_data["data"]:
return None
# Merge files with the same 'file' value and compute averages
file_accumulator = {} # key: file name, value: {"sizes": [sizes], "symbols": {name: [sizes]}, "sections": {name: [sizes]}}
for json_data in all_json_data["data"]:
for f in json_data.get("files", []):
fname = f["file"]
if fname not in file_accumulator:
file_accumulator[fname] = {
"sizes": [],
"path": f.get("path"),
"symbols": defaultdict(list),
"sections": defaultdict(list),
}
size_val = f.get("size", 0)
file_accumulator[fname]["sizes"].append(size_val)
for sym in f.get("symbols", []):
name = sym.get("name")
if name is None:
continue
file_accumulator[fname]["symbols"][name].append(sym.get("size", 0))
sections_map = f.get("sections") or {}
for sname, ssize in sections_map.items():
# linkermap -v produces nested dicts {subsection: size}, flatten to total
if isinstance(ssize, dict):
ssize = sum(ssize.values())
file_accumulator[fname]["sections"][sname].append(ssize)
# Build json_average with averaged values
files_average = []
for fname, data in file_accumulator.items():
avg_size = round(sum(data["sizes"]) / len(data["sizes"])) if data["sizes"] else 0
symbols_avg = []
for sym_name, sizes in data["symbols"].items():
if not sizes:
continue
symbols_avg.append({"name": sym_name, "size": round(sum(sizes) / len(sizes))})
symbols_avg.sort(key=lambda x: x["size"], reverse=True)
sections_avg = {
sec_name: round(sum(sizes) / len(sizes))
for sec_name, sizes in data["sections"].items()
if sizes
}
files_average.append(
{
"file": fname,
"path": data["path"],
"size": avg_size,
"symbols": symbols_avg,
"sections": sections_avg,
}
)
total_size = sum(f["size"] for f in files_average) or 1
for f in files_average:
f["percent"] = (f["size"] / total_size) * 100 if total_size else 0
for sym in f["symbols"]:
sym["percent"] = (sym["size"] / f["size"]) * 100 if f["size"] else 0
json_average = {
"file_list": all_json_data["file_list"],
"files": files_average,
}
return json_average
def compare_files(base_file, new_file, filters=None):
"""Compare two CSV or JSON inputs and generate a difference report."""
filters = filters or []
base_avg = compute_avg(combine_files([base_file], filters))
new_avg = compute_avg(combine_files([new_file], filters))
if not base_avg or not new_avg:
return None
base_files = {f["file"]: f for f in base_avg["files"]}
new_files = {f["file"]: f for f in new_avg["files"]}
all_file_names = set(base_files.keys()) | set(new_files.keys())
comparison_files = []
for fname in sorted(all_file_names):
b = base_files.get(fname, {})
n = new_files.get(fname, {})
b_size = b.get("size", 0)
n_size = n.get("size", 0)
base_sections = b.get("sections") or {}
new_sections = n.get("sections") or {}
# Symbol diffs
b_syms = {s["name"]: s for s in b.get("symbols", [])}
n_syms = {s["name"]: s for s in n.get("symbols", [])}
all_syms = set(b_syms.keys()) | set(n_syms.keys())
symbols = []
for sym in all_syms:
sb = b_syms.get(sym, {}).get("size", 0)
sn = n_syms.get(sym, {}).get("size", 0)
symbols.append({"name": sym, "base": sb, "new": sn, "diff": sn - sb})
symbols.sort(key=lambda x: abs(x["diff"]), reverse=True)
comparison_files.append({
"file": fname,
"size": {"base": b_size, "new": n_size, "diff": n_size - b_size},
"symbols": symbols,
"sections": {
name: {
"base": base_sections.get(name, 0),
"new": new_sections.get(name, 0),
"diff": new_sections.get(name, 0) - base_sections.get(name, 0),
}
for name in sorted(set(base_sections) | set(new_sections))
},
})
base_total = sum(f["size"] for f in base_avg["files"])
new_total = sum(f["size"] for f in new_avg["files"])
total = {
"base": base_total,
"new": new_total,
"diff": new_total - base_total,
}
return {
"base_file": base_file,
"new_file": new_file,
"total": total,
"files": comparison_files,
}
def get_sort_key(sort_order):
"""Get sort key function based on sort order.
Args:
sort_order: One of 'size-', 'size+', 'name-', 'name+'
Returns:
Tuple of (key_func, reverse)
"""
def _size_val(entry):
return entry.get('size', 0)
if sort_order == 'size-':
return _size_val, True
elif sort_order == 'size+':
return _size_val, False
elif sort_order == 'name-':
return lambda x: x.get('file', ''), True
else: # name+
return lambda x: x.get('file', ''), False
def format_diff(base, new, diff):
"""Format a diff value with percentage."""
if diff == 0:
return f"{new}"
if base == 0 or new == 0:
return f"{base} ➙ {new}"
pct = (diff / base) * 100
sign = "+" if diff > 0 else ""
return f"{base} ➙ {new} ({sign}{diff}, {sign}{pct:.1f}%)"
def write_json_output(json_data, path):
"""Write JSON output with indentation."""
with open(path, "w", encoding="utf-8") as outf:
json.dump(json_data, outf, indent=2)
def render_combine_table(json_data, sort_order='name+'):
"""Render averaged sizes as markdown table lines (no title)."""
files = json_data.get("files", [])
if not files:
return ["No entries."]
key_func, reverse = get_sort_key(sort_order)
files_sorted = sorted(files, key=key_func, reverse=reverse)
total_size = json_data.get("TOTAL") or sum(f.get("size", 0) for f in files_sorted)
pct_strings = [
f"{(f.get('percent') if f.get('percent') is not None else (f.get('size', 0) / total_size * 100 if total_size else 0)):.1f}%"
for f in files_sorted]
pct_width = 6
size_width = max(len("size"), *(len(str(f.get("size", 0))) for f in files_sorted), len(str(total_size)))
file_width = max(len("File"), *(len(f.get("file", "")) for f in files_sorted), len("TOTAL"))
# Build section totals on the fly from file data
sections_global = defaultdict(int)
for f in files_sorted:
for name, size in (f.get("sections") or {}).items():
sections_global[name] += size
# Display sections in reverse alphabetical order for stable column layout
section_names = sorted(sections_global.keys(), reverse=True)
section_widths = {}
for name in section_names:
max_val = max((f.get("sections", {}).get(name, 0) for f in files_sorted), default=0)
section_widths[name] = max(len(name), len(str(max_val)), 1)
if not section_names:
header = f"| {'File':<{file_width}} | {'size':>{size_width}} | {'%':>{pct_width}} |"
separator = f"| :{'-' * (file_width - 1)} | {'-' * (size_width - 1)}: | {'-' * (pct_width - 1)}: |"
else:
header_parts = [f"| {'File':<{file_width}} |"]
sep_parts = [f"| :{'-' * (file_width - 1)} |"]
for name in section_names:
header_parts.append(f" {name:>{section_widths[name]}} |")
sep_parts.append(f" {'-' * (section_widths[name] - 1)}: |")
header_parts.append(f" {'size':>{size_width}} | {'%':>{pct_width}} |")
sep_parts.append(f" {'-' * (size_width - 1)}: | {'-' * (pct_width - 1)}: |")
header = "".join(header_parts)
separator = "".join(sep_parts)
lines = [header, separator]
for f, pct_str in zip(files_sorted, pct_strings):
size_val = f.get("size", 0)
parts = [f"| {f.get('file', ''):<{file_width}} |"]
if section_names:
sections_map = f.get("sections") or {}
for name in section_names:
parts.append(f" {sections_map.get(name, 0):>{section_widths[name]}} |")
parts.append(f" {size_val:>{size_width}} | {pct_str:>{pct_width}} |")
lines.append("".join(parts))
total_parts = [f"| {'TOTAL':<{file_width}} |"]
if section_names:
for name in section_names:
total_parts.append(f" {sections_global.get(name, 0):>{section_widths[name]}} |")
total_parts.append(f" {total_size:>{size_width}} | {'100.0%':>{pct_width}} |")
lines.append("".join(total_parts))
return lines
def write_combine_markdown(json_data, path, sort_order='name+', title="TinyUSB Average Code Size Metrics"):
"""Write averaged size data to a markdown file."""
md_lines = [f"## {title}", "", "<details><summary>Size table</summary>", ""]
md_lines.extend(render_combine_table(json_data, sort_order))
md_lines.extend(["", "</details>", ""])
if json_data.get("file_list"):
md_lines.extend(["<details>", "<summary>Input files</summary>", ""])
md_lines.extend([f"- {mf}" for mf in json_data["file_list"]])
md_lines.extend(["", "</details>", ""])
with open(path, "w", encoding="utf-8") as f:
f.write("\n".join(md_lines))
def write_compare_markdown(comparison, path, sort_order='size'):
"""Write comparison data to markdown file."""
md_lines = [
"## Size Difference Report",
"",
"Because TinyUSB code size varies by port and configuration, the metrics below represent the averaged totals across all example builds.",
"",
"Note: If there is no change, only one value is shown.",
"",
]
significant, minor, unchanged = _split_by_significance(comparison["files"], sort_order)
def render(title, rows, collapsed=False):
if collapsed:
md_lines.append(f"<details><summary>{title}</summary>")
md_lines.append("")
else:
md_lines.append(f"### {title}")
md_lines.extend(render_compare_table(_build_rows(rows, sort_order), include_sum=True))
md_lines.append("")
if collapsed:
md_lines.append("</details>")
md_lines.append("")
render("Changes >1% in size", significant)
render("Changes <1% in size", minor, collapsed=True)
render("No changes", unchanged, collapsed=True)
with open(path, "w", encoding="utf-8") as f:
f.write("\n".join(md_lines))
def print_compare_summary(comparison, sort_order='name+'):
"""Print diff report to stdout in table form."""
files = comparison["files"]
rows = _build_rows(files, sort_order)
lines = render_compare_table(rows, include_sum=True)
for line in lines:
print(line)
def _build_rows(files, sort_order):
"""Sort files and prepare printable fields."""
def sort_key(file_row):
if sort_order == 'size-':
return abs(file_row["size"]["diff"])
if sort_order in ('size', 'size+'):
return abs(file_row["size"]["diff"])
if sort_order == 'name-':
return file_row['file']
return file_row['file']
reverse = sort_order in ('size-', 'name-')
files_sorted = sorted(files, key=sort_key, reverse=reverse)
rows = []
for f in files_sorted:
sd = f["size"]
diff_val = sd['new'] - sd['base']
if sd['base'] == 0:
pct_str = "n/a"
else:
pct_val = (diff_val / sd['base']) * 100
pct_str = f"{pct_val:+.1f}%"
rows.append({
"file": f['file'],
"base": sd['base'],
"new": sd['new'],
"diff": diff_val,
"pct": pct_str,
"sections": f.get("sections", {}),
})
return rows
def _split_by_significance(files, sort_order):
"""Split files into >1% changes, <1% changes, and no changes."""
def is_significant(file_row):
base = file_row["size"]["base"]
diff = abs(file_row["size"]["diff"])
if base == 0:
return diff != 0
return (diff / base) * 100 > 1.0
rows_sorted = sorted(
files,
key=lambda f: abs(f["size"]["diff"]) if sort_order.startswith("size") else f["file"],
reverse=sort_order in ('size-', 'name-'),
)
significant = []
minor = []
unchanged = []
for f in rows_sorted:
if f["size"]["diff"] == 0:
unchanged.append(f)
else:
(significant if is_significant(f) else minor).append(f)
return significant, minor, unchanged
def render_compare_table(rows, include_sum):
"""Return markdown table lines for given rows."""
if not rows:
return ["No entries.", ""]
# collect section columns (reverse alpha)
section_names = sorted(
{name for r in rows for name in (r.get("sections") or {})},
reverse=True,
)
def fmt_abs(val_old, val_new):
diff = val_new - val_old
if diff == 0:
return f"{val_new}"
sign = "+" if diff > 0 else ""
return f"{val_old} ➙ {val_new} ({sign}{diff})"
sum_base = sum(r["base"] for r in rows)
sum_new = sum(r["new"] for r in rows)
total_diff = sum_new - sum_base
total_pct = "n/a" if sum_base == 0 else f"{(total_diff / sum_base) * 100:+.1f}%"
file_width = max(len("file"), *(len(r["file"]) for r in rows), len("TOTAL"))
size_width = max(
len("size"),
*(len(fmt_abs(r["base"], r["new"])) for r in rows),
len(fmt_abs(sum_base, sum_new)),
)
pct_width = max(len("% diff"), *(len(r["pct"]) for r in rows), len(total_pct))
section_widths = {}
for name in section_names:
max_val_len = 0
for r in rows:
sec_entry = (r.get("sections") or {}).get(name, {"base": 0, "new": 0})
max_val_len = max(max_val_len, len(fmt_abs(sec_entry.get("base", 0), sec_entry.get("new", 0))))
section_widths[name] = max(len(name), max_val_len, 1)
header_parts = [f"| {'file':<{file_width}} |"]
sep_parts = [f"| :{'-' * (file_width - 1)} |"]
for name in section_names:
header_parts.append(f" {name:>{section_widths[name]}} |")
sep_parts.append(f" {'-' * (section_widths[name] - 1)}: |")
header_parts.append(f" {'size':>{size_width}} | {'% diff':>{pct_width}} |")
sep_parts.append(f" {'-' * (size_width - 1)}: | {'-' * (pct_width - 1)}: |")
header = "".join(header_parts)
separator = "".join(sep_parts)
lines = [header, separator]
for r in rows:
parts = [f"| {r['file']:<{file_width}} |"]
sections_map = r.get("sections") or {}
for name in section_names:
sec_entry = sections_map.get(name, {"base": 0, "new": 0})
parts.append(f" {fmt_abs(sec_entry.get('base', 0), sec_entry.get('new', 0)):>{section_widths[name]}} |")
parts.append(f" {fmt_abs(r['base'], r['new']):>{size_width}} | {r['pct']:>{pct_width}} |")
lines.append("".join(parts))
if include_sum:
total_parts = [f"| {'TOTAL':<{file_width}} |"]
for name in section_names:
total_base = sum((r.get("sections") or {}).get(name, {}).get("base", 0) for r in rows)
total_new = sum((r.get("sections") or {}).get(name, {}).get("new", 0) for r in rows)
total_parts.append(f" {fmt_abs(total_base, total_new):>{section_widths[name]}} |")
total_parts.append(f" {fmt_abs(sum_base, sum_new):>{size_width}} | {total_pct:>{pct_width}} |")
lines.append("".join(total_parts))
return lines
def cmd_combine(args):
"""Handle combine subcommand."""
input_files = expand_files(args.files)
all_json_data = combine_files(input_files, args.filters)
json_average = compute_avg(all_json_data)
if json_average is None:
print("No valid map files found", file=sys.stderr)
sys.exit(1)
if not args.quiet:
for line in render_combine_table(json_average, sort_order=args.sort):
print(line)
if args.json_out:
write_json_output(json_average, args.out + '.json')
if args.markdown_out:
write_combine_markdown(json_average, args.out + '.md', sort_order=args.sort,
title="TinyUSB Average Code Size Metrics")
def cmd_compare(args):
"""Handle compare subcommand."""
comparison = compare_files(args.base, args.new, args.filters)
if comparison is None:
print("Failed to compare files", file=sys.stderr)
sys.exit(1)
if not args.quiet:
print_compare_summary(comparison, args.sort)
if args.markdown_out:
write_compare_markdown(comparison, args.out + '.md', args.sort)
if not args.quiet:
print(f"Comparison written to {args.out}.md")
def main(argv=None):
parser = argparse.ArgumentParser(description='Code size metrics tool')
subparsers = parser.add_subparsers(dest='command', required=True, help='Available commands')
# Combine subcommand
combine_parser = subparsers.add_parser('combine', help='Combine and average bloaty CSV outputs or metrics JSON files')
combine_parser.add_argument('files', nargs='+',
help='Path to bloaty CSV output or TinyUSB metrics JSON file(s) (including linkermap-generated) or glob pattern(s)')
combine_parser.add_argument('-f', '--filter', dest='filters', action='append', default=[],
help='Only include compile units whose path contains this substring (can be repeated)')
combine_parser.add_argument('-o', '--out', dest='out', default='metrics',
help='Output path basename for JSON and Markdown files (default: metrics)')
combine_parser.add_argument('-j', '--json', dest='json_out', action='store_true',
help='Write JSON output file')
combine_parser.add_argument('-m', '--markdown', dest='markdown_out', action='store_true',
help='Write Markdown output file')
combine_parser.add_argument('-q', '--quiet', dest='quiet', action='store_true',
help='Suppress summary output')
combine_parser.add_argument('-S', '--sort', dest='sort', default='size-',
choices=['size', 'size-', 'size+', 'name', 'name-', 'name+'],
help='Sort order: size/size- (descending), size+ (ascending), name/name+ (ascending), name- (descending). Default: size-')
# Compare subcommand
compare_parser = subparsers.add_parser('compare', help='Compare two metrics inputs (bloaty CSV or metrics JSON)')
compare_parser.add_argument('base', help='Base CSV/metrics JSON file')
compare_parser.add_argument('new', help='New CSV/metrics JSON file')
compare_parser.add_argument('-f', '--filter', dest='filters', action='append', default=[],
help='Only include compile units whose path contains this substring (can be repeated)')
compare_parser.add_argument('-o', '--out', dest='out', default='metrics_compare',
help='Output path basename for Markdown/JSON files (default: metrics_compare)')
compare_parser.add_argument('-m', '--markdown', dest='markdown_out', action='store_true',
help='Write Markdown output file')
compare_parser.add_argument('-S', '--sort', dest='sort', default='name+',
choices=['size', 'size-', 'size+', 'name', 'name-', 'name+'],
help='Sort order: size/size- (descending), size+ (ascending), name/name+ (ascending), name- (descending). Default: name+')
compare_parser.add_argument('-q', '--quiet', dest='quiet', action='store_true',
help='Suppress stdout summary output')
args = parser.parse_args(argv)
if args.command == 'combine':
cmd_combine(args)
elif args.command == 'compare':
cmd_compare(args)
if __name__ == '__main__':
main()
+350
View File
@@ -0,0 +1,350 @@
#!/usr/bin/env python3
"""Build base branch (master) and current tree, then compare code size metrics.
Creates cmake-metrics/<board>/{base,build} directories for each board.
With --combined, also writes cmake-metrics/_combined/metrics_compare.md aggregating
all boards into a single comparison.
Usage:
python tools/metrics_compare_base.py -b raspberry_pi_pico
python tools/metrics_compare_base.py -b raspberry_pi_pico -b raspberry_pi_pico2
python tools/metrics_compare_base.py -b raspberry_pi_pico -f portable/raspberrypi
python tools/metrics_compare_base.py -b raspberry_pi_pico -e device/cdc_msc
python tools/metrics_compare_base.py -b raspberry_pi_pico -e device/cdc_msc --bloaty
python tools/metrics_compare_base.py --ci # first board of each arm-gcc family, combined
python tools/metrics_compare_base.py -b pico -b pico2 --combined # aggregate listed boards
"""
import argparse
import glob
import json
import os
import re
import shlex
import subprocess
import sys
TINYUSB_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
METRICS_DIR = os.path.join(TINYUSB_ROOT, 'cmake-metrics')
def tinyusb_src_filter(checkout_dir):
"""Return a path-substring filter that uniquely matches TinyUSB stack source files
in `checkout_dir`. The substring is the absolute path to the checkout's `src/`
dir — collision-free with vendored deps (pico-sdk, lwip, FreeRTOS, etc.) which
live at unrelated paths."""
return os.path.realpath(os.path.join(checkout_dir, 'src')) + os.sep
verbose = False
def run(cmd, **kwargs):
"""Run a command. cmd must be a list (no shell=True). On `timeout=`-induced
TimeoutExpired, return a CompletedProcess with rc=124 instead of letting the
exception propagate, so the caller can fall through to error reporting and
worktree cleanup rather than crashing with a traceback."""
if not isinstance(cmd, list):
raise TypeError('run() requires a list, got str — fix the caller')
if verbose:
print(f' $ {" ".join(shlex.quote(str(c)) for c in cmd)}')
try:
return subprocess.run(cmd, capture_output=True, text=True, **kwargs)
except subprocess.TimeoutExpired as e:
msg = f'Command timed out after {e.timeout}s: {" ".join(shlex.quote(str(c)) for c in cmd)}'
stderr = (e.stderr or '') + ('\n' if e.stderr else '') + msg
return subprocess.CompletedProcess(cmd, 124, stdout=(e.stdout or ''), stderr=stderr)
def symlink_deps(main_root, worktree_dir):
"""Symlink dependency directories (fetched by tools/get_deps.py) from the main
checkout into the temporary worktree. Without this, the base build fails because
the worktree doesn't have the untracked deps."""
def link_subdirs(rel_parent):
src_parent = os.path.join(main_root, rel_parent)
dst_parent = os.path.join(worktree_dir, rel_parent)
if not os.path.isdir(src_parent):
return
os.makedirs(dst_parent, exist_ok=True)
for entry in os.listdir(src_parent):
src = os.path.join(src_parent, entry)
dst = os.path.join(dst_parent, entry)
if os.path.isdir(src) and not os.path.exists(dst):
os.symlink(src, dst)
# lib/* and tools/* deps (e.g. lib/lwip, tools/linkermap)
link_subdirs('lib')
link_subdirs('tools')
# hw/mcu/<vendor>/<dep> (e.g. hw/mcu/raspberry_pi/Pico-PIO-USB)
hw_mcu = os.path.join(main_root, 'hw', 'mcu')
if os.path.isdir(hw_mcu):
for vendor in os.listdir(hw_mcu):
link_subdirs(os.path.join('hw', 'mcu', vendor))
def ci_first_boards():
"""Return the first board (alphabetical) of each arm-gcc CI family."""
matrix_py = os.path.join(TINYUSB_ROOT, '.github', 'workflows', 'ci_set_matrix.py')
if not os.path.isfile(matrix_py):
return []
ret = run([sys.executable, matrix_py])
if ret.returncode != 0:
return []
try:
data = json.loads(ret.stdout)
except json.JSONDecodeError:
return []
families = data.get('arm-gcc', [])
boards = []
bsp_root = os.path.join(TINYUSB_ROOT, 'hw', 'bsp')
for family in families:
family_boards = sorted(
d for d in os.listdir(os.path.join(bsp_root, family, 'boards'))
if os.path.isdir(os.path.join(bsp_root, family, 'boards', d))
) if os.path.isdir(os.path.join(bsp_root, family, 'boards')) else []
if family_boards:
boards.append(family_boards[0])
return boards
def build_board(src_dir, build_dir, board, example=None):
"""Configure and build examples for a board. Returns True on success.
When `example` is given, only that target is built (`cmake --build --target NAME`),
keeping single-example workflows fast.
"""
os.makedirs(build_dir, exist_ok=True)
ret = run(['cmake', '-B', build_dir, '-G', 'Ninja',
f'-DBOARD={board}', '-DCMAKE_BUILD_TYPE=MinSizeRel',
os.path.join(src_dir, 'examples')])
if ret.returncode != 0:
print(f' Error configuring {board}: {ret.stderr}')
return False
cmd = ['cmake', '--build', build_dir]
if example:
cmd += ['--target', os.path.basename(example)]
ret = run(cmd, timeout=600)
if ret.returncode != 0:
print(f' Error building {board}: {ret.stderr}')
return False
return True
def generate_metrics(build_dir, out_basename, filters, example=None):
"""Run metrics.py combine on .map.json files. Returns metrics json path or None.
`filters` is a list of substrings; metrics.py keeps a compile unit if its path
contains any of them.
"""
if example:
patterns = glob.glob(f'{build_dir}/{example}/*.map.json')
else:
patterns = glob.glob(f'{build_dir}/**/*.map.json', recursive=True)
if not patterns:
print(f' Error: no .map.json files in {build_dir}' + (f' for {example}' if example else ''))
return None
metrics_py = os.path.join(TINYUSB_ROOT, 'tools', 'metrics.py')
cmd = [sys.executable, metrics_py, 'combine']
for f in filters:
cmd += ['-f', f]
cmd += ['-j', '-q', '-o', out_basename, *patterns]
ret = run(cmd)
if ret.returncode != 0:
print(f' Error: {ret.stderr}')
return None
return f'{out_basename}.json'
def main():
global verbose
parser = argparse.ArgumentParser(description='Compare code size metrics with base branch')
parser.add_argument('-b', '--board', action='append', default=[],
help='Board name (repeatable). Required unless --ci is given.')
parser.add_argument('-f', '--filter', action='append', default=None,
help='Path-substring filter (repeatable). When given, '
'overrides the default and is applied to BOTH base and '
'current builds. Default: each side\'s own absolute '
'<checkout>/src/ path, which uniquely matches TinyUSB '
'stack code without colliding with vendored deps.')
parser.add_argument('--base-branch', default='master',
help='Base branch to compare against (default: master)')
parser.add_argument('-e', '--example', action='append', default=None,
help='Compare specific example (repeatable, e.g. -e device/cdc_msc -e host/cdc_msc_hid)')
parser.add_argument('--bloaty', action='store_true',
help='Use bloaty for detailed section/symbol diff (requires -e)')
parser.add_argument('--ci', action='store_true',
help='Add the first board of every arm-gcc CI family. Implies --combined.')
parser.add_argument('--combined', action='store_true',
help='Aggregate map.json files across all boards into one comparison '
'(in cmake-metrics/_combined/), instead of (or in addition to) per-board.')
parser.add_argument('-v', '--verbose', action='store_true',
help='Print build commands')
args = parser.parse_args()
verbose = args.verbose
if args.bloaty and not args.example:
parser.error('--bloaty requires -e/--example')
if args.ci:
args.combined = True
ci_boards = ci_first_boards()
if not ci_boards:
parser.error('--ci: failed to derive boards from .github/workflows/ci_set_matrix.py')
# Append, dedup, preserve order
seen = set(args.board)
for b in ci_boards:
if b not in seen:
args.board.append(b)
seen.add(b)
if not args.board:
parser.error('at least one -b BOARD is required (or pass --ci)')
metrics_py = os.path.join(TINYUSB_ROOT, 'tools', 'metrics.py')
worktree_dir = os.path.join(METRICS_DIR, '_worktree')
# Per-side filters: when no override is given, each build uses its own
# absolute <checkout>/src/ path so we only match TinyUSB stack code from that
# checkout (and never vendored-dep `src/` like pico-sdk/src/...).
if args.filter:
base_filters = cur_filters = list(args.filter)
else:
base_filters = [tinyusb_src_filter(worktree_dir)]
cur_filters = [tinyusb_src_filter(TINYUSB_ROOT)]
# Step 1: Create worktree for base branch
print(f'[1/5] Setting up {args.base_branch} worktree...')
if os.path.isdir(worktree_dir):
run(['git', '-C', TINYUSB_ROOT, 'worktree', 'remove', '--force', worktree_dir])
# --detach: check out the ref at a detached HEAD instead of trying to claim the
# branch. Lets us add a worktree of `master` even if master is already checked
# out elsewhere (main repo, another worktree).
ret = run(['git', '-C', TINYUSB_ROOT, 'worktree', 'add', '--detach',
worktree_dir, args.base_branch])
if ret.returncode != 0:
print(f'Error creating worktree: {ret.stderr}')
sys.exit(1)
# Symlink dependency dirs (lib/*, hw/mcu/*/*, tools/*) so the worktree builds.
symlink_deps(TINYUSB_ROOT, worktree_dir)
try:
examples = args.example or [None]
# For --combined: track every (base_build, cur_build) pair so we can aggregate at the end.
built_pairs = []
for board in args.board:
print(f'\n=== {board} ===')
board_dir = os.path.join(METRICS_DIR, board)
base_build = os.path.join(board_dir, 'base')
cur_build = os.path.join(board_dir, 'build')
# Build only the requested examples (or all if -e not given). Single-example
# mode used to build everything and filter at metric time — that was wasted work.
board_failed = False
for example in examples:
build_label = f' --target {os.path.basename(example)}' if example else ''
print(f'[2/5] Building {args.base_branch} for {board}{build_label}...')
if not build_board(worktree_dir, base_build, board, example):
board_failed = True
break
print(f'[3/5] Building current for {board}{build_label}...')
if not build_board(TINYUSB_ROOT, cur_build, board, example):
board_failed = True
break
if board_failed:
continue
built_pairs.append((board, base_build, cur_build))
for example in examples:
suffix = f'_{example.replace("/", "_")}' if example else ''
label = f' ({example})' if example else ''
# Step 4: Generate metrics
print(f'[4/5] Generating metrics for {board}{label}...')
base_json = generate_metrics(base_build, os.path.join(board_dir, f'base_metrics{suffix}'),
base_filters, example)
cur_json = generate_metrics(cur_build, os.path.join(board_dir, f'build_metrics{suffix}'),
cur_filters, example)
if not base_json or not cur_json:
continue
# Step 5: Compare
out_base = os.path.join(board_dir, f'metrics_compare{suffix}')
print(f'[5/5] Comparing {board}{label}...')
ret = run([sys.executable, metrics_py, 'compare', '-m', '-o', out_base, base_json, cur_json])
print(ret.stdout)
# Optional: bloaty diff
if args.bloaty and example:
elf_name = os.path.basename(example)
base_elf = os.path.join(base_build, example, f'{elf_name}.elf')
cur_elf = os.path.join(cur_build, example, f'{elf_name}.elf')
if os.path.exists(base_elf) and os.path.exists(cur_elf):
# Bloaty expects one regex; OR-join all filters (current side
# for the new ELF, base side for the base ELF).
bloaty_regex = '(' + '|'.join(
re.escape(f) for f in (cur_filters + base_filters)
) + ')'
bloaty_common = ['bloaty', '--domain=vm', f'--source-filter={bloaty_regex}']
print(f'--- bloaty sections ---')
ret = run(bloaty_common + ['-d', 'compileunits,sections', cur_elf, '--', base_elf])
print(ret.stdout)
print(f'--- bloaty symbols ---')
ret = run(bloaty_common + ['-d', 'compileunits,symbols', '-s', 'vm',
cur_elf, '--', base_elf])
print(ret.stdout)
else:
print(f' bloaty: ELF not found')
# Optional combined comparison across all boards.
# Aggregates the per-board metrics JSONs (not raw map.json globs) so the argv
# stays small even with --ci spanning many boards.
if args.combined and built_pairs:
combined_dir = os.path.join(METRICS_DIR, '_combined')
os.makedirs(combined_dir, exist_ok=True)
# Use the no-suffix per-board JSONs (whole-board metrics). Combined mode
# is meant for board-level sweeps; -e/--example combinations skip combined.
base_jsons, cur_jsons = [], []
for board, _, _ in built_pairs:
bj = os.path.join(METRICS_DIR, board, 'base_metrics.json')
cj = os.path.join(METRICS_DIR, board, 'build_metrics.json')
if os.path.isfile(bj) and os.path.isfile(cj):
base_jsons.append(bj)
cur_jsons.append(cj)
if not base_jsons or not cur_jsons:
print(' combined: no per-board metrics found (did you pass -e? skip --combined with -e)')
else:
print(f'\n=== combined ({len(base_jsons)} boards) ===')
base_out = os.path.join(combined_dir, 'base_metrics')
cur_out = os.path.join(combined_dir, 'build_metrics')
# Per-board JSONs are already filtered to TinyUSB-only files; combine
# without re-filtering so we don't accidentally drop entries.
def _combine(out_basename, inputs):
cmd = [sys.executable, metrics_py, 'combine',
'-j', '-q', '-o', out_basename, *inputs]
return run(cmd)
ret = _combine(base_out, base_jsons)
if ret.returncode != 0:
print(f' combined base error: {ret.stderr}')
else:
ret = _combine(cur_out, cur_jsons)
if ret.returncode != 0:
print(f' combined current error: {ret.stderr}')
else:
out_combined = os.path.join(combined_dir, 'metrics_compare')
ret = run([sys.executable, metrics_py, 'compare', '-m',
'-o', out_combined, f'{base_out}.json', f'{cur_out}.json'])
print(ret.stdout)
print(f' combined report: {out_combined}.md')
finally:
print(f'\nCleaning up worktree...')
run(['git', '-C', TINYUSB_ROOT, 'worktree', 'remove', '--force', worktree_dir])
if __name__ == '__main__':
main()
+48
View File
@@ -0,0 +1,48 @@
#!/usr/bin/env python3
import sys
import struct
def align(num, alignment):
if num % alignment != 0:
num += (alignment - num % alignment)
return num
def process_file(input, output):
with open(input, 'rb') as fin:
content = bytearray(fin.read())
align_value = 512
padded_length = align(len(content), align_value)
# pad file to actual length
content += b'\x00' * (padded_length - len(content))
struct_format = '<L8sLL'
(instruction, magic, checksum, length) = struct.unpack_from(struct_format, content)
if magic != b'eGON.BT0':
print("Magic is invalid:", magic)
return 2
checksum = 0x5F0A6C39
length = align(length, align_value)
struct.pack_into(struct_format, content, 0, instruction, magic, checksum, length)
checksum = 0
for i in range(0, length, 4):
(n, ) = struct.unpack_from('<L', content, i)
checksum += n
checksum %= 4294967296
struct.pack_into(struct_format, content, 0, instruction, magic, checksum, length)
with open(output, 'wb') as fout:
fout.write(content)
return 0
if __name__ == "__main__":
if len(sys.argv) != 3:
print("Usage: mksunxi.py input.bin output.bin")
exit(1)
exit(process_file(sys.argv[1], sys.argv[2]))
+44
View File
@@ -0,0 +1,44 @@
#!/usr/bin/env python3
import argparse
import pcapng
import zipfile
import hashlib
def extract_packets(pcap_file):
"""Reads a wireshark packet capture and extracts the binary packets"""
packets = []
with open(pcap_file, 'rb') as fp:
scanner = pcapng.FileScanner(fp)
for block in scanner:
if isinstance(block, pcapng.blocks.EnhancedPacket):
packets.append(block.packet_data)
return packets
def build_corpus_zip(zip_file_output, packets):
"""Builds a zip file with a file per packet
The structure of this zip corpus is a simple content addressable storage
i.e. seed_file_name == sha256_digest(packet).
"""
with zipfile.ZipFile(zip_file_output, 'a') as out:
for packet in packets:
hash = hashlib.sha256(packet).hexdigest()
if hash not in out.namelist():
out.writestr(hash, packet)
def main(pcap_file, output_zip_file):
packets = extract_packets(pcap_file)
build_corpus_zip(output_zip_file, packets)
if __name__ == "__main__":
parser = argparse.ArgumentParser(
prog = "pcapng_to_corpus.py",
description="""Converts a wireshark capture to a zip of binary packet
files suitable for an oss-fuzz corpus. In the case the
zip corpus already exists, this script will modify
the zip file in place adding seed entries.""")
parser.add_argument('pcapng_capture_file')
parser.add_argument('oss_fuzz_corpus_zip')
args = parser.parse_args()
main(args.pcapng_capture_file, args.oss_fuzz_corpus_zip)
@@ -0,0 +1,108 @@
;************************************************************
; Windows USB CDC ACM Setup File
; Copyright (c) 2000 Microsoft Corporation
[Version]
Signature="$Windows NT$"
Class=Ports
ClassGuid={4D36E978-E325-11CE-BFC1-08002BE10318}
Provider=%MFGNAME%
LayoutFile=layout.inf
CatalogFile=%MFGFILENAME%.cat
DriverVer=11/15/2007,5.1.2600.0
DriverPackageDisplayName=%DESCRIPTION%
[Manufacturer]
%MFGNAME%=DeviceList, NTamd64
[DestinationDirs]
DefaultDestDir=12
;------------------------------------------------------------------------------
; Windows 2000/XP/Vista-32bit Sections
;------------------------------------------------------------------------------
[DriverInstall.nt]
include=mdmcpq.inf
CopyFiles=DriverCopyFiles.nt
AddReg=DriverInstall.nt.AddReg
[DriverCopyFiles.nt]
usbser.sys,,,0x20
[DriverInstall.nt.AddReg]
HKR,,DevLoader,,*ntkern
HKR,,NTMPDriver,,%DRIVERFILENAME%.sys
HKR,,EnumPropPages32,,"MsPorts.dll,SerialPortPropPageProvider"
[DriverInstall.nt.Services]
AddService=usbser, 0x00000002, DriverService.nt
[DriverService.nt]
DisplayName=%SERVICE%
ServiceType=1
StartType=3
ErrorControl=1
ServiceBinary=%12%\%DRIVERFILENAME%.sys
;------------------------------------------------------------------------------
; Vista-64bit Sections
;------------------------------------------------------------------------------
[DriverInstall.NTamd64]
include=mdmcpq.inf
CopyFiles=DriverCopyFiles.NTamd64
AddReg=DriverInstall.NTamd64.AddReg
[DriverCopyFiles.NTamd64]
%DRIVERFILENAME%.sys,,,0x20
[DriverInstall.NTamd64.AddReg]
HKR,,DevLoader,,*ntkern
HKR,,NTMPDriver,,%DRIVERFILENAME%.sys
HKR,,EnumPropPages32,,"MsPorts.dll,SerialPortPropPageProvider"
[DriverInstall.NTamd64.Services]
AddService=usbser, 0x00000002, DriverService.NTamd64
[DriverService.NTamd64]
DisplayName=%SERVICE%
ServiceType=1
StartType=3
ErrorControl=1
ServiceBinary=%12%\%DRIVERFILENAME%.sys
;------------------------------------------------------------------------------
; Vendor and Product ID Definitions
;------------------------------------------------------------------------------
; When developing your USB device, the VID and PID used in the PC side
; application program and the firmware on the microcontroller must match.
; Modify the below line to use your VID and PID. Use the format as shown below.
; Note: One INF file can be used for multiple devices with different VID and PIDs.
; For each supported device, append ",USB\VID_xxxx&PID_yyyy" to the end of the line.
;------------------------------------------------------------------------------
[SourceDisksFiles]
[SourceDisksNames]
[DeviceList]
%DESCRIPTION%=DriverInstall, USB\VID_CAFE&PID_4001&MI_00, USB\VID_CAFE&PID_4003&MI_00, USB\VID_CAFE&PID_4005&MI_00, USB\VID_CAFE&PID_4007&MI_00, USB\VID_CAFE&PID_4009&MI_00, USB\VID_CAFE&PID_400b&MI_00, USB\VID_CAFE&PID_400d&MI_00, USB\VID_CAFE&PID_400f&MI_00, USB\VID_CAFE&PID_4011&MI_00, USB\VID_CAFE&PID_4013&MI_00, USB\VID_CAFE&PID_4015&MI_00, USB\VID_CAFE&PID_4017&MI_00, USB\VID_CAFE&PID_4019&MI_00, USB\VID_CAFE&PID_401b&MI_00, USB\VID_CAFE&PID_401d&MI_00, USB\VID_CAFE&PID_401f&MI_00, USB\VID_CAFE&PID_4021&MI_00, USB\VID_CAFE&PID_4023&MI_00, USB\VID_CAFE&PID_4025&MI_00, USB\VID_CAFE&PID_4027&MI_00, USB\VID_CAFE&PID_4029&MI_00, USB\VID_CAFE&PID_402b&MI_00, USB\VID_CAFE&PID_402d&MI_00, USB\VID_CAFE&PID_402f&MI_00, USB\VID_CAFE&PID_4031&MI_00, USB\VID_CAFE&PID_4033&MI_00, USB\VID_CAFE&PID_4035&MI_00, USB\VID_CAFE&PID_4037&MI_00, USB\VID_CAFE&PID_4039&MI_00, USB\VID_CAFE&PID_403b&MI_00, USB\VID_CAFE&PID_403d&MI_00, USB\VID_CAFE&PID_403f&MI_00
[DeviceList.NTamd64]
%DESCRIPTION%=DriverInstall, USB\VID_CAFE&PID_4001&MI_00, USB\VID_CAFE&PID_4003&MI_00, USB\VID_CAFE&PID_4005&MI_00, USB\VID_CAFE&PID_4007&MI_00, USB\VID_CAFE&PID_4009&MI_00, USB\VID_CAFE&PID_400b&MI_00, USB\VID_CAFE&PID_400d&MI_00, USB\VID_CAFE&PID_400f&MI_00, USB\VID_CAFE&PID_4011&MI_00, USB\VID_CAFE&PID_4013&MI_00, USB\VID_CAFE&PID_4015&MI_00, USB\VID_CAFE&PID_4017&MI_00, USB\VID_CAFE&PID_4019&MI_00, USB\VID_CAFE&PID_401b&MI_00, USB\VID_CAFE&PID_401d&MI_00, USB\VID_CAFE&PID_401f&MI_00, USB\VID_CAFE&PID_4021&MI_00, USB\VID_CAFE&PID_4023&MI_00, USB\VID_CAFE&PID_4025&MI_00, USB\VID_CAFE&PID_4027&MI_00, USB\VID_CAFE&PID_4029&MI_00, USB\VID_CAFE&PID_402b&MI_00, USB\VID_CAFE&PID_402d&MI_00, USB\VID_CAFE&PID_402f&MI_00, USB\VID_CAFE&PID_4031&MI_00, USB\VID_CAFE&PID_4033&MI_00, USB\VID_CAFE&PID_4035&MI_00, USB\VID_CAFE&PID_4037&MI_00, USB\VID_CAFE&PID_4039&MI_00, USB\VID_CAFE&PID_403b&MI_00, USB\VID_CAFE&PID_403d&MI_00, USB\VID_CAFE&PID_403f&MI_00
;------------------------------------------------------------------------------
; String Definitions
;------------------------------------------------------------------------------
;Modify these strings to customize your device
;------------------------------------------------------------------------------
[Strings]
MFGFILENAME="tinyusb_usbser"
DRIVERFILENAME ="usbser"
MFGNAME="tinyusb.org"
INSTDISK="tinyusb CDC Driver"
DESCRIPTION="tinyusb Serial"
SERVICE="USB RS-232 Emulation Driver"