Files
ruff/scripts/generate_builtin_modules.py
T

140 lines
3.9 KiB
Python

# /// script
# requires-python = ">=3.12"
# dependencies = []
#
# [tool.ty.rules]
# blanket-ignore-comment = "warn"
# missing-type-argument = "warn"
# possibly-unresolved-reference = "warn"
# unsound-return-statement = "warn"
# unsound-yield = "warn"
# unsupported-dynamic-base = "warn"
# division-by-zero = "warn"
# dynamic-function-decorator-return = "warn"
# unsound-assignment = "warn"
# redundant-condition-strict = "warn"
# disjoint-cast = "warn"
# missing-direct-dependency = "warn"
#
# [tool.uv]
# no-build = true
# exclude-newer = "P7D"
# ///
"""Script to generate `crates/ruff_python_stdlib/src/sys/builtin_modules.rs`.
This script requires `uvx` to be available on PATH.
"""
from __future__ import annotations
import builtins
import subprocess
import textwrap
from functools import partial
from pathlib import Path
from typing import Final
MODULE_CRATE = "ruff_python_stdlib"
MODULE_PATH = Path("crates") / MODULE_CRATE / "src" / "sys" / "builtin_modules.rs"
PYTHON_VERSIONS: Final = range(8, 16) # Python 3.8 through 3.15
def run(command: list[str]) -> str:
try:
proc = subprocess.run(
command,
check=True,
text=True,
capture_output=True,
)
except subprocess.CalledProcessError as e:
print(e.stdout)
print(e.stderr)
raise
return proc.stdout
def builtin_modules_on_version(minor_version: int) -> set[str]:
command_1 = [
"uv",
"--managed-python",
"python",
"install",
f"python3.{minor_version}",
"--upgrade",
]
run(command_1)
command_2 = [
"uvx",
"--managed-python",
f"python3.{minor_version}",
"-c",
"import sys; print(sys.builtin_module_names)",
]
return set(eval(run(command_2)))
def generate_module() -> None:
with MODULE_PATH.open("w") as f:
print = partial(builtins.print, file=f)
print(
textwrap.dedent(
"""\
//! This file is generated by `scripts/generate_builtin_modules.py`
/// Return `true` if `module` is a [builtin module] on the given
/// Python 3 version.
///
/// "Builtin modules" are modules that are compiled directly into the
/// Python interpreter. These can never be shadowed by first-party
/// modules; the normal rules of module resolution do not apply to these
/// modules.
///
/// [builtin module]: https://docs.python.org/3/library/sys.html#sys.builtin_module_names
#[allow(clippy::unnested_or_patterns)]
pub fn is_builtin_module(minor_version: u8, module: &str) -> bool {
matches!((minor_version, module),
""",
)
)
modules_by_version = {
minor_version: builtin_modules_on_version(minor_version)
for minor_version in PYTHON_VERSIONS
}
# First, add a case for the modules that are in all versions.
ubiquitous_modules = set.intersection(*modules_by_version.values())
print("(_, ")
for i, module in enumerate(sorted(ubiquitous_modules)):
if i > 0:
print(" | ", end="")
print(f'"{module}"')
print(")")
# Next, add any version-specific modules.
for minor_version in PYTHON_VERSIONS:
version_modules = set.difference(
modules_by_version[minor_version],
ubiquitous_modules,
)
print(" | ")
print(f"({minor_version}, ")
for i, module in enumerate(sorted(version_modules)):
if i > 0:
print(" | ", end="")
print(f'"{module}"')
print(")")
print(")}")
subprocess.run(["cargo", "fmt", "--package", MODULE_CRATE], check=True)
if __name__ == "__main__":
generate_module()