From 0a03f7cff7b6892ca93333fa5061c1ca454de0cb Mon Sep 17 00:00:00 2001 From: zvecr Date: Wed, 5 Jun 2019 01:17:01 +0100 Subject: Update docker util script and travis to use new base container --- util/docker_build.sh | 2 +- util/travis_build.sh | 2 +- util/travis_test.sh | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) (limited to 'util') diff --git a/util/docker_build.sh b/util/docker_build.sh index c573ebcae..6feeb1f5d 100755 --- a/util/docker_build.sh +++ b/util/docker_build.sh @@ -46,5 +46,5 @@ fi dir=$(pwd -W 2>/dev/null) || dir=$PWD # Use Windows path if on Windows # Run container and build firmware -docker run --rm -it $usb_args -v "$dir":/qmk_firmware qmkfm/qmk_firmware \ +docker run --rm -it $usb_args -w /qmk_firmware/ -v "$dir":/qmk_firmware qmkfm/base_container \ make "$keyboard${keymap:+:$keymap}${target:+:$target}" diff --git a/util/travis_build.sh b/util/travis_build.sh index 554ec8b68..fd5511a72 100755 --- a/util/travis_build.sh +++ b/util/travis_build.sh @@ -3,7 +3,7 @@ # if docker is installed - call make within the qmk docker image if command -v docker >/dev/null; then function make() { - docker run --rm -e MAKEFLAGS="$MAKEFLAGS" -w /qmk_firmware/ -v "$PWD":/qmk_firmware --user $(id -u):$(id -g) qmkfm/qmk_firmware make "$@" + docker run --rm -e MAKEFLAGS="$MAKEFLAGS" -w /qmk_firmware/ -v "$PWD":/qmk_firmware --user $(id -u):$(id -g) qmkfm/base_container make "$@" } fi diff --git a/util/travis_test.sh b/util/travis_test.sh index 3be4afff7..e6a50ac16 100644 --- a/util/travis_test.sh +++ b/util/travis_test.sh @@ -22,7 +22,7 @@ fi # if docker is installed - call make within the qmk docker image if command -v docker >/dev/null; then function make() { - docker run --rm -e MAKEFLAGS="$MAKEFLAGS" -w /qmk_firmware/ -v "$PWD":/qmk_firmware --user $(id -u):$(id -g) qmkfm/qmk_firmware make "$@" + docker run --rm -e MAKEFLAGS="$MAKEFLAGS" -w /qmk_firmware/ -v "$PWD":/qmk_firmware --user $(id -u):$(id -g) qmkfm/base_container make "$@" } fi -- cgit v1.2.3-70-g09d2 From a25dd58bc56b0c4010673723ac44eaff914979bb Mon Sep 17 00:00:00 2001 From: skullydazed Date: Mon, 15 Jul 2019 12:14:27 -0700 Subject: QMK CLI and JSON keymap support (#6176) * Script to generate keymap.c from JSON file. * Support for keymap.json * Add a warning about the keymap.c getting overwritten. * Fix keymap generating * Install the python deps * Flesh out more of the python environment * Remove defunct json2keymap * Style everything with yapf * Polish up python support * Hide json keymap.c into the .build dir * Polish up qmk-compile-json * Make milc work with positional arguments * Fix a couple small things * Fix some errors and make the CLI more understandable * Make the qmk wrapper more robust * Add basic QMK Doctor * Clean up docstrings and flesh them out as needed * remove unused compile_firmware() function --- .editorconfig | 4 + .gitignore | 3 + bin/qmk | 97 +++ bin/qmk-compile-json | 1 + bin/qmk-doctor | 1 + bin/qmk-hello | 1 + bin/qmk-json-keymap | 1 + build_json.mk | 27 + build_keyboard.mk | 60 +- docs/_summary.md | 4 + docs/cli.md | 31 + docs/coding_conventions_c.md | 58 ++ docs/coding_conventions_python.md | 314 +++++++++ docs/contributing.md | 58 +- docs/python_development.md | 45 ++ .../clueboard/66_hotswap/keymaps/json/keymap.json | 1 + lib/python/milc.py | 716 +++++++++++++++++++++ lib/python/qmk/__init__.py | 0 lib/python/qmk/cli/compile/__init__.py | 0 lib/python/qmk/cli/compile/json.py | 44 ++ lib/python/qmk/cli/doctor.py | 47 ++ lib/python/qmk/cli/hello.py | 13 + lib/python/qmk/cli/json/__init__.py | 0 lib/python/qmk/cli/json/keymap.py | 54 ++ lib/python/qmk/errors.py | 6 + lib/python/qmk/keymap.py | 100 +++ lib/python/qmk/path.py | 32 + requirements.txt | 5 + setup.cfg | 330 ++++++++++ util/freebsd_install.sh | 2 + util/linux_install.sh | 5 + util/macos_install.sh | 3 + util/msys2_install.sh | 3 + util/wsl_install.sh | 5 +- 34 files changed, 1988 insertions(+), 83 deletions(-) create mode 100755 bin/qmk create mode 120000 bin/qmk-compile-json create mode 120000 bin/qmk-doctor create mode 120000 bin/qmk-hello create mode 120000 bin/qmk-json-keymap create mode 100644 build_json.mk create mode 100644 docs/cli.md create mode 100644 docs/coding_conventions_c.md create mode 100644 docs/coding_conventions_python.md create mode 100644 docs/python_development.md create mode 100644 keyboards/clueboard/66_hotswap/keymaps/json/keymap.json create mode 100644 lib/python/milc.py create mode 100644 lib/python/qmk/__init__.py create mode 100644 lib/python/qmk/cli/compile/__init__.py create mode 100755 lib/python/qmk/cli/compile/json.py create mode 100755 lib/python/qmk/cli/doctor.py create mode 100755 lib/python/qmk/cli/hello.py create mode 100644 lib/python/qmk/cli/json/__init__.py create mode 100755 lib/python/qmk/cli/json/keymap.py create mode 100644 lib/python/qmk/errors.py create mode 100644 lib/python/qmk/keymap.py create mode 100644 lib/python/qmk/path.py create mode 100644 requirements.txt create mode 100644 setup.cfg (limited to 'util') diff --git a/.editorconfig b/.editorconfig index 26e3a39cf..60827f04b 100644 --- a/.editorconfig +++ b/.editorconfig @@ -16,6 +16,10 @@ insert_final_newline = true trim_trailing_whitespace = false indent_size = 4 +[{qmk,*.py}] +charset = utf-8 +max_line_length = 200 + # Make these match what we have in .gitattributes [*.mk] end_of_line = lf diff --git a/.gitignore b/.gitignore index 7cd7fa801..140bf4aa7 100644 --- a/.gitignore +++ b/.gitignore @@ -70,3 +70,6 @@ util/Win_Check_Output.txt secrets.tar id_rsa_* /.vs + +# python things +__pycache__ diff --git a/bin/qmk b/bin/qmk new file mode 100755 index 000000000..c34365bed --- /dev/null +++ b/bin/qmk @@ -0,0 +1,97 @@ +#!/usr/bin/env python3 +"""CLI wrapper for running QMK commands. +""" +import os +import subprocess +import sys +from glob import glob +from time import strftime +from importlib import import_module +from importlib.util import find_spec + +# Add the QMK python libs to our path +script_dir = os.path.dirname(os.path.realpath(__file__)) +qmk_dir = os.path.abspath(os.path.join(script_dir, '..')) +python_lib_dir = os.path.abspath(os.path.join(qmk_dir, 'lib', 'python')) +sys.path.append(python_lib_dir) + +# Change to the root of our checkout +os.environ['ORIG_CWD'] = os.getcwd() +os.chdir(qmk_dir) + +# Make sure our modules have been setup +with open('requirements.txt', 'r') as fd: + for line in fd.readlines(): + line = line.strip().replace('<', '=').replace('>', '=') + + if line[0] == '#': + continue + + if '#' in line: + line = line.split('#')[0] + + module = line.split('=')[0] if '=' in line else line + if not find_spec(module): + print('Your QMK build environment is not fully setup!\n') + print('Please run `./util/qmk_install.sh` to setup QMK.') + exit(255) + +# Figure out our version +command = ['git', 'describe', '--abbrev=6', '--dirty', '--always', '--tags'] +result = subprocess.run(command, text=True, capture_output=True) + +if result.returncode == 0: + os.environ['QMK_VERSION'] = 'QMK ' + result.stdout.strip() +else: + os.environ['QMK_VERSION'] = 'QMK ' + strftime('%Y-%m-%d-%H:%M:%S') + +# Setup the CLI +import milc +milc.EMOJI_LOGLEVELS['INFO'] = '{fg_blue}ψ{style_reset_all}' + +# If we were invoked as `qmk ` massage sys.argv into `qmk-`. +# This means we can't accept arguments to the qmk script itself. +script_name = os.path.basename(sys.argv[0]) +if script_name == 'qmk': + if len(sys.argv) == 1: + milc.cli.log.error('No subcommand specified!\n') + + if len(sys.argv) == 1 or sys.argv[1] in ['-h', '--help']: + milc.cli.echo('usage: qmk [...]') + milc.cli.echo('\nsubcommands:') + subcommands = glob(os.path.join(qmk_dir, 'bin', 'qmk-*')) + for subcommand in sorted(subcommands): + subcommand = os.path.basename(subcommand).split('-', 1)[1] + milc.cli.echo('\t%s', subcommand) + milc.cli.echo('\nqmk --help for more information') + exit(1) + + if sys.argv[1] in ['-V', '--version']: + milc.cli.echo(os.environ['QMK_VERSION']) + exit(0) + + sys.argv[0] = script_name = '-'.join((script_name, sys.argv[1])) + del sys.argv[1] + +# Look for which module to import +if script_name == 'qmk': + milc.cli.print_help() + exit(0) +elif not script_name.startswith('qmk-'): + milc.cli.log.error('Invalid symlink, must start with "qmk-": %s', script_name) +else: + subcommand = script_name.replace('-', '.').replace('_', '.').split('.') + subcommand.insert(1, 'cli') + subcommand = '.'.join(subcommand) + + try: + import_module(subcommand) + except ModuleNotFoundError as e: + if e.__class__.__name__ != subcommand: + raise + + milc.cli.log.error('Invalid subcommand! Could not import %s.', subcommand) + exit(1) + +if __name__ == '__main__': + milc.cli() diff --git a/bin/qmk-compile-json b/bin/qmk-compile-json new file mode 120000 index 000000000..c92dce8a1 --- /dev/null +++ b/bin/qmk-compile-json @@ -0,0 +1 @@ +qmk \ No newline at end of file diff --git a/bin/qmk-doctor b/bin/qmk-doctor new file mode 120000 index 000000000..c92dce8a1 --- /dev/null +++ b/bin/qmk-doctor @@ -0,0 +1 @@ +qmk \ No newline at end of file diff --git a/bin/qmk-hello b/bin/qmk-hello new file mode 120000 index 000000000..c92dce8a1 --- /dev/null +++ b/bin/qmk-hello @@ -0,0 +1 @@ +qmk \ No newline at end of file diff --git a/bin/qmk-json-keymap b/bin/qmk-json-keymap new file mode 120000 index 000000000..c92dce8a1 --- /dev/null +++ b/bin/qmk-json-keymap @@ -0,0 +1 @@ +qmk \ No newline at end of file diff --git a/build_json.mk b/build_json.mk new file mode 100644 index 000000000..2e23ed148 --- /dev/null +++ b/build_json.mk @@ -0,0 +1,27 @@ +# Look for a json keymap file +ifneq ("$(wildcard $(MAIN_KEYMAP_PATH_5)/keymap.json)","") + KEYMAP_C := $(KEYBOARD_OUTPUT)/src/keymap.c + KEYMAP_JSON := $(MAIN_KEYMAP_PATH_5)/keymap.json + KEYMAP_PATH := $(MAIN_KEYMAP_PATH_5) +else ifneq ("$(wildcard $(MAIN_KEYMAP_PATH_4)/keymap.json)","") + KEYMAP_C := $(KEYBOARD_OUTPUT)/src/keymap.c + KEYMAP_JSON := $(MAIN_KEYMAP_PATH_4)/keymap.json + KEYMAP_PATH := $(MAIN_KEYMAP_PATH_4) +else ifneq ("$(wildcard $(MAIN_KEYMAP_PATH_3)/keymap.json)","") + KEYMAP_C := $(KEYBOARD_OUTPUT)/src/keymap.c + KEYMAP_JSON := $(MAIN_KEYMAP_PATH_3)/keymap.json + KEYMAP_PATH := $(MAIN_KEYMAP_PATH_3) +else ifneq ("$(wildcard $(MAIN_KEYMAP_PATH_2)/keymap.json)","") + KEYMAP_C := $(KEYBOARD_OUTPUT)/src/keymap.c + KEYMAP_JSON := $(MAIN_KEYMAP_PATH_2)/keymap.json + KEYMAP_PATH := $(MAIN_KEYMAP_PATH_2) +else ifneq ("$(wildcard $(MAIN_KEYMAP_PATH_1)/keymap.json)","") + KEYMAP_C := $(KEYBOARD_OUTPUT)/src/keymap.c + KEYMAP_JSON := $(MAIN_KEYMAP_PATH_1)/keymap.json + KEYMAP_PATH := $(MAIN_KEYMAP_PATH_1) +endif + +# Generate the keymap.c +ifneq ("$(KEYMAP_JSON)","") + _ = $(shell bin/qmk-json-keymap -f $(KEYMAP_JSON) -o $(KEYMAP_C)) +endif diff --git a/build_keyboard.mk b/build_keyboard.mk index 213cb4445..0e3c5ea23 100644 --- a/build_keyboard.mk +++ b/build_keyboard.mk @@ -98,31 +98,38 @@ MAIN_KEYMAP_PATH_3 := $(KEYBOARD_PATH_3)/keymaps/$(KEYMAP) MAIN_KEYMAP_PATH_4 := $(KEYBOARD_PATH_4)/keymaps/$(KEYMAP) MAIN_KEYMAP_PATH_5 := $(KEYBOARD_PATH_5)/keymaps/$(KEYMAP) -ifneq ("$(wildcard $(MAIN_KEYMAP_PATH_5)/keymap.c)","") - -include $(MAIN_KEYMAP_PATH_5)/rules.mk - KEYMAP_C := $(MAIN_KEYMAP_PATH_5)/keymap.c - KEYMAP_PATH := $(MAIN_KEYMAP_PATH_5) -else ifneq ("$(wildcard $(MAIN_KEYMAP_PATH_4)/keymap.c)","") - -include $(MAIN_KEYMAP_PATH_4)/rules.mk - KEYMAP_C := $(MAIN_KEYMAP_PATH_4)/keymap.c - KEYMAP_PATH := $(MAIN_KEYMAP_PATH_4) -else ifneq ("$(wildcard $(MAIN_KEYMAP_PATH_3)/keymap.c)","") - -include $(MAIN_KEYMAP_PATH_3)/rules.mk - KEYMAP_C := $(MAIN_KEYMAP_PATH_3)/keymap.c - KEYMAP_PATH := $(MAIN_KEYMAP_PATH_3) -else ifneq ("$(wildcard $(MAIN_KEYMAP_PATH_2)/keymap.c)","") - -include $(MAIN_KEYMAP_PATH_2)/rules.mk - KEYMAP_C := $(MAIN_KEYMAP_PATH_2)/keymap.c - KEYMAP_PATH := $(MAIN_KEYMAP_PATH_2) -else ifneq ("$(wildcard $(MAIN_KEYMAP_PATH_1)/keymap.c)","") - -include $(MAIN_KEYMAP_PATH_1)/rules.mk - KEYMAP_C := $(MAIN_KEYMAP_PATH_1)/keymap.c - KEYMAP_PATH := $(MAIN_KEYMAP_PATH_1) -else ifneq ($(LAYOUTS),) - include build_layout.mk -else - $(error Could not find keymap) - # this state should never be reached +# Check for keymap.json first, so we can regenerate keymap.c +include build_json.mk + +ifeq ("$(wildcard $(KEYMAP_PATH))", "") + # Look through the possible keymap folders until we find a matching keymap.c + ifneq ("$(wildcard $(MAIN_KEYMAP_PATH_5)/keymap.c)","") + -include $(MAIN_KEYMAP_PATH_5)/rules.mk + KEYMAP_C := $(MAIN_KEYMAP_PATH_5)/keymap.c + KEYMAP_PATH := $(MAIN_KEYMAP_PATH_5) + else ifneq ("$(wildcard $(MAIN_KEYMAP_PATH_4)/keymap.c)","") + -include $(MAIN_KEYMAP_PATH_4)/rules.mk + KEYMAP_C := $(MAIN_KEYMAP_PATH_4)/keymap.c + KEYMAP_PATH := $(MAIN_KEYMAP_PATH_4) + else ifneq ("$(wildcard $(MAIN_KEYMAP_PATH_3)/keymap.c)","") + -include $(MAIN_KEYMAP_PATH_3)/rules.mk + KEYMAP_C := $(MAIN_KEYMAP_PATH_3)/keymap.c + KEYMAP_PATH := $(MAIN_KEYMAP_PATH_3) + else ifneq ("$(wildcard $(MAIN_KEYMAP_PATH_2)/keymap.c)","") + -include $(MAIN_KEYMAP_PATH_2)/rules.mk + KEYMAP_C := $(MAIN_KEYMAP_PATH_2)/keymap.c + KEYMAP_PATH := $(MAIN_KEYMAP_PATH_2) + else ifneq ("$(wildcard $(MAIN_KEYMAP_PATH_1)/keymap.c)","") + -include $(MAIN_KEYMAP_PATH_1)/rules.mk + KEYMAP_C := $(MAIN_KEYMAP_PATH_1)/keymap.c + KEYMAP_PATH := $(MAIN_KEYMAP_PATH_1) + else ifneq ($(LAYOUTS),) + # If we haven't found a keymap yet fall back to community layouts + include build_layout.mk + else + $(error Could not find keymap) + # this state should never be reached + endif endif ifeq ($(strip $(CTPC)), yes) @@ -313,7 +320,6 @@ ifneq ("$(wildcard $(USER_PATH)/config.h)","") CONFIG_H += $(USER_PATH)/config.h endif - # Object files directory # To put object files in current directory, use a dot (.), do NOT make # this an empty or blank macro! @@ -323,7 +329,7 @@ ifneq ("$(wildcard $(KEYMAP_PATH)/config.h)","") CONFIG_H += $(KEYMAP_PATH)/config.h endif -# # project specific files +# project specific files SRC += $(KEYBOARD_SRC) \ $(KEYMAP_C) \ $(QUANTUM_SRC) diff --git a/docs/_summary.md b/docs/_summary.md index 8a40ccd7f..611c283ac 100644 --- a/docs/_summary.md +++ b/docs/_summary.md @@ -8,6 +8,7 @@ * [QMK Basics](README.md) * [QMK Introduction](getting_started_introduction.md) + * [QMK CLI](cli.md) * [Contributing to QMK](contributing.md) * [How to Use Github](getting_started_github.md) * [Getting Help](getting_started_getting_help.md) @@ -34,6 +35,8 @@ * [Keyboard Guidelines](hardware_keyboard_guidelines.md) * [Config Options](config_options.md) * [Keycodes](keycodes.md) + * [Coding Conventions - C](coding_conventions_c.md) + * [Coding Conventions - Python](coding_conventions_python.md) * [Documentation Best Practices](documentation_best_practices.md) * [Documentation Templates](documentation_templates.md) * [Glossary](reference_glossary.md) @@ -41,6 +44,7 @@ * [Useful Functions](ref_functions.md) * [Configurator Support](reference_configurator_support.md) * [info.json Format](reference_info_json.md) + * [Python Development](python_development.md) * [Features](features.md) * [Basic Keycodes](keycodes_basic.md) diff --git a/docs/cli.md b/docs/cli.md new file mode 100644 index 000000000..0365f2c9c --- /dev/null +++ b/docs/cli.md @@ -0,0 +1,31 @@ +# QMK CLI + +This page describes how to setup and use the QMK CLI. + +# Overview + +The QMK CLI makes building and working with QMK keyboards easier. We have provided a number of commands to help you work with QMK: + +* `qmk compile-json` + +# Setup + +Simply add the `qmk_firmware/bin` directory to your `PATH`. You can run the `qmk` commands from any directory. + +``` +export PATH=$PATH:$HOME/qmk_firmware/bin +``` + +You may want to add this to your `.profile`, `.bash_profile`, `.zsh_profile`, or other shell startup scripts. + +# Commands + +## `qmk compile-json` + +This command allows you to compile JSON files you have downloaded from . + +**Usage**: + +``` +qmk compile-json mine.json +``` diff --git a/docs/coding_conventions_c.md b/docs/coding_conventions_c.md new file mode 100644 index 000000000..cbddedf8b --- /dev/null +++ b/docs/coding_conventions_c.md @@ -0,0 +1,58 @@ +# Coding Conventions (C) + +Most of our style is pretty easy to pick up on, but right now it's not entirely consistent. You should match the style of the code surrounding your change, but if that code is inconsistent or unclear use the following guidelines: + +* We indent using four (4) spaces (soft tabs) +* We use a modified One True Brace Style + * Opening Brace: At the end of the same line as the statement that opens the block + * Closing Brace: Lined up with the first character of the statement that opens the block + * Else If: Place the closing brace at the beginning of the line and the next opening brace at the end of the same line. + * Optional Braces: Always include optional braces. + * Good: if (condition) { return false; } + * Bad: if (condition) return false; +* We encourage use of C style comments: `/* */` + * Think of them as a story describing the feature + * Use them liberally to explain why particular decisions were made. + * Do not write obvious comments + * If you not sure if a comment is obvious, go ahead and include it. +* In general we don't wrap lines, they can be as long as needed. If you do choose to wrap lines please do not wrap any wider than 76 columns. +* We use `#pragma once` at the start of header files rather than old-style include guards (`#ifndef THIS_FILE_H`, `#define THIS_FILE_H`, ..., `#endif`) +* We accept both forms of preprocessor if's: `#ifdef DEFINED` and `#if defined(DEFINED)` + * If you are not sure which to prefer use the `#if defined(DEFINED)` form. + * Do not change existing code from one style to the other, except when moving to a multiple condition `#if`. + * Do not put whitespace between `#` and `if`. + * When deciding how (or if) to indent directives keep these points in mind: + * Readability is more important than consistency. + * Follow the file's existing style. If the file is mixed follow the style that makes sense for the section you are modifying. + * When choosing to indent you can follow the indention level of the surrounding C code, or preprocessor directives can have their own indent level. Choose the style that best communicates the intent of your code. + +Here is an example for easy reference: + +```c +/* Enums for foo */ +enum foo_state { + FOO_BAR, + FOO_BAZ, +}; + +/* Returns a value */ +int foo(void) { + if (some_condition) { + return FOO_BAR; + } else { + return -1; + } +} +``` + +# Auto-formatting with clang-format + +[Clang-format](https://clang.llvm.org/docs/ClangFormat.html) is part of LLVM and can automatically format your code for you, because ain't nobody got time to do it manually. We supply a configuration file for it that applies most of the coding conventions listed above. It will only change whitespace and newlines, so you will still have to remember to include optional braces yourself. + +Use the [full LLVM installer](http://llvm.org/builds/) to get clang-format on Windows, or use `sudo apt install clang-format` on Ubuntu. + +If you run it from the command-line, pass `-style=file` as an option and it will automatically find the .clang-format configuration file in the QMK root directory. + +If you use VSCode, the standard C/C++ plugin supports clang-format, alternatively there is a [separate extension](https://marketplace.visualstudio.com/items?itemName=LLVMExtensions.ClangFormat) for it. + +Some things (like LAYOUT macros) are destroyed by clang-format, so either don't run it on those files, or wrap the sensitive code in `// clang-format off` and `// clang-format on`. diff --git a/docs/coding_conventions_python.md b/docs/coding_conventions_python.md new file mode 100644 index 000000000..c7743050e --- /dev/null +++ b/docs/coding_conventions_python.md @@ -0,0 +1,314 @@ +# Coding Conventions (Python) + +Most of our style follows PEP8 with some local modifications to make things less nit-picky. + +* We target Python 3.5 for compatability with all supported platforms. +* We indent using four (4) spaces (soft tabs) +* We encourage liberal use of comments + * Think of them as a story describing the feature + * Use them liberally to explain why particular decisions were made. + * Do not write obvious comments + * If you not sure if a comment is obvious, go ahead and include it. +* We require useful docstrings for all functions. +* In general we don't wrap lines, they can be as long as needed. If you do choose to wrap lines please do not wrap any wider than 76 columns. +* Some of our practices conflict with the wider python community to make our codebase more approachable to non-pythonistas. + +# YAPF + +You can use [yapf](https://github.com/google/yapf) to style your code. We provide a config in [setup.cfg](setup.cfg). + +# Imports + +We don't have a hard and fast rule for when to use `import ...` vs `from ... import ...`. Understandability and maintainability is our ultimate goal. + +Generally we prefer to import specific function and class names from a module to keep code shorter and easier to understand. Sometimes this results in a name that is ambiguous, and in such cases we prefer to import the module instead. You should avoid using the "as" keyword when importing, unless you are importing a compatability module. + +Imports should be one line per module. We group import statements together using the standard python rules- system, 3rd party, local. + +Do not use `from foo import *`. Supply a list of objects you want to import instead, or import the whole module. + +## Import Examples + +Good: + +``` +from qmk import effects + +effects.echo() +``` + +Bad: + +``` +from qmk.effects import echo + +echo() # It's unclear where echo comes from +``` + +Good: + +``` +from qmk.keymap import compile_firmware + +compile_firmware() +``` + +OK, but the above is better: + +``` +import qmk.keymap + +qmk.keymap.compile_firmware() +``` + +# Statements + +One statement per line. + +Even when allowed (EG `if foo: bar`) we do not combine 2 statements onto a single line. + +# Naming + +`module_name`, `package_name`, `ClassName`, `method_name`, `ExceptionName`, `function_name`, `GLOBAL_CONSTANT_NAME`, `global_var_name`, `instance_var_name`, `function_parameter_name`, `local_var_name`. + +Function names, variable names, and filenames should be descriptive; eschew abbreviation. In particular, do not use abbreviations that are ambiguous or unfamiliar to readers outside your project, and do not abbreviate by deleting letters within a word. + +Always use a .py filename extension. Never use dashes. + +## Names to Avoid + +* single character names except for counters or iterators. You may use "e" as an exception identifier in try/except statements. +* dashes (-) in any package/module name +* __double_leading_and_trailing_underscore__ names (reserved by Python) + +# Docstrings + +To maintain consistency with our docstrings we've set out the following guidelines. + +* Use markdown formatting +* Always use triple-dquote docstrings with at least one linebreak: `"""\n"""` +* First line is a short (< 70 char) description of what the function does +* If you need more in your docstring leave a blank line between the description and the rest. +* Start indented lines at the same indent level as the opening triple-dquote +* Document all function arguments using the format described below +* If present, Args:, Returns:, and Raises: should be the last three things in the docstring, separated by a blank line each. + +## Simple docstring example + +``` +def my_awesome_function(): + """Return the number of seconds since 1970 Jan 1 00:00 UTC. + """ + return int(time.time()) +``` + +## Complex docstring example + +``` +def my_awesome_function(): + """Return the number of seconds since 1970 Jan 1 00:00 UTC. + + This function always returns an integer number of seconds. + """ + return int(time.time()) +``` + +## Function arguments docstring example + +``` +def my_awesome_function(start=None, offset=0): + """Return the number of seconds since 1970 Jan 1 00:00 UTC. + + This function always returns an integer number of seconds. + + + Args: + start + The time to start at instead of 1970 Jan 1 00:00 UTC + + offset + Return an answer that has this number of seconds subtracted first + + Returns: + An integer describing a number of seconds. + + Raises: + ValueError + When `start` or `offset` are not positive numbers + """ + if start < 0 or offset < 0: + raise ValueError('start and offset must be positive numbers.') + + if not start: + start = time.time() + + return int(start - offset) +``` + +# Exceptions + +Exceptions are used to handle exceptional situations. They should not be used for flow control. This is a break from the python norm of "ask for forgiveness." If you are catching an exception it should be to handle a situation that is unusual. + +If you use a catch-all exception for any reason you must log the exception and stacktrace using cli.log. + +Make your try/except blocks as short as possible. If you need a lot of try statements you may need to restructure your code. + +# Tuples + +When defining one-item tuples always include a trailing comma so that it is obvious you are using a tuple. Do not rely on implicit one-item tuple unpacking. Better still use a list which is unambiguous. + +This is particularly important when using the printf-style format strings that are commonly used. + +# Lists and Dictionaries + +We have configured YAPF to differentiate between sequence styles with a trailing comma. When a trailing comma is omitted YAPF will format the sequence as a single line. When a trailing comma is included YAPF will format the sequence with one item per line. + +You should generally prefer to keep short definition on a single line. Break out to multiple lines sooner rather than later to aid readability and maintainability. + +# Parentheses + +Avoid excessive parentheses, but do use parentheses to make code easier to understand. Do not use them in return statements unless you are explicitly returning a tuple, or it is part of a math expression. + +# Format Strings + +We generally prefer printf-style format strings. Example: + +``` +name = 'World' +print('Hello, %s!' % (name,)) +``` + +This style is used by the logging module, which we make use of extensively, and we have adopted it in other places for consistency. It is also more familiar to C programmers, who are a big part of our casual audience. + +Our included CLI module has support for using these without using the percent (%) operator. Look at `cli.echo()` and the various `cli.log` functions (EG, `cli.log.info()`) for more details. + +# Comprehensions & Generator Expressions + +We encourage the liberal use of comprehensions and generators, but do not let them get too complex. If you need complexity fall back to a for loop that is easier to understand. + +# Lambdas + +OK to use but probably should be avoided. With comprehensions and generators the need for lambdas is not as strong as it once was. + +# Conditional Expressions + +OK in variable assignment, but otherwise should be avoided. + +Conditional expressions are if statements that are in line with code. For example: + +``` +x = 1 if cond else 2 +``` + +It's generally not a good idea to use these as function arguments, sequence items, etc. It's too easy to overlook. + +# Default Argument Values + +Encouraged, but values must be immutable objects. + +When specifying default values in argument lists always be careful to specify objects that can't be modified in place. If you use a mutable object the changes you make will persist between calls, which is usually not what you want. Even if that is what you intend to do it is confusing for others and will hinder understanding. + +Bad: + +``` +def my_func(foo={}): + pass +``` + +Good: + +``` +def my_func(foo=None): + if not foo: + foo = {} +``` + +# Properties + +Always use properties instead of getter and setter functions. + +``` +class Foo(object): + def __init__(self): + self._bar = None + + @property + def bar(self): + return self._bar + + @bar.setter + def bar(self, bar): + self._bar = bar +``` + +# True/False Evaluations + +You should generally prefer the implicit True/False evaluation in if statements, rather than checking equivalency. + +Bad: + +``` +if foo == True: + pass + +if bar == False: + pass +``` + +Good: + +``` +if foo: + pass + +if not bar: + pass +``` + +# Decorators + +Use when appropriate. Try to avoid too much magic unless it helps with understanding. + +# Threading and Multiprocessing + +Should be avoided. If you need this you will have to make a strong case before we merge your code. + +# Power Features + +Python is an extremely flexible language and gives you many fancy features such as custom metaclasses, access to bytecode, on-the-fly compilation, dynamic inheritance, object reparenting, import hacks, reflection, modification of system internals, etc. + +Don't use these. + +Performance is not a critical concern for us, and code understandability is. We want our codebase to be approachable by someone who only has a day or two to play with it. These features generally come with a cost to easy understanding, and we would prefer to have code that can be readily understood over faster or more compact code. + +Note that some standard library modules use these techniques and it is ok to make use of those modules. But please keep readability and understandability in mind when using them. + +# Type Annotated Code + +For now we are not using any type annotation system, and would prefer that code remain unannotated. We may revisit this in the future. + +# Function length + +Prefer small and focused functions. + +We recognize that long functions are sometimes appropriate, so no hard limit is placed on function length. If a function exceeds about 40 lines, think about whether it can be broken up without harming the structure of the program. + +Even if your long function works perfectly now, someone modifying it in a few months may add new behavior. This could result in bugs that are hard to find. Keeping your functions short and simple makes it easier for other people to read and modify your code. + +You could find long and complicated functions when working with some code. Do not be intimidated by modifying existing code: if working with such a function proves to be difficult, you find that errors are hard to debug, or you want to use a piece of it in several different contexts, consider breaking up the function into smaller and more manageable pieces. + +# FIXMEs + +It is OK to leave FIXMEs in code. Why? Encouraging people to at least document parts of code that need to be thought out more (or that are confusing) is better than leaving this code undocumented. + +All FIXMEs should be formatted like: + +``` +FIXME(username): Revisit this code when the frob feature is done. +``` + +...where username is your GitHub username. + +# Unit Tests + +These are good. We should have some one day. diff --git a/docs/contributing.md b/docs/contributing.md index 7d1a9691c..761bc9959 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -54,62 +54,10 @@ Never made an open source contribution before? Wondering how contributions work # Coding Conventions -Most of our style is pretty easy to pick up on, but right now it's not entirely consistent. You should match the style of the code surrounding your change, but if that code is inconsistent or unclear use the following guidelines: - -* We indent using four (4) spaces (soft tabs) -* We use a modified One True Brace Style - * Opening Brace: At the end of the same line as the statement that opens the block - * Closing Brace: Lined up with the first character of the statement that opens the block - * Else If: Place the closing brace at the beginning of the line and the next opening brace at the end of the same line. - * Optional Braces: Always include optional braces. - * Good: if (condition) { return false; } - * Bad: if (condition) return false; -* We encourage use of C style comments: `/* */` - * Think of them as a story describing the feature - * Use them liberally to explain why particular decisions were made. - * Do not write obvious comments - * If you not sure if a comment is obvious, go ahead and include it. -* In general we don't wrap lines, they can be as long as needed. If you do choose to wrap lines please do not wrap any wider than 76 columns. -* We use `#pragma once` at the start of header files rather than old-style include guards (`#ifndef THIS_FILE_H`, `#define THIS_FILE_H`, ..., `#endif`) -* We accept both forms of preprocessor if's: `#ifdef DEFINED` and `#if defined(DEFINED)` - * If you are not sure which to prefer use the `#if defined(DEFINED)` form. - * Do not change existing code from one style to the other, except when moving to a multiple condition `#if`. - * Do not put whitespace between `#` and `if`. - * When deciding how (or if) to indent directives keep these points in mind: - * Readability is more important than consistency. - * Follow the file's existing style. If the file is mixed follow the style that makes sense for the section you are modifying. - * When choosing to indent you can follow the indention level of the surrounding C code, or preprocessor directives can have their own indent level. Choose the style that best communicates the intent of your code. - -Here is an example for easy reference: +Most of our style is pretty easy to pick up on. If you are familiar with either C or Python you should not have too much trouble with our local styles. -```c -/* Enums for foo */ -enum foo_state { - FOO_BAR, - FOO_BAZ, -}; - -/* Returns a value */ -int foo(void) { - if (some_condition) { - return FOO_BAR; - } else { - return -1; - } -} -``` - -# Auto-formatting with clang-format - -[Clang-format](https://clang.llvm.org/docs/ClangFormat.html) is part of LLVM and can automatically format your code for you, because ain't nobody got time to do it manually. We supply a configuration file for it that applies most of the coding conventions listed above. It will only change whitespace and newlines, so you will still have to remember to include optional braces yourself. - -Use the [full LLVM installer](http://llvm.org/builds/) to get clang-format on Windows, or use `sudo apt install clang-format` on Ubuntu. - -If you run it from the command-line, pass `-style=file` as an option and it will automatically find the .clang-format configuration file in the QMK root directory. - -If you use VSCode, the standard C/C++ plugin supports clang-format, alternatively there is a [separate extension](https://marketplace.visualstudio.com/items?itemName=LLVMExtensions.ClangFormat) for it. - -Some things (like LAYOUT macros) are destroyed by clang-format, so either don't run it on those files, or wrap the sensitive code in `// clang-format off` and `// clang-format on`. +* [Coding Conventions - C](coding_conventions_c.md) +* [Coding Conventions - Python](coding_conventions_python.md) # General Guidelines diff --git a/docs/python_development.md b/docs/python_development.md new file mode 100644 index 000000000..b976a7c0e --- /dev/null +++ b/docs/python_development.md @@ -0,0 +1,45 @@ +# Python Development in QMK + +This document gives an overview of how QMK has structured its python code. You should read this before working on any of the python code. + +## Script directories + +There are two places scripts live in QMK: `qmk_firmware/bin` and `qmk_firmware/util`. You should use `bin` for any python scripts that utilize the `qmk` wrapper. Scripts that are standalone and not run very often live in `util`. + +We discourage putting anything into `bin` that does not utilize the `qmk` wrapper. If you think you have a good reason for doing so please talk to us about your use case. + +## Python Modules + +Most of the QMK python modules can be found in `qmk_firmware/lib/python`. This is the path that we append to `sys.path`. + +We have a module hierarchy under that path: + +* `qmk_firmware/lib/python` + * `milc.py` - The CLI library we use. Will be pulled out into its own module in the future. + * `qmk` - Code associated with QMK + * `cli` - Modules that will be imported for CLI commands. + * `errors.py` - Errors that can be raised within QMK apps + * `keymap.py` - Functions for working with keymaps + +## CLI Scripts + +We have a CLI wrapper that you should utilize for any user facing scripts. We think it's pretty easy to use and it gives you a lot of nice things for free. + +To use the wrapper simply place a module into `qmk_firmware/lib/python/qmk/cli`, and create a symlink to `bin/qmk` named after your module. Dashes in command names will be converted into dots so you can use hierarchy to manage commands. + +When `qmk` is run it checks to see how it was invoked. If it was invoked as `qmk` the module name is take from `sys.argv[1]`. If it was invoked as `qmk-` then everything after the first dash is taken as the module name. Dashes and underscores are converted to dots, and then `qmk.cli` is prepended before the module is imported. + +The module uses `@cli.entrypoint()` and `@cli.argument()` decorators to define an entrypoint, which is where execution starts. + +## Example CLI Script + +We have provided a QMK Hello World script you can use as an example. To run it simply run `qmk hello` or `qmk-hello`. The source code is listed below. + +``` +from milc import cli + +@cli.argument('-n', '--name', default='World', help='Name to greet.') +@cli.entrypoint('QMK Python Hello World.') +def main(cli): + cli.echo('Hello, %s!', cli.config.general.name) +``` diff --git a/keyboards/clueboard/66_hotswap/keymaps/json/keymap.json b/keyboards/clueboard/66_hotswap/keymaps/json/keymap.json new file mode 100644 index 000000000..20aa9f0f6 --- /dev/null +++ b/keyboards/clueboard/66_hotswap/keymaps/json/keymap.json @@ -0,0 +1 @@ +{"keyboard":"clueboard/66_hotswap/gen1","keymap":"default_66","layout":"LAYOUT","layers":[["KC_GESC","KC_1","KC_2","KC_3","KC_4","KC_5","KC_6","KC_7","KC_8","KC_9","KC_0","KC_MINS","KC_EQL","KC_BSPC","KC_PGUP","KC_TAB","KC_Q","KC_W","KC_E","KC_R","KC_T","KC_Y","KC_U","KC_I","KC_O","KC_P","KC_LBRC","KC_RBRC","KC_BSLS","KC_PGDN","KC_CAPS","KC_A","KC_S","KC_D","KC_F","KC_G","KC_H","KC_J","KC_K","KC_L","KC_SCLN","KC_QUOT","KC_ENT","KC_LSFT","KC_Z","KC_X","KC_C","KC_V","KC_B","KC_N","KC_M","KC_COMM","KC_DOT","KC_SLSH","KC_RSFT","KC_UP","KC_LCTL","KC_LGUI","KC_LALT","KC_SPC","KC_SPC","KC_RALT","KC_RGUI","MO(1)","KC_RCTL","KC_LEFT","KC_DOWN","KC_RGHT"],["KC_GRV","KC_F1","KC_F2","KC_F3","KC_F4","KC_F5","KC_F6","KC_F7","KC_F8","KC_F9","KC_F10","KC_F11","KC_F12","KC_DEL","BL_INC","KC_NO","KC_NO","KC_NO","KC_NO","KC_NO","KC_NO","KC_NO","KC_NO","KC_NO","KC_MPRV","KC_MPLY","KC_MNXT","KC_NO","KC_MUTE","BL_DEC","KC_NO","KC_NO","MO(2)","KC_NO","KC_NO","KC_NO","KC_NO","KC_NO","KC_NO","KC_NO","KC_NO","KC_NO","KC_NO","KC_NO","KC_NO","KC_NO","KC_NO","KC_NO","KC_NO","KC_NO","KC_NO","KC_NO","KC_NO","KC_NO","KC_NO","KC_PGUP","KC_NO","KC_NO","KC_NO","KC_NO","KC_NO","KC_NO","KC_NO","MO(1)","KC_NO","KC_HOME","KC_PGDN","KC_END"],["KC_NO","KC_NO","KC_NO","KC_NO","KC_NO","KC_NO","KC_NO","KC_NO","KC_NO","KC_NO","KC_NO","KC_NO","KC_NO","BL_TOGG","BL_INC","KC_NO","KC_NO","KC_NO","KC_NO","RESET","KC_NO","KC_NO","KC_NO","KC_NO","KC_NO","KC_NO","KC_NO","KC_NO","KC_NO","BL_DEC","KC_NO","KC_NO","MO(2)","KC_NO","KC_NO","KC_NO","KC_NO","KC_NO","KC_NO","KC_NO","KC_NO","KC_NO","KC_NO","KC_NO","KC_NO","KC_NO","KC_NO","KC_NO","KC_NO","KC_NO","KC_NO","KC_NO","KC_NO","KC_NO","KC_NO","KC_NO","KC_NO","KC_NO","KC_NO","KC_NO","BL_STEP","KC_NO","KC_NO","MO(1)","KC_NO","KC_NO","KC_NO","KC_NO"]],"author":"","notes":""} \ No newline at end of file diff --git a/lib/python/milc.py b/lib/python/milc.py new file mode 100644 index 000000000..6e82edf8b --- /dev/null +++ b/lib/python/milc.py @@ -0,0 +1,716 @@ +#!/usr/bin/env python3 +# coding=utf-8 +"""MILC - A CLI Framework + +PYTHON_ARGCOMPLETE_OK + +MILC is an opinionated framework for writing CLI apps. It optimizes for the +most common unix tool pattern- small tools that are run from the command +line but generally do not feature any user interaction while they run. + +For more details see the MILC documentation: + + +""" +from __future__ import division, print_function, unicode_literals +import argparse +import logging +import os +import re +import sys +from decimal import Decimal +from tempfile import NamedTemporaryFile +from time import sleep + +try: + from ConfigParser import RawConfigParser +except ImportError: + from configparser import RawConfigParser + +try: + import thread + import threading +except ImportError: + thread = None + +import argcomplete +import colorama + +# Log Level Representations +EMOJI_LOGLEVELS = { + 'CRITICAL': '{bg_red}{fg_white}¬_¬{style_reset_all}', + 'ERROR': '{fg_red}☒{style_reset_all}', + 'WARNING': '{fg_yellow}⚠{style_reset_all}', + 'INFO': '{fg_blue}ℹ{style_reset_all}', + 'DEBUG': '{fg_cyan}☐{style_reset_all}', + 'NOTSET': '{style_reset_all}¯\\_(o_o)_/¯' +} +EMOJI_LOGLEVELS['FATAL'] = EMOJI_LOGLEVELS['CRITICAL'] +EMOJI_LOGLEVELS['WARN'] = EMOJI_LOGLEVELS['WARNING'] + +# ANSI Color setup +# Regex was gratefully borrowed from kfir on stackoverflow: +# https://stackoverflow.com/a/45448194 +ansi_regex = r'\x1b(' \ + r'(\[\??\d+[hl])|' \ + r'([=<>a-kzNM78])|' \ + r'([\(\)][a-b0-2])|' \ + r'(\[\d{0,2}[ma-dgkjqi])|' \ + r'(\[\d+;\d+[hfy]?)|' \ + r'(\[;?[hf])|' \ + r'(#[3-68])|' \ + r'([01356]n)|' \ + r'(O[mlnp-z]?)|' \ + r'(/Z)|' \ + r'(\d+)|' \ + r'(\[\?\d;\d0c)|' \ + r'(\d;\dR))' +ansi_escape = re.compile(ansi_regex, flags=re.IGNORECASE) +ansi_styles = ( + ('fg', colorama.ansi.AnsiFore()), + ('bg', colorama.ansi.AnsiBack()), + ('style', colorama.ansi.AnsiStyle()), +) +ansi_colors = {} + +for prefix, obj in ansi_styles: + for color in [x for x in obj.__dict__ if not x.startswith('_')]: + ansi_colors[prefix + '_' + color.lower()] = getattr(obj, color) + + +def format_ansi(text): + """Return a copy of text with certain strings replaced with ansi. + """ + # Avoid .format() so we don't have to worry about the log content + for color in ansi_colors: + text = text.replace('{%s}' % color, ansi_colors[color]) + return text + ansi_colors['style_reset_all'] + + +class ANSIFormatter(logging.Formatter): + """A log formatter that inserts ANSI color. + """ + + def format(self, record): + msg = super(ANSIFormatter, self).format(record) + return format_ansi(msg) + + +class ANSIEmojiLoglevelFormatter(ANSIFormatter): + """A log formatter that makes the loglevel an emoji. + """ + + def format(self, record): + record.levelname = EMOJI_LOGLEVELS[record.levelname].format(**ansi_colors) + return super(ANSIEmojiLoglevelFormatter, self).format(record) + + +class ANSIStrippingFormatter(ANSIFormatter): + """A log formatter that strips ANSI. + """ + + def format(self, record): + msg = super(ANSIStrippingFormatter, self).format(record) + return ansi_escape.sub('', msg) + + +class Configuration(object): + """Represents the running configuration. + + This class never raises IndexError, instead it will return None if a + section or option does not yet exist. + """ + + def __contains__(self, key): + return self._config.__contains__(key) + + def __iter__(self): + return self._config.__iter__() + + def __len__(self): + return self._config.__len__() + + def __repr__(self): + return self._config.__repr__() + + def keys(self): + return self._config.keys() + + def items(self): + return self._config.items() + + def values(self): + return self._config.values() + + def __init__(self, *args, **kwargs): + self._config = {} + self.default_container = ConfigurationOption + + def __getitem__(self, key): + """Returns a config section, creating it if it doesn't exist yet. + """ + if key not in self._config: + self.__dict__[key] = self._config[key] = ConfigurationOption() + + return self._config[key] + + def __setitem__(self, key, value): + self.__dict__[key] = value + self._config[key] = value + + def __delitem__(self, key): + if key in self.__dict__ and key[0] != '_': + del self.__dict__[key] + del self._config[key] + + +class ConfigurationOption(Configuration): + def __init__(self, *args, **kwargs): + super(ConfigurationOption, self).__init__(*args, **kwargs) + self.default_container = dict + + def __getitem__(self, key): + """Returns a config section, creating it if it doesn't exist yet. + """ + if key not in self._config: + self.__dict__[key] = self._config[key] = None + + return self._config[key] + + +def handle_store_boolean(self, *args, **kwargs): + """Does the add_argument for action='store_boolean'. + """ + kwargs['add_dest'] = False + disabled_args = None + disabled_kwargs = kwargs.copy() + disabled_kwargs['action'] = 'store_false' + disabled_kwargs['help'] = 'Disable ' + kwargs['help'] + kwargs['action'] = 'store_true' + kwargs['help'] = 'Enable ' + kwargs['help'] + + for flag in args: + if flag[:2] == '--': + disabled_args = ('--no-' + flag[2:],) + break + + self.add_argument(*args, **kwargs) + self.add_argument(*disabled_args, **disabled_kwargs) + + return (args, kwargs, disabled_args, disabled_kwargs) + + +class SubparserWrapper(object): + """Wrap subparsers so we can populate the normal and the shadow parser. + """ + + def __init__(self, cli, submodule, subparser): + self.cli = cli + self.submodule = submodule + self.subparser = subparser + + for attr in dir(subparser): + if not hasattr(self, attr): + setattr(self, attr, getattr(subparser, attr)) + + def completer(self, completer): + """Add an arpcomplete completer to this subcommand. + """ + self.subparser.completer = completer + + def add_argument(self, *args, **kwargs): + if kwargs.get('add_dest', True): + kwargs['dest'] = self.submodule + '_' + self.cli.get_argument_name(*args, **kwargs) + if 'add_dest' in kwargs: + del kwargs['add_dest'] + + if 'action' in kwargs and kwargs['action'] == 'store_boolean': + return handle_store_boolean(self, *args, **kwargs) + + self.cli.acquire_lock() + self.subparser.add_argument(*args, **kwargs) + + if 'default' in kwargs: + del kwargs['default'] + if 'action' in kwargs and kwargs['action'] == 'store_false': + kwargs['action'] == 'store_true' + self.cli.subcommands_default[self.submodule].add_argument(*args, **kwargs) + self.cli.release_lock() + + +class MILC(object): + """MILC - An Opinionated Batteries Included Framework + """ + + def __init__(self): + """Initialize the MILC object. + """ + # Setup a lock for thread safety + self._lock = threading.RLock() if thread else None + + # Define some basic info + self.acquire_lock() + self._description = None + self._entrypoint = None + self._inside_context_manager = False + self.ansi = ansi_colors + self.config = Configuration() + self.config_file = None + self.prog_name = sys.argv[0][:-3] if sys.argv[0].endswith('.py') else sys.argv[0] + self.version = os.environ.get('QMK_VERSION', 'unknown') + self.release_lock() + + # Initialize all the things + self.initialize_argparse() + self.initialize_logging() + + @property + def description(self): + return self._description + + @description.setter + def description(self, value): + self._description = self._arg_parser.description = self._arg_defaults.description = value + + def echo(self, text, *args, **kwargs): + """Print colorized text to stdout, as long as stdout is a tty. + + ANSI color strings (such as {fg-blue}) will be converted into ANSI + escape sequences, and the ANSI reset sequence will be added to all + strings. + + If *args or **kwargs are passed they will be used to %-format the strings. + """ + if args and kwargs: + raise RuntimeError('You can only specify *args or **kwargs, not both!') + + if sys.stdout.isatty(): + args = args or kwargs + text = format_ansi(text) + + print(text % args) + + def initialize_argparse(self): + """Prepare to process arguments from sys.argv. + """ + kwargs = { + 'fromfile_prefix_chars': '@', + 'conflict_handler': 'resolve', + } + + self.acquire_lock() + self.subcommands = {} + self.subcommands_default = {} + self._subparsers = None + self._subparsers_default = None + self.argwarn = argcomplete.warn + self.args = None + self._arg_defaults = argparse.ArgumentParser(**kwargs) + self._arg_parser = argparse.ArgumentParser(**kwargs) + self.set_defaults = self._arg_parser.set_defaults + self.print_usage = self._arg_parser.print_usage + self.print_help = self._arg_parser.print_help + self.release_lock() + + def completer(self, completer): + """Add an arpcomplete completer to this subcommand. + """ + self._arg_parser.completer = completer + + def add_argument(self, *args, **kwargs): + """Wrapper to add arguments to both the main and the shadow argparser. + """ + if kwargs.get('add_dest', True) and args[0][0] == '-': + kwargs['dest'] = 'general_' + self.get_argument_name(*args, **kwargs) + if 'add_dest' in kwargs: + del kwargs['add_dest'] + + if 'action' in kwargs and kwargs['action'] == 'store_boolean': + return handle_store_boolean(self, *args, **kwargs) + + self.acquire_lock() + self._arg_parser.add_argument(*args, **kwargs) + + # Populate the shadow parser + if 'default' in kwargs: + del kwargs['default'] + if 'action' in kwargs and kwargs['action'] == 'store_false': + kwargs['action'] == 'store_true' + self._arg_defaults.add_argument(*args, **kwargs) + self.release_lock() + + def initialize_logging(self): + """Prepare the defaults for the logging infrastructure. + """ + self.acquire_lock() + self.log_file = None + self.log_file_mode = 'a' + self.log_file_handler = None + self.log_print = True + self.log_print_to = sys.stderr + self.log_print_level = logging.INFO + self.log_file_level = logging.DEBUG + self.log_level = logging.INFO + self.log = logging.getLogger(self.__class__.__name__) + self.log.setLevel(logging.DEBUG) + logging.root.setLevel(logging.DEBUG) + self.release_lock() + + self.add_argument('-V', '--version', version=self.version, action='version', help='Display the version and exit') + self.add_argument('-v', '--verbose', action='store_true', help='Make the logging more verbose') + self.add_argument('--datetime-fmt', default='%Y-%m-%d %H:%M:%S', help='Format string for datetimes') + self.add_argument('--log-fmt', default='%(levelname)s %(message)s', help='Format string for printed log output') + self.add_argument('--log-file-fmt', default='[%(levelname)s] [%(asctime)s] [file:%(pathname)s] [line:%(lineno)d] %(message)s', help='Format string for log file.') + self.add_argument('--log-file', help='File to write log messages to') + self.add_argument('--color', action='store_boolean', default=True, help='color in output') + self.add_argument('-c', '--config-file', help='The config file to read and/or write') + self.add_argument('--save-config', action='store_true', help='Save the running configuration to the config file') + + def add_subparsers(self, title='Sub-commands', **kwargs): + if self._inside_context_manager: + raise RuntimeError('You must run this before the with statement!') + + self.acquire_lock() + self._subparsers_default = self._arg_defaults.add_subparsers(title=title, dest='subparsers', **kwargs) + self._subparsers = self._arg_parser.add_subparsers(title=title, dest='subparsers', **kwargs) + self.release_lock() + + def acquire_lock(self): + """Acquire the MILC lock for exclusive access to properties. + """ + if self._lock: + self._lock.acquire() + + def release_lock(self): + """Release the MILC lock. + """ + if self._lock: + self._lock.release() + + def find_config_file(self): + """Locate the config file. + """ + if self.config_file: + return self.config_file + + if self.args and self.args.general_config_file: + return self.args.general_config_file + + return os.path.abspath(os.path.expanduser('~/.%s.ini' % self.prog_name)) + + def get_argument_name(self, *args, **kwargs): + """Takes argparse arguments and returns the dest name. + """ + try: + return self._arg_parser._get_optional_kwargs(*args, **kwargs)['dest'] + except ValueError: + return self._arg_parser._get_positional_kwargs(*args, **kwargs)['dest'] + + def argument(self, *args, **kwargs): + """Decorator to call self.add_argument or self..add_argument. + """ + if self._inside_context_manager: + raise RuntimeError('You must run this before the with statement!') + + def argument_function(handler): + if handler is self._entrypoint: + self.add_argument(*args, **kwargs) + + elif handler.__name__ in self.subcommands: + self.subcommands[handler.__name__].add_argument(*args, **kwargs) + + else: + raise RuntimeError('Decorated function is not entrypoint or subcommand!') + + return handler + + return argument_function + + def arg_passed(self, arg): + """Returns True if arg was passed on the command line. + """ + return self.args_passed[arg] in (None, False) + + def parse_args(self): + """Parse the CLI args. + """ + if self.args: + self.log.debug('Warning: Arguments have already been parsed, ignoring duplicate attempt!') + return + + argcomplete.autocomplete(self._arg_parser) + + self.acquire_lock() + self.args = self._arg_parser.parse_args() + self.args_passed = self._arg_defaults.parse_args() + + if 'entrypoint' in self.args: + self._entrypoint = self.args.entrypoint + + if self.args.general_config_file: + self.config_file = self.args.general_config_file + + self.release_lock() + + def read_config(self): + """Parse the configuration file and determine the runtime configuration. + """ + self.acquire_lock() + self.config_file = self.find_config_file() + + if self.config_file and os.path.exists(self.config_file): + config = RawConfigParser(self.config) + config.read(self.config_file) + + # Iterate over the config file options and write them into self.config + for section in config.sections(): + for option in config.options(section): + value = config.get(section, option) + + # Coerce values into useful datatypes + if value.lower() in ['1', 'yes', 'true', 'on']: + value = True + elif value.lower() in ['0', 'no', 'false', 'none', 'off']: + value = False + elif value.replace('.', '').isdigit(): + if '.' in value: + value = Decimal(value) + else: + value = int(value) + + self.config[section][option] = value + + # Fold the CLI args into self.config + for argument in vars(self.args): + if argument in ('subparsers', 'entrypoint'): + continue + + if '_' not in argument: + continue + + section, option = argument.split('_', 1) + if hasattr(self.args_passed, argument): + self.config[section][option] = getattr(self.args, argument) + else: + if option not in self.config[section]: + self.config[section][option] = getattr(self.args, argument) + + self.release_lock() + + def save_config(self): + """Save the current configuration to the config file. + """ + self.log.debug("Saving config file to '%s'", self.config_file) + + if not self.config_file: + self.log.warning('%s.config_file file not set, not saving config!', self.__class__.__name__) + return + + self.acquire_lock() + + config = RawConfigParser() + for section_name, section in self.config._config.items(): + config.add_section(section_name) + for option_name, value in section.items(): + if section_name == 'general': + if option_name in ['save_config']: + continue + config.set(section_name, option_name, str(value)) + + with NamedTemporaryFile(mode='w', dir=os.path.dirname(self.config_file), delete=False) as tmpfile: + config.write(tmpfile) + + # Move the new config file into place atomically + if os.path.getsize(tmpfile.name) > 0: + os.rename(tmpfile.name, self.config_file) + else: + self.log.warning('Config file saving failed, not replacing %s with %s.', self.config_file, tmpfile.name) + + self.release_lock() + + def __call__(self): + """Execute the entrypoint function. + """ + if not self._inside_context_manager: + # If they didn't use the context manager use it ourselves + with self: + self.__call__() + return + + if not self._entrypoint: + raise RuntimeError('No entrypoint provided!') + + return self._entrypoint(self) + + def entrypoint(self, description): + """Set the entrypoint for when no subcommand is provided. + """ + if self._inside_context_manager: + raise RuntimeError('You must run this before cli()!') + + self.acquire_lock() + self.description = description + self.release_lock() + + def entrypoint_func(handler): + self.acquire_lock() + self._entrypoint = handler + self.release_lock() + + return handler + + return entrypoint_func + + def add_subcommand(self, handler, description, name=None, **kwargs): + """Register a subcommand. + + If name is not provided we use `handler.__name__`. + """ + if self._inside_context_manager: + raise RuntimeError('You must run this before the with statement!') + + if self._subparsers is None: + self.add_subparsers() + + if not name: + name = handler.__name__ + + self.acquire_lock() + kwargs['help'] = description + self.subcommands_default[name] = self._subparsers_default.add_parser(name, **kwargs) + self.subcommands[name] = SubparserWrapper(self, name, self._subparsers.add_parser(name, **kwargs)) + self.subcommands[name].set_defaults(entrypoint=handler) + + if name not in self.__dict__: + self.__dict__[name] = self.subcommands[name] + else: + self.log.debug("Could not add subcommand '%s' to attributes, key already exists!", name) + + self.release_lock() + + return handler + + def subcommand(self, description, **kwargs): + """Decorator to register a subcommand. + """ + + def subcommand_function(handler): + return self.add_subcommand(handler, description, **kwargs) + + return subcommand_function + + def setup_logging(self): + """Called by __enter__() to setup the logging configuration. + """ + if len(logging.root.handlers) != 0: + # This is not a design decision. This is what I'm doing for now until I can examine and think about this situation in more detail. + raise RuntimeError('MILC should be the only system installing root log handlers!') + + self.acquire_lock() + + if self.config['general']['verbose']: + self.log_print_level = logging.DEBUG + + self.log_file = self.config['general']['log_file'] or self.log_file + self.log_file_format = self.config['general']['log_file_fmt'] + self.log_file_format = ANSIStrippingFormatter(self.config['general']['log_file_fmt'], self.config['general']['datetime_fmt']) + self.log_format = self.config['general']['log_fmt'] + + if self.config.general.color: + self.log_format = ANSIEmojiLoglevelFormatter(self.args.general_log_fmt, self.config.general.datetime_fmt) + else: + self.log_format = ANSIStrippingFormatter(self.args.general_log_fmt, self.config.general.datetime_fmt) + + if self.log_file: + self.log_file_handler = logging.FileHandler(self.log_file, self.log_file_mode) + self.log_file_handler.setLevel(self.log_file_level) + self.log_file_handler.setFormatter(self.log_file_format) + logging.root.addHandler(self.log_file_handler) + + if self.log_print: + self.log_print_handler = logging.StreamHandler(self.log_print_to) + self.log_print_handler.setLevel(self.log_print_level) + self.log_print_handler.setFormatter(self.log_format) + logging.root.addHandler(self.log_print_handler) + + self.release_lock() + + def __enter__(self): + if self._inside_context_manager: + self.log.debug('Warning: context manager was entered again. This usually means that self.__call__() was called before the with statement. You probably do not want to do that.') + return + + self.acquire_lock() + self._inside_context_manager = True + self.release_lock() + + colorama.init() + self.parse_args() + self.read_config() + self.setup_logging() + + if self.config.general.save_config: + self.save_config() + + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + self.acquire_lock() + self._inside_context_manager = False + self.release_lock() + + if exc_type is not None and not isinstance(SystemExit(), exc_type): + print(exc_type) + logging.exception(exc_val) + exit(255) + + +cli = MILC() + +if __name__ == '__main__': + + @cli.argument('-c', '--comma', help='comma in output', default=True, action='store_boolean') + @cli.entrypoint('My useful CLI tool with subcommands.') + def main(cli): + comma = ',' if cli.config.general.comma else '' + cli.log.info('{bg_green}{fg_red}Hello%s World!', comma) + + @cli.argument('-n', '--name', help='Name to greet', default='World') + @cli.subcommand('Description of hello subcommand here.') + def hello(cli): + comma = ',' if cli.config.general.comma else '' + cli.log.info('{fg_blue}Hello%s %s!', comma, cli.config.hello.name) + + def goodbye(cli): + comma = ',' if cli.config.general.comma else '' + cli.log.info('{bg_red}Goodbye%s %s!', comma, cli.config.goodbye.name) + + @cli.argument('-n', '--name', help='Name to greet', default='World') + @cli.subcommand('Think a bit before greeting the user.') + def thinking(cli): + comma = ',' if cli.config.general.comma else '' + spinner = cli.spinner(text='Just a moment...', spinner='earth') + spinner.start() + sleep(2) + spinner.stop() + + with cli.spinner(text='Almost there!', spinner='moon'): + sleep(2) + + cli.log.info('{fg_cyan}Hello%s %s!', comma, cli.config.thinking.name) + + @cli.subcommand('Show off our ANSI colors.') + def pride(cli): + cli.echo('{bg_red} ') + cli.echo('{bg_lightred_ex} ') + cli.echo('{bg_lightyellow_ex} ') + cli.echo('{bg_green} ') + cli.echo('{bg_blue} ') + cli.echo('{bg_magenta} ') + + # You can register subcommands using decorators as seen above, or using functions like like this: + cli.add_subcommand(goodbye, 'This will show up in --help output.') + cli.goodbye.add_argument('-n', '--name', help='Name to bid farewell to', default='World') + + cli() # Automatically picks between main(), hello() and goodbye() + print(sorted(ansi_colors.keys())) diff --git a/lib/python/qmk/__init__.py b/lib/python/qmk/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/lib/python/qmk/cli/compile/__init__.py b/lib/python/qmk/cli/compile/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/lib/python/qmk/cli/compile/json.py b/lib/python/qmk/cli/compile/json.py new file mode 100755 index 000000000..89c16b206 --- /dev/null +++ b/lib/python/qmk/cli/compile/json.py @@ -0,0 +1,44 @@ +"""Create a keymap directory from a configurator export. +""" +import json +import os +import sys +import subprocess + +from milc import cli + +import qmk.keymap +import qmk.path + + +@cli.argument('filename', help='Configurator JSON export') +@cli.entrypoint('Compile a QMK Configurator export.') +def main(cli): + """Compile a QMK Configurator export. + + This command creates a new keymap from a configurator export, overwriting an existing keymap if one exists. + + FIXME(skullydazed): add code to check and warn if the keymap already exists + """ + # Error checking + if cli.args.filename == ('-'): + cli.log.error('Reading from STDIN is not (yet) supported.') + exit(1) + if not os.path.exists(qmk.path.normpath(cli.args.filename)): + cli.log.error('JSON file does not exist!') + exit(1) + + # Parse the configurator json + with open(qmk.path.normpath(cli.args.filename), 'r') as fd: + user_keymap = json.load(fd) + + # Generate the keymap + keymap_path = qmk.path.keymap(user_keymap['keyboard']) + cli.log.info('Creating {fg_cyan}%s{style_reset_all} keymap in {fg_cyan}%s', user_keymap['keymap'], keymap_path) + qmk.keymap.write(user_keymap['keyboard'], user_keymap['keymap'], user_keymap['layout'], user_keymap['layers']) + cli.log.info('Wrote keymap to {fg_cyan}%s/%s/keymap.c', keymap_path, user_keymap['keymap']) + + # Compile the keymap + command = ['make', ':'.join((user_keymap['keyboard'], user_keymap['keymap']))] + cli.log.info('Compiling keymap with {fg_cyan}%s\n\n', ' '.join(command)) + subprocess.run(command) diff --git a/lib/python/qmk/cli/doctor.py b/lib/python/qmk/cli/doctor.py new file mode 100755 index 000000000..9ce765a4b --- /dev/null +++ b/lib/python/qmk/cli/doctor.py @@ -0,0 +1,47 @@ +"""QMK Python Doctor + +Check up for QMK environment. +""" +import shutil +import platform +import os + +from milc import cli + + +@cli.entrypoint('Basic QMK environment checks') +def main(cli): + """Basic QMK environment checks. + + This is currently very simple, it just checks that all the expected binaries are on your system. + + TODO(unclaimed): + * [ ] Run the binaries to make sure they work + * [ ] Compile a trivial program with each compiler + * [ ] Check for udev entries on linux + """ + + binaries = ['dfu-programmer', 'avrdude', 'dfu-util', 'avr-gcc', 'arm-none-eabi-gcc'] + + cli.log.info('QMK Doctor is Checking your environment') + + ok = True + for binary in binaries: + res = shutil.which(binary) + if res is None: + cli.log.error('{fg_red}QMK can\'t find ' + binary + ' in your path') + ok = False + + OS = platform.system() + if OS == "Darwin": + cli.log.info("Detected {fg_cyan}macOS") + elif OS == "Linux": + cli.log.info("Detected {fg_cyan}linux") + test = 'systemctl list-unit-files | grep enabled | grep -i ModemManager' + if os.system(test) == 0: + cli.log.warn("{bg_yellow}Detected modem manager. Please disable it if you are using Pro Micros") + else: + cli.log.info("Assuming {fg_cyan}Windows") + + if ok: + cli.log.info('{fg_green}QMK is ready to go') diff --git a/lib/python/qmk/cli/hello.py b/lib/python/qmk/cli/hello.py new file mode 100755 index 000000000..bc0cb6de1 --- /dev/null +++ b/lib/python/qmk/cli/hello.py @@ -0,0 +1,13 @@ +"""QMK Python Hello World + +This is an example QMK CLI script. +""" +from milc import cli + + +@cli.argument('-n', '--name', default='World', help='Name to greet.') +@cli.entrypoint('QMK Hello World.') +def main(cli): + """Log a friendly greeting. + """ + cli.log.info('Hello, %s!', cli.config.general.name) diff --git a/lib/python/qmk/cli/json/__init__.py b/lib/python/qmk/cli/json/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/lib/python/qmk/cli/json/keymap.py b/lib/python/qmk/cli/json/keymap.py new file mode 100755 index 000000000..35fc8f9c0 --- /dev/null +++ b/lib/python/qmk/cli/json/keymap.py @@ -0,0 +1,54 @@ +"""Generate a keymap.c from a configurator export. +""" +import json +import os +import sys + +from milc import cli + +import qmk.keymap + + +@cli.argument('-o', '--output', help='File to write to') +@cli.argument('filename', help='Configurator JSON file') +@cli.entrypoint('Create a keymap.c from a QMK Configurator export.') +def main(cli): + """Generate a keymap.c from a configurator export. + + This command uses the `qmk.keymap` module to generate a keymap.c from a configurator export. The generated keymap is written to stdout, or to a file if -o is provided. + """ + # Error checking + if cli.args.filename == ('-'): + cli.log.error('Reading from STDIN is not (yet) supported.') + cli.print_usage() + exit(1) + if not os.path.exists(qmk.path.normpath(cli.args.filename)): + cli.log.error('JSON file does not exist!') + cli.print_usage() + exit(1) + + # Environment processing + if cli.args.output == ('-'): + cli.args.output = None + + # Parse the configurator json + with open(qmk.path.normpath(cli.args.filename), 'r') as fd: + user_keymap = json.load(fd) + + # Generate the keymap + keymap_c = qmk.keymap.generate(user_keymap['keyboard'], user_keymap['layout'], user_keymap['layers']) + + if cli.args.output: + output_dir = os.path.dirname(cli.args.output) + + if not os.path.exists(output_dir): + os.makedirs(output_dir) + + output_file = qmk.path.normpath(cli.args.output) + with open(output_file, 'w') as keymap_fd: + keymap_fd.write(keymap_c) + + cli.log.info('Wrote keymap to %s.', cli.args.output) + + else: + print(keymap_c) diff --git a/lib/python/qmk/errors.py b/lib/python/qmk/errors.py new file mode 100644 index 000000000..f9bf5b9af --- /dev/null +++ b/lib/python/qmk/errors.py @@ -0,0 +1,6 @@ +class NoSuchKeyboardError(Exception): + """Raised when we can't find a keyboard/keymap directory. + """ + + def __init__(self, message): + self.message = message diff --git a/lib/python/qmk/keymap.py b/lib/python/qmk/keymap.py new file mode 100644 index 000000000..6eccab788 --- /dev/null +++ b/lib/python/qmk/keymap.py @@ -0,0 +1,100 @@ +"""Functions that help you work with QMK keymaps. +""" +import json +import logging +import os +from traceback import format_exc + +import qmk.path +from qmk.errors import NoSuchKeyboardError + +# The `keymap.c` template to use when a keyboard doesn't have its own +DEFAULT_KEYMAP_C = """#include QMK_KEYBOARD_H + +/* THIS FILE WAS GENERATED! + * + * This file was generated by qmk-compile-json. You may or may not want to + * edit it directly. + */ + +const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = { +__KEYMAP_GOES_HERE__ +}; +""" + + +def template(keyboard): + """Returns the `keymap.c` template for a keyboard. + + If a template exists in `keyboards//templates/keymap.c` that + text will be used instead of `DEFAULT_KEYMAP_C`. + + Args: + keyboard + The keyboard to return a template for. + """ + template_name = 'keyboards/%s/templates/keymap.c' % keyboard + + if os.path.exists(template_name): + with open(template_name, 'r') as fd: + return fd.read() + + return DEFAULT_KEYMAP_C + + +def generate(keyboard, layout, layers): + """Returns a keymap.c for the specified keyboard, layout, and layers. + + Args: + keyboard + The name of the keyboard + + layout + The LAYOUT macro this keymap uses. + + layers + An array of arrays describing the keymap. Each item in the inner array should be a string that is a valid QMK keycode. + """ + layer_txt = [] + for layer_num, layer in enumerate(layers): + if layer_num != 0: + layer_txt[-1] = layer_txt[-1] + ',' + layer_keys = ', '.join(layer) + layer_txt.append('\t[%s] = %s(%s)' % (layer_num, layout, layer_keys)) + + keymap = '\n'.join(layer_txt) + keymap_c = template(keyboard, keymap) + + return keymap_c.replace('__KEYMAP_GOES_HERE__', keymap) + + +def write(keyboard, keymap, layout, layers): + """Generate the `keymap.c` and write it to disk. + + Returns the filename written to. + + Args: + keyboard + The name of the keyboard + + keymap + The name of the keymap + + layout + The LAYOUT macro this keymap uses. + + layers + An array of arrays describing the keymap. Each item in the inner array should be a string that is a valid QMK keycode. + """ + keymap_c = generate(keyboard, layout, layers) + keymap_path = qmk.path.keymap(keyboard) + keymap_dir = os.path.join(keymap_path, keymap) + keymap_file = os.path.join(keymap_dir, 'keymap.c') + + if not os.path.exists(keymap_dir): + os.makedirs(keymap_dir) + + with open(keymap_file, 'w') as keymap_fd: + keymap_fd.write(keymap_c) + + return keymap_file diff --git a/lib/python/qmk/path.py b/lib/python/qmk/path.py new file mode 100644 index 000000000..f2a8346a5 --- /dev/null +++ b/lib/python/qmk/path.py @@ -0,0 +1,32 @@ +"""Functions that help us work with files and folders. +""" +import os + + +def keymap(keyboard): + """Locate the correct directory for storing a keymap. + + Args: + keyboard + The name of the keyboard. Example: clueboard/66/rev3 + """ + for directory in ['.', '..', '../..', '../../..', '../../../..', '../../../../..']: + basepath = os.path.normpath(os.path.join('keyboards', keyboard, directory, 'keymaps')) + + if os.path.exists(basepath): + return basepath + + logging.error('Could not find keymaps directory!') + raise NoSuchKeyboardError('Could not find keymaps directory for: %s' % keyboard) + + +def normpath(path): + """Returns the fully resolved absolute path to a file. + + This function will return the absolute path to a file as seen from the + directory the script was called from. + """ + if path and path[0] == '/': + return os.path.normpath(path) + + return os.path.normpath(os.path.join(os.environ['ORIG_CWD'], path)) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 000000000..351dc2524 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +# Python requirements +# milc FIXME(skullydazed): Included in the repo for now. +argcomplete +colorama +#halo diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 000000000..528512ac6 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,330 @@ +# Python settings for QMK + +[yapf] +# Align closing bracket with visual indentation. +align_closing_bracket_with_visual_indent=True + +# Allow dictionary keys to exist on multiple lines. For example: +# +# x = { +# ('this is the first element of a tuple', +# 'this is the second element of a tuple'): +# value, +# } +allow_multiline_dictionary_keys=False + +# Allow lambdas to be formatted on more than one line. +allow_multiline_lambdas=False + +# Allow splitting before a default / named assignment in an argument list. +allow_split_before_default_or_named_assigns=True + +# Allow splits before the dictionary value. +allow_split_before_dict_value=True + +# Let spacing indicate operator precedence. For example: +# +# a = 1 * 2 + 3 / 4 +# b = 1 / 2 - 3 * 4 +# c = (1 + 2) * (3 - 4) +# d = (1 - 2) / (3 + 4) +# e = 1 * 2 - 3 +# f = 1 + 2 + 3 + 4 +# +# will be formatted as follows to indicate precedence: +# +# a = 1*2 + 3/4 +# b = 1/2 - 3*4 +# c = (1+2) * (3-4) +# d = (1-2) / (3+4) +# e = 1*2 - 3 +# f = 1 + 2 + 3 + 4 +# +arithmetic_precedence_indication=True + +# Number of blank lines surrounding top-level function and class +# definitions. +blank_lines_around_top_level_definition=2 + +# Insert a blank line before a class-level docstring. +blank_line_before_class_docstring=False + +# Insert a blank line before a module docstring. +blank_line_before_module_docstring=False + +# Insert a blank line before a 'def' or 'class' immediately nested +# within another 'def' or 'class'. For example: +# +# class Foo: +# # <------ this blank line +# def method(): +# ... +blank_line_before_nested_class_or_def=False + +# Do not split consecutive brackets. Only relevant when +# dedent_closing_brackets is set. For example: +# +# call_func_that_takes_a_dict( +# { +# 'key1': 'value1', +# 'key2': 'value2', +# } +# ) +# +# would reformat to: +# +# call_func_that_takes_a_dict({ +# 'key1': 'value1', +# 'key2': 'value2', +# }) +coalesce_brackets=True + +# The column limit. +column_limit=256 + +# The style for continuation alignment. Possible values are: +# +# - SPACE: Use spaces for continuation alignment. This is default behavior. +# - FIXED: Use fixed number (CONTINUATION_INDENT_WIDTH) of columns +# (ie: CONTINUATION_INDENT_WIDTH/INDENT_WIDTH tabs) for continuation +# alignment. +# - VALIGN-RIGHT: Vertically align continuation lines with indent +# characters. Slightly right (one more indent character) if cannot +# vertically align continuation lines with indent characters. +# +# For options FIXED, and VALIGN-RIGHT are only available when USE_TABS is +# enabled. +continuation_align_style=SPACE + +# Indent width used for line continuations. +continuation_indent_width=4 + +# Put closing brackets on a separate line, dedented, if the bracketed +# expression can't fit in a single line. Applies to all kinds of brackets, +# including function definitions and calls. For example: +# +# config = { +# 'key1': 'value1', +# 'key2': 'value2', +# } # <--- this bracket is dedented and on a separate line +# +# time_series = self.remote_client.query_entity_counters( +# entity='dev3246.region1', +# key='dns.query_latency_tcp', +# transform=Transformation.AVERAGE(window=timedelta(seconds=60)), +# start_ts=now()-timedelta(days=3), +# end_ts=now(), +# ) # <--- this bracket is dedented and on a separate line +dedent_closing_brackets=True + +# Disable the heuristic which places each list element on a separate line +# if the list is comma-terminated. +disable_ending_comma_heuristic=False + +# Place each dictionary entry onto its own line. +each_dict_entry_on_separate_line=True + +# The regex for an i18n comment. The presence of this comment stops +# reformatting of that line, because the comments are required to be +# next to the string they translate. +i18n_comment= + +# The i18n function call names. The presence of this function stops +# reformattting on that line, because the string it has cannot be moved +# away from the i18n comment. +i18n_function_call= + +# Indent blank lines. +indent_blank_lines=False + +# Indent the dictionary value if it cannot fit on the same line as the +# dictionary key. For example: +# +# config = { +# 'key1': +# 'value1', +# 'key2': value1 + +# value2, +# } +indent_dictionary_value=True + +# The number of columns to use for indentation. +indent_width=4 + +# Join short lines into one line. E.g., single line 'if' statements. +join_multiple_lines=False + +# Do not include spaces around selected binary operators. For example: +# +# 1 + 2 * 3 - 4 / 5 +# +# will be formatted as follows when configured with "*,/": +# +# 1 + 2*3 - 4/5 +no_spaces_around_selected_binary_operators= + +# Use spaces around default or named assigns. +spaces_around_default_or_named_assign=False + +# Use spaces around the power operator. +spaces_around_power_operator=False + +# The number of spaces required before a trailing comment. +# This can be a single value (representing the number of spaces +# before each trailing comment) or list of values (representing +# alignment column values; trailing comments within a block will +# be aligned to the first column value that is greater than the maximum +# line length within the block). For example: +# +# With spaces_before_comment=5: +# +# 1 + 1 # Adding values +# +# will be formatted as: +# +# 1 + 1 # Adding values <-- 5 spaces between the end of the statement and comment +# +# With spaces_before_comment=15, 20: +# +# 1 + 1 # Adding values +# two + two # More adding +# +# longer_statement # This is a longer statement +# short # This is a shorter statement +# +# a_very_long_statement_that_extends_beyond_the_final_column # Comment +# short # This is a shorter statement +# +# will be formatted as: +# +# 1 + 1 # Adding values <-- end of line comments in block aligned to col 15 +# two + two # More adding +# +# longer_statement # This is a longer statement <-- end of line comments in block aligned to col 20 +# short # This is a shorter statement +# +# a_very_long_statement_that_extends_beyond_the_final_column # Comment <-- the end of line comments are aligned based on the line length +# short # This is a shorter statement +# +spaces_before_comment=2 + +# Insert a space between the ending comma and closing bracket of a list, +# etc. +space_between_ending_comma_and_closing_bracket=False + +# Split before arguments +split_all_comma_separated_values=False + +# Split before arguments if the argument list is terminated by a +# comma. +split_arguments_when_comma_terminated=True + +# Set to True to prefer splitting before '+', '-', '*', '/', '//', or '@' +# rather than after. +split_before_arithmetic_operator=False + +# Set to True to prefer splitting before '&', '|' or '^' rather than +# after. +split_before_bitwise_operator=True + +# Split before the closing bracket if a list or dict literal doesn't fit on +# a single line. +split_before_closing_bracket=True + +# Split before a dictionary or set generator (comp_for). For example, note +# the split before the 'for': +# +# foo = { +# variable: 'Hello world, have a nice day!' +# for variable in bar if variable != 42 +# } +split_before_dict_set_generator=True + +# Split before the '.' if we need to split a longer expression: +# +# foo = ('This is a really long string: {}, {}, {}, {}'.format(a, b, c, d)) +# +# would reformat to something like: +# +# foo = ('This is a really long string: {}, {}, {}, {}' +# .format(a, b, c, d)) +split_before_dot=False + +# Split after the opening paren which surrounds an expression if it doesn't +# fit on a single line. +split_before_expression_after_opening_paren=False + +# If an argument / parameter list is going to be split, then split before +# the first argument. +split_before_first_argument=False + +# Set to True to prefer splitting before 'and' or 'or' rather than +# after. +split_before_logical_operator=False + +# Split named assignments onto individual lines. +split_before_named_assigns=True + +# Set to True to split list comprehensions and generators that have +# non-trivial expressions and multiple clauses before each of these +# clauses. For example: +# +# result = [ +# a_long_var + 100 for a_long_var in xrange(1000) +# if a_long_var % 10] +# +# would reformat to something like: +# +# result = [ +# a_long_var + 100 +# for a_long_var in xrange(1000) +# if a_long_var % 10] +split_complex_comprehension=True + +# The penalty for splitting right after the opening bracket. +split_penalty_after_opening_bracket=300 + +# The penalty for splitting the line after a unary operator. +split_penalty_after_unary_operator=10000 + +# The penalty of splitting the line around the '+', '-', '*', '/', '//', +# ``%``, and '@' operators. +split_penalty_arithmetic_operator=300 + +# The penalty for splitting right before an if expression. +split_penalty_before_if_expr=0 + +# The penalty of splitting the line around the '&', '|', and '^' +# operators. +split_penalty_bitwise_operator=300 + +# The penalty for splitting a list comprehension or generator +# expression. +split_penalty_comprehension=80 + +# The penalty for characters over the column limit. +split_penalty_excess_character=7000 + +# The penalty incurred by adding a line split to the unwrapped line. The +# more line splits added the higher the penalty. +split_penalty_for_added_line_split=30 + +# The penalty of splitting a list of "import as" names. For example: +# +# from a_very_long_or_indented_module_name_yada_yad import (long_argument_1, +# long_argument_2, +# long_argument_3) +# +# would reformat to something like: +# +# from a_very_long_or_indented_module_name_yada_yad import ( +# long_argument_1, long_argument_2, long_argument_3) +split_penalty_import_names=0 + +# The penalty of splitting the line around the 'and' and 'or' +# operators. +split_penalty_logical_operator=300 + +# Use the Tab character for indentation. +use_tabs=False + diff --git a/util/freebsd_install.sh b/util/freebsd_install.sh index c8696e8cc..815759203 100755 --- a/util/freebsd_install.sh +++ b/util/freebsd_install.sh @@ -1,4 +1,5 @@ #!/bin/sh +util_dir=$(dirname "$0") pkg update pkg install -y \ git \ @@ -17,3 +18,4 @@ pkg install -y \ arm-none-eabi-newlib \ diffutils \ python3 +pip3 install -r ${util_dir}/../requirements.txt diff --git a/util/linux_install.sh b/util/linux_install.sh index 4731ec015..d21cd3c1c 100755 --- a/util/linux_install.sh +++ b/util/linux_install.sh @@ -8,6 +8,8 @@ SLACKWARE_WARNING="You will need the following packages from slackbuilds.org:\n\ SOLUS_INFO="Your tools are now installed. To start using them, open new terminal or source these scripts:\n\t/usr/share/defaults/etc/profile.d/50-arm-toolchain-path.sh\n\t/usr/share/defaults/etc/profile.d/50-avr-toolchain-path.sh" +util_dir=$(dirname "$0") + if grep ID /etc/os-release | grep -qE "fedora"; then sudo dnf install \ arm-none-eabi-binutils-cs \ @@ -183,3 +185,6 @@ else echo echo "https://docs.qmk.fm/#/contributing" fi + +# Global install tasks +pip3 install -r ${util_dir}/../requirements.txt diff --git a/util/macos_install.sh b/util/macos_install.sh index 915ff3143..f7e304424 100755 --- a/util/macos_install.sh +++ b/util/macos_install.sh @@ -1,5 +1,7 @@ #!/bin/bash +util_dir=$(dirname "$0") + if ! brew --version 2>&1 > /dev/null; then echo "Error! Homebrew not installed or broken!" echo -n "Would you like to install homebrew now? [y/n] " @@ -24,3 +26,4 @@ brew tap PX4/homebrew-px4 brew update brew install avr-gcc@8 gcc-arm-none-eabi dfu-programmer avrdude dfu-util python3 brew link --force avr-gcc@8 +pip3 install -r ${util_dir}/../requirements.txt diff --git a/util/msys2_install.sh b/util/msys2_install.sh index bcb628ab2..bed176da6 100755 --- a/util/msys2_install.sh +++ b/util/msys2_install.sh @@ -5,6 +5,7 @@ download_dir=~/qmk_utils avrtools=avr8-gnu-toolchain armtools=gcc-arm-none-eabi installflip=false +util_dir=$(dirname "$0") echo "Installing dependencies needed for the installation (quazip)" pacman --needed -S base-devel mingw-w64-x86_64-toolchain msys/git msys/p7zip msys/python3 msys/unzip @@ -92,6 +93,8 @@ else fi popd +pip3 install -r ${util_dir}/../requirements.txt + cp -f "$dir/activate_msys2.sh" "$download_dir/" if grep "^source ~/qmk_utils/activate_msys2.sh$" ~/.bashrc diff --git a/util/wsl_install.sh b/util/wsl_install.sh index c2c206d2b..197d9f089 100755 --- a/util/wsl_install.sh +++ b/util/wsl_install.sh @@ -1,6 +1,7 @@ #!/bin/bash -dir=$(cd -P -- "$(dirname -- "$0")" && pwd -P) +util_dir=$(dirname "$0") +dir=$(cd -P -- "$util_dir" && pwd -P) pushd "$dir"; if [[ $dir != /mnt/* ]]; @@ -28,6 +29,8 @@ download_dir=wsl_downloaded source "$dir/win_shared_install.sh" +pip3 install -r ${util_dir}/../requirements.txt + pushd "$download_dir" while true; do echo -- cgit v1.2.3-70-g09d2 From 29e9caa82bdfe898dd7fca82fcecf0cae2374859 Mon Sep 17 00:00:00 2001 From: Joel Challis Date: Tue, 16 Jul 2019 09:20:34 +0100 Subject: Configure Vagrant to use qmk_base_container (#6194) * Initial conversion of vagrant to use qmkfm/base_container * Fix vagrant when using docker provider * Workaround for VirtualBox VM restarts * Generalise Vagrant docs slightly and add FAQ --- Vagrantfile | 31 ++++++++++++++++++--------- docs/getting_started_vagrant.md | 46 ++++++++++++++++++++++++++++++++++++----- util/vagrant/Dockerfile | 33 +++++++++++++++++++++++++++++ util/vagrant/readme.md | 12 +++++++++++ 4 files changed, 107 insertions(+), 15 deletions(-) create mode 100644 util/vagrant/Dockerfile create mode 100644 util/vagrant/readme.md (limited to 'util') diff --git a/Vagrantfile b/Vagrantfile index 552711d63..dae4e0d53 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -52,26 +52,37 @@ Vagrant.configure(2) do |config| end # Docker provider pulls from hub.docker.com respecting docker.image if - # config.vm.box is nil. Note that this bind-mounts from the current dir to + # config.vm.box is nil. In this case, we adhoc build util/vagrant/Dockerfile. + # Note that this bind-mounts from the current dir to # /vagrant in the guest, so unless your UID is 1000 to match vagrant in the # image, you'll need to: chmod -R a+rw . config.vm.provider "docker" do |docker, override| override.vm.box = nil - docker.image = "jesselang/debian-vagrant:stretch" + docker.build_dir = "util/vagrant" docker.has_ssh = true end - # This script ensures the required packages for AVR programming are installed - # It also ensures the system always gets the latest updates when powered on - # If this causes issues you can run a 'vagrant destroy' and then - # add a # before ,run: (or change "always" to "once") and run 'vagrant up' to get a working - # non-updated box and then attempt to troubleshoot or open a Github issue - config.vm.provision "shell", inline: "/vagrant/util/qmk_install.sh", run: "always" + # Unless we are running the docker container directly + # 1. run container detached on vm + # 2. attach on 'vagrant ssh' + ["virtualbox", "vmware_workstation", "vmware_fusion"].each do |type| + config.vm.provider type do |virt, override| + override.vm.provision "docker" do |d| + d.run "qmkfm/base_container", + cmd: "tail -f /dev/null", + args: "--privileged -v /dev:/dev -v '/vagrant:/vagrant'" + end + + override.vm.provision "shell", inline: <<-SHELL + echo 'docker restart qmkfm-base_container && exec docker exec -it qmkfm-base_container /bin/bash -l' >> ~vagrant/.bashrc + SHELL + end + end config.vm.post_up_message = <<-EOT - Log into the VM using 'vagrant ssh'. QMK directory synchronized with host is - located at /vagrant + Log into the environment using 'vagrant ssh'. QMK directory synchronized with + host is located at /vagrant To compile the .hex files use make command inside this directory, e.g. cd /vagrant make :default diff --git a/docs/getting_started_vagrant.md b/docs/getting_started_vagrant.md index 848a43a1f..b62524271 100644 --- a/docs/getting_started_vagrant.md +++ b/docs/getting_started_vagrant.md @@ -1,16 +1,20 @@ # Vagrant Quick Start -This project includes a Vagrantfile that will allow you to build a new firmware for your keyboard very easily without major changes to your primary operating system. This also ensures that when you clone the project and perform a build, you have the exact same environment as anyone else using the Vagrantfile to build. This makes it much easier for people to help you troubleshoot any issues you encounter. +This project includes a `Vagrantfile` that will allow you to build a new firmware for your keyboard very easily without major changes to your primary operating system. This also ensures that when you clone the project and perform a build, you have the exact same environment as anyone else using the Vagrantfile to build. This makes it much easier for people to help you troubleshoot any issues you encounter. ## Requirements -Using the `/Vagrantfile` in this repository requires you have [Vagrant](http://www.vagrantup.com/) as well as [VirtualBox](https://www.virtualbox.org/) (or [VMware Workstation](https://www.vmware.com/products/workstation) and [Vagrant VMware plugin](http://www.vagrantup.com/vmware) but the (paid) VMware plugin requires a licensed copy of VMware Workstation/Fusion). +Using the `Vagrantfile` in this repository requires you have [Vagrant](http://www.vagrantup.com/) as well as a supported provider installed: -*COMPATIBILITY NOTICE* Certain versions of Virtualbox 5 appear to have an incompatibility with the Virtualbox extensions installed in the boxes in this Vagrantfile. If you encounter any issues with the /vagrant mount not succeeding, please upgrade your version of Virtualbox to at least 5.0.12. **Alternately, you can try running the following command:** `vagrant plugin install vagrant-vbguest` +* [VirtualBox](https://www.virtualbox.org/) (Version at least 5.0.12) + * Sold as 'the most accessible platform to use Vagrant' +* [VMware Workstation](https://www.vmware.com/products/workstation) and [Vagrant VMware plugin](http://www.vagrantup.com/vmware) + * The (paid) VMware plugin requires a licensed copy of VMware Workstation/Fusion +* [Docker](https://www.docker.com/) -Other than having Vagrant and Virtualbox installed and possibly a restart of your computer afterwards, you can simple run a 'vagrant up' anywhere inside the folder where you checked out this project and it will start a Linux virtual machine that contains all the tools required to build this project. There is a post Vagrant startup hint that will get you off on the right foot, otherwise you can also reference the build documentation below. +Other than having Vagrant, a suitable provider installed and possibly a restart of your computer afterwards, you can simple run a 'vagrant up' anywhere inside the folder where you checked out this project and it will start an environment (either a virtual machine or container) that contains all the tools required to build this project. There is a post Vagrant startup hint that will get you off on the right foot, otherwise you can also reference the build documentation below. -# Flashing the Firmware +## Flashing the Firmware The "easy" way to flash the firmware is using a tool from your host OS: @@ -19,3 +23,35 @@ The "easy" way to flash the firmware is using a tool from your host OS: * [Atmel FLIP](http://www.atmel.com/tools/flip.aspx) If you want to program via the command line you can uncomment the ['modifyvm'] lines in the Vagrantfile to enable the USB passthrough into Linux and then program using the command line tools like dfu-util/dfu-programmer or you can install the Teensy CLI version. + +## Vagrantfile Overview +The development environment is configured to run the QMK Docker image, `qmkfm/base_container`. This not only ensures predictability between systems, it also mirrors the CI environment. + +## FAQ + +### Why am I seeing issues under Virtualbox? +Certain versions of Virtualbox 5 appear to have an incompatibility with the Virtualbox extensions installed in the boxes in this Vagrantfile. If you encounter any issues with the /vagrant mount not succeeding, please upgrade your version of Virtualbox to at least 5.0.12. **Alternately, you can try running the following command:** + +```console +vagrant plugin install vagrant-vbguest +``` + +### How do I remove an existing environment? +Finished with your environment? From anywhere inside the folder where you checked out this project, Execute: + +```console +vagrant destory +``` + +### What if I want to use Docker directly? +Want to benefit from the Vagrant workflow without a virtual machine? The Vagrantfile is configured to bypass running a virtual machine, and run the container directly. Execute the following when bringing up the environment to force the use of Docker: +```console +vagrant up --provider=docker +``` + +### How do I access the virtual machine instead of the Docker container? +Execute the following to bypass the `vagrant` user booting directly to the official qmk builder image: + +```console +vagrant ssh -c 'sudo -i' +``` \ No newline at end of file diff --git a/util/vagrant/Dockerfile b/util/vagrant/Dockerfile new file mode 100644 index 000000000..1936ee023 --- /dev/null +++ b/util/vagrant/Dockerfile @@ -0,0 +1,33 @@ +FROM qmkfm/base_container + +# Basic upgrades; install sudo and SSH. +RUN apt-get update && apt-get install --no-install-recommends -y \ + sudo \ + openssh-server \ + && rm -rf /var/lib/apt/lists/* +RUN mkdir /var/run/sshd +RUN sed -i 's/PermitRootLogin yes/PermitRootLogin no/' /etc/ssh/sshd_config +RUN echo 'UseDNS no' >> /etc/ssh/sshd_config + +# Remove the policy file once we're finished installing software. +# This allows invoke-rc.d and friends to work as expected. +RUN rm /usr/sbin/policy-rc.d + +# Add the Vagrant user and necessary passwords. +RUN groupadd vagrant +RUN useradd -c "Vagrant" -g vagrant -d /home/vagrant -m -s /bin/bash vagrant +RUN echo 'root:vagrant' | chpasswd +RUN echo 'vagrant:vagrant' | chpasswd + +# Allow the vagrant user to use sudo without a password. +RUN echo 'vagrant ALL=(ALL) NOPASSWD: ALL' > /etc/sudoers.d/vagrant + +# Install Vagrant's insecure public key so provisioning and 'vagrant ssh' work. +RUN mkdir /home/vagrant/.ssh +ADD https://raw.githubusercontent.com/mitchellh/vagrant/master/keys/vagrant.pub /home/vagrant/.ssh/authorized_keys +RUN chmod 0600 /home/vagrant/.ssh/authorized_keys +RUN chown -R vagrant:vagrant /home/vagrant/.ssh +RUN chmod 0700 /home/vagrant/.ssh + +EXPOSE 22 +CMD ["/usr/sbin/sshd", "-D"] diff --git a/util/vagrant/readme.md b/util/vagrant/readme.md new file mode 100644 index 000000000..e4b870a64 --- /dev/null +++ b/util/vagrant/readme.md @@ -0,0 +1,12 @@ +# QMK Vagrant Utilities + +## Dockerfile +Vagrant-friendly `qmkfm/base_container`. + +In order for the Docker provider and `vagrant ssh` to function the container has a few extra requirements. + +* vagrant user +* ssh server + * configured with expected public key +* sudo + * passwordless for vagrant user -- cgit v1.2.3-70-g09d2 From 59d3b37130fc08caf0ed2926332e8741d5fd8517 Mon Sep 17 00:00:00 2001 From: fauxpark Date: Wed, 7 Aug 2019 04:20:53 +1000 Subject: Fix qmk_install.sh on Windows dropping to command prompt (#6488) --- util/win_shared_install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'util') diff --git a/util/win_shared_install.sh b/util/win_shared_install.sh index c7517881e..f8fc9308f 100755 --- a/util/win_shared_install.sh +++ b/util/win_shared_install.sh @@ -34,7 +34,7 @@ function install_drivers { pushd "$download_dir" cp -f "$dir/drivers.txt" . echo - cmd.exe /c "qmk_driver_installer.exe $1 $2 drivers.txt" + cmd.exe //c "qmk_driver_installer.exe $1 $2 drivers.txt" popd > /dev/null } -- cgit v1.2.3-70-g09d2 From 9813a6f950b46277eb29c54105809767cc0daa65 Mon Sep 17 00:00:00 2001 From: Joel Challis Date: Thu, 15 Aug 2019 22:03:26 +0100 Subject: Add an alternative method for keyboard discovery to speed up build (#6073) * Add an alternative method for keyboard discovery to speed up build * Chain MAKEFLAGS for docker_build.sh * Slight improvement to number of items sent to sort * Remove debug line * Fix line escape --- Makefile | 9 ++++++++- util/docker_build.sh | 8 +++++++- 2 files changed, 15 insertions(+), 2 deletions(-) (limited to 'util') diff --git a/Makefile b/Makefile index a6c3ee35b..6512f7217 100644 --- a/Makefile +++ b/Makefile @@ -20,7 +20,10 @@ endif override SILENT := false ifndef SUB_IS_SILENT -QMK_VERSION := $(shell git describe --abbrev=0 --tags 2>/dev/null) +ifndef SKIP_GIT + QMK_VERSION := $(shell git describe --abbrev=0 --tags 2>/dev/null) +endif + ifneq ($(QMK_VERSION),) $(info QMK Firmware $(QMK_VERSION)) endif @@ -94,6 +97,7 @@ $(eval $(call NEXT_PATH_ELEMENT)) # endif define GET_KEYBOARDS +ifndef ALT_GET_KEYBOARDS All_RULES_MK := $$(patsubst $(ROOT_DIR)/keyboards/%/rules.mk,%,$$(wildcard $(ROOT_DIR)/keyboards/*/rules.mk)) All_RULES_MK += $$(patsubst $(ROOT_DIR)/keyboards/%/rules.mk,%,$$(wildcard $(ROOT_DIR)/keyboards/*/*/rules.mk)) All_RULES_MK += $$(patsubst $(ROOT_DIR)/keyboards/%/rules.mk,%,$$(wildcard $(ROOT_DIR)/keyboards/*/*/*/rules.mk)) @@ -105,6 +109,9 @@ define GET_KEYBOARDS KEYMAPS_MK += $$(patsubst $(ROOT_DIR)/keyboards/%/rules.mk,%,$$(wildcard $(ROOT_DIR)/keyboards/*/*/*/*/keymaps/*/rules.mk)) KEYBOARDS := $$(sort $$(filter-out $$(KEYMAPS_MK), $$(All_RULES_MK))) +else + KEYBOARDS := $(shell find keyboards/ -type f -iname "rules.mk" | grep -v keymaps | sed 's!keyboards/\(.*\)/rules.mk!\1!' | sort | uniq) +endif endef $(eval $(call GET_KEYBOARDS)) diff --git a/util/docker_build.sh b/util/docker_build.sh index 6feeb1f5d..f36d5bcde 100755 --- a/util/docker_build.sh +++ b/util/docker_build.sh @@ -46,5 +46,11 @@ fi dir=$(pwd -W 2>/dev/null) || dir=$PWD # Use Windows path if on Windows # Run container and build firmware -docker run --rm -it $usb_args -w /qmk_firmware/ -v "$dir":/qmk_firmware qmkfm/base_container \ +docker run --rm -it $usb_args \ + -w /qmk_firmware/ \ + -v "$dir":/qmk_firmware \ + -e ALT_GET_KEYBOARDS=true \ + -e SKIP_GIT="$SKIP_GIT" \ + -e MAKEFLAGS="$MAKEFLAGS" \ + qmkfm/base_container \ make "$keyboard${keymap:+:$keymap}${target:+:$target}" -- cgit v1.2.3-70-g09d2 From 9e20478e6bdebc9cea49204482992471bccaf6a1 Mon Sep 17 00:00:00 2001 From: skullydazed Date: Tue, 9 Jul 2019 15:47:18 -0700 Subject: Enforce clang-format (#6293) * Enforce clang-format on commit for core files * forgot about tests --- util/linux_install.sh | 6 ++++++ util/macos_install.sh | 2 +- util/msys2_install.sh | 2 +- util/travis_compiled_push.sh | 3 ++- 4 files changed, 10 insertions(+), 3 deletions(-) (limited to 'util') diff --git a/util/linux_install.sh b/util/linux_install.sh index d21cd3c1c..5f29cad94 100755 --- a/util/linux_install.sh +++ b/util/linux_install.sh @@ -19,6 +19,7 @@ if grep ID /etc/os-release | grep -qE "fedora"; then avr-gcc \ avr-libc \ binutils-avr32-linux-gnu \ + clang \ dfu-util \ dfu-programmer \ diffutils \ @@ -44,6 +45,7 @@ elif grep ID /etc/os-release | grep -qE 'debian|ubuntu'; then avr-libc \ binutils-arm-none-eabi \ binutils-avr \ + clang-format \ dfu-programmer \ dfu-util \ diffutils \ @@ -68,6 +70,7 @@ elif grep ID /etc/os-release | grep -q 'arch\|manjaro'; then avr-libc \ avr-gcc \ base-devel \ + clang \ dfu-util \ diffutils \ gcc \ @@ -96,6 +99,7 @@ elif grep ID /etc/os-release | grep -q gentoo; then dev-embedded/avrdude \ dev-lang/python:3.5 \ net-misc/wget \ + sys-devel/clang \ sys-devel/gcc \ sys-devel/crossdev sudo crossdev -s4 --stable --g =4.9.4 --portage --verbose --target avr @@ -112,6 +116,7 @@ elif grep ID /etc/os-release | grep -q sabayon; then dev-embedded/avrdude \ dev-lang/python \ net-misc/wget \ + sys-devel/clang \ sys-devel/gcc \ sys-devel/crossdev sudo crossdev -s4 --stable --g =4.9.4 --portage --verbose --target avr @@ -126,6 +131,7 @@ elif grep ID /etc/os-release | grep -qE "opensuse|tumbleweed"; then fi sudo zypper install \ avr-libc \ + clang \ $CROSS_AVR_GCC \ $CROSS_ARM_GCC \ cross-avr-binutils \ diff --git a/util/macos_install.sh b/util/macos_install.sh index f7e304424..d9ec50a7e 100755 --- a/util/macos_install.sh +++ b/util/macos_install.sh @@ -24,6 +24,6 @@ fi brew tap osx-cross/avr brew tap PX4/homebrew-px4 brew update -brew install avr-gcc@8 gcc-arm-none-eabi dfu-programmer avrdude dfu-util python3 +brew install avr-gcc@8 gcc-arm-none-eabi dfu-programmer avrdude clang-format dfu-util python3 brew link --force avr-gcc@8 pip3 install -r ${util_dir}/../requirements.txt diff --git a/util/msys2_install.sh b/util/msys2_install.sh index bed176da6..ac9a6c769 100755 --- a/util/msys2_install.sh +++ b/util/msys2_install.sh @@ -8,7 +8,7 @@ installflip=false util_dir=$(dirname "$0") echo "Installing dependencies needed for the installation (quazip)" -pacman --needed -S base-devel mingw-w64-x86_64-toolchain msys/git msys/p7zip msys/python3 msys/unzip +pacman --needed -S base-devel mingw-w64-x86_64-toolchain msys/clang msys/git msys/p7zip msys/python3 msys/unzip source "$dir/win_shared_install.sh" diff --git a/util/travis_compiled_push.sh b/util/travis_compiled_push.sh index 04021ae7c..f20e30cdb 100755 --- a/util/travis_compiled_push.sh +++ b/util/travis_compiled_push.sh @@ -9,9 +9,10 @@ echo "Using git hash ${rev}" if [[ "$TRAVIS_BRANCH" == "master" && "$TRAVIS_PULL_REQUEST" == "false" ]] ; then -# convert to unix line-endings +# fix formatting git checkout master git diff --diff-filter=M --name-only -n 1 -z ${TRAVIS_COMMIT_RANGE} | xargs -0 dos2unix +git diff --diff-filter=M --name-only -n 1 -z ${TRAVIS_COMMIT_RANGE} | grep -e '^drivers' -e '^quantum' -e '^tests' -e '^tmk_core' | xargs -0 clang-format git diff --diff-filter=M --name-only -n 1 -z ${TRAVIS_COMMIT_RANGE} | xargs -0 git add git commit -m "convert to unix line-endings [skip ci]" && git push git@github.com:qmk/qmk_firmware.git master -- cgit v1.2.3-70-g09d2 From 210da974a071ca4df810e5330bdaf10ef0c6dfef Mon Sep 17 00:00:00 2001 From: skullydazed Date: Wed, 10 Jul 2019 16:24:48 -0700 Subject: Add new files to the list of files that are formatted. (#6296) --- util/travis_compiled_push.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'util') diff --git a/util/travis_compiled_push.sh b/util/travis_compiled_push.sh index f20e30cdb..4737d693b 100755 --- a/util/travis_compiled_push.sh +++ b/util/travis_compiled_push.sh @@ -11,10 +11,10 @@ if [[ "$TRAVIS_BRANCH" == "master" && "$TRAVIS_PULL_REQUEST" == "false" ]] ; the # fix formatting git checkout master -git diff --diff-filter=M --name-only -n 1 -z ${TRAVIS_COMMIT_RANGE} | xargs -0 dos2unix -git diff --diff-filter=M --name-only -n 1 -z ${TRAVIS_COMMIT_RANGE} | grep -e '^drivers' -e '^quantum' -e '^tests' -e '^tmk_core' | xargs -0 clang-format -git diff --diff-filter=M --name-only -n 1 -z ${TRAVIS_COMMIT_RANGE} | xargs -0 git add -git commit -m "convert to unix line-endings [skip ci]" && git push git@github.com:qmk/qmk_firmware.git master +git diff --diff-filter=AM --name-only -n 1 -z ${TRAVIS_COMMIT_RANGE} | xargs -0 dos2unix +git diff --diff-filter=AM --name-only -n 1 -z ${TRAVIS_COMMIT_RANGE} | grep -e '^drivers' -e '^quantum' -e '^tests' -e '^tmk_core' | xargs -0 clang-format +git diff --diff-filter=AM --name-only -n 1 -z ${TRAVIS_COMMIT_RANGE} | xargs -0 git add +git commit -m "format code according to conventions [skip ci]" && git push git@github.com:qmk/qmk_firmware.git master increment_version () { -- cgit v1.2.3-70-g09d2 From 5095a999b7e99c2a087837ea4360b8e53c11f219 Mon Sep 17 00:00:00 2001 From: fauxpark Date: Tue, 3 Sep 2019 00:09:09 +1000 Subject: Fix msys2 not installing any packages because it can't find clang (#6655) --- util/msys2_install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'util') diff --git a/util/msys2_install.sh b/util/msys2_install.sh index ac9a6c769..58397c7a3 100755 --- a/util/msys2_install.sh +++ b/util/msys2_install.sh @@ -8,7 +8,7 @@ installflip=false util_dir=$(dirname "$0") echo "Installing dependencies needed for the installation (quazip)" -pacman --needed -S base-devel mingw-w64-x86_64-toolchain msys/clang msys/git msys/p7zip msys/python3 msys/unzip +pacman --needed -S base-devel mingw-w64-x86_64-toolchain mingw-w64-x86_64-clang msys/git msys/p7zip msys/python3 msys/unzip source "$dir/win_shared_install.sh" -- cgit v1.2.3-70-g09d2 From 05d0e8c09eb972e654e15936aa0118add35f6376 Mon Sep 17 00:00:00 2001 From: Cory Watson Date: Mon, 2 Sep 2019 10:32:14 -0400 Subject: Add `dfu-programmer` to `pacman -S` (#6619) * Add `dfu-programmer` to `pacman -S` (#6618) `dfu-programmer` now resides at `extra/dfu-programmer` and is no longer in the AUR * Add `--needed` option to `pacman -S` for efficiency * Fix * Update util/linux_install.sh Co-Authored-By: noroadsleft <18669334+noroadsleft@users.noreply.github.com> --- util/linux_install.sh | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) (limited to 'util') diff --git a/util/linux_install.sh b/util/linux_install.sh index 5f29cad94..73ba25e13 100755 --- a/util/linux_install.sh +++ b/util/linux_install.sh @@ -61,7 +61,7 @@ elif grep ID /etc/os-release | grep -qE 'debian|ubuntu'; then elif grep ID /etc/os-release | grep -q 'arch\|manjaro'; then sudo pacman -U https://archive.archlinux.org/packages/a/avr-gcc/avr-gcc-8.3.0-1-x86_64.pkg.tar.xz - sudo pacman -S \ + sudo pacman -S --needed \ arm-none-eabi-binutils \ arm-none-eabi-gcc \ arm-none-eabi-newlib \ @@ -71,6 +71,7 @@ elif grep ID /etc/os-release | grep -q 'arch\|manjaro'; then avr-gcc \ base-devel \ clang \ + dfu-programmer \ dfu-util \ diffutils \ gcc \ @@ -79,10 +80,6 @@ elif grep ID /etc/os-release | grep -q 'arch\|manjaro'; then unzip \ wget \ zip - git clone https://aur.archlinux.org/dfu-programmer.git /tmp/dfu-programmer - cd /tmp/dfu-programmer || exit 1 - makepkg -sic - rm -rf /tmp/dfu-programmer/ elif grep ID /etc/os-release | grep -q gentoo; then echo "$GENTOO_WARNING" | fmt -- cgit v1.2.3-70-g09d2 From 4d339b7b5d1ecc2320080798d7e07e2d43675578 Mon Sep 17 00:00:00 2001 From: Konstantin Đorđević Date: Sat, 7 Sep 2019 10:17:54 +0200 Subject: Update docker_build.sh: indentation fix, error echo function (#6659) * Replace spaces with tab in docker_build.sh * Use errcho instead of echo >&2 --- util/docker_build.sh | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) (limited to 'util') diff --git a/util/docker_build.sh b/util/docker_build.sh index f36d5bcde..99d59d169 100755 --- a/util/docker_build.sh +++ b/util/docker_build.sh @@ -1,6 +1,10 @@ #!/bin/sh # NOTE: This script uses tabs for indentation +errcho() { + echo "$@" >&2 +} + USAGE="Usage: $0 [keyboard[:keymap[:target]]]" # Check preconditions @@ -11,11 +15,11 @@ for arg; do fi done if [ $# -gt 1 ]; then - echo "$USAGE" >&2 + errcho "$USAGE" exit 1 elif ! command -v docker >/dev/null 2>&1; then - echo "Error: docker not found" >&2 - echo "See https://docs.docker.com/install/#supported-platforms for installation instructions" >&2 + errcho "Error: docker not found" + errcho "See https://docs.docker.com/install/#supported-platforms for installation instructions" exit 2 fi @@ -29,7 +33,7 @@ else $1 EOF if [ -n "$x" ]; then - echo "$USAGE" >&2 + errcho "$USAGE" exit 1 fi fi @@ -37,9 +41,9 @@ if [ -n "$target" ]; then if [ "$(uname)" = "Linux" ] || docker-machine active >/dev/null 2>&1; then usb_args="--privileged -v /dev:/dev" else - echo "Error: target requires docker-machine to work on your platform" >&2 - echo "See http://gw.tnode.com/docker/docker-machine-with-usb-support-on-windows-macos" >&2 - echo "Consider flashing with QMK Toolbox (https://github.com/qmk/qmk_toolbox) instead" >&2 + errcho "Error: target requires docker-machine to work on your platform" + errcho "See http://gw.tnode.com/docker/docker-machine-with-usb-support-on-windows-macos" + errcho "Consider flashing with QMK Toolbox (https://github.com/qmk/qmk_toolbox) instead" exit 3 fi fi @@ -47,7 +51,7 @@ dir=$(pwd -W 2>/dev/null) || dir=$PWD # Use Windows path if on Windows # Run container and build firmware docker run --rm -it $usb_args \ - -w /qmk_firmware/ \ + -w /qmk_firmware \ -v "$dir":/qmk_firmware \ -e ALT_GET_KEYBOARDS=true \ -e SKIP_GIT="$SKIP_GIT" \ -- cgit v1.2.3-70-g09d2 From 1013ae2d34dc34e0e7d63fe875e70801ded3673c Mon Sep 17 00:00:00 2001 From: skullY Date: Thu, 29 Aug 2019 12:24:27 -0700 Subject: Add python tests to the travis check --- util/travis_build.sh | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) (limited to 'util') diff --git a/util/travis_build.sh b/util/travis_build.sh index fd5511a72..3fff768f6 100755 --- a/util/travis_build.sh +++ b/util/travis_build.sh @@ -22,7 +22,7 @@ if [[ "$TRAVIS_COMMIT_MESSAGE" != *"[skip build]"* ]] ; then eval $MAKE_ALL : $((exit_code = $exit_code + $?)) else - NEFM=$(git diff --name-only -n 1 ${TRAVIS_COMMIT_RANGE} | grep -Ev '^(keyboards/)' | grep -Ev '^(docs/)' | wc -l) + NEFM=$(git diff --name-only -n 1 ${TRAVIS_COMMIT_RANGE} | grep -Ev '^(keyboards/)' | grep -Ev '^(docs/)' | wc -l) BRANCH=$(git rev-parse --abbrev-ref HEAD) # is this branch master or a "non docs, non keyboards" change if [ $NEFM -gt 0 -o "$BRANCH" = "master" ]; then @@ -51,6 +51,14 @@ if [[ "$TRAVIS_COMMIT_MESSAGE" != *"[skip build]"* ]] ; then fi done fi + # Check and run python tests if necessary + PFM=$(git diff --name-only -n 1 ${TRAVIS_COMMIT_RANGE} | grep -E '^(lib/python/)' | wc -l) + if [ $PFM -gt 0 -o "$BRANCH" = "master" ]; then + echo + echo "Running python tests." + bin/qmk nose2 + : $((exit_code = $exit_code + $?)) + fi fi exit $exit_code fi -- cgit v1.2.3-70-g09d2 From 18690ddaeaa66de91bcd635fb2eeee4364e1d162 Mon Sep 17 00:00:00 2001 From: skullY Date: Thu, 29 Aug 2019 12:31:16 -0700 Subject: filter python from the list of things that trigger default builds --- util/travis_build.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'util') diff --git a/util/travis_build.sh b/util/travis_build.sh index 3fff768f6..81546c115 100755 --- a/util/travis_build.sh +++ b/util/travis_build.sh @@ -22,7 +22,7 @@ if [[ "$TRAVIS_COMMIT_MESSAGE" != *"[skip build]"* ]] ; then eval $MAKE_ALL : $((exit_code = $exit_code + $?)) else - NEFM=$(git diff --name-only -n 1 ${TRAVIS_COMMIT_RANGE} | grep -Ev '^(keyboards/)' | grep -Ev '^(docs/)' | wc -l) + NEFM=$(git diff --name-only -n 1 ${TRAVIS_COMMIT_RANGE} | grep -Ev '^(keyboards/)' | grep -Ev '^(docs/)' | grep -Ev '^(lib/python/)' | grep -Ev (^bin/qmk) | wc -l) BRANCH=$(git rev-parse --abbrev-ref HEAD) # is this branch master or a "non docs, non keyboards" change if [ $NEFM -gt 0 -o "$BRANCH" = "master" ]; then @@ -52,7 +52,7 @@ if [[ "$TRAVIS_COMMIT_MESSAGE" != *"[skip build]"* ]] ; then done fi # Check and run python tests if necessary - PFM=$(git diff --name-only -n 1 ${TRAVIS_COMMIT_RANGE} | grep -E '^(lib/python/)' | wc -l) + PFM=$(git diff --name-only -n 1 ${TRAVIS_COMMIT_RANGE} | grep -E -e '^(lib/python/)' -e '^(bin/qmk)' | wc -l) if [ $PFM -gt 0 -o "$BRANCH" = "master" ]; then echo echo "Running python tests." -- cgit v1.2.3-70-g09d2 From 16366dd23d2c37932e2c0975800f0a8b30d53863 Mon Sep 17 00:00:00 2001 From: skullY Date: Thu, 29 Aug 2019 12:44:08 -0700 Subject: add missing apostrophes --- util/travis_build.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'util') diff --git a/util/travis_build.sh b/util/travis_build.sh index 81546c115..9ea0c5c4b 100755 --- a/util/travis_build.sh +++ b/util/travis_build.sh @@ -22,7 +22,7 @@ if [[ "$TRAVIS_COMMIT_MESSAGE" != *"[skip build]"* ]] ; then eval $MAKE_ALL : $((exit_code = $exit_code + $?)) else - NEFM=$(git diff --name-only -n 1 ${TRAVIS_COMMIT_RANGE} | grep -Ev '^(keyboards/)' | grep -Ev '^(docs/)' | grep -Ev '^(lib/python/)' | grep -Ev (^bin/qmk) | wc -l) + NEFM=$(git diff --name-only -n 1 ${TRAVIS_COMMIT_RANGE} | grep -Ev '^(keyboards/)' | grep -Ev '^(docs/)' | grep -Ev '^(lib/python/)' | grep -Ev '(^bin/qmk)' | wc -l) BRANCH=$(git rev-parse --abbrev-ref HEAD) # is this branch master or a "non docs, non keyboards" change if [ $NEFM -gt 0 -o "$BRANCH" = "master" ]; then -- cgit v1.2.3-70-g09d2 From 6ca29f2b9b02763aa3de607e8d08f14d5284918f Mon Sep 17 00:00:00 2001 From: skullY Date: Thu, 29 Aug 2019 13:05:11 -0700 Subject: Run the python tests inside docker --- util/travis_build.sh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'util') diff --git a/util/travis_build.sh b/util/travis_build.sh index 9ea0c5c4b..605b6d5f0 100755 --- a/util/travis_build.sh +++ b/util/travis_build.sh @@ -2,9 +2,9 @@ # if docker is installed - call make within the qmk docker image if command -v docker >/dev/null; then - function make() { - docker run --rm -e MAKEFLAGS="$MAKEFLAGS" -w /qmk_firmware/ -v "$PWD":/qmk_firmware --user $(id -u):$(id -g) qmkfm/base_container make "$@" - } + function make() { + docker run --rm -e MAKEFLAGS="$MAKEFLAGS" -w /qmk_firmware/ -v "$PWD":/qmk_firmware --user $(id -u):$(id -g) qmkfm/base_container make "$@" + } fi # test force push @@ -30,7 +30,7 @@ if [[ "$TRAVIS_COMMIT_MESSAGE" != *"[skip build]"* ]] ; then eval $MAKE_ALL : $((exit_code = $exit_code + $?)) else - # keyboards project format + # keyboards project format # /keyboards/board1/rev/keymaps/ # /keyboards/board2/keymaps/ # ensure we strip everything off after and including the keymaps folder to get board and/or revision @@ -56,7 +56,7 @@ if [[ "$TRAVIS_COMMIT_MESSAGE" != *"[skip build]"* ]] ; then if [ $PFM -gt 0 -o "$BRANCH" = "master" ]; then echo echo "Running python tests." - bin/qmk nose2 + docker run --rm -w /qmk_firmware/ -v "$PWD":/qmk_firmware --user $(id -u):$(id -g) qmkfm/base_container bin/qmk nose2 : $((exit_code = $exit_code + $?)) fi fi -- cgit v1.2.3-70-g09d2 From 7f65323e101a911fb64abc49ff3c81f8ed7656a7 Mon Sep 17 00:00:00 2001 From: Sorixelle <38685302+Sorixelle@users.noreply.github.com> Date: Sat, 21 Sep 2019 16:55:51 +1000 Subject: Add support for Void Linux systems to the qmk_install.sh script (#5526) * Add support for Void Linux systems to the qmk_install.sh script * Fix typos + grammatical edits in comments * Sort distributions by alphabetical order in linux_install.sh * Revert previous commit and sort Void packages in alphabetical order * Fix permissions on `util/linux_install.sh` --- util/linux_install.sh | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) (limited to 'util') diff --git a/util/linux_install.sh b/util/linux_install.sh index 73ba25e13..dca0110a5 100755 --- a/util/linux_install.sh +++ b/util/linux_install.sh @@ -183,6 +183,29 @@ elif grep ID /etc/os-release | grep -q solus; then unzip printf "\n$SOLUS_INFO\n" +elif grep ID /etc/os-release | grep -q void; then + # musl Void systems don't have glibc cross compilers avaliable in their repos. + # glibc Void systems do have musl cross compilers though, for some reason. + # So, default to musl, and switch to glibc if it is installed. + CROSS_ARM=cross-arm-linux-musleabi + if xbps-query glibc > /dev/null; then # Check is glibc if installed + CROSS_ARM=cross-arm-linux-gnueabi + fi + + sudo xbps-install \ + avr-binutils \ + avr-gcc \ + avr-libc \ + $CROSS_ARM \ + dfu-programmer \ + dfu-util \ + gcc \ + git \ + make \ + wget \ + unzip \ + zip + else echo "Sorry, we don't recognize your OS. Help us by contributing support!" echo -- cgit v1.2.3-70-g09d2 From d569f0877155efc752994f8a21f5cf001f9d6ae6 Mon Sep 17 00:00:00 2001 From: skullydazed Date: Sun, 22 Sep 2019 13:25:33 -0700 Subject: Configuration system for CLI (#6708) * Rework how bin/qmk handles subcommands * qmk config wip * Code to show all configs * Fully working `qmk config` command * Mark some CLI arguments so they don't pollute the config file * Fleshed out config support, nicer subcommand support * sync with installable cli * pyformat * Add a test for subcommand_modules * Documentation for the `qmk config` command * split config_token on space so qmk config is more predictable * Rework how subcommands are imported * Document `arg_only` * Document deleting from CLI * Document how multiple operations work * Add cli config to the doc index * Add tests for the cli commands * Make running the tests more reliable * Be more selective about building all default keymaps * Update new-keymap to fit the new subcommand style * Add documentation about writing CLI scripts * Document new-keyboard * Update docs/cli_configuration.md Co-Authored-By: noroadsleft <18669334+noroadsleft@users.noreply.github.com> * Update docs/cli_development.md Co-Authored-By: noroadsleft <18669334+noroadsleft@users.noreply.github.com> * Update docs/cli_development.md Co-Authored-By: noroadsleft <18669334+noroadsleft@users.noreply.github.com> * Update docs/cli_development.md Co-Authored-By: noroadsleft <18669334+noroadsleft@users.noreply.github.com> * Address yan's comments. * Apply suggestions from code review suggestions from @noahfrederick Co-Authored-By: Noah Frederick * Apply suggestions from code review Co-Authored-By: Noah Frederick * Remove pip3 from the test runner --- bin/qmk | 92 ++++++---------- bin/qmk-compile-json | 1 - bin/qmk-doctor | 1 - bin/qmk-hello | 1 - bin/qmk-json-keymap | 1 - build_json.mk | 2 +- docs/_summary.md | 3 +- docs/cli.md | 114 +++++++++++++++++-- docs/cli_configuration.md | 121 +++++++++++++++++++++ docs/cli_development.md | 175 ++++++++++++++++++++++++++++++ docs/python_development.md | 45 -------- docs/redirects.json | 4 + lib/python/milc.py | 113 +++++++++++-------- lib/python/qmk/cli/__init__.py | 13 +++ lib/python/qmk/cli/cformat.py | 6 +- lib/python/qmk/cli/compile.py | 10 +- lib/python/qmk/cli/config.py | 96 ++++++++++++++++ lib/python/qmk/cli/doctor.py | 5 +- lib/python/qmk/cli/hello.py | 6 +- lib/python/qmk/cli/json/__init__.py | 5 + lib/python/qmk/cli/json/keymap.py | 20 ++-- lib/python/qmk/cli/new/__init__.py | 1 + lib/python/qmk/cli/new/keymap.py | 17 ++- lib/python/qmk/cli/nose2.py | 18 --- lib/python/qmk/cli/pyformat.py | 5 +- lib/python/qmk/cli/pytest.py | 20 ++++ lib/python/qmk/path.py | 1 + lib/python/qmk/tests/test_cli_commands.py | 39 +++++++ requirements.txt | 2 +- util/travis_build.sh | 7 +- 30 files changed, 730 insertions(+), 214 deletions(-) delete mode 120000 bin/qmk-compile-json delete mode 120000 bin/qmk-doctor delete mode 120000 bin/qmk-hello delete mode 120000 bin/qmk-json-keymap create mode 100644 docs/cli_configuration.md create mode 100644 docs/cli_development.md delete mode 100644 docs/python_development.md create mode 100644 lib/python/qmk/cli/config.py delete mode 100644 lib/python/qmk/cli/nose2.py create mode 100644 lib/python/qmk/cli/pytest.py create mode 100644 lib/python/qmk/tests/test_cli_commands.py (limited to 'util') diff --git a/bin/qmk b/bin/qmk index 1aa16e17d..876473fa4 100755 --- a/bin/qmk +++ b/bin/qmk @@ -4,10 +4,8 @@ import os import subprocess import sys -from glob import glob -from time import strftime -from importlib import import_module from importlib.util import find_spec +from time import strftime # Add the QMK python libs to our path script_dir = os.path.dirname(os.path.realpath(__file__)) @@ -15,12 +13,8 @@ qmk_dir = os.path.abspath(os.path.join(script_dir, '..')) python_lib_dir = os.path.abspath(os.path.join(qmk_dir, 'lib', 'python')) sys.path.append(python_lib_dir) -# Change to the root of our checkout -os.environ['ORIG_CWD'] = os.getcwd() -os.chdir(qmk_dir) - # Make sure our modules have been setup -with open('requirements.txt', 'r') as fd: +with open(os.path.join(qmk_dir, 'requirements.txt'), 'r') as fd: for line in fd.readlines(): line = line.strip().replace('<', '=').replace('>', '=') @@ -32,72 +26,58 @@ with open('requirements.txt', 'r') as fd: module = line.split('=')[0] if '=' in line else line if not find_spec(module): - print('Your QMK build environment is not fully setup!\n') - print('Please run `./util/qmk_install.sh` to setup QMK.') + print('Could not find module %s!', module) + print('Please run `pip3 install -r requirements.txt` to install the python dependencies.') exit(255) # Figure out our version +# TODO(skullydazed/anyone): Find a method that doesn't involve git. This is slow in docker and on windows. command = ['git', 'describe', '--abbrev=6', '--dirty', '--always', '--tags'] -result = subprocess.run(command, universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) +result = subprocess.run(command, universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) if result.returncode == 0: - os.environ['QMK_VERSION'] = 'QMK ' + result.stdout.strip() + os.environ['QMK_VERSION'] = result.stdout.strip() else: - os.environ['QMK_VERSION'] = 'QMK ' + strftime('%Y-%m-%d-%H:%M:%S') + os.environ['QMK_VERSION'] = 'nogit-' + strftime('%Y-%m-%d-%H:%M:%S') + '-dirty' # Setup the CLI import milc -milc.EMOJI_LOGLEVELS['INFO'] = '{fg_blue}Ψ{style_reset_all}' -# If we were invoked as `qmk ` massage sys.argv into `qmk-`. -# This means we can't accept arguments to the qmk script itself. -script_name = os.path.basename(sys.argv[0]) -if script_name == 'qmk': - if len(sys.argv) == 1: - milc.cli.log.error('No subcommand specified!\n') - - if len(sys.argv) == 1 or sys.argv[1] in ['-h', '--help']: - milc.cli.echo('usage: qmk [...]') - milc.cli.echo('\nsubcommands:') - subcommands = glob(os.path.join(qmk_dir, 'bin', 'qmk-*')) - for subcommand in sorted(subcommands): - subcommand = os.path.basename(subcommand).split('-', 1)[1] - milc.cli.echo('\t%s', subcommand) - milc.cli.echo('\nqmk --help for more information') - exit(1) +milc.EMOJI_LOGLEVELS['INFO'] = '{fg_blue}Ψ{style_reset_all}' - if sys.argv[1] in ['-V', '--version']: - milc.cli.echo(os.environ['QMK_VERSION']) - exit(0) - sys.argv[0] = script_name = '-'.join((script_name, sys.argv[1])) - del sys.argv[1] +@milc.cli.entrypoint('QMK Helper Script') +def qmk_main(cli): + """The function that gets run when no subcommand is provided. + """ + cli.print_help() -# Look for which module to import -if script_name == 'qmk': - milc.cli.print_help() - exit(0) -elif not script_name.startswith('qmk-'): - milc.cli.log.error('Invalid symlink, must start with "qmk-": %s', script_name) -else: - subcommand = script_name.replace('-', '.').replace('_', '.').split('.') - subcommand.insert(1, 'cli') - subcommand = '.'.join(subcommand) - try: - import_module(subcommand) - except ModuleNotFoundError as e: - if e.__class__.__name__ != subcommand: - raise +def main(): + """Setup our environment and then call the CLI entrypoint. + """ + # Change to the root of our checkout + os.environ['ORIG_CWD'] = os.getcwd() + os.chdir(qmk_dir) - milc.cli.log.error('Invalid subcommand! Could not import %s.', subcommand) - exit(1) + # Import the subcommands + import qmk.cli -if __name__ == '__main__': + # Execute return_code = milc.cli() + if return_code is False: exit(1) - elif return_code is not True and isinstance(return_code, int) and return_code < 256: + + 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) + exit(255) + exit(return_code) - else: - exit(0) + + exit(0) + + +if __name__ == '__main__': + main() diff --git a/bin/qmk-compile-json b/bin/qmk-compile-json deleted file mode 120000 index c92dce8a1..000000000 --- a/bin/qmk-compile-json +++ /dev/null @@ -1 +0,0 @@ -qmk \ No newline at end of file diff --git a/bin/qmk-doctor b/bin/qmk-doctor deleted file mode 120000 index c92dce8a1..000000000 --- a/bin/qmk-doctor +++ /dev/null @@ -1 +0,0 @@ -qmk \ No newline at end of file diff --git a/bin/qmk-hello b/bin/qmk-hello deleted file mode 120000 index c92dce8a1..000000000 --- a/bin/qmk-hello +++ /dev/null @@ -1 +0,0 @@ -qmk \ No newline at end of file diff --git a/bin/qmk-json-keymap b/bin/qmk-json-keymap deleted file mode 120000 index c92dce8a1..000000000 --- a/bin/qmk-json-keymap +++ /dev/null @@ -1 +0,0 @@ -qmk \ No newline at end of file diff --git a/build_json.mk b/build_json.mk index 8820a8f4a..e2a33e3b6 100644 --- a/build_json.mk +++ b/build_json.mk @@ -23,5 +23,5 @@ endif # Generate the keymap.c ifneq ("$(KEYMAP_JSON)","") - _ = $(shell test -e $(KEYMAP_C) || bin/qmk-json-keymap $(KEYMAP_JSON) -o $(KEYMAP_C)) + _ = $(shell test -e $(KEYMAP_C) || bin/qmk json-keymap $(KEYMAP_JSON) -o $(KEYMAP_C)) endif diff --git a/docs/_summary.md b/docs/_summary.md index 4e87d8f1f..233b2cdaa 100644 --- a/docs/_summary.md +++ b/docs/_summary.md @@ -9,6 +9,7 @@ * [QMK Basics](README.md) * [QMK Introduction](getting_started_introduction.md) * [QMK CLI](cli.md) + * [QMK CLI Config](cli_configuration.md) * [Contributing to QMK](contributing.md) * [How to Use Github](getting_started_github.md) * [Getting Help](getting_started_getting_help.md) @@ -48,7 +49,7 @@ * [Useful Functions](ref_functions.md) * [Configurator Support](reference_configurator_support.md) * [info.json Format](reference_info_json.md) - * [Python Development](python_development.md) + * [Python CLI Development](cli_development.md) * [Features](features.md) * [Basic Keycodes](keycodes_basic.md) diff --git a/docs/cli.md b/docs/cli.md index 4b8472b19..cb609e2a9 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -4,22 +4,70 @@ This page describes how to setup and use the QMK CLI. # Overview -The QMK CLI makes building and working with QMK keyboards easier. We have provided a number of commands to help you work with QMK: +The QMK CLI makes building and working with QMK keyboards easier. We have provided a number of commands to simplify and streamline tasks such as obtaining and compiling the QMK firmware, creating keymaps, and more. -* `qmk compile` -* `qmk doctor` +* [Global CLI](#global-cli) +* [Local CLI](#local-cli) +* [CLI Commands](#cli-commands) -# Setup +# Requirements -Simply add the `qmk_firmware/bin` directory to your `PATH`. You can run the `qmk` commands from any directory. +The CLI requires Python 3.5 or greater. We try to keep the number of requirements small but you will also need to install the packages listed in [`requirements.txt`](https://github.com/qmk/qmk_firmware/blob/master/requirements.txt). + +# Global CLI + +QMK provides an installable CLI that can be used to setup your QMK build environment, work with QMK, and which makes working with multiple copies of `qmk_firmware` easier. We recommend installing and updating this periodically. + +## Install Using Homebrew (macOS, some Linux) + +If you have installed [Homebrew](https://brew.sh) you can tap and install QMK: + +``` +brew tap qmk/qmk +brew install qmk +export QMK_HOME='~/qmk_firmware' # Optional, set the location for `qmk_firmware` +qmk setup # This will clone `qmk/qmk_firmware` and optionally set up your build environment +``` + +## Install Using easy_install or pip + +If your system is not listed above you can install QMK manually. First ensure that you have python 3.5 (or later) installed and have installed pip. Then install QMK with this command: + +``` +pip3 install qmk +export QMK_HOME='~/qmk_firmware' # Optional, set the location for `qmk_firmware` +qmk setup # This will clone `qmk/qmk_firmware` and optionally set up your build environment +``` + +## Packaging For Other Operating Systems + +We are looking for people to create and maintain a `qmk` package for more operating systems. If you would like to create a package for your OS please follow these guidelines: + +* Follow best practices for your OS when they conflict with these guidelines + * Documment why in a comment when you do deviate +* Install using a virtualenv +* Instruct the user to set the environment variable `QMK_HOME` to have the firmware source checked out somewhere other than `~/qmk_firmware`. + +# Local CLI + +If you do not want to use the global CLI there is a local CLI bundled with `qmk_firmware`. You can find it in `qmk_firmware/bin/qmk`. You can run the `qmk` command from any directory and it will always operate on that copy of `qmk_firmware`. + +**Example**: ``` -export PATH=$PATH:$HOME/qmk_firmware/bin +$ ~/qmk_firmware/bin/qmk hello +Ψ Hello, World! ``` -You may want to add this to your `.profile`, `.bash_profile`, `.zsh_profile`, or other shell startup scripts. +## Local CLI Limitations -# Commands +There are some limitations to the local CLI compared to the global CLI: + +* The local CLI does not support `qmk setup` or `qmk clone` +* The local CLI always operates on the same `qmk_firmware` tree, even if you have multiple repositories cloned. +* The local CLI does not run in a virtualenv, so it's possible that dependencies will conflict + +# CLI Commands ## `qmk compile` @@ -46,3 +94,53 @@ This command formats C code using clang-format. Run it with no arguments to form ``` qmk cformat [file1] [file2] [...] [fileN] ``` + +## `qmk config` + +This command lets you configure the behavior of QMK. For the full `qmk config` documentation see [CLI Configuration](cli_configuration.md). + +**Usage**: + +``` +qmk config [-ro] [config_token1] [config_token2] [...] [config_tokenN] +``` + +## `qmk doctor` + +This command examines your environment and alerts you to potential build or flash problems. + +**Usage**: + +``` +qmk doctor +``` + +## `qmk new-keymap` + +This command creates a new keymap based on a keyboard's existing default keymap. + +**Usage**: + +``` +qmk new-keymap [-kb KEYBOARD] [-km KEYMAP] +``` + +## `qmk pyformat` + +This command formats python code in `qmk_firmware`. + +**Usage**: + +``` +qmk pyformat +``` + +## `qmk pytest` + +This command runs the python test suite. If you make changes to python code you should ensure this runs successfully. + +**Usage**: + +``` +qmk pytest +``` diff --git a/docs/cli_configuration.md b/docs/cli_configuration.md new file mode 100644 index 000000000..ad9ff291c --- /dev/null +++ b/docs/cli_configuration.md @@ -0,0 +1,121 @@ +# QMK CLI Configuration + +This document explains how `qmk config` works. + +# Introduction + +Configuration for QMK CLI is a key/value system. Each key consists of a subcommand and an argument name separated by a period. This allows for a straightforward and direct translation between config keys and the arguments they set. + +## Simple Example + +As an example let's look at the command `qmk compile --keyboard clueboard/66/rev4 --keymap default`. + +There are two command line arguments that could be read from configuration instead: + +* `compile.keyboard` +* `compile.keymap` + +Let's set these now: + +``` +$ qmk config compile.keyboard=clueboard/66/rev4 compile.keymap=default +compile.keyboard: None -> clueboard/66/rev4 +compile.keymap: None -> default +Ψ Wrote configuration to '/Users/example/Library/Application Support/qmk/qmk.ini' +``` + +Now I can run `qmk compile` without specifying my keyboard and keymap each time. + +## Setting User Defaults + +Sometimes you want to share a setting between multiple commands. For example, multiple commands take the argument `--keyboard`. Rather than setting this value for every command you can set a user value which will be used by any command that takes that argument. + +Example: + +``` +$ qmk config user.keyboard=clueboard/66/rev4 user.keymap=default +user.keyboard: None -> clueboard/66/rev4 +user.keymap: None -> default +Ψ Wrote configuration to '/Users/example/Library/Application Support/qmk/qmk.ini' +``` + +# CLI Documentation (`qmk config`) + +The `qmk config` command is used to interact with the underlying configuration. When run with no argument it shows the current configuration. When arguments are supplied they are assumed to be configuration tokens, which are strings containing no spaces with the following form: + + [.][=] + +## Setting Configuration Values + +You can set configuration values by putting an equal sign (=) into your config key. The key must always be the full `
.` form. + +Example: + +``` +$ qmk config default.keymap=default +default.keymap: None -> default +Ψ Wrote configuration to '/Users/example/Library/Application Support/qmk/qmk.ini' +``` + +## Reading Configuration Values + +You can read configuration values for the entire configuration, a single key, or for an entire section. You can also specify multiple keys to display more than one value. + +### Entire Configuration Example + + qmk config + +### Whole Section Example + + qmk config compile + +### Single Key Example + + qmk config compile.keyboard + +### Multiple Keys Example + + qmk config user compile.keyboard compile.keymap + +## Deleting Configuration Values + +You can delete a configuration value by setting it to the special string `None`. + +Example: + +``` +$ qmk config default.keymap=None +default.keymap: default -> None +Ψ Wrote configuration to '/Users/example/Library/Application Support/qmk/qmk.ini' +``` + +## Multiple Operations + +You can combine multiple read and write operations into a single command. They will be executed and displayed in order: + +``` +$ qmk config compile default.keymap=default compile.keymap=None +compile.keymap=skully +compile.keyboard=clueboard/66_hotswap/gen1 +default.keymap: None -> default +compile.keymap: skully -> None +Ψ Wrote configuration to '/Users/example/Library/Application Support/qmk/qmk.ini' +``` + +# User Configuration Options + +| Key | Default Value | Description | +|-----|---------------|-------------| +| user.keyboard | None | The keyboard path (Example: `clueboard/66/rev4`) | +| user.keymap | None | The keymap name (Example: `default`) | +| user.name | None | The user's github username. | + +# All Configuration Options + +| Key | Default Value | Description | +|-----|---------------|-------------| +| compile.keyboard | None | The keyboard path (Example: `clueboard/66/rev4`) | +| compile.keymap | None | The keymap name (Example: `default`) | +| hello.name | None | The name to greet when run. | +| new_keyboard.keyboard | None | The keyboard path (Example: `clueboard/66/rev4`) | +| new_keyboard.keymap | None | The keymap name (Example: `default`) | diff --git a/docs/cli_development.md b/docs/cli_development.md new file mode 100644 index 000000000..f5c7ad139 --- /dev/null +++ b/docs/cli_development.md @@ -0,0 +1,175 @@ +# QMK CLI Development + +This document has useful information for developers wishing to write new `qmk` subcommands. + +# Overview + +The QMK CLI operates using the subcommand pattern made famous by git. The main `qmk` script is simply there to setup the environment and pick the correct entrypoint to run. Each subcommand is a self-contained module with an entrypoint (decorated by `@cli.subcommand()`) that performs some action and returns a shell returncode, or None. + +# Subcommands + +[MILC](https://github.com/clueboard/milc) is the CLI framework `qmk` uses to handle argument parsing, configuration, logging, and many other features. It lets you focus on writing your tool without wasting your time writing glue code. + +Subcommands in the local CLI are always found in `qmk_firmware/lib/python/qmk/cli`. + +Let's start by looking at an example subcommand. This is `lib/python/qmk/cli/hello.py`: + +```python +"""QMK Python Hello World + +This is an example QMK CLI script. +""" +from milc import cli + + +@cli.argument('-n', '--name', default='World', help='Name to greet.') +@cli.subcommand('QMK Hello World.') +def hello(cli): + """Log a friendly greeting. + """ + cli.log.info('Hello, %s!', cli.config.hello.name) +``` + +First we import the `cli` object from `milc`. This is how we interact with the user and control the script's behavior. We use `@cli.argument()` to define a command line flag, `--name`. This also creates a configuration variable named `hello.name` (and the corresponding `user.name`) which the user can set so they don't have to specify the argument. The `cli.subcommand()` decorator designates this function as a subcommand. The name of the subcommand will be taken from the name of the function. + +Once inside our function we find a typical "Hello, World!" program. We use `cli.log` to access the underlying [Logger Object](https://docs.python.org/3.5/library/logging.html#logger-objects), whose behavior is user controllable. We also access the value for name supplied by the user as `cli.config.hello.name`. The value for `cli.config.hello.name` will be determined by looking at the `--name` argument supplied by the user, if not provided it will use the value in the `qmk.ini` config file, and if neither of those is provided it will fall back to the default supplied in the `cli.argument()` decorator. + +# User Interaction + +MILC and the QMK CLI have several nice tools for interacting with the user. Using these standard tools will allow you to colorize your text for easier interactions, and allow the user to control when and how that information is displayed and stored. + +## Printing Text + +There are two main methods for outputting text in a subcommand- `cli.log` and `cli.echo()`. They operate in similar ways but you should prefer to use `cli.log.info()` for most general purpose printing. + +You can use special tokens to colorize your text, to make it easier to understand the output of your program. See [Colorizing Text](#colorizing-text) below. + +Both of these methods support built-in string formatting using python's [printf style string format operations](https://docs.python.org/3.5/library/stdtypes.html#old-string-formatting). You can use tokens such as `%s` and `%d` within your text strings then pass the values as arguments. See our Hello, World program above for an example. + +You should never use the format operator (`%`) directly, always pass values as arguments. + +### Logging (`cli.log`) + +The `cli.log` object gives you access to a [Logger Object](https://docs.python.org/3.5/library/logging.html#logger-objects). We have configured our log output to show the user a nice emoji for each log level (or the log level name if their terminal does not support unicode.) This way the user can tell at a glance which messages are most important when something goes wrong. + +The default log level is `INFO`. If the user runs `qmk -v ` the default log level will be set to `DEBUG`. + +| Function | Emoji | +|----------|-------| +| cli.log.critical | `{bg_red}{fg_white}¬_¬{style_reset_all}` | +| cli.log.error | `{fg_red}☒{style_reset_all}` | +| cli.log.warning | `{fg_yellow}⚠{style_reset_all}` | +| cli.log.info | `{fg_blue}Ψ{style_reset_all}` | +| cli.log.debug | `{fg_cyan}☐{style_reset_all}` | +| cli.log.notset | `{style_reset_all}¯\\_(o_o)_/¯` | + +### Printing (`cli.echo`) + +Sometimes you simply need to print text outside of the log system. This is appropriate if you are outputting fixed data or writing out something that should never be logged. Most of the time you should prefer `cli.log.info()` over `cli.echo`. + +### Colorizing Text + +You can colorize the output of your text by including color tokens within text. Use color to highlight, not to convey information. Remember that the user can disable color, and your subcommand should still be usable if they do. + +You should generally avoid setting the background color, unless it's integral to what you are doing. Remember that users have a lot of preferences when it comes to their terminal color, so you should pick colors that work well against both black and white backgrounds. + +Colors prefixed with 'fg' will affect the foreground (text) color. Colors prefixed with 'bg' will affect the background color. + +| Color | Background | Extended Background | Foreground | Extended Foreground| +|-------|------------|---------------------|------------|--------------------| +| Black | {bg_black} | {bg_lightblack_ex} | {fg_black} | {fg_lightblack_ex} | +| Blue | {bg_blue} | {bg_lightblue_ex} | {fg_blue} | {fg_lightblue_ex} | +| Cyan | {bg_cyan} | {bg_lightcyan_ex} | {fg_cyan} | {fg_lightcyan_ex} | +| Green | {bg_green} | {bg_lightgreen_ex} | {fg_green} | {fg_lightgreen_ex} | +| Magenta | {bg_magenta} | {bg_lightmagenta_ex} | {fg_magenta} | {fg_lightmagenta_ex} | +| Red | {bg_red} | {bg_lightred_ex} | {fg_red} | {fg_lightred_ex} | +| White | {bg_white} | {bg_lightwhite_ex} | {fg_white} | {fg_lightwhite_ex} | +| Yellow | {bg_yellow} | {bg_lightyellow_ex} | {fg_yellow} | {fg_lightyellow_ex} | + +There are also control sequences that can be used to change the behavior of +ANSI output: + +| Control Sequences | Description | +|-------------------|-------------| +| {style_bright} | Make the text brighter | +| {style_dim} | Make the text dimmer | +| {style_normal} | Make the text normal (neither `{style_bright}` nor `{style_dim}`) | +| {style_reset_all} | Reset all text attributes to default. (This is automatically added to the end of every string.) | +| {bg_reset} | Reset the background color to the user's default | +| {fg_reset} | Reset the foreground color to the user's default | + +# Arguments and Configuration + +QMK handles the details of argument parsing and configuration for you. When you add a new argument it is automatically incorporated into the config tree based on your subcommand's name and the long name of the argument. You can access this configuration in `cli.config`, using either attribute-style access (`cli.config..`) or dictionary-style access (`cli.config['']['']`). + +Under the hood QMK uses [ConfigParser](https://docs.python.org/3/library/configparser.html) to store configurations. This gives us an easy and straightforward way to represent the configuration in a human-editable way. We have wrapped access to this configuration to provide some nicities that ConfigParser does not normally have. + +## Reading Configuration Values + +You can interact with `cli.config` in all the ways you'd normally expect. For example the `qmk compile` command gets the keyboard name from `cli.config.compile.keyboard`. It does not need to know whether that value came from the command line, an environment variable, or the configuration file. + +Iteration is also supported: + +``` +for section in cli.config: + for key in cli.config[section]: + cli.log.info('%s.%s: %s', section, key, cli.config[section][key]) +``` + +## Setting Configuration Values + +You can set configuration values in the usual ways. + +Dictionary style: + +``` +cli.config['
'][''] = +``` + +Attribute style: + +``` +cli.config.
. = +``` + +## Deleting Configuration Values + +You can delete configuration values in the usual ways. + +Dictionary style: + +``` +del(cli.config['
']['']) +``` + +Attribute style: + +``` +del(cli.config.
.) +``` + +## Writing The Configuration File + +The configuration is not written out when it is changed. Most commands do not need to do this. We prefer to have the user change their configuration deliberitely using `qmk config`. + +You can use `cli.save_config()` to write out the configuration. + +## Excluding Arguments From Configuration + +Some arguments should not be propagated to the configuration file. These can be excluded by adding `arg_only=True` when creating the argument. + +Example: + +``` +@cli.argument('-o', '--output', arg_only=True, help='File to write to') +@cli.argument('filename', arg_only=True, help='Configurator JSON file') +@cli.subcommand('Create a keymap.c from a QMK Configurator export.') +def json_keymap(cli): + pass +``` + +You will only be able to access these arguments using `cli.args`. For example: + +``` +cli.log.info('Reading from %s and writing to %s', cli.args.filename, cli.args.output) +``` diff --git a/docs/python_development.md b/docs/python_development.md deleted file mode 100644 index b976a7c0e..000000000 --- a/docs/python_development.md +++ /dev/null @@ -1,45 +0,0 @@ -# Python Development in QMK - -This document gives an overview of how QMK has structured its python code. You should read this before working on any of the python code. - -## Script directories - -There are two places scripts live in QMK: `qmk_firmware/bin` and `qmk_firmware/util`. You should use `bin` for any python scripts that utilize the `qmk` wrapper. Scripts that are standalone and not run very often live in `util`. - -We discourage putting anything into `bin` that does not utilize the `qmk` wrapper. If you think you have a good reason for doing so please talk to us about your use case. - -## Python Modules - -Most of the QMK python modules can be found in `qmk_firmware/lib/python`. This is the path that we append to `sys.path`. - -We have a module hierarchy under that path: - -* `qmk_firmware/lib/python` - * `milc.py` - The CLI library we use. Will be pulled out into its own module in the future. - * `qmk` - Code associated with QMK - * `cli` - Modules that will be imported for CLI commands. - * `errors.py` - Errors that can be raised within QMK apps - * `keymap.py` - Functions for working with keymaps - -## CLI Scripts - -We have a CLI wrapper that you should utilize for any user facing scripts. We think it's pretty easy to use and it gives you a lot of nice things for free. - -To use the wrapper simply place a module into `qmk_firmware/lib/python/qmk/cli`, and create a symlink to `bin/qmk` named after your module. Dashes in command names will be converted into dots so you can use hierarchy to manage commands. - -When `qmk` is run it checks to see how it was invoked. If it was invoked as `qmk` the module name is take from `sys.argv[1]`. If it was invoked as `qmk-` then everything after the first dash is taken as the module name. Dashes and underscores are converted to dots, and then `qmk.cli` is prepended before the module is imported. - -The module uses `@cli.entrypoint()` and `@cli.argument()` decorators to define an entrypoint, which is where execution starts. - -## Example CLI Script - -We have provided a QMK Hello World script you can use as an example. To run it simply run `qmk hello` or `qmk-hello`. The source code is listed below. - -``` -from milc import cli - -@cli.argument('-n', '--name', default='World', help='Name to greet.') -@cli.entrypoint('QMK Python Hello World.') -def main(cli): - cli.echo('Hello, %s!', cli.config.general.name) -``` diff --git a/docs/redirects.json b/docs/redirects.json index 814518f40..651148c2c 100644 --- a/docs/redirects.json +++ b/docs/redirects.json @@ -43,6 +43,10 @@ { "from": "unicode.html", "to": "feature_unicode.html" + }, + { + "from": "python_development.html", + "to": "cli_development.html" } ] } diff --git a/lib/python/milc.py b/lib/python/milc.py index c62c1b166..1a29bb25c 100644 --- a/lib/python/milc.py +++ b/lib/python/milc.py @@ -17,6 +17,7 @@ import argparse import logging import os import re +import shlex import sys from decimal import Decimal from tempfile import NamedTemporaryFile @@ -35,6 +36,10 @@ except ImportError: import argcomplete import colorama +from appdirs import user_config_dir + +# Disable logging until we can configure it how the user wants +logging.basicConfig(filename='/dev/null') # Log Level Representations EMOJI_LOGLEVELS = { @@ -47,6 +52,7 @@ EMOJI_LOGLEVELS = { } EMOJI_LOGLEVELS['FATAL'] = EMOJI_LOGLEVELS['CRITICAL'] EMOJI_LOGLEVELS['WARN'] = EMOJI_LOGLEVELS['WARNING'] +UNICODE_SUPPORT = sys.stdout.encoding.lower().startswith('utf') # ANSI Color setup # Regex was gratefully borrowed from kfir on stackoverflow: @@ -97,11 +103,12 @@ class ANSIFormatter(logging.Formatter): class ANSIEmojiLoglevelFormatter(ANSIFormatter): - """A log formatter that makes the loglevel an emoji. + """A log formatter that makes the loglevel an emoji on UTF capable terminals. """ def format(self, record): - record.levelname = EMOJI_LOGLEVELS[record.levelname].format(**ansi_colors) + if UNICODE_SUPPORT: + record.levelname = EMOJI_LOGLEVELS[record.levelname].format(**ansi_colors) return super(ANSIEmojiLoglevelFormatter, self).format(record) @@ -144,13 +151,15 @@ class Configuration(object): def __init__(self, *args, **kwargs): self._config = {} - self.default_container = ConfigurationOption + + def __getattr__(self, key): + return self.__getitem__(key) def __getitem__(self, key): """Returns a config section, creating it if it doesn't exist yet. """ if key not in self._config: - self.__dict__[key] = self._config[key] = ConfigurationOption() + self.__dict__[key] = self._config[key] = ConfigurationSection(self) return self._config[key] @@ -161,30 +170,34 @@ class Configuration(object): def __delitem__(self, key): if key in self.__dict__ and key[0] != '_': del self.__dict__[key] - del self._config[key] + if key in self._config: + del self._config[key] -class ConfigurationOption(Configuration): - def __init__(self, *args, **kwargs): - super(ConfigurationOption, self).__init__(*args, **kwargs) - self.default_container = dict +class ConfigurationSection(Configuration): + def __init__(self, parent, *args, **kwargs): + super(ConfigurationSection, self).__init__(*args, **kwargs) + self.parent = parent def __getitem__(self, key): - """Returns a config section, creating it if it doesn't exist yet. + """Returns a config value, pulling from the `user` section as a fallback. """ - if key not in self._config: - self.__dict__[key] = self._config[key] = None + if key in self._config: + return self._config[key] - return self._config[key] + elif key in self.parent.user: + return self.parent.user[key] + + return None def handle_store_boolean(self, *args, **kwargs): """Does the add_argument for action='store_boolean'. """ - kwargs['add_dest'] = False disabled_args = None disabled_kwargs = kwargs.copy() disabled_kwargs['action'] = 'store_false' + disabled_kwargs['dest'] = self.get_argument_name(*args, **kwargs) disabled_kwargs['help'] = 'Disable ' + kwargs['help'] kwargs['action'] = 'store_true' kwargs['help'] = 'Enable ' + kwargs['help'] @@ -219,11 +232,6 @@ class SubparserWrapper(object): self.subparser.completer = completer def add_argument(self, *args, **kwargs): - if kwargs.get('add_dest', True): - kwargs['dest'] = self.submodule + '_' + self.cli.get_argument_name(*args, **kwargs) - if 'add_dest' in kwargs: - del kwargs['add_dest'] - if 'action' in kwargs and kwargs['action'] == 'store_boolean': return handle_store_boolean(self, *args, **kwargs) @@ -254,12 +262,16 @@ class MILC(object): self._entrypoint = None self._inside_context_manager = False self.ansi = ansi_colors + self.arg_only = [] self.config = Configuration() self.config_file = None - self.prog_name = sys.argv[0][:-3] if sys.argv[0].endswith('.py') else sys.argv[0] self.version = os.environ.get('QMK_VERSION', 'unknown') self.release_lock() + # Figure out our program name + self.prog_name = sys.argv[0][:-3] if sys.argv[0].endswith('.py') else sys.argv[0] + self.prog_name = self.prog_name.split('/')[-1] + # Initialize all the things self.initialize_argparse() self.initialize_logging() @@ -273,7 +285,7 @@ class MILC(object): self._description = self._arg_parser.description = self._arg_defaults.description = value def echo(self, text, *args, **kwargs): - """Print colorized text to stdout, as long as stdout is a tty. + """Print colorized text to stdout. ANSI color strings (such as {fg-blue}) will be converted into ANSI escape sequences, and the ANSI reset sequence will be added to all @@ -284,11 +296,10 @@ class MILC(object): if args and kwargs: raise RuntimeError('You can only specify *args or **kwargs, not both!') - if sys.stdout.isatty(): - args = args or kwargs - text = format_ansi(text) + args = args or kwargs + text = format_ansi(text) - print(text % args) + print(text % args) def initialize_argparse(self): """Prepare to process arguments from sys.argv. @@ -313,21 +324,21 @@ class MILC(object): self.release_lock() def completer(self, completer): - """Add an arpcomplete completer to this subcommand. + """Add an argcomplete completer to this subcommand. """ self._arg_parser.completer = completer def add_argument(self, *args, **kwargs): """Wrapper to add arguments to both the main and the shadow argparser. """ + if 'action' in kwargs and kwargs['action'] == 'store_boolean': + return handle_store_boolean(self, *args, **kwargs) + if kwargs.get('add_dest', True) and args[0][0] == '-': kwargs['dest'] = 'general_' + self.get_argument_name(*args, **kwargs) if 'add_dest' in kwargs: del kwargs['add_dest'] - if 'action' in kwargs and kwargs['action'] == 'store_boolean': - return handle_store_boolean(self, *args, **kwargs) - self.acquire_lock() self._arg_parser.add_argument(*args, **kwargs) @@ -396,7 +407,7 @@ class MILC(object): if self.args and self.args.general_config_file: return self.args.general_config_file - return os.path.abspath(os.path.expanduser('~/.%s.ini' % self.prog_name)) + return os.path.join(user_config_dir(appname='qmk', appauthor='QMK'), '%s.ini' % self.prog_name) def get_argument_name(self, *args, **kwargs): """Takes argparse arguments and returns the dest name. @@ -413,6 +424,11 @@ class MILC(object): raise RuntimeError('You must run this before the with statement!') def argument_function(handler): + if 'arg_only' in kwargs and kwargs['arg_only']: + arg_name = self.get_argument_name(*args, **kwargs) + self.arg_only.append(arg_name) + del kwargs['arg_only'] + if handler is self._entrypoint: self.add_argument(*args, **kwargs) @@ -485,15 +501,20 @@ class MILC(object): if argument in ('subparsers', 'entrypoint'): continue - if '_' not in argument: - continue - - section, option = argument.split('_', 1) - if hasattr(self.args_passed, argument): - self.config[section][option] = getattr(self.args, argument) + if '_' in argument: + section, option = argument.split('_', 1) else: - if option not in self.config[section]: - self.config[section][option] = getattr(self.args, argument) + section = self._entrypoint.__name__ + option = argument + + if option not in self.arg_only: + if hasattr(self.args_passed, argument): + arg_value = getattr(self.args, argument) + if arg_value: + self.config[section][option] = arg_value + else: + if option not in self.config[section]: + self.config[section][option] = getattr(self.args, argument) self.release_lock() @@ -509,6 +530,8 @@ class MILC(object): self.acquire_lock() config = RawConfigParser() + config_dir = os.path.dirname(self.config_file) + for section_name, section in self.config._config.items(): config.add_section(section_name) for option_name, value in section.items(): @@ -517,7 +540,10 @@ class MILC(object): continue config.set(section_name, option_name, str(value)) - with NamedTemporaryFile(mode='w', dir=os.path.dirname(self.config_file), delete=False) as tmpfile: + if not os.path.exists(config_dir): + os.makedirs(config_dir) + + with NamedTemporaryFile(mode='w', dir=config_dir, delete=False) as tmpfile: config.write(tmpfile) # Move the new config file into place atomically @@ -527,6 +553,7 @@ class MILC(object): self.log.warning('Config file saving failed, not replacing %s with %s.', self.config_file, tmpfile.name) self.release_lock() + cli.log.info('Wrote configuration to %s', shlex.quote(self.config_file)) def __call__(self): """Execute the entrypoint function. @@ -602,8 +629,8 @@ class MILC(object): """Called by __enter__() to setup the logging configuration. """ if len(logging.root.handlers) != 0: - # This is not a design decision. This is what I'm doing for now until I can examine and think about this situation in more detail. - raise RuntimeError('MILC should be the only system installing root log handlers!') + # MILC is the only thing that should have root log handlers + logging.root.handlers = [] self.acquire_lock() @@ -648,8 +675,9 @@ class MILC(object): self.read_config() self.setup_logging() - if self.config.general.save_config: + if 'save_config' in self.config.general and self.config.general.save_config: self.save_config() + exit(0) return self @@ -712,4 +740,3 @@ if __name__ == '__main__': cli.goodbye.add_argument('-n', '--name', help='Name to bid farewell to', default='World') cli() # Automatically picks between main(), hello() and goodbye() - print(sorted(ansi_colors.keys())) diff --git a/lib/python/qmk/cli/__init__.py b/lib/python/qmk/cli/__init__.py index e69de29bb..fb4e0ecb4 100644 --- a/lib/python/qmk/cli/__init__.py +++ b/lib/python/qmk/cli/__init__.py @@ -0,0 +1,13 @@ +"""QMK CLI Subcommands + +We list each subcommand here explicitly because all the reliable ways of searching for modules are slow and delay startup. +""" +from . import cformat +from . import compile +from . import config +from . import doctor +from . import hello +from . import json +from . import new +from . import pyformat +from . import pytest diff --git a/lib/python/qmk/cli/cformat.py b/lib/python/qmk/cli/cformat.py index 91e650368..d2382bdbd 100644 --- a/lib/python/qmk/cli/cformat.py +++ b/lib/python/qmk/cli/cformat.py @@ -6,9 +6,9 @@ import subprocess from milc import cli -@cli.argument('files', nargs='*', help='Filename(s) to format.') -@cli.entrypoint("Format C code according to QMK's style.") -def main(cli): +@cli.argument('files', nargs='*', arg_only=True, help='Filename(s) to format.') +@cli.subcommand("Format C code according to QMK's style.") +def cformat(cli): """Format C code according to QMK's style. """ clang_format = ['clang-format', '-i'] diff --git a/lib/python/qmk/cli/compile.py b/lib/python/qmk/cli/compile.py index 7e14ad8fb..6646891b3 100755 --- a/lib/python/qmk/cli/compile.py +++ b/lib/python/qmk/cli/compile.py @@ -14,11 +14,11 @@ import qmk.keymap import qmk.path -@cli.argument('filename', nargs='?', type=FileType('r'), help='The configurator export to compile') +@cli.argument('filename', nargs='?', arg_only=True, type=FileType('r'), help='The configurator export to compile') @cli.argument('-kb', '--keyboard', help='The keyboard to build a firmware for. Ignored when a configurator export is supplied.') @cli.argument('-km', '--keymap', help='The keymap to build a firmware for. Ignored when a configurator export is supplied.') -@cli.entrypoint('Compile a QMK Firmware.') -def main(cli): +@cli.subcommand('Compile a QMK Firmware.') +def compile(cli): """Compile a QMK Firmware. If a Configurator export is supplied this command will create a new keymap, overwriting an existing keymap if one exists. @@ -41,9 +41,9 @@ def main(cli): # Compile the keymap command = ['make', ':'.join((user_keymap['keyboard'], user_keymap['keymap']))] - elif cli.config.general.keyboard and cli.config.general.keymap: + elif cli.config.compile.keyboard and cli.config.compile.keymap: # Generate the make command for a specific keyboard/keymap. - command = ['make', ':'.join((cli.config.general.keyboard, cli.config.general.keymap))] + command = ['make', ':'.join((cli.config.compile.keyboard, cli.config.compile.keymap))] else: cli.log.error('You must supply a configurator export or both `--keyboard` and `--keymap`.') diff --git a/lib/python/qmk/cli/config.py b/lib/python/qmk/cli/config.py new file mode 100644 index 000000000..d6c774e65 --- /dev/null +++ b/lib/python/qmk/cli/config.py @@ -0,0 +1,96 @@ +"""Read and write configuration settings +""" +import os +import subprocess + +from milc import cli + + +def print_config(section, key): + """Print a single config setting to stdout. + """ + cli.echo('%s.%s{fg_cyan}={fg_reset}%s', section, key, cli.config[section][key]) + + +@cli.argument('-ro', '--read-only', action='store_true', help='Operate in read-only mode.') +@cli.argument('configs', nargs='*', arg_only=True, help='Configuration options to read or write.') +@cli.subcommand("Read and write configuration settings.") +def config(cli): + """Read and write config settings. + + This script iterates over the config_tokens supplied as argument. Each config_token has the following form: + + section[.key][=value] + + If only a section (EG 'compile') is supplied all keys for that section will be displayed. + + If section.key is supplied the value for that single key will be displayed. + + If section.key=value is supplied the value for that single key will be set. + + If section.key=None is supplied the key will be deleted. + + No validation is done to ensure that the supplied section.key is actually used by qmk scripts. + """ + if not cli.args.configs: + # Walk the config tree + for section in cli.config: + for key in cli.config[section]: + print_config(section, key) + + return True + + # Process config_tokens + save_config = False + + for argument in cli.args.configs: + # Split on space in case they quoted multiple config tokens + for config_token in argument.split(' '): + # Extract the section, config_key, and value to write from the supplied config_token. + if '=' in config_token: + key, value = config_token.split('=') + else: + key = config_token + value = None + + if '.' in key: + section, config_key = key.split('.', 1) + else: + section = key + config_key = None + + # Validation + if config_key and '.' in config_key: + cli.log.error('Config keys may not have more than one period! "%s" is not valid.', key) + return False + + # Do what the user wants + if section and config_key and value: + # Write a config key + log_string = '%s.%s{fg_cyan}:{fg_reset} %s {fg_cyan}->{fg_reset} %s' + if cli.args.read_only: + log_string += ' {fg_red}(change not written)' + + cli.echo(log_string, section, config_key, cli.config[section][config_key], value) + + if not cli.args.read_only: + if value == 'None': + del cli.config[section][config_key] + else: + cli.config[section][config_key] = value + save_config = True + + elif section and config_key: + # Display a single key + print_config(section, config_key) + + elif section: + # Display an entire section + for key in cli.config[section]: + print_config(section, key) + + # Ending actions + if save_config: + cli.save_config() + + return True diff --git a/lib/python/qmk/cli/doctor.py b/lib/python/qmk/cli/doctor.py index 5a713b20f..3474422a8 100755 --- a/lib/python/qmk/cli/doctor.py +++ b/lib/python/qmk/cli/doctor.py @@ -11,8 +11,8 @@ from glob import glob from milc import cli -@cli.entrypoint('Basic QMK environment checks') -def main(cli): +@cli.subcommand('Basic QMK environment checks') +def doctor(cli): """Basic QMK environment checks. This is currently very simple, it just checks that all the expected binaries are on your system. @@ -36,6 +36,7 @@ def main(cli): else: try: subprocess.run([binary, '--version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=5, check=True) + cli.log.info('Found {fg_cyan}%s', binary) except subprocess.CalledProcessError: cli.log.error("{fg_red}Can't run `%s --version`", binary) ok = False diff --git a/lib/python/qmk/cli/hello.py b/lib/python/qmk/cli/hello.py index bc0cb6de1..bee28c301 100755 --- a/lib/python/qmk/cli/hello.py +++ b/lib/python/qmk/cli/hello.py @@ -6,8 +6,8 @@ from milc import cli @cli.argument('-n', '--name', default='World', help='Name to greet.') -@cli.entrypoint('QMK Hello World.') -def main(cli): +@cli.subcommand('QMK Hello World.') +def hello(cli): """Log a friendly greeting. """ - cli.log.info('Hello, %s!', cli.config.general.name) + cli.log.info('Hello, %s!', cli.config.hello.name) diff --git a/lib/python/qmk/cli/json/__init__.py b/lib/python/qmk/cli/json/__init__.py index e69de29bb..f4ebfc45b 100644 --- a/lib/python/qmk/cli/json/__init__.py +++ b/lib/python/qmk/cli/json/__init__.py @@ -0,0 +1,5 @@ +"""QMK CLI JSON Subcommands + +We list each subcommand here explicitly because all the reliable ways of searching for modules are slow and delay startup. +""" +from . import keymap diff --git a/lib/python/qmk/cli/json/keymap.py b/lib/python/qmk/cli/json/keymap.py index e2d0b5809..a65acd619 100755 --- a/lib/python/qmk/cli/json/keymap.py +++ b/lib/python/qmk/cli/json/keymap.py @@ -9,10 +9,10 @@ from milc import cli import qmk.keymap -@cli.argument('-o', '--output', help='File to write to') -@cli.argument('filename', help='Configurator JSON file') -@cli.entrypoint('Create a keymap.c from a QMK Configurator export.') -def main(cli): +@cli.argument('-o', '--output', arg_only=True, help='File to write to') +@cli.argument('filename', arg_only=True, help='Configurator JSON file') +@cli.subcommand('Create a keymap.c from a QMK Configurator export.') +def json_keymap(cli): """Generate a keymap.c from a configurator export. This command uses the `qmk.keymap` module to generate a keymap.c from a configurator export. The generated keymap is written to stdout, or to a file if -o is provided. @@ -28,8 +28,8 @@ def main(cli): exit(1) # Environment processing - if cli.config.general.output == ('-'): - cli.config.general.output = None + if cli.args.output == ('-'): + cli.args.output = None # Parse the configurator json with open(qmk.path.normpath(cli.args.filename), 'r') as fd: @@ -38,17 +38,17 @@ def main(cli): # Generate the keymap keymap_c = qmk.keymap.generate(user_keymap['keyboard'], user_keymap['layout'], user_keymap['layers']) - if cli.config.general.output: - output_dir = os.path.dirname(cli.config.general.output) + if cli.args.output: + output_dir = os.path.dirname(cli.args.output) if not os.path.exists(output_dir): os.makedirs(output_dir) - output_file = qmk.path.normpath(cli.config.general.output) + output_file = qmk.path.normpath(cli.args.output) with open(output_file, 'w') as keymap_fd: keymap_fd.write(keymap_c) - cli.log.info('Wrote keymap to %s.', cli.config.general.output) + cli.log.info('Wrote keymap to %s.', cli.args.output) else: print(keymap_c) diff --git a/lib/python/qmk/cli/new/__init__.py b/lib/python/qmk/cli/new/__init__.py index e69de29bb..c6a26939b 100644 --- a/lib/python/qmk/cli/new/__init__.py +++ b/lib/python/qmk/cli/new/__init__.py @@ -0,0 +1 @@ +from . import keymap diff --git a/lib/python/qmk/cli/new/keymap.py b/lib/python/qmk/cli/new/keymap.py index b378e5ab4..5efb81c93 100755 --- a/lib/python/qmk/cli/new/keymap.py +++ b/lib/python/qmk/cli/new/keymap.py @@ -6,15 +6,15 @@ import shutil from milc import cli -@cli.argument('-k', '--keyboard', help='Specify keyboard name. Example: 1upkeyboards/1up60hse') -@cli.argument('-u', '--username', help='Specify any name for the new keymap directory') -@cli.entrypoint('Creates a new keymap for the keyboard of your choosing') -def main(cli): +@cli.argument('-kb', '--keyboard', help='Specify keyboard name. Example: 1upkeyboards/1up60hse') +@cli.argument('-km', '--keymap', help='Specify the name for the new keymap directory') +@cli.subcommand('Creates a new keymap for the keyboard of your choosing') +def new_keymap(cli): """Creates a new keymap for the keyboard of your choosing. """ # ask for user input if keyboard or username was not provided in the command line - keyboard = cli.config.general.keyboard if cli.config.general.keyboard else input("Keyboard Name: ") - username = cli.config.general.username if cli.config.general.username else input("Username: ") + keyboard = cli.config.new_keymap.keyboard if cli.config.new_keymap.keyboard else input("Keyboard Name: ") + keymap = cli.config.new_keymap.keymap if cli.config.new_keymap.keymap else input("Keymap Name: ") # generate keymap paths kb_path = os.path.join(os.getcwd(), "keyboards", keyboard) @@ -36,6 +36,5 @@ def main(cli): shutil.copytree(keymap_path_default, keymap_path, symlinks=True) # end message to user - cli.log.info("%s keymap directory created in: %s\n" + - "Compile a firmware file with your new keymap by typing: \n" + - "qmk compile -kb %s -km %s", username, keymap_path, keyboard, username) + cli.log.info("%s keymap directory created in: %s", username, keymap_path) + cli.log.info("Compile a firmware with your new keymap by typing: \n" + "qmk compile -kb %s -km %s", keyboard, username) diff --git a/lib/python/qmk/cli/nose2.py b/lib/python/qmk/cli/nose2.py deleted file mode 100644 index c6c9c67b3..000000000 --- a/lib/python/qmk/cli/nose2.py +++ /dev/null @@ -1,18 +0,0 @@ -"""QMK Python Unit Tests - -QMK script to run unit and integration tests against our python code. -""" -from milc import cli - - -@cli.entrypoint('QMK Python Unit Tests') -def main(cli): - """Use nose2 to run unittests - """ - try: - import nose2 - except ImportError: - cli.log.error('Could not import nose2! Please install it with {fg_cyan}pip3 install nose2') - return False - - nose2.discover() diff --git a/lib/python/qmk/cli/pyformat.py b/lib/python/qmk/cli/pyformat.py index b1f8c02b2..a53ba40c0 100755 --- a/lib/python/qmk/cli/pyformat.py +++ b/lib/python/qmk/cli/pyformat.py @@ -5,12 +5,13 @@ from milc import cli import subprocess -@cli.entrypoint("Format python code according to QMK's style.") -def main(cli): +@cli.subcommand("Format python code according to QMK's style.") +def pyformat(cli): """Format python code according to QMK's style. """ try: subprocess.run(['yapf', '-vv', '-ri', 'bin/qmk', 'lib/python'], check=True) cli.log.info('Successfully formatted the python code in `bin/qmk` and `lib/python`.') + except subprocess.CalledProcessError: cli.log.error('Error formatting python code!') diff --git a/lib/python/qmk/cli/pytest.py b/lib/python/qmk/cli/pytest.py new file mode 100644 index 000000000..14613e1d9 --- /dev/null +++ b/lib/python/qmk/cli/pytest.py @@ -0,0 +1,20 @@ +"""QMK Python Unit Tests + +QMK script to run unit and integration tests against our python code. +""" +import sys +from milc import cli + + +@cli.subcommand('QMK Python Unit Tests') +def pytest(cli): + """Use nose2 to run unittests + """ + try: + import nose2 + + except ImportError: + cli.log.error('Could not import nose2! Please install it with {fg_cyan}pip3 install nose2') + return False + + nose2.discover(argv=['nose2', '-v']) diff --git a/lib/python/qmk/path.py b/lib/python/qmk/path.py index cf087265f..2149625cc 100644 --- a/lib/python/qmk/path.py +++ b/lib/python/qmk/path.py @@ -2,6 +2,7 @@ """ import logging import os +from pkgutil import walk_packages from qmk.errors import NoSuchKeyboardError diff --git a/lib/python/qmk/tests/test_cli_commands.py b/lib/python/qmk/tests/test_cli_commands.py new file mode 100644 index 000000000..2fc6e0f72 --- /dev/null +++ b/lib/python/qmk/tests/test_cli_commands.py @@ -0,0 +1,39 @@ +import subprocess + + +def check_subcommand(command, *args): + cmd = ['bin/qmk', command] + list(args) + return subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True) + + +def test_cformat(): + assert check_subcommand('cformat', 'tmk_core/common/backlight.c').returncode == 0 + + +def test_compile(): + assert check_subcommand('compile', '-kb', 'handwired/onekey/pytest', '-km', 'default').returncode == 0 + + +def test_config(): + result = check_subcommand('config') + assert result.returncode == 0 + assert 'general.color' in result.stdout + + +def test_doctor(): + result = check_subcommand('doctor') + assert result.returncode == 0 + assert 'QMK Doctor is checking your environment.' in result.stderr + assert 'QMK is ready to go' in result.stderr + + +def test_hello(): + result = check_subcommand('hello') + assert result.returncode == 0 + assert 'Hello,' in result.stderr + + +def test_pyformat(): + result = check_subcommand('pyformat') + assert result.returncode == 0 + assert 'Successfully formatted the python code' in result.stderr diff --git a/requirements.txt b/requirements.txt index 351dc2524..f6257e399 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ # Python requirements # milc FIXME(skullydazed): Included in the repo for now. +appdirs argcomplete colorama -#halo diff --git a/util/travis_build.sh b/util/travis_build.sh index 605b6d5f0..2bc1ccd62 100755 --- a/util/travis_build.sh +++ b/util/travis_build.sh @@ -18,11 +18,12 @@ if [[ "$TRAVIS_COMMIT_MESSAGE" != *"[skip build]"* ]] ; then exit_code=0 git diff --name-only -n 1 ${TRAVIS_COMMIT_RANGE} if [ $? -eq 128 ]; then - echo "Making default keymaps for all keyboards" + # We don't know what changed so just build the default keymaps + echo "Making default keymaps for all keyboards (fallback)" eval $MAKE_ALL : $((exit_code = $exit_code + $?)) else - NEFM=$(git diff --name-only -n 1 ${TRAVIS_COMMIT_RANGE} | grep -Ev '^(keyboards/)' | grep -Ev '^(docs/)' | grep -Ev '^(lib/python/)' | grep -Ev '(^bin/qmk)' | wc -l) + NEFM=$(git diff --name-only -n 1 ${TRAVIS_COMMIT_RANGE} | grep -Ev '^(keyboards/)' | grep -Ev '^(docs/)' | grep -Ev '^(lib/python/)' | grep -Ev '^(bin/qmk)' | grep -Ev '^(requirements.txt)' | grep -Ev '^(util/)' | wc -l) BRANCH=$(git rev-parse --abbrev-ref HEAD) # is this branch master or a "non docs, non keyboards" change if [ $NEFM -gt 0 -o "$BRANCH" = "master" ]; then @@ -56,7 +57,7 @@ if [[ "$TRAVIS_COMMIT_MESSAGE" != *"[skip build]"* ]] ; then if [ $PFM -gt 0 -o "$BRANCH" = "master" ]; then echo echo "Running python tests." - docker run --rm -w /qmk_firmware/ -v "$PWD":/qmk_firmware --user $(id -u):$(id -g) qmkfm/base_container bin/qmk nose2 + docker run --rm -w /qmk_firmware/ -v "$PWD":/qmk_firmware --user $(id -u):$(id -g) qmkfm/base_container 'bin/qmk pytest' : $((exit_code = $exit_code + $?)) fi fi -- cgit v1.2.3-70-g09d2 From c47fa31a0031a911eb4d97a043fe32792a57eba6 Mon Sep 17 00:00:00 2001 From: fauxpark Date: Tue, 1 Oct 2019 10:45:44 +1000 Subject: Port drivers.txt changes from the Toolbox (#6786) --- util/drivers.txt | 37 ++----------------------------------- 1 file changed, 2 insertions(+), 35 deletions(-) (limited to 'util') diff --git a/util/drivers.txt b/util/drivers.txt index 4e5439c39..71d99eaa9 100644 --- a/util/drivers.txt +++ b/util/drivers.txt @@ -3,45 +3,12 @@ # Use a comma as a separator without spaces # Driver can be one of winusb,libusb,libusbk # Use Windows Powershell and type [guid]::NewGuid() to generate guids +winusb,STM32 Bootloader,0483,DF11,6d98a87f-4ecf-464d-89ed-8c684d857a75 +libusb,USBaspLoader,16C0,05DC,e69affdc-0ef0-427c-aefb-4e593c9d2724 winusb,Kiibohd DFU Bootloader,1C11,B007,aa5a3f86-b81e-4416-89ad-0c1ea1ed63af -winusb,STM32 Bootloader,0483,df11,6d98a87f-4ecf-464d-89ed-8c684d857a75 -libusb,ATxmega16C4,03EB,2FD8,23266ee7-5423-4cc4-993b-034571c43a90 -libusb,ATxmega32C4,03EB,2FD9,d4b62886-2ac8-4534-aa24-eae0a2c3ce43 -libusb,ATxmega64C3,03EB,2FD6,08467ca7-9b5a-41d2-8d8a-4a26d0b5285b -libusb,ATxmega128C3,03EB,2FD7,1ca69799-6d95-46cf-be69-5b3d0eb915e6 -libusb,ATxmega256C3,03EB,2FDA,216ddc8b-6c67-4cc0-b934-482829a483a0 -libusb,ATxmega384C3,03EB,2FDB,0e4e3347-6025-4d49-ba80-2375ea690c28 -libusb,ATxmega64A1U,03EB,2FE8,2553d8fa-7de1-44a6-bdbf-57be8bb37e28 -libusb,ATxmega128A1U,03EB,2FED,6d9fd0ff-755d-4e29-bd29-df0a9a7544b9 -libusb,ATxmega64A4U,03EB,2FDD,bcf5e7c3-44a1-4fd1-971f-9ef9843f6291 -libusb,ATxmega128A4U,03EB,2FDE,3f976bb6-36ca-44cc-a728-844bc1d0d168 -libusb,ATxmega64B3,03EB,2FDF,de280c81-c12a-4ca7-bf34-566151786418 -libusb,ATxmega128B3,03EB,2FE0,2ad1ffeb-eb83-4e78-b34a-d5633771991f -libusb,ATxmega64B1,03EB,2FE1,002874a6-7fc7-413b-9ac4-2b52c5a230bd -libusb,ATxmega128B1,03EB,2FEA,60ea9d08-2ae6-4434-b743-ce6f73537136 -libusb,ATxmega256A3BU,03EB,2FE2,5949bd0a-8bd4-417b-b1c5-7d249836bf0d -libusb,ATxmega16A4U,03EB,2FE3,cc3172b0-e86a-4758-914e-951bca6ca7f5 -libusb,ATxmega32A4U,03EB,2FE4,f44c515f-7d17-4612-a532-ee620afb22b2 -libusb,ATxmega64A4U,03EB,2FE5,c1af4f1c-045f-40c9-893a-3ad4adb2e67d -libusb,ATxmega128A3U,03EB,2FE6,26f275f0-d6b2-46d8-8334-e4de66996c74 -libusb,ATxmega192A3U,03EB,2FE7,b7b50d98-0429-4235-8f08-5466e4f83ed4 -libusb,UC3,03EB,2FDC,972d9af7-d71b-44c7-a895-9340b362f545 -libusb,ATUC3,03EB,2FE9,d5855d0a-f82e-4df5-9c14-2b0b1dcb65bd -libusb,AT32UC3C,03EB,2FEB,1eeb52aa-fd24-47fd-8a76-056446d1a54f -libusb,ATxmega256A3U,03EB,2FEC,198fa8ea-3157-4863-b9a8-a3f6fe027367 -libusb,ATmega8U2,03EB,2FEE,14018055-46f4-4c62-aa03-e8fafeedaf72 libusb,ATmega16U2,03EB,2FEF,007274da-b75f-492e-a288-8fc0aff8339f libusb,ATmega32U2,03EB,2FF0,ddc2c572-cb6e-4f61-a6cc-1a5de941f063 -libusb,AT32UC3A3,03EB,2FF1,8b614283-36c0-46a2-890d-65f61b5b6201 -libusb,ATmega32U6,03EB,2FF2,a207dd90-2814-4418-b5b7-4b708fdf1bfd libusb,ATmega16U4,03EB,2FF3,3180d426-bf93-4578-a693-2efbc337da8e libusb,ATmega32U4,03EB,2FF4,5f9726fd-f9de-487a-9fbd-8b3524a7a56a -libusb,AT32UC3B,03EB,2FF6,ef90068a-277a-44db-805a-9b83a6beb29a -libusb,AT90USB82,03EB,2FF7,062fa2ab-f9d8-4a0d-83c1-df0521cfd0f6 -libusb,AT32UC3A,03EB,2FF8,24080a67-3874-4fb8-8808-fb4cc297c466 libusb,AT90USB64,03EB,2FF9,c6a708ad-e97d-43cd-b04a-3180d737a71b -libusb,AT90USB162,03EB,2FFA,de67bff5-6e39-4e9c-8dfe-de7fce113716 libusb,AT90USB128,03EB,2FFB,fd217df3-59d0-440a-a8f3-4c0c8c84daa3 -libusb,AT89C5130,03EB,2FFD,31b69a56-9ac0-4fab-a3ae-cd7bb7021ec5 -libusb,AT8XC5122,03EB,2FFE,395a6118-8568-41b2-913a-d16912722342 -libusb,AT89C5132,03EB,2FFF,266ca4bc-5e59-4a7b-82dc-6e8732373d40 -- cgit v1.2.3-70-g09d2 From ed1bf3afa25d7e7674df7e8618dfaf243de3058b Mon Sep 17 00:00:00 2001 From: fauxpark Date: Thu, 10 Oct 2019 21:48:37 +1100 Subject: Prevent clang-format messing up placeholder tokens within keyboard templates (#6790) * Use .template file extension for keyboard template files * Filter out .template files completely before passing to clang-format * Undo file extension stuff; just ignore quantum/template dir --- quantum/template/avr/config.h | 10 ++- quantum/template/avr/keyboard.c | 51 ++++++++++++++ quantum/template/avr/template.c | 51 -------------- quantum/template/base/keyboard.h | 35 ++++++++++ quantum/template/base/keymaps/default/keymap.c | 23 +++++-- quantum/template/base/keymaps/default/readme.md | 2 +- quantum/template/base/template.h | 29 -------- quantum/template/ps2avrgb/config.h | 11 ++- quantum/template/ps2avrgb/keyboard.c | 90 +++++++++++++++++++++++++ quantum/template/ps2avrgb/template.c | 90 ------------------------- util/new_keyboard.sh | 4 +- util/travis_compiled_push.sh | 2 +- 12 files changed, 205 insertions(+), 193 deletions(-) create mode 100644 quantum/template/avr/keyboard.c delete mode 100644 quantum/template/avr/template.c create mode 100644 quantum/template/base/keyboard.h delete mode 100644 quantum/template/base/template.h create mode 100644 quantum/template/ps2avrgb/keyboard.c delete mode 100644 quantum/template/ps2avrgb/template.c (limited to 'util') diff --git a/quantum/template/avr/config.h b/quantum/template/avr/config.h index 713d6be3a..304a54ae5 100644 --- a/quantum/template/avr/config.h +++ b/quantum/template/avr/config.h @@ -23,8 +23,8 @@ along with this program. If not, see . #define VENDOR_ID 0xFEED #define PRODUCT_ID 0x0000 #define DEVICE_VER 0x0001 -#define MANUFACTURER % YOUR_NAME % -#define PRODUCT % KEYBOARD % +#define MANUFACTURER %YOUR_NAME% +#define PRODUCT %KEYBOARD% #define DESCRIPTION A custom keyboard /* key matrix size */ @@ -41,10 +41,8 @@ along with this program. If not, see . * ROW2COL = ROW = Anode (+), COL = Cathode (-, marked on diode) * */ -#define MATRIX_ROW_PINS \ - { D0, D5 } -#define MATRIX_COL_PINS \ - { F1, F0, B0 } +#define MATRIX_ROW_PINS { D0, D5 } +#define MATRIX_COL_PINS { F1, F0, B0 } #define UNUSED_PINS /* COL2ROW, ROW2COL*/ diff --git a/quantum/template/avr/keyboard.c b/quantum/template/avr/keyboard.c new file mode 100644 index 000000000..e852a42c4 --- /dev/null +++ b/quantum/template/avr/keyboard.c @@ -0,0 +1,51 @@ +/* Copyright %YEAR% %YOUR_NAME% + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "%KEYBOARD%.h" + +// Optional override functions below. +// You can leave any or all of these undefined. +// These are only required if you want to perform custom actions. + +/* + +void matrix_init_kb(void) { + // put your keyboard start-up code here + // runs once when the firmware starts up + + matrix_init_user(); +} + +void matrix_scan_kb(void) { + // put your looping keyboard code here + // runs every cycle (a lot) + + matrix_scan_user(); +} + +bool process_record_kb(uint16_t keycode, keyrecord_t *record) { + // put your per-action keyboard code here + // runs for every action, just before processing by the firmware + + return process_record_user(keycode, record); +} + +void led_set_kb(uint8_t usb_led) { + // put your keyboard LED indicator (ex: Caps Lock LED) toggling code here + + led_set_user(usb_led); +} + +*/ diff --git a/quantum/template/avr/template.c b/quantum/template/avr/template.c deleted file mode 100644 index e852a42c4..000000000 --- a/quantum/template/avr/template.c +++ /dev/null @@ -1,51 +0,0 @@ -/* Copyright %YEAR% %YOUR_NAME% - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#include "%KEYBOARD%.h" - -// Optional override functions below. -// You can leave any or all of these undefined. -// These are only required if you want to perform custom actions. - -/* - -void matrix_init_kb(void) { - // put your keyboard start-up code here - // runs once when the firmware starts up - - matrix_init_user(); -} - -void matrix_scan_kb(void) { - // put your looping keyboard code here - // runs every cycle (a lot) - - matrix_scan_user(); -} - -bool process_record_kb(uint16_t keycode, keyrecord_t *record) { - // put your per-action keyboard code here - // runs for every action, just before processing by the firmware - - return process_record_user(keycode, record); -} - -void led_set_kb(uint8_t usb_led) { - // put your keyboard LED indicator (ex: Caps Lock LED) toggling code here - - led_set_user(usb_led); -} - -*/ diff --git a/quantum/template/base/keyboard.h b/quantum/template/base/keyboard.h new file mode 100644 index 000000000..2e531b1fd --- /dev/null +++ b/quantum/template/base/keyboard.h @@ -0,0 +1,35 @@ +/* Copyright %YEAR% %YOUR_NAME% + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once + +#include "quantum.h" + +/* This a shortcut to help you visually see your layout. + * + * The first section contains all of the arguments representing the physical + * layout of the board and position of the keys. + * + * The second converts the arguments into a two-dimensional array which + * represents the switch matrix. + */ +#define LAYOUT( \ + k00, k01, k02, \ + k10, k11 \ +) \ +{ \ + { k00, k01, k02 }, \ + { k10, KC_NO, k11 }, \ +} diff --git a/quantum/template/base/keymaps/default/keymap.c b/quantum/template/base/keymaps/default/keymap.c index 308cb92a7..3508055b7 100644 --- a/quantum/template/base/keymaps/default/keymap.c +++ b/quantum/template/base/keymaps/default/keymap.c @@ -16,11 +16,16 @@ #include QMK_KEYBOARD_H // Defines the keycodes used by our macros in process_record_user -enum custom_keycodes { QMKBEST = SAFE_RANGE, QMKURL }; +enum custom_keycodes { + QMKBEST = SAFE_RANGE, + QMKURL +}; const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = { - [0] = LAYOUT(/* Base */ - KC_A, KC_1, KC_H, KC_TAB, KC_SPC), + [0] = LAYOUT( /* Base */ + KC_A, KC_1, KC_H, + KC_TAB, KC_SPC + ), }; bool process_record_user(uint16_t keycode, keyrecord_t *record) { @@ -45,8 +50,14 @@ bool process_record_user(uint16_t keycode, keyrecord_t *record) { return true; } -void matrix_init_user(void) {} +void matrix_init_user(void) { + +} -void matrix_scan_user(void) {} +void matrix_scan_user(void) { -void led_set_user(uint8_t usb_led) {} +} + +void led_set_user(uint8_t usb_led) { + +} diff --git a/quantum/template/base/keymaps/default/readme.md b/quantum/template/base/keymaps/default/readme.md index 21aa663d5..e052ed80f 100644 --- a/quantum/template/base/keymaps/default/readme.md +++ b/quantum/template/base/keymaps/default/readme.md @@ -1 +1 @@ -# The default keymap for %KEYBOARD% \ No newline at end of file +# The default keymap for %KEYBOARD% diff --git a/quantum/template/base/template.h b/quantum/template/base/template.h deleted file mode 100644 index 595da73c6..000000000 --- a/quantum/template/base/template.h +++ /dev/null @@ -1,29 +0,0 @@ -/* Copyright %YEAR% %YOUR_NAME% - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#pragma once - -#include "quantum.h" - -/* This a shortcut to help you visually see your layout. - * - * The first section contains all of the arguments representing the physical - * layout of the board and position of the keys. - * - * The second converts the arguments into a two-dimensional array which - * represents the switch matrix. - */ -#define LAYOUT(k00, k01, k02, k10, k11) \ - { {k00, k01, k02}, {k10, KC_NO, k11}, } diff --git a/quantum/template/ps2avrgb/config.h b/quantum/template/ps2avrgb/config.h index a780a10af..f6d7c25e0 100644 --- a/quantum/template/ps2avrgb/config.h +++ b/quantum/template/ps2avrgb/config.h @@ -23,7 +23,7 @@ along with this program. If not, see . #define PRODUCT_ID 0x422D #define DEVICE_VER 0x0001 #define MANUFACTURER You -#define PRODUCT % KEYBOARD % +#define PRODUCT %KEYBOARD% #define DESCRIPTION A custom keyboard #define RGBLED_NUM 16 @@ -31,13 +31,10 @@ along with this program. If not, see . #define MATRIX_ROWS 8 #define MATRIX_COLS 11 -#define MATRIX_ROW_PINS \ - { B0, B1, B2, B3, B4, B5, B6, B7 } -#define MATRIX_COL_PINS \ - { A0, A1, A2, A3, A4, A5, A6, A7, C7, C6, C5 } +#define MATRIX_ROW_PINS { B0, B1, B2, B3, B4, B5, B6, B7 } +#define MATRIX_COL_PINS { A0, A1, A2, A3, A4, A5, A6, A7, C7, C6, C5 } // #define MATRIX_COL_PINS { A0, A1, A2, A3, A4, A5, A6, A7, C7, C6, C5, C4, C3, C2, C1, C0, D7 } -#define UNUSED_PINS \ - {} +#define UNUSED_PINS {} #define DIODE_DIRECTION COL2ROW #define DEBOUNCE 5 diff --git a/quantum/template/ps2avrgb/keyboard.c b/quantum/template/ps2avrgb/keyboard.c new file mode 100644 index 000000000..efc851748 --- /dev/null +++ b/quantum/template/ps2avrgb/keyboard.c @@ -0,0 +1,90 @@ +/* Copyright %YEAR% %YOUR_NAME% + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "%KEYBOARD%.h" + +#ifdef RGBLIGHT_ENABLE + +# include +# include "i2c_master.h" +# include "rgblight.h" + +extern rgblight_config_t rgblight_config; + +void matrix_init_kb(void) { + i2c_init(); + // call user level keymaps, if any + matrix_init_user(); +} + +// custom RGB driver +void rgblight_set(void) { + if (!rgblight_config.enable) { + memset(led, 0, 3 * RGBLED_NUM); + } + + i2c_transmit(0xb0, (uint8_t*)led, 3 * RGBLED_NUM, 100); +} + +bool rgb_init = false; + +void matrix_scan_kb(void) { + // if LEDs were previously on before poweroff, turn them back on + if (rgb_init == false && rgblight_config.enable) { + i2c_transmit(0xb0, (uint8_t*)led, 3 * RGBLED_NUM, 100); + rgb_init = true; + } + + rgblight_task(); + matrix_scan_user(); +} + +#endif + +// Optional override functions below. +// You can leave any or all of these undefined. +// These are only required if you want to perform custom actions. + +/* + +void matrix_init_kb(void) { + // put your keyboard start-up code here + // runs once when the firmware starts up + + matrix_init_user(); +} + +void matrix_scan_kb(void) { + // put your looping keyboard code here + // runs every cycle (a lot) + + matrix_scan_user(); +} + +bool process_record_kb(uint16_t keycode, keyrecord_t *record) { + // put your per-action keyboard code here + // runs for every action, just before processing by the firmware + + return process_record_user(keycode, record); +} + +void led_set_kb(uint8_t usb_led) { + // put your keyboard LED indicator (ex: Caps Lock LED) toggling code here + + led_set_user(usb_led); +} + +*/ diff --git a/quantum/template/ps2avrgb/template.c b/quantum/template/ps2avrgb/template.c deleted file mode 100644 index efc851748..000000000 --- a/quantum/template/ps2avrgb/template.c +++ /dev/null @@ -1,90 +0,0 @@ -/* Copyright %YEAR% %YOUR_NAME% - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include "%KEYBOARD%.h" - -#ifdef RGBLIGHT_ENABLE - -# include -# include "i2c_master.h" -# include "rgblight.h" - -extern rgblight_config_t rgblight_config; - -void matrix_init_kb(void) { - i2c_init(); - // call user level keymaps, if any - matrix_init_user(); -} - -// custom RGB driver -void rgblight_set(void) { - if (!rgblight_config.enable) { - memset(led, 0, 3 * RGBLED_NUM); - } - - i2c_transmit(0xb0, (uint8_t*)led, 3 * RGBLED_NUM, 100); -} - -bool rgb_init = false; - -void matrix_scan_kb(void) { - // if LEDs were previously on before poweroff, turn them back on - if (rgb_init == false && rgblight_config.enable) { - i2c_transmit(0xb0, (uint8_t*)led, 3 * RGBLED_NUM, 100); - rgb_init = true; - } - - rgblight_task(); - matrix_scan_user(); -} - -#endif - -// Optional override functions below. -// You can leave any or all of these undefined. -// These are only required if you want to perform custom actions. - -/* - -void matrix_init_kb(void) { - // put your keyboard start-up code here - // runs once when the firmware starts up - - matrix_init_user(); -} - -void matrix_scan_kb(void) { - // put your looping keyboard code here - // runs every cycle (a lot) - - matrix_scan_user(); -} - -bool process_record_kb(uint16_t keycode, keyrecord_t *record) { - // put your per-action keyboard code here - // runs for every action, just before processing by the firmware - - return process_record_user(keycode, record); -} - -void led_set_kb(uint8_t usb_led) { - // put your keyboard LED indicator (ex: Caps Lock LED) toggling code here - - led_set_user(usb_led); -} - -*/ diff --git a/util/new_keyboard.sh b/util/new_keyboard.sh index 35d89e402..fe7c1a829 100755 --- a/util/new_keyboard.sh +++ b/util/new_keyboard.sh @@ -41,8 +41,8 @@ copy_templates() { echo " done" echo -n "Renaming keyboard files..." - mv "${keyboard_dir}/template.c" "${keyboard_dir}/${keyboard_name}.c" - mv "${keyboard_dir}/template.h" "${keyboard_dir}/${keyboard_name}.h" + mv "${keyboard_dir}/keyboard.c" "${keyboard_dir}/${keyboard_name}.c" + mv "${keyboard_dir}/keyboard.h" "${keyboard_dir}/${keyboard_name}.h" echo " done" } diff --git a/util/travis_compiled_push.sh b/util/travis_compiled_push.sh index 4737d693b..8ca65f21a 100755 --- a/util/travis_compiled_push.sh +++ b/util/travis_compiled_push.sh @@ -12,7 +12,7 @@ if [[ "$TRAVIS_BRANCH" == "master" && "$TRAVIS_PULL_REQUEST" == "false" ]] ; the # fix formatting git checkout master git diff --diff-filter=AM --name-only -n 1 -z ${TRAVIS_COMMIT_RANGE} | xargs -0 dos2unix -git diff --diff-filter=AM --name-only -n 1 -z ${TRAVIS_COMMIT_RANGE} | grep -e '^drivers' -e '^quantum' -e '^tests' -e '^tmk_core' | xargs -0 clang-format +git diff --diff-filter=AM --name-only -n 1 -z ${TRAVIS_COMMIT_RANGE} | grep -e '^drivers' -e '^quantum' -e '^tests' -e '^tmk_core' | grep -v 'quantum/template' | xargs -0 clang-format git diff --diff-filter=AM --name-only -n 1 -z ${TRAVIS_COMMIT_RANGE} | xargs -0 git add git commit -m "format code according to conventions [skip ci]" && git push git@github.com:qmk/qmk_firmware.git master -- cgit v1.2.3-70-g09d2 From e1de0d74a6f6c4bdc762b32fb78e449aed0fcecb Mon Sep 17 00:00:00 2001 From: Joel Challis Date: Mon, 14 Oct 2019 17:57:53 +0100 Subject: Move running pytest to travis_test (#7005) --- util/travis_build.sh | 10 +--------- util/travis_test.sh | 36 ++++++++++++++++++++++++++---------- 2 files changed, 27 insertions(+), 19 deletions(-) (limited to 'util') diff --git a/util/travis_build.sh b/util/travis_build.sh index 2bc1ccd62..225c8548f 100755 --- a/util/travis_build.sh +++ b/util/travis_build.sh @@ -1,6 +1,6 @@ #!/bin/bash -# if docker is installed - call make within the qmk docker image +# if docker is installed - patch calls to within the qmk docker image if command -v docker >/dev/null; then function make() { docker run --rm -e MAKEFLAGS="$MAKEFLAGS" -w /qmk_firmware/ -v "$PWD":/qmk_firmware --user $(id -u):$(id -g) qmkfm/base_container make "$@" @@ -52,14 +52,6 @@ if [[ "$TRAVIS_COMMIT_MESSAGE" != *"[skip build]"* ]] ; then fi done fi - # Check and run python tests if necessary - PFM=$(git diff --name-only -n 1 ${TRAVIS_COMMIT_RANGE} | grep -E -e '^(lib/python/)' -e '^(bin/qmk)' | wc -l) - if [ $PFM -gt 0 -o "$BRANCH" = "master" ]; then - echo - echo "Running python tests." - docker run --rm -w /qmk_firmware/ -v "$PWD":/qmk_firmware --user $(id -u):$(id -g) qmkfm/base_container 'bin/qmk pytest' - : $((exit_code = $exit_code + $?)) - fi fi exit $exit_code fi diff --git a/util/travis_test.sh b/util/travis_test.sh index e6a50ac16..9b7402c28 100644 --- a/util/travis_test.sh +++ b/util/travis_test.sh @@ -1,29 +1,45 @@ #!/bin/bash +# if docker is installed - patch calls to within the qmk docker image +if command -v docker >/dev/null; then + function make() { + docker run --rm -e MAKEFLAGS="$MAKEFLAGS" -w /qmk_firmware/ -v "$PWD":/qmk_firmware --user $(id -u):$(id -g) qmkfm/base_container make "$@" + } + function qmk() { + docker run --rm -w /qmk_firmware/ -v "$PWD":/qmk_firmware --user $(id -u):$(id -g) qmkfm/base_container bin/qmk "$@" + } +fi + TRAVIS_COMMIT_MESSAGE="${TRAVIS_COMMIT_MESSAGE:-none}" TRAVIS_COMMIT_RANGE="${TRAVIS_COMMIT_RANGE:-HEAD~1..HEAD}" # test force push #TRAVIS_COMMIT_RANGE="c287f1bfc5c8...81f62atc4c1d" -NUM_IMPACTING_CHANGES=$(git diff --name-only -n 1 ${TRAVIS_COMMIT_RANGE} | grep -Ecv '^(docs/)') BRANCH=$(git rev-parse --abbrev-ref HEAD) +CHANGES=$(git diff --name-only -n 1 ${TRAVIS_COMMIT_RANGE}) + +NUM_CORE_CHANGES=$(echo "$CHANGES" | grep -Ecv -e '^(docs/)' -e '^(keyboards/)' -e '^(layouts/)') +NUM_PY_CHANGES=$(echo "$CHANGES" | grep -Ec -e '^(lib/python/)' -e '^(bin/qmk)') if [[ "$TRAVIS_COMMIT_MESSAGE" == *"[skip test]"* ]]; then echo "Skipping due to commit message" exit 0 fi -if [ "$BRANCH" != "master" ] && [ "$NUM_IMPACTING_CHANGES" == "0" ]; then - echo "Skipping due to changes not impacting tests" - exit 0 +exit_code=0 + +if [ "$BRANCH" == "master" ] || [ "$NUM_CORE_CHANGES" != "0" ]; then + echo "Running tests." + make test:all + : $((exit_code = $exit_code + $?)) + fi -# if docker is installed - call make within the qmk docker image -if command -v docker >/dev/null; then - function make() { - docker run --rm -e MAKEFLAGS="$MAKEFLAGS" -w /qmk_firmware/ -v "$PWD":/qmk_firmware --user $(id -u):$(id -g) qmkfm/base_container make "$@" - } +if [ "$BRANCH" == "master" ] || [ "$NUM_PY_CHANGES" != "0" ]; then + echo "Running python tests." + qmk pytest + : $((exit_code = $exit_code + $?)) fi -make test:all +exit $exit_code -- cgit v1.2.3-70-g09d2 From 5c1b7fb5021646d05014a89ec1b8ccf458e4f50e Mon Sep 17 00:00:00 2001 From: Yang Li Date: Sat, 19 Oct 2019 08:22:54 +0800 Subject: Add python-pip as package dependency for archlinux (#7041) --- util/linux_install.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'util') diff --git a/util/linux_install.sh b/util/linux_install.sh index dca0110a5..c54a80623 100755 --- a/util/linux_install.sh +++ b/util/linux_install.sh @@ -77,6 +77,7 @@ elif grep ID /etc/os-release | grep -q 'arch\|manjaro'; then gcc \ git \ python \ + python-pip \ unzip \ wget \ zip @@ -213,4 +214,4 @@ else fi # Global install tasks -pip3 install -r ${util_dir}/../requirements.txt +pip3 install --user -r ${util_dir}/../requirements.txt -- cgit v1.2.3-70-g09d2 From b23f6011c34dcb471c312655f7af37c0a0f5f779 Mon Sep 17 00:00:00 2001 From: Amber Holly Date: Sat, 19 Oct 2019 02:14:49 +0100 Subject: Remove build option firmware size impacts (#6947) * Update rules.mk template to remove build option size impacts * Add rules.mk cleaning script * Update all rules.mk files to remove build option firmware size impact messages * Remove references to feature filesize in documentation * Revert "Update all rules.mk files to remove build option firmware size impact messages" This reverts commit 7cfe70976bcc223bf47c960b2e6af8596df80a32. * Fix regex in cleanup script and exclude keymaps/ directories * Update quantum/template/avr/rules.mk Fixed missing tabs/spaces. Co-Authored-By: fauxpark --- docs/config_options.md | 10 +++++----- docs/feature_hd44780.md | 8 ++++---- docs/feature_terminal.md | 4 ++-- docs/getting_started_make_guide.md | 8 +------- quantum/template/avr/rules.mk | 13 ++++++------- util/rules_cleaner.sh | 40 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 58 insertions(+), 25 deletions(-) create mode 100755 util/rules_cleaner.sh (limited to 'util') diff --git a/docs/config_options.md b/docs/config_options.md index ec3d1a1c8..abd98ec8a 100644 --- a/docs/config_options.md +++ b/docs/config_options.md @@ -310,13 +310,13 @@ This is a [make](https://www.gnu.org/software/make/manual/make.html) file that i Use these to enable or disable building certain features. The more you have enabled the bigger your firmware will be, and you run the risk of building a firmware too large for your MCU. * `BOOTMAGIC_ENABLE` - * Virtual DIP switch configuration(+1000) + * Virtual DIP switch configuration * `MOUSEKEY_ENABLE` - * Mouse keys(+4700) + * Mouse keys * `EXTRAKEY_ENABLE` - * Audio control and System control(+450) + * Audio control and System control * `CONSOLE_ENABLE` - * Console for debug(+400) + * Console for debug * `COMMAND_ENABLE` * Commands for debug and configuration * `COMBO_ENABLE` @@ -348,7 +348,7 @@ Use these to enable or disable building certain features. The more you have enab * `NO_USB_STARTUP_CHECK` * Disables usb suspend check after keyboard startup. Usually the keyboard waits for the host to wake it up before any tasks are performed. This is useful for split keyboards as one half will not get a wakeup call but must send commands to the master. * `LINK_TIME_OPTIMIZATION_ENABLE` - = Enables Link Time Optimization (`LTO`) when compiling the keyboard. This makes the process take longer, but can significantly reduce the compiled size (and since the firmware is small, the added time is not noticable). However, this will automatically disable the old Macros and Functions features automatically, as these break when `LTO` is enabled. It does this by automatically defining `NO_ACTION_MACRO` and `NO_ACTION_FUNCTION` + = Enables Link Time Optimization (`LTO`) when compiling the keyboard. This makes the process take longer, but can significantly reduce the compiled size (and since the firmware is small, the added time is not noticable). However, this will automatically disable the old Macros and Functions features automatically, as these break when `LTO` is enabled. It does this by automatically defining `NO_ACTION_MACRO` and `NO_ACTION_FUNCTION` ## USB Endpoint Limitations diff --git a/docs/feature_hd44780.md b/docs/feature_hd44780.md index e0838948a..0a174035b 100644 --- a/docs/feature_hd44780.md +++ b/docs/feature_hd44780.md @@ -2,7 +2,7 @@ This is an integration of Peter Fleury's LCD library. This page will explain the basics. [For in depth documentation visit his page.](http://homepage.hispeed.ch/peterfleury/doxygen/avr-gcc-libraries/group__pfleury__lcd.html) -You can enable support for HD44780 Displays by setting the `HD44780_ENABLE` flag in your keyboards `rules.mk` to yes. This will use about 400 KB of extra space. +You can enable support for HD44780 Displays by setting the `HD44780_ENABLE` flag in your keyboards `rules.mk` to yes. ## Configuration @@ -26,7 +26,7 @@ Uncomment the section labled HD44780 and change the parameters as needed. #define LCD_DATA3_PORT LCD_PORT //< port for 4bit data bit 3 #define LCD_DATA0_PIN 4 //< pin for 4bit data bit 0 #define LCD_DATA1_PIN 5 //< pin for 4bit data bit 1 -#define LCD_DATA2_PIN 6 //< pin for 4bit data bit 2 +#define LCD_DATA2_PIN 6 //< pin for 4bit data bit 2 #define LCD_DATA3_PIN 7 //< pin for 4bit data bit 3 #define LCD_RS_PORT LCD_PORT //< port for RS line #define LCD_RS_PIN 3 //< pin for RS line @@ -39,14 +39,14 @@ Uncomment the section labled HD44780 and change the parameters as needed. Should you need to configure other properties you can copy them from `quantum/hd44780.h` and set them in your `config.h` -## Usage +## Usage To initialize your display, call `lcd_init()` with one of these parameters: ```` LCD_DISP_OFF : display off LCD_DISP_ON : display on, cursor off LCD_DISP_ON_CURSOR : display on, cursor on -LCD_DISP_ON_CURSOR_BLINK : display on, cursor on flashing +LCD_DISP_ON_CURSOR_BLINK : display on, cursor on flashing ```` This is best done in your keyboards `matrix_init_kb` or your keymaps `matrix_init_user`. It is advised to clear the display before use. diff --git a/docs/feature_terminal.md b/docs/feature_terminal.md index 1863599f8..f85062216 100644 --- a/docs/feature_terminal.md +++ b/docs/feature_terminal.md @@ -1,6 +1,6 @@ # Terminal -> This feature is currently *huge* at 4400 bytes, and should probably only be put on boards with a lot of memory, or for fun. +> This feature is currently *huge*, and should probably only be put on boards with a lot of memory, or for fun. The terminal feature is a command-line-like interface designed to communicate through a text editor with keystrokes. It's beneficial to turn off auto-indent features in your editor. @@ -56,7 +56,7 @@ Outputs the last 5 commands entered 1. help 2. about 3. keymap 0 -4. help +4. help 5. flush-buffer ``` diff --git a/docs/getting_started_make_guide.md b/docs/getting_started_make_guide.md index 71e9d33f2..d1ecf6f5a 100644 --- a/docs/getting_started_make_guide.md +++ b/docs/getting_started_make_guide.md @@ -41,8 +41,6 @@ Set these variables to `no` to disable them, and `yes` to enable them. This allows you to hold a key and the salt key (space by default) and have access to a various EEPROM settings that persist over power loss. It's advised you keep this disabled, as the settings are often changed by accident, and produce confusing results that makes it difficult to debug. It's one of the more common problems encountered in help sessions. -Consumes about 1000 bytes. - `MOUSEKEY_ENABLE` This gives you control over cursor movements and clicks via keycodes/custom functions. @@ -67,8 +65,6 @@ To see the text, open `hid_listen` and enjoy looking at your printed messages. **NOTE:** Do not include *uprint* messages in anything other than your keymap code. It must not be used within the QMK system framework. Otherwise, you will bloat other people's .hex files. -Consumes about 400 bytes. - `COMMAND_ENABLE` This enables magic commands, typically fired with the default magic key combo `LSHIFT+RSHIFT+KEY`. Magic commands include turning on debugging messages (`MAGIC+D`) or temporarily toggling NKRO (`MAGIC+N`). @@ -125,11 +121,9 @@ Use this to debug changes to variable values, see the [tracing variables](unit_t This enables using the Quantum SYSEX API to send strings (somewhere?) -This consumes about 5390 bytes. - `KEY_LOCK_ENABLE` -This enables [key lock](feature_key_lock.md). This consumes an additional 260 bytes. +This enables [key lock](feature_key_lock.md). `SPLIT_KEYBOARD` diff --git a/quantum/template/avr/rules.mk b/quantum/template/avr/rules.mk index 0ed05e71b..bb2bd14f9 100644 --- a/quantum/template/avr/rules.mk +++ b/quantum/template/avr/rules.mk @@ -23,10 +23,10 @@ BOOTLOADER = atmel-dfu # Build Options # change yes to no to disable # -BOOTMAGIC_ENABLE = no # Virtual DIP switch configuration(+1000) -MOUSEKEY_ENABLE = yes # Mouse keys(+4700) -EXTRAKEY_ENABLE = yes # Audio control and System control(+450) -CONSOLE_ENABLE = yes # Console for debug(+400) +BOOTMAGIC_ENABLE = no # Virtual DIP switch configuration +MOUSEKEY_ENABLE = yes # Mouse keys +EXTRAKEY_ENABLE = yes # Audio control and System control +CONSOLE_ENABLE = yes # Console for debug COMMAND_ENABLE = yes # Commands for debug and configuration # Do not enable SLEEP_LED_ENABLE. it uses the same timer as BACKLIGHT_ENABLE SLEEP_LED_ENABLE = no # Breathing sleep LED during USB suspend @@ -34,9 +34,8 @@ SLEEP_LED_ENABLE = no # Breathing sleep LED during USB suspend NKRO_ENABLE = no # USB Nkey Rollover BACKLIGHT_ENABLE = no # Enable keyboard backlight functionality RGBLIGHT_ENABLE = no # Enable keyboard RGB underglow -MIDI_ENABLE = no # MIDI support (+2400 to 4200, depending on config) -UNICODE_ENABLE = no # Unicode +MIDI_ENABLE = no # MIDI support BLUETOOTH_ENABLE = no # Enable Bluetooth with the Adafruit EZ-Key HID AUDIO_ENABLE = no # Audio output on port C6 FAUXCLICKY_ENABLE = no # Use buzzer to emulate clicky switches -HD44780_ENABLE = no # Enable support for HD44780 based LCDs (+400) +HD44780_ENABLE = no # Enable support for HD44780 based LCDs diff --git a/util/rules_cleaner.sh b/util/rules_cleaner.sh new file mode 100755 index 000000000..ac27c2b09 --- /dev/null +++ b/util/rules_cleaner.sh @@ -0,0 +1,40 @@ +#!/bin/bash + +# This script finds all rules.mk files in keyboards/ subdirectories, +# and deletes the build option filesize impacts from them. + +# Print an error message with the word "ERROR" in red. +echo_error() { + echo -e "[\033[0;91mERROR\033[m]: $1" +} + +# If we've been started from util/, we want to be in qmk_firmware/ +[[ "$PWD" == *util ]] && cd .. + +# The root qmk_firmware/ directory should have a subdirectory called quantum/ +if [ ! -d "quantum" ]; then + echo_error "Could not detect the QMK firmware directory!" + echo_error "Are you sure you're in the right place?" + exit 1 +fi + +# Set the inplace editing parameter for sed. +# macOS/BSD sed expects a file extension immediately following -i. +set_sed_i() { + sed_i=(-i) + + case $(uname -a) in + *Darwin*) sed_i=(-i "") + esac +} +set_sed_i + +# Exclude keyamps/ directories +files=$(find keyboards -type f -name 'rules.mk' -not \( -path '*/keymaps*' -prune \)) + +# Edit rules.mk files +for file in $files; do + sed "${sed_i[@]}" -e "s/(+[0-9].*)$//g" "$file" +done + +echo "Cleaned up rules.mk files." -- cgit v1.2.3-70-g09d2 From 917ab71c52111c5ca89382eba74e08e616264d59 Mon Sep 17 00:00:00 2001 From: Yadunand Prem Date: Tue, 29 Oct 2019 09:13:07 +1100 Subject: Clean macos install script (#7186) --- util/macos_install.sh | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) (limited to 'util') diff --git a/util/macos_install.sh b/util/macos_install.sh index d9ec50a7e..c5c27d167 100755 --- a/util/macos_install.sh +++ b/util/macos_install.sh @@ -2,20 +2,20 @@ util_dir=$(dirname "$0") -if ! brew --version 2>&1 > /dev/null; then +if ! brew --version >/dev/null 2>&1; then echo "Error! Homebrew not installed or broken!" echo -n "Would you like to install homebrew now? [y/n] " - while read ANSWER; do + while read -r ANSWER; do case $ANSWER in - y|Y) - /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" - break + y | Y) + /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" + break ;; - n|N) - exit 1 + n | N) + exit 1 ;; - *) - echo -n "Would you like to install homebrew now? [y/n] " + *) + echo -n "Would you like to install homebrew now? [y/n] " ;; esac done @@ -26,4 +26,4 @@ brew tap PX4/homebrew-px4 brew update brew install avr-gcc@8 gcc-arm-none-eabi dfu-programmer avrdude clang-format dfu-util python3 brew link --force avr-gcc@8 -pip3 install -r ${util_dir}/../requirements.txt +pip3 install -r "${util_dir}/../requirements.txt" -- cgit v1.2.3-70-g09d2 From e6cc9cc78d40af1386f9a116d5fcc95c20233110 Mon Sep 17 00:00:00 2001 From: fauxpark Date: Sat, 2 Nov 2019 13:06:03 +1100 Subject: Template updates (#7221) * Template updates * Flesh out info.json & keymap.c, and turn double spaces section into a list * Add enum to demonstrate layer naming * Semicolon --- quantum/template/avr/keyboard.c | 51 ------------------------- quantum/template/avr/readme.md | 6 +-- quantum/template/avr/rules.mk | 1 + quantum/template/base/info.json | 18 +++++++++ quantum/template/base/keyboard.c | 50 +++++++++++++++++++++++++ quantum/template/base/keyboard.h | 7 ++-- quantum/template/base/keymaps/default/keymap.c | 19 ++++++++-- quantum/template/ps2avrgb/keyboard.c | 52 -------------------------- quantum/template/ps2avrgb/readme.md | 6 +-- quantum/template/ps2avrgb/rules.mk | 18 +++++---- util/new_keyboard.sh | 2 + 11 files changed, 107 insertions(+), 123 deletions(-) delete mode 100644 quantum/template/avr/keyboard.c create mode 100644 quantum/template/base/keyboard.c delete mode 100644 quantum/template/ps2avrgb/keyboard.c (limited to 'util') diff --git a/quantum/template/avr/keyboard.c b/quantum/template/avr/keyboard.c deleted file mode 100644 index e852a42c4..000000000 --- a/quantum/template/avr/keyboard.c +++ /dev/null @@ -1,51 +0,0 @@ -/* Copyright %YEAR% %YOUR_NAME% - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#include "%KEYBOARD%.h" - -// Optional override functions below. -// You can leave any or all of these undefined. -// These are only required if you want to perform custom actions. - -/* - -void matrix_init_kb(void) { - // put your keyboard start-up code here - // runs once when the firmware starts up - - matrix_init_user(); -} - -void matrix_scan_kb(void) { - // put your looping keyboard code here - // runs every cycle (a lot) - - matrix_scan_user(); -} - -bool process_record_kb(uint16_t keycode, keyrecord_t *record) { - // put your per-action keyboard code here - // runs for every action, just before processing by the firmware - - return process_record_user(keycode, record); -} - -void led_set_kb(uint8_t usb_led) { - // put your keyboard LED indicator (ex: Caps Lock LED) toggling code here - - led_set_user(usb_led); -} - -*/ diff --git a/quantum/template/avr/readme.md b/quantum/template/avr/readme.md index 719222d37..0430dd0fc 100644 --- a/quantum/template/avr/readme.md +++ b/quantum/template/avr/readme.md @@ -4,9 +4,9 @@ A short description of the keyboard/project -Keyboard Maintainer: [%YOUR_NAME%](https://github.com/yourusername) -Hardware Supported: The PCBs, controllers supported -Hardware Availability: links to where you can find this hardware +* Keyboard Maintainer: [%YOUR_NAME%](https://github.com/yourusername) +* Hardware Supported: The PCBs, controllers supported +* Hardware Availability: Links to where you can find this hardware Make example for this keyboard (after setting up your build environment): diff --git a/quantum/template/avr/rules.mk b/quantum/template/avr/rules.mk index bb2bd14f9..ea0c83c68 100644 --- a/quantum/template/avr/rules.mk +++ b/quantum/template/avr/rules.mk @@ -13,6 +13,7 @@ BOOTLOADER = atmel-dfu # If you don't know the bootloader type, then you can specify the # Boot Section Size in *bytes* by uncommenting out the OPT_DEFS line +# Otherwise, delete this section # Teensy halfKay 512 # Teensy++ halfKay 1024 # Atmel DFU loader 4096 diff --git a/quantum/template/base/info.json b/quantum/template/base/info.json index e69de29bb..b8e8f1f2d 100644 --- a/quantum/template/base/info.json +++ b/quantum/template/base/info.json @@ -0,0 +1,18 @@ +{ + "keyboard_name": "%KEYBOARD%", + "url": "", + "maintainer": "%YOUR_NAME%", + "width": 3, + "height": 2, + "layouts": { + "LAYOUT": { + "layout": [ + {"label":"k00", "x":0, "y":0}, + {"label":"k01", "x":1, "y":0}, + {"label":"k02", "x":2, "y":0}, + {"label":"k10", "x":0, "y":1, "w":1.5}, + {"label":"k12", "x":1.5, "y":1, "w":1.5} + ] + } + } +} diff --git a/quantum/template/base/keyboard.c b/quantum/template/base/keyboard.c new file mode 100644 index 000000000..55e4fffd3 --- /dev/null +++ b/quantum/template/base/keyboard.c @@ -0,0 +1,50 @@ +/* Copyright %YEAR% %YOUR_NAME% + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "%KEYBOARD%.h" + +// Optional override functions below. +// You can leave any or all of these undefined. +// These are only required if you want to perform custom actions. + +/* +void matrix_init_kb(void) { + // put your keyboard start-up code here + // runs once when the firmware starts up + + matrix_init_user(); +} + +void matrix_scan_kb(void) { + // put your looping keyboard code here + // runs every cycle (a lot) + + matrix_scan_user(); +} + +bool process_record_kb(uint16_t keycode, keyrecord_t *record) { + // put your per-action keyboard code here + // runs for every action, just before processing by the firmware + + return process_record_user(keycode, record); +} + +void led_set_kb(uint8_t usb_led) { + // put your keyboard LED indicator (ex: Caps Lock LED) toggling code here + + led_set_user(usb_led); +} +*/ diff --git a/quantum/template/base/keyboard.h b/quantum/template/base/keyboard.h index 2e531b1fd..8a21d9257 100644 --- a/quantum/template/base/keyboard.h +++ b/quantum/template/base/keyboard.h @@ -13,11 +13,12 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ + #pragma once #include "quantum.h" -/* This a shortcut to help you visually see your layout. +/* This is a shortcut to help you visually see your layout. * * The first section contains all of the arguments representing the physical * layout of the board and position of the keys. @@ -27,9 +28,9 @@ */ #define LAYOUT( \ k00, k01, k02, \ - k10, k11 \ + k10, k12 \ ) \ { \ { k00, k01, k02 }, \ - { k10, KC_NO, k11 }, \ + { k10, KC_NO, k12 } \ } diff --git a/quantum/template/base/keymaps/default/keymap.c b/quantum/template/base/keymaps/default/keymap.c index 3508055b7..4d5bac7b2 100644 --- a/quantum/template/base/keymaps/default/keymap.c +++ b/quantum/template/base/keymaps/default/keymap.c @@ -15,6 +15,12 @@ */ #include QMK_KEYBOARD_H +// Defines names for use in layer keycodes and the keymap +enum layer_names { + _BASE, + _FN +}; + // Defines the keycodes used by our macros in process_record_user enum custom_keycodes { QMKBEST = SAFE_RANGE, @@ -22,10 +28,15 @@ enum custom_keycodes { }; const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = { - [0] = LAYOUT( /* Base */ - KC_A, KC_1, KC_H, - KC_TAB, KC_SPC + /* Base */ + [_BASE] = LAYOUT( + KC_A, KC_1, MO(_FN), + KC_TAB, KC_SPC ), + [_FN] = LAYOUT( + QMKBEST, QMKURL, _______, + RESET, XXXXXXX + ) }; bool process_record_user(uint16_t keycode, keyrecord_t *record) { @@ -50,6 +61,7 @@ bool process_record_user(uint16_t keycode, keyrecord_t *record) { return true; } +/* void matrix_init_user(void) { } @@ -61,3 +73,4 @@ void matrix_scan_user(void) { void led_set_user(uint8_t usb_led) { } +*/ diff --git a/quantum/template/ps2avrgb/keyboard.c b/quantum/template/ps2avrgb/keyboard.c deleted file mode 100644 index 73cd668f8..000000000 --- a/quantum/template/ps2avrgb/keyboard.c +++ /dev/null @@ -1,52 +0,0 @@ -/* Copyright %YEAR% %YOUR_NAME% - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include "%KEYBOARD%.h" - -// Optional override functions below. -// You can leave any or all of these undefined. -// These are only required if you want to perform custom actions. - -/* - -void matrix_init_kb(void) { - // put your keyboard start-up code here - // runs once when the firmware starts up - - matrix_init_user(); -} - -void matrix_scan_kb(void) { - // put your looping keyboard code here - // runs every cycle (a lot) - - matrix_scan_user(); -} - -bool process_record_kb(uint16_t keycode, keyrecord_t *record) { - // put your per-action keyboard code here - // runs for every action, just before processing by the firmware - - return process_record_user(keycode, record); -} - -void led_set_kb(uint8_t usb_led) { - // put your keyboard LED indicator (ex: Caps Lock LED) toggling code here - - led_set_user(usb_led); -} - -*/ diff --git a/quantum/template/ps2avrgb/readme.md b/quantum/template/ps2avrgb/readme.md index 3dce0e950..b45ef91c9 100644 --- a/quantum/template/ps2avrgb/readme.md +++ b/quantum/template/ps2avrgb/readme.md @@ -4,9 +4,9 @@ A short description of the keyboard/project -Keyboard Maintainer: [%YOUR_NAME%](https://github.com/yourusername) -Hardware Supported: The PCBs, controllers supported -Hardware Availability: links to where you can find this hardware +* Keyboard Maintainer: [%YOUR_NAME%](https://github.com/yourusername) +* Hardware Supported: The PCBs, controllers supported +* Hardware Availability: links to where you can find this hardware Make example for this keyboard (after setting up your build environment): diff --git a/quantum/template/ps2avrgb/rules.mk b/quantum/template/ps2avrgb/rules.mk index ec57b03dc..b9b81a675 100644 --- a/quantum/template/ps2avrgb/rules.mk +++ b/quantum/template/ps2avrgb/rules.mk @@ -11,14 +11,16 @@ MCU = atmega32a # ATmega328P USBasp BOOTLOADER = bootloadHID -# build options -BOOTMAGIC_ENABLE = no -MOUSEKEY_ENABLE = no -EXTRAKEY_ENABLE = yes -CONSOLE_ENABLE = yes -COMMAND_ENABLE = yes -BACKLIGHT_ENABLE = no -RGBLIGHT_ENABLE = yes +# Build Options +# change yes to no to disable +# +BOOTMAGIC_ENABLE = no # Virtual DIP switch configuration +MOUSEKEY_ENABLE = no # Mouse keys +EXTRAKEY_ENABLE = yes # Audio control and System control +CONSOLE_ENABLE = yes # Console for debug +COMMAND_ENABLE = yes # Commands for debug and configuration +BACKLIGHT_ENABLE = no # Enable keyboard backlight functionality +RGBLIGHT_ENABLE = yes # Enable keyboard RGB underglow WS2812_DRIVER = i2c OPT_DEFS = -DDEBUG_LEVEL=0 diff --git a/util/new_keyboard.sh b/util/new_keyboard.sh index fe7c1a829..11c6497e2 100755 --- a/util/new_keyboard.sh +++ b/util/new_keyboard.sh @@ -86,6 +86,7 @@ replace_year_placeholders() { replace_keyboard_placeholders() { local replace_keyboard_filenames=( "${keyboard_dir}/config.h" + "${keyboard_dir}/info.json" "${keyboard_dir}/readme.md" "${keyboard_dir}/${keyboard_name}.c" "${keyboard_dir}/keymaps/default/readme.md" @@ -97,6 +98,7 @@ replace_keyboard_placeholders() { replace_name_placeholders() { local replace_name_filenames=( "${keyboard_dir}/config.h" + "${keyboard_dir}/info.json" "${keyboard_dir}/readme.md" "${keyboard_dir}/${keyboard_name}.c" "${keyboard_dir}/${keyboard_name}.h" -- cgit v1.2.3-70-g09d2 From d382eeeb9d3b795c16533c5f64ab1455e774f925 Mon Sep 17 00:00:00 2001 From: Joel Challis Date: Tue, 5 Nov 2019 00:18:57 +0000 Subject: CI - Fix change detection (#7038) * Fix travis change detection * Fix travis change detection - add back python ignore --- .travis.yml | 1 + util/travis_build.sh | 79 ++++++++++++++++++-------------------------- util/travis_compiled_push.sh | 1 + util/travis_docs.sh | 1 + util/travis_push.sh | 5 --- util/travis_test.sh | 27 +++------------ util/travis_utils.sh | 24 ++++++++++++++ 7 files changed, 64 insertions(+), 74 deletions(-) create mode 100755 util/travis_utils.sh (limited to 'util') diff --git a/.travis.yml b/.travis.yml index 84fa63faf..bfac998c1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,6 +16,7 @@ install: - npm install -g moxygen script: - git rev-parse --short HEAD + - git diff --name-only HEAD $TRAVIS_BRANCH - bash util/travis_test.sh - bash util/travis_build.sh - bash util/travis_docs.sh diff --git a/util/travis_build.sh b/util/travis_build.sh index 225c8548f..6d5dbed68 100755 --- a/util/travis_build.sh +++ b/util/travis_build.sh @@ -1,57 +1,42 @@ #!/bin/bash -# if docker is installed - patch calls to within the qmk docker image -if command -v docker >/dev/null; then - function make() { - docker run --rm -e MAKEFLAGS="$MAKEFLAGS" -w /qmk_firmware/ -v "$PWD":/qmk_firmware --user $(id -u):$(id -g) qmkfm/base_container make "$@" - } +source util/travis_utils.sh + +NUM_CORE_CHANGES=$(echo "$QMK_CHANGES" | grep -Ecv -e '^(docs/)' -e '^(keyboards/)' -e '^(layouts/)' -e '^(util/)' -e '^(lib/python/)' -e '^(bin/qmk)' -e '^(requirements.txt)' -e '(.travis.yml)') + +if [[ "$TRAVIS_COMMIT_MESSAGE" == *"[skip build]"* ]]; then + echo "Skipping due to commit message" + exit 0 fi -# test force push -#TRAVIS_COMMIT_RANGE="c287f1bfc5c8...81f62atc4c1d" +if [ "$LOCAL_BRANCH" == "master" ] || [ "$NUM_CORE_CHANGES" != "0" ]; then + echo "Making default keymaps for all keyboards" + make all:default + exit $? +fi -TRAVIS_COMMIT_MESSAGE="${TRAVIS_COMMIT_MESSAGE:-none}" -TRAVIS_COMMIT_RANGE="${TRAVIS_COMMIT_RANGE:-HEAD~1..HEAD}" -MAKE_ALL="make all:default" +exit_code=0 -if [[ "$TRAVIS_COMMIT_MESSAGE" != *"[skip build]"* ]] ; then - exit_code=0 - git diff --name-only -n 1 ${TRAVIS_COMMIT_RANGE} - if [ $? -eq 128 ]; then - # We don't know what changed so just build the default keymaps - echo "Making default keymaps for all keyboards (fallback)" - eval $MAKE_ALL +for KB in $(make list-keyboards); do + KEYBOARD_CHANGES=$(echo "$QMK_CHANGES" | grep -E '^(keyboards/'${KB}'/)') + if [[ -z "$KEYBOARD_CHANGES" ]]; then + # skip as no changes for this keyboard + continue + fi + + KEYMAP_ONLY=$(echo "$KEYBOARD_CHANGES" | grep -cv /keymaps/) + if [[ $KEYMAP_ONLY -gt 0 ]]; then + echo "Making all keymaps for $KB" + make ${KB}:all : $((exit_code = $exit_code + $?)) else - NEFM=$(git diff --name-only -n 1 ${TRAVIS_COMMIT_RANGE} | grep -Ev '^(keyboards/)' | grep -Ev '^(docs/)' | grep -Ev '^(lib/python/)' | grep -Ev '^(bin/qmk)' | grep -Ev '^(requirements.txt)' | grep -Ev '^(util/)' | wc -l) - BRANCH=$(git rev-parse --abbrev-ref HEAD) - # is this branch master or a "non docs, non keyboards" change - if [ $NEFM -gt 0 -o "$BRANCH" = "master" ]; then - echo "Making default keymaps for all keyboards" - eval $MAKE_ALL + CHANGED_KEYMAPS=$(echo "$KEYBOARD_CHANGES" | grep -oP '(?<=keyboards/'${KB}'/keymaps/)([a-zA-Z0-9_-]+)(?=\/)') + for KM in $CHANGED_KEYMAPS ; do + echo "Making $KM for $KB" + make ${KB}:${KM} : $((exit_code = $exit_code + $?)) - else - # keyboards project format - # /keyboards/board1/rev/keymaps/ - # /keyboards/board2/keymaps/ - # ensure we strip everything off after and including the keymaps folder to get board and/or revision - MKB=$(git diff --name-only -n 1 ${TRAVIS_COMMIT_RANGE} | grep -oP '(?<=keyboards\/)([a-zA-Z0-9_\/]+)(?=\/)' | sed 's^/keymaps/.*^^' | sort -u) - for KB in $MKB ; do - KEYMAP_ONLY=$(git diff --name-only -n 1 ${TRAVIS_COMMIT_RANGE} | grep -Ev '^(keyboards/'${KB}'/keymaps/)' | wc -l) - if [[ $KEYMAP_ONLY -gt 0 ]]; then - echo "Making all keymaps for $KB" - make ${KB}:all - : $((exit_code = $exit_code + $?)) - else - MKM=$(git diff --name-only -n 1 ${TRAVIS_COMMIT_RANGE} | grep -oP '(?<=keyboards/'${KB}'/keymaps/)([a-zA-Z0-9_]+)(?=\/)' | sort -u) - for KM in $MKM ; do - echo "Making $KM for $KB" - make ${KB}:${KM} - : $((exit_code = $exit_code + $?)) - done - fi - done - fi + done fi - exit $exit_code -fi +done + +exit $exit_code diff --git a/util/travis_compiled_push.sh b/util/travis_compiled_push.sh index 8ca65f21a..e0490cd70 100755 --- a/util/travis_compiled_push.sh +++ b/util/travis_compiled_push.sh @@ -1,5 +1,6 @@ #!/bin/bash +source util/travis_utils.sh source util/travis_push.sh set -o errexit -o nounset diff --git a/util/travis_docs.sh b/util/travis_docs.sh index 5cfd63466..93c2d7186 100755 --- a/util/travis_docs.sh +++ b/util/travis_docs.sh @@ -1,5 +1,6 @@ #!/bin/bash +source util/travis_utils.sh source util/travis_push.sh if [[ "$TRAVIS_COMMIT_MESSAGE" != *"[skip docs]"* ]] ; then diff --git a/util/travis_push.sh b/util/travis_push.sh index d100f9d48..7f7d4c2c6 100755 --- a/util/travis_push.sh +++ b/util/travis_push.sh @@ -1,10 +1,5 @@ # Use this by sourcing it in your script. -TRAVIS_BRANCH="${TRAVIS_BRANCH:master}" -TRAVIS_PULL_REQUEST="${TRAVIS_PULL_REQUEST:false}" -TRAVIS_COMMIT_MESSAGE="${TRAVIS_COMMIT_MESSAGE:-none}" -TRAVIS_COMMIT_RANGE="${TRAVIS_COMMIT_RANGE:-HEAD~1..HEAD}" - if [[ "$TRAVIS_BRANCH" == "master" && "$TRAVIS_PULL_REQUEST" == "false" ]] ; then git config --global user.name "QMK Bot" git config --global user.email "hello@qmk.fm" diff --git a/util/travis_test.sh b/util/travis_test.sh index 9b7402c28..95991907e 100644 --- a/util/travis_test.sh +++ b/util/travis_test.sh @@ -1,26 +1,9 @@ #!/bin/bash -# if docker is installed - patch calls to within the qmk docker image -if command -v docker >/dev/null; then - function make() { - docker run --rm -e MAKEFLAGS="$MAKEFLAGS" -w /qmk_firmware/ -v "$PWD":/qmk_firmware --user $(id -u):$(id -g) qmkfm/base_container make "$@" - } - function qmk() { - docker run --rm -w /qmk_firmware/ -v "$PWD":/qmk_firmware --user $(id -u):$(id -g) qmkfm/base_container bin/qmk "$@" - } -fi - -TRAVIS_COMMIT_MESSAGE="${TRAVIS_COMMIT_MESSAGE:-none}" -TRAVIS_COMMIT_RANGE="${TRAVIS_COMMIT_RANGE:-HEAD~1..HEAD}" - -# test force push -#TRAVIS_COMMIT_RANGE="c287f1bfc5c8...81f62atc4c1d" - -BRANCH=$(git rev-parse --abbrev-ref HEAD) -CHANGES=$(git diff --name-only -n 1 ${TRAVIS_COMMIT_RANGE}) +source util/travis_utils.sh -NUM_CORE_CHANGES=$(echo "$CHANGES" | grep -Ecv -e '^(docs/)' -e '^(keyboards/)' -e '^(layouts/)') -NUM_PY_CHANGES=$(echo "$CHANGES" | grep -Ec -e '^(lib/python/)' -e '^(bin/qmk)') +NUM_CORE_CHANGES=$(echo "$QMK_CHANGES" | grep -Ecv -e '^(docs/)' -e '^(keyboards/)' -e '^(layouts/)' -e '^(util/)' -e '^(lib/python/)' -e '^(bin/qmk)' -e '^(requirements.txt)' -e '(.travis.yml)') +NUM_PY_CHANGES=$(echo "$QMK_CHANGES" | grep -Ec -e '^(lib/python/)' -e '^(bin/qmk)') if [[ "$TRAVIS_COMMIT_MESSAGE" == *"[skip test]"* ]]; then echo "Skipping due to commit message" @@ -29,14 +12,14 @@ fi exit_code=0 -if [ "$BRANCH" == "master" ] || [ "$NUM_CORE_CHANGES" != "0" ]; then +if [ "$LOCAL_BRANCH" == "master" ] || [ "$NUM_CORE_CHANGES" != "0" ]; then echo "Running tests." make test:all : $((exit_code = $exit_code + $?)) fi -if [ "$BRANCH" == "master" ] || [ "$NUM_PY_CHANGES" != "0" ]; then +if [ "$LOCAL_BRANCH" == "master" ] || [ "$NUM_PY_CHANGES" != "0" ]; then echo "Running python tests." qmk pytest : $((exit_code = $exit_code + $?)) diff --git a/util/travis_utils.sh b/util/travis_utils.sh new file mode 100755 index 000000000..031bcda1a --- /dev/null +++ b/util/travis_utils.sh @@ -0,0 +1,24 @@ +# Use this by sourcing it in your script. + +# Provide sane defaults for travis environment +TRAVIS_BRANCH="${TRAVIS_BRANCH:master}" +TRAVIS_PULL_REQUEST="${TRAVIS_PULL_REQUEST:false}" +TRAVIS_COMMIT_MESSAGE="${TRAVIS_COMMIT_MESSAGE:-none}" +TRAVIS_COMMIT_RANGE="${TRAVIS_COMMIT_RANGE:-HEAD~1..HEAD}" + +# test force push +#TRAVIS_COMMIT_RANGE="c287f1bfc5c8...81f62atc4c1d" + +# Extra variables +LOCAL_BRANCH=${TRAVIS_PULL_REQUEST_BRANCH:-${TRAVIS_BRANCH}} +QMK_CHANGES=$(git diff --name-only HEAD ${TRAVIS_BRANCH}) + +# if docker is installed - patch calls to within the qmk docker image +if command -v docker >/dev/null; then + function make() { + docker run --rm -e MAKEFLAGS="$MAKEFLAGS" -w /qmk_firmware/ -v "$PWD":/qmk_firmware --user $(id -u):$(id -g) qmkfm/base_container make "$@" + } + function qmk() { + docker run --rm -w /qmk_firmware/ -v "$PWD":/qmk_firmware --user $(id -u):$(id -g) qmkfm/base_container bin/qmk "$@" + } +fi -- cgit v1.2.3-70-g09d2 From 7e8f239c2e15091bca82f69c3982e585814c0f70 Mon Sep 17 00:00:00 2001 From: Joel Challis Date: Wed, 13 Nov 2019 07:53:05 +0000 Subject: [CI] Revert to previous logic for branch name and changes (#7343) --- util/travis_utils.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'util') diff --git a/util/travis_utils.sh b/util/travis_utils.sh index 031bcda1a..e3d806dfb 100755 --- a/util/travis_utils.sh +++ b/util/travis_utils.sh @@ -10,8 +10,8 @@ TRAVIS_COMMIT_RANGE="${TRAVIS_COMMIT_RANGE:-HEAD~1..HEAD}" #TRAVIS_COMMIT_RANGE="c287f1bfc5c8...81f62atc4c1d" # Extra variables -LOCAL_BRANCH=${TRAVIS_PULL_REQUEST_BRANCH:-${TRAVIS_BRANCH}} -QMK_CHANGES=$(git diff --name-only HEAD ${TRAVIS_BRANCH}) +LOCAL_BRANCH=$(git rev-parse --abbrev-ref HEAD) +QMK_CHANGES=$(git diff --name-only -n 1 ${TRAVIS_COMMIT_RANGE}) # if docker is installed - patch calls to within the qmk docker image if command -v docker >/dev/null; then -- cgit v1.2.3-70-g09d2 From fc867422a3d8f72eca2589b450f46da48e891fee Mon Sep 17 00:00:00 2001 From: fauxpark Date: Thu, 14 Nov 2019 05:13:27 +1100 Subject: Update ARM GCC on macOS to 8.3 (#7318) --- util/macos_install.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'util') diff --git a/util/macos_install.sh b/util/macos_install.sh index c5c27d167..f993003f0 100755 --- a/util/macos_install.sh +++ b/util/macos_install.sh @@ -22,8 +22,8 @@ if ! brew --version >/dev/null 2>&1; then fi brew tap osx-cross/avr -brew tap PX4/homebrew-px4 +brew tap osx-cross/arm brew update -brew install avr-gcc@8 gcc-arm-none-eabi dfu-programmer avrdude clang-format dfu-util python3 +brew install avr-gcc@8 arm-gcc-bin dfu-programmer avrdude clang-format dfu-util python3 brew link --force avr-gcc@8 pip3 install -r "${util_dir}/../requirements.txt" -- cgit v1.2.3-70-g09d2