Skip to content

Commit f32d707

Browse files
dbortfacebook-github-bot
authored andcommitted
Add requires-python = ">=3.10" to pyproject.toml (pytorch#4131)
Summary: We only support python 3.10/3.11/3.12 right now, so make that explicit. The last time I tried to do this, the linter tried to force us to use new-in-3.10 syntax. I was able to avoid that this time by telling the `black` formatter to use a larger range of python versions. Also modify setup.py to be 3.8 and 3.13 compatible so that it can run and complain about the version mismatch instead of just failing to run. Then, to save users some time, check the version in install_requirements.sh before doing any real work. And `cd` to the script's directory before starting so that users can invoke it like `/path/to/executorch/install_requirements.sh`. Pull Request resolved: pytorch#4131 Test Plan: Built wheels in CI by adding the `ciflow/binaries/all` label. Linted all python files: ``` find . -type f -name "*.py" | grep -vE 'third-party|cmake-out|pip-out' > /tmp/f lintrunner --paths-cmd='cat /tmp/f' ``` Also created a conda environment with python 3.8 and ran `install_requirements.sh` (without the fail-fast check). It used to fail on syntax errors, but now it fails because the version can't be satisfied. To test the fail-fast logic, ran install_requirements.sh in 3.8 and 3.10 conda environments, and saw that it failed/passed as expected. To test the failure cases, made some local modifications and saw that it continued to build the wheel after printing a warning: - Remove/rename pyproject.toml - Remove the `requires-python` line from pyproject.toml - Modify the `requires-python` line to contain an invalid range - Import a missing module in the `python_is_compatible()` code To test the new `cd` logic: ``` cd build ../install_requirements.sh # This worked ``` Reviewed By: mergennachin Differential Revision: D59291599 Pulled By: dbort fbshipit-source-id: 5bfc97346180b65ad8719753c4126af025a41ae0
1 parent 38d67db commit f32d707

File tree

3 files changed

+83
-23
lines changed

3 files changed

+83
-23
lines changed

install_requirements.sh

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@
55
# This source code is licensed under the BSD-style license found in the
66
# LICENSE file in the root directory of this source tree.
77

8-
# Install required python dependencies for developing
9-
# Dependencies are defined in .pyproject.toml
8+
# Before doing anything, cd to the directory containing this script.
9+
cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null || /bin/true
10+
11+
# Find the names of the python tools to use.
1012
if [[ -z $PYTHON_EXECUTABLE ]];
1113
then
1214
if [[ -z $CONDA_DEFAULT_ENV ]] || [[ $CONDA_DEFAULT_ENV == "base" ]] || [[ ! -x "$(command -v python)" ]];
@@ -24,6 +26,61 @@ else
2426
PIP_EXECUTABLE=pip3
2527
fi
2628

29+
# Returns 0 if the current python version is compatible with the version range
30+
# in pyprojects.toml, or returns 1 if it is not compatible. If the check logic
31+
# itself fails, prints a warning and returns 0.
32+
python_is_compatible() {
33+
# Scrape the version range from pyproject.toml, which should be
34+
# in the current directory.
35+
local version_specifier
36+
version_specifier="$(
37+
grep "^requires-python" pyproject.toml \
38+
| head -1 \
39+
| sed -e 's/[^"]*"//;s/".*//'
40+
)"
41+
if [[ -z ${version_specifier} ]]; then
42+
echo "WARNING: Skipping python version check: version range not found" >& 2
43+
return 0
44+
fi
45+
46+
# Install the packaging module if necessary.
47+
if ! python -c 'import packaging' 2> /dev/null ; then
48+
${PIP_EXECUTABLE} install packaging
49+
fi
50+
51+
# Compare the current python version to the range in version_specifier. Exits
52+
# with status 1 if the version is not compatible, or with status 0 if the
53+
# version is compatible or the logic itself fails.
54+
${PYTHON_EXECUTABLE} <<EOF
55+
import sys
56+
try:
57+
import packaging.version
58+
import packaging.specifiers
59+
import platform
60+
61+
python_version = packaging.version.parse(platform.python_version())
62+
version_range = packaging.specifiers.SpecifierSet("${version_specifier}")
63+
if python_version not in version_range:
64+
print(
65+
"ERROR: ExecuTorch does not support python version "
66+
+ f"{python_version}: must satisfy \"${version_specifier}\"",
67+
file=sys.stderr,
68+
)
69+
sys.exit(1)
70+
except Exception as e:
71+
print(f"WARNING: Skipping python version check: {e}", file=sys.stderr)
72+
sys.exit(0)
73+
EOF
74+
75+
return $?
76+
}
77+
78+
# Fail fast if the wheel build will fail because the current python version
79+
# isn't supported. But don't fail if the check logic itself has problems: the
80+
# wheel build will do a final check before proceeding.
81+
if ! python_is_compatible; then
82+
exit 1
83+
fi
2784

2885
# Parse options.
2986
EXECUTORCH_BUILD_PYBIND=OFF

pyproject.toml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,12 @@ classifiers = [
4444
"Programming Language :: Python :: 3",
4545
# Update this as we support more versions of python.
4646
"Programming Language :: Python :: 3.10",
47+
"Programming Language :: Python :: 3.11",
48+
"Programming Language :: Python :: 3.12",
4749
]
4850

4951
# Python dependencies required for use.
52+
requires-python = ">=3.10"
5053
dependencies=[
5154
"expecttest",
5255
"flatbuffers",
@@ -93,3 +96,9 @@ flatc = "executorch.data.bin:flatc"
9396
[tool.usort]
9497
# Do not try to put "first-party" imports in their own section.
9598
first_party_detection = false
99+
100+
[tool.black]
101+
# Emit syntax compatible with older versions of python instead of only the range
102+
# specified by `requires-python`. TODO: Remove this once we support these older
103+
# versions of python and can expand the `requires-python` range.
104+
target-version = ["py38", "py39", "py310", "py311", "py312"]

setup.py

Lines changed: 15 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@
5858
from distutils import log
5959
from distutils.sysconfig import get_python_lib
6060
from pathlib import Path
61-
from typing import Optional
61+
from typing import List, Optional
6262

6363
from setuptools import Extension, setup
6464
from setuptools.command.build import build
@@ -82,31 +82,27 @@ def _is_env_enabled(env_var: str, default: bool = False) -> bool:
8282
return True
8383

8484
@classmethod
85-
@property
8685
def pybindings(cls) -> bool:
8786
return cls._is_env_enabled("EXECUTORCH_BUILD_PYBIND", default=False)
8887

8988
@classmethod
90-
@property
9189
def llama_custom_ops(cls) -> bool:
9290
return cls._is_env_enabled("EXECUTORCH_BUILD_KERNELS_CUSTOM_AOT", default=True)
9391

9492
@classmethod
95-
@property
9693
def flatc(cls) -> bool:
9794
return cls._is_env_enabled("EXECUTORCH_BUILD_FLATC", default=True)
9895

9996

10097
class Version:
101-
"""Static properties that describe the version of the pip package."""
98+
"""Static strings that describe the version of the pip package."""
10299

103100
# Cached values returned by the properties.
104101
__root_dir_attr: Optional[str] = None
105102
__string_attr: Optional[str] = None
106103
__git_hash_attr: Optional[str] = None
107104

108105
@classmethod
109-
@property
110106
def _root_dir(cls) -> str:
111107
"""The path to the root of the git repo."""
112108
if cls.__root_dir_attr is None:
@@ -115,7 +111,6 @@ def _root_dir(cls) -> str:
115111
return str(cls.__root_dir_attr)
116112

117113
@classmethod
118-
@property
119114
def git_hash(cls) -> Optional[str]:
120115
"""The current git hash, if known."""
121116
if cls.__git_hash_attr is None:
@@ -124,7 +119,7 @@ def git_hash(cls) -> Optional[str]:
124119
try:
125120
cls.__git_hash_attr = (
126121
subprocess.check_output(
127-
["git", "rev-parse", "HEAD"], cwd=cls._root_dir
122+
["git", "rev-parse", "HEAD"], cwd=cls._root_dir()
128123
)
129124
.decode("ascii")
130125
.strip()
@@ -135,7 +130,6 @@ def git_hash(cls) -> Optional[str]:
135130
return cls.__git_hash_attr if cls.__git_hash_attr else None
136131

137132
@classmethod
138-
@property
139133
def string(cls) -> str:
140134
"""The version string."""
141135
if cls.__string_attr is None:
@@ -147,10 +141,10 @@ def string(cls) -> str:
147141
# Otherwise, read the version from a local file and add the git
148142
# commit if available.
149143
version = (
150-
open(os.path.join(cls._root_dir, "version.txt")).read().strip()
144+
open(os.path.join(cls._root_dir(), "version.txt")).read().strip()
151145
)
152-
if cls.git_hash:
153-
version += "+" + cls.git_hash[:7]
146+
if cls.git_hash():
147+
version += "+" + cls.git_hash()[:7]
154148
cls.__string_attr = version
155149
return cls.__string_attr
156150

@@ -160,9 +154,9 @@ def write_to_python_file(cls, path: str) -> None:
160154
lines = [
161155
"from typing import Optional",
162156
'__all__ = ["__version__", "git_version"]',
163-
f'__version__ = "{cls.string}"',
157+
f'__version__ = "{cls.string()}"',
164158
# A string or None.
165-
f"git_version: Optional[str] = {repr(cls.git_hash)}",
159+
f"git_version: Optional[str] = {repr(cls.git_hash())}",
166160
]
167161
with open(path, "w") as fp:
168162
fp.write("\n".join(lines) + "\n")
@@ -480,7 +474,7 @@ def run(self):
480474
# extension entries themselves instead of hard-coding them here.
481475
build_args += ["--target", "flatc"]
482476

483-
if ShouldBuild.pybindings:
477+
if ShouldBuild.pybindings():
484478
cmake_args += [
485479
"-DEXECUTORCH_BUILD_PYBIND=ON",
486480
"-DEXECUTORCH_BUILD_KERNELS_QUANTIZED=ON", # add quantized ops to pybindings.
@@ -491,7 +485,7 @@ def run(self):
491485
# add entries like `-DEXECUTORCH_BUILD_XNNPACK=ON` to the CMAKE_ARGS
492486
# environment variable.
493487

494-
if ShouldBuild.llama_custom_ops:
488+
if ShouldBuild.llama_custom_ops():
495489
cmake_args += [
496490
"-DEXECUTORCH_BUILD_KERNELS_CUSTOM=ON", # add llama sdpa ops to pybindings.
497491
"-DEXECUTORCH_BUILD_KERNELS_CUSTOM_AOT=ON",
@@ -553,16 +547,16 @@ def run(self):
553547
build.run(self)
554548

555549

556-
def get_ext_modules() -> list[Extension]:
550+
def get_ext_modules() -> List[Extension]:
557551
"""Returns the set of extension modules to build."""
558552

559553
ext_modules = []
560-
if ShouldBuild.flatc:
554+
if ShouldBuild.flatc():
561555
ext_modules.append(
562556
BuiltFile("third-party/flatbuffers/flatc", "executorch/data/bin/")
563557
)
564558

565-
if ShouldBuild.pybindings:
559+
if ShouldBuild.pybindings():
566560
ext_modules.append(
567561
# Install the prebuilt pybindings extension wrapper for the runtime,
568562
# portable kernels, and a selection of backends. This lets users
@@ -571,7 +565,7 @@ def get_ext_modules() -> list[Extension]:
571565
"_portable_lib.*", "executorch.extension.pybindings._portable_lib"
572566
)
573567
)
574-
if ShouldBuild.llama_custom_ops:
568+
if ShouldBuild.llama_custom_ops():
575569
ext_modules.append(
576570
# Install the prebuilt library for custom ops used in llama.
577571
BuiltFile(
@@ -588,7 +582,7 @@ def get_ext_modules() -> list[Extension]:
588582

589583

590584
setup(
591-
version=Version.string,
585+
version=Version.string(),
592586
# TODO(dbort): Could use py_modules to restrict the set of modules we
593587
# package, and package_data to restrict the set up non-python files we
594588
# include. See also setuptools/discovery.py for custom finders.

0 commit comments

Comments
 (0)