1# Copyright 2021-2022 Google LLC 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# https://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15""" 16Invoke tasks 17""" 18 19# ----------------------------------------------------------------------------- 20# Imports 21# ----------------------------------------------------------------------------- 22import os 23 24from invoke import task, call, Collection 25from invoke.exceptions import Exit, UnexpectedExit 26 27 28# ----------------------------------------------------------------------------- 29ROOT_DIR = os.path.dirname(os.path.realpath(__file__)) 30 31ns = Collection() 32 33 34# ----------------------------------------------------------------------------- 35# Build 36# ----------------------------------------------------------------------------- 37build_tasks = Collection() 38ns.add_collection(build_tasks, name="build") 39 40 41# ----------------------------------------------------------------------------- 42@task 43def build(ctx, install=False): 44 if install: 45 ctx.run('python -m pip install .[build]') 46 47 ctx.run("python -m build") 48 49 50# ----------------------------------------------------------------------------- 51@task 52def release_build(ctx): 53 build(ctx, install=True) 54 55 56# ----------------------------------------------------------------------------- 57@task 58def mkdocs(ctx): 59 ctx.run("mkdocs build -f docs/mkdocs/mkdocs.yml") 60 61 62# ----------------------------------------------------------------------------- 63build_tasks.add_task(build, default=True) 64build_tasks.add_task(release_build, name="release") 65build_tasks.add_task(mkdocs, name="mkdocs") 66 67 68# ----------------------------------------------------------------------------- 69# Test 70# ----------------------------------------------------------------------------- 71test_tasks = Collection() 72ns.add_collection(test_tasks, name="test") 73 74 75# ----------------------------------------------------------------------------- 76@task(incrementable=["verbose"]) 77def test(ctx, match=None, junit=False, install=False, html=False, verbose=0): 78 # Install the package before running the tests 79 if install: 80 ctx.run("python -m pip install .[test]") 81 82 args = "" 83 if junit: 84 args += "--junit-xml test-results.xml" 85 if match is not None: 86 args += f" -k '{match}'" 87 if html: 88 args += " --html results.html" 89 if verbose > 0: 90 args += f" -{'v' * verbose}" 91 ctx.run(f"python -m pytest {os.path.join(ROOT_DIR, 'tests')} {args}") 92 93 94# ----------------------------------------------------------------------------- 95@task 96def release_test(ctx): 97 test(ctx, install=True) 98 99 100# ----------------------------------------------------------------------------- 101test_tasks.add_task(test, default=True) 102test_tasks.add_task(release_test, name="release") 103 104# ----------------------------------------------------------------------------- 105# Project 106# ----------------------------------------------------------------------------- 107project_tasks = Collection() 108ns.add_collection(project_tasks, name="project") 109 110 111# ----------------------------------------------------------------------------- 112@task 113def lint(ctx, disable='C,R', errors_only=False): 114 options = [] 115 if disable: 116 options.append(f"--disable={disable}") 117 if errors_only: 118 options.append("-E") 119 120 if errors_only: 121 qualifier = ' (errors only)' 122 else: 123 qualifier = f' (disabled: {disable})' if disable else '' 124 125 print(f">>> Running the linter{qualifier}...") 126 try: 127 ctx.run(f"pylint {' '.join(options)} bumble apps examples tasks.py") 128 print("The linter is happy. ✅ '") 129 except UnexpectedExit as exc: 130 print("Please check your code against the linter messages. ❌") 131 raise Exit(code=1) from exc 132 133 134# ----------------------------------------------------------------------------- 135@task 136def format_code(ctx, check=False, diff=False): 137 options = [] 138 if check: 139 options.append("--check") 140 if diff: 141 options.append("--diff") 142 143 print(">>> Running the formatter...") 144 try: 145 ctx.run(f"black -S {' '.join(options)} .") 146 except UnexpectedExit as exc: 147 print("Please run 'invoke project.format' or 'black .' to format the code. ❌") 148 raise Exit(code=1) from exc 149 150 151# ----------------------------------------------------------------------------- 152@task 153def check_types(ctx): 154 checklist = ["apps", "bumble", "examples", "tests", "tasks.py"] 155 try: 156 ctx.run(f"mypy {' '.join(checklist)}") 157 except UnexpectedExit as exc: 158 print("Please check your code against the mypy messages.") 159 raise Exit(code=1) from exc 160 161 162# ----------------------------------------------------------------------------- 163@task( 164 pre=[ 165 call(format_code, check=True), 166 call(lint, errors_only=True), 167 call(check_types), 168 test, 169 ] 170) 171def pre_commit(_ctx): 172 print("All good!") 173 174 175# ----------------------------------------------------------------------------- 176project_tasks.add_task(lint) 177project_tasks.add_task(format_code, name="format") 178project_tasks.add_task(check_types, name="check-types") 179project_tasks.add_task(pre_commit) 180