circuitpython/tools/ci_check_duplicate_usb_vid_...

160 lines
6.4 KiB
Python

#!/usr/bin/env python3
#
# This file is part of the MicroPython project, http://micropython.org/
#
# The MIT License (MIT)
#
# Copyright (c) 2020 Michael Schroeder
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
from collections import defaultdict
import argparse
import pathlib
import re
import sys
DEFAULT_CLUSTERLIST = {
"0x04D8:0xEC44": ["pycubed", "pycubed_mram", "pycubed_mram_v05", "pycubed_v05"],
"0x1B4F:0x8D24": ["sparkfun_qwiic_micro_no_flash", "sparkfun_qwiic_micro_with_flash"],
"0x1D50:0x6153": [
"jpconstantineau_pykey18",
"jpconstantineau_pykey44",
"jpconstantineau_pykey60",
"jpconstantineau_pykey87",
],
"0x239A:0x8019": [
"circuitplayground_express",
"circuitplayground_express_crickit",
"circuitplayground_express_displayio",
],
"0x239A:0x801F": ["trinket_m0_haxpress", "trinket_m0"],
"0x239A:0x8021": ["metro_m4_express", "cp32-m4"],
"0x239A:0x8023": ["feather_m0_express", "feather_m0_supersized"],
"0x239A:0x80A6": ["espressif_esp32s2_devkitc_1_n4r2", "espressif_saola_1_wrover"],
"0x239A:0x80AC": ["unexpectedmaker_feathers2", "unexpectedmaker_feathers2_prerelease"],
"0x239A:0x80C8": ["espressif_kaluga_1", "espressif_kaluga_1.3"],
"0x303A:0x7003": [
"espressif_esp32s3_devkitc_1_n8",
"espressif_esp32s3_devkitc_1_n8r2",
"espressif_esp32s3_devkitc_1_n8r8",
"espressif_esp32s3_devkitc_1_n32r8",
"espressif_esp32s3_devkitc_1_n8r8_hacktablet",
],
"0x303A:0x7009": [
"espressif_esp32s2_devkitc_1_n4",
"espressif_esp32s2_devkitc_1_n4r2",
"espressif_esp32s2_devkitc_1_n8r2",
],
"0x239A:0x102E": ["weact_studio_pico", "weact_studio_pico_16mb"],
"0x303A:0x8166": ["yd_esp32_s3_n8r8", "yd_esp32_s3_n16r8"],
"0x2341:0x056B": ["arduino_nano_esp32s3", "arduino_nano_esp32s3_inverted_statusled"],
"0x2E8A:0x1020": ["waveshare_rp2040_plus_4mb", "waveshare_rp2040_plus_16mb"],
}
cli_parser = argparse.ArgumentParser(
description="USB VID/PID and Creator/Creation ID Duplicate Checker"
)
def configboard_files():
"""A pathlib glob search for all ports/*/boards/*/mpconfigboard.mk file
paths.
:returns: A ``pathlib.Path.glob()`` generator object
"""
working_dir = pathlib.Path(__file__).resolve().parent.parent
return working_dir.glob("ports/**/boards/**/mpconfigboard.mk")
VID_PATTERN = re.compile(r"^USB_VID\s*=\s*(.*)", flags=re.M)
PID_PATTERN = re.compile(r"^USB_PID\s*=\s*(.*)", flags=re.M)
CREATOR_PATTERN = re.compile(r"^CIRCUITPY_CREATOR_ID\s*=\s*(.*)", flags=re.M)
CREATION_PATTERN = re.compile(r"^CIRCUITPY_CREATION_ID\s*=\s*(.*)", flags=re.M)
def check_vid_pid(files, clusterlist):
"""Compiles a list of USB VID & PID values for all boards, and checks
for duplicates. Exits with ``sys.exit()`` (non-zero exit code)
if duplicates are found, and lists the duplicates.
"""
usb_pattern = re.compile(
r"^CIRCUITPY_USB\s*=\s*0$|^IDF_TARGET = (esp32|esp32c3|esp32c6|esp32h2)$", flags=re.M
)
usb_ids = defaultdict(set)
for board_config in files:
src_text = board_config.read_text()
usb_vid = VID_PATTERN.search(src_text)
usb_pid = PID_PATTERN.search(src_text)
creator = CREATOR_PATTERN.search(src_text)
creation = CREATION_PATTERN.search(src_text)
non_usb = usb_pattern.search(src_text)
board_name = board_config.parts[-2]
if usb_vid and usb_pid:
id_group = f"0x{int(usb_vid.group(1), 16):04X}:0x{int(usb_pid.group(1), 16):04X}"
elif non_usb:
if creator is None or creation is None:
print(f"{board_name=} {creator=} {creation=}", file=sys.stderr)
continue
id_group = f"0x{int(creator.group(1), 16):08X}:0x{int(creation.group(1), 16):08X}"
else:
raise SystemExit(f"Could not find expected settings in {board_config}")
usb_ids[id_group].add(board_name)
duplicates = []
for key, boards in usb_ids.items():
if len(boards) == 1:
continue
# It is a cluster
cluster = set(clusterlist.get(key, []))
if cluster != boards:
if key == "":
duplicates.append(f"- Non-USB:\n" f" Boards: {', '.join(sorted(boards))}")
else:
duplicates.append(f"- VID/PID: {key}\n" f" Boards: {', '.join(sorted(boards))}")
if duplicates:
duplicates = "\n".join(duplicates)
duplicate_message = (
f"Duplicate VID/PID usage found!\n{duplicates}\n"
f"If you are open source maker, then you can request a PID from http://pid.codes\n"
f"For boards without native USB, you can request a Creator ID from https://github.com/creationid/creators/\n"
f"Otherwise, companies should pay the USB-IF for a vendor ID: https://www.usb.org/getting-vendor-id\n"
f"FAQ: Why does CircuitPython require a unique VID:PID for every board definition? https://learn.adafruit.com/how-to-add-a-new-board-to-circuitpython/frequently-asked-questions#faq-3130480"
)
sys.exit(duplicate_message)
else:
print("No unexpected ID duplicates found.")
if __name__ == "__main__":
arguments = cli_parser.parse_args()
print("Running USB VID/PID and Creator/Creation ID Duplicate Checker...")
board_files = configboard_files()
check_vid_pid(board_files, DEFAULT_CLUSTERLIST)