aboutsummaryrefslogtreecommitdiff
path: root/circuitpython/tools/build_board_info.py
diff options
context:
space:
mode:
Diffstat (limited to 'circuitpython/tools/build_board_info.py')
-rwxr-xr-xcircuitpython/tools/build_board_info.py339
1 files changed, 339 insertions, 0 deletions
diff --git a/circuitpython/tools/build_board_info.py b/circuitpython/tools/build_board_info.py
new file mode 100755
index 0000000..1534288
--- /dev/null
+++ b/circuitpython/tools/build_board_info.py
@@ -0,0 +1,339 @@
+#!/usr/bin/env python3
+
+# SPDX-FileCopyrightText: 2014 MicroPython & CircuitPython contributors (https://github.com/adafruit/circuitpython/graphs/contributors)
+#
+# SPDX-License-Identifier: MIT
+
+import json
+import os
+import subprocess
+import sys
+import sh
+import base64
+from datetime import date
+from sh.contrib import git
+
+sys.path.append("adabot")
+import adabot.github_requests as github
+
+sys.path.append("../docs")
+from shared_bindings_matrix import (
+ SUPPORTED_PORTS,
+ aliases_by_board,
+ support_matrix_by_board,
+)
+
+BIN = ("bin",)
+UF2 = ("uf2",)
+BIN_UF2 = ("bin", "uf2")
+HEX = ("hex",)
+HEX_UF2 = ("hex", "uf2")
+SPK = ("spk",)
+DFU = ("dfu",)
+BIN_DFU = ("bin", "dfu")
+COMBINED_HEX = ("combined.hex",)
+KERNEL8_IMG = ("disk.img.zip", "kernel8.img")
+KERNEL_IMG = ("disk.img.zip", "kernel.img")
+
+# Default extensions
+extension_by_port = {
+ "atmel-samd": UF2,
+ "broadcom": KERNEL8_IMG,
+ "cxd56": SPK,
+ "espressif": BIN_UF2,
+ "litex": DFU,
+ "mimxrt10xx": HEX_UF2,
+ "nrf": UF2,
+ "raspberrypi": UF2,
+ "stm": BIN,
+}
+
+# Per board overrides
+extension_by_board = {
+ # samd
+ "arduino_mkr1300": BIN_UF2,
+ "arduino_mkrzero": BIN_UF2,
+ "arduino_nano_33_iot": BIN_UF2,
+ "arduino_zero": BIN_UF2,
+ "feather_m0_adalogger": BIN_UF2,
+ "feather_m0_basic": BIN_UF2,
+ "feather_m0_rfm69": BIN_UF2,
+ "feather_m0_rfm9x": BIN_UF2,
+ "uchip": BIN_UF2,
+ # nRF52840 dev kits that may not have UF2 bootloaders,
+ "makerdiary_nrf52840_mdk": HEX,
+ "makerdiary_nrf52840_mdk_usb_dongle": HEX_UF2,
+ "pca10056": BIN_UF2,
+ "pca10059": BIN_UF2,
+ "electronut_labs_blip": HEX,
+ "microbit_v2": COMBINED_HEX,
+ # stm32
+ "meowbit_v121": UF2,
+ # esp32c3
+ "adafruit_qtpy_esp32c3": BIN,
+ "ai_thinker_esp32-c3s": BIN,
+ "ai_thinker_esp32-c3s-2m": BIN,
+ "espressif_esp32c3_devkitm_1_n4": BIN,
+ "lilygo_ttgo_t-01c3": BIN,
+ "microdev_micro_c3": BIN,
+ "lilygo_ttgo_t-oi-plus": BIN,
+ # broadcom
+ "raspberrypi_zero": KERNEL_IMG,
+ "raspberrypi_zero_w": KERNEL_IMG,
+}
+
+language_allow_list = set(
+ [
+ "ID",
+ "de_DE",
+ "en_GB",
+ "en_US",
+ "en_x_pirate",
+ "es",
+ "fil",
+ "fr",
+ "it_IT",
+ "ja",
+ "nl",
+ "pl",
+ "pt_BR",
+ "ru",
+ "sv",
+ "tr",
+ "zh_Latn_pinyin",
+ ]
+)
+
+
+def get_languages(list_all=False):
+ languages = set()
+ for f in os.scandir("../locale"):
+ if f.name.endswith(".po"):
+ languages.add(f.name[:-3])
+ if not list_all:
+ languages = languages & language_allow_list
+ return sorted(list(languages), key=str.casefold)
+
+
+def get_board_mapping():
+ boards = {}
+ for port in SUPPORTED_PORTS:
+ board_path = os.path.join("../ports", port, "boards")
+ for board_path in os.scandir(board_path):
+ if board_path.is_dir():
+ board_files = os.listdir(board_path.path)
+ board_id = board_path.name
+ extensions = extension_by_port[port]
+ extensions = extension_by_board.get(board_path.name, extensions)
+ aliases = aliases_by_board.get(board_path.name, [])
+ boards[board_id] = {
+ "port": port,
+ "extensions": extensions,
+ "download_count": 0,
+ "aliases": aliases,
+ }
+ for alias in aliases:
+ boards[alias] = {
+ "port": port,
+ "extensions": extensions,
+ "download_count": 0,
+ "alias": True,
+ "aliases": [],
+ }
+ return boards
+
+
+def get_version_info():
+ version = None
+ sha = git("rev-parse", "--short", "HEAD").stdout.decode("utf-8")
+ try:
+ version = git("describe", "--tags", "--exact-match").stdout.decode("utf-8").strip()
+ except sh.ErrorReturnCode_128:
+ # No exact match
+ pass
+
+ if "GITHUB_SHA" in os.environ:
+ sha = os.environ["GITHUB_SHA"]
+
+ if not version:
+ version = "{}-{}".format(date.today().strftime("%Y%m%d"), sha[:7])
+
+ return sha, version
+
+
+def get_current_info():
+ response = github.get("/repos/adafruit/circuitpython-org/git/refs/heads/main")
+ if not response.ok:
+ print(response.text)
+ raise RuntimeError("cannot get main sha")
+ commit_sha = response.json()["object"]["sha"]
+
+ response = github.get(
+ "/repos/adafruit/circuitpython-org/contents/_data/files.json?ref=" + commit_sha
+ )
+ if not response.ok:
+ print(response.text)
+ raise RuntimeError("cannot get previous files.json")
+
+ response = response.json()
+
+ git_info = commit_sha, response["sha"]
+ current_list = json.loads(base64.b64decode(response["content"]).decode("utf-8"))
+ current_info = {}
+ for info in current_list:
+ current_info[info["id"]] = info
+ return git_info, current_info
+
+
+def create_json(updated):
+ # Convert the dictionary to a list of boards. Liquid templates only handle arrays.
+ updated_list = []
+ all_ids = sorted(updated.keys())
+ for id in all_ids:
+ info = updated[id]
+ info["id"] = id
+ updated_list.append(info)
+ return json.dumps(updated_list, sort_keys=True, indent=1).encode("utf-8") + b"\n"
+
+
+def create_pr(changes, updated, git_info, user):
+ commit_sha, original_blob_sha = git_info
+ branch_name = "new_release_" + changes["new_release"]
+ updated = create_json(updated)
+ # print(updated.decode("utf-8"))
+
+ pr_title = "Automated website update for release {}".format(changes["new_release"])
+ boards = ""
+ if changes["new_boards"]:
+ boards = "New boards:\n* " + "\n* ".join(changes["new_boards"])
+ languages = ""
+ if changes["new_languages"]:
+ languages = "New languages:\n* " + "\n* ".join(changes["new_languages"])
+ message = "Automated website update for release {} by Blinka.\n\n{}\n\n{}\n".format(
+ changes["new_release"], boards, languages
+ )
+
+ create_branch = {"ref": "refs/heads/" + branch_name, "sha": commit_sha}
+ response = github.post("/repos/{}/circuitpython-org/git/refs".format(user), json=create_branch)
+ if not response.ok and response.json()["message"] != "Reference already exists":
+ print("unable to create branch")
+ print(response.text)
+ return
+
+ update_file = {
+ "message": message,
+ "content": base64.b64encode(updated).decode("utf-8"),
+ "sha": original_blob_sha,
+ "branch": branch_name,
+ }
+
+ response = github.put(
+ "/repos/{}/circuitpython-org/contents/_data/files.json".format(user), json=update_file
+ )
+ if not response.ok:
+ print("unable to post new file")
+ print(response.text)
+ return
+ pr_info = {
+ "title": pr_title,
+ "head": user + ":" + branch_name,
+ "base": "main",
+ "body": message,
+ "maintainer_can_modify": True,
+ }
+ response = github.post("/repos/adafruit/circuitpython-org/pulls", json=pr_info)
+ if not response.ok:
+ print("unable to create pr")
+ print(response.text)
+ return
+ print(changes)
+ print(pr_info)
+
+
+def print_active_user():
+ response = github.get("/user")
+ if response.ok:
+ user = response.json()["login"]
+ print("Logged in as {}".format(user))
+ return user
+ else:
+ print("Not logged in")
+ return None
+
+
+def generate_download_info():
+ boards = {}
+ errors = []
+
+ new_tag = os.environ["RELEASE_TAG"]
+
+ changes = {"new_release": new_tag, "new_boards": [], "new_languages": []}
+
+ user = print_active_user()
+
+ sha, this_version = get_version_info()
+
+ git_info, current_info = get_current_info()
+
+ languages = get_languages()
+
+ support_matrix = support_matrix_by_board(use_branded_name=False)
+
+ new_stable = "-" not in new_tag
+
+ previous_releases = set()
+ previous_languages = set()
+
+ # Delete the release we are replacing
+ for board in current_info:
+ info = current_info[board]
+ for version in list(info["versions"]):
+ previous_releases.add(version["version"])
+ previous_languages.update(version["languages"])
+ if version["stable"] == new_stable or (
+ new_stable and version["version"].startswith(this_version)
+ ):
+ info["versions"].remove(version)
+
+ board_mapping = get_board_mapping()
+
+ for port in SUPPORTED_PORTS:
+ board_path = os.path.join("../ports", port, "boards")
+ for board_path in os.scandir(board_path):
+ if board_path.is_dir():
+ board_files = os.listdir(board_path.path)
+ board_id = board_path.name
+ board_info = board_mapping[board_id]
+
+ for alias in [board_id] + board_info["aliases"]:
+ alias_info = board_mapping[alias]
+ if alias not in current_info:
+ changes["new_boards"].append(alias)
+ current_info[alias] = {"downloads": 0, "versions": []}
+
+ new_version = {
+ "stable": new_stable,
+ "version": new_tag,
+ "modules": support_matrix[alias][0],
+ "languages": languages,
+ "extensions": board_info["extensions"],
+ "frozen_libraries": [frozen[0] for frozen in support_matrix[alias][1]],
+ }
+ current_info[alias]["downloads"] = alias_info["download_count"]
+ current_info[alias]["versions"].append(new_version)
+
+ changes["new_languages"] = set(languages) - previous_languages
+
+ if changes["new_release"] and user:
+ create_pr(changes, current_info, git_info, user)
+ else:
+ print("No new release to update")
+ if "DEBUG" in os.environ:
+ print(create_json(current_info).decode("utf8"))
+
+
+if __name__ == "__main__":
+ if "RELEASE_TAG" in os.environ and os.environ["RELEASE_TAG"]:
+ generate_download_info()
+ else:
+ print("skipping website update because this isn't a tag")