Skip to content

Commit cf59eb0

Browse files
authored
Use ruff instead of black and flake8 (#599)
* 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
1 parent 7cd6ab4 commit cf59eb0

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

73 files changed

+234
-279
lines changed

.github/workflows/ci.yml

+9-5
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,16 @@ jobs:
2525
uses: actions/setup-python@v5
2626
with:
2727
python-version: '3.12'
28-
- name: Install dev dependencies
28+
- name: Install dependencies
2929
run: |
3030
python -m pip install --upgrade pip
31-
pip install -U -e .[lint]
32-
- name: Flake8
31+
pip install ruff
32+
- name: Ruff lint
33+
run: |
34+
ruff check --output-format=github .
35+
- name: Ruff format
3336
run: |
34-
flake8 .
37+
ruff format --check .
3538
3639
test-codegen-build:
3740
name: Test Codegen
@@ -140,7 +143,8 @@ jobs:
140143
- name: Install dependencies
141144
run: |
142145
python -m pip install --upgrade pip
143-
pip install -U -e .[tests] glfw pyinstaller
146+
pip install -U -e .
147+
pip install -U pytest numpy psutil pyinstaller glfw
144148
- name: Test PyInstaller
145149
run: |
146150
pyinstaller --version

README.md

+6-7
Original file line numberDiff line numberDiff line change
@@ -120,9 +120,8 @@ This code is distributed under the 2-clause BSD license.
120120
binaries.
121121
* You can use `python tools/download_wgpu_native.py` when needed.
122122
* Or point the `WGPU_LIB_PATH` environment variable to a custom build of `wgpu-native`.
123-
* Use `black .` to apply autoformatting.
124-
* Use `flake8 .` to check for flake errors.
125-
* Use `pytest .` to run the tests.
123+
* Use `ruff format` to apply autoformatting.
124+
* Use `ruff check` to check for linting errors.
126125

127126

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

137136
The test suite is divided into multiple parts:
138137

139-
* `pytest -v tests` runs the core unit tests.
138+
* `pytest -v tests` runs the unit tests.
140139
* `pytest -v examples` tests the examples.
141-
* `pytest -v wgpu/__pyinstaller` tests if wgpu is properly supported by
142-
pyinstaller.
143-
* `pytest -v codegen` lints the generated binding code.
140+
* `pytest -v wgpu/__pyinstaller` tests if wgpu is properly supported by pyinstaller.
141+
* `pytest -v codegen` tests the code that autogenerates the API.
142+
* `pytest -v tests_mem` tests against memoryleaks.
144143

145144
There are two types of tests for examples included:
146145

codegen/__main__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
sys.path.insert(0, os.path.abspath(os.path.join(__file__, "..", "..")))
1212

1313

14-
from codegen import main, file_cache # noqa: E402
14+
from codegen import main, file_cache
1515

1616

1717
if __name__ == "__main__":

codegen/apipatcher.py

+4-5
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
spec (IDL), and the backend implementations from the base API.
44
"""
55

6-
from codegen.utils import print, blacken, to_snake_case, to_camel_case, Patcher
6+
from codegen.utils import print, format_code, to_snake_case, to_camel_case, Patcher
77
from codegen.idlparser import get_idl_parser
88
from codegen.files import file_cache
99

@@ -268,7 +268,6 @@ def __init__(self):
268268
self.detect_async_props_and_methods()
269269

270270
def detect_async_props_and_methods(self):
271-
272271
self.async_idl_names = async_idl_names = {} # (sync-name, async-name)
273272

274273
for classname, interface in self.idl.classes.items():
@@ -434,13 +433,13 @@ def get_method_def(self, classname, methodname):
434433
py_args = [self._arg_from_struct_field(field) for field in fields]
435434
if py_args[0].startswith("label: str"):
436435
py_args[0] = 'label=""'
437-
py_args = ["self", "*"] + py_args
436+
py_args = ["self", "*", *py_args]
438437
else:
439-
py_args = ["self"] + argnames
438+
py_args = ["self", *argnames]
440439

441440
# Construct final def
442441
line = preamble + ", ".join(py_args) + "): pass\n"
443-
line = blacken(line, True).split("):")[0] + "):"
442+
line = format_code(line, True).split("):")[0] + "):"
444443
return " " + line
445444

446445
def _arg_from_struct_field(self, field):

codegen/apiwriter.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
import re
66

7-
from codegen.utils import print, blacken, to_snake_case
7+
from codegen.utils import print, format_code, to_snake_case
88
from codegen.idlparser import get_idl_parser
99
from codegen.files import file_cache
1010

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

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

@@ -135,6 +135,6 @@ def write_structs():
135135
pylines.append(")\n")
136136

137137
# Write
138-
code = blacken("\n".join(pylines))
138+
code = format_code("\n".join(pylines))
139139
file_cache.write("structs.py", code)
140140
print(f"Wrote {n} structs to structs.py")

codegen/idlparser.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ def _remove_comments(self, text):
161161
return "\n".join(lines)
162162

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

codegen/tests/test_codegen_apipatcher.py

+3-5
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
""" Test some parts of apipatcher.py, and Implicitly tests idlparser.py.
2-
"""
1+
"""Test some parts of apipatcher.py, and Implicitly tests idlparser.py."""
32

4-
from codegen.utils import blacken
3+
from codegen.utils import format_code
54
from codegen.apipatcher import CommentRemover, AbstractCommentInjector, IdlPatcherMixin
65

76

@@ -101,7 +100,7 @@ def spam(self):
101100
def eggs(self):
102101
pass
103102
"""
104-
code3 = blacken(dedent(code3)).strip()
103+
code3 = format_code(dedent(code3)).strip()
105104

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

112111

113112
def test_async_api_logic():
114-
115113
class Object(object):
116114
pass
117115

codegen/tests/test_codegen_result.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
""" Test some aspects of the generated code.
2-
"""
1+
"""Test some aspects of the generated code."""
32

43
from codegen.files import read_file
54

codegen/tests/test_codegen_rspatcher.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
""" Test some parts of rsbackend.py, and implicitly tests hparser.py.
2-
"""
1+
"""Test some parts of rsbackend.py, and implicitly tests hparser.py."""
32

43
from codegen.wgpu_native_patcher import patch_wgpu_native_backend
54

codegen/tests/test_codegen_utils.py

+11-11
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
from codegen.utils import (
66
remove_c_comments,
7-
blacken,
7+
format_code,
88
Patcher,
99
to_snake_case,
1010
to_camel_case,
@@ -59,7 +59,7 @@ def test_remove_c_comments():
5959
assert code2 == code3
6060

6161

62-
def test_blacken_singleline():
62+
def test_format_code_singleline():
6363
code1 = """
6464
def foo():
6565
pass
@@ -98,20 +98,20 @@ def foo(a1, a2, a3):
9898
code1 = dedent(code1).strip()
9999
code2 = dedent(code2).strip()
100100

101-
code3 = blacken(code1, True)
101+
code3 = format_code(code1, True)
102102
code3 = code3.replace("\n\n", "\n").replace("\n\n", "\n").strip()
103103

104104
assert code3 == code2
105105

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

113113

114-
def test_blacken_comments():
114+
def test_format_code_comments():
115115
code1 = """
116116
def foo(): # hi
117117
pass
@@ -133,7 +133,7 @@ def foo(a1, a2, a3): # hi ha ho
133133
code1 = dedent(code1).strip()
134134
code2 = dedent(code2).strip()
135135

136-
code3 = blacken(code1, True)
136+
code3 = format_code(code1, True)
137137
code3 = code3.replace("\n\n", "\n").replace("\n\n", "\n").strip()
138138

139139
assert code3 == code2
@@ -160,7 +160,7 @@ def bar3(self):
160160
pass
161161
"""
162162

163-
code = blacken(dedent(code))
163+
code = format_code(dedent(code))
164164
p = Patcher(code)
165165

166166
# Dump before doing anything, should yield original
@@ -201,7 +201,7 @@ def bar3(self):
201201
for line, i in p.iter_lines():
202202
if line.lstrip().startswith("#"):
203203
p.replace_line(i, "# comment")
204-
with raises(Exception):
204+
with raises(AssertionError):
205205
p.replace_line(i, "# comment")
206206
code2 = p.dumps()
207207
assert code2.count("#") == 4

codegen/utils.py

+35-15
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@
55
import os
66
import sys
77
import tempfile
8-
9-
import black
8+
import subprocess
109

1110

1211
def to_snake_case(name):
@@ -48,7 +47,7 @@ def print(*args, **kwargs):
4847
"""Report something (will be printed and added to a file."""
4948
# __builtins__.print(*args, **kwargs)
5049
if args and not args[0].lstrip().startswith("#"):
51-
args = ("*",) + args
50+
args = ("*", *args)
5251
for f in _file_objects_to_print_to:
5352
__builtins__["print"](*args, file=f, flush=True, **kwargs)
5453

@@ -103,14 +102,35 @@ def remove_c_comments(code):
103102
return new_code
104103

105104

106-
def blacken(src, singleline=False):
107-
"""Format the given src string using black. If singleline is True,
108-
all function signatures become single-line, so they can be parsed
109-
and updated.
105+
class FormatError(Exception):
106+
pass
107+
108+
109+
def format_code(src, singleline=False):
110+
"""Format the given src string. If singleline is True, all function
111+
signatures become single-line, so they can be parsed and updated.
110112
"""
111-
# Normal black
112-
mode = black.FileMode(line_length=999 if singleline else 88)
113-
result = black.format_str(src, mode=mode)
113+
114+
# Use Ruff to format the line. Ruff does not yet have a Python API, so we use its CLI.
115+
tempfilename = os.path.join(tempfile.gettempdir(), "wgpupy_codegen_format.py")
116+
with open(tempfilename, "wb") as fp:
117+
fp.write(src.encode())
118+
line_length = 320 if singleline else 88
119+
cmd = [
120+
sys.executable,
121+
"-m",
122+
"ruff",
123+
"format",
124+
"--line-length",
125+
str(line_length),
126+
tempfilename,
127+
]
128+
p = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
129+
if p.returncode:
130+
raise FormatError(p.stdout.decode(errors="ignore"))
131+
with open(tempfilename, "rb") as fp:
132+
result = fp.read().decode()
133+
os.remove(tempfilename)
114134

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

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

239259
return text
240260

0 commit comments

Comments
 (0)