Browse Source

Download relevant toolchain.

Nick Brassel 2 years ago
parent
commit
33f5e67408

+ 1 - 0
.gitignore

@@ -25,6 +25,7 @@
 *.la
 *.stackdump
 *.sym
+qmk_toolchains*
 
 # QMK-specific
 api_data/v1

+ 9 - 0
lib/python/qmk/cli/__init__.py

@@ -82,6 +82,7 @@ subcommands = [
     'qmk.cli.painter',
     'qmk.cli.pytest',
     'qmk.cli.test.c',
+    'qmk.cli.toolchain.commands',
     'qmk.cli.userspace.add',
     'qmk.cli.userspace.compile',
     'qmk.cli.userspace.doctor',
@@ -258,3 +259,11 @@ for subcommand in subcommands:
             _eprint(f'Warning: Could not import {subcommand}: {e.__class__.__name__}, {e}')
         else:
             raise
+
+# Add the QMK toolchains to $PATH
+try:
+    from qmk.constants import QMK_TOOLCHAINS_PATH  # noqa
+    if QMK_TOOLCHAINS_PATH.exists():
+        os.environ['PATH'] = f'{QMK_TOOLCHAINS_PATH}/bin{os.pathsep}{os.environ["PATH"]}'
+except Exception:
+    pass

+ 0 - 0
lib/python/qmk/cli/toolchain/__init__.py


+ 70 - 0
lib/python/qmk/cli/toolchain/commands.py

@@ -0,0 +1,70 @@
+"""This script sets up the QMK-provided toolchains.
+"""
+import platform
+import shutil
+import subprocess
+import os
+from milc import cli
+from pathlib import Path
+
+from qmk.constants import QMK_TOOLCHAINS_PATH, QMK_TOOLCHAINS_TAG
+from qmk.util import download_with_progress, cached_get
+
+
+def _os_prefix():
+    os = platform.system().lower()
+    if "linux" in os:
+        return "linux"
+    elif "darwin" in os or "mac" in os or "macos" in os or "osx" in os or "macosx" in os:
+        return "macos"
+    elif "windows" in os or "cygwin" in os or "msys" in os or "win32" in os or "win64" in os or "nt" in os:
+        return "windows"
+    return "unknown"
+
+
+def _arch_suffix():
+    arch = platform.machine().lower()
+    if "amd64" in arch or "x86_64" in arch or "x64" in arch or "x86-64" in arch:
+        return "X64"
+    elif "arm64" in arch or "aarch64" in arch:
+        return "ARM64"
+    return "unknown"
+
+
+def _os_arch():
+    return f'{_os_prefix()}{_arch_suffix()}'
+
+
+def _gcc_version_str(gcc_exe):
+    lines = cli.run([gcc_exe, '-v'], check=True, capture_output=True, combined_output=True, stdin=subprocess.DEVNULL).stdout.strip().splitlines()
+    return list(filter(lambda x: x.startswith('gcc version'), lines))[0].strip()
+
+
+@cli.argument('-t', '--tag', default=QMK_TOOLCHAINS_TAG, help=f'The tag of the QMK-provided toolchains to download. Defaults to `{QMK_TOOLCHAINS_TAG}`.')
+@cli.subcommand('Set up the QMK-provided toolchains.', hidden=False if cli.config.user.developer else True)
+def toolchain_setup(cli):
+    """Set up the QMK-provided toolchains.
+    """
+
+    # Remove the toolchains directory if it exists already
+    if QMK_TOOLCHAINS_PATH.exists():
+        cli.log.info(f'Removing existing toolchains at `{QMK_TOOLCHAINS_PATH}`...')
+        shutil.rmtree(QMK_TOOLCHAINS_PATH)
+
+    url = f'https://api.github.com/repos/qmk/qmk_toolchains/releases/tags/{cli.args.tag}'
+    payload = cached_get(url)
+    if payload.status_code != 200:
+        cli.log.error(f'Failed to fetch toolchain metadata from `{url}`.')
+        return
+    downloadables = filter(lambda x: _os_arch() in x['name'], payload.json()['assets'])
+    for downloadable in downloadables:
+        if not Path(downloadable["name"]).exists():
+            download_with_progress(downloadable['browser_download_url'], downloadable["name"])
+        cli.log.info(f'Extracting `{downloadable["name"]}` to `{QMK_TOOLCHAINS_PATH}`...')
+        QMK_TOOLCHAINS_PATH.mkdir(parents=True, exist_ok=True)
+        cli.run(['tar', 'xf', downloadable["name"], '-C', QMK_TOOLCHAINS_PATH, '--strip-components=1'], capture_output=False, check=True, stdin=subprocess.DEVNULL)
+
+    os.environ['PATH'] = f'{QMK_TOOLCHAINS_PATH}/bin{os.pathsep}{os.environ["PATH"]}'
+
+    for gcc_exe in ['avr-gcc', 'arm-none-eabi-gcc', 'riscv32-unknown-elf-gcc']:
+        cli.log.info(f'{gcc_exe:24s}: {_gcc_version_str(gcc_exe)}')

+ 6 - 0
lib/python/qmk/constants.py

@@ -21,6 +21,12 @@ QMK_FIRMWARE_UPSTREAM = 'qmk/qmk_firmware'
 # This is the number of directories under `qmk_firmware/keyboards` that will be traversed. This is currently a limitation of our make system.
 MAX_KEYBOARD_SUBFOLDERS = 5
 
+# Intended qmk/qmk_toolchains release to deploy
+QMK_TOOLCHAINS_TAG = 'v14.1.0-4'
+
+# QMK Toolchains path
+QMK_TOOLCHAINS_PATH = Path('~/.local/qmk/toolchains').expanduser()
+
 # Supported processor types
 CHIBIOS_PROCESSORS = 'cortex-m0', 'cortex-m0plus', 'cortex-m3', 'cortex-m4', 'MKL26Z64', 'MK20DX128', 'MK20DX256', 'MK64FX512', 'MK66FX1M0', 'RP2040', 'STM32F042', 'STM32F072', 'STM32F103', 'STM32F303', 'STM32F401', 'STM32F405', 'STM32F407', 'STM32F411', 'STM32F446', 'STM32G431', 'STM32G474', 'STM32H723', 'STM32H733', 'STM32L412', 'STM32L422', 'STM32L432', 'STM32L433', 'STM32L442', 'STM32L443', 'GD32VF103', 'WB32F3G71', 'WB32FQ95'
 LUFA_PROCESSORS = 'at90usb162', 'atmega16u2', 'atmega32u2', 'atmega16u4', 'atmega32u4', 'at90usb646', 'at90usb647', 'at90usb1286', 'at90usb1287', None

+ 19 - 0
lib/python/qmk/util.py

@@ -3,6 +3,7 @@
 import contextlib
 import multiprocessing
 import sys
+from pathlib import Path
 
 from milc import cli
 
@@ -27,6 +28,24 @@ def maybe_exit_config(should_exit: bool = True, should_reraise: bool = False):
     maybe_exit_reraise = should_reraise
 
 
+def cached_get(*args, **kwargs):
+    import requests_cache
+    session = requests_cache.CachedSession(Path('~/.local/qmk/qmk_requests.sqlite').expanduser(), expire_after=300, cache_control=True)
+    return session.get(*args, **kwargs)
+
+
+def download_with_progress(url, filename):
+    import requests
+    import tqdm
+    response = requests.get(url, stream=True)
+    total_size = int(response.headers.get('content-length', 0))
+    with tqdm.tqdm(desc=filename, total=total_size, unit='B', unit_scale=True) as pbar:
+        with open(filename, 'wb') as file:
+            for data in response.iter_content(1024):
+                file.write(data)
+                pbar.update(len(data))
+
+
 @contextlib.contextmanager
 def parallelize():
     """Returns a function that can be used in place of a map() call.

+ 4 - 1
requirements.txt

@@ -6,8 +6,11 @@ dotty-dict
 hid
 hjson
 jsonschema>=4
-milc>=1.4.2
+milc>=1.8.0
 pygments
 pyserial
 pyusb
 pillow
+requests
+requests-cache
+tqdm