Skip to content

Commit 00b6383

Browse files
committed
Package headers into pip wheel
Summary: Add a new BaseExtension - HeaderFile. It takes a source directory relative to ExecuTorch root and copies all headers recursively into pip wheel during packaging. The destination is hardcoded to `executorch/include/executorch` in the pip wheel. Test Plan: ```bash python setup.py bdist_wheel ``` This prints out the following lines: ``` creating pip-out/bdist.linux-x86_64/wheel/executorch/include/executorch/runtime creating pip-out/bdist.linux-x86_64/wheel/executorch/include/executorch/runtime/core copying pip-out/lib.linux-x86_64-cpython-311/executorch/include/executorch/runtime/core/array_ref.h -> pip-out/bdist.linux-x86_64/wheel/executorch/include/executorch/runtime/core ... ``` Then install the wheel: ```bash pip install dist/executorch-0.5.0a0+52d5218-cp311-cp311-linux_x86_64.whl ``` And we can find the headers in site-packages ``` /data/users/larryliu/executorch/pip-out/lib.linux-x86_64-cpython-311/executorch/include/executorch/runtime/core/array_ref.h ``` Reviewers: Subscribers: Tasks: Tags:
1 parent 52d5218 commit 00b6383

File tree

1 file changed

+117
-29
lines changed

1 file changed

+117
-29
lines changed

setup.py

Lines changed: 117 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,14 @@ def get_executable_name(name: str) -> str:
191191
class _BaseExtension(Extension):
192192
"""A base class that maps an abstract source to an abstract destination."""
193193

194-
def __init__(self, src: str, dst: str, name: str):
194+
def __init__(
195+
self,
196+
src: str,
197+
dst: str,
198+
name: str,
199+
is_cmake_built: bool = True,
200+
is_dst_dir: bool = False,
201+
):
195202
# Source path; semantics defined by the subclass.
196203
self.src: str = src
197204

@@ -206,6 +213,14 @@ def __init__(self, src: str, dst: str, name: str):
206213
# that doesn't look like a module path.
207214
self.name: str = name
208215

216+
# If True, the file is built by cmake. If False, the file is copied from
217+
# the source tree.
218+
self.is_cmake_built: bool = is_cmake_built
219+
220+
# If True, the destination is a directory. If False, the destination is a
221+
# file.
222+
self.is_dst_dir: bool = is_dst_dir
223+
209224
super().__init__(name=self.name, sources=[])
210225

211226
def src_path(self, installer: "InstallerBuildExt") -> Path:
@@ -215,26 +230,25 @@ def src_path(self, installer: "InstallerBuildExt") -> Path:
215230
installer: The InstallerBuildExt instance that is installing the
216231
file.
217232
"""
218-
# Share the cmake-out location with CustomBuild.
219-
cmake_cache_dir = Path(installer.get_finalized_command("build").cmake_cache_dir)
233+
if self.is_cmake_built:
234+
# Share the cmake-out location with CustomBuild.
235+
cmake_cache_dir = Path(
236+
installer.get_finalized_command("build").cmake_cache_dir
237+
)
238+
239+
cfg = get_build_type(installer.debug)
220240

221-
cfg = get_build_type(installer.debug)
241+
if os.name == "nt":
242+
# Replace %BUILD_TYPE% with the current build type.
243+
self.src = self.src.replace("%BUILD_TYPE%", cfg)
244+
else:
245+
# Remove %BUILD_TYPE% from the path.
246+
self.src = self.src.replace("/%BUILD_TYPE%", "")
222247

223-
if os.name == "nt":
224-
# Replace %BUILD_TYPE% with the current build type.
225-
self.src = self.src.replace("%BUILD_TYPE%", cfg)
248+
# Construct the full source path, do not resolve globs.
249+
return cmake_cache_dir / Path(self.src)
226250
else:
227-
# Remove %BUILD_TYPE% from the path.
228-
self.src = self.src.replace("/%BUILD_TYPE%", "")
229-
230-
# Construct the full source path, resolving globs. If there are no glob
231-
# pattern characters, this will just ensure that the source file exists.
232-
srcs = tuple(cmake_cache_dir.glob(self.src))
233-
if len(srcs) != 1:
234-
raise ValueError(
235-
f"Expected exactly one file matching '{self.src}'; found {repr(srcs)}"
236-
)
237-
return srcs[0]
251+
return Path(self.src)
238252

239253

240254
class BuiltFile(_BaseExtension):
@@ -302,6 +316,54 @@ def dst_path(self, installer: "InstallerBuildExt") -> Path:
302316
return dst_root / Path(self.dst)
303317

304318

319+
class HeaderFile(_BaseExtension):
320+
"""An extension that installs headers in a directory.
321+
322+
This isn't technically a `build_ext` style python extension, but there's no
323+
dedicated command for installing arbitrary data. It's convenient to use
324+
this, though, because it lets us manage the files to install as entries in
325+
`ext_modules`.
326+
"""
327+
328+
def __init__(
329+
self,
330+
src_dir: str,
331+
src_name: Optional[str] = None,
332+
dst_dir: Optional[str] = None,
333+
):
334+
"""Initializes a BuiltFile.
335+
336+
Args:
337+
src_dir: The directory of the headers to install, relative to the root
338+
ExecuTorch source directory. Under the hood, we glob all the headers
339+
using `*.h` patterns.
340+
src_name: The name of the header to install. If not specified, all the
341+
headers in the src_dir will be installed.
342+
dst_dir: The directory to install to, relative to the root of the pip
343+
package. If not specified, defaults to `include/executorch/<src_dir>`.
344+
"""
345+
if src_name is None:
346+
src_name = "*.h"
347+
src = os.path.join(src_dir, src_name)
348+
if dst_dir is None:
349+
dst = "executorch/include/executorch/"
350+
else:
351+
dst = dst_dir
352+
super().__init__(
353+
src=src, dst=dst, name=src_name, is_cmake_built=False, is_dst_dir=True
354+
)
355+
356+
def dst_path(self, installer: "InstallerBuildExt") -> Path:
357+
"""Returns the path to the destination file.
358+
359+
Args:
360+
installer: The InstallerBuildExt instance that is installing the
361+
file.
362+
"""
363+
dst_root = Path(installer.build_lib).resolve()
364+
return dst_root / Path(self.dst)
365+
366+
305367
class BuiltExtension(_BaseExtension):
306368
"""An extension that installs a python extension that was built by cmake."""
307369

@@ -364,19 +426,30 @@ def build_extension(self, ext: _BaseExtension) -> None:
364426
src_file: Path = ext.src_path(self)
365427
dst_file: Path = ext.dst_path(self)
366428

367-
# Ensure that the destination directory exists.
368-
self.mkpath(os.fspath(dst_file.parent))
369-
370429
# Copy the file.
371-
self.copy_file(os.fspath(src_file), os.fspath(dst_file))
430+
src_list = src_file.parent.rglob(src_file.name)
431+
for src in src_list:
432+
if ext.is_dst_dir:
433+
# Destination is a prefix directory. Copy the file to the
434+
# destination directory.
435+
dst = dst_file / src
436+
else:
437+
# Destination is a file. Copy the file to the destination file.
438+
dst = dst_file
439+
440+
# Ensure that the destination directory exists.
441+
if not dst.parent.exists():
442+
self.mkpath(os.fspath(dst.parent))
443+
444+
self.copy_file(os.fspath(src), os.fspath(dst))
372445

373-
# Ensure that the destination file is writable, even if the source was
374-
# not. build_py does this by passing preserve_mode=False to copy_file,
375-
# but that would clobber the X bit on any executables. TODO(dbort): This
376-
# probably won't work on Windows.
377-
if not os.access(src_file, os.W_OK):
378-
# Make the file writable. This should respect the umask.
379-
os.chmod(src_file, os.stat(src_file).st_mode | 0o222)
446+
# Ensure that the destination file is writable, even if the source was
447+
# not. build_py does this by passing preserve_mode=False to copy_file,
448+
# but that would clobber the X bit on any executables. TODO(dbort): This
449+
# probably won't work on Windows.
450+
if not os.access(src, os.W_OK):
451+
# Make the file writable. This should respect the umask.
452+
os.chmod(src, os.stat(src).st_mode | 0o222)
380453

381454

382455
class CustomBuildPy(build_py):
@@ -618,6 +691,21 @@ def run(self):
618691
def get_ext_modules() -> List[Extension]:
619692
"""Returns the set of extension modules to build."""
620693
ext_modules = []
694+
695+
# Copy all the necessary headers into include/executorch/ so that they can
696+
# be found in the pip package. This is a subset of the headers that are
697+
# essential for building custom ops extensions
698+
for include_dir in [
699+
"runtime/core/",
700+
"runtime/kernel/",
701+
"runtime/platform/",
702+
"extension/kernel_util/",
703+
]:
704+
ext_modules.append(
705+
HeaderFile(
706+
src_dir=include_dir,
707+
)
708+
)
621709
if ShouldBuild.flatc():
622710
ext_modules.append(
623711
BuiltFile(

0 commit comments

Comments
 (0)