| #!/usr/bin/env python |
| |
| from __future__ import annotations |
| |
| import glob |
| import os |
| import os.path |
| import sys |
| from typing import TYPE_CHECKING, Any |
| |
| if sys.version_info < (3, 8, 0): # noqa: UP036 |
| sys.stderr.write("ERROR: You need Python 3.8 or later to use mypy.\n") |
| exit(1) |
| |
| # we'll import stuff from the source tree, let's ensure is on the sys path |
| sys.path.insert(0, os.path.dirname(os.path.realpath(__file__))) |
| |
| # This requires setuptools when building; setuptools is not needed |
| # when installing from a wheel file (though it is still needed for |
| # alternative forms of installing, as suggested by README.md). |
| from setuptools import Extension, find_packages, setup |
| from setuptools.command.build_py import build_py |
| |
| from mypy.version import __version__ as version |
| |
| if TYPE_CHECKING: |
| from typing_extensions import TypeGuard |
| |
| description = "Optional static typing for Python" |
| long_description = """ |
| Mypy -- Optional Static Typing for Python |
| ========================================= |
| |
| Add type annotations to your Python programs, and use mypy to type |
| check them. Mypy is essentially a Python linter on steroids, and it |
| can catch many programming errors by analyzing your program, without |
| actually having to run it. Mypy has a powerful type system with |
| features such as type inference, gradual typing, generics and union |
| types. |
| """.lstrip() |
| |
| |
| def is_list_of_setuptools_extension(items: list[Any]) -> TypeGuard[list[Extension]]: |
| return all(isinstance(item, Extension) for item in items) |
| |
| |
| def find_package_data(base, globs, root="mypy"): |
| """Find all interesting data files, for setup(package_data=) |
| |
| Arguments: |
| root: The directory to search in. |
| globs: A list of glob patterns to accept files. |
| """ |
| |
| rv_dirs = [root for root, dirs, files in os.walk(base)] |
| rv = [] |
| for rv_dir in rv_dirs: |
| files = [] |
| for pat in globs: |
| files += glob.glob(os.path.join(rv_dir, pat)) |
| if not files: |
| continue |
| rv.extend([os.path.relpath(f, root) for f in files]) |
| return rv |
| |
| |
| class CustomPythonBuild(build_py): |
| def pin_version(self): |
| path = os.path.join(self.build_lib, "mypy") |
| self.mkpath(path) |
| with open(os.path.join(path, "version.py"), "w") as stream: |
| stream.write(f'__version__ = "{version}"\n') |
| |
| def run(self): |
| self.execute(self.pin_version, ()) |
| build_py.run(self) |
| |
| |
| cmdclass = {"build_py": CustomPythonBuild} |
| |
| package_data = ["py.typed"] |
| |
| package_data += find_package_data(os.path.join("mypy", "typeshed"), ["*.py", "*.pyi"]) |
| package_data += [os.path.join("mypy", "typeshed", "stdlib", "VERSIONS")] |
| |
| package_data += find_package_data(os.path.join("mypy", "xml"), ["*.xsd", "*.xslt", "*.css"]) |
| |
| USE_MYPYC = False |
| # To compile with mypyc, a mypyc checkout must be present on the PYTHONPATH |
| if len(sys.argv) > 1 and "--use-mypyc" in sys.argv: |
| sys.argv.remove("--use-mypyc") |
| USE_MYPYC = True |
| if os.getenv("MYPY_USE_MYPYC", None) == "1": |
| USE_MYPYC = True |
| |
| if USE_MYPYC: |
| MYPYC_BLACKLIST = tuple( |
| os.path.join("mypy", x) |
| for x in ( |
| # Need to be runnable as scripts |
| "__main__.py", |
| "pyinfo.py", |
| os.path.join("dmypy", "__main__.py"), |
| # Uses __getattr__/__setattr__ |
| "split_namespace.py", |
| # Lies to mypy about code reachability |
| "bogus_type.py", |
| # We don't populate __file__ properly at the top level or something? |
| # Also I think there would be problems with how we generate version.py. |
| "version.py", |
| # Skip these to reduce the size of the build |
| "stubtest.py", |
| "stubgenc.py", |
| "stubdoc.py", |
| ) |
| ) + ( |
| # Don't want to grab this accidentally |
| os.path.join("mypyc", "lib-rt", "setup.py"), |
| # Uses __file__ at top level https://github.com/mypyc/mypyc/issues/700 |
| os.path.join("mypyc", "__main__.py"), |
| ) |
| |
| everything = [os.path.join("mypy", x) for x in find_package_data("mypy", ["*.py"])] + [ |
| os.path.join("mypyc", x) for x in find_package_data("mypyc", ["*.py"], root="mypyc") |
| ] |
| # Start with all the .py files |
| all_real_pys = [ |
| x for x in everything if not x.startswith(os.path.join("mypy", "typeshed") + os.sep) |
| ] |
| # Strip out anything in our blacklist |
| mypyc_targets = [x for x in all_real_pys if x not in MYPYC_BLACKLIST] |
| # Strip out any test code |
| mypyc_targets = [ |
| x |
| for x in mypyc_targets |
| if not x.startswith( |
| ( |
| os.path.join("mypy", "test") + os.sep, |
| os.path.join("mypyc", "test") + os.sep, |
| os.path.join("mypyc", "doc") + os.sep, |
| os.path.join("mypyc", "test-data") + os.sep, |
| ) |
| ) |
| ] |
| # ... and add back in the one test module we need |
| mypyc_targets.append(os.path.join("mypy", "test", "visitors.py")) |
| |
| # The targets come out of file system apis in an unspecified |
| # order. Sort them so that the mypyc output is deterministic. |
| mypyc_targets.sort() |
| |
| use_other_mypyc = os.getenv("ALTERNATE_MYPYC_PATH", None) |
| if use_other_mypyc: |
| # This bit is super unfortunate: we want to use a different |
| # mypy/mypyc version, but we've already imported parts, so we |
| # remove the modules that we've imported already, which will |
| # let the right versions be imported by mypyc. |
| del sys.modules["mypy"] |
| del sys.modules["mypy.version"] |
| del sys.modules["mypy.git"] |
| sys.path.insert(0, use_other_mypyc) |
| |
| from mypyc.build import mypycify |
| |
| opt_level = os.getenv("MYPYC_OPT_LEVEL", "3") |
| debug_level = os.getenv("MYPYC_DEBUG_LEVEL", "1") |
| force_multifile = os.getenv("MYPYC_MULTI_FILE", "") == "1" |
| ext_modules = mypycify( |
| mypyc_targets + ["--config-file=mypy_bootstrap.ini"], |
| opt_level=opt_level, |
| debug_level=debug_level, |
| # Use multi-file compilation mode on windows because without it |
| # our Appveyor builds run out of memory sometimes. |
| multi_file=sys.platform == "win32" or force_multifile, |
| ) |
| assert is_list_of_setuptools_extension(ext_modules), "Expected mypycify to use setuptools" |
| |
| else: |
| ext_modules = [] |
| |
| |
| classifiers = [ |
| "Development Status :: 5 - Production/Stable", |
| "Environment :: Console", |
| "Intended Audience :: Developers", |
| "License :: OSI Approved :: MIT License", |
| "Programming Language :: Python :: 3", |
| "Programming Language :: Python :: 3.8", |
| "Programming Language :: Python :: 3.9", |
| "Programming Language :: Python :: 3.10", |
| "Programming Language :: Python :: 3.11", |
| "Topic :: Software Development", |
| "Typing :: Typed", |
| ] |
| |
| setup( |
| name="mypy", |
| version=version, |
| description=description, |
| long_description=long_description, |
| author="Jukka Lehtosalo", |
| author_email="jukka.lehtosalo@iki.fi", |
| url="https://www.mypy-lang.org/", |
| license="MIT", |
| py_modules=[], |
| ext_modules=ext_modules, |
| packages=find_packages(), |
| package_data={"mypy": package_data}, |
| entry_points={ |
| "console_scripts": [ |
| "mypy=mypy.__main__:console_entry", |
| "stubgen=mypy.stubgen:main", |
| "stubtest=mypy.stubtest:main", |
| "dmypy=mypy.dmypy.client:console_entry", |
| "mypyc=mypyc.__main__:main", |
| ] |
| }, |
| classifiers=classifiers, |
| cmdclass=cmdclass, |
| # When changing this, also update mypy-requirements.txt. |
| install_requires=[ |
| "typing_extensions>=4.1.0", |
| "mypy_extensions >= 1.0.0", |
| "tomli>=1.1.0; python_version<'3.11'", |
| ], |
| # Same here. |
| extras_require={ |
| "dmypy": "psutil >= 4.0", |
| "mypyc": "setuptools >= 50", |
| "python2": "", |
| "reports": "lxml", |
| "install-types": "pip", |
| }, |
| python_requires=">=3.8", |
| include_package_data=True, |
| project_urls={ |
| "News": "https://mypy-lang.org/news.html", |
| "Documentation": "https://mypy.readthedocs.io/en/stable/index.html", |
| "Repository": "https://github.com/python/mypy", |
| }, |
| ) |