From f393ad005385f48cb07fec38c0c50b40ff735317 Mon Sep 17 00:00:00 2001 From: Nick Brassel Date: Wed, 26 Nov 2025 07:28:03 +1100 Subject: [PATCH] Use bootstrapper for build environment installation. (#210) * Use bootstrapper for build environment installation. * Publishing tags. * Path. * Minimisation of final image. * Minimisation of final image. * QMK distribution $PATH manipulation, to match qmk_firmware. * QMK distribution $PATH manipulation, to match qmk_firmware. * Fixup library search path on macOS. * Escape hatch for path prefix. * Add new environment variables. --- .github/workflows/docker-republish.yml | 4 ++++ Dockerfile | 25 ++++++++++++++++++++----- qmk_cli/script_qmk.py | 12 ++++++++++++ qmk_cli/subcommands/console.py | 14 ++++++++++++++ qmk_cli/subcommands/env.py | 4 +++- setup.cfg | 1 + 6 files changed, 54 insertions(+), 6 deletions(-) diff --git a/.github/workflows/docker-republish.yml b/.github/workflows/docker-republish.yml index 3adaad0..2c727f1 100644 --- a/.github/workflows/docker-republish.yml +++ b/.github/workflows/docker-republish.yml @@ -54,3 +54,7 @@ jobs: tags: | ghcr.io/qmk/qmk_cli:latest qmkfm/qmk_cli:latest + ghcr.io/qmk/qmk_cli:${{ github.sha }} + qmkfm/qmk_cli:${{ github.sha }} + ghcr.io/qmk/qmk_cli:${{ steps.previoustag.outputs.tag }} + qmkfm/qmk_cli:${{ steps.previoustag.outputs.tag }} diff --git a/Dockerfile b/Dockerfile index 8491575..3dd4162 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,13 +1,28 @@ -FROM ghcr.io/qmk/qmk_base_container:latest +FROM ghcr.io/qmk/qmk_base_container:latest as builder # Copy package in ADD dist /tmp/dist +# Install QMK CLI via bootstrap script +ARG TARGETPLATFORM +RUN /bin/bash -c "curl -fsSL https://install.qmk.fm | sh -s -- --confirm" + +# Do the equivalent of entering the virtual environment +ENV PATH=/home/qmk/.local/share/uv/tools/qmk/bin:/home/qmk/.local/bin:$PATH \ + VIRTUAL_ENV=/home/qmk/.local/share/uv/tools/qmk + # Install python packages RUN python3 -m pip uninstall -y qmk || true RUN python3 -m pip install --upgrade pip setuptools wheel nose2 && \ - python3 -m pip install /tmp/dist/qmk-*.whl && \ - rm -rf /tmp/dist + python3 -m pip install /tmp/dist/qmk-*.whl -# Set the default location for qmk_firmware -ENV QMK_HOME /qmk_firmware +# 2nd stage so we don't have /tmp/dist in the final image +FROM ghcr.io/qmk/qmk_base_container:latest + +# Do the equivalent of entering the virtual environment +ENV PATH=/home/qmk/.local/share/uv/tools/qmk/bin:/home/qmk/.local/bin:$PATH \ + VIRTUAL_ENV=/home/qmk/.local/share/uv/tools/qmk + +ARG TARGETPLATFORM +COPY --from=builder /home/qmk /home/qmk +COPY --from=builder /usr/lib/udev/rules.d/50-qmk.rules /usr/lib/udev/rules.d/50-qmk.rules diff --git a/qmk_cli/script_qmk.py b/qmk_cli/script_qmk.py index 0a0203e..420f29e 100644 --- a/qmk_cli/script_qmk.py +++ b/qmk_cli/script_qmk.py @@ -4,6 +4,7 @@ This program can be run from anywhere, with or without a qmk_firmware repository. If a qmk_firmware repository can be located we will use that to augment our available subcommands. """ import os +from pathlib import Path import shlex import subprocess import sys @@ -11,10 +12,21 @@ from platform import platform from traceback import print_exc import milc +import platformdirs from . import __version__ from .helpers import find_qmk_firmware, is_qmk_firmware, find_qmk_userspace, is_qmk_userspace +# Ensure the QMK distribution is on the `$PATH` if present. This must be kept in sync with qmk/qmk_firmware. +_default_distrib_path = milc.cli.run(['cygpath', '-w', '/opt/qmk']).stdout.strip() if 'windows' in platform().lower() else platformdirs.user_data_dir('qmk') # this must be kept in sync with the default values inside `util/env-bootstrap.sh`! +QMK_DISTRIB_DIR = Path(os.environ.get('QMK_DISTRIB_DIR', _default_distrib_path)) +if QMK_DISTRIB_DIR.exists(): + os.environ['PATH'] = str(QMK_DISTRIB_DIR / 'bin') + os.pathsep + os.environ['PATH'] + +# Prepend any user-defined path prefix +if 'QMK_PATH_PREFIX' in os.environ: + os.environ['PATH'] = os.environ['QMK_PATH_PREFIX'] + os.pathsep + os.environ['PATH'] + milc.cli.milc_options(version=__version__) milc.EMOJI_LOGLEVELS['INFO'] = '{fg_blue}Ψ{style_reset_all}' diff --git a/qmk_cli/subcommands/console.py b/qmk_cli/subcommands/console.py index 0d8e62f..07a3d10 100644 --- a/qmk_cli/subcommands/console.py +++ b/qmk_cli/subcommands/console.py @@ -96,7 +96,19 @@ def import_usb_core(): def import_hid(): """Attempts to import the hid module. """ + import os + old_cwd = os.getcwd() try: + # macOS library search paths don't include homebrew by default when launched via a `uv`-based deployment. + # The `hid` python module does a search and attempts to load a set of known library names, but doesn't qualify the path, nor gives the option to do so. + # To work around this, we chdir to the homebrew lib directory before importing hid, then chdir back to where we were. + if 'darwin' in platform().lower() or 'macos' in platform().lower(): + for path in ('/opt/homebrew/lib', '/usr/local/lib'): + lib_search = Path(path) / 'libhidapi.dylib' + if lib_search.exists(): + os.chdir(path) + break + import hid return hid @@ -107,6 +119,8 @@ def import_hid(): return import_hid() raise + finally: + os.chdir(old_cwd) class MonitorDevice(object): diff --git a/qmk_cli/subcommands/env.py b/qmk_cli/subcommands/env.py index 83323d8..0f3960a 100644 --- a/qmk_cli/subcommands/env.py +++ b/qmk_cli/subcommands/env.py @@ -15,7 +15,9 @@ def env(cli): data = { 'QMK_HOME': home, 'QMK_FIRMWARE': home if is_qmk_firmware(Path(home)) else "", - 'QMK_USERSPACE': userspace if is_qmk_userspace(Path(userspace)) else "" + 'QMK_USERSPACE': userspace if is_qmk_userspace(Path(userspace)) else "", + 'QMK_DISTRIB_DIR': os.environ.get('QMK_DISTRIB_DIR', ""), + 'QMK_PATH_PREFIX': os.environ.get('QMK_PATH_PREFIX', "") } # Now munge the current cli config diff --git a/setup.cfg b/setup.cfg index c3da40d..14ce61d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -38,6 +38,7 @@ classifiers = install_requires = hid milc>=1.9.0 + platformdirs pyusb # qmk_firmware packages dotty-dict