initial commit
This commit is contained in:
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -0,0 +1,401 @@
|
||||
#! /usr/bin/env python3
|
||||
"""
|
||||
Code generation script for class methods
|
||||
to be exported as public API
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import ast
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from textwrap import indent
|
||||
from typing import TYPE_CHECKING, TypeGuard
|
||||
|
||||
import attrs
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Iterable, Iterator
|
||||
|
||||
# keep these imports up to date with conditional imports in test_gen_exports
|
||||
# isort: split
|
||||
import astor
|
||||
|
||||
PREFIX = "_generated"
|
||||
|
||||
HEADER = """# ***********************************************************
|
||||
# ******* WARNING: AUTOGENERATED! ALL EDITS WILL BE LOST ******
|
||||
# *************************************************************
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
|
||||
from ._ki import enable_ki_protection
|
||||
from ._run import GLOBAL_RUN_CONTEXT
|
||||
"""
|
||||
|
||||
TEMPLATE = """try:
|
||||
return{}GLOBAL_RUN_CONTEXT.{}.{}
|
||||
except AttributeError:
|
||||
raise RuntimeError("must be called from async context") from None
|
||||
"""
|
||||
|
||||
|
||||
@attrs.define
|
||||
class File:
|
||||
path: Path
|
||||
modname: str
|
||||
platform: str = attrs.field(default="", kw_only=True)
|
||||
imports: str = attrs.field(default="", kw_only=True)
|
||||
|
||||
|
||||
def is_function(node: ast.AST) -> TypeGuard[ast.FunctionDef | ast.AsyncFunctionDef]:
|
||||
"""Check if the AST node is either a function
|
||||
or an async function
|
||||
"""
|
||||
return isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef))
|
||||
|
||||
|
||||
def is_public(node: ast.AST) -> TypeGuard[ast.FunctionDef | ast.AsyncFunctionDef]:
|
||||
"""Check if the AST node has a _public decorator"""
|
||||
if is_function(node):
|
||||
for decorator in node.decorator_list:
|
||||
if isinstance(decorator, ast.Name) and decorator.id == "_public":
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def get_public_methods(
|
||||
tree: ast.AST,
|
||||
) -> Iterator[ast.FunctionDef | ast.AsyncFunctionDef]:
|
||||
"""Return a list of methods marked as public.
|
||||
The function walks the given tree and extracts
|
||||
all objects that are functions which are marked
|
||||
public.
|
||||
"""
|
||||
for node in ast.walk(tree):
|
||||
if is_public(node):
|
||||
yield node
|
||||
|
||||
|
||||
def create_passthrough_args(funcdef: ast.FunctionDef | ast.AsyncFunctionDef) -> str:
|
||||
"""Given a function definition, create a string that represents taking all
|
||||
the arguments from the function, and passing them through to another
|
||||
invocation of the same function.
|
||||
|
||||
Example input: ast.parse("def f(a, *, b): ...")
|
||||
Example output: "(a, b=b)"
|
||||
"""
|
||||
call_args = [arg.arg for arg in funcdef.args.args]
|
||||
if funcdef.args.vararg:
|
||||
call_args.append("*" + funcdef.args.vararg.arg)
|
||||
for arg in funcdef.args.kwonlyargs:
|
||||
call_args.append(arg.arg + "=" + arg.arg) # noqa: PERF401 # clarity
|
||||
if funcdef.args.kwarg:
|
||||
call_args.append("**" + funcdef.args.kwarg.arg)
|
||||
return "({})".format(", ".join(call_args))
|
||||
|
||||
|
||||
def run_black(file: File, source: str) -> tuple[bool, str]:
|
||||
"""Run black on the specified file.
|
||||
|
||||
Returns:
|
||||
Tuple of success and result string.
|
||||
ex.:
|
||||
(False, "Failed to run black!\nerror: cannot format ...")
|
||||
(True, "<formatted source>")
|
||||
|
||||
Raises:
|
||||
ImportError: If black is not installed.
|
||||
"""
|
||||
# imported to check that `subprocess` calls will succeed
|
||||
import black # noqa: F401
|
||||
|
||||
# Black has an undocumented API, but it doesn't easily allow reading configuration from
|
||||
# pyproject.toml, and simultaneously pass in / receive the code as a string.
|
||||
# https://github.com/psf/black/issues/779
|
||||
result = subprocess.run(
|
||||
# "-" as a filename = use stdin, return on stdout.
|
||||
[sys.executable, "-m", "black", "--stdin-filename", file.path, "-"],
|
||||
input=source,
|
||||
capture_output=True,
|
||||
encoding="utf8",
|
||||
)
|
||||
|
||||
if result.returncode != 0:
|
||||
return False, f"Failed to run black!\n{result.stderr}"
|
||||
return True, result.stdout
|
||||
|
||||
|
||||
def run_ruff(file: File, source: str) -> tuple[bool, str]:
|
||||
"""Run ruff on the specified file.
|
||||
|
||||
Returns:
|
||||
Tuple of success and result string.
|
||||
ex.:
|
||||
(False, "Failed to run ruff!\nerror: Failed to parse ...")
|
||||
(True, "<formatted source>")
|
||||
|
||||
Raises:
|
||||
ImportError: If ruff is not installed.
|
||||
"""
|
||||
# imported to check that `subprocess` calls will succeed
|
||||
import ruff # noqa: F401
|
||||
|
||||
result = subprocess.run(
|
||||
# "-" as a filename = use stdin, return on stdout.
|
||||
[
|
||||
sys.executable,
|
||||
"-m",
|
||||
"ruff",
|
||||
"check",
|
||||
"--fix",
|
||||
"--unsafe-fixes",
|
||||
"--stdin-filename",
|
||||
file.path,
|
||||
"-",
|
||||
],
|
||||
input=source,
|
||||
capture_output=True,
|
||||
encoding="utf8",
|
||||
)
|
||||
|
||||
if result.returncode != 0:
|
||||
return False, f"Failed to run ruff!\n{result.stderr}"
|
||||
return True, result.stdout
|
||||
|
||||
|
||||
def run_linters(file: File, source: str) -> str:
|
||||
"""Format the specified file using black and ruff.
|
||||
|
||||
Returns:
|
||||
Formatted source code.
|
||||
|
||||
Raises:
|
||||
ImportError: If either is not installed.
|
||||
SystemExit: If either failed.
|
||||
"""
|
||||
|
||||
for fn in (run_black, run_ruff):
|
||||
success, source = fn(file, source)
|
||||
if not success:
|
||||
print(source)
|
||||
sys.exit(1)
|
||||
|
||||
return source
|
||||
|
||||
|
||||
def gen_public_wrappers_source(file: File) -> str:
|
||||
"""Scan the given .py file for @_public decorators, and generate wrapper
|
||||
functions.
|
||||
|
||||
"""
|
||||
header = [HEADER]
|
||||
header.append(file.imports)
|
||||
if file.platform:
|
||||
# Simple checks to avoid repeating imports. If this messes up, type checkers/tests will
|
||||
# just give errors.
|
||||
if "TYPE_CHECKING" not in file.imports:
|
||||
header.append("from typing import TYPE_CHECKING\n")
|
||||
if "import sys" not in file.imports: # pragma: no cover
|
||||
header.append("import sys\n")
|
||||
header.append(
|
||||
f'\nassert not TYPE_CHECKING or sys.platform=="{file.platform}"\n',
|
||||
)
|
||||
|
||||
generated = ["".join(header)]
|
||||
|
||||
source = astor.code_to_ast.parse_file(file.path)
|
||||
method_names = []
|
||||
for method in get_public_methods(source):
|
||||
# Remove self from arguments
|
||||
assert method.args.args[0].arg == "self"
|
||||
del method.args.args[0]
|
||||
method_names.append(method.name)
|
||||
|
||||
for dec in method.decorator_list: # pragma: no cover
|
||||
if isinstance(dec, ast.Name) and dec.id == "contextmanager":
|
||||
is_cm = True
|
||||
break
|
||||
else:
|
||||
is_cm = False
|
||||
|
||||
# Remove decorators
|
||||
method.decorator_list = [ast.Name("enable_ki_protection")]
|
||||
|
||||
# Create pass through arguments
|
||||
new_args = create_passthrough_args(method)
|
||||
|
||||
# Remove method body without the docstring
|
||||
if ast.get_docstring(method) is None:
|
||||
del method.body[:]
|
||||
else:
|
||||
# The first entry is always the docstring
|
||||
del method.body[1:]
|
||||
|
||||
# Create the function definition including the body
|
||||
func = astor.to_source(method, indent_with=" " * 4)
|
||||
|
||||
if is_cm: # pragma: no cover
|
||||
func = func.replace("->Iterator", "->AbstractContextManager")
|
||||
|
||||
# Create export function body
|
||||
template = TEMPLATE.format(
|
||||
" await " if isinstance(method, ast.AsyncFunctionDef) else " ",
|
||||
file.modname,
|
||||
method.name + new_args,
|
||||
)
|
||||
|
||||
# Assemble function definition arguments and body
|
||||
snippet = func + indent(template, " " * 4)
|
||||
|
||||
# Append the snippet to the corresponding module
|
||||
generated.append(snippet)
|
||||
|
||||
method_names.sort()
|
||||
# Insert after the header, before function definitions
|
||||
generated.insert(1, f"__all__ = {method_names!r}")
|
||||
return "\n\n".join(generated)
|
||||
|
||||
|
||||
def matches_disk_files(new_files: dict[str, str]) -> bool:
|
||||
for new_path, new_source in new_files.items():
|
||||
if not os.path.exists(new_path):
|
||||
return False
|
||||
old_source = Path(new_path).read_text(encoding="utf-8")
|
||||
if old_source != new_source:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def process(files: Iterable[File], *, do_test: bool) -> None:
|
||||
new_files = {}
|
||||
for file in files:
|
||||
print("Scanning:", file.path)
|
||||
new_source = gen_public_wrappers_source(file)
|
||||
new_source = run_linters(file, new_source)
|
||||
dirname, basename = os.path.split(file.path)
|
||||
new_path = os.path.join(dirname, PREFIX + basename)
|
||||
new_files[new_path] = new_source
|
||||
matches_disk = matches_disk_files(new_files)
|
||||
if do_test:
|
||||
if not matches_disk:
|
||||
print("Generated sources are outdated. Please regenerate.")
|
||||
sys.exit(1)
|
||||
else:
|
||||
print("Generated sources are up to date.")
|
||||
else:
|
||||
for new_path, new_source in new_files.items():
|
||||
Path(new_path).write_text(new_source, encoding="utf-8", newline="\n")
|
||||
print("Regenerated sources successfully.")
|
||||
if not matches_disk: # TODO: test this branch
|
||||
# With pre-commit integration, show that we edited files.
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
# This is in fact run in CI, but only in the formatting check job, which
|
||||
# doesn't collect coverage.
|
||||
def main() -> None: # pragma: no cover
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Generate python code for public api wrappers",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--test",
|
||||
"-t",
|
||||
action="store_true",
|
||||
help="test if code is still up to date",
|
||||
)
|
||||
parsed_args = parser.parse_args()
|
||||
|
||||
source_root = Path.cwd()
|
||||
# Double-check we found the right directory
|
||||
assert (source_root / "LICENSE").exists()
|
||||
core = source_root / "src/trio/_core"
|
||||
to_wrap = [
|
||||
File(core / "_run.py", "runner", imports=IMPORTS_RUN),
|
||||
File(
|
||||
core / "_instrumentation.py",
|
||||
"runner.instruments",
|
||||
imports=IMPORTS_INSTRUMENT,
|
||||
),
|
||||
File(
|
||||
core / "_io_windows.py",
|
||||
"runner.io_manager",
|
||||
platform="win32",
|
||||
imports=IMPORTS_WINDOWS,
|
||||
),
|
||||
File(
|
||||
core / "_io_epoll.py",
|
||||
"runner.io_manager",
|
||||
platform="linux",
|
||||
imports=IMPORTS_EPOLL,
|
||||
),
|
||||
File(
|
||||
core / "_io_kqueue.py",
|
||||
"runner.io_manager",
|
||||
platform="darwin",
|
||||
imports=IMPORTS_KQUEUE,
|
||||
),
|
||||
]
|
||||
|
||||
process(to_wrap, do_test=parsed_args.test)
|
||||
|
||||
|
||||
IMPORTS_RUN = """\
|
||||
from collections.abc import Awaitable, Callable
|
||||
from typing import Any, TYPE_CHECKING
|
||||
|
||||
import outcome
|
||||
import contextvars
|
||||
|
||||
from ._run import _NO_SEND, RunStatistics, Task
|
||||
from ._entry_queue import TrioToken
|
||||
from .._abc import Clock
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing_extensions import Unpack
|
||||
from ._run import PosArgT
|
||||
"""
|
||||
IMPORTS_INSTRUMENT = """\
|
||||
from ._instrumentation import Instrument
|
||||
"""
|
||||
|
||||
IMPORTS_EPOLL = """\
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .._file_io import _HasFileNo
|
||||
"""
|
||||
|
||||
IMPORTS_KQUEUE = """\
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import select
|
||||
from collections.abc import Callable
|
||||
from contextlib import AbstractContextManager
|
||||
|
||||
from .. import _core
|
||||
from .._file_io import _HasFileNo
|
||||
from ._traps import Abort, RaiseCancelT
|
||||
"""
|
||||
|
||||
IMPORTS_WINDOWS = """\
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from contextlib import AbstractContextManager
|
||||
|
||||
from typing_extensions import Buffer
|
||||
|
||||
from .._file_io import _HasFileNo
|
||||
from ._unbounded_queue import UnboundedQueue
|
||||
from ._windows_cffi import Handle, CData
|
||||
"""
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,126 @@
|
||||
"""Translates Mypy's output into GitHub's error/warning annotation syntax.
|
||||
|
||||
See: https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions
|
||||
|
||||
This first is run with Mypy's output piped in, to collect messages in
|
||||
mypy_annotate.dat. After all platforms run, we run this again, which prints the
|
||||
messages in GitHub's format but with cross-platform failures deduplicated.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import pickle
|
||||
import re
|
||||
import sys
|
||||
|
||||
import attrs
|
||||
|
||||
# Example: 'package/filename.py:42:1:46:3: error: Type error here [code]'
|
||||
report_re = re.compile(
|
||||
r"""
|
||||
([^:]+): # Filename (anything but ":")
|
||||
([0-9]+): # Line number (start)
|
||||
(?:([0-9]+): # Optional column number
|
||||
(?:([0-9]+):([0-9]+):)? # then also optionally, 2 more numbers for end columns
|
||||
)?
|
||||
\s*(error|warn|note): # Kind, prefixed with space
|
||||
(.+) # Message
|
||||
""",
|
||||
re.VERBOSE,
|
||||
)
|
||||
|
||||
mypy_to_github = {
|
||||
"error": "error",
|
||||
"warn": "warning",
|
||||
"note": "notice",
|
||||
}
|
||||
|
||||
|
||||
@attrs.frozen(kw_only=True)
|
||||
class Result:
|
||||
"""Accumulated results, used as a dict key to deduplicate."""
|
||||
|
||||
filename: str
|
||||
start_line: int
|
||||
kind: str
|
||||
message: str
|
||||
start_col: int | None = None
|
||||
end_line: int | None = None
|
||||
end_col: int | None = None
|
||||
|
||||
|
||||
def process_line(line: str) -> Result | None:
|
||||
if match := report_re.fullmatch(line.rstrip()):
|
||||
filename, st_line, st_col, end_line, end_col, kind, message = match.groups()
|
||||
return Result(
|
||||
filename=filename,
|
||||
start_line=int(st_line),
|
||||
start_col=int(st_col) if st_col is not None else None,
|
||||
end_line=int(end_line) if end_line is not None else None,
|
||||
end_col=int(end_col) if end_col is not None else None,
|
||||
kind=mypy_to_github[kind],
|
||||
message=message,
|
||||
)
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def export(results: dict[Result, list[str]]) -> None:
|
||||
"""Display the collected results."""
|
||||
for res, platforms in results.items():
|
||||
print(f"::{res.kind} file={res.filename},line={res.start_line},", end="")
|
||||
if res.start_col is not None:
|
||||
print(f"col={res.start_col},", end="")
|
||||
if res.end_col is not None and res.end_line is not None:
|
||||
print(f"endLine={res.end_line},endColumn={res.end_col},", end="")
|
||||
message = f"({res.start_line}:{res.start_col} - {res.end_line}:{res.end_col}):{res.message}"
|
||||
else:
|
||||
message = f"({res.start_line}:{res.start_col}):{res.message}"
|
||||
else:
|
||||
message = f"{res.start_line}:{res.message}"
|
||||
print(f"title=Mypy-{'+'.join(platforms)}::{res.filename}:{message}")
|
||||
|
||||
|
||||
def main(argv: list[str]) -> None:
|
||||
"""Look for error messages, and convert the format."""
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument(
|
||||
"--dumpfile",
|
||||
help="File to write pickled messages to.",
|
||||
required=True,
|
||||
)
|
||||
parser.add_argument(
|
||||
"--platform",
|
||||
help="OS name, if set Mypy should be piped to stdin.",
|
||||
default=None,
|
||||
)
|
||||
cmd_line = parser.parse_args(argv)
|
||||
|
||||
results: dict[Result, list[str]]
|
||||
try:
|
||||
with open(cmd_line.dumpfile, "rb") as f:
|
||||
results = pickle.load(f)
|
||||
except (FileNotFoundError, pickle.UnpicklingError):
|
||||
# If we fail to load, assume it's an old result.
|
||||
results = {}
|
||||
|
||||
if cmd_line.platform is None:
|
||||
# Write out the results.
|
||||
export(results)
|
||||
else:
|
||||
platform: str = cmd_line.platform
|
||||
for line in sys.stdin:
|
||||
parsed = process_line(line)
|
||||
if parsed is not None:
|
||||
try:
|
||||
results[parsed].append(platform)
|
||||
except KeyError:
|
||||
results[parsed] = [platform]
|
||||
sys.stdout.write(line)
|
||||
with open(cmd_line.dumpfile, "wb") as f:
|
||||
pickle.dump(results, f)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(sys.argv[1:])
|
||||
@@ -0,0 +1,98 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
"""Sync Requirements - Automatically upgrade test requirements pinned
|
||||
versions from pre-commit config file."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from yaml import load as load_yaml
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Generator
|
||||
|
||||
from yaml import CLoader as _CLoader, Loader as _Loader
|
||||
|
||||
Loader: type[_CLoader | _Loader]
|
||||
|
||||
try:
|
||||
from yaml import CLoader as Loader
|
||||
except ImportError:
|
||||
from yaml import Loader
|
||||
|
||||
|
||||
def yield_pre_commit_version_data(
|
||||
pre_commit_text: str,
|
||||
) -> Generator[tuple[str, str], None, None]:
|
||||
"""Yield (name, rev) tuples from pre-commit config file."""
|
||||
pre_commit_config = load_yaml(pre_commit_text, Loader)
|
||||
for repo in pre_commit_config["repos"]:
|
||||
if "repo" not in repo or "rev" not in repo:
|
||||
continue
|
||||
url = repo["repo"]
|
||||
name = url.rsplit("/", 1)[-1]
|
||||
rev = repo["rev"].removeprefix("v")
|
||||
yield name, rev
|
||||
|
||||
|
||||
def update_requirements(
|
||||
requirements: Path,
|
||||
version_data: dict[str, str],
|
||||
) -> bool:
|
||||
"""Return if updated requirements file.
|
||||
|
||||
Update requirements file to match versions in version_data."""
|
||||
changed = False
|
||||
old_lines = requirements.read_text(encoding="utf-8").splitlines(True)
|
||||
|
||||
with requirements.open("w", encoding="utf-8") as file:
|
||||
for line in old_lines:
|
||||
# If comment or not version mark line, ignore.
|
||||
if line.startswith("#") or "==" not in line:
|
||||
file.write(line)
|
||||
continue
|
||||
name, rest = line.split("==", 1)
|
||||
# Maintain extra markers if they exist
|
||||
old_version = rest.strip()
|
||||
extra = "\n"
|
||||
if ";" in rest:
|
||||
old_version, extra = rest.split(";", 1)
|
||||
old_version = old_version.strip()
|
||||
extra = " ;" + extra
|
||||
version = version_data.get(name)
|
||||
# If does not exist, skip
|
||||
if version is None:
|
||||
file.write(line)
|
||||
continue
|
||||
# Otherwise might have changed
|
||||
new_line = f"{name}=={version}{extra}"
|
||||
if new_line != line:
|
||||
if not changed:
|
||||
changed = True
|
||||
print("Changed test requirements version to match pre-commit")
|
||||
print(f"{name}=={old_version} -> {name}=={version}")
|
||||
file.write(new_line)
|
||||
return changed
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
source_root = Path.cwd().absolute()
|
||||
|
||||
# Double-check we found the right directory
|
||||
assert (source_root / "LICENSE").exists()
|
||||
pre_commit = source_root / ".pre-commit-config.yaml"
|
||||
test_requirements = source_root / "test-requirements.txt"
|
||||
|
||||
pre_commit_text = pre_commit.read_text(encoding="utf-8")
|
||||
|
||||
# Get tool versions from pre-commit
|
||||
# Get correct names
|
||||
pre_commit_versions = {
|
||||
name.removesuffix("-mirror").removesuffix("-pre-commit"): version
|
||||
for name, version in yield_pre_commit_version_data(pre_commit_text)
|
||||
}
|
||||
changed = update_requirements(test_requirements, pre_commit_versions)
|
||||
sys.exit(int(changed))
|
||||
@@ -0,0 +1,220 @@
|
||||
# builder for CFFI out-of-line mode, for reduced import time.
|
||||
# run this to generate `trio._core._generated_windows_ffi`.
|
||||
import re
|
||||
|
||||
import cffi
|
||||
|
||||
LIB = """
|
||||
// https://msdn.microsoft.com/en-us/library/windows/desktop/aa383751(v=vs.85).aspx
|
||||
typedef int BOOL;
|
||||
typedef unsigned char BYTE;
|
||||
typedef unsigned char UCHAR;
|
||||
typedef BYTE BOOLEAN;
|
||||
typedef void* PVOID;
|
||||
typedef PVOID HANDLE;
|
||||
typedef unsigned long DWORD;
|
||||
typedef unsigned long ULONG;
|
||||
typedef unsigned int NTSTATUS;
|
||||
typedef unsigned long u_long;
|
||||
typedef ULONG *PULONG;
|
||||
typedef const void *LPCVOID;
|
||||
typedef void *LPVOID;
|
||||
typedef const wchar_t *LPCWSTR;
|
||||
typedef DWORD* LPDWORD;
|
||||
|
||||
typedef uintptr_t ULONG_PTR;
|
||||
typedef uintptr_t UINT_PTR;
|
||||
|
||||
typedef UINT_PTR SOCKET;
|
||||
|
||||
typedef struct _OVERLAPPED {
|
||||
ULONG_PTR Internal;
|
||||
ULONG_PTR InternalHigh;
|
||||
union {
|
||||
struct {
|
||||
DWORD Offset;
|
||||
DWORD OffsetHigh;
|
||||
} DUMMYSTRUCTNAME;
|
||||
PVOID Pointer;
|
||||
} DUMMYUNIONNAME;
|
||||
|
||||
HANDLE hEvent;
|
||||
} OVERLAPPED, *LPOVERLAPPED;
|
||||
|
||||
typedef OVERLAPPED WSAOVERLAPPED;
|
||||
typedef LPOVERLAPPED LPWSAOVERLAPPED;
|
||||
typedef PVOID LPSECURITY_ATTRIBUTES;
|
||||
typedef PVOID LPCSTR;
|
||||
|
||||
typedef struct _OVERLAPPED_ENTRY {
|
||||
ULONG_PTR lpCompletionKey;
|
||||
LPOVERLAPPED lpOverlapped;
|
||||
ULONG_PTR Internal;
|
||||
DWORD dwNumberOfBytesTransferred;
|
||||
} OVERLAPPED_ENTRY, *LPOVERLAPPED_ENTRY;
|
||||
|
||||
// kernel32.dll
|
||||
HANDLE WINAPI CreateIoCompletionPort(
|
||||
_In_ HANDLE FileHandle,
|
||||
_In_opt_ HANDLE ExistingCompletionPort,
|
||||
_In_ ULONG_PTR CompletionKey,
|
||||
_In_ DWORD NumberOfConcurrentThreads
|
||||
);
|
||||
|
||||
BOOL SetFileCompletionNotificationModes(
|
||||
HANDLE FileHandle,
|
||||
UCHAR Flags
|
||||
);
|
||||
|
||||
HANDLE CreateFileW(
|
||||
LPCWSTR lpFileName,
|
||||
DWORD dwDesiredAccess,
|
||||
DWORD dwShareMode,
|
||||
LPSECURITY_ATTRIBUTES lpSecurityAttributes,
|
||||
DWORD dwCreationDisposition,
|
||||
DWORD dwFlagsAndAttributes,
|
||||
HANDLE hTemplateFile
|
||||
);
|
||||
|
||||
BOOL WINAPI CloseHandle(
|
||||
_In_ HANDLE hObject
|
||||
);
|
||||
|
||||
BOOL WINAPI PostQueuedCompletionStatus(
|
||||
_In_ HANDLE CompletionPort,
|
||||
_In_ DWORD dwNumberOfBytesTransferred,
|
||||
_In_ ULONG_PTR dwCompletionKey,
|
||||
_In_opt_ LPOVERLAPPED lpOverlapped
|
||||
);
|
||||
|
||||
BOOL WINAPI GetQueuedCompletionStatusEx(
|
||||
_In_ HANDLE CompletionPort,
|
||||
_Out_ LPOVERLAPPED_ENTRY lpCompletionPortEntries,
|
||||
_In_ ULONG ulCount,
|
||||
_Out_ PULONG ulNumEntriesRemoved,
|
||||
_In_ DWORD dwMilliseconds,
|
||||
_In_ BOOL fAlertable
|
||||
);
|
||||
|
||||
BOOL WINAPI CancelIoEx(
|
||||
_In_ HANDLE hFile,
|
||||
_In_opt_ LPOVERLAPPED lpOverlapped
|
||||
);
|
||||
|
||||
BOOL WriteFile(
|
||||
HANDLE hFile,
|
||||
LPCVOID lpBuffer,
|
||||
DWORD nNumberOfBytesToWrite,
|
||||
LPDWORD lpNumberOfBytesWritten,
|
||||
LPOVERLAPPED lpOverlapped
|
||||
);
|
||||
|
||||
BOOL ReadFile(
|
||||
HANDLE hFile,
|
||||
LPVOID lpBuffer,
|
||||
DWORD nNumberOfBytesToRead,
|
||||
LPDWORD lpNumberOfBytesRead,
|
||||
LPOVERLAPPED lpOverlapped
|
||||
);
|
||||
|
||||
BOOL WINAPI SetConsoleCtrlHandler(
|
||||
_In_opt_ void* HandlerRoutine,
|
||||
_In_ BOOL Add
|
||||
);
|
||||
|
||||
HANDLE CreateEventA(
|
||||
LPSECURITY_ATTRIBUTES lpEventAttributes,
|
||||
BOOL bManualReset,
|
||||
BOOL bInitialState,
|
||||
LPCSTR lpName
|
||||
);
|
||||
|
||||
BOOL SetEvent(
|
||||
HANDLE hEvent
|
||||
);
|
||||
|
||||
BOOL ResetEvent(
|
||||
HANDLE hEvent
|
||||
);
|
||||
|
||||
DWORD WaitForSingleObject(
|
||||
HANDLE hHandle,
|
||||
DWORD dwMilliseconds
|
||||
);
|
||||
|
||||
DWORD WaitForMultipleObjects(
|
||||
DWORD nCount,
|
||||
HANDLE *lpHandles,
|
||||
BOOL bWaitAll,
|
||||
DWORD dwMilliseconds
|
||||
);
|
||||
|
||||
ULONG RtlNtStatusToDosError(
|
||||
NTSTATUS Status
|
||||
);
|
||||
|
||||
int WSAIoctl(
|
||||
SOCKET s,
|
||||
DWORD dwIoControlCode,
|
||||
LPVOID lpvInBuffer,
|
||||
DWORD cbInBuffer,
|
||||
LPVOID lpvOutBuffer,
|
||||
DWORD cbOutBuffer,
|
||||
LPDWORD lpcbBytesReturned,
|
||||
LPWSAOVERLAPPED lpOverlapped,
|
||||
// actually LPWSAOVERLAPPED_COMPLETION_ROUTINE
|
||||
void* lpCompletionRoutine
|
||||
);
|
||||
|
||||
int WSAGetLastError();
|
||||
|
||||
BOOL DeviceIoControl(
|
||||
HANDLE hDevice,
|
||||
DWORD dwIoControlCode,
|
||||
LPVOID lpInBuffer,
|
||||
DWORD nInBufferSize,
|
||||
LPVOID lpOutBuffer,
|
||||
DWORD nOutBufferSize,
|
||||
LPDWORD lpBytesReturned,
|
||||
LPOVERLAPPED lpOverlapped
|
||||
);
|
||||
|
||||
// From https://github.com/piscisaureus/wepoll/blob/master/src/afd.h
|
||||
typedef struct _AFD_POLL_HANDLE_INFO {
|
||||
HANDLE Handle;
|
||||
ULONG Events;
|
||||
NTSTATUS Status;
|
||||
} AFD_POLL_HANDLE_INFO, *PAFD_POLL_HANDLE_INFO;
|
||||
|
||||
// This is really defined as a messy union to allow stuff like
|
||||
// i.DUMMYSTRUCTNAME.LowPart, but we don't need those complications.
|
||||
// Under all that it's just an int64.
|
||||
typedef int64_t LARGE_INTEGER;
|
||||
|
||||
typedef struct _AFD_POLL_INFO {
|
||||
LARGE_INTEGER Timeout;
|
||||
ULONG NumberOfHandles;
|
||||
ULONG Exclusive;
|
||||
AFD_POLL_HANDLE_INFO Handles[1];
|
||||
} AFD_POLL_INFO, *PAFD_POLL_INFO;
|
||||
|
||||
"""
|
||||
|
||||
# cribbed from pywincffi
|
||||
# programmatically strips out those annotations MSDN likes, like _In_
|
||||
LIB = re.sub(r"\b(_In_|_Inout_|_Out_|_Outptr_|_Reserved_)(opt_)?\b", " ", LIB)
|
||||
|
||||
# Other fixups:
|
||||
# - get rid of FAR, cffi doesn't like it
|
||||
LIB = re.sub(r"\bFAR\b", " ", LIB)
|
||||
# - PASCAL is apparently an alias for __stdcall (on modern compilers - modern
|
||||
# being _MSC_VER >= 800)
|
||||
LIB = re.sub(r"\bPASCAL\b", "__stdcall", LIB)
|
||||
|
||||
ffibuilder = cffi.FFI()
|
||||
# a bit hacky but, it works
|
||||
ffibuilder.set_source("trio._core._generated_windows_ffi", None)
|
||||
ffibuilder.cdef(LIB)
|
||||
|
||||
if __name__ == "__main__":
|
||||
ffibuilder.compile("src")
|
||||
Reference in New Issue
Block a user