Compare commits
157 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2de8cf606c | |||
| 1f6bb7dab0 | |||
| 2eb3279945 | |||
| 8fd7af98d9 | |||
| 240ccd781a | |||
| af9e8063f6 | |||
| 3054dad82b | |||
| 39a271132b | |||
| c19c21449e | |||
| f245cb7899 | |||
| 0b5866159a | |||
| 0dd2280781 | |||
| c049da0c6f | |||
| 7b69bd6f11 | |||
| c594e5df09 | |||
| 915bb40473 | |||
| af1403fc41 | |||
| 13e5f5c855 | |||
| f393ad0053 | |||
| 947e49a15d | |||
| cd3ed07cd0 | |||
| 558e248e20 | |||
| cd1e98e9d0 | |||
| 8c2ae297ed | |||
| 7be310f364 | |||
| 2316a34ce9 | |||
| 36c23e3de6 | |||
| b6bc144e9e | |||
| 93e629608e | |||
| cacd0fabbc | |||
| d9f9b5a27d | |||
| 4d4892f58a | |||
| ffe20fb63e | |||
| b384f871e8 | |||
| f2b8522c89 | |||
| 75dcb0e475 | |||
| 1e98a2e459 | |||
| c53fb88479 | |||
| fed1e39611 | |||
| ed0d5699ff | |||
| b1542e4a62 | |||
| 9c7c371419 | |||
| ae45bdeb02 | |||
| d20af71118 | |||
| 8f85a8856f | |||
| 87c356773f | |||
| 27a97e4ebf | |||
| 53d8dab86f | |||
| 86a3decd0a | |||
| d3917b10e7 | |||
| 86b3b74b21 | |||
| bb385f7786 | |||
| e07ad4c9ee | |||
| 1e03b45986 | |||
| 7a1b556b25 | |||
| d7911ffaf7 | |||
| daa7f54dd0 | |||
| 753136ade4 | |||
| 579763620d | |||
| cbc22adb76 | |||
| 82bb65bd52 | |||
| 671e553239 | |||
| fa4bcc034d | |||
| 0a048da8a4 | |||
| 7808a1166c | |||
| b9b2ebbe13 | |||
| b4b53e80eb | |||
| 2212fdabf7 | |||
| 1e8a174e0c | |||
| 6868d5ba6f | |||
| e203006537 | |||
| 1c72e1e564 | |||
| aecd02e850 | |||
| 5019970d01 | |||
| 446547df04 | |||
| dea98e71b8 | |||
| ea62fa1cec | |||
| 32129ee5a2 | |||
| 6638c13c36 | |||
| 98ab6a52f6 | |||
| f9d3020616 | |||
| 1c98cf7053 | |||
| 6b98cb311f | |||
| 8a4bf2d437 | |||
| db645b7db8 | |||
| 3f4c491202 | |||
| 39d3adddff | |||
| d557058db7 | |||
| 3126bdb90a | |||
| d2f73683f8 | |||
| 83978d4c68 | |||
| bb00940631 | |||
| 29bcd3aa01 | |||
| 42481f2db3 | |||
| 928d22d370 | |||
| 169e12a4c6 | |||
| 26ad0cd2b9 | |||
| 63fc8ff722 | |||
| bb912d3473 | |||
| 7cb41c2daa | |||
| c7af299b48 | |||
| c0698e953b | |||
| 54bf8328a1 | |||
| ace9daea3e | |||
| 255990f45b | |||
| 1742e84da5 | |||
| 2b4021eaa2 | |||
| 670ead38ad | |||
| c12c4a6e0d | |||
| bd5e3b5cb4 | |||
| 82fede52ac | |||
| 76fa43cf78 | |||
| 7ec12a8b9d | |||
| b714ea60c3 | |||
| 1797e83de8 | |||
| 593ae652e6 | |||
| 963818be15 | |||
| 47b977f9b2 | |||
| 8c0f655483 | |||
| e0aacc9dc7 | |||
| 24472967eb | |||
| 1a2e43aa58 | |||
| ffed902922 | |||
| 325868514b | |||
| dbfdd2dec5 | |||
| de7f8f6b70 | |||
| 552fe67430 | |||
| de28654c6d | |||
| 02c190894a | |||
| 7df9a61f89 | |||
| dad8b3c15a | |||
| e24448ed18 | |||
| 9befc84294 | |||
| bb489e8ec2 | |||
| f208f0228c | |||
| e4e8f987e1 | |||
| 9f74f1ca9c | |||
| bb1a9e43cf | |||
| 501182017e | |||
| 710dc3e951 | |||
| 12242d94c2 | |||
| 19336ff9ee | |||
| 8e1de74918 | |||
| 137ed60056 | |||
| d320265936 | |||
| 97346a44bc | |||
| 1e0b08250d | |||
| 6a928e9322 | |||
| 5315431058 | |||
| aa11928c03 | |||
| 369c3e150a | |||
| c4e82e0740 | |||
| 95152b6f7f | |||
| 3bd1a79bfa | |||
| 42fcfc3930 | |||
| 60f2bb743d | |||
| f8902bd258 |
@@ -0,0 +1,12 @@
|
||||
[bumpversion]
|
||||
current_version = 1.2.0
|
||||
commit = True
|
||||
tag = True
|
||||
tag_name = {new_version}
|
||||
message = New release: {current_version} → {new_version}
|
||||
|
||||
[bumpversion:file:.bumpversion.cfg]
|
||||
|
||||
[bumpversion:file:qmk_cli/__init__.py]
|
||||
|
||||
[bumpversion:file:setup.cfg]
|
||||
@@ -7,6 +7,8 @@ updates:
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
reviewers:
|
||||
- "qmk/collaborators"
|
||||
- package-ecosystem: "pip" # See documentation for possible values
|
||||
directory: "/" # Location of package manifests
|
||||
schedule:
|
||||
|
||||
@@ -7,20 +7,38 @@ on:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
test_cli_base_container:
|
||||
runs-on: ubuntu-latest
|
||||
container: ghcr.io/qmk/qmk_base_container
|
||||
|
||||
env:
|
||||
QMK_HOME: ~/qmk_firmware
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Install dependencies
|
||||
run: apt-get update && apt-get install -y python3-venv
|
||||
|
||||
- name: Run ci_tests
|
||||
run: ./ci_tests -a
|
||||
|
||||
test_cli_linux_macos:
|
||||
needs: test_cli_base_container
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
env:
|
||||
QMK_HOME: ~/qmk_firmware
|
||||
strategy:
|
||||
matrix:
|
||||
os: [macos-latest, ubuntu-latest]
|
||||
python-version: [3.7, 3.8, 3.9]
|
||||
python-version: ['3.9', '3.10', '3.11', '3.12', '3.13', '3.14']
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2.3.4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v2.2.2
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
@@ -28,29 +46,32 @@ jobs:
|
||||
run: ./ci_tests -a
|
||||
|
||||
test_cli_win:
|
||||
needs: test_cli_base_container
|
||||
|
||||
runs-on: windows-latest
|
||||
env:
|
||||
QMK_HOME: $HOME/qmk_firmware
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2.3.4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: (MSYS2) Setup and install dependencies
|
||||
uses: msys2/setup-msys2@v2
|
||||
with:
|
||||
update: true
|
||||
install: git mingw-w64-x86_64-toolchain mingw-w64-x86_64-python-pip
|
||||
install: git mingw-w64-x86_64-toolchain mingw-w64-x86_64-python-pip mingw-w64-x86_64-python-build mingw-w64-x86_64-python-pillow mingw-w64-x86_64-rust
|
||||
|
||||
# Upgrade pip due to msys packaging + pypa/build/pull/736 issues
|
||||
- name: (MSYS2) Install Python dependencies
|
||||
shell: msys2 {0}
|
||||
run: |
|
||||
python3 -m pip install -U setuptools wheel
|
||||
python3 -m pip install --break-system-packages --force-reinstall --upgrade pip
|
||||
|
||||
- name: (MSYS2) Install QMK CLI from source
|
||||
shell: msys2 {0}
|
||||
run: |
|
||||
python3 -m pip install -U build
|
||||
python3 -m build
|
||||
python3 -m pip install dist/qmk-*.tar.gz
|
||||
python3 -m pip install --break-system-packages dist/qmk-*.tar.gz
|
||||
- name: (MSYS2) Run qmk setup -y
|
||||
shell: msys2 {0}
|
||||
run: qmk setup -y
|
||||
|
||||
@@ -39,11 +39,11 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2.3.4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
uses: github/codeql-action/init@v4
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
@@ -54,7 +54,7 @@ jobs:
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v1
|
||||
uses: github/codeql-action/autobuild@v4
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
@@ -68,4 +68,4 @@ jobs:
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
||||
uses: github/codeql-action/analyze@v4
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
name: Test Docker Image
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
test:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.9'
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v4
|
||||
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Run ci_tests
|
||||
run: ./ci_tests
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python3 -m pip install --upgrade pip
|
||||
pip install setuptools wheel
|
||||
pip install -r requirements-dev.txt
|
||||
|
||||
- name: Build Python
|
||||
run: |
|
||||
python3 -m build
|
||||
|
||||
- name: Build Container
|
||||
uses: docker/build-push-action@v7
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64
|
||||
@@ -0,0 +1,60 @@
|
||||
name: Rebuild Docker Image
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
redeploy:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.9'
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v4
|
||||
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: 'Get Previous tag'
|
||||
id: previoustag
|
||||
uses: "WyriHaximus/github-action-get-previous-tag@v2"
|
||||
|
||||
- name: Download previous artifact
|
||||
run: |
|
||||
pip download -d "${GITHUB_WORKSPACE}/dist" qmk=="${{ steps.previoustag.outputs.tag }}"
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
|
||||
|
||||
- name: Build and Push to Docker Hub
|
||||
uses: docker/build-push-action@v7
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
platforms: linux/amd64,linux/arm64
|
||||
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 }}
|
||||
@@ -17,48 +17,74 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2.3.4
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2.2.2
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.7'
|
||||
python-version: '3.9'
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v4
|
||||
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Run ci_tests
|
||||
run: ./ci_tests
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python3 -m pip install --upgrade pip
|
||||
pip install setuptools wheel
|
||||
pip install setuptools wheel
|
||||
pip install -r requirements-dev.txt
|
||||
|
||||
- name: Bump version
|
||||
run: |
|
||||
git config --local user.email "hello@qmk.fm"
|
||||
git config --local user.name "QMK Bot"
|
||||
bumpversion --dry-run --list ${{ github.event.inputs.version_part }} | grep new_version= | sed "s/new_version/NEW_CLI_VERSION/" >> "$GITHUB_ENV"
|
||||
bumpversion ${{ github.event.inputs.version_part }}
|
||||
|
||||
- name: Push changes
|
||||
uses: ad-m/github-push-action@master
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
branch: master
|
||||
tags: true
|
||||
|
||||
- name: Build and publish
|
||||
env:
|
||||
TWINE_USERNAME: qmk
|
||||
TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
|
||||
TWINE_USERNAME: __token__
|
||||
TWINE_PASSWORD: ${{ secrets.PYPI_APITOKEN }}
|
||||
run: |
|
||||
python3 -m build
|
||||
twine upload dist/*
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@v1.10.0
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
|
||||
|
||||
- name: Build and Push to Docker Hub
|
||||
uses: docker/build-push-action@v2.6.1
|
||||
uses: docker/build-push-action@v7
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
tags: qmkfm/qmk_cli:latest
|
||||
- name: Trigger OS package builds
|
||||
run: ./trigger_packages
|
||||
env:
|
||||
QMK_BOT_TOKEN: ${{ secrets.QMK_BOT_TOKEN }}
|
||||
platforms: linux/amd64,linux/arm64
|
||||
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:${{ env.NEW_CLI_VERSION }}
|
||||
qmkfm/qmk_cli:${{ env.NEW_CLI_VERSION }}
|
||||
|
||||
|
||||
+27
-5
@@ -1,13 +1,35 @@
|
||||
FROM qmkfm/base_container
|
||||
FROM ghcr.io/qmk/qmk_base_container:latest as builder
|
||||
|
||||
# Copy package in
|
||||
ADD dist /tmp/dist
|
||||
|
||||
# Install QMK CLI via bootstrap script
|
||||
RUN mkdir -p /opt/uv/bin \
|
||||
&& export UV_PYTHON_INSTALL_DIR=/opt/uv/bin \
|
||||
&& /bin/bash -c "curl -fsSL https://install.qmk.fm | sh -s -- --confirm --uv-install-dir=/usr/local/bin --uv-tool-dir=/opt/uv/tools --qmk-distrib-dir=/opt/qmk"
|
||||
|
||||
# Do the equivalent of entering the virtual environment
|
||||
ENV PATH=/opt/qmk/bin:/opt/uv/tools/qmk/bin:/usr/local/bin:$PATH \
|
||||
VIRTUAL_ENV=/opt/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
|
||||
|
||||
# Allow the uv-installed python to be usable by others
|
||||
RUN chmod -R go=u,go-w /opt/uv /opt/qmk
|
||||
|
||||
# 2nd stage so we don't have /tmp/dist in the final image
|
||||
FROM ghcr.io/qmk/qmk_base_container:latest
|
||||
|
||||
COPY --from=builder /opt/uv /opt/uv
|
||||
COPY --from=builder /opt/qmk /opt/qmk
|
||||
|
||||
# Do the equivalent of entering the virtual environment
|
||||
ENV PATH=/opt/qmk/bin:/opt/uv/tools/qmk/bin:$PATH \
|
||||
VIRTUAL_ENV=/opt/uv/tools/qmk \
|
||||
QMK_DISTRIB_DIR=/opt/qmk \
|
||||
QMK_HOME=/qmk_firmware \
|
||||
QMK_USERSPACE=/qmk_userspace
|
||||
|
||||
# Set the default location for qmk_firmware
|
||||
ENV QMK_HOME /qmk_firmware
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
# QMK CLI
|
||||
[](https://github.com/qmk/qmk_cli/actions?query=workflow%3A%22CLI+Setup%22)
|
||||
|
||||
A program to help users work with [QMK Firmware](https://qmk.fm/).
|
||||
> A program to help users work with [QMK Firmware](https://qmk.fm/).
|
||||
|
||||
# Features
|
||||
[](https://github.com/qmk/qmk_cli/tags)
|
||||
[](https://github.com/qmk/qmk_cli/actions?query=workflow%3ACLI+branch%3Amaster)
|
||||
[](https://discord.gg/qmk)
|
||||
|
||||
## Features
|
||||
|
||||
* Interact with your qmk_firmware tree from any location
|
||||
* Use `qmk clone` to pull down anyone's `qmk_firmware` fork
|
||||
@@ -17,9 +20,38 @@ A program to help users work with [QMK Firmware](https://qmk.fm/).
|
||||
* `qmk lint`
|
||||
* ...and many more!
|
||||
|
||||
# Quickstart
|
||||
## Quickstart
|
||||
|
||||
* `python3 -m pip install qmk`
|
||||
* `qmk setup`
|
||||
Follow our [documentation to get started](https://docs.qmk.fm/newbs_getting_started).
|
||||
|
||||
## Packages
|
||||
|
||||
We provide "install and go" packages for many Operating Systems.
|
||||
|
||||
### macOS
|
||||
|
||||
Using [Homebrew](https://brew.sh):
|
||||
|
||||
brew install qmk/qmk/qmk
|
||||
|
||||
### Windows
|
||||
|
||||
Download our custom MSYS2 installer here: https://msys.qmk.fm/
|
||||
|
||||
## Building
|
||||
|
||||
We follow PEP517, you can install using [build](https://pypi.org/project/build/):
|
||||
|
||||
Setup:
|
||||
|
||||
python3 -m pip install build
|
||||
|
||||
Build:
|
||||
|
||||
python3 -m build
|
||||
|
||||
You can read more about working with PEP517 packages in the [Python Packaging User Guide](https://packaging.python.org/guides/distributing-packages-using-setuptools/#packaging-your-project).
|
||||
|
||||
## Documentation
|
||||
|
||||
Full documentation: <https://docs.qmk.fm/#/tutorial>
|
||||
|
||||
@@ -36,8 +36,8 @@ python3 -m pip install -r requirements-dev.txt
|
||||
# Ensure that qmk works
|
||||
echo "*** Testing 'qmk clone -h'"
|
||||
qmk clone -h
|
||||
echo "*** Testing 'qmk config -a'"
|
||||
qmk config -a
|
||||
#echo "*** Testing 'qmk config -a'" # Test disabled as `milc` at least 1.6.8+ returns False and thus non-zero exit code
|
||||
#qmk config -a
|
||||
echo "*** Testing 'qmk setup -n'"
|
||||
qmk setup -n
|
||||
|
||||
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
"""A program to help you work with qmk_firmware."""
|
||||
|
||||
__version__ = '0.3.1'
|
||||
__version__ = '1.2.0'
|
||||
|
||||
+7
-12
@@ -4,10 +4,6 @@ import subprocess
|
||||
|
||||
from milc import cli
|
||||
|
||||
default_repo = 'qmk_firmware'
|
||||
default_fork = 'qmk/' + default_repo
|
||||
default_branch = 'master'
|
||||
|
||||
|
||||
def git_clone(url, destination, branch):
|
||||
git_clone = [
|
||||
@@ -18,7 +14,7 @@ def git_clone(url, destination, branch):
|
||||
url,
|
||||
str(destination),
|
||||
]
|
||||
cli.log.debug('Git clone command: %s', git_clone)
|
||||
cli.log.debug(f'Git clone command: {git_clone}', )
|
||||
|
||||
try:
|
||||
with subprocess.Popen(git_clone, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, bufsize=1, universal_newlines=True, encoding='utf-8') as p:
|
||||
@@ -28,13 +24,12 @@ def git_clone(url, destination, branch):
|
||||
except Exception as e:
|
||||
git_cmd = ' '.join([s.replace(' ', r'\ ') for s in git_clone])
|
||||
|
||||
cli.log.error("Could not run '%s': %s: %s", git_cmd, e.__class__.__name__, e)
|
||||
cli.log.error(f'Could not run "{git_cmd}": {e.__class__}: {e}')
|
||||
return False
|
||||
|
||||
if p.returncode == 0:
|
||||
cli.log.info('Successfully cloned %s to %s!', url, destination)
|
||||
return True
|
||||
|
||||
else:
|
||||
cli.log.error('git clone exited %d', p.returncode)
|
||||
if p.returncode != 0:
|
||||
cli.log.error(f'git clone exited {p.returncode}')
|
||||
return False
|
||||
|
||||
cli.log.info(f'Successfully cloned {url} to {destination}!')
|
||||
return True
|
||||
|
||||
+59
-2
@@ -1,13 +1,23 @@
|
||||
"""Useful helper functions.
|
||||
"""
|
||||
import os
|
||||
import json
|
||||
from functools import lru_cache
|
||||
from importlib.util import find_spec
|
||||
from pathlib import Path
|
||||
|
||||
from milc import cli
|
||||
|
||||
|
||||
def AbsPath(arg):
|
||||
"""Resolve relative paths to the original working directory.
|
||||
"""
|
||||
arg = Path(arg)
|
||||
if not arg.is_absolute():
|
||||
return (Path(os.environ['ORIG_CWD']) / arg).absolute()
|
||||
|
||||
return arg
|
||||
|
||||
|
||||
def is_qmk_firmware(qmk_firmware):
|
||||
"""Returns True if the given Path() is a qmk_firmware clone.
|
||||
"""
|
||||
@@ -26,7 +36,7 @@ def is_qmk_firmware(qmk_firmware):
|
||||
return True
|
||||
|
||||
|
||||
@lru_cache(maxsize=2)
|
||||
@lru_cache(maxsize=1)
|
||||
def find_qmk_firmware():
|
||||
"""Look for qmk_firmware in the usual places.
|
||||
|
||||
@@ -47,6 +57,7 @@ def find_qmk_firmware():
|
||||
return Path.home() / 'qmk_firmware'
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def in_qmk_firmware():
|
||||
"""Returns the path to the qmk_firmware we are currently in, or None if we are not inside qmk_firmware.
|
||||
"""
|
||||
@@ -58,3 +69,49 @@ def in_qmk_firmware():
|
||||
# Move up a directory before the next iteration
|
||||
cur_dir = cur_dir / '..'
|
||||
cur_dir = cur_dir.resolve()
|
||||
|
||||
|
||||
def is_qmk_userspace(qmk_userspace):
|
||||
"""Returns True if the given Path() is a qmk_userspace clone.
|
||||
"""
|
||||
path = qmk_userspace / 'qmk.json'
|
||||
if not path.exists():
|
||||
return False
|
||||
|
||||
try:
|
||||
return 'userspace_version' in json.loads(path.read_text(encoding="UTF-8"))
|
||||
except json.decoder.JSONDecodeError as e:
|
||||
return False
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def find_qmk_userspace():
|
||||
"""Look for qmk_userspace in the usual places.
|
||||
"""
|
||||
if in_qmk_userspace():
|
||||
return in_qmk_userspace()
|
||||
|
||||
if 'QMK_USERSPACE' in os.environ:
|
||||
path = Path(os.environ['QMK_USERSPACE']).expanduser()
|
||||
if path.exists():
|
||||
return path.resolve()
|
||||
return path
|
||||
|
||||
if cli.config.user.overlay_dir:
|
||||
return Path(cli.config.user.overlay_dir).expanduser().resolve()
|
||||
|
||||
return Path.home() / 'qmk_userspace'
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def in_qmk_userspace():
|
||||
"""Returns the path to the qmk_userspace we are currently in, or None if we are not inside qmk_userspace.
|
||||
"""
|
||||
cur_dir = Path.cwd()
|
||||
while len(cur_dir.parents) > 0:
|
||||
if is_qmk_userspace(cur_dir):
|
||||
return cur_dir
|
||||
|
||||
# Move up a directory before the next iteration
|
||||
cur_dir = cur_dir / '..'
|
||||
cur_dir = cur_dir.resolve()
|
||||
|
||||
+38
-19
@@ -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,16 +12,38 @@ 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
|
||||
from .helpers import find_qmk_firmware, is_qmk_firmware, find_qmk_userspace, is_qmk_userspace
|
||||
|
||||
milc.set_metadata(version=__version__)
|
||||
|
||||
def _get_default_distrib_path():
|
||||
if 'windows' in platform().lower():
|
||||
try:
|
||||
result = milc.cli.run(['cygpath', '-w', '/opt/qmk'])
|
||||
if result.returncode == 0:
|
||||
return result.stdout.strip()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return platformdirs.user_data_dir('qmk')
|
||||
|
||||
|
||||
# Ensure the QMK distribution is on the `$PATH` if present. This must be kept in sync with qmk/qmk_firmware.
|
||||
QMK_DISTRIB_DIR = Path(os.environ.get('QMK_DISTRIB_DIR', _get_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}'
|
||||
|
||||
# These must happen after the milc.set_metadata() call
|
||||
import milc.subcommand.config # noqa, must come after milc.set_metadata()
|
||||
from milc.questions import yesno
|
||||
# These must happen after the milc.milc_options() call
|
||||
import milc.subcommand.config # noqa, must come after milc.milc_options()
|
||||
|
||||
|
||||
@milc.cli.entrypoint('CLI wrapper for running QMK commands.')
|
||||
@@ -46,21 +69,17 @@ def main():
|
||||
"""Setup the environment before dispatching to the entrypoint.
|
||||
"""
|
||||
# Warn if they use an outdated python version
|
||||
if sys.version_info < (3, 7):
|
||||
if sys.version_info < (3, 9):
|
||||
print('Warning: Your Python version is out of date! Some subcommands may not work!')
|
||||
print('Please upgrade to Python 3.7 or later.')
|
||||
|
||||
if 'windows' in platform().lower():
|
||||
msystem = os.environ.get('MSYSTEM', '')
|
||||
|
||||
if 'mingw64' not in sys.executable or 'MINGW64' not in msystem:
|
||||
print('ERROR: It seems you are not using the MINGW64 terminal.')
|
||||
print('Please close this terminal and open a new MSYS2 MinGW 64-bit terminal.')
|
||||
print('Python: %s, MSYSTEM: %s' % (sys.executable, msystem))
|
||||
exit(1)
|
||||
print('Please upgrade to Python 3.9 or later.')
|
||||
|
||||
# Environment setup
|
||||
qmk_userspace = find_qmk_userspace()
|
||||
qmk_firmware = find_qmk_firmware()
|
||||
if is_qmk_userspace(qmk_userspace):
|
||||
os.environ['QMK_USERSPACE'] = str(qmk_userspace)
|
||||
elif 'QMK_USERSPACE' in os.environ: # Failed to find valid userspace, including what was in the environment if specified -- wipe any environment variable if present as it's clearly invalid.
|
||||
os.environ.pop('QMK_USERSPACE')
|
||||
os.environ['QMK_HOME'] = str(qmk_firmware)
|
||||
os.environ['ORIG_CWD'] = os.getcwd()
|
||||
|
||||
@@ -77,9 +96,9 @@ def main():
|
||||
|
||||
except ImportError as e:
|
||||
if qmk_firmware.name != 'qmk_firmware':
|
||||
print('Warning: %s does not end in "qmk_firmware". Do you need to set QMK_HOME to "%s/qmk_firmware"?' % (qmk_firmware, qmk_firmware))
|
||||
print(f'Warning: {qmk_firmware} does not end in "qmk_firmware". Do you need to set QMK_HOME to "{qmk_firmware}/qmk_firmware"?')
|
||||
|
||||
print('Error: %s: %s', (e.__class__.__name__, e))
|
||||
print(f'Error: {e.__class__}: {e}')
|
||||
print_exc()
|
||||
sys.exit(1)
|
||||
|
||||
@@ -91,7 +110,7 @@ def main():
|
||||
|
||||
elif return_code is not True and isinstance(return_code, int):
|
||||
if return_code < 0 or return_code > 255:
|
||||
milc.cli.log.error('Invalid return_code: %d', return_code)
|
||||
milc.cli.log.error(f'Invalid return_code: {return_code}')
|
||||
exit(255)
|
||||
|
||||
exit(return_code)
|
||||
|
||||
@@ -5,26 +5,24 @@ from pathlib import Path
|
||||
|
||||
from milc import cli
|
||||
from qmk_cli.git import git_clone
|
||||
from qmk_cli.helpers import AbsPath
|
||||
|
||||
default_repo = 'qmk_firmware'
|
||||
default_fork = 'qmk/' + default_repo
|
||||
default_branch = 'master'
|
||||
|
||||
|
||||
@cli.argument('--baseurl', default='https://github.com', help='The URL all git operations start from (Default: https://github.com)')
|
||||
@cli.argument('-b', '--branch', default=default_branch, help='The branch to clone. Default: %s' % default_branch)
|
||||
@cli.argument('destination', default=None, nargs='?', help='The directory to clone to. Default: (current directory)')
|
||||
@cli.argument('fork', default=default_fork, nargs='?', help='The qmk_firmware fork to clone. Default: %s' % default_fork)
|
||||
@cli.argument('--baseurl', arg_only=True, default='https://github.com', help='The URL all git operations start from (Default: https://github.com)')
|
||||
@cli.argument('-b', '--branch', arg_only=True, default=default_branch, help=f'The branch to clone. Default: {default_branch}')
|
||||
@cli.argument('destination', arg_only=True, default=Path(os.environ['ORIG_CWD']) / default_repo, type=AbsPath, nargs='?', help='The directory to clone to. Default: (current directory)')
|
||||
@cli.argument('fork', arg_only=True, default=default_fork, nargs='?', help=f'The qmk_firmware fork to clone. Default: {default_fork}')
|
||||
@cli.subcommand('Clone a qmk_firmware fork.')
|
||||
def clone(cli):
|
||||
if not cli.args.destination:
|
||||
cli.args.destination = os.path.join(os.environ['ORIG_CWD'], default_fork.split('/')[-1])
|
||||
git_url = '/'.join((cli.args.baseurl, cli.args.fork))
|
||||
|
||||
qmk_firmware = Path(cli.args.destination)
|
||||
git_url = '/'.join((cli.config.clone.baseurl, cli.args.fork))
|
||||
|
||||
if qmk_firmware.exists():
|
||||
cli.log.error('Destination already exists: %s', cli.args.destination)
|
||||
# Exists (but not an empty dir)
|
||||
if cli.args.destination.exists() and any(cli.args.destination.iterdir()):
|
||||
cli.log.error(f'Destination already exists: {cli.args.destination}')
|
||||
exit(1)
|
||||
|
||||
return git_clone(git_url, cli.args.destination, cli.config.clone.branch)
|
||||
return git_clone(git_url, cli.args.destination, cli.args.branch)
|
||||
|
||||
@@ -25,6 +25,8 @@ LOG_COLOR = {
|
||||
|
||||
KNOWN_BOOTLOADERS = {
|
||||
# VID , PID
|
||||
('03EB', '2045'): 'lufa-ms: LUFA Mass Storage Bootloader',
|
||||
('03EB', '2067'): 'qmk-hid: HID Bootloader',
|
||||
('03EB', '2FEF'): 'atmel-dfu: ATmega16U2',
|
||||
('03EB', '2FF0'): 'atmel-dfu: ATmega32U2',
|
||||
('03EB', '2FF3'): 'atmel-dfu: ATmega16U4',
|
||||
@@ -34,27 +36,47 @@ KNOWN_BOOTLOADERS = {
|
||||
('03EB', '2FFB'): 'atmel-dfu: AT90USB128',
|
||||
('03EB', '6124'): 'Microchip SAM-BA',
|
||||
('0483', 'DF11'): 'stm32-dfu: STM32 BOOTLOADER',
|
||||
('16C0', '05DC'): 'USBasp: USBaspLoader',
|
||||
('16C0', '05DF'): 'bootloadHID: HIDBoot',
|
||||
('16C0', '05DC'): 'usbasploader: USBaspLoader',
|
||||
('16C0', '05DF'): 'bootloadhid: HIDBoot',
|
||||
('16C0', '0478'): 'halfkay: Teensy Halfkay',
|
||||
('1B4F', '9203'): 'caterina: Pro Micro 3.3V',
|
||||
('1B4F', '9205'): 'caterina: Pro Micro 5V',
|
||||
('1B4F', '9207'): 'caterina: LilyPadUSB',
|
||||
('1C11', 'B007'): 'kiibohd: Kiibohd DFU Bootloader',
|
||||
('1EAF', '0003'): 'stm32duino: Maple 003',
|
||||
('1FFB', '0101'): 'caterina: Polou A-Star 32U4 Bootloader',
|
||||
('1FFB', '0101'): 'caterina: Pololu A-Star 32U4 Bootloader',
|
||||
('2341', '0036'): 'caterina: Arduino Leonardo',
|
||||
('2341', '0037'): 'caterina: Arduino Micro',
|
||||
('239A', '000C'): 'caterina: Adafruit Feather 32U4',
|
||||
('239A', '000D'): 'caterina: Adafruit ItsyBitsy 32U4 3v',
|
||||
('239A', '000E'): 'caterina: Adafruit ItsyBitsy 32U4 5v',
|
||||
('239A', '000E'): 'caterina: Adafruit ItsyBitsy 32U4 5v',
|
||||
('2A03', '0036'): 'caterina: Arduino Leonardo',
|
||||
('2A03', '0037'): 'caterina: Arduino Micro',
|
||||
('314B', '0106'): 'apm32-dfu: APM32 DFU ISP Mode'
|
||||
('314B', '0106'): 'apm32-dfu: APM32 DFU ISP Mode',
|
||||
('342D', 'DFA0'): 'wb32-dfu: WB32 Device in DFU Mode'
|
||||
}
|
||||
|
||||
|
||||
def _get_next_color():
|
||||
"""Cycles to the next available log color.
|
||||
"""
|
||||
color = LOG_COLOR['colors'][LOG_COLOR['next']]
|
||||
LOG_COLOR['next'] = (LOG_COLOR['next'] + 1) % len(LOG_COLOR['colors'])
|
||||
return color
|
||||
|
||||
|
||||
def _gen_header(dev):
|
||||
"""Generate a consistent logging segment.
|
||||
"""
|
||||
return f'{dev["color"]}{dev["manufacturer_string"]} {dev["product_string"]}{{style_reset_all}} ({dev["color"]}{int2hex(dev["vendor_id"])}:{int2hex(dev["product_id"])}:{dev["index"]}{{style_reset_all}})'
|
||||
|
||||
|
||||
def int2hex(number):
|
||||
"""Returns a string representation of the number as hex.
|
||||
"""
|
||||
return f'{number:04X}'
|
||||
|
||||
|
||||
def install_deps():
|
||||
"""Install the necessary dependencies for qmk console.
|
||||
"""
|
||||
@@ -67,9 +89,10 @@ def install_deps():
|
||||
elif 'windows' in this_platform:
|
||||
command = ['pacboy', 'sync', '--needed', '--noconfirm', '--disable-download-timeout', 'hidapi:x']
|
||||
else:
|
||||
cli.log.error('Unsupported platform: %s', this_platform)
|
||||
cli.log.error(f'Unsupported platform: {this_platform}')
|
||||
|
||||
if yesno("Would you like to run `%s` to install the necessary package?", ' '.join(command)):
|
||||
cmd_str = ' '.join(command)
|
||||
if yesno(f'Would you like to run "{cmd_str}" to install the necessary package?'):
|
||||
cli.run(command, capture_output=False)
|
||||
return True
|
||||
|
||||
@@ -83,7 +106,7 @@ def import_usb_core():
|
||||
return usb.core
|
||||
|
||||
except ImportError as e:
|
||||
cli.log.error('Could not import usb.core: %s', e)
|
||||
cli.log.error(f'Could not import usb.core: {e}')
|
||||
|
||||
if install_deps():
|
||||
return import_usb_core()
|
||||
@@ -94,17 +117,31 @@ 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
|
||||
|
||||
except ImportError as e:
|
||||
cli.log.error('Could not import hid: %s', e)
|
||||
cli.log.error(f'Could not import hid: {e}')
|
||||
|
||||
if install_deps():
|
||||
return import_hid()
|
||||
|
||||
raise
|
||||
finally:
|
||||
os.chdir(old_cwd)
|
||||
|
||||
|
||||
class MonitorDevice(object):
|
||||
@@ -115,8 +152,7 @@ class MonitorDevice(object):
|
||||
self.device = self.hid.Device(path=hid_device['path'])
|
||||
self.current_line = ''
|
||||
|
||||
cli.log.info('Console Connected: %(color)s%(manufacturer_string)s %(product_string)s{style_reset_all} (%(color)s%(vendor_id)04X:%(product_id)04X:%(index)d{style_reset_all})', hid_device)
|
||||
|
||||
cli.log.info(f'Console Connected: {_gen_header(hid_device)}')
|
||||
def read(self, size, encoding='ascii', timeout=1):
|
||||
"""Read size bytes from the device.
|
||||
"""
|
||||
@@ -136,12 +172,10 @@ class MonitorDevice(object):
|
||||
def run_forever(self):
|
||||
while True:
|
||||
try:
|
||||
message = {**self.hid_device, 'text': self.read_line()}
|
||||
identifier = (int2hex(message['vendor_id']), int2hex(message['product_id'])) if self.numeric else (message['manufacturer_string'], message['product_string'])
|
||||
message['identifier'] = ':'.join(identifier)
|
||||
message['ts'] = '{style_dim}{fg_green}%s{style_reset_all} ' % (strftime(cli.config.general.datetime_fmt),) if cli.args.timestamp else ''
|
||||
text = self.read_line()
|
||||
ts = f'{{style_dim}}{{fg_green}}{strftime(cli.config.general.datetime_fmt)}{{style_reset_all}}' if cli.args.timestamp else ''
|
||||
|
||||
cli.echo('%(ts)s%(color)s%(identifier)s:%(index)d{style_reset_all}: %(text)s' % message)
|
||||
cli.echo(f'{ts}{_gen_header(self.hid_device)}: {text}')
|
||||
|
||||
except self.hid.HIDException:
|
||||
break
|
||||
@@ -165,13 +199,12 @@ class FindDevices(object):
|
||||
try:
|
||||
for device in list(live_devices):
|
||||
if not live_devices[device]['thread'].is_alive():
|
||||
cli.log.info('Console Disconnected: %(color)s%(manufacturer_string)s %(product_string)s{style_reset_all} (%(color)s%(vendor_id)04X:%(product_id)04X:%(index)d{style_reset_all})', live_devices[device])
|
||||
cli.log.info(f'Console Disconnected: {_gen_header(live_devices[device])}')
|
||||
del live_devices[device]
|
||||
|
||||
for device in self.find_devices():
|
||||
if device['path'] not in live_devices:
|
||||
device['color'] = LOG_COLOR['colors'][LOG_COLOR['next']]
|
||||
LOG_COLOR['next'] = (LOG_COLOR['next'] + 1) % len(LOG_COLOR['colors'])
|
||||
device['color'] = _get_next_color()
|
||||
live_devices[device['path']] = device
|
||||
|
||||
try:
|
||||
@@ -180,9 +213,7 @@ class FindDevices(object):
|
||||
|
||||
device['thread'].start()
|
||||
except Exception as e:
|
||||
device['e'] = e
|
||||
device['e_name'] = e.__class__.__name__
|
||||
cli.log.error("Could not connect to %(color)s%(manufacturer_string)s %(product_string)s{style_reset_all} (%(color)s:%(vendor_id)04X:%(product_id)04X:%(index)d): %(e_name)s: %(e)s", device)
|
||||
cli.log.error(f'Could not connect to {_gen_header(device)}: {e.__class__.__name__}: {e}')
|
||||
if cli.config.general.verbose:
|
||||
cli.log.exception(e)
|
||||
del live_devices[device['path']]
|
||||
@@ -193,7 +224,7 @@ class FindDevices(object):
|
||||
live_bootloaders[device.address]._qmk_found = True
|
||||
else:
|
||||
name = KNOWN_BOOTLOADERS[(int2hex(device.idVendor), int2hex(device.idProduct))]
|
||||
cli.log.info('Bootloader Connected: {style_bright}{fg_magenta}%s', name)
|
||||
cli.log.info(f'Bootloader Connected: {{style_bright}}{{fg_magenta}}{name}')
|
||||
device._qmk_found = True
|
||||
live_bootloaders[device.address] = device
|
||||
|
||||
@@ -202,7 +233,7 @@ class FindDevices(object):
|
||||
live_bootloaders[device]._qmk_found = False
|
||||
else:
|
||||
name = KNOWN_BOOTLOADERS[(int2hex(live_bootloaders[device].idVendor), int2hex(live_bootloaders[device].idProduct))]
|
||||
cli.log.info('Bootloader Disconnected: {style_bright}{fg_magenta}%s', name)
|
||||
cli.log.info(f'Bootloader Disconnected: {{style_bright}}{{fg_magenta}}{name}')
|
||||
del live_bootloaders[device]
|
||||
|
||||
sleep(.1)
|
||||
@@ -269,23 +300,17 @@ class FindDevices(object):
|
||||
# Add index numbers
|
||||
device_index = {}
|
||||
for device in devices:
|
||||
id = ':'.join((int2hex(device['vendor_id']), int2hex(device['product_id'])))
|
||||
ident = ':'.join((int2hex(device['vendor_id']), int2hex(device['product_id'])))
|
||||
|
||||
if id not in device_index:
|
||||
device_index[id] = 0
|
||||
if ident not in device_index:
|
||||
device_index[ident] = 0
|
||||
|
||||
device_index[id] += 1
|
||||
device['index'] = device_index[id]
|
||||
device_index[ident] += 1
|
||||
device['index'] = device_index[ident]
|
||||
|
||||
return devices
|
||||
|
||||
|
||||
def int2hex(number):
|
||||
"""Returns a string representation of the number as hex.
|
||||
"""
|
||||
return "%04X" % number
|
||||
|
||||
|
||||
def list_devices(device_finder):
|
||||
"""Show the user a nicely formatted list of devices.
|
||||
"""
|
||||
@@ -294,9 +319,8 @@ def list_devices(device_finder):
|
||||
if devices:
|
||||
cli.log.info('Available devices:')
|
||||
for dev in devices:
|
||||
color = LOG_COLOR['colors'][LOG_COLOR['next']]
|
||||
LOG_COLOR['next'] = (LOG_COLOR['next'] + 1) % len(LOG_COLOR['colors'])
|
||||
cli.log.info("\t%s%s:%s:%d{style_reset_all}\t%s %s", color, int2hex(dev['vendor_id']), int2hex(dev['product_id']), dev['index'], dev['manufacturer_string'], dev['product_string'])
|
||||
color = _get_next_color()
|
||||
cli.log.info(f'\t{color}{int2hex(dev["vendor_id"])}:{int2hex(dev["product_id"])}:{dev["index"]}{{style_reset_all}}\t{dev["manufacturer_string"]} {dev["product_string"]}')
|
||||
|
||||
if cli.args.bootloaders:
|
||||
bootloaders = device_finder.find_bootloaders()
|
||||
@@ -305,7 +329,8 @@ def list_devices(device_finder):
|
||||
cli.log.info('Available Bootloaders:')
|
||||
|
||||
for dev in bootloaders:
|
||||
cli.log.info("\t%s:%s\t%s", int2hex(dev.idVendor), int2hex(dev.idProduct), KNOWN_BOOTLOADERS[(int2hex(dev.idVendor), int2hex(dev.idProduct))])
|
||||
color = _get_next_color()
|
||||
cli.log.info(f'\t{color}{int2hex(dev.idVendor)}:{int2hex(dev.idProduct)}{{style_reset_all}}\t{KNOWN_BOOTLOADERS[(int2hex(dev.idVendor), int2hex(dev.idProduct))]}')
|
||||
|
||||
|
||||
@cli.argument('--bootloaders', arg_only=True, default=True, action='store_boolean', help='displaying bootloaders.')
|
||||
@@ -332,17 +357,17 @@ def console(cli):
|
||||
vid, pid, index = device
|
||||
|
||||
if not index.isdigit():
|
||||
cli.log.error('Device index must be a number! Got "%s" instead.', index)
|
||||
cli.log.error(f'Device index must be a number! Got "{index}" instead.')
|
||||
exit(1)
|
||||
|
||||
index = int(index)
|
||||
|
||||
if index < 1:
|
||||
cli.log.error('Device index must be greater than 0! Got %s', index)
|
||||
cli.log.error(f'Device index must be greater than 0! Got "{index}".')
|
||||
exit(1)
|
||||
|
||||
else:
|
||||
cli.log.error('Invalid format for device, expected "<pid>:<vid>[:<index>]" but got "%s".', cli.config.console.device)
|
||||
cli.log.error(f'Invalid format for device, expected "<pid>:<vid>[:<index>]" but got "{cli.config.console.device}".')
|
||||
cli.print_help()
|
||||
exit(1)
|
||||
|
||||
|
||||
@@ -4,16 +4,20 @@ import os
|
||||
from pathlib import Path
|
||||
|
||||
from milc import cli
|
||||
from qmk_cli.helpers import is_qmk_firmware
|
||||
from qmk_cli.helpers import is_qmk_firmware, is_qmk_userspace
|
||||
|
||||
|
||||
@cli.argument('var', default=None, nargs='?', help='Optional variable to query')
|
||||
@cli.argument('var', arg_only=True, default=None, nargs='?', help='Optional variable to query')
|
||||
@cli.subcommand('Prints environment information.')
|
||||
def env(cli):
|
||||
home = os.environ.get('QMK_HOME', "")
|
||||
home = os.environ.get('QMK_HOME', '')
|
||||
userspace = os.environ.get('QMK_USERSPACE', '')
|
||||
data = {
|
||||
'QMK_HOME': home,
|
||||
'QMK_FIRMWARE': home if is_qmk_firmware(Path(home)) else ""
|
||||
'QMK_FIRMWARE': home if is_qmk_firmware(Path(home)) 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
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
"""Setup qmk_firmware on your computer.
|
||||
"""
|
||||
import os
|
||||
import shlex
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from shutil import rmtree
|
||||
|
||||
from milc import cli
|
||||
from milc.questions import yesno
|
||||
from milc.questions import choice, question, yesno
|
||||
from qmk_cli.git import git_clone
|
||||
from qmk_cli.helpers import is_qmk_firmware
|
||||
from qmk_cli.helpers import AbsPath, is_qmk_firmware
|
||||
|
||||
default_base = 'https://github.com'
|
||||
default_repo = 'qmk_firmware'
|
||||
@@ -20,7 +20,7 @@ default_branch = 'master'
|
||||
def git_upstream(destination):
|
||||
"""Add the qmk/qmk_firmware upstream to a qmk_firmware clone.
|
||||
"""
|
||||
git_url = '/'.join((cli.config.setup.baseurl, default_fork))
|
||||
git_url = '/'.join((cli.args.baseurl, default_fork))
|
||||
git_cmd = [
|
||||
'git',
|
||||
'-C',
|
||||
@@ -35,54 +35,86 @@ def git_upstream(destination):
|
||||
for line in p.stdout:
|
||||
print(line, end='')
|
||||
|
||||
if p.returncode == 0:
|
||||
cli.log.info('Added %s as remote upstream.', git_url)
|
||||
return True
|
||||
else:
|
||||
cli.log.error('%s exited %d', ' '.join(git_cmd), p.returncode)
|
||||
if p.returncode != 0:
|
||||
cli.log.error(f'git remote exited {p.returncode}')
|
||||
return False
|
||||
|
||||
cli.log.info(f'Added {git_url} as remote upstream.')
|
||||
return True
|
||||
|
||||
|
||||
def git_clone_fork(fork, branch, force=False):
|
||||
if force:
|
||||
rmtree(cli.args.home)
|
||||
|
||||
git_url = '/'.join((cli.args.baseurl, fork))
|
||||
if git_clone(git_url, cli.args.home, branch):
|
||||
git_upstream(cli.args.home)
|
||||
else:
|
||||
exit(1)
|
||||
|
||||
|
||||
@cli.argument('-n', '--no', arg_only=True, action='store_true', help='Answer no to all questions')
|
||||
@cli.argument('-y', '--yes', arg_only=True, action='store_true', help='Answer yes to all questions')
|
||||
@cli.argument('--baseurl', default=default_base, help='The URL all git operations start from. Default: %s' % default_base)
|
||||
@cli.argument('-b', '--branch', default=default_branch, help='The branch to clone. Default: %s' % default_branch)
|
||||
@cli.argument('-H', '--home', default=Path(os.environ['QMK_HOME']), type=Path, help='The location for QMK Firmware. Default: %s' % os.environ['QMK_HOME'])
|
||||
@cli.argument('fork', default=default_fork, nargs='?', help='The qmk_firmware fork to clone. Default: %s' % default_fork)
|
||||
@cli.argument('--baseurl', arg_only=True, default=default_base, help=f'The URL all git operations start from. Default: {default_base}')
|
||||
@cli.argument('-b', '--branch', arg_only=True, default=default_branch, help=f'The branch to clone. Default: {default_branch}')
|
||||
@cli.argument('-H', '--home', arg_only=True, default=Path(os.environ['QMK_HOME']), type=AbsPath, help=f'The location for QMK Firmware. Default: {os.environ["QMK_HOME"]}')
|
||||
@cli.argument('fork', arg_only=True, default=default_fork, nargs='?', help=f'The qmk_firmware fork to clone. Default: {default_fork}')
|
||||
@cli.subcommand('Setup your computer for qmk_firmware.')
|
||||
def setup(cli):
|
||||
"""Guide the user through setting up their QMK environment.
|
||||
"""
|
||||
clone_prompt = 'Would you like to clone {fg_cyan}%s{fg_reset} to {fg_cyan}%s{fg_reset}?' % (cli.args.fork, shlex.quote(str(cli.args.home)))
|
||||
home_prompt = 'Would you like to set {fg_cyan}%s{fg_reset} as your QMK home?' % (shlex.quote(str(cli.args.home)),)
|
||||
clone_prompt = f'Would you like to clone {{fg_cyan}}{cli.args.fork}{{fg_reset}} to {{fg_cyan}}{cli.args.home}{{fg_reset}}?'
|
||||
home_prompt = f'Would you like to set {{fg_cyan}}{cli.args.home}{{fg_reset}} as your QMK home?'
|
||||
|
||||
# Sanity checks
|
||||
if cli.args.yes and cli.args.no:
|
||||
cli.log.error("Can't use both --yes and --no at the same time.")
|
||||
exit(1)
|
||||
|
||||
# Check on qmk_firmware, and if it doesn't exist offer to check it out.
|
||||
# Check on qmk_firmware
|
||||
# If it exists, ask the user what to do with it
|
||||
# If it doesn't exist, offer to check it out
|
||||
if is_qmk_firmware(cli.args.home):
|
||||
cli.log.info('Found qmk_firmware at %s.', str(cli.args.home))
|
||||
cli.log.info(f'Found qmk_firmware at "{cli.args.home}".')
|
||||
found_prompt = 'What do you want to do?'
|
||||
found_options = [
|
||||
f'Delete and reclone {cli.args.fork}',
|
||||
'Delete and clone a different fork',
|
||||
'Keep it and continue'
|
||||
]
|
||||
delete_confirm = 'WARNING: This will delete your current qmk_firmware directory. Proceed?'
|
||||
|
||||
elif cli.args.home.exists():
|
||||
found_action = choice(found_prompt, options=found_options, default=2)
|
||||
if found_action == f'Delete and reclone {cli.args.fork}':
|
||||
if not yesno(delete_confirm, default=False):
|
||||
exit(1)
|
||||
|
||||
git_clone_fork(cli.args.fork, cli.args.branch, force=True)
|
||||
|
||||
elif found_action == 'Delete and clone a different fork':
|
||||
fork_name = question('Enter the name of the fork:', default=cli.args.fork)
|
||||
branch_name = question('Enter the branch name to clone:', default=cli.args.branch)
|
||||
|
||||
if not yesno(delete_confirm, default=False):
|
||||
exit(1)
|
||||
|
||||
git_clone_fork(fork_name, branch_name, force=True)
|
||||
|
||||
# Exists (but not an empty dir)
|
||||
elif cli.args.home.exists() and any(cli.args.home.iterdir()):
|
||||
path_str = str(cli.args.home)
|
||||
|
||||
if cli.args.home.name != 'qmk_firmware':
|
||||
cli.log.warning('Warning: %s does not end in "qmk_firmware". Did you mean to use "--home %s/qmk_firmware"?' % (path_str, path_str))
|
||||
cli.log.warning(f'Warning: {path_str} does not end in "qmk_firmware". Did you mean to use "--home {path_str}/qmk_firmware"?')
|
||||
|
||||
cli.log.error("Path '%s' exists but is not a qmk_firmware clone!", path_str)
|
||||
cli.log.error(f'Path "{path_str}" exists but is not a qmk_firmware clone!')
|
||||
exit(1)
|
||||
|
||||
else:
|
||||
cli.log.error('Could not find qmk_firmware!')
|
||||
if yesno(clone_prompt):
|
||||
git_url = '/'.join((cli.config.setup.baseurl, cli.args.fork))
|
||||
|
||||
if git_clone(git_url, cli.args.home, cli.config.setup.branch):
|
||||
git_upstream(cli.args.home)
|
||||
else:
|
||||
exit(1)
|
||||
git_clone_fork(cli.args.fork, cli.args.branch)
|
||||
else:
|
||||
cli.log.warning('Not cloning qmk_firmware due to user input or --no flag.')
|
||||
|
||||
@@ -104,4 +136,4 @@ def setup(cli):
|
||||
if cli.args.yes:
|
||||
doctor_command.append('-y')
|
||||
|
||||
cli.run(doctor_command, stdin=None, capture_output=False)
|
||||
cli.run(doctor_command, stdin=None, capture_output=False, cwd=cli.args.home)
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Increment the verison number and release a new version of qmk_cli.
|
||||
#
|
||||
# Required packages: pip3 install bumpversion twine
|
||||
|
||||
echo "Use the github action instead!"
|
||||
exit 1
|
||||
+3
-28
@@ -1,28 +1,3 @@
|
||||
bleach==4.0.0
|
||||
build==0.6.0.post1
|
||||
bump2version==1.0.1
|
||||
bumpversion==0.6.0
|
||||
certifi==2021.5.30
|
||||
cffi==1.14.6
|
||||
chardet==4.0.0
|
||||
cryptography==3.4.7
|
||||
docutils==0.17.1
|
||||
idna==2.10
|
||||
importlib-metadata==4.6.3
|
||||
jeepney==0.7.1
|
||||
keyring==23.0.1
|
||||
packaging==21.0
|
||||
pkginfo==1.7.1
|
||||
pycparser==2.20
|
||||
pyparsing==2.4.7
|
||||
readme-renderer==29.0
|
||||
requests==2.26.0
|
||||
requests-toolbelt==0.9.1
|
||||
rfc3986==1.5.0
|
||||
SecretStorage==3.3.1
|
||||
six==1.16.0
|
||||
tqdm==4.62.0
|
||||
twine==3.4.2
|
||||
urllib3==1.26.6
|
||||
webencodings==0.5.1
|
||||
zipp==3.5.0
|
||||
build
|
||||
bumpversion
|
||||
twine
|
||||
|
||||
@@ -1,13 +1,5 @@
|
||||
[bumpversion]
|
||||
current_version = 0.3.1
|
||||
commit = True
|
||||
tag = True
|
||||
tag_name = {new_version}
|
||||
message = New release: {current_version} → {new_version}
|
||||
|
||||
[bumpversion:file:qmk_cli/__init__.py]
|
||||
|
||||
[bumpversion:file:setup.cfg]
|
||||
#[bumpversion]
|
||||
# Bumpversion config has been moved to .bumpversion.cfg
|
||||
|
||||
[bdist_wheel]
|
||||
universal = 1
|
||||
@@ -17,19 +9,19 @@ ignore = E501,E226
|
||||
|
||||
[metadata]
|
||||
name = qmk
|
||||
version = 0.3.0
|
||||
version = 1.2.0
|
||||
author = skullydazed
|
||||
author_email = skullydazed@gmail.com
|
||||
description = A program to help users work with QMK Firmware.
|
||||
long_description = file: README.md
|
||||
long_description_content_type = text/markdown
|
||||
license = MIT License
|
||||
project_urls =
|
||||
project_urls =
|
||||
Bug Tracker = https://github.com/qmk/qmk_cli/issues
|
||||
Documentation = https://docs.qmk.fm/#/cli
|
||||
Homepage = https://qmk.fm/
|
||||
Source = https://github.com/qmk/qmk_cli/
|
||||
classifiers =
|
||||
classifiers =
|
||||
Development Status :: 3 - Alpha
|
||||
Environment :: Console
|
||||
Intended Audience :: Developers
|
||||
@@ -43,30 +35,23 @@ classifiers =
|
||||
Topic :: Utilities
|
||||
|
||||
[options]
|
||||
install_requires =
|
||||
appdirs==1.4.4
|
||||
argcomplete==1.12.3
|
||||
attrs==21.2.0
|
||||
colorama==0.4.4
|
||||
coverage==5.5
|
||||
halo==0.0.31
|
||||
hid==1.0.4
|
||||
log-symbols==0.0.14
|
||||
mccabe==0.6.1
|
||||
milc==1.5.0
|
||||
pycodestyle==2.7.0
|
||||
pyflakes==2.3.1
|
||||
pyrsistent==0.18.0
|
||||
pyusb==1.2.1
|
||||
setuptools>=45
|
||||
six==1.16.0
|
||||
spinners==0.0.24
|
||||
termcolor==1.1.0
|
||||
install_requires =
|
||||
hid
|
||||
milc>=1.9.0
|
||||
platformdirs
|
||||
pyusb
|
||||
# qmk_firmware packages
|
||||
dotty-dict
|
||||
hjson
|
||||
jsonschema>=4
|
||||
pillow
|
||||
pygments
|
||||
pyserial
|
||||
packages = find:
|
||||
python_requiures = >=3.7
|
||||
python_requires = >=3.9
|
||||
|
||||
[options.entry_points]
|
||||
console_scripts =
|
||||
console_scripts =
|
||||
qmk = qmk_cli.script_qmk:main
|
||||
|
||||
[yapf]
|
||||
@@ -87,13 +72,13 @@ continuation_indent_width = 4
|
||||
dedent_closing_brackets = True
|
||||
disable_ending_comma_heuristic = False
|
||||
each_dict_entry_on_separate_line = True
|
||||
i18n_comment =
|
||||
i18n_function_call =
|
||||
i18n_comment =
|
||||
i18n_function_call =
|
||||
indent_blank_lines = False
|
||||
indent_dictionary_value = True
|
||||
indent_width = 4
|
||||
join_multiple_lines = False
|
||||
no_spaces_around_selected_binary_operators =
|
||||
no_spaces_around_selected_binary_operators =
|
||||
spaces_around_default_or_named_assign = False
|
||||
spaces_around_power_operator = False
|
||||
spaces_before_comment = 2
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Trigger workflows that build QMK packages.
|
||||
"""
|
||||
from os import environ
|
||||
from pathlib import Path
|
||||
|
||||
import requests
|
||||
|
||||
# Pull in environment vars
|
||||
gh_user = environ.get('GH_USERNAME', 'qmk-bot')
|
||||
gh_pat = environ.get('QMK_BOT_TOKEN', '')
|
||||
gh_repo_owner = environ.get('REPO_OWNER', 'qmk')
|
||||
gh_repo_name = environ.get('REPO_NAME', 'qmk_fpm')
|
||||
gh_ref = environ.get('GITHUB_REF', 'main')
|
||||
gh_workflow_ids = environ.get('WORKFLOW_IDS', 'debian-buster-publish.yml,fedora-33-publish.yml,fedora-35-publish.yml,fedora-32-publish.yml,fedora-34-publish.yml,ubuntu-focal-publish.yml').split(',')
|
||||
gh_api_url = environ.get('GITHUB_API_URL', 'https://api.github.com')
|
||||
gh_workflow_args = {'ref': gh_ref}
|
||||
gh_workflow_headers = {'Accept': 'application/vnd.github.v3+json'}
|
||||
|
||||
for gh_workflow_id in gh_workflow_ids:
|
||||
gh_workflow_endpoint = f'{gh_api_url}/repos/{gh_repo_owner}/{gh_repo_name}/actions/workflows/{gh_workflow_id}/dispatches'
|
||||
print(f'Triggering {gh_workflow_id} workflow at: {gh_workflow_endpoint}')
|
||||
workflow_dispatch = requests.post(gh_workflow_endpoint, headers=gh_workflow_headers, json=gh_workflow_args, auth=(gh_user, gh_pat))
|
||||
if workflow_dispatch.status_code != 204:
|
||||
print(f'Error from GitHub API, status_code {workflow_dispatch.status_code}!')
|
||||
print(workflow_dispatch.text)
|
||||
exit(1)
|
||||
|
||||
exit(0)
|
||||
Reference in New Issue
Block a user