initial commit
This commit is contained in:
commit
b44ce7bd77
2716 files changed
+559005
No files matched your search
Vendored
+333
@@ -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
@@ -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
@@ -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.
@@ -0,0 +1,17 @@
|
||||
attch
|
||||
busses
|
||||
dout
|
||||
endianess
|
||||
fro
|
||||
hsi
|
||||
inout
|
||||
mot
|
||||
ore
|
||||
pris
|
||||
ptd
|
||||
ser
|
||||
sie
|
||||
synopsys
|
||||
te
|
||||
thre
|
||||
tre
|
||||
+46
@@ -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())
|
||||
Vendored
+117
@@ -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
@@ -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
@@ -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())
|
||||
Vendored
+87
@@ -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
@@ -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
@@ -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))
|
||||
Vendored
+662
@@ -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
@@ -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()
|
||||
Vendored
+48
@@ -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
@@ -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"
|
||||
Reference in new issue
Block a user