Skip to content

Commit d72f53e

Browse files
committed
[build] Properly implement editable mode
1 parent 84273f4 commit d72f53e

File tree

3 files changed

+86
-29
lines changed

3 files changed

+86
-29
lines changed

extension/llm/custom_ops/custom_ops.py

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,22 +23,20 @@
2323
assert op2 is not None
2424
except:
2525
import glob
26-
27-
import executorch
26+
import os
2827

2928
# This is needed to ensure that custom ops are registered
3029
from executorch.extension.pybindings import portable_lib # noqa # usort: skip
3130

3231
# Ideally package is installed in only one location but usage of
3332
# PYATHONPATH can result in multiple locations.
3433
# ATM this is mainly used in CI for qnn runner. Will need to revisit this
35-
executorch_package_path = executorch.__path__[-1]
36-
logging.info(f"Looking for libcustom_ops_aot_lib.so in {executorch_package_path}")
37-
libs = list(
38-
glob.glob(
39-
f"{executorch_package_path}/**/libcustom_ops_aot_lib.*", recursive=True
40-
)
41-
)
34+
from pathlib import Path
35+
package_path = Path(__file__).parent.resolve()
36+
logging.info(f"Looking for libcustom_ops_aot_lib.so in {package_path}")
37+
38+
libs = list(package_path.glob("**/libcustom_ops_aot_lib.*"))
39+
4240
assert len(libs) == 1, f"Expected 1 library but got {len(libs)}"
4341
logging.info(f"Loading custom ops library: {libs[0]}")
4442
torch.ops.load_library(libs[0])

pyproject.toml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,25 @@ Changelog = "https://github.com/pytorch/executorch/releases"
8282
[project.scripts]
8383
flatc = "executorch.data.bin:flatc"
8484

85+
# TODO(dbort): Could use py_modules to restrict the set of modules we
86+
# package, and package_data to restrict the set up non-python files we
87+
# include. See also setuptools/discovery.py for custom finders.
88+
[tool.setuptools.package-dir]
89+
"executorch.backends" = "backends"
90+
"executorch.codegen" = "codegen"
91+
# TODO(mnachin T180504136): Do not put examples/models
92+
# into core pip packages. Refactor out the necessary utils
93+
# or core models files into a separate package.
94+
"executorch.examples.models" = "examples/models"
95+
"executorch.exir" = "exir"
96+
"executorch.extension" = "extension"
97+
"executorch.kernels.quantized" = "kernels/quantized"
98+
"executorch.schema" = "schema"
99+
"executorch.devtools" = "devtools"
100+
"executorch.devtools.bundled_program" = "devtools/bundled_program"
101+
"executorch.runtime" = "runtime"
102+
"executorch.util" = "util"
103+
85104
[tool.setuptools.package-data]
86105
# TODO(dbort): Prune /test[s]/ dirs, /third-party/ dirs, yaml files that we
87106
# don't need.

setup.py

Lines changed: 60 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
import platform
5252
import re
5353
import sys
54+
import site
5455

5556
# Import this before distutils so that setuptools can intercept the distuils
5657
# imports.
@@ -239,7 +240,7 @@ def src_path(self, installer: "InstallerBuildExt") -> Path:
239240
srcs = tuple(cmake_cache_dir.glob(self.src))
240241
if len(srcs) != 1:
241242
raise ValueError(
242-
f"""Expected exactly one file matching '{self.src}'; found {repr(srcs)}.
243+
f"""Expected exactly one file matching '{self.src}' in {cmake_cache_dir}; found {repr(srcs)}.
243244
244245
If that file is a CMake-built extension module file, and we are installing in editable mode, please disable the corresponding build option since it's not supported yet.
245246
@@ -371,7 +372,63 @@ def dst_path(self, installer: "InstallerBuildExt") -> Path:
371372

372373
class InstallerBuildExt(build_ext):
373374
"""Installs files that were built by cmake."""
375+
def __init__(self, *args, **kwargs):
376+
self._ran_build = False
377+
super().__init__(*args, **kwargs)
374378

379+
def run(self):
380+
# Run the build command first in editable mode. Since `build` command
381+
# will also trigger `build_ext` command, only run this once.
382+
if self._ran_build:
383+
return
384+
385+
if self.editable_mode:
386+
self._ran_build = True
387+
self.run_command("build")
388+
super().run()
389+
390+
391+
def copy_extensions_to_source(self) -> None:
392+
"""For each extension in `ext_modules`, we need to copy the extension
393+
file from the build directory to the correct location in the local
394+
directory.
395+
396+
This should only be triggered when inplace mode (editable mode) is enabled.
397+
398+
Args:
399+
400+
Returns:
401+
"""
402+
build_py = self.get_finalized_command('build_py')
403+
for ext in self.extensions:
404+
if isinstance(ext, BuiltExtension):
405+
modpath = ext.name.split('.')
406+
package = '.'.join(modpath[:-1])
407+
package_dir = os.path.abspath(build_py.get_package_dir(package))
408+
else:
409+
# HACK: get rid of the leading "executorch" in ext.dst.
410+
# This is because we don't have a root level "executorch" module.
411+
package_dir = ext.dst.removeprefix("executorch/")
412+
413+
# Ensure that the destination directory exists.
414+
self.mkpath(os.fspath(package_dir))
415+
416+
regular_file = ext.src_path(self)
417+
inplace_file = os.path.join(package_dir, os.path.basename(ext.src_path(self)))
418+
419+
# Always copy, even if source is older than destination, to ensure
420+
# that the right extensions for the current Python/platform are
421+
# used.
422+
if os.path.exists(regular_file) or not ext.optional:
423+
self.copy_file(regular_file, inplace_file, level=self.verbose)
424+
425+
if ext._needs_stub:
426+
inplace_stub = self._get_equivalent_stub(ext, inplace_file)
427+
self._write_stub_file(inplace_stub, ext, compile=True)
428+
# Always compile stub and remove the original (leave the cache behind)
429+
# (this behaviour was observed in previous iterations of the code)
430+
431+
375432
# TODO(dbort): Depend on the "build" command to ensure it runs first
376433

377434
def build_extension(self, ext: _BaseExtension) -> None:
@@ -630,6 +687,8 @@ def run(self):
630687
if not self.dry_run:
631688
# Dry run should log the command but not actually run it.
632689
(Path(cmake_cache_dir) / "CMakeCache.txt").unlink(missing_ok=True)
690+
# Set PYTHONPATH to the location of the pip package.
691+
os.environ["PYTHONPATH"] = site.getsitepackages()[0] + ";" + os.environ["PYTHONPATH"]
633692
with Buck2EnvironmentFixer():
634693
# The context manager may patch the environment while running this
635694
# cmake command, which happens to run buck2 to get some source
@@ -741,25 +800,6 @@ def get_ext_modules() -> List[Extension]:
741800

742801
setup(
743802
version=Version.string(),
744-
# TODO(dbort): Could use py_modules to restrict the set of modules we
745-
# package, and package_data to restrict the set up non-python files we
746-
# include. See also setuptools/discovery.py for custom finders.
747-
package_dir={
748-
"executorch/backends": "backends",
749-
"executorch/codegen": "codegen",
750-
# TODO(mnachin T180504136): Do not put examples/models
751-
# into core pip packages. Refactor out the necessary utils
752-
# or core models files into a separate package.
753-
"executorch/examples/models": "examples/models",
754-
"executorch/exir": "exir",
755-
"executorch/extension": "extension",
756-
"executorch/kernels/quantized": "kernels/quantized",
757-
"executorch/schema": "schema",
758-
"executorch/devtools": "devtools",
759-
"executorch/devtools/bundled_program": "devtools/bundled_program",
760-
"executorch/runtime": "runtime",
761-
"executorch/util": "util",
762-
},
763803
cmdclass={
764804
"build": CustomBuild,
765805
"build_ext": InstallerBuildExt,

0 commit comments

Comments
 (0)