Compare commits
162 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 | |||
| f7125127f1 | |||
| f8fe363999 | |||
| bbd667e30c | |||
| 32506bf1bb | |||
| 45a231af0c | |||
| 1f7ad8a73e | |||
| 2faca28631 | |||
| d99eed2ed0 | |||
| a5a481e322 | |||
| 21daea7028 | |||
| 51e80c2f52 | |||
| 567ef9637e | |||
| e8818c9d23 | |||
| 2710813c51 | |||
| fb69e6d50a | |||
| 135d9f63a1 | |||
| 695cd7eb4d | |||
| 61ca025afd | |||
| 2bd1630621 | |||
| 34517d01b7 | |||
| 0a4d66e13a | |||
| fe0a3af05f | |||
| 4508b8548f | |||
| 2ea90a0893 | |||
| 9fccc990be | |||
| 6d99eacdf8 | |||
| dbd731585a | |||
| 9d480bd287 | |||
| 709f416e52 | |||
| bcdac9a80e | |||
| 4772e68828 | |||
| 24741c7d2b | |||
| b756421528 | |||
| 3d96bcdc14 | |||
| 6e6b6bcde6 | |||
| 1df06eed72 | |||
| 60a56b6976 | |||
| 1e3605feb3 | |||
| 48ddcefea1 | |||
| 2f6f38cc33 | |||
| c73ae7e97c | |||
| 0475193870 | |||
| 8d9b3b2880 | |||
| 0dbe3dc448 | |||
| c41fd5e940 | |||
| b70736a876 | |||
| 69b3b890d2 | |||
| 8b30a9766d | |||
| 982349a450 | |||
| 4d161c9ec4 | |||
| 047eb00594 | |||
| 558251860a | |||
| b685fe5d3f | |||
| e447fb4252 | |||
| 066a865084 | |||
| a1276f8d58 | |||
| 3555e34ae9 | |||
| b36cf144e3 | |||
| fb05b69adc | |||
| 4283c258d9 | |||
| b5e8aa4cac | |||
| 1c6d81fc3b | |||
| 0ffae74377 | |||
| cbe3ad0248 | |||
| 97689b53d7 | |||
| 054e071787 | |||
| 78e0fda2f0 | |||
| 9c6123483b | |||
| e851c8fcb5 | |||
| 92168e1ef0 | |||
| fd744ac24c | |||
| 840c791cc8 | |||
| 8f5b8e83c0 | |||
| 1da98510d3 | |||
| 6ec242b133 | |||
| 6433cbc0f4 | |||
| aedad7e602 | |||
| 504291bbf5 | |||
| c6e736adc7 | |||
| fe42ab3534 | |||
| d805fe2b6e | |||
| 5321a121fc | |||
| 3c31d41bf3 | |||
| c6977fa851 | |||
| 3b6c0fd058 | |||
| 216f5d44c1 | |||
| 0bf0b5a503 | |||
| 4b11e04672 | |||
| aba043abdc | |||
| efd9f68243 | |||
| e3e0856f96 | |||
| 670a07ff0b | |||
| c4c76e3534 | |||
| 9ad519d4a2 | |||
| 655979cef5 | |||
| 6b0d5fe85b | |||
| d163222123 | |||
| 5342ab3e10 |
@@ -0,0 +1,12 @@
|
||||
[bumpversion]
|
||||
current_version = 1.1.1
|
||||
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]
|
||||
@@ -0,0 +1,17 @@
|
||||
# Please see the documentation for all configuration options:
|
||||
# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions" # See documentation for possible values
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
reviewers:
|
||||
- "qmk/collaborators"
|
||||
- package-ecosystem: "pip" # See documentation for possible values
|
||||
directory: "/" # Location of package manifests
|
||||
schedule:
|
||||
interval: "daily"
|
||||
reviewers:
|
||||
- "qmk/collaborators"
|
||||
@@ -14,30 +14,18 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [macos-latest, ubuntu-latest]
|
||||
python-version: [3.6, 3.7, 3.8]
|
||||
python-version: [3.7, 3.8, 3.9]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v1
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
- name: Install Python dependencies
|
||||
run: |
|
||||
python3 -m pip install wheel
|
||||
echo "::add-path::$HOME/.local/bin"
|
||||
|
||||
- name: Install QMK CLI from source
|
||||
run: |
|
||||
python3 -m pip install -r requirements.txt
|
||||
python3 setup.py sdist bdist_wheel
|
||||
cd ..
|
||||
python3 -m pip install --force-reinstall --no-index --no-deps --prefix=~/.local --find-links qmk_cli/dist qmk
|
||||
|
||||
- name: Run qmk setup -y
|
||||
run: qmk setup -y
|
||||
- name: Run ci_tests
|
||||
run: ./ci_tests -a
|
||||
|
||||
test_cli_win:
|
||||
runs-on: windows-latest
|
||||
@@ -45,28 +33,24 @@ jobs:
|
||||
QMK_HOME: $HOME/qmk_firmware
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- 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-pillow
|
||||
|
||||
- name: (MSYS2) Upgrade pip and setuptools and install wheel
|
||||
- name: (MSYS2) Install Python dependencies
|
||||
shell: msys2 {0}
|
||||
run: |
|
||||
python3 -m pip install --upgrade pip setuptools
|
||||
python3 -m pip install wheel
|
||||
|
||||
python3 -m pip install -U setuptools wheel
|
||||
- name: (MSYS2) Install QMK CLI from source
|
||||
shell: msys2 {0}
|
||||
run: |
|
||||
python3 -m pip install -r requirements.txt
|
||||
python3 setup.py sdist bdist_wheel
|
||||
cd ..
|
||||
python3 -m pip install --force-reinstall --no-index --no-deps --find-links qmk_cli/dist qmk
|
||||
|
||||
python3 -m pip install -U build
|
||||
python3 -m build
|
||||
python3 -m pip install dist/qmk-*.tar.gz
|
||||
- name: (MSYS2) Run qmk setup -y
|
||||
shell: msys2 {0}
|
||||
run: qmk setup -y
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
# For most projects, this workflow file will not need changing; you simply need
|
||||
# to commit it to your repository.
|
||||
#
|
||||
# You may wish to alter this file to override the set of languages analyzed,
|
||||
# or to provide custom queries or build logic.
|
||||
#
|
||||
# ******** NOTE ********
|
||||
# We have attempted to detect the languages in your repository. Please check
|
||||
# the `language` matrix defined below to confirm you have the correct set of
|
||||
# supported CodeQL languages.
|
||||
#
|
||||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [ master ]
|
||||
schedule:
|
||||
- cron: '22 18 * * 0'
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: [ 'python' ]
|
||||
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
|
||||
# Learn more:
|
||||
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
||||
|
||||
# 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@v2
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
|
||||
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
||||
# and modify them (or add more) to build your code if your project
|
||||
# uses a compiled language
|
||||
|
||||
#- run: |
|
||||
# make bootstrap
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
||||
@@ -0,0 +1,35 @@
|
||||
name: Rebuild Docker Image
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
redeploy:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.7'
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: 'Get Previous tag'
|
||||
id: previoustag
|
||||
uses: "WyriHaximus/github-action-get-previous-tag@v1"
|
||||
- name: Download previous artifact
|
||||
run: |
|
||||
pip download -d "${GITHUB_WORKSPACE}/dist" qmk=="${{ steps.previoustag.outputs.tag }}"
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@v2.0.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
|
||||
- name: Build and Push to Docker Hub
|
||||
uses: docker/build-push-action@v3.1.1
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
tags: qmkfm/qmk_cli:latest
|
||||
@@ -0,0 +1,64 @@
|
||||
# This workflow will upload a Python Package using Twine when a release is created
|
||||
# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries
|
||||
|
||||
name: Upload Python Package
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version_part:
|
||||
description: 'Which part of the version to increment (patch, minor, major)'
|
||||
required: true
|
||||
default: 'patch'
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.7'
|
||||
- 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: Bump version
|
||||
run: |
|
||||
git config --local user.email "hello@qmk.fm"
|
||||
git config --local user.name "QMK Bot"
|
||||
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 }}
|
||||
run: |
|
||||
python3 -m build
|
||||
twine upload dist/*
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@v2.0.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
|
||||
- name: Build and Push to Docker Hub
|
||||
uses: docker/build-push-action@v3.1.1
|
||||
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 }}
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"python.formatting.provider": "yapf"
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
# Code Of Conduct
|
||||
|
||||
QMK strives to be an inclusive, tolerant, and welcoming community. We encourage participation from anyone regardless of age, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, political belief, race, religion, or sexual identity and orientation.
|
||||
|
||||
> “A gentle word turns away wrath, but a harsh word stirs up anger."
|
||||
|
||||
Our users, contributors, and collaborators are expected to treat each other with kindness and respect, to assume good intentions, and to gently correct, where possible, rather than react with escalation. While our goal is to be as accurate as possible, kindness and understanding are more valuable than correctness. Some examples of behavior we will not tolerate include, but is not limited to:
|
||||
|
||||
* The use of sexualized language or imagery
|
||||
* Unwelcome advances, sexual or otherwise
|
||||
* Insults or derogatory comments, or personal or political attacks
|
||||
* Publishing others’ private information without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||
|
||||
If someone is violating this Code of Conduct you may email hello@qmk.fm to bring your concern to the Members. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
FROM qmkfm/base_container
|
||||
|
||||
# Copy package in
|
||||
ADD dist /tmp/dist
|
||||
|
||||
# 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
|
||||
|
||||
# Set the default location for qmk_firmware
|
||||
ENV QMK_HOME /qmk_firmware
|
||||
@@ -1,5 +1,6 @@
|
||||
# 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/).
|
||||
|
||||
# Features
|
||||
@@ -7,11 +8,52 @@ A program to help users work with [QMK Firmware](https://qmk.fm/).
|
||||
* Interact with your qmk_firmware tree from any location
|
||||
* Use `qmk clone` to pull down anyone's `qmk_firmware` fork
|
||||
* Setup your build environment with `qmk setup`
|
||||
* Use `qmk console` to get debugging information from your keyboard(s)
|
||||
* Check that your environment is correctly setup with `qmk doctor`
|
||||
* Dispatches to `qmk_firmware/bin/qmk` for additional functionality:
|
||||
* `qmk compile-json`
|
||||
* More to come
|
||||
* Integrates with qmk_firmware for additional functionality:
|
||||
* `qmk compile`
|
||||
* `qmk info`
|
||||
* `qmk flash`
|
||||
* `qmk lint`
|
||||
* ...and many more!
|
||||
|
||||
# Packages
|
||||
|
||||
We provide "install and go" packages for many Operating Systems.
|
||||
|
||||
## Linux
|
||||
|
||||
Packages for several distributions available here: https://github.com/qmk/qmk_fpm
|
||||
|
||||
## macOS
|
||||
|
||||
Using [Homebrew](https://brew.sh):
|
||||
|
||||
brew install qmk/qmk/qmk
|
||||
|
||||
## Windows
|
||||
|
||||
Download our custom MSYS2 installer here: https://msys.qmk.fm/
|
||||
|
||||
# Quickstart
|
||||
|
||||
To be written.
|
||||
* `python3 -m pip install qmk`
|
||||
* `qmk setup`
|
||||
|
||||
# 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>
|
||||
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
Use this section to tell people about which versions of your project are
|
||||
currently being supported with security updates.
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| 0.0.x | :white_check_mark: |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
* [Open an Issue](https://github.com/qmk/qmk_cli/issues/new?assignees=&labels=bug&template=bug_report.md&title=%5BBug%5D+)
|
||||
@@ -0,0 +1,59 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
#set -x
|
||||
|
||||
ADVANCED=false
|
||||
TMPDIR=$(mktemp -d)
|
||||
QMK_HOME="$TMPDIR/qmk_firmware"
|
||||
export QMK_HOME
|
||||
|
||||
for arg in $@; do
|
||||
if [ "$arg" = "-a" ]; then
|
||||
ADVANCED=true
|
||||
fi
|
||||
done
|
||||
|
||||
if [ $ADVANCED = true ]; then
|
||||
echo "*** Running in advanced mode with tmp files in $TMPDIR"
|
||||
else
|
||||
echo "*** Running in basic mode with tmp files in $TMPDIR"
|
||||
fi
|
||||
|
||||
# Setup our virtualenv
|
||||
if [ -e .ci_venv ]; then
|
||||
rm -rf .ci_venv
|
||||
fi
|
||||
|
||||
python3 -m venv .ci_venv
|
||||
source .ci_venv/bin/activate
|
||||
|
||||
# Install dependencies
|
||||
python3 -m pip install -U pip wheel
|
||||
python3 -m pip install .
|
||||
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 setup -n'"
|
||||
qmk setup -n
|
||||
|
||||
echo
|
||||
echo "*** Basic tests completed successfully!"
|
||||
|
||||
# Run advanced test if requested
|
||||
if [ $ADVANCED = true ]; then
|
||||
echo
|
||||
echo "*** Testing 'qmk setup -y'"
|
||||
qmk setup -y
|
||||
|
||||
echo
|
||||
echo "*** Advanced tests completed successfully!"
|
||||
fi
|
||||
|
||||
# Cleanup
|
||||
deactivate
|
||||
rm -rf .ci_venv $TMPDIR
|
||||
@@ -0,0 +1,6 @@
|
||||
[build-system]
|
||||
requires = [
|
||||
"setuptools>=42",
|
||||
"wheel"
|
||||
]
|
||||
build-backend = "setuptools.build_meta"
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
"""A program to help you work with qmk_firmware."""
|
||||
|
||||
__version__ = '0.0.36'
|
||||
__version__ = '1.1.1'
|
||||
|
||||
+12
-4
@@ -16,17 +16,25 @@ def git_clone(url, destination, branch):
|
||||
'--recurse-submodules',
|
||||
'--branch=' + branch,
|
||||
url,
|
||||
destination,
|
||||
str(destination),
|
||||
]
|
||||
cli.log.debug('Git clone command: %s', git_clone)
|
||||
|
||||
with subprocess.Popen(git_clone, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, bufsize=1, universal_newlines=True, encoding='utf-8') as p:
|
||||
for line in p.stdout:
|
||||
print(line, end='')
|
||||
try:
|
||||
with subprocess.Popen(git_clone, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, bufsize=1, universal_newlines=True, encoding='utf-8') as p:
|
||||
for line in p.stdout:
|
||||
print(line, end='')
|
||||
|
||||
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)
|
||||
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)
|
||||
return False
|
||||
|
||||
+53
-29
@@ -1,36 +1,60 @@
|
||||
"""Useful helper functions.
|
||||
"""
|
||||
from milc import cli, format_ansi
|
||||
import os
|
||||
from functools import lru_cache
|
||||
from importlib.util import find_spec
|
||||
from pathlib import Path
|
||||
|
||||
from milc import cli
|
||||
|
||||
|
||||
def question(question, boolean=True, default=''):
|
||||
"""Asks the user to answer a question.
|
||||
|
||||
This keeps re-asking until it gets acceptible input.
|
||||
def is_qmk_firmware(qmk_firmware):
|
||||
"""Returns True if the given Path() is a qmk_firmware clone.
|
||||
"""
|
||||
if cli.args.yes:
|
||||
return True
|
||||
paths = [
|
||||
qmk_firmware,
|
||||
qmk_firmware / 'quantum',
|
||||
qmk_firmware / 'requirements.txt',
|
||||
qmk_firmware / 'requirements-dev.txt',
|
||||
qmk_firmware / 'lib/python/qmk/cli/__init__.py'
|
||||
]
|
||||
|
||||
if default and default.lower() == 'y':
|
||||
answer_key = 'Y/n'
|
||||
elif default and default.lower() == 'n':
|
||||
answer_key = 'y/N'
|
||||
else:
|
||||
answer_key = 'y/n'
|
||||
|
||||
prompt = format_ansi('\n*** %s [%s] ' % (question, answer_key))
|
||||
|
||||
while True:
|
||||
answer = input(prompt)
|
||||
print()
|
||||
if answer == '' and default.lower() == 'y':
|
||||
answer = 'y'
|
||||
elif answer == '' and default.lower() == 'n':
|
||||
answer = 'n'
|
||||
|
||||
if answer.lower() in ['y', 'yes']:
|
||||
return True
|
||||
elif answer.lower() in ['n', 'no']:
|
||||
for path in paths:
|
||||
if not path.exists():
|
||||
return False
|
||||
else:
|
||||
cli.echo('Invalid answer!')
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@lru_cache(maxsize=2)
|
||||
def find_qmk_firmware():
|
||||
"""Look for qmk_firmware in the usual places.
|
||||
|
||||
This function returns the path to qmk_firmware, or the default location if one does not exist.
|
||||
"""
|
||||
if in_qmk_firmware():
|
||||
return in_qmk_firmware()
|
||||
|
||||
if cli.config.user.qmk_home:
|
||||
return Path(cli.config.user.qmk_home).expanduser().resolve()
|
||||
|
||||
if 'QMK_HOME' in os.environ:
|
||||
path = Path(os.environ['QMK_HOME']).expanduser()
|
||||
if path.exists():
|
||||
return path.resolve()
|
||||
return path
|
||||
|
||||
return Path.home() / 'qmk_firmware'
|
||||
|
||||
|
||||
def in_qmk_firmware():
|
||||
"""Returns the path to the qmk_firmware we are currently in, or None if we are not inside qmk_firmware.
|
||||
"""
|
||||
cur_dir = Path.cwd()
|
||||
while len(cur_dir.parents) > 0:
|
||||
if is_qmk_firmware(cur_dir):
|
||||
return cur_dir
|
||||
|
||||
# Move up a directory before the next iteration
|
||||
cur_dir = cur_dir / '..'
|
||||
cur_dir = cur_dir.resolve()
|
||||
|
||||
+35
-59
@@ -3,18 +3,25 @@
|
||||
|
||||
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 argparse
|
||||
import platform
|
||||
import os
|
||||
import shlex
|
||||
import subprocess
|
||||
import sys
|
||||
from functools import lru_cache
|
||||
from pathlib import Path
|
||||
from platform import platform
|
||||
from traceback import print_exc
|
||||
|
||||
import milc
|
||||
|
||||
from . import __version__
|
||||
from .helpers import find_qmk_firmware, is_qmk_firmware
|
||||
|
||||
milc.set_metadata(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
|
||||
|
||||
|
||||
@milc.cli.entrypoint('CLI wrapper for running QMK commands.')
|
||||
def qmk_main(cli):
|
||||
@@ -23,59 +30,27 @@ def qmk_main(cli):
|
||||
cli.print_help()
|
||||
|
||||
|
||||
@lru_cache(maxsize=2)
|
||||
def in_qmk_firmware():
|
||||
"""Returns the path to the qmk_firmware we are currently in, or None if we are not inside qmk_firmware.
|
||||
def run_cmd(*command):
|
||||
"""Run a command in a subshell.
|
||||
"""
|
||||
cur_dir = Path.cwd()
|
||||
while len(cur_dir.parents) > 0:
|
||||
found_bin = cur_dir / 'bin' / 'qmk'
|
||||
# An additional check for something that exists in the root of qmk_firmware,
|
||||
# but not in the script's install directory, to avoid recursive execution,
|
||||
# if started from install directory.
|
||||
# e.g.: cd ~/.local/bin && ./qmk
|
||||
found_quantum = cur_dir / 'quantum'
|
||||
if found_bin.is_file() and found_quantum.is_dir():
|
||||
command = [sys.executable, found_bin.as_posix()]
|
||||
result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
if 'windows' in milc.cli.platform.lower():
|
||||
safecmd = map(shlex.quote, command)
|
||||
safecmd = ' '.join(safecmd)
|
||||
command = [os.environ['SHELL'], '-c', safecmd]
|
||||
|
||||
if result.returncode == 0:
|
||||
return cur_dir
|
||||
|
||||
# Move up a directory before the next iteration
|
||||
cur_dir = cur_dir / '..'
|
||||
cur_dir = cur_dir.resolve()
|
||||
|
||||
|
||||
def find_qmk_firmware():
|
||||
"""Look for qmk_firmware in the usual places.
|
||||
|
||||
This function returns the path to qmk_firmware, or the default location if one does not exist.
|
||||
"""
|
||||
if in_qmk_firmware():
|
||||
return in_qmk_firmware()
|
||||
|
||||
if milc.cli.config.user.qmk_home:
|
||||
return Path(milc.cli.config.user.qmk_home).expanduser().resolve()
|
||||
|
||||
if 'QMK_HOME' in os.environ:
|
||||
path = Path(os.environ['QMK_HOME']).expanduser()
|
||||
if path.exists():
|
||||
return path.resolve()
|
||||
return path
|
||||
|
||||
return Path.home() / 'qmk_firmware'
|
||||
return subprocess.run(command)
|
||||
|
||||
|
||||
# Python setuptools entrypoint
|
||||
def main():
|
||||
"""Setup the environment before dispatching to the entrypoint.
|
||||
"""
|
||||
# Warn if they use an outdated python version
|
||||
if sys.version_info < (3, 6):
|
||||
if sys.version_info < (3, 7):
|
||||
print('Warning: Your Python version is out of date! Some subcommands may not work!')
|
||||
print('Please upgrade to Python 3.6 or later.')
|
||||
print('Please upgrade to Python 3.7 or later.')
|
||||
|
||||
if 'windows' in platform.platform().lower():
|
||||
if 'windows' in platform().lower():
|
||||
msystem = os.environ.get('MSYSTEM', '')
|
||||
|
||||
if 'mingw64' not in sys.executable or 'MINGW64' not in msystem:
|
||||
@@ -85,26 +60,27 @@ def main():
|
||||
exit(1)
|
||||
|
||||
# Environment setup
|
||||
import qmk_cli
|
||||
milc.cli.version = qmk_cli.__version__
|
||||
qmk_firmware = find_qmk_firmware()
|
||||
os.environ['QMK_HOME'] = str(qmk_firmware)
|
||||
os.environ['ORIG_CWD'] = os.getcwd()
|
||||
|
||||
# Import the subcommand modules
|
||||
import qmk_cli.subcommands
|
||||
|
||||
if qmk_firmware.exists():
|
||||
# Check out and initialize the qmk_firmware environment
|
||||
if is_qmk_firmware(qmk_firmware):
|
||||
# All subcommands are run relative to the qmk_firmware root to make it easier to use the right copy of qmk_firmware.
|
||||
os.chdir(str(qmk_firmware))
|
||||
sys.path.append(str(qmk_firmware / 'lib' / 'python'))
|
||||
try:
|
||||
import qmk.cli
|
||||
except ImportError:
|
||||
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))
|
||||
sys.path.append(str(qmk_firmware / 'lib/python'))
|
||||
|
||||
print('Error: %s is too old or not set up correctly!' % qmk_firmware)
|
||||
print('Please update it or remove it completely before continuing.')
|
||||
try:
|
||||
import qmk.cli # noqa
|
||||
|
||||
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('Error: %s: %s', (e.__class__.__name__, e))
|
||||
print_exc()
|
||||
sys.exit(1)
|
||||
|
||||
# Call the entrypoint
|
||||
|
||||
@@ -2,5 +2,7 @@
|
||||
|
||||
We list each subcommand here explicitly because all the reliable ways of searching for modules are slow and delay startup.
|
||||
"""
|
||||
from . import clone
|
||||
from . import setup
|
||||
from . import clone # noqa
|
||||
from . import console # noqa
|
||||
from . import env # noqa
|
||||
from . import setup # noqa
|
||||
|
||||
@@ -11,20 +11,20 @@ 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='The branch to clone. Default: %s' % default_branch)
|
||||
@cli.argument('destination', arg_only=True, default=None, nargs='?', help='The directory to clone to. Default: (current directory)')
|
||||
@cli.argument('fork', arg_only=True, default=default_fork, nargs='?', help='The qmk_firmware fork to clone. Default: %s' % 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])
|
||||
|
||||
qmk_firmware = Path(cli.args.destination)
|
||||
git_url = '/'.join((cli.config.clone.baseurl, cli.args.fork))
|
||||
git_url = '/'.join((cli.args.baseurl, cli.args.fork))
|
||||
|
||||
if qmk_firmware.exists():
|
||||
cli.log.error('Destination already exists: %s', 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)
|
||||
|
||||
@@ -0,0 +1,359 @@
|
||||
"""Acquire debugging information from usb hid devices
|
||||
|
||||
cli implementation of https://www.pjrc.com/teensy/hid_listen.html
|
||||
"""
|
||||
from functools import lru_cache
|
||||
from pathlib import Path
|
||||
from platform import platform
|
||||
from threading import Thread
|
||||
from time import sleep, strftime
|
||||
|
||||
from milc import cli
|
||||
from milc.questions import yesno
|
||||
|
||||
LOG_COLOR = {
|
||||
'next': 0,
|
||||
'colors': [
|
||||
'{fg_blue}',
|
||||
'{fg_cyan}',
|
||||
'{fg_green}',
|
||||
'{fg_magenta}',
|
||||
'{fg_red}',
|
||||
'{fg_yellow}',
|
||||
],
|
||||
}
|
||||
|
||||
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',
|
||||
('03EB', '2FF4'): 'atmel-dfu: ATmega32U4',
|
||||
('03EB', '2FF9'): 'atmel-dfu: AT90USB64',
|
||||
('03EB', '2FFA'): 'atmel-dfu: AT90USB162',
|
||||
('03EB', '2FFB'): 'atmel-dfu: AT90USB128',
|
||||
('03EB', '6124'): 'Microchip SAM-BA',
|
||||
('0483', 'DF11'): 'stm32-dfu: STM32 BOOTLOADER',
|
||||
('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: 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',
|
||||
('2A03', '0036'): 'caterina: Arduino Leonardo',
|
||||
('2A03', '0037'): 'caterina: Arduino Micro',
|
||||
('314B', '0106'): 'apm32-dfu: APM32 DFU ISP Mode'
|
||||
}
|
||||
|
||||
|
||||
def install_deps():
|
||||
"""Install the necessary dependencies for qmk console.
|
||||
"""
|
||||
this_platform = platform().lower()
|
||||
|
||||
if 'darwin' in this_platform or 'macos' in this_platform:
|
||||
command = ['brew', 'install', 'hidapi']
|
||||
elif 'linux' in this_platform:
|
||||
command = ['sudo', 'apt', 'install', '-y', 'libhidapi-hidraw0', 'libusb-dev']
|
||||
elif 'windows' in this_platform:
|
||||
command = ['pacboy', 'sync', '--needed', '--noconfirm', '--disable-download-timeout', 'hidapi:x']
|
||||
else:
|
||||
cli.log.error('Unsupported platform: %s', this_platform)
|
||||
|
||||
if yesno("Would you like to run `%s` to install the necessary package?", ' '.join(command)):
|
||||
cli.run(command, capture_output=False)
|
||||
return True
|
||||
|
||||
|
||||
@lru_cache(maxsize=0)
|
||||
def import_usb_core():
|
||||
"""Attempts to import the usb.core module.
|
||||
"""
|
||||
try:
|
||||
import usb.core
|
||||
return usb.core
|
||||
|
||||
except ImportError as e:
|
||||
cli.log.error('Could not import usb.core: %s', e)
|
||||
|
||||
if install_deps():
|
||||
return import_usb_core()
|
||||
|
||||
raise
|
||||
|
||||
|
||||
def import_hid():
|
||||
"""Attempts to import the hid module.
|
||||
"""
|
||||
try:
|
||||
import hid
|
||||
return hid
|
||||
|
||||
except ImportError as e:
|
||||
cli.log.error('Could not import hid: %s', e)
|
||||
|
||||
if install_deps():
|
||||
return import_hid()
|
||||
|
||||
raise
|
||||
|
||||
|
||||
class MonitorDevice(object):
|
||||
def __init__(self, hid_device, numeric):
|
||||
self.hid = import_hid()
|
||||
self.hid_device = hid_device
|
||||
self.numeric = numeric
|
||||
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)
|
||||
|
||||
def read(self, size, encoding='ascii', timeout=1):
|
||||
"""Read size bytes from the device.
|
||||
"""
|
||||
return self.device.read(size, timeout).decode(encoding)
|
||||
|
||||
def read_line(self):
|
||||
"""Read from the device's console until we get a \n.
|
||||
"""
|
||||
while '\n' not in self.current_line:
|
||||
self.current_line += self.read(32).replace('\x00', '')
|
||||
|
||||
lines = self.current_line.split('\n', 1)
|
||||
self.current_line = lines[1]
|
||||
|
||||
return lines[0]
|
||||
|
||||
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 ''
|
||||
|
||||
cli.echo('%(ts)s%(color)s%(identifier)s:%(index)d{style_reset_all}: %(text)s' % message)
|
||||
|
||||
except self.hid.HIDException:
|
||||
break
|
||||
|
||||
|
||||
class FindDevices(object):
|
||||
def __init__(self, vid, pid, index, numeric):
|
||||
self.hid = import_hid()
|
||||
self.vid = vid
|
||||
self.pid = pid
|
||||
self.index = index
|
||||
self.numeric = numeric
|
||||
|
||||
def run_forever(self):
|
||||
"""Process messages from our queue in a loop.
|
||||
"""
|
||||
live_devices = {}
|
||||
live_bootloaders = {}
|
||||
|
||||
while True:
|
||||
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])
|
||||
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'])
|
||||
live_devices[device['path']] = device
|
||||
|
||||
try:
|
||||
monitor = MonitorDevice(device, self.numeric)
|
||||
device['thread'] = Thread(target=monitor.run_forever, daemon=True)
|
||||
|
||||
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)
|
||||
if cli.config.general.verbose:
|
||||
cli.log.exception(e)
|
||||
del live_devices[device['path']]
|
||||
|
||||
if cli.args.bootloaders:
|
||||
for device in self.find_bootloaders():
|
||||
if device.address in live_bootloaders:
|
||||
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)
|
||||
device._qmk_found = True
|
||||
live_bootloaders[device.address] = device
|
||||
|
||||
for device in list(live_bootloaders):
|
||||
if live_bootloaders[device]._qmk_found:
|
||||
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)
|
||||
del live_bootloaders[device]
|
||||
|
||||
sleep(.1)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
break
|
||||
|
||||
def is_bootloader(self, hid_device):
|
||||
"""Returns true if the device in question matches a known bootloader vid/pid.
|
||||
"""
|
||||
return (int2hex(hid_device.idVendor), int2hex(hid_device.idProduct)) in KNOWN_BOOTLOADERS
|
||||
|
||||
def is_console_hid(self, hid_device):
|
||||
"""Returns true when the usage page indicates it's a teensy-style console.
|
||||
"""
|
||||
return hid_device['usage_page'] == 0xFF31 and hid_device['usage'] == 0x0074
|
||||
|
||||
def is_filtered_device(self, hid_device):
|
||||
"""Returns True if the device should be included in the list of available consoles.
|
||||
"""
|
||||
return int2hex(hid_device['vendor_id']) == self.vid and int2hex(hid_device['product_id']) == self.pid
|
||||
|
||||
def find_devices_by_report(self, hid_devices):
|
||||
"""Returns a list of available teensy-style consoles by doing a brute-force search.
|
||||
|
||||
Some versions of linux don't report usage and usage_page. In that case we fallback to reading the report (possibly inaccurately) ourselves.
|
||||
"""
|
||||
devices = []
|
||||
|
||||
for device in hid_devices:
|
||||
path = device['path'].decode('utf-8')
|
||||
|
||||
if path.startswith('/dev/hidraw'):
|
||||
number = path[11:]
|
||||
report = Path(f'/sys/class/hidraw/hidraw{number}/device/report_descriptor')
|
||||
|
||||
if report.exists():
|
||||
rp = report.read_bytes()
|
||||
|
||||
if rp[1] == 0x31 and rp[3] == 0x09:
|
||||
devices.append(device)
|
||||
|
||||
return devices
|
||||
|
||||
def find_bootloaders(self):
|
||||
"""Returns a list of available bootloader devices.
|
||||
"""
|
||||
import usb.core
|
||||
|
||||
return list(filter(self.is_bootloader, usb.core.find(find_all=True)))
|
||||
|
||||
def find_devices(self):
|
||||
"""Returns a list of available teensy-style consoles.
|
||||
"""
|
||||
hid_devices = self.hid.enumerate()
|
||||
devices = list(filter(self.is_console_hid, hid_devices))
|
||||
|
||||
if not devices:
|
||||
devices = self.find_devices_by_report(hid_devices)
|
||||
|
||||
if self.vid and self.pid:
|
||||
devices = list(filter(self.is_filtered_device, devices))
|
||||
|
||||
# Add index numbers
|
||||
device_index = {}
|
||||
for device in devices:
|
||||
id = ':'.join((int2hex(device['vendor_id']), int2hex(device['product_id'])))
|
||||
|
||||
if id not in device_index:
|
||||
device_index[id] = 0
|
||||
|
||||
device_index[id] += 1
|
||||
device['index'] = device_index[id]
|
||||
|
||||
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.
|
||||
"""
|
||||
devices = device_finder.find_devices()
|
||||
|
||||
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'])
|
||||
|
||||
if cli.args.bootloaders:
|
||||
bootloaders = device_finder.find_bootloaders()
|
||||
|
||||
if bootloaders:
|
||||
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))])
|
||||
|
||||
|
||||
@cli.argument('--bootloaders', arg_only=True, default=True, action='store_boolean', help='displaying bootloaders.')
|
||||
@cli.argument('-d', '--device', help='Device to select - uses format <pid>:<vid>[:<index>].')
|
||||
@cli.argument('-l', '--list', arg_only=True, action='store_true', help='List available hid_listen devices.')
|
||||
@cli.argument('-n', '--numeric', arg_only=True, action='store_true', help='Show VID/PID instead of names.')
|
||||
@cli.argument('-t', '--timestamp', arg_only=True, action='store_true', help='Print the timestamp for received messages as well.')
|
||||
@cli.argument('-w', '--wait', type=int, default=1, help="How many seconds to wait between checks (Default: 1)")
|
||||
@cli.subcommand('Acquire debugging information from usb hid devices.')
|
||||
def console(cli):
|
||||
"""Acquire debugging information from usb hid devices
|
||||
"""
|
||||
vid = None
|
||||
pid = None
|
||||
index = 1
|
||||
|
||||
if cli.config.console.device:
|
||||
device = cli.config.console.device.split(':')
|
||||
|
||||
if len(device) == 2:
|
||||
vid, pid = device
|
||||
|
||||
elif len(device) == 3:
|
||||
vid, pid, index = device
|
||||
|
||||
if not index.isdigit():
|
||||
cli.log.error('Device index must be a number! Got "%s" instead.', index)
|
||||
exit(1)
|
||||
|
||||
index = int(index)
|
||||
|
||||
if index < 1:
|
||||
cli.log.error('Device index must be greater than 0! Got %s', index)
|
||||
exit(1)
|
||||
|
||||
else:
|
||||
cli.log.error('Invalid format for device, expected "<pid>:<vid>[:<index>]" but got "%s".', cli.config.console.device)
|
||||
cli.print_help()
|
||||
exit(1)
|
||||
|
||||
vid = vid.upper()
|
||||
pid = pid.upper()
|
||||
|
||||
device_finder = FindDevices(vid, pid, index, cli.args.numeric)
|
||||
|
||||
if cli.args.list:
|
||||
return list_devices(device_finder)
|
||||
|
||||
print('Looking for devices...', flush=True)
|
||||
device_finder.run_forever()
|
||||
@@ -0,0 +1,30 @@
|
||||
"""Prints environment information.
|
||||
"""
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
from milc import cli
|
||||
from qmk_cli.helpers import is_qmk_firmware
|
||||
|
||||
|
||||
@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', "")
|
||||
data = {
|
||||
'QMK_HOME': home,
|
||||
'QMK_FIRMWARE': home if is_qmk_firmware(Path(home)) else ""
|
||||
}
|
||||
|
||||
# Now munge the current cli config
|
||||
for key, val in cli.config.general.items():
|
||||
converted_key = 'QMK_' + key.upper()
|
||||
data[converted_key] = val
|
||||
|
||||
if cli.args.var:
|
||||
# dump out requested arg
|
||||
print(data[cli.args.var])
|
||||
else:
|
||||
# dump out everything
|
||||
for key, val in data.items():
|
||||
print(f'{key}="{val}"')
|
||||
@@ -9,6 +9,7 @@ from pathlib import Path
|
||||
from milc import cli
|
||||
from milc.questions import yesno
|
||||
from qmk_cli.git import git_clone
|
||||
from qmk_cli.helpers import is_qmk_firmware
|
||||
|
||||
default_base = 'https://github.com'
|
||||
default_repo = 'qmk_firmware'
|
||||
@@ -19,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',
|
||||
@@ -44,10 +45,10 @@ def git_upstream(destination):
|
||||
|
||||
@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='The URL all git operations start from. Default: %s' % default_base)
|
||||
@cli.argument('-b', '--branch', arg_only=True, default=default_branch, help='The branch to clone. Default: %s' % default_branch)
|
||||
@cli.argument('-H', '--home', arg_only=True, default=Path(os.environ['QMK_HOME']), type=Path, help='The location for QMK Firmware. Default: %s' % os.environ['QMK_HOME'])
|
||||
@cli.argument('fork', arg_only=True, default=default_fork, nargs='?', help='The qmk_firmware fork to clone. Default: %s' % default_fork)
|
||||
@cli.subcommand('Setup your computer for qmk_firmware.')
|
||||
def setup(cli):
|
||||
"""Guide the user through setting up their QMK environment.
|
||||
@@ -61,31 +62,46 @@ def setup(cli):
|
||||
exit(1)
|
||||
|
||||
# Check on qmk_firmware, and if it doesn't exist offer to check it out.
|
||||
if (cli.args.home / 'Makefile').exists():
|
||||
if is_qmk_firmware(cli.args.home):
|
||||
cli.log.info('Found qmk_firmware at %s.', str(cli.args.home))
|
||||
|
||||
elif cli.args.home.exists():
|
||||
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.error("Path '%s' exists but is not a qmk_firmware clone!", path_str)
|
||||
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))
|
||||
git_url = '/'.join((cli.args.baseurl, cli.args.fork))
|
||||
|
||||
if git_clone(git_url, cli.args.home, cli.config.setup.branch):
|
||||
if git_clone(git_url, cli.args.home, cli.args.branch):
|
||||
git_upstream(cli.args.home)
|
||||
else:
|
||||
exit(1)
|
||||
else:
|
||||
cli.log.warning('Not cloning qmk_firmware due to user input or --no flag.')
|
||||
|
||||
# Offer to set `user.qmk_home` for them.
|
||||
if cli.args.home != os.environ['QMK_HOME'] and yesno(home_prompt):
|
||||
if str(cli.args.home) != os.environ['QMK_HOME'] and yesno(home_prompt):
|
||||
cli.config['user']['qmk_home'] = str(cli.args.home.absolute())
|
||||
cli.config_source['user']['qmk_home'] = 'config_file'
|
||||
cli.write_config_option('user', 'qmk_home')
|
||||
|
||||
# Run `qmk_firmware/bin/qmk doctor` to check the rest of the environment out
|
||||
qmk_bin = cli.args.home / 'bin/qmk'
|
||||
doctor_cmd = [sys.executable, qmk_bin.as_posix(), 'doctor']
|
||||
# Run `qmk doctor` to check the rest of the environment out
|
||||
if cli.args.home.exists():
|
||||
color = '--color' if cli.config.general.color else '--no-color'
|
||||
unicode = '--unicode' if cli.config.general.unicode else '--no-unicode'
|
||||
doctor_command = [Path(sys.argv[0]).as_posix(), color, unicode, 'doctor']
|
||||
|
||||
if cli.args.yes:
|
||||
doctor_cmd.append('--yes')
|
||||
if cli.args.no:
|
||||
doctor_command.append('-n')
|
||||
|
||||
if cli.args.no:
|
||||
doctor_cmd.append('--no')
|
||||
if cli.args.yes:
|
||||
doctor_command.append('-y')
|
||||
|
||||
subprocess.run(doctor_cmd)
|
||||
cli.run(doctor_command, stdin=None, capture_output=False)
|
||||
|
||||
@@ -1,24 +1,7 @@
|
||||
#!/bin/sh
|
||||
#!/bin/bash
|
||||
# Increment the verison number and release a new version of qmk_cli.
|
||||
#
|
||||
# Required packages: pip3 install bumpversion twine
|
||||
|
||||
set -e
|
||||
set -x
|
||||
|
||||
PYPI_USERNAME=${PYPI_USERNAME:=skully}
|
||||
FLIT_USERNAME=$PYPI_USERNAME
|
||||
TWINE_USERNAME=$PYPI_USERNAME
|
||||
|
||||
export FLIT_USERNAME TWINE_USERNAME
|
||||
|
||||
rm -f dist/*
|
||||
bumpversion patch
|
||||
git push origin master --tags
|
||||
|
||||
# For now we don't use flit
|
||||
#flit publish
|
||||
|
||||
# Use setuptools until flit works with homebrew
|
||||
python3 setup.py sdist bdist_wheel
|
||||
twine upload dist/qmk-*
|
||||
echo "Use the github action instead!"
|
||||
exit 1
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
build
|
||||
bumpversion
|
||||
requests
|
||||
twine
|
||||
@@ -1,13 +1,5 @@
|
||||
[bumpversion]
|
||||
current_version = 0.0.36
|
||||
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
|
||||
@@ -16,17 +8,51 @@ universal = 1
|
||||
ignore = E501,E226
|
||||
|
||||
[metadata]
|
||||
name = qmk
|
||||
version = 1.1.1
|
||||
author = skullydazed
|
||||
author-email = skullydazed@gmail.com
|
||||
description-file = README.md
|
||||
dist-name = qmk
|
||||
license_file = LICENSE
|
||||
module = qmk_cli
|
||||
home-page = https://github.com/qmk/qmk_cli
|
||||
requires-python = >=3.5
|
||||
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 =
|
||||
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 =
|
||||
Development Status :: 3 - Alpha
|
||||
Environment :: Console
|
||||
Intended Audience :: Developers
|
||||
Intended Audience :: System Administrators
|
||||
Intended Audience :: End Users/Desktop
|
||||
License :: OSI Approved :: MIT License
|
||||
Natural Language :: English
|
||||
Programming Language :: Python :: 3 :: Only
|
||||
Topic :: Scientific/Engineering
|
||||
Topic :: Software Development
|
||||
Topic :: Utilities
|
||||
|
||||
[entry_points]
|
||||
qmk = qmk_cli.script_qmk:main
|
||||
[options]
|
||||
install_requires =
|
||||
hid
|
||||
milc>=1.4.2
|
||||
pyusb
|
||||
setuptools>=45
|
||||
# qmk_firmware packages
|
||||
dotty-dict
|
||||
hjson
|
||||
jsonschema>=4
|
||||
pillow
|
||||
pygments
|
||||
pyserial
|
||||
packages = find:
|
||||
python_requires = >=3.7
|
||||
|
||||
[options.entry_points]
|
||||
console_scripts =
|
||||
qmk = qmk_cli.script_qmk:main
|
||||
|
||||
[yapf]
|
||||
align_closing_bracket_with_visual_indent = True
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
from configparser import ConfigParser
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
setup_cfg = ConfigParser()
|
||||
setup_cfg.read('setup.cfg')
|
||||
metadata = setup_cfg['metadata']
|
||||
|
||||
if __name__ == "__main__":
|
||||
with open('README.md', encoding='utf-8') as readme_file:
|
||||
long_description=readme_file.read()
|
||||
setup(
|
||||
name=metadata['dist-name'],
|
||||
description='A program to help users work with QMK Firmware.',
|
||||
entry_points={
|
||||
'console_scripts': ['%s = %s' % l for l in setup_cfg['entry_points'].items()],
|
||||
},
|
||||
license='MIT License',
|
||||
url=metadata['home-page'],
|
||||
version=setup_cfg['bumpversion']['current_version'],
|
||||
author=metadata['author'],
|
||||
author_email=metadata['author-email'],
|
||||
maintainer=metadata['author'],
|
||||
maintainer_email=metadata['author-email'],
|
||||
long_description=long_description,
|
||||
long_description_content_type="text/markdown",
|
||||
packages=find_packages(),
|
||||
py_modules = ['milc'],
|
||||
classifiers=[
|
||||
'Development Status :: 3 - Alpha',
|
||||
'Environment :: Console',
|
||||
'Intended Audience :: Developers',
|
||||
'Intended Audience :: System Administrators',
|
||||
'Intended Audience :: End Users/Desktop',
|
||||
'License :: OSI Approved :: MIT License',
|
||||
'Natural Language :: English',
|
||||
'Programming Language :: Python :: 3 :: Only',
|
||||
'Topic :: Scientific/Engineering',
|
||||
'Topic :: Software Development',
|
||||
'Topic :: Utilities',
|
||||
],
|
||||
requires_python=metadata['requires-python'],
|
||||
install_requires=[
|
||||
"appdirs",
|
||||
"argcomplete",
|
||||
"colorama",
|
||||
"flake8",
|
||||
"hjson",
|
||||
"milc>=1.0.8",
|
||||
"nose2",
|
||||
"pygments",
|
||||
"yapf"
|
||||
],
|
||||
)
|
||||
Executable
+29
@@ -0,0 +1,29 @@
|
||||
#!/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('BRANCH_NAME', 'main')
|
||||
gh_workflow_ids = environ.get('WORKFLOW_IDS', 'debian-buster-publish.yml,fedora-32-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