|
56 | 56 | from distutils import log
|
57 | 57 | from distutils.sysconfig import get_python_lib
|
58 | 58 | from pathlib import Path
|
| 59 | +from typing import Optional |
59 | 60 |
|
60 | 61 | from setuptools import Extension, setup
|
61 | 62 | from setuptools.command.build import build
|
@@ -84,6 +85,77 @@ def pybindings(cls) -> bool:
|
84 | 85 | return cls._is_env_enabled("EXECUTORCH_BUILD_PYBIND", default=False)
|
85 | 86 |
|
86 | 87 |
|
| 88 | +class Version: |
| 89 | + """Static properties that describe the version of the pip package.""" |
| 90 | + |
| 91 | + # Cached values returned by the properties. |
| 92 | + __root_dir_attr: Optional[str] = None |
| 93 | + __string_attr: Optional[str] = None |
| 94 | + __git_hash_attr: Optional[str] = None |
| 95 | + |
| 96 | + @classmethod |
| 97 | + @property |
| 98 | + def _root_dir(cls) -> str: |
| 99 | + """The path to the root of the git repo.""" |
| 100 | + if cls.__root_dir_attr is None: |
| 101 | + # This setup.py file lives in the root of the repo. |
| 102 | + cls.__root_dir_attr = str(Path(__file__).parent.resolve()) |
| 103 | + return str(cls.__root_dir_attr) |
| 104 | + |
| 105 | + @classmethod |
| 106 | + @property |
| 107 | + def git_hash(cls) -> Optional[str]: |
| 108 | + """The current git hash, if known.""" |
| 109 | + if cls.__git_hash_attr is None: |
| 110 | + import subprocess |
| 111 | + |
| 112 | + try: |
| 113 | + cls.__git_hash_attr = ( |
| 114 | + subprocess.check_output( |
| 115 | + ["git", "rev-parse", "HEAD"], cwd=cls._root_dir |
| 116 | + ) |
| 117 | + .decode("ascii") |
| 118 | + .strip() |
| 119 | + ) |
| 120 | + except subprocess.CalledProcessError: |
| 121 | + cls.__git_hash_attr = "" # Non-None but empty. |
| 122 | + # A non-None but empty value indicates that we don't know it. |
| 123 | + return cls.__git_hash_attr if cls.__git_hash_attr else None |
| 124 | + |
| 125 | + @classmethod |
| 126 | + @property |
| 127 | + def string(cls) -> str: |
| 128 | + """The version string.""" |
| 129 | + if cls.__string_attr is None: |
| 130 | + # If set, BUILD_VERSION should override any local version |
| 131 | + # information. CI will use this to manage, e.g., release vs. nightly |
| 132 | + # versions. |
| 133 | + version = os.getenv("BUILD_VERSION", "").strip() |
| 134 | + if not version: |
| 135 | + # Otherwise, read the version from a local file and add the git |
| 136 | + # commit if available. |
| 137 | + version = ( |
| 138 | + open(os.path.join(cls._root_dir, "version.txt")).read().strip() |
| 139 | + ) |
| 140 | + if cls.git_hash: |
| 141 | + version += "+" + cls.git_hash[:7] |
| 142 | + cls.__string_attr = version |
| 143 | + return cls.__string_attr |
| 144 | + |
| 145 | + @classmethod |
| 146 | + def write_to_python_file(cls, path: str) -> None: |
| 147 | + """Creates a file similar to PyTorch core's `torch/version.py`.""" |
| 148 | + lines = [ |
| 149 | + "from typing import Optional", |
| 150 | + '__all__ = ["__version__", "git_version"]', |
| 151 | + f'__version__ = "{cls.string}"', |
| 152 | + # A string or None. |
| 153 | + f"git_version: Optional[str] = {repr(cls.git_hash)}", |
| 154 | + ] |
| 155 | + with open(path, "w") as fp: |
| 156 | + fp.write("\n".join(lines) + "\n") |
| 157 | + |
| 158 | + |
87 | 159 | class _BaseExtension(Extension):
|
88 | 160 | """A base class that maps an abstract source to an abstract destination."""
|
89 | 161 |
|
@@ -269,6 +341,9 @@ def run(self):
|
269 | 341 | # package subdirectory.
|
270 | 342 | dst_root = os.path.join(self.build_lib, self.get_package_dir("executorch"))
|
271 | 343 |
|
| 344 | + # Create the version file. |
| 345 | + Version.write_to_python_file(os.path.join(dst_root, "version.py")) |
| 346 | + |
272 | 347 | # Manually copy files into the output package directory. These are
|
273 | 348 | # typically python "resource" files that will live alongside the python
|
274 | 349 | # code that uses them.
|
@@ -447,6 +522,7 @@ def get_ext_modules() -> list[Extension]:
|
447 | 522 |
|
448 | 523 |
|
449 | 524 | setup(
|
| 525 | + version=Version.string, |
450 | 526 | # TODO(dbort): Could use py_modules to restrict the set of modules we
|
451 | 527 | # package, and package_data to restrict the set up non-python files we
|
452 | 528 | # include. See also setuptools/discovery.py for custom finders.
|
|
0 commit comments