diff options
Diffstat (limited to 'lib')
| m--------- | lib/chibios | 0 | ||||
| m--------- | lib/chibios-contrib | 0 | ||||
| -rw-r--r-- | lib/python/qmk/cli/__init__.py | 3 | ||||
| -rw-r--r-- | lib/python/qmk/cli/console.py | 303 | ||||
| -rw-r--r-- | lib/python/qmk/cli/doctor/check.py | 1 | ||||
| -rw-r--r-- | lib/python/qmk/cli/flash.py | 14 | ||||
| -rwxr-xr-x | lib/python/qmk/cli/generate/compilation_database.py | 133 | ||||
| -rwxr-xr-x | lib/python/qmk/cli/generate/develop_pr_list.py | 119 | ||||
| -rw-r--r-- | lib/python/qmk/cli/generate/dfu_header.py | 2 | ||||
| -rwxr-xr-x | lib/python/qmk/cli/generate/rules_mk.py | 9 | ||||
| -rwxr-xr-x | lib/python/qmk/cli/json2c.py | 2 | ||||
| -rw-r--r-- | lib/python/qmk/cli/lint.py | 145 | ||||
| -rw-r--r-- | lib/python/qmk/cli/pytest.py | 2 | ||||
| -rw-r--r-- | lib/python/qmk/commands.py | 18 | ||||
| -rw-r--r-- | lib/python/qmk/constants.py | 2 | ||||
| -rw-r--r-- | lib/python/qmk/info.py | 5 | ||||
| -rw-r--r-- | lib/python/qmk/keymap.py | 100 | ||||
| -rw-r--r-- | lib/python/qmk/tests/minimal_info.json | 2 | ||||
| -rw-r--r-- | lib/python/qmk/tests/test_cli_commands.py | 18 | ||||
| -rw-r--r-- | lib/python/qmk/tests/test_qmk_keymap.py | 8 | ||||
| m--------- | lib/ugfx | 0 |
21 files changed, 492 insertions, 394 deletions
diff --git a/lib/chibios b/lib/chibios | |||
| Subproject 413e39c5681d181720440f2a8b7391f581788d7 | Subproject d7b9d1c87f724bd7c8cd1486d6d0dc3ba52e0d5 | ||
diff --git a/lib/chibios-contrib b/lib/chibios-contrib | |||
| Subproject 4568901a91e9bef78ea96a7a83e8150fe1f7353 | Subproject d1c2126d1cd867c50127da84425805e225df855 | ||
diff --git a/lib/python/qmk/cli/__init__.py b/lib/python/qmk/cli/__init__.py index ea961315b..c51eece95 100644 --- a/lib/python/qmk/cli/__init__.py +++ b/lib/python/qmk/cli/__init__.py | |||
| @@ -36,7 +36,6 @@ subcommands = [ | |||
| 36 | 'qmk.cli.chibios.confmigrate', | 36 | 'qmk.cli.chibios.confmigrate', |
| 37 | 'qmk.cli.clean', | 37 | 'qmk.cli.clean', |
| 38 | 'qmk.cli.compile', | 38 | 'qmk.cli.compile', |
| 39 | 'qmk.cli.console', | ||
| 40 | 'qmk.cli.docs', | 39 | 'qmk.cli.docs', |
| 41 | 'qmk.cli.doctor', | 40 | 'qmk.cli.doctor', |
| 42 | 'qmk.cli.fileformat', | 41 | 'qmk.cli.fileformat', |
| @@ -46,7 +45,9 @@ subcommands = [ | |||
| 46 | 'qmk.cli.format.python', | 45 | 'qmk.cli.format.python', |
| 47 | 'qmk.cli.format.text', | 46 | 'qmk.cli.format.text', |
| 48 | 'qmk.cli.generate.api', | 47 | 'qmk.cli.generate.api', |
| 48 | 'qmk.cli.generate.compilation_database', | ||
| 49 | 'qmk.cli.generate.config_h', | 49 | 'qmk.cli.generate.config_h', |
| 50 | 'qmk.cli.generate.develop_pr_list', | ||
| 50 | 'qmk.cli.generate.dfu_header', | 51 | 'qmk.cli.generate.dfu_header', |
| 51 | 'qmk.cli.generate.docs', | 52 | 'qmk.cli.generate.docs', |
| 52 | 'qmk.cli.generate.info_json', | 53 | 'qmk.cli.generate.info_json', |
diff --git a/lib/python/qmk/cli/console.py b/lib/python/qmk/cli/console.py deleted file mode 100644 index 98c6bc0dc..000000000 --- a/lib/python/qmk/cli/console.py +++ /dev/null | |||
| @@ -1,303 +0,0 @@ | |||
| 1 | """Acquire debugging information from usb hid devices | ||
| 2 | |||
| 3 | cli implementation of https://www.pjrc.com/teensy/hid_listen.html | ||
| 4 | """ | ||
| 5 | from pathlib import Path | ||
| 6 | from threading import Thread | ||
| 7 | from time import sleep, strftime | ||
| 8 | |||
| 9 | import hid | ||
| 10 | import usb.core | ||
| 11 | |||
| 12 | from milc import cli | ||
| 13 | |||
| 14 | LOG_COLOR = { | ||
| 15 | 'next': 0, | ||
| 16 | 'colors': [ | ||
| 17 | '{fg_blue}', | ||
| 18 | '{fg_cyan}', | ||
| 19 | '{fg_green}', | ||
| 20 | '{fg_magenta}', | ||
| 21 | '{fg_red}', | ||
| 22 | '{fg_yellow}', | ||
| 23 | ], | ||
| 24 | } | ||
| 25 | |||
| 26 | KNOWN_BOOTLOADERS = { | ||
| 27 | # VID , PID | ||
| 28 | ('03EB', '2FEF'): 'atmel-dfu: ATmega16U2', | ||
| 29 | ('03EB', '2FF0'): 'atmel-dfu: ATmega32U2', | ||
| 30 | ('03EB', '2FF3'): 'atmel-dfu: ATmega16U4', | ||
| 31 | ('03EB', '2FF4'): 'atmel-dfu: ATmega32U4', | ||
| 32 | ('03EB', '2FF9'): 'atmel-dfu: AT90USB64', | ||
| 33 | ('03EB', '2FFA'): 'atmel-dfu: AT90USB162', | ||
| 34 | ('03EB', '2FFB'): 'atmel-dfu: AT90USB128', | ||
| 35 | ('03EB', '6124'): 'Microchip SAM-BA', | ||
| 36 | ('0483', 'DF11'): 'stm32-dfu: STM32 BOOTLOADER', | ||
| 37 | ('16C0', '05DC'): 'usbasploader: USBaspLoader', | ||
| 38 | ('16C0', '05DF'): 'bootloadhid: HIDBoot', | ||
| 39 | ('16C0', '0478'): 'halfkay: Teensy Halfkay', | ||
| 40 | ('1B4F', '9203'): 'caterina: Pro Micro 3.3V', | ||
| 41 | ('1B4F', '9205'): 'caterina: Pro Micro 5V', | ||
| 42 | ('1B4F', '9207'): 'caterina: LilyPadUSB', | ||
| 43 | ('1C11', 'B007'): 'kiibohd: Kiibohd DFU Bootloader', | ||
| 44 | ('1EAF', '0003'): 'stm32duino: Maple 003', | ||
| 45 | ('1FFB', '0101'): 'caterina: Polou A-Star 32U4 Bootloader', | ||
| 46 | ('2341', '0036'): 'caterina: Arduino Leonardo', | ||
| 47 | ('2341', '0037'): 'caterina: Arduino Micro', | ||
| 48 | ('239A', '000C'): 'caterina: Adafruit Feather 32U4', | ||
| 49 | ('239A', '000D'): 'caterina: Adafruit ItsyBitsy 32U4 3v', | ||
| 50 | ('239A', '000E'): 'caterina: Adafruit ItsyBitsy 32U4 5v', | ||
| 51 | ('2A03', '0036'): 'caterina: Arduino Leonardo', | ||
| 52 | ('2A03', '0037'): 'caterina: Arduino Micro', | ||
| 53 | ('314B', '0106'): 'apm32-dfu: APM32 DFU ISP Mode', | ||
| 54 | ('03EB', '2067'): 'qmk-hid: HID Bootloader', | ||
| 55 | ('03EB', '2045'): 'lufa-ms: LUFA Mass Storage Bootloader' | ||
| 56 | } | ||
| 57 | |||
| 58 | |||
| 59 | class MonitorDevice(object): | ||
| 60 | def __init__(self, hid_device, numeric): | ||
| 61 | self.hid_device = hid_device | ||
| 62 | self.numeric = numeric | ||
| 63 | self.device = hid.Device(path=hid_device['path']) | ||
| 64 | self.current_line = '' | ||
| 65 | |||
| 66 | cli.log.info('Console Connected: %(color)s%(manufacturer_string)s %(product_string)s{style_reset_all} (%(color)s%(vendor_id)04X:%(product_id)04X:%(index)d{style_reset_all})', hid_device) | ||
| 67 | |||
| 68 | def read(self, size, encoding='ascii', timeout=1): | ||
| 69 | """Read size bytes from the device. | ||
| 70 | """ | ||
| 71 | return self.device.read(size, timeout).decode(encoding) | ||
| 72 | |||
| 73 | def read_line(self): | ||
| 74 | """Read from the device's console until we get a \n. | ||
| 75 | """ | ||
| 76 | while '\n' not in self.current_line: | ||
| 77 | self.current_line += self.read(32).replace('\x00', '') | ||
| 78 | |||
| 79 | lines = self.current_line.split('\n', 1) | ||
| 80 | self.current_line = lines[1] | ||
| 81 | |||
| 82 | return lines[0] | ||
| 83 | |||
| 84 | def run_forever(self): | ||
| 85 | while True: | ||
| 86 | try: | ||
| 87 | message = {**self.hid_device, 'text': self.read_line()} | ||
| 88 | identifier = (int2hex(message['vendor_id']), int2hex(message['product_id'])) if self.numeric else (message['manufacturer_string'], message['product_string']) | ||
| 89 | message['identifier'] = ':'.join(identifier) | ||
| 90 | message['ts'] = '{style_dim}{fg_green}%s{style_reset_all} ' % (strftime(cli.config.general.datetime_fmt),) if cli.args.timestamp else '' | ||
| 91 | |||
| 92 | cli.echo('%(ts)s%(color)s%(identifier)s:%(index)d{style_reset_all}: %(text)s' % message) | ||
| 93 | |||
| 94 | except hid.HIDException: | ||
| 95 | break | ||
| 96 | |||
| 97 | |||
| 98 | class FindDevices(object): | ||
| 99 | def __init__(self, vid, pid, index, numeric): | ||
| 100 | self.vid = vid | ||
| 101 | self.pid = pid | ||
| 102 | self.index = index | ||
| 103 | self.numeric = numeric | ||
| 104 | |||
| 105 | def run_forever(self): | ||
| 106 | """Process messages from our queue in a loop. | ||
| 107 | """ | ||
| 108 | live_devices = {} | ||
| 109 | live_bootloaders = {} | ||
| 110 | |||
| 111 | while True: | ||
| 112 | try: | ||
| 113 | for device in list(live_devices): | ||
| 114 | if not live_devices[device]['thread'].is_alive(): | ||
| 115 | cli.log.info('Console Disconnected: %(color)s%(manufacturer_string)s %(product_string)s{style_reset_all} (%(color)s%(vendor_id)04X:%(product_id)04X:%(index)d{style_reset_all})', live_devices[device]) | ||
| 116 | del live_devices[device] | ||
| 117 | |||
| 118 | for device in self.find_devices(): | ||
| 119 | if device['path'] not in live_devices: | ||
| 120 | device['color'] = LOG_COLOR['colors'][LOG_COLOR['next']] | ||
| 121 | LOG_COLOR['next'] = (LOG_COLOR['next'] + 1) % len(LOG_COLOR['colors']) | ||
| 122 | live_devices[device['path']] = device | ||
| 123 | |||
| 124 | try: | ||
| 125 | monitor = MonitorDevice(device, self.numeric) | ||
| 126 | device['thread'] = Thread(target=monitor.run_forever, daemon=True) | ||
| 127 | |||
| 128 | device['thread'].start() | ||
| 129 | except Exception as e: | ||
| 130 | device['e'] = e | ||
| 131 | device['e_name'] = e.__class__.__name__ | ||
| 132 | cli.log.error("Could not connect to %(color)s%(manufacturer_string)s %(product_string)s{style_reset_all} (%(color)s:%(vendor_id)04X:%(product_id)04X:%(index)d): %(e_name)s: %(e)s", device) | ||
| 133 | if cli.config.general.verbose: | ||
| 134 | cli.log.exception(e) | ||
| 135 | del live_devices[device['path']] | ||
| 136 | |||
| 137 | if cli.args.bootloaders: | ||
| 138 | for device in self.find_bootloaders(): | ||
| 139 | if device.address in live_bootloaders: | ||
| 140 | live_bootloaders[device.address]._qmk_found = True | ||
| 141 | else: | ||
| 142 | name = KNOWN_BOOTLOADERS[(int2hex(device.idVendor), int2hex(device.idProduct))] | ||
| 143 | cli.log.info('Bootloader Connected: {style_bright}{fg_magenta}%s', name) | ||
| 144 | device._qmk_found = True | ||
| 145 | live_bootloaders[device.address] = device | ||
| 146 | |||
| 147 | for device in list(live_bootloaders): | ||
| 148 | if live_bootloaders[device]._qmk_found: | ||
| 149 | live_bootloaders[device]._qmk_found = False | ||
| 150 | else: | ||
| 151 | name = KNOWN_BOOTLOADERS[(int2hex(live_bootloaders[device].idVendor), int2hex(live_bootloaders[device].idProduct))] | ||
| 152 | cli.log.info('Bootloader Disconnected: {style_bright}{fg_magenta}%s', name) | ||
| 153 | del live_bootloaders[device] | ||
| 154 | |||
| 155 | sleep(.1) | ||
| 156 | |||
| 157 | except KeyboardInterrupt: | ||
| 158 | break | ||
| 159 | |||
| 160 | def is_bootloader(self, hid_device): | ||
| 161 | """Returns true if the device in question matches a known bootloader vid/pid. | ||
| 162 | """ | ||
| 163 | return (int2hex(hid_device.idVendor), int2hex(hid_device.idProduct)) in KNOWN_BOOTLOADERS | ||
| 164 | |||
| 165 | def is_console_hid(self, hid_device): | ||
| 166 | """Returns true when the usage page indicates it's a teensy-style console. | ||
| 167 | """ | ||
| 168 | return hid_device['usage_page'] == 0xFF31 and hid_device['usage'] == 0x0074 | ||
| 169 | |||
| 170 | def is_filtered_device(self, hid_device): | ||
| 171 | """Returns True if the device should be included in the list of available consoles. | ||
| 172 | """ | ||
| 173 | return int2hex(hid_device['vendor_id']) == self.vid and int2hex(hid_device['product_id']) == self.pid | ||
| 174 | |||
| 175 | def find_devices_by_report(self, hid_devices): | ||
| 176 | """Returns a list of available teensy-style consoles by doing a brute-force search. | ||
| 177 | |||
| 178 | Some versions of linux don't report usage and usage_page. In that case we fallback to reading the report (possibly inaccurately) ourselves. | ||
| 179 | """ | ||
| 180 | devices = [] | ||
| 181 | |||
| 182 | for device in hid_devices: | ||
| 183 | path = device['path'].decode('utf-8') | ||
| 184 | |||
| 185 | if path.startswith('/dev/hidraw'): | ||
| 186 | number = path[11:] | ||
| 187 | report = Path(f'/sys/class/hidraw/hidraw{number}/device/report_descriptor') | ||
| 188 | |||
| 189 | if report.exists(): | ||
| 190 | rp = report.read_bytes() | ||
| 191 | |||
| 192 | if rp[1] == 0x31 and rp[3] == 0x09: | ||
| 193 | devices.append(device) | ||
| 194 | |||
| 195 | return devices | ||
| 196 | |||
| 197 | def find_bootloaders(self): | ||
| 198 | """Returns a list of available bootloader devices. | ||
| 199 | """ | ||
| 200 | return list(filter(self.is_bootloader, usb.core.find(find_all=True))) | ||
| 201 | |||
| 202 | def find_devices(self): | ||
| 203 | """Returns a list of available teensy-style consoles. | ||
| 204 | """ | ||
| 205 | hid_devices = hid.enumerate() | ||
| 206 | devices = list(filter(self.is_console_hid, hid_devices)) | ||
| 207 | |||
| 208 | if not devices: | ||
| 209 | devices = self.find_devices_by_report(hid_devices) | ||
| 210 | |||
| 211 | if self.vid and self.pid: | ||
| 212 | devices = list(filter(self.is_filtered_device, devices)) | ||
| 213 | |||
| 214 | # Add index numbers | ||
| 215 | device_index = {} | ||
| 216 | for device in devices: | ||
| 217 | id = ':'.join((int2hex(device['vendor_id']), int2hex(device['product_id']))) | ||
| 218 | |||
| 219 | if id not in device_index: | ||
| 220 | device_index[id] = 0 | ||
| 221 | |||
| 222 | device_index[id] += 1 | ||
| 223 | device['index'] = device_index[id] | ||
| 224 | |||
| 225 | return devices | ||
| 226 | |||
| 227 | |||
| 228 | def int2hex(number): | ||
| 229 | """Returns a string representation of the number as hex. | ||
| 230 | """ | ||
| 231 | return "%04X" % number | ||
| 232 | |||
| 233 | |||
| 234 | def list_devices(device_finder): | ||
| 235 | """Show the user a nicely formatted list of devices. | ||
| 236 | """ | ||
| 237 | devices = device_finder.find_devices() | ||
| 238 | |||
| 239 | if devices: | ||
| 240 | cli.log.info('Available devices:') | ||
| 241 | for dev in devices: | ||
| 242 | color = LOG_COLOR['colors'][LOG_COLOR['next']] | ||
| 243 | LOG_COLOR['next'] = (LOG_COLOR['next'] + 1) % len(LOG_COLOR['colors']) | ||
| 244 | cli.log.info("\t%s%s:%s:%d{style_reset_all}\t%s %s", color, int2hex(dev['vendor_id']), int2hex(dev['product_id']), dev['index'], dev['manufacturer_string'], dev['product_string']) | ||
| 245 | |||
| 246 | if cli.args.bootloaders: | ||
| 247 | bootloaders = device_finder.find_bootloaders() | ||
| 248 | |||
| 249 | if bootloaders: | ||
| 250 | cli.log.info('Available Bootloaders:') | ||
| 251 | |||
| 252 | for dev in bootloaders: | ||
| 253 | cli.log.info("\t%s:%s\t%s", int2hex(dev.idVendor), int2hex(dev.idProduct), KNOWN_BOOTLOADERS[(int2hex(dev.idVendor), int2hex(dev.idProduct))]) | ||
| 254 | |||
| 255 | |||
| 256 | @cli.argument('--bootloaders', arg_only=True, default=True, action='store_boolean', help='displaying bootloaders.') | ||
| 257 | @cli.argument('-d', '--device', help='Device to select - uses format <pid>:<vid>[:<index>].') | ||
| 258 | @cli.argument('-l', '--list', arg_only=True, action='store_true', help='List available hid_listen devices.') | ||
| 259 | @cli.argument('-n', '--numeric', arg_only=True, action='store_true', help='Show VID/PID instead of names.') | ||
| 260 | @cli.argument('-t', '--timestamp', arg_only=True, action='store_true', help='Print the timestamp for received messages as well.') | ||
| 261 | @cli.argument('-w', '--wait', type=int, default=1, help="How many seconds to wait between checks (Default: 1)") | ||
| 262 | @cli.subcommand('Acquire debugging information from usb hid devices.', hidden=False if cli.config.user.developer else True) | ||
| 263 | def console(cli): | ||
| 264 | """Acquire debugging information from usb hid devices | ||
| 265 | """ | ||
| 266 | vid = None | ||
| 267 | pid = None | ||
| 268 | index = 1 | ||
| 269 | |||
| 270 | if cli.config.console.device: | ||
| 271 | device = cli.config.console.device.split(':') | ||
| 272 | |||
| 273 | if len(device) == 2: | ||
| 274 | vid, pid = device | ||
| 275 | |||
| 276 | elif len(device) == 3: | ||
| 277 | vid, pid, index = device | ||
| 278 | |||
| 279 | if not index.isdigit(): | ||
| 280 | cli.log.error('Device index must be a number! Got "%s" instead.', index) | ||
| 281 | exit(1) | ||
| 282 | |||
| 283 | index = int(index) | ||
| 284 | |||
| 285 | if index < 1: | ||
| 286 | cli.log.error('Device index must be greater than 0! Got %s', index) | ||
| 287 | exit(1) | ||
| 288 | |||
| 289 | else: | ||
| 290 | cli.log.error('Invalid format for device, expected "<pid>:<vid>[:<index>]" but got "%s".', cli.config.console.device) | ||
| 291 | cli.print_help() | ||
| 292 | exit(1) | ||
| 293 | |||
| 294 | vid = vid.upper() | ||
| 295 | pid = pid.upper() | ||
| 296 | |||
| 297 | device_finder = FindDevices(vid, pid, index, cli.args.numeric) | ||
| 298 | |||
| 299 | if cli.args.list: | ||
| 300 | return list_devices(device_finder) | ||
| 301 | |||
| 302 | print('Looking for devices...', flush=True) | ||
| 303 | device_finder.run_forever() | ||
diff --git a/lib/python/qmk/cli/doctor/check.py b/lib/python/qmk/cli/doctor/check.py index 0807f4151..2d691b64b 100644 --- a/lib/python/qmk/cli/doctor/check.py +++ b/lib/python/qmk/cli/doctor/check.py | |||
| @@ -26,7 +26,6 @@ ESSENTIAL_BINARIES = { | |||
| 26 | 'arm-none-eabi-gcc': { | 26 | 'arm-none-eabi-gcc': { |
| 27 | 'version_arg': '-dumpversion' | 27 | 'version_arg': '-dumpversion' |
| 28 | }, | 28 | }, |
| 29 | 'bin/qmk': {}, | ||
| 30 | } | 29 | } |
| 31 | 30 | ||
| 32 | 31 | ||
diff --git a/lib/python/qmk/cli/flash.py b/lib/python/qmk/cli/flash.py index c2d9e09c6..28e48a410 100644 --- a/lib/python/qmk/cli/flash.py +++ b/lib/python/qmk/cli/flash.py | |||
| @@ -18,17 +18,21 @@ def print_bootloader_help(): | |||
| 18 | """Prints the available bootloaders listed in docs.qmk.fm. | 18 | """Prints the available bootloaders listed in docs.qmk.fm. |
| 19 | """ | 19 | """ |
| 20 | cli.log.info('Here are the available bootloaders:') | 20 | cli.log.info('Here are the available bootloaders:') |
| 21 | cli.echo('\tavrdude') | ||
| 22 | cli.echo('\tbootloadhid') | ||
| 21 | cli.echo('\tdfu') | 23 | cli.echo('\tdfu') |
| 24 | cli.echo('\tdfu-util') | ||
| 25 | cli.echo('\tmdloader') | ||
| 26 | cli.echo('\tst-flash') | ||
| 27 | cli.echo('\tst-link-cli') | ||
| 28 | cli.log.info('Enhanced variants for split keyboards:') | ||
| 29 | cli.echo('\tavrdude-split-left') | ||
| 30 | cli.echo('\tavrdude-split-right') | ||
| 22 | cli.echo('\tdfu-ee') | 31 | cli.echo('\tdfu-ee') |
| 23 | cli.echo('\tdfu-split-left') | 32 | cli.echo('\tdfu-split-left') |
| 24 | cli.echo('\tdfu-split-right') | 33 | cli.echo('\tdfu-split-right') |
| 25 | cli.echo('\tavrdude') | ||
| 26 | cli.echo('\tBootloadHID') | ||
| 27 | cli.echo('\tdfu-util') | ||
| 28 | cli.echo('\tdfu-util-split-left') | 34 | cli.echo('\tdfu-util-split-left') |
| 29 | cli.echo('\tdfu-util-split-right') | 35 | cli.echo('\tdfu-util-split-right') |
| 30 | cli.echo('\tst-link-cli') | ||
| 31 | cli.echo('\tst-flash') | ||
| 32 | cli.echo('For more info, visit https://docs.qmk.fm/#/flashing') | 36 | cli.echo('For more info, visit https://docs.qmk.fm/#/flashing') |
| 33 | 37 | ||
| 34 | 38 | ||
diff --git a/lib/python/qmk/cli/generate/compilation_database.py b/lib/python/qmk/cli/generate/compilation_database.py new file mode 100755 index 000000000..602635270 --- /dev/null +++ b/lib/python/qmk/cli/generate/compilation_database.py | |||
| @@ -0,0 +1,133 @@ | |||
| 1 | """Creates a compilation database for the given keyboard build. | ||
| 2 | """ | ||
| 3 | |||
| 4 | import json | ||
| 5 | import os | ||
| 6 | import re | ||
| 7 | import shlex | ||
| 8 | import shutil | ||
| 9 | from functools import lru_cache | ||
| 10 | from pathlib import Path | ||
| 11 | from typing import Dict, Iterator, List, Union | ||
| 12 | |||
| 13 | from milc import cli, MILC | ||
| 14 | |||
| 15 | from qmk.commands import create_make_command | ||
| 16 | from qmk.constants import QMK_FIRMWARE | ||
| 17 | from qmk.decorators import automagic_keyboard, automagic_keymap | ||
| 18 | |||
| 19 | |||
| 20 | @lru_cache(maxsize=10) | ||
| 21 | def system_libs(binary: str) -> List[Path]: | ||
| 22 | """Find the system include directory that the given build tool uses. | ||
| 23 | """ | ||
| 24 | cli.log.debug("searching for system library directory for binary: %s", binary) | ||
| 25 | bin_path = shutil.which(binary) | ||
| 26 | |||
| 27 | # Actually query xxxxxx-gcc to find its include paths. | ||
| 28 | if binary.endswith("gcc") or binary.endswith("g++"): | ||
| 29 | result = cli.run([binary, '-E', '-Wp,-v', '-'], capture_output=True, check=True, input='\n') | ||
| 30 | paths = [] | ||
| 31 | for line in result.stderr.splitlines(): | ||
| 32 | if line.startswith(" "): | ||
| 33 | paths.append(Path(line.strip()).resolve()) | ||
| 34 | return paths | ||
| 35 | |||
| 36 | return list(Path(bin_path).resolve().parent.parent.glob("*/include")) if bin_path else [] | ||
| 37 | |||
| 38 | |||
| 39 | file_re = re.compile(r'printf "Compiling: ([^"]+)') | ||
| 40 | cmd_re = re.compile(r'LOG=\$\((.+?)&&') | ||
| 41 | |||
| 42 | |||
| 43 | def parse_make_n(f: Iterator[str]) -> List[Dict[str, str]]: | ||
| 44 | """parse the output of `make -n <target>` | ||
| 45 | |||
| 46 | This function makes many assumptions about the format of your build log. | ||
| 47 | This happens to work right now for qmk. | ||
| 48 | """ | ||
| 49 | |||
| 50 | state = 'start' | ||
| 51 | this_file = None | ||
| 52 | records = [] | ||
| 53 | for line in f: | ||
| 54 | if state == 'start': | ||
| 55 | m = file_re.search(line) | ||
| 56 | if m: | ||
| 57 | this_file = m.group(1) | ||
| 58 | state = 'cmd' | ||
| 59 | |||
| 60 | if state == 'cmd': | ||
| 61 | assert this_file | ||
| 62 | m = cmd_re.search(line) | ||
| 63 | if m: | ||
| 64 | # we have a hit! | ||
| 65 | this_cmd = m.group(1) | ||
| 66 | args = shlex.split(this_cmd) | ||
| 67 | for s in system_libs(args[0]): | ||
| 68 | args += ['-isystem', '%s' % s] | ||
| 69 | new_cmd = ' '.join(shlex.quote(s) for s in args if s != '-mno-thumb-interwork') | ||
| 70 | records.append({"directory": str(QMK_FIRMWARE.resolve()), "command": new_cmd, "file": this_file}) | ||
| 71 | state = 'start' | ||
| 72 | |||
| 73 | return records | ||
| 74 | |||
| 75 | |||
| 76 | @cli.argument('-kb', '--keyboard', help='The keyboard to build a firmware for. Ignored when a configurator export is supplied.') | ||
| 77 | @cli.argument('-km', '--keymap', help='The keymap to build a firmware for. Ignored when a configurator export is supplied.') | ||
| 78 | @cli.subcommand('Create a compilation database.') | ||
| 79 | @automagic_keyboard | ||
| 80 | @automagic_keymap | ||
| 81 | def generate_compilation_database(cli: MILC) -> Union[bool, int]: | ||
| 82 | """Creates a compilation database for the given keyboard build. | ||
| 83 | |||
| 84 | Does a make clean, then a make -n for this target and uses the dry-run output to create | ||
| 85 | a compilation database (compile_commands.json). This file can help some IDEs and | ||
| 86 | IDE-like editors work better. For more information about this: | ||
| 87 | |||
| 88 | https://clang.llvm.org/docs/JSONCompilationDatabase.html | ||
| 89 | """ | ||
| 90 | command = None | ||
| 91 | # check both config domains: the magic decorator fills in `generate_compilation_database` but the user is | ||
| 92 | # more likely to have set `compile` in their config file. | ||
| 93 | current_keyboard = cli.config.generate_compilation_database.keyboard or cli.config.user.keyboard | ||
| 94 | current_keymap = cli.config.generate_compilation_database.keymap or cli.config.user.keymap | ||
| 95 | |||
| 96 | if current_keyboard and current_keymap: | ||
| 97 | # Generate the make command for a specific keyboard/keymap. | ||
| 98 | command = create_make_command(current_keyboard, current_keymap, dry_run=True) | ||
| 99 | elif not current_keyboard: | ||
| 100 | cli.log.error('Could not determine keyboard!') | ||
| 101 | elif not current_keymap: | ||
| 102 | cli.log.error('Could not determine keymap!') | ||
| 103 | |||
| 104 | if not command: | ||
| 105 | cli.log.error('You must supply both `--keyboard` and `--keymap`, or be in a directory for a keyboard or keymap.') | ||
| 106 | cli.echo('usage: qmk compiledb [-kb KEYBOARD] [-km KEYMAP]') | ||
| 107 | return False | ||
| 108 | |||
| 109 | # remove any environment variable overrides which could trip us up | ||
| 110 | env = os.environ.copy() | ||
| 111 | env.pop("MAKEFLAGS", None) | ||
| 112 | |||
| 113 | # re-use same executable as the main make invocation (might be gmake) | ||
| 114 | clean_command = [command[0], 'clean'] | ||
| 115 | cli.log.info('Making clean with {fg_cyan}%s', ' '.join(clean_command)) | ||
| 116 | cli.run(clean_command, capture_output=False, check=True, env=env) | ||
| 117 | |||
| 118 | cli.log.info('Gathering build instructions from {fg_cyan}%s', ' '.join(command)) | ||
| 119 | |||
| 120 | result = cli.run(command, capture_output=True, check=True, env=env) | ||
| 121 | db = parse_make_n(result.stdout.splitlines()) | ||
| 122 | if not db: | ||
| 123 | cli.log.error("Failed to parse output from make output:\n%s", result.stdout) | ||
| 124 | return False | ||
| 125 | |||
| 126 | cli.log.info("Found %s compile commands", len(db)) | ||
| 127 | |||
| 128 | dbpath = QMK_FIRMWARE / 'compile_commands.json' | ||
| 129 | |||
| 130 | cli.log.info(f"Writing build database to {dbpath}") | ||
| 131 | dbpath.write_text(json.dumps(db, indent=4)) | ||
| 132 | |||
| 133 | return True | ||
diff --git a/lib/python/qmk/cli/generate/develop_pr_list.py b/lib/python/qmk/cli/generate/develop_pr_list.py new file mode 100755 index 000000000..de4eaa7d8 --- /dev/null +++ b/lib/python/qmk/cli/generate/develop_pr_list.py | |||
| @@ -0,0 +1,119 @@ | |||
| 1 | """Export the initial list of PRs associated with a `develop` merge to `master`. | ||
| 2 | """ | ||
| 3 | import os | ||
| 4 | import re | ||
| 5 | from pathlib import Path | ||
| 6 | from subprocess import DEVNULL | ||
| 7 | |||
| 8 | from milc import cli | ||
| 9 | |||
| 10 | cache_timeout = 7 * 86400 | ||
| 11 | fix_expr = re.compile(r'fix', flags=re.IGNORECASE) | ||
| 12 | clean1_expr = re.compile(r'\[(develop|keyboard|keymap|core|cli|bug|docs|feature)\]', flags=re.IGNORECASE) | ||
| 13 | clean2_expr = re.compile(r'^(develop|keyboard|keymap|core|cli|bug|docs|feature):', flags=re.IGNORECASE) | ||
| 14 | |||
| 15 | |||
| 16 | def _get_pr_info(cache, gh, pr_num): | ||
| 17 | pull = cache.get(f'pull:{pr_num}') | ||
| 18 | if pull is None: | ||
| 19 | print(f'Retrieving info for PR #{pr_num}') | ||
| 20 | pull = gh.pulls.get(owner='qmk', repo='qmk_firmware', pull_number=pr_num) | ||
| 21 | cache.set(f'pull:{pr_num}', pull, cache_timeout) | ||
| 22 | return pull | ||
| 23 | |||
| 24 | |||
| 25 | def _try_open_cache(cli): | ||
| 26 | # These dependencies are manually handled because people complain. Fun. | ||
| 27 | try: | ||
| 28 | from sqlite_cache.sqlite_cache import SqliteCache | ||
| 29 | except ImportError: | ||
| 30 | return None | ||
| 31 | |||
| 32 | cache_loc = Path(cli.config_file).parent | ||
| 33 | return SqliteCache(cache_loc) | ||
| 34 | |||
| 35 | |||
| 36 | def _get_github(): | ||
| 37 | try: | ||
| 38 | from ghapi.all import GhApi | ||
| 39 | except ImportError: | ||
| 40 | return None | ||
| 41 | |||
| 42 | return GhApi() | ||
| 43 | |||
| 44 | |||
| 45 | @cli.argument('-f', '--from-ref', default='0.11.0', help='Git revision/tag/reference/branch to begin search') | ||
| 46 | @cli.argument('-b', '--branch', default='upstream/develop', help='Git branch to iterate (default: "upstream/develop")') | ||
| 47 | @cli.subcommand('Creates the develop PR list.', hidden=False if cli.config.user.developer else True) | ||
| 48 | def generate_develop_pr_list(cli): | ||
| 49 | """Retrieves information from GitHub regarding the list of PRs associated | ||
| 50 | with a merge of `develop` branch into `master`. | ||
| 51 | |||
| 52 | Requires environment variable GITHUB_TOKEN to be set. | ||
| 53 | """ | ||
| 54 | |||
| 55 | if 'GITHUB_TOKEN' not in os.environ or os.environ['GITHUB_TOKEN'] == '': | ||
| 56 | cli.log.error('Environment variable "GITHUB_TOKEN" is not set.') | ||
| 57 | return 1 | ||
| 58 | |||
| 59 | cache = _try_open_cache(cli) | ||
| 60 | gh = _get_github() | ||
| 61 | |||
| 62 | git_args = ['git', 'rev-list', '--oneline', '--no-merges', '--reverse', f'{cli.args.from_ref}...{cli.args.branch}', '^upstream/master'] | ||
| 63 | commit_list = cli.run(git_args, capture_output=True, stdin=DEVNULL) | ||
| 64 | |||
| 65 | if cache is None or gh is None: | ||
| 66 | cli.log.error('Missing one or more dependent python packages: "ghapi", "python-sqlite-cache"') | ||
| 67 | return 1 | ||
| 68 | |||
| 69 | pr_list_bugs = [] | ||
| 70 | pr_list_dependencies = [] | ||
| 71 | pr_list_core = [] | ||
| 72 | pr_list_keyboards = [] | ||
| 73 | pr_list_keyboard_fixes = [] | ||
| 74 | pr_list_cli = [] | ||
| 75 | pr_list_others = [] | ||
| 76 | |||
| 77 | def _categorise_commit(commit_info): | ||
| 78 | def fix_or_normal(info, fixes_collection, normal_collection): | ||
| 79 | if "bug" in info['pr_labels'] or fix_expr.search(info['title']): | ||
| 80 | fixes_collection.append(info) | ||
| 81 | else: | ||
| 82 | normal_collection.append(info) | ||
| 83 | |||
| 84 | if "dependencies" in commit_info['pr_labels']: | ||
| 85 | fix_or_normal(commit_info, pr_list_bugs, pr_list_dependencies) | ||
| 86 | elif "core" in commit_info['pr_labels']: | ||
| 87 | fix_or_normal(commit_info, pr_list_bugs, pr_list_core) | ||
| 88 | elif "keyboard" in commit_info['pr_labels'] or "keymap" in commit_info['pr_labels'] or "via" in commit_info['pr_labels']: | ||
| 89 | fix_or_normal(commit_info, pr_list_keyboard_fixes, pr_list_keyboards) | ||
| 90 | elif "cli" in commit_info['pr_labels']: | ||
| 91 | fix_or_normal(commit_info, pr_list_bugs, pr_list_cli) | ||
| 92 | else: | ||
| 93 | fix_or_normal(commit_info, pr_list_bugs, pr_list_others) | ||
| 94 | |||
| 95 | git_expr = re.compile(r'^(?P<hash>[a-f0-9]+) (?P<title>.*) \(#(?P<pr>[0-9]+)\)$') | ||
| 96 | for line in commit_list.stdout.split('\n'): | ||
| 97 | match = git_expr.search(line) | ||
| 98 | if match: | ||
| 99 | pr_info = _get_pr_info(cache, gh, match.group("pr")) | ||
| 100 | commit_info = {'hash': match.group("hash"), 'title': match.group("title"), 'pr_num': int(match.group("pr")), 'pr_labels': [label.name for label in pr_info.labels.items]} | ||
| 101 | _categorise_commit(commit_info) | ||
| 102 | |||
| 103 | def _dump_commit_list(name, collection): | ||
| 104 | if len(collection) == 0: | ||
| 105 | return | ||
| 106 | print("") | ||
| 107 | print(f"{name}:") | ||
| 108 | for commit in sorted(collection, key=lambda x: x['pr_num']): | ||
| 109 | title = clean1_expr.sub('', clean2_expr.sub('', commit['title'])).strip() | ||
| 110 | pr_num = commit['pr_num'] | ||
| 111 | print(f'* {title} ([#{pr_num}](https://github.com/qmk/qmk_firmware/pull/{pr_num}))') | ||
| 112 | |||
| 113 | _dump_commit_list("Bugs", pr_list_bugs) | ||
| 114 | _dump_commit_list("Core", pr_list_core) | ||
| 115 | _dump_commit_list("CLI", pr_list_cli) | ||
| 116 | _dump_commit_list("Submodule updates", pr_list_dependencies) | ||
| 117 | _dump_commit_list("Keyboards", pr_list_keyboards) | ||
| 118 | _dump_commit_list("Keyboard fixes", pr_list_keyboard_fixes) | ||
| 119 | _dump_commit_list("Others", pr_list_others) | ||
diff --git a/lib/python/qmk/cli/generate/dfu_header.py b/lib/python/qmk/cli/generate/dfu_header.py index 5a1b109f1..7fb585fc7 100644 --- a/lib/python/qmk/cli/generate/dfu_header.py +++ b/lib/python/qmk/cli/generate/dfu_header.py | |||
| @@ -32,7 +32,7 @@ def generate_dfu_header(cli): | |||
| 32 | 32 | ||
| 33 | keyboard_h_lines = ['/* This file was generated by `qmk generate-dfu-header`. Do not edit or copy.', ' */', '', '#pragma once'] | 33 | keyboard_h_lines = ['/* This file was generated by `qmk generate-dfu-header`. Do not edit or copy.', ' */', '', '#pragma once'] |
| 34 | keyboard_h_lines.append(f'#define MANUFACTURER {kb_info_json["manufacturer"]}') | 34 | keyboard_h_lines.append(f'#define MANUFACTURER {kb_info_json["manufacturer"]}') |
| 35 | keyboard_h_lines.append(f'#define PRODUCT {cli.config.generate_dfu_header.keyboard} Bootloader') | 35 | keyboard_h_lines.append(f'#define PRODUCT {kb_info_json["keyboard_name"]} Bootloader') |
| 36 | 36 | ||
| 37 | # Optional | 37 | # Optional |
| 38 | if 'qmk_lufa_bootloader.esc_output' in kb_info_json: | 38 | if 'qmk_lufa_bootloader.esc_output' in kb_info_json: |
diff --git a/lib/python/qmk/cli/generate/rules_mk.py b/lib/python/qmk/cli/generate/rules_mk.py index dcaff29fa..5d8d7cc8a 100755 --- a/lib/python/qmk/cli/generate/rules_mk.py +++ b/lib/python/qmk/cli/generate/rules_mk.py | |||
| @@ -67,12 +67,9 @@ def generate_rules_mk(cli): | |||
| 67 | # Iterate through features to enable/disable them | 67 | # Iterate through features to enable/disable them |
| 68 | if 'features' in kb_info_json: | 68 | if 'features' in kb_info_json: |
| 69 | for feature, enabled in kb_info_json['features'].items(): | 69 | for feature, enabled in kb_info_json['features'].items(): |
| 70 | if feature == 'bootmagic_lite' and enabled: | 70 | feature = feature.upper() |
| 71 | rules_mk_lines.append('BOOTMAGIC_ENABLE ?= lite') | 71 | enabled = 'yes' if enabled else 'no' |
| 72 | else: | 72 | rules_mk_lines.append(f'{feature}_ENABLE ?= {enabled}') |
| 73 | feature = feature.upper() | ||
| 74 | enabled = 'yes' if enabled else 'no' | ||
| 75 | rules_mk_lines.append(f'{feature}_ENABLE ?= {enabled}') | ||
| 76 | 73 | ||
| 77 | # Set SPLIT_TRANSPORT, if needed | 74 | # Set SPLIT_TRANSPORT, if needed |
| 78 | if kb_info_json.get('split', {}).get('transport', {}).get('protocol') == 'custom': | 75 | if kb_info_json.get('split', {}).get('transport', {}).get('protocol') == 'custom': |
diff --git a/lib/python/qmk/cli/json2c.py b/lib/python/qmk/cli/json2c.py index a90578c02..ae8248e6b 100755 --- a/lib/python/qmk/cli/json2c.py +++ b/lib/python/qmk/cli/json2c.py | |||
| @@ -33,7 +33,7 @@ def json2c(cli): | |||
| 33 | cli.args.output = None | 33 | cli.args.output = None |
| 34 | 34 | ||
| 35 | # Generate the keymap | 35 | # Generate the keymap |
| 36 | keymap_c = qmk.keymap.generate_c(user_keymap['keyboard'], user_keymap['layout'], user_keymap['layers']) | 36 | keymap_c = qmk.keymap.generate_c(user_keymap) |
| 37 | 37 | ||
| 38 | if cli.args.output: | 38 | if cli.args.output: |
| 39 | cli.args.output.parent.mkdir(parents=True, exist_ok=True) | 39 | cli.args.output.parent.mkdir(parents=True, exist_ok=True) |
diff --git a/lib/python/qmk/cli/lint.py b/lib/python/qmk/cli/lint.py index 02b31fbc4..96593ed69 100644 --- a/lib/python/qmk/cli/lint.py +++ b/lib/python/qmk/cli/lint.py | |||
| @@ -1,72 +1,129 @@ | |||
| 1 | """Command to look over a keyboard/keymap and check for common mistakes. | 1 | """Command to look over a keyboard/keymap and check for common mistakes. |
| 2 | """ | 2 | """ |
| 3 | from pathlib import Path | ||
| 4 | |||
| 3 | from milc import cli | 5 | from milc import cli |
| 4 | 6 | ||
| 5 | from qmk.decorators import automagic_keyboard, automagic_keymap | 7 | from qmk.decorators import automagic_keyboard, automagic_keymap |
| 6 | from qmk.info import info_json | 8 | from qmk.info import info_json |
| 7 | from qmk.keyboard import find_readme, keyboard_completer | 9 | from qmk.keyboard import keyboard_completer, list_keyboards |
| 8 | from qmk.keymap import locate_keymap | 10 | from qmk.keymap import locate_keymap |
| 9 | from qmk.path import is_keyboard, keyboard | 11 | from qmk.path import is_keyboard, keyboard |
| 10 | 12 | ||
| 11 | 13 | ||
| 12 | @cli.argument('--strict', action='store_true', help='Treat warnings as errors.') | 14 | def keymap_check(kb, km): |
| 13 | @cli.argument('-kb', '--keyboard', completer=keyboard_completer, help='The keyboard to check.') | 15 | """Perform the keymap level checks. |
| 14 | @cli.argument('-km', '--keymap', help='The keymap to check.') | 16 | """ |
| 17 | ok = True | ||
| 18 | keymap_path = locate_keymap(kb, km) | ||
| 19 | |||
| 20 | if not keymap_path: | ||
| 21 | ok = False | ||
| 22 | cli.log.error("%s: Can't find %s keymap.", kb, km) | ||
| 23 | |||
| 24 | return ok | ||
| 25 | |||
| 26 | |||
| 27 | def rules_mk_assignment_only(keyboard_path): | ||
| 28 | """Check the keyboard-level rules.mk to ensure it only has assignments. | ||
| 29 | """ | ||
| 30 | current_path = Path() | ||
| 31 | errors = [] | ||
| 32 | |||
| 33 | for path_part in keyboard_path.parts: | ||
| 34 | current_path = current_path / path_part | ||
| 35 | rules_mk = current_path / 'rules.mk' | ||
| 36 | |||
| 37 | if rules_mk.exists(): | ||
| 38 | continuation = None | ||
| 39 | |||
| 40 | for i, line in enumerate(rules_mk.open()): | ||
| 41 | line = line.strip() | ||
| 42 | |||
| 43 | if '#' in line: | ||
| 44 | line = line[:line.index('#')] | ||
| 45 | |||
| 46 | if continuation: | ||
| 47 | line = continuation + line | ||
| 48 | continuation = None | ||
| 49 | |||
| 50 | if line: | ||
| 51 | if line[-1] == '\\': | ||
| 52 | continuation = line[:-1] | ||
| 53 | continue | ||
| 54 | |||
| 55 | if line and '=' not in line: | ||
| 56 | errors.append(f'Non-assignment code on line +{i} {rules_mk}: {line}') | ||
| 57 | |||
| 58 | return errors | ||
| 59 | |||
| 60 | |||
| 61 | @cli.argument('--strict', action='store_true', help='Treat warnings as errors') | ||
| 62 | @cli.argument('-kb', '--keyboard', completer=keyboard_completer, help='Comma separated list of keyboards to check') | ||
| 63 | @cli.argument('-km', '--keymap', help='The keymap to check') | ||
| 64 | @cli.argument('--all-kb', action='store_true', arg_only=True, help='Check all keyboards') | ||
| 15 | @cli.subcommand('Check keyboard and keymap for common mistakes.') | 65 | @cli.subcommand('Check keyboard and keymap for common mistakes.') |
| 16 | @automagic_keyboard | 66 | @automagic_keyboard |
| 17 | @automagic_keymap | 67 | @automagic_keymap |
| 18 | def lint(cli): | 68 | def lint(cli): |
| 19 | """Check keyboard and keymap for common mistakes. | 69 | """Check keyboard and keymap for common mistakes. |
| 20 | """ | 70 | """ |
| 21 | if not cli.config.lint.keyboard: | 71 | failed = [] |
| 22 | cli.log.error('Missing required argument: --keyboard') | ||
| 23 | cli.print_help() | ||
| 24 | return False | ||
| 25 | 72 | ||
| 26 | if not is_keyboard(cli.config.lint.keyboard): | 73 | # Determine our keyboard list |
| 27 | cli.log.error('No such keyboard: %s', cli.config.lint.keyboard) | 74 | if cli.args.all_kb: |
| 28 | return False | 75 | if cli.args.keyboard: |
| 76 | cli.log.warning('Both --all-kb and --keyboard passed, --all-kb takes presidence.') | ||
| 29 | 77 | ||
| 30 | # Gather data about the keyboard. | 78 | keyboard_list = list_keyboards() |
| 31 | ok = True | 79 | elif not cli.config.lint.keyboard: |
| 32 | keyboard_path = keyboard(cli.config.lint.keyboard) | 80 | cli.log.error('Missing required arguments: --keyboard or --all-kb') |
| 33 | keyboard_info = info_json(cli.config.lint.keyboard) | 81 | cli.print_help() |
| 34 | readme_path = find_readme(cli.config.lint.keyboard) | 82 | return False |
| 35 | missing_readme_path = keyboard_path / 'readme.md' | 83 | else: |
| 84 | keyboard_list = cli.config.lint.keyboard.split(',') | ||
| 36 | 85 | ||
| 37 | # Check for errors in the info.json | 86 | # Lint each keyboard |
| 38 | if keyboard_info['parse_errors']: | 87 | for kb in keyboard_list: |
| 39 | ok = False | 88 | if not is_keyboard(kb): |
| 40 | cli.log.error('Errors found when generating info.json.') | 89 | cli.log.error('No such keyboard: %s', kb) |
| 90 | continue | ||
| 41 | 91 | ||
| 42 | if cli.config.lint.strict and keyboard_info['parse_warnings']: | 92 | # Gather data about the keyboard. |
| 43 | ok = False | 93 | ok = True |
| 44 | cli.log.error('Warnings found when generating info.json (Strict mode enabled.)') | 94 | keyboard_path = keyboard(kb) |
| 95 | keyboard_info = info_json(kb) | ||
| 45 | 96 | ||
| 46 | # Check for a readme.md and warn if it doesn't exist | 97 | # Check for errors in the info.json |
| 47 | if not readme_path: | 98 | if keyboard_info['parse_errors']: |
| 48 | ok = False | 99 | ok = False |
| 49 | cli.log.error('Missing %s', missing_readme_path) | 100 | cli.log.error('%s: Errors found when generating info.json.', kb) |
| 50 | 101 | ||
| 51 | # Keymap specific checks | 102 | if cli.config.lint.strict and keyboard_info['parse_warnings']: |
| 52 | if cli.config.lint.keymap: | 103 | ok = False |
| 53 | keymap_path = locate_keymap(cli.config.lint.keyboard, cli.config.lint.keymap) | 104 | cli.log.error('%s: Warnings found when generating info.json (Strict mode enabled.)', kb) |
| 54 | 105 | ||
| 55 | if not keymap_path: | 106 | # Check the rules.mk file(s) |
| 107 | rules_mk_assignment_errors = rules_mk_assignment_only(keyboard_path) | ||
| 108 | if rules_mk_assignment_errors: | ||
| 56 | ok = False | 109 | ok = False |
| 57 | cli.log.error("Can't find %s keymap for %s keyboard.", cli.config.lint.keymap, cli.config.lint.keyboard) | 110 | cli.log.error('%s: Non-assignment code found in rules.mk. Move it to post_rules.mk instead.', kb) |
| 58 | else: | 111 | for assignment_error in rules_mk_assignment_errors: |
| 59 | keymap_readme = keymap_path.parent / 'readme.md' | 112 | cli.log.error(assignment_error) |
| 60 | if not keymap_readme.exists(): | ||
| 61 | cli.log.warning('Missing %s', keymap_readme) | ||
| 62 | 113 | ||
| 63 | if cli.config.lint.strict: | 114 | # Keymap specific checks |
| 64 | ok = False | 115 | if cli.config.lint.keymap: |
| 116 | if not keymap_check(kb, cli.config.lint.keymap): | ||
| 117 | ok = False | ||
| 118 | |||
| 119 | # Report status | ||
| 120 | if not ok: | ||
| 121 | failed.append(kb) | ||
| 65 | 122 | ||
| 66 | # Check and report the overall status | 123 | # Check and report the overall status |
| 67 | if ok: | 124 | if failed: |
| 68 | cli.log.info('Lint check passed!') | 125 | cli.log.error('Lint check failed for: %s', ', '.join(failed)) |
| 69 | return True | 126 | return False |
| 70 | 127 | ||
| 71 | cli.log.error('Lint check failed!') | 128 | cli.log.info('Lint check passed!') |
| 72 | return False | 129 | return True |
diff --git a/lib/python/qmk/cli/pytest.py b/lib/python/qmk/cli/pytest.py index bdb336b9a..a7f01a872 100644 --- a/lib/python/qmk/cli/pytest.py +++ b/lib/python/qmk/cli/pytest.py | |||
| @@ -12,6 +12,6 @@ def pytest(cli): | |||
| 12 | """Run several linting/testing commands. | 12 | """Run several linting/testing commands. |
| 13 | """ | 13 | """ |
| 14 | nose2 = cli.run(['nose2', '-v'], capture_output=False, stdin=DEVNULL) | 14 | nose2 = cli.run(['nose2', '-v'], capture_output=False, stdin=DEVNULL) |
| 15 | flake8 = cli.run(['flake8', 'lib/python', 'bin/qmk'], capture_output=False, stdin=DEVNULL) | 15 | flake8 = cli.run(['flake8', 'lib/python'], capture_output=False, stdin=DEVNULL) |
| 16 | 16 | ||
| 17 | return flake8.returncode | nose2.returncode | 17 | return flake8.returncode | nose2.returncode |
diff --git a/lib/python/qmk/commands.py b/lib/python/qmk/commands.py index 421453d83..5a0194377 100644 --- a/lib/python/qmk/commands.py +++ b/lib/python/qmk/commands.py | |||
| @@ -28,7 +28,7 @@ def _find_make(): | |||
| 28 | return make_cmd | 28 | return make_cmd |
| 29 | 29 | ||
| 30 | 30 | ||
| 31 | def create_make_target(target, parallel=1, **env_vars): | 31 | def create_make_target(target, dry_run=False, parallel=1, **env_vars): |
| 32 | """Create a make command | 32 | """Create a make command |
| 33 | 33 | ||
| 34 | Args: | 34 | Args: |
| @@ -36,6 +36,9 @@ def create_make_target(target, parallel=1, **env_vars): | |||
| 36 | target | 36 | target |
| 37 | Usually a make rule, such as 'clean' or 'all'. | 37 | Usually a make rule, such as 'clean' or 'all'. |
| 38 | 38 | ||
| 39 | dry_run | ||
| 40 | make -n -- don't actually build | ||
| 41 | |||
| 39 | parallel | 42 | parallel |
| 40 | The number of make jobs to run in parallel | 43 | The number of make jobs to run in parallel |
| 41 | 44 | ||
| @@ -52,10 +55,10 @@ def create_make_target(target, parallel=1, **env_vars): | |||
| 52 | for key, value in env_vars.items(): | 55 | for key, value in env_vars.items(): |
| 53 | env.append(f'{key}={value}') | 56 | env.append(f'{key}={value}') |
| 54 | 57 | ||
| 55 | return [make_cmd, *get_make_parallel_args(parallel), *env, target] | 58 | return [make_cmd, *(['-n'] if dry_run else []), *get_make_parallel_args(parallel), *env, target] |
| 56 | 59 | ||
| 57 | 60 | ||
| 58 | def create_make_command(keyboard, keymap, target=None, parallel=1, **env_vars): | 61 | def create_make_command(keyboard, keymap, target=None, dry_run=False, parallel=1, **env_vars): |
| 59 | """Create a make compile command | 62 | """Create a make compile command |
| 60 | 63 | ||
| 61 | Args: | 64 | Args: |
| @@ -69,6 +72,9 @@ def create_make_command(keyboard, keymap, target=None, parallel=1, **env_vars): | |||
| 69 | target | 72 | target |
| 70 | Usually a bootloader. | 73 | Usually a bootloader. |
| 71 | 74 | ||
| 75 | dry_run | ||
| 76 | make -n -- don't actually build | ||
| 77 | |||
| 72 | parallel | 78 | parallel |
| 73 | The number of make jobs to run in parallel | 79 | The number of make jobs to run in parallel |
| 74 | 80 | ||
| @@ -84,7 +90,7 @@ def create_make_command(keyboard, keymap, target=None, parallel=1, **env_vars): | |||
| 84 | if target: | 90 | if target: |
| 85 | make_args.append(target) | 91 | make_args.append(target) |
| 86 | 92 | ||
| 87 | return create_make_target(':'.join(make_args), parallel, **env_vars) | 93 | return create_make_target(':'.join(make_args), dry_run=dry_run, parallel=parallel, **env_vars) |
| 88 | 94 | ||
| 89 | 95 | ||
| 90 | def get_git_version(current_time, repo_dir='.', check_dir='.'): | 96 | def get_git_version(current_time, repo_dir='.', check_dir='.'): |
| @@ -184,7 +190,7 @@ def compile_configurator_json(user_keymap, bootloader=None, parallel=1, **env_va | |||
| 184 | target = f'{keyboard_filesafe}_{user_keymap["keymap"]}' | 190 | target = f'{keyboard_filesafe}_{user_keymap["keymap"]}' |
| 185 | keyboard_output = Path(f'{KEYBOARD_OUTPUT_PREFIX}{keyboard_filesafe}') | 191 | keyboard_output = Path(f'{KEYBOARD_OUTPUT_PREFIX}{keyboard_filesafe}') |
| 186 | keymap_output = Path(f'{keyboard_output}_{user_keymap["keymap"]}') | 192 | keymap_output = Path(f'{keyboard_output}_{user_keymap["keymap"]}') |
| 187 | c_text = qmk.keymap.generate_c(user_keymap['keyboard'], user_keymap['layout'], user_keymap['layers']) | 193 | c_text = qmk.keymap.generate_c(user_keymap) |
| 188 | keymap_dir = keymap_output / 'src' | 194 | keymap_dir = keymap_output / 'src' |
| 189 | keymap_c = keymap_dir / 'keymap.c' | 195 | keymap_c = keymap_dir / 'keymap.c' |
| 190 | 196 | ||
| @@ -233,7 +239,7 @@ def compile_configurator_json(user_keymap, bootloader=None, parallel=1, **env_va | |||
| 233 | f'VERBOSE={verbose}', | 239 | f'VERBOSE={verbose}', |
| 234 | f'COLOR={color}', | 240 | f'COLOR={color}', |
| 235 | 'SILENT=false', | 241 | 'SILENT=false', |
| 236 | f'QMK_BIN={"bin/qmk" if "DEPRECATED_BIN_QMK" in os.environ else "qmk"}', | 242 | 'QMK_BIN="qmk"', |
| 237 | ]) | 243 | ]) |
| 238 | 244 | ||
| 239 | return make_command | 245 | return make_command |
diff --git a/lib/python/qmk/constants.py b/lib/python/qmk/constants.py index 71a6c91c7..73f596ba2 100644 --- a/lib/python/qmk/constants.py +++ b/lib/python/qmk/constants.py | |||
| @@ -13,7 +13,7 @@ QMK_FIRMWARE_UPSTREAM = 'qmk/qmk_firmware' | |||
| 13 | MAX_KEYBOARD_SUBFOLDERS = 5 | 13 | MAX_KEYBOARD_SUBFOLDERS = 5 |
| 14 | 14 | ||
| 15 | # Supported processor types | 15 | # Supported processor types |
| 16 | CHIBIOS_PROCESSORS = 'cortex-m0', 'cortex-m0plus', 'cortex-m3', 'cortex-m4', 'MKL26Z64', 'MK20DX128', 'MK20DX256', 'MK66F18', 'STM32F042', 'STM32F072', 'STM32F103', 'STM32F303', 'STM32F401', 'STM32F407', 'STM32F411', 'STM32F446', 'STM32G431', 'STM32G474', 'STM32L412', 'STM32L422', 'STM32L433', 'STM32L443' | 16 | CHIBIOS_PROCESSORS = 'cortex-m0', 'cortex-m0plus', 'cortex-m3', 'cortex-m4', 'MKL26Z64', 'MK20DX128', 'MK20DX256', 'MK66FX1M0', 'STM32F042', 'STM32F072', 'STM32F103', 'STM32F303', 'STM32F401', 'STM32F407', 'STM32F411', 'STM32F446', 'STM32G431', 'STM32G474', 'STM32L412', 'STM32L422', 'STM32L433', 'STM32L443', 'GD32VF103' |
| 17 | LUFA_PROCESSORS = 'at90usb162', 'atmega16u2', 'atmega32u2', 'atmega16u4', 'atmega32u4', 'at90usb646', 'at90usb647', 'at90usb1286', 'at90usb1287', None | 17 | LUFA_PROCESSORS = 'at90usb162', 'atmega16u2', 'atmega32u2', 'atmega16u4', 'atmega32u4', 'at90usb646', 'at90usb647', 'at90usb1286', 'at90usb1287', None |
| 18 | VUSB_PROCESSORS = 'atmega32a', 'atmega328p', 'atmega328', 'attiny85' | 18 | VUSB_PROCESSORS = 'atmega32a', 'atmega328p', 'atmega328', 'attiny85' |
| 19 | 19 | ||
diff --git a/lib/python/qmk/info.py b/lib/python/qmk/info.py index cc9948451..dc42fdd4d 100644 --- a/lib/python/qmk/info.py +++ b/lib/python/qmk/info.py | |||
| @@ -122,11 +122,6 @@ def _extract_features(info_data, rules): | |||
| 122 | if rules.get('BOOTMAGIC_ENABLE') == 'full': | 122 | if rules.get('BOOTMAGIC_ENABLE') == 'full': |
| 123 | rules['BOOTMAGIC_ENABLE'] = 'on' | 123 | rules['BOOTMAGIC_ENABLE'] = 'on' |
| 124 | 124 | ||
| 125 | # Skip non-boolean features we haven't implemented special handling for | ||
| 126 | for feature in 'HAPTIC_ENABLE', 'QWIIC_ENABLE': | ||
| 127 | if rules.get(feature): | ||
| 128 | del rules[feature] | ||
| 129 | |||
| 130 | # Process the rest of the rules as booleans | 125 | # Process the rest of the rules as booleans |
| 131 | for key, value in rules.items(): | 126 | for key, value in rules.items(): |
| 132 | if key.endswith('_ENABLE'): | 127 | if key.endswith('_ENABLE'): |
diff --git a/lib/python/qmk/keymap.py b/lib/python/qmk/keymap.py index 6eec49cfd..00b5a78a5 100644 --- a/lib/python/qmk/keymap.py +++ b/lib/python/qmk/keymap.py | |||
| @@ -17,6 +17,7 @@ from qmk.errors import CppError | |||
| 17 | 17 | ||
| 18 | # The `keymap.c` template to use when a keyboard doesn't have its own | 18 | # The `keymap.c` template to use when a keyboard doesn't have its own |
| 19 | DEFAULT_KEYMAP_C = """#include QMK_KEYBOARD_H | 19 | DEFAULT_KEYMAP_C = """#include QMK_KEYBOARD_H |
| 20 | __INCLUDES__ | ||
| 20 | 21 | ||
| 21 | /* THIS FILE WAS GENERATED! | 22 | /* THIS FILE WAS GENERATED! |
| 22 | * | 23 | * |
| @@ -27,6 +28,7 @@ DEFAULT_KEYMAP_C = """#include QMK_KEYBOARD_H | |||
| 27 | const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = { | 28 | const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = { |
| 28 | __KEYMAP_GOES_HERE__ | 29 | __KEYMAP_GOES_HERE__ |
| 29 | }; | 30 | }; |
| 31 | |||
| 30 | """ | 32 | """ |
| 31 | 33 | ||
| 32 | 34 | ||
| @@ -180,10 +182,11 @@ def generate_json(keymap, keyboard, layout, layers): | |||
| 180 | return new_keymap | 182 | return new_keymap |
| 181 | 183 | ||
| 182 | 184 | ||
| 183 | def generate_c(keyboard, layout, layers): | 185 | def generate_c(keymap_json): |
| 184 | """Returns a `keymap.c` or `keymap.json` for the specified keyboard, layout, and layers. | 186 | """Returns a `keymap.c`. |
| 187 | |||
| 188 | `keymap_json` is a dictionary with the following keys: | ||
| 185 | 189 | ||
| 186 | Args: | ||
| 187 | keyboard | 190 | keyboard |
| 188 | The name of the keyboard | 191 | The name of the keyboard |
| 189 | 192 | ||
| @@ -192,19 +195,89 @@ def generate_c(keyboard, layout, layers): | |||
| 192 | 195 | ||
| 193 | layers | 196 | layers |
| 194 | An array of arrays describing the keymap. Each item in the inner array should be a string that is a valid QMK keycode. | 197 | An array of arrays describing the keymap. Each item in the inner array should be a string that is a valid QMK keycode. |
| 198 | |||
| 199 | macros | ||
| 200 | A sequence of strings containing macros to implement for this keyboard. | ||
| 195 | """ | 201 | """ |
| 196 | new_keymap = template_c(keyboard) | 202 | new_keymap = template_c(keymap_json['keyboard']) |
| 197 | layer_txt = [] | 203 | layer_txt = [] |
| 198 | for layer_num, layer in enumerate(layers): | 204 | |
| 205 | for layer_num, layer in enumerate(keymap_json['layers']): | ||
| 199 | if layer_num != 0: | 206 | if layer_num != 0: |
| 200 | layer_txt[-1] = layer_txt[-1] + ',' | 207 | layer_txt[-1] = layer_txt[-1] + ',' |
| 201 | layer = map(_strip_any, layer) | 208 | layer = map(_strip_any, layer) |
| 202 | layer_keys = ', '.join(layer) | 209 | layer_keys = ', '.join(layer) |
| 203 | layer_txt.append('\t[%s] = %s(%s)' % (layer_num, layout, layer_keys)) | 210 | layer_txt.append('\t[%s] = %s(%s)' % (layer_num, keymap_json['layout'], layer_keys)) |
| 204 | 211 | ||
| 205 | keymap = '\n'.join(layer_txt) | 212 | keymap = '\n'.join(layer_txt) |
| 206 | new_keymap = new_keymap.replace('__KEYMAP_GOES_HERE__', keymap) | 213 | new_keymap = new_keymap.replace('__KEYMAP_GOES_HERE__', keymap) |
| 207 | 214 | ||
| 215 | if keymap_json.get('macros'): | ||
| 216 | macro_txt = [ | ||
| 217 | 'bool process_record_user(uint16_t keycode, keyrecord_t *record) {', | ||
| 218 | ' if (record->event.pressed) {', | ||
| 219 | ' switch (keycode) {', | ||
| 220 | ] | ||
| 221 | |||
| 222 | for i, macro_array in enumerate(keymap_json['macros']): | ||
| 223 | macro = [] | ||
| 224 | |||
| 225 | for macro_fragment in macro_array: | ||
| 226 | if isinstance(macro_fragment, str): | ||
| 227 | macro_fragment = macro_fragment.replace('\\', '\\\\') | ||
| 228 | macro_fragment = macro_fragment.replace('\r\n', r'\n') | ||
| 229 | macro_fragment = macro_fragment.replace('\n', r'\n') | ||
| 230 | macro_fragment = macro_fragment.replace('\r', r'\n') | ||
| 231 | macro_fragment = macro_fragment.replace('\t', r'\t') | ||
| 232 | macro_fragment = macro_fragment.replace('"', r'\"') | ||
| 233 | |||
| 234 | macro.append(f'"{macro_fragment}"') | ||
| 235 | |||
| 236 | elif isinstance(macro_fragment, dict): | ||
| 237 | newstring = [] | ||
| 238 | |||
| 239 | if macro_fragment['action'] == 'delay': | ||
| 240 | newstring.append(f"SS_DELAY({macro_fragment['duration']})") | ||
| 241 | |||
| 242 | elif macro_fragment['action'] == 'beep': | ||
| 243 | newstring.append(r'"\a"') | ||
| 244 | |||
| 245 | elif macro_fragment['action'] == 'tap' and len(macro_fragment['keycodes']) > 1: | ||
| 246 | last_keycode = macro_fragment['keycodes'].pop() | ||
| 247 | |||
| 248 | for keycode in macro_fragment['keycodes']: | ||
| 249 | newstring.append(f'SS_DOWN(X_{keycode})') | ||
| 250 | |||
| 251 | newstring.append(f'SS_TAP(X_{last_keycode})') | ||
| 252 | |||
| 253 | for keycode in reversed(macro_fragment['keycodes']): | ||
| 254 | newstring.append(f'SS_UP(X_{keycode})') | ||
| 255 | |||
| 256 | else: | ||
| 257 | for keycode in macro_fragment['keycodes']: | ||
| 258 | newstring.append(f"SS_{macro_fragment['action'].upper()}(X_{keycode})") | ||
| 259 | |||
| 260 | macro.append(''.join(newstring)) | ||
| 261 | |||
| 262 | new_macro = "".join(macro) | ||
| 263 | new_macro = new_macro.replace('""', '') | ||
| 264 | macro_txt.append(f' case MACRO_{i}:') | ||
| 265 | macro_txt.append(f' SEND_STRING({new_macro});') | ||
| 266 | macro_txt.append(' return false;') | ||
| 267 | |||
| 268 | macro_txt.append(' }') | ||
| 269 | macro_txt.append(' }') | ||
| 270 | macro_txt.append('\n return true;') | ||
| 271 | macro_txt.append('};') | ||
| 272 | macro_txt.append('') | ||
| 273 | |||
| 274 | new_keymap = '\n'.join((new_keymap, *macro_txt)) | ||
| 275 | |||
| 276 | if keymap_json.get('host_language'): | ||
| 277 | new_keymap = new_keymap.replace('__INCLUDES__', f'#include "keymap_{keymap_json["host_language"]}.h"\n#include "sendstring_{keymap_json["host_language"]}.h"\n') | ||
| 278 | else: | ||
| 279 | new_keymap = new_keymap.replace('__INCLUDES__', '') | ||
| 280 | |||
| 208 | return new_keymap | 281 | return new_keymap |
| 209 | 282 | ||
| 210 | 283 | ||
| @@ -217,7 +290,7 @@ def write_file(keymap_filename, keymap_content): | |||
| 217 | return keymap_filename | 290 | return keymap_filename |
| 218 | 291 | ||
| 219 | 292 | ||
| 220 | def write_json(keyboard, keymap, layout, layers): | 293 | def write_json(keyboard, keymap, layout, layers, macros=None): |
| 221 | """Generate the `keymap.json` and write it to disk. | 294 | """Generate the `keymap.json` and write it to disk. |
| 222 | 295 | ||
| 223 | Returns the filename written to. | 296 | Returns the filename written to. |
| @@ -235,19 +308,19 @@ def write_json(keyboard, keymap, layout, layers): | |||
| 235 | layers | 308 | layers |
| 236 | An array of arrays describing the keymap. Each item in the inner array should be a string that is a valid QMK keycode. | 309 | An array of arrays describing the keymap. Each item in the inner array should be a string that is a valid QMK keycode. |
| 237 | """ | 310 | """ |
| 238 | keymap_json = generate_json(keyboard, keymap, layout, layers) | 311 | keymap_json = generate_json(keyboard, keymap, layout, layers, macros=None) |
| 239 | keymap_content = json.dumps(keymap_json) | 312 | keymap_content = json.dumps(keymap_json) |
| 240 | keymap_file = qmk.path.keymap(keyboard) / keymap / 'keymap.json' | 313 | keymap_file = qmk.path.keymap(keyboard) / keymap / 'keymap.json' |
| 241 | 314 | ||
| 242 | return write_file(keymap_file, keymap_content) | 315 | return write_file(keymap_file, keymap_content) |
| 243 | 316 | ||
| 244 | 317 | ||
| 245 | def write(keyboard, keymap, layout, layers): | 318 | def write(keymap_json): |
| 246 | """Generate the `keymap.c` and write it to disk. | 319 | """Generate the `keymap.c` and write it to disk. |
| 247 | 320 | ||
| 248 | Returns the filename written to. | 321 | Returns the filename written to. |
| 249 | 322 | ||
| 250 | Args: | 323 | `keymap_json` should be a dict with the following keys: |
| 251 | keyboard | 324 | keyboard |
| 252 | The name of the keyboard | 325 | The name of the keyboard |
| 253 | 326 | ||
| @@ -259,9 +332,12 @@ def write(keyboard, keymap, layout, layers): | |||
| 259 | 332 | ||
| 260 | layers | 333 | layers |
| 261 | An array of arrays describing the keymap. Each item in the inner array should be a string that is a valid QMK keycode. | 334 | An array of arrays describing the keymap. Each item in the inner array should be a string that is a valid QMK keycode. |
| 335 | |||
| 336 | macros | ||
| 337 | A list of macros for this keymap. | ||
| 262 | """ | 338 | """ |
| 263 | keymap_content = generate_c(keyboard, layout, layers) | 339 | keymap_content = generate_c(keymap_json) |
| 264 | keymap_file = qmk.path.keymap(keyboard) / keymap / 'keymap.c' | 340 | keymap_file = qmk.path.keymap(keymap_json['keyboard']) / keymap_json['keymap'] / 'keymap.c' |
| 265 | 341 | ||
| 266 | return write_file(keymap_file, keymap_content) | 342 | return write_file(keymap_file, keymap_content) |
| 267 | 343 | ||
diff --git a/lib/python/qmk/tests/minimal_info.json b/lib/python/qmk/tests/minimal_info.json index 11ef12fef..3aae4722b 100644 --- a/lib/python/qmk/tests/minimal_info.json +++ b/lib/python/qmk/tests/minimal_info.json | |||
| @@ -4,7 +4,7 @@ | |||
| 4 | "layouts": { | 4 | "layouts": { |
| 5 | "LAYOUT": { | 5 | "LAYOUT": { |
| 6 | "layout": [ | 6 | "layout": [ |
| 7 | { "label": "KC_A", "x": 0, "y": 0, "matrix": [0, 0] } | 7 | { "label": "KC_A", "matrix": [0, 0], "x": 0, "y": 0 } |
| 8 | ] | 8 | ] |
| 9 | } | 9 | } |
| 10 | } | 10 | } |
diff --git a/lib/python/qmk/tests/test_cli_commands.py b/lib/python/qmk/tests/test_cli_commands.py index 0dad5d5fc..2973f8170 100644 --- a/lib/python/qmk/tests/test_cli_commands.py +++ b/lib/python/qmk/tests/test_cli_commands.py | |||
| @@ -142,6 +142,14 @@ def test_json2c(): | |||
| 142 | assert result.stdout == '#include QMK_KEYBOARD_H\nconst uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {\t[0] = LAYOUT_ortho_1x1(KC_A)};\n\n' | 142 | assert result.stdout == '#include QMK_KEYBOARD_H\nconst uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {\t[0] = LAYOUT_ortho_1x1(KC_A)};\n\n' |
| 143 | 143 | ||
| 144 | 144 | ||
| 145 | def test_json2c_macros(): | ||
| 146 | result = check_subcommand("json2c", 'keyboards/handwired/pytest/macro/keymaps/default/keymap.json') | ||
| 147 | check_returncode(result) | ||
| 148 | assert 'LAYOUT_ortho_1x1(MACRO_0)' in result.stdout | ||
| 149 | assert 'case MACRO_0:' in result.stdout | ||
| 150 | assert 'SEND_STRING("Hello, World!"SS_TAP(X_ENTER));' in result.stdout | ||
| 151 | |||
| 152 | |||
| 145 | def test_json2c_stdin(): | 153 | def test_json2c_stdin(): |
| 146 | result = check_subcommand_stdin('keyboards/handwired/pytest/has_template/keymaps/default_json/keymap.json', 'json2c', '-') | 154 | result = check_subcommand_stdin('keyboards/handwired/pytest/has_template/keymaps/default_json/keymap.json', 'json2c', '-') |
| 147 | check_returncode(result) | 155 | check_returncode(result) |
| @@ -151,7 +159,7 @@ def test_json2c_stdin(): | |||
| 151 | def test_info(): | 159 | def test_info(): |
| 152 | result = check_subcommand('info', '-kb', 'handwired/pytest/basic') | 160 | result = check_subcommand('info', '-kb', 'handwired/pytest/basic') |
| 153 | check_returncode(result) | 161 | check_returncode(result) |
| 154 | assert 'Keyboard Name: handwired/pytest/basic' in result.stdout | 162 | assert 'Keyboard Name: pytest' in result.stdout |
| 155 | assert 'Processor: atmega32u4' in result.stdout | 163 | assert 'Processor: atmega32u4' in result.stdout |
| 156 | assert 'Layout:' not in result.stdout | 164 | assert 'Layout:' not in result.stdout |
| 157 | assert 'k0' not in result.stdout | 165 | assert 'k0' not in result.stdout |
| @@ -160,7 +168,7 @@ def test_info(): | |||
| 160 | def test_info_keyboard_render(): | 168 | def test_info_keyboard_render(): |
| 161 | result = check_subcommand('info', '-kb', 'handwired/pytest/basic', '-l') | 169 | result = check_subcommand('info', '-kb', 'handwired/pytest/basic', '-l') |
| 162 | check_returncode(result) | 170 | check_returncode(result) |
| 163 | assert 'Keyboard Name: handwired/pytest/basic' in result.stdout | 171 | assert 'Keyboard Name: pytest' in result.stdout |
| 164 | assert 'Processor: atmega32u4' in result.stdout | 172 | assert 'Processor: atmega32u4' in result.stdout |
| 165 | assert 'Layouts:' in result.stdout | 173 | assert 'Layouts:' in result.stdout |
| 166 | assert 'k0' in result.stdout | 174 | assert 'k0' in result.stdout |
| @@ -169,7 +177,7 @@ def test_info_keyboard_render(): | |||
| 169 | def test_info_keymap_render(): | 177 | def test_info_keymap_render(): |
| 170 | result = check_subcommand('info', '-kb', 'handwired/pytest/basic', '-km', 'default_json') | 178 | result = check_subcommand('info', '-kb', 'handwired/pytest/basic', '-km', 'default_json') |
| 171 | check_returncode(result) | 179 | check_returncode(result) |
| 172 | assert 'Keyboard Name: handwired/pytest/basic' in result.stdout | 180 | assert 'Keyboard Name: pytest' in result.stdout |
| 173 | assert 'Processor: atmega32u4' in result.stdout | 181 | assert 'Processor: atmega32u4' in result.stdout |
| 174 | 182 | ||
| 175 | if is_windows: | 183 | if is_windows: |
| @@ -181,7 +189,7 @@ def test_info_keymap_render(): | |||
| 181 | def test_info_matrix_render(): | 189 | def test_info_matrix_render(): |
| 182 | result = check_subcommand('info', '-kb', 'handwired/pytest/basic', '-m') | 190 | result = check_subcommand('info', '-kb', 'handwired/pytest/basic', '-m') |
| 183 | check_returncode(result) | 191 | check_returncode(result) |
| 184 | assert 'Keyboard Name: handwired/pytest/basic' in result.stdout | 192 | assert 'Keyboard Name: pytest' in result.stdout |
| 185 | assert 'Processor: atmega32u4' in result.stdout | 193 | assert 'Processor: atmega32u4' in result.stdout |
| 186 | assert 'LAYOUT_ortho_1x1' in result.stdout | 194 | assert 'LAYOUT_ortho_1x1' in result.stdout |
| 187 | 195 | ||
| @@ -242,7 +250,7 @@ def test_generate_config_h(): | |||
| 242 | assert '# define DESCRIPTION handwired/pytest/basic' in result.stdout | 250 | assert '# define DESCRIPTION handwired/pytest/basic' in result.stdout |
| 243 | assert '# define DIODE_DIRECTION COL2ROW' in result.stdout | 251 | assert '# define DIODE_DIRECTION COL2ROW' in result.stdout |
| 244 | assert '# define MANUFACTURER none' in result.stdout | 252 | assert '# define MANUFACTURER none' in result.stdout |
| 245 | assert '# define PRODUCT handwired/pytest/basic' in result.stdout | 253 | assert '# define PRODUCT pytest' in result.stdout |
| 246 | assert '# define PRODUCT_ID 0x6465' in result.stdout | 254 | assert '# define PRODUCT_ID 0x6465' in result.stdout |
| 247 | assert '# define VENDOR_ID 0xFEED' in result.stdout | 255 | assert '# define VENDOR_ID 0xFEED' in result.stdout |
| 248 | assert '# define MATRIX_COLS 1' in result.stdout | 256 | assert '# define MATRIX_COLS 1' in result.stdout |
diff --git a/lib/python/qmk/tests/test_qmk_keymap.py b/lib/python/qmk/tests/test_qmk_keymap.py index b9e80df67..5e2efc123 100644 --- a/lib/python/qmk/tests/test_qmk_keymap.py +++ b/lib/python/qmk/tests/test_qmk_keymap.py | |||
| @@ -22,7 +22,13 @@ def test_template_json_pytest_has_template(): | |||
| 22 | 22 | ||
| 23 | 23 | ||
| 24 | def test_generate_c_pytest_has_template(): | 24 | def test_generate_c_pytest_has_template(): |
| 25 | templ = qmk.keymap.generate_c('handwired/pytest/has_template', 'LAYOUT', [['KC_A']]) | 25 | keymap_json = { |
| 26 | 'keyboard': 'handwired/pytest/has_template', | ||
| 27 | 'layout': 'LAYOUT', | ||
| 28 | 'layers': [['KC_A']], | ||
| 29 | 'macros': None, | ||
| 30 | } | ||
| 31 | templ = qmk.keymap.generate_c(keymap_json) | ||
| 26 | assert templ == '#include QMK_KEYBOARD_H\nconst uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {\t[0] = LAYOUT(KC_A)};\n' | 32 | assert templ == '#include QMK_KEYBOARD_H\nconst uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {\t[0] = LAYOUT(KC_A)};\n' |
| 27 | 33 | ||
| 28 | 34 | ||
diff --git a/lib/ugfx b/lib/ugfx deleted file mode 160000 | |||
| Subproject 40b48f470addad6a4fb1177de1a69a181158739 | |||
