mirror of
https://github.com/python/cpython.git
synced 2025-09-26 18:29:57 +00:00
gh-109125: Run mypy on Tools/wasm
(#109126)
This commit is contained in:
parent
ed582a2ed9
commit
f65497fd25
5 changed files with 71 additions and 40 deletions
6
.github/workflows/mypy.yml
vendored
6
.github/workflows/mypy.yml
vendored
|
@ -7,11 +7,12 @@ on:
|
||||||
- main
|
- main
|
||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
paths:
|
||||||
- "Tools/clinic/**"
|
- ".github/workflows/mypy.yml"
|
||||||
- "Tools/cases_generator/**"
|
- "Tools/cases_generator/**"
|
||||||
|
- "Tools/clinic/**"
|
||||||
- "Tools/peg_generator/**"
|
- "Tools/peg_generator/**"
|
||||||
- "Tools/requirements-dev.txt"
|
- "Tools/requirements-dev.txt"
|
||||||
- ".github/workflows/mypy.yml"
|
- "Tools/wasm/**"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
|
@ -34,6 +35,7 @@ jobs:
|
||||||
"Tools/cases_generator",
|
"Tools/cases_generator",
|
||||||
"Tools/clinic",
|
"Tools/clinic",
|
||||||
"Tools/peg_generator",
|
"Tools/peg_generator",
|
||||||
|
"Tools/wasm",
|
||||||
]
|
]
|
||||||
name: Run mypy on ${{ matrix.target }}
|
name: Run mypy on ${{ matrix.target }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
14
Tools/wasm/mypy.ini
Normal file
14
Tools/wasm/mypy.ini
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
[mypy]
|
||||||
|
files = Tools/wasm
|
||||||
|
pretty = True
|
||||||
|
show_traceback = True
|
||||||
|
|
||||||
|
# Make sure the wasm can be run using Python 3.8:
|
||||||
|
python_version = 3.8
|
||||||
|
|
||||||
|
# Be strict...
|
||||||
|
strict = True
|
||||||
|
enable_error_code = truthy-bool,ignore-without-code
|
||||||
|
|
||||||
|
# except for incomplete defs, which are useful for module authors:
|
||||||
|
disallow_incomplete_defs = False
|
|
@ -16,6 +16,7 @@ import shutil
|
||||||
import sys
|
import sys
|
||||||
import sysconfig
|
import sysconfig
|
||||||
import zipfile
|
import zipfile
|
||||||
|
from typing import Dict
|
||||||
|
|
||||||
# source directory
|
# source directory
|
||||||
SRCDIR = pathlib.Path(__file__).parent.parent.parent.absolute()
|
SRCDIR = pathlib.Path(__file__).parent.parent.parent.absolute()
|
||||||
|
@ -110,7 +111,8 @@ def get_builddir(args: argparse.Namespace) -> pathlib.Path:
|
||||||
|
|
||||||
def get_sysconfigdata(args: argparse.Namespace) -> pathlib.Path:
|
def get_sysconfigdata(args: argparse.Namespace) -> pathlib.Path:
|
||||||
"""Get path to sysconfigdata relative to build root"""
|
"""Get path to sysconfigdata relative to build root"""
|
||||||
data_name = sysconfig._get_sysconfigdata_name()
|
assert isinstance(args.builddir, pathlib.Path)
|
||||||
|
data_name: str = sysconfig._get_sysconfigdata_name() # type: ignore[attr-defined]
|
||||||
if not data_name.startswith(SYSCONFIG_NAMES):
|
if not data_name.startswith(SYSCONFIG_NAMES):
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Invalid sysconfig data name '{data_name}'.", SYSCONFIG_NAMES
|
f"Invalid sysconfig data name '{data_name}'.", SYSCONFIG_NAMES
|
||||||
|
@ -146,7 +148,7 @@ def create_stdlib_zip(
|
||||||
pzf.writepy(entry, filterfunc=filterfunc)
|
pzf.writepy(entry, filterfunc=filterfunc)
|
||||||
|
|
||||||
|
|
||||||
def detect_extension_modules(args: argparse.Namespace):
|
def detect_extension_modules(args: argparse.Namespace) -> Dict[str, bool]:
|
||||||
modules = {}
|
modules = {}
|
||||||
|
|
||||||
# disabled by Modules/Setup.local ?
|
# disabled by Modules/Setup.local ?
|
||||||
|
@ -161,7 +163,7 @@ def detect_extension_modules(args: argparse.Namespace):
|
||||||
# disabled by configure?
|
# disabled by configure?
|
||||||
with open(args.sysconfig_data) as f:
|
with open(args.sysconfig_data) as f:
|
||||||
data = f.read()
|
data = f.read()
|
||||||
loc = {}
|
loc: Dict[str, Dict[str, str]] = {}
|
||||||
exec(data, globals(), loc)
|
exec(data, globals(), loc)
|
||||||
|
|
||||||
for key, value in loc["build_time_vars"].items():
|
for key, value in loc["build_time_vars"].items():
|
||||||
|
@ -195,7 +197,7 @@ parser.add_argument(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main() -> None:
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
relative_prefix = args.prefix.relative_to(pathlib.Path("/"))
|
relative_prefix = args.prefix.relative_to(pathlib.Path("/"))
|
||||||
|
|
|
@ -40,7 +40,17 @@ import warnings
|
||||||
import webbrowser
|
import webbrowser
|
||||||
|
|
||||||
# for Python 3.8
|
# for Python 3.8
|
||||||
from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple, Union
|
from typing import (
|
||||||
|
cast,
|
||||||
|
Any,
|
||||||
|
Callable,
|
||||||
|
Dict,
|
||||||
|
Iterable,
|
||||||
|
List,
|
||||||
|
Optional,
|
||||||
|
Tuple,
|
||||||
|
Union,
|
||||||
|
)
|
||||||
|
|
||||||
logger = logging.getLogger("wasm_build")
|
logger = logging.getLogger("wasm_build")
|
||||||
|
|
||||||
|
@ -64,7 +74,7 @@ EMSDK_BROKEN_VERSION = {
|
||||||
(3, 1, 16): "https://github.com/emscripten-core/emscripten/issues/17393",
|
(3, 1, 16): "https://github.com/emscripten-core/emscripten/issues/17393",
|
||||||
(3, 1, 20): "https://github.com/emscripten-core/emscripten/issues/17720",
|
(3, 1, 20): "https://github.com/emscripten-core/emscripten/issues/17720",
|
||||||
}
|
}
|
||||||
_MISSING = pathlib.PurePath("MISSING")
|
_MISSING = pathlib.Path("MISSING")
|
||||||
|
|
||||||
WASM_WEBSERVER = WASMTOOLS / "wasm_webserver.py"
|
WASM_WEBSERVER = WASMTOOLS / "wasm_webserver.py"
|
||||||
|
|
||||||
|
@ -109,7 +119,7 @@ https://wasmtime.dev/ to install wasmtime.
|
||||||
|
|
||||||
def parse_emconfig(
|
def parse_emconfig(
|
||||||
emconfig: pathlib.Path = EM_CONFIG,
|
emconfig: pathlib.Path = EM_CONFIG,
|
||||||
) -> Tuple[pathlib.PurePath, pathlib.PurePath]:
|
) -> Tuple[pathlib.Path, pathlib.Path]:
|
||||||
"""Parse EM_CONFIG file and lookup EMSCRIPTEN_ROOT and NODE_JS.
|
"""Parse EM_CONFIG file and lookup EMSCRIPTEN_ROOT and NODE_JS.
|
||||||
|
|
||||||
The ".emscripten" config file is a Python snippet that uses "EM_CONFIG"
|
The ".emscripten" config file is a Python snippet that uses "EM_CONFIG"
|
||||||
|
@ -150,11 +160,11 @@ PYTHON_VERSION = read_python_version()
|
||||||
|
|
||||||
|
|
||||||
class ConditionError(ValueError):
|
class ConditionError(ValueError):
|
||||||
def __init__(self, info: str, text: str):
|
def __init__(self, info: str, text: str) -> None:
|
||||||
self.info = info
|
self.info = info
|
||||||
self.text = text
|
self.text = text
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self) -> str:
|
||||||
return f"{type(self).__name__}: '{self.info}'\n{self.text}"
|
return f"{type(self).__name__}: '{self.info}'\n{self.text}"
|
||||||
|
|
||||||
|
|
||||||
|
@ -180,19 +190,19 @@ class Platform:
|
||||||
name: str
|
name: str
|
||||||
pythonexe: str
|
pythonexe: str
|
||||||
config_site: Optional[pathlib.PurePath]
|
config_site: Optional[pathlib.PurePath]
|
||||||
configure_wrapper: Optional[pathlib.PurePath]
|
configure_wrapper: Optional[pathlib.Path]
|
||||||
make_wrapper: Optional[pathlib.PurePath]
|
make_wrapper: Optional[pathlib.PurePath]
|
||||||
environ: dict
|
environ: Dict[str, Any]
|
||||||
check: Callable[[], None]
|
check: Callable[[], None]
|
||||||
# Used for build_emports().
|
# Used for build_emports().
|
||||||
ports: Optional[pathlib.PurePath]
|
ports: Optional[pathlib.PurePath]
|
||||||
cc: Optional[pathlib.PurePath]
|
cc: Optional[pathlib.PurePath]
|
||||||
|
|
||||||
def getenv(self, profile: "BuildProfile") -> dict:
|
def getenv(self, profile: "BuildProfile") -> Dict[str, Any]:
|
||||||
return self.environ.copy()
|
return self.environ.copy()
|
||||||
|
|
||||||
|
|
||||||
def _check_clean_src():
|
def _check_clean_src() -> None:
|
||||||
candidates = [
|
candidates = [
|
||||||
SRCDIR / "Programs" / "python.o",
|
SRCDIR / "Programs" / "python.o",
|
||||||
SRCDIR / "Python" / "frozen_modules" / "importlib._bootstrap.h",
|
SRCDIR / "Python" / "frozen_modules" / "importlib._bootstrap.h",
|
||||||
|
@ -202,7 +212,7 @@ def _check_clean_src():
|
||||||
raise DirtySourceDirectory(os.fspath(candidate), CLEAN_SRCDIR)
|
raise DirtySourceDirectory(os.fspath(candidate), CLEAN_SRCDIR)
|
||||||
|
|
||||||
|
|
||||||
def _check_native():
|
def _check_native() -> None:
|
||||||
if not any(shutil.which(cc) for cc in ["cc", "gcc", "clang"]):
|
if not any(shutil.which(cc) for cc in ["cc", "gcc", "clang"]):
|
||||||
raise MissingDependency("cc", INSTALL_NATIVE)
|
raise MissingDependency("cc", INSTALL_NATIVE)
|
||||||
if not shutil.which("make"):
|
if not shutil.which("make"):
|
||||||
|
@ -234,12 +244,12 @@ NATIVE = Platform(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _check_emscripten():
|
def _check_emscripten() -> None:
|
||||||
if EMSCRIPTEN_ROOT is _MISSING:
|
if EMSCRIPTEN_ROOT is _MISSING:
|
||||||
raise MissingDependency("Emscripten SDK EM_CONFIG", INSTALL_EMSDK)
|
raise MissingDependency("Emscripten SDK EM_CONFIG", INSTALL_EMSDK)
|
||||||
# sanity check
|
# sanity check
|
||||||
emconfigure = EMSCRIPTEN.configure_wrapper
|
emconfigure = EMSCRIPTEN.configure_wrapper
|
||||||
if not emconfigure.exists():
|
if emconfigure is not None and not emconfigure.exists():
|
||||||
raise MissingDependency(os.fspath(emconfigure), INSTALL_EMSDK)
|
raise MissingDependency(os.fspath(emconfigure), INSTALL_EMSDK)
|
||||||
# version check
|
# version check
|
||||||
version_txt = EMSCRIPTEN_ROOT / "emscripten-version.txt"
|
version_txt = EMSCRIPTEN_ROOT / "emscripten-version.txt"
|
||||||
|
@ -250,7 +260,10 @@ def _check_emscripten():
|
||||||
if version.endswith("-git"):
|
if version.endswith("-git"):
|
||||||
# git / upstream / tot-upstream installation
|
# git / upstream / tot-upstream installation
|
||||||
version = version[:-4]
|
version = version[:-4]
|
||||||
version_tuple = tuple(int(v) for v in version.split("."))
|
version_tuple = cast(
|
||||||
|
Tuple[int, int, int],
|
||||||
|
tuple(int(v) for v in version.split("."))
|
||||||
|
)
|
||||||
if version_tuple < EMSDK_MIN_VERSION:
|
if version_tuple < EMSDK_MIN_VERSION:
|
||||||
raise ConditionError(
|
raise ConditionError(
|
||||||
os.fspath(version_txt),
|
os.fspath(version_txt),
|
||||||
|
@ -293,7 +306,7 @@ EMSCRIPTEN = Platform(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _check_wasi():
|
def _check_wasi() -> None:
|
||||||
wasm_ld = WASI_SDK_PATH / "bin" / "wasm-ld"
|
wasm_ld = WASI_SDK_PATH / "bin" / "wasm-ld"
|
||||||
if not wasm_ld.exists():
|
if not wasm_ld.exists():
|
||||||
raise MissingDependency(os.fspath(wasm_ld), INSTALL_WASI_SDK)
|
raise MissingDependency(os.fspath(wasm_ld), INSTALL_WASI_SDK)
|
||||||
|
@ -400,7 +413,7 @@ class EmscriptenTarget(enum.Enum):
|
||||||
node_debug = "node-debug"
|
node_debug = "node-debug"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_browser(self):
|
def is_browser(self) -> bool:
|
||||||
cls = type(self)
|
cls = type(self)
|
||||||
return self in {cls.browser, cls.browser_debug}
|
return self in {cls.browser, cls.browser_debug}
|
||||||
|
|
||||||
|
@ -421,7 +434,7 @@ class SupportLevel(enum.Enum):
|
||||||
experimental = "experimental, may be broken"
|
experimental = "experimental, may be broken"
|
||||||
broken = "broken / unavailable"
|
broken = "broken / unavailable"
|
||||||
|
|
||||||
def __bool__(self):
|
def __bool__(self) -> bool:
|
||||||
cls = type(self)
|
cls = type(self)
|
||||||
return self in {cls.supported, cls.working}
|
return self in {cls.supported, cls.working}
|
||||||
|
|
||||||
|
@ -500,7 +513,7 @@ class BuildProfile:
|
||||||
cmd.insert(0, os.fspath(platform.make_wrapper))
|
cmd.insert(0, os.fspath(platform.make_wrapper))
|
||||||
return cmd
|
return cmd
|
||||||
|
|
||||||
def getenv(self) -> dict:
|
def getenv(self) -> Dict[str, Any]:
|
||||||
"""Generate environ dict for platform"""
|
"""Generate environ dict for platform"""
|
||||||
env = os.environ.copy()
|
env = os.environ.copy()
|
||||||
env.setdefault("MAKEFLAGS", f"-j{os.cpu_count()}")
|
env.setdefault("MAKEFLAGS", f"-j{os.cpu_count()}")
|
||||||
|
@ -529,7 +542,7 @@ class BuildProfile:
|
||||||
cmd: Iterable[str],
|
cmd: Iterable[str],
|
||||||
args: Iterable[str] = (),
|
args: Iterable[str] = (),
|
||||||
cwd: Optional[pathlib.Path] = None,
|
cwd: Optional[pathlib.Path] = None,
|
||||||
):
|
) -> int:
|
||||||
cmd = list(cmd)
|
cmd = list(cmd)
|
||||||
cmd.extend(args)
|
cmd.extend(args)
|
||||||
if cwd is None:
|
if cwd is None:
|
||||||
|
@ -541,46 +554,46 @@ class BuildProfile:
|
||||||
env=self.getenv(),
|
env=self.getenv(),
|
||||||
)
|
)
|
||||||
|
|
||||||
def _check_execute(self):
|
def _check_execute(self) -> None:
|
||||||
if self.is_browser:
|
if self.is_browser:
|
||||||
raise ValueError(f"Cannot execute on {self.target}")
|
raise ValueError(f"Cannot execute on {self.target}")
|
||||||
|
|
||||||
def run_build(self, *args):
|
def run_build(self, *args: str) -> None:
|
||||||
"""Run configure (if necessary) and make"""
|
"""Run configure (if necessary) and make"""
|
||||||
if not self.makefile.exists():
|
if not self.makefile.exists():
|
||||||
logger.info("Makefile not found, running configure")
|
logger.info("Makefile not found, running configure")
|
||||||
self.run_configure(*args)
|
self.run_configure(*args)
|
||||||
self.run_make("all", *args)
|
self.run_make("all", *args)
|
||||||
|
|
||||||
def run_configure(self, *args):
|
def run_configure(self, *args: str) -> int:
|
||||||
"""Run configure script to generate Makefile"""
|
"""Run configure script to generate Makefile"""
|
||||||
os.makedirs(self.builddir, exist_ok=True)
|
os.makedirs(self.builddir, exist_ok=True)
|
||||||
return self._run_cmd(self.configure_cmd, args)
|
return self._run_cmd(self.configure_cmd, args)
|
||||||
|
|
||||||
def run_make(self, *args):
|
def run_make(self, *args: str) -> int:
|
||||||
"""Run make (defaults to build all)"""
|
"""Run make (defaults to build all)"""
|
||||||
return self._run_cmd(self.make_cmd, args)
|
return self._run_cmd(self.make_cmd, args)
|
||||||
|
|
||||||
def run_pythoninfo(self, *args):
|
def run_pythoninfo(self, *args: str) -> int:
|
||||||
"""Run 'make pythoninfo'"""
|
"""Run 'make pythoninfo'"""
|
||||||
self._check_execute()
|
self._check_execute()
|
||||||
return self.run_make("pythoninfo", *args)
|
return self.run_make("pythoninfo", *args)
|
||||||
|
|
||||||
def run_test(self, target: str, testopts: Optional[str] = None):
|
def run_test(self, target: str, testopts: Optional[str] = None) -> int:
|
||||||
"""Run buildbottests"""
|
"""Run buildbottests"""
|
||||||
self._check_execute()
|
self._check_execute()
|
||||||
if testopts is None:
|
if testopts is None:
|
||||||
testopts = self.default_testopts
|
testopts = self.default_testopts
|
||||||
return self.run_make(target, f"TESTOPTS={testopts}")
|
return self.run_make(target, f"TESTOPTS={testopts}")
|
||||||
|
|
||||||
def run_py(self, *args):
|
def run_py(self, *args: str) -> int:
|
||||||
"""Run Python with hostrunner"""
|
"""Run Python with hostrunner"""
|
||||||
self._check_execute()
|
self._check_execute()
|
||||||
self.run_make(
|
return self.run_make(
|
||||||
"--eval", f"run: all; $(HOSTRUNNER) ./$(PYTHON) {shlex.join(args)}", "run"
|
"--eval", f"run: all; $(HOSTRUNNER) ./$(PYTHON) {shlex.join(args)}", "run"
|
||||||
)
|
)
|
||||||
|
|
||||||
def run_browser(self, bind="127.0.0.1", port=8000):
|
def run_browser(self, bind: str = "127.0.0.1", port: int = 8000) -> None:
|
||||||
"""Run WASM webserver and open build in browser"""
|
"""Run WASM webserver and open build in browser"""
|
||||||
relbuilddir = self.builddir.relative_to(SRCDIR)
|
relbuilddir = self.builddir.relative_to(SRCDIR)
|
||||||
url = f"http://{bind}:{port}/{relbuilddir}/python.html"
|
url = f"http://{bind}:{port}/{relbuilddir}/python.html"
|
||||||
|
@ -611,7 +624,7 @@ class BuildProfile:
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def clean(self, all: bool = False):
|
def clean(self, all: bool = False) -> None:
|
||||||
"""Clean build directory"""
|
"""Clean build directory"""
|
||||||
if all:
|
if all:
|
||||||
if self.builddir.exists():
|
if self.builddir.exists():
|
||||||
|
@ -619,7 +632,7 @@ class BuildProfile:
|
||||||
elif self.makefile.exists():
|
elif self.makefile.exists():
|
||||||
self.run_make("clean")
|
self.run_make("clean")
|
||||||
|
|
||||||
def build_emports(self, force: bool = False):
|
def build_emports(self, force: bool = False) -> None:
|
||||||
"""Pre-build emscripten ports."""
|
"""Pre-build emscripten ports."""
|
||||||
platform = self.host.platform
|
platform = self.host.platform
|
||||||
if platform.ports is None or platform.cc is None:
|
if platform.ports is None or platform.cc is None:
|
||||||
|
@ -829,7 +842,7 @@ parser.add_argument(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main() -> None:
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
level=logging.INFO if args.verbose else logging.ERROR,
|
level=logging.INFO if args.verbose else logging.ERROR,
|
||||||
|
|
|
@ -21,21 +21,21 @@ class MyHTTPRequestHandler(server.SimpleHTTPRequestHandler):
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
def end_headers(self):
|
def end_headers(self) -> None:
|
||||||
self.send_my_headers()
|
self.send_my_headers()
|
||||||
super().end_headers()
|
super().end_headers()
|
||||||
|
|
||||||
def send_my_headers(self):
|
def send_my_headers(self) -> None:
|
||||||
self.send_header("Cross-Origin-Opener-Policy", "same-origin")
|
self.send_header("Cross-Origin-Opener-Policy", "same-origin")
|
||||||
self.send_header("Cross-Origin-Embedder-Policy", "require-corp")
|
self.send_header("Cross-Origin-Embedder-Policy", "require-corp")
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main() -> None:
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
if not args.bind:
|
if not args.bind:
|
||||||
args.bind = None
|
args.bind = None
|
||||||
|
|
||||||
server.test(
|
server.test( # type: ignore[attr-defined]
|
||||||
HandlerClass=MyHTTPRequestHandler,
|
HandlerClass=MyHTTPRequestHandler,
|
||||||
protocol="HTTP/1.1",
|
protocol="HTTP/1.1",
|
||||||
port=args.port,
|
port=args.port,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue