Skip to content

Commit

Permalink
Use ruff instead of black and flake8 (#599)
Browse files Browse the repository at this point in the history
* Use ruff instead of black and flake8

* ruff format

* Update cases where ruff and black conflict

* Update dependencies

* remove setup.cfg

* Update references to black and flake

* Refactor part of codegen that formats code

* more lint checks

* and more lint/perf fixes

* fix tests

* codegen

* fix test
  • Loading branch information
almarklein authored Sep 26, 2024
1 parent 7cd6ab4 commit cf59eb0
Show file tree
Hide file tree
Showing 73 changed files with 234 additions and 279 deletions.
14 changes: 9 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,16 @@ jobs:
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install dev dependencies
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -U -e .[lint]
- name: Flake8
pip install ruff
- name: Ruff lint
run: |
ruff check --output-format=github .
- name: Ruff format
run: |
flake8 .
ruff format --check .
test-codegen-build:
name: Test Codegen
Expand Down Expand Up @@ -140,7 +143,8 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -U -e .[tests] glfw pyinstaller
pip install -U -e .
pip install -U pytest numpy psutil pyinstaller glfw
- name: Test PyInstaller
run: |
pyinstaller --version
Expand Down
13 changes: 6 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,9 +120,8 @@ This code is distributed under the 2-clause BSD license.
binaries.
* You can use `python tools/download_wgpu_native.py` when needed.
* Or point the `WGPU_LIB_PATH` environment variable to a custom build of `wgpu-native`.
* Use `black .` to apply autoformatting.
* Use `flake8 .` to check for flake errors.
* Use `pytest .` to run the tests.
* Use `ruff format` to apply autoformatting.
* Use `ruff check` to check for linting errors.


### Updating to a later version of WebGPU or wgpu-native
Expand All @@ -136,11 +135,11 @@ for more information.

The test suite is divided into multiple parts:

* `pytest -v tests` runs the core unit tests.
* `pytest -v tests` runs the unit tests.
* `pytest -v examples` tests the examples.
* `pytest -v wgpu/__pyinstaller` tests if wgpu is properly supported by
pyinstaller.
* `pytest -v codegen` lints the generated binding code.
* `pytest -v wgpu/__pyinstaller` tests if wgpu is properly supported by pyinstaller.
* `pytest -v codegen` tests the code that autogenerates the API.
* `pytest -v tests_mem` tests against memoryleaks.

There are two types of tests for examples included:

Expand Down
2 changes: 1 addition & 1 deletion codegen/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
sys.path.insert(0, os.path.abspath(os.path.join(__file__, "..", "..")))


from codegen import main, file_cache # noqa: E402
from codegen import main, file_cache


if __name__ == "__main__":
Expand Down
9 changes: 4 additions & 5 deletions codegen/apipatcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
spec (IDL), and the backend implementations from the base API.
"""

from codegen.utils import print, blacken, to_snake_case, to_camel_case, Patcher
from codegen.utils import print, format_code, to_snake_case, to_camel_case, Patcher
from codegen.idlparser import get_idl_parser
from codegen.files import file_cache

Expand Down Expand Up @@ -268,7 +268,6 @@ def __init__(self):
self.detect_async_props_and_methods()

def detect_async_props_and_methods(self):

self.async_idl_names = async_idl_names = {} # (sync-name, async-name)

for classname, interface in self.idl.classes.items():
Expand Down Expand Up @@ -434,13 +433,13 @@ def get_method_def(self, classname, methodname):
py_args = [self._arg_from_struct_field(field) for field in fields]
if py_args[0].startswith("label: str"):
py_args[0] = 'label=""'
py_args = ["self", "*"] + py_args
py_args = ["self", "*", *py_args]
else:
py_args = ["self"] + argnames
py_args = ["self", *argnames]

# Construct final def
line = preamble + ", ".join(py_args) + "): pass\n"
line = blacken(line, True).split("):")[0] + "):"
line = format_code(line, True).split("):")[0] + "):"
return " " + line

def _arg_from_struct_field(self, field):
Expand Down
8 changes: 4 additions & 4 deletions codegen/apiwriter.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import re

from codegen.utils import print, blacken, to_snake_case
from codegen.utils import print, format_code, to_snake_case
from codegen.idlparser import get_idl_parser
from codegen.files import file_cache

Expand Down Expand Up @@ -59,7 +59,7 @@ def write_flags():
pylines.append(f" {key} = {val!r}") # note: can add docs using "#: "
pylines.append("\n")
# Write
code = blacken("\n".join(pylines))
code = format_code("\n".join(pylines))
file_cache.write("flags.py", code)
print(f"Wrote {n} flags to flags.py")

Expand Down Expand Up @@ -88,7 +88,7 @@ def write_enums():
pylines.append(f' {key} = "{val}"') # note: can add docs using "#: "
pylines.append("\n")
# Write
code = blacken("\n".join(pylines))
code = format_code("\n".join(pylines))
file_cache.write("enums.py", code)
print(f"Wrote {n} enums to enums.py")

Expand Down Expand Up @@ -135,6 +135,6 @@ def write_structs():
pylines.append(")\n")

# Write
code = blacken("\n".join(pylines))
code = format_code("\n".join(pylines))
file_cache.write("structs.py", code)
print(f"Wrote {n} structs to structs.py")
2 changes: 1 addition & 1 deletion codegen/idlparser.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ def _remove_comments(self, text):
return "\n".join(lines)

def resolve_type(self, typename):
"""Resolve a type to a suitable name that is also valid so that flake8
"""Resolve a type to a suitable name that is also valid so that the linter
wont complain when this is used as a type annotation.
"""

Expand Down
8 changes: 3 additions & 5 deletions codegen/tests/test_codegen_apipatcher.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
""" Test some parts of apipatcher.py, and Implicitly tests idlparser.py.
"""
"""Test some parts of apipatcher.py, and Implicitly tests idlparser.py."""

from codegen.utils import blacken
from codegen.utils import format_code
from codegen.apipatcher import CommentRemover, AbstractCommentInjector, IdlPatcherMixin


Expand Down Expand Up @@ -101,7 +100,7 @@ def spam(self):
def eggs(self):
pass
"""
code3 = blacken(dedent(code3)).strip()
code3 = format_code(dedent(code3)).strip()

p = MyCommentInjector()
p.apply(dedent(code1))
Expand All @@ -111,7 +110,6 @@ def eggs(self):


def test_async_api_logic():

class Object(object):
pass

Expand Down
3 changes: 1 addition & 2 deletions codegen/tests/test_codegen_result.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
""" Test some aspects of the generated code.
"""
"""Test some aspects of the generated code."""

from codegen.files import read_file

Expand Down
3 changes: 1 addition & 2 deletions codegen/tests/test_codegen_rspatcher.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
""" Test some parts of rsbackend.py, and implicitly tests hparser.py.
"""
"""Test some parts of rsbackend.py, and implicitly tests hparser.py."""

from codegen.wgpu_native_patcher import patch_wgpu_native_backend

Expand Down
22 changes: 11 additions & 11 deletions codegen/tests/test_codegen_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from codegen.utils import (
remove_c_comments,
blacken,
format_code,
Patcher,
to_snake_case,
to_camel_case,
Expand Down Expand Up @@ -59,7 +59,7 @@ def test_remove_c_comments():
assert code2 == code3


def test_blacken_singleline():
def test_format_code_singleline():
code1 = """
def foo():
pass
Expand Down Expand Up @@ -98,20 +98,20 @@ def foo(a1, a2, a3):
code1 = dedent(code1).strip()
code2 = dedent(code2).strip()

code3 = blacken(code1, True)
code3 = format_code(code1, True)
code3 = code3.replace("\n\n", "\n").replace("\n\n", "\n").strip()

assert code3 == code2

# Also test simply long lines
code = "foo = 1" + " + 1" * 100
assert len(code) > 300
code = "foo = 1" + " + 1" * 75
assert len(code) > 300 # Ruff's max line-length is 320
assert code.count("\n") == 0
assert blacken(code, False).strip().count("\n") > 3
assert blacken(code, True).strip().count("\n") == 0
assert format_code(code, False).strip().count("\n") > 3
assert format_code(code, True).strip().count("\n") == 0


def test_blacken_comments():
def test_format_code_comments():
code1 = """
def foo(): # hi
pass
Expand All @@ -133,7 +133,7 @@ def foo(a1, a2, a3): # hi ha ho
code1 = dedent(code1).strip()
code2 = dedent(code2).strip()

code3 = blacken(code1, True)
code3 = format_code(code1, True)
code3 = code3.replace("\n\n", "\n").replace("\n\n", "\n").strip()

assert code3 == code2
Expand All @@ -160,7 +160,7 @@ def bar3(self):
pass
"""

code = blacken(dedent(code))
code = format_code(dedent(code))
p = Patcher(code)

# Dump before doing anything, should yield original
Expand Down Expand Up @@ -201,7 +201,7 @@ def bar3(self):
for line, i in p.iter_lines():
if line.lstrip().startswith("#"):
p.replace_line(i, "# comment")
with raises(Exception):
with raises(AssertionError):
p.replace_line(i, "# comment")
code2 = p.dumps()
assert code2.count("#") == 4
Expand Down
50 changes: 35 additions & 15 deletions codegen/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@
import os
import sys
import tempfile

import black
import subprocess


def to_snake_case(name):
Expand Down Expand Up @@ -48,7 +47,7 @@ def print(*args, **kwargs):
"""Report something (will be printed and added to a file."""
# __builtins__.print(*args, **kwargs)
if args and not args[0].lstrip().startswith("#"):
args = ("*",) + args
args = ("*", *args)
for f in _file_objects_to_print_to:
__builtins__["print"](*args, file=f, flush=True, **kwargs)

Expand Down Expand Up @@ -103,14 +102,35 @@ def remove_c_comments(code):
return new_code


def blacken(src, singleline=False):
"""Format the given src string using black. If singleline is True,
all function signatures become single-line, so they can be parsed
and updated.
class FormatError(Exception):
pass


def format_code(src, singleline=False):
"""Format the given src string. If singleline is True, all function
signatures become single-line, so they can be parsed and updated.
"""
# Normal black
mode = black.FileMode(line_length=999 if singleline else 88)
result = black.format_str(src, mode=mode)

# Use Ruff to format the line. Ruff does not yet have a Python API, so we use its CLI.
tempfilename = os.path.join(tempfile.gettempdir(), "wgpupy_codegen_format.py")
with open(tempfilename, "wb") as fp:
fp.write(src.encode())
line_length = 320 if singleline else 88
cmd = [
sys.executable,
"-m",
"ruff",
"format",
"--line-length",
str(line_length),
tempfilename,
]
p = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
if p.returncode:
raise FormatError(p.stdout.decode(errors="ignore"))
with open(tempfilename, "rb") as fp:
result = fp.read().decode()
os.remove(tempfilename)

# Make defs single-line. You'd think that setting the line length
# to a very high number would do the trick, but it does not.
Expand Down Expand Up @@ -175,7 +195,7 @@ def _init(self, code):
self._diffs = {}
self._classes = {}
if code:
self.lines = blacken(code, True).splitlines() # inf line length
self.lines = format_code(code, True).splitlines() # inf line length

def remove_line(self, i):
"""Remove the line at the given position. There must not have been
Expand Down Expand Up @@ -221,8 +241,8 @@ def dumps(self, format=True):
text = "\n".join(lines)
if format:
try:
text = blacken(text)
except black.InvalidInput as err: # pragma: no cover
text = format_code(text)
except FormatError as err: # pragma: no cover
# If you get this error, it really helps to load the code
# in an IDE to see where the error is. Let's help with that ...
filename = os.path.join(tempfile.gettempdir(), "wgpu_patcher_fail.py")
Expand All @@ -233,8 +253,8 @@ def dumps(self, format=True):
raise RuntimeError(
f"It appears that the patcher has generated invalid Python:"
f"\n\n {err}\n\n"
f'Wrote the generated (but unblackened) code to:\n\n "{filename}"'
)
f'Wrote the generated (but unformatted) code to:\n\n "{filename}"'
) from None

return text

Expand Down
Loading

0 comments on commit cf59eb0

Please sign in to comment.