@@ -191,7 +191,14 @@ def get_executable_name(name: str) -> str:
191
191
class _BaseExtension (Extension ):
192
192
"""A base class that maps an abstract source to an abstract destination."""
193
193
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
+ ):
195
202
# Source path; semantics defined by the subclass.
196
203
self .src : str = src
197
204
@@ -206,6 +213,14 @@ def __init__(self, src: str, dst: str, name: str):
206
213
# that doesn't look like a module path.
207
214
self .name : str = name
208
215
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
+
209
224
super ().__init__ (name = self .name , sources = [])
210
225
211
226
def src_path (self , installer : "InstallerBuildExt" ) -> Path :
@@ -215,26 +230,25 @@ def src_path(self, installer: "InstallerBuildExt") -> Path:
215
230
installer: The InstallerBuildExt instance that is installing the
216
231
file.
217
232
"""
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 )
220
240
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%" , "" )
222
247
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 )
226
250
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 )
238
252
239
253
240
254
class BuiltFile (_BaseExtension ):
@@ -302,6 +316,54 @@ def dst_path(self, installer: "InstallerBuildExt") -> Path:
302
316
return dst_root / Path (self .dst )
303
317
304
318
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
+
305
367
class BuiltExtension (_BaseExtension ):
306
368
"""An extension that installs a python extension that was built by cmake."""
307
369
@@ -364,19 +426,30 @@ def build_extension(self, ext: _BaseExtension) -> None:
364
426
src_file : Path = ext .src_path (self )
365
427
dst_file : Path = ext .dst_path (self )
366
428
367
- # Ensure that the destination directory exists.
368
- self .mkpath (os .fspath (dst_file .parent ))
369
-
370
429
# 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 ))
372
445
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 )
380
453
381
454
382
455
class CustomBuildPy (build_py ):
@@ -618,6 +691,21 @@ def run(self):
618
691
def get_ext_modules () -> List [Extension ]:
619
692
"""Returns the set of extension modules to build."""
620
693
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
+ )
621
709
if ShouldBuild .flatc ():
622
710
ext_modules .append (
623
711
BuiltFile (
0 commit comments