@@ -62,7 +62,39 @@ def finalize_options(self):
6262 ("inplace" , "inplace" ),
6363 )
6464
65+ def get_target_triple (self ):
66+ # If we are on a 64-bit machine, but running a 32-bit Python, then
67+ # we'll target a 32-bit Rust build.
68+ # Automatic target detection can be overridden via the CARGO_BUILD_TARGET
69+ # environment variable.
70+ if os .getenv ("CARGO_BUILD_TARGET" ):
71+ return os .environ ["CARGO_BUILD_TARGET" ]
72+ elif self .plat_name == "win32" :
73+ return "i686-pc-windows-msvc"
74+ elif self .plat_name == "win-amd64" :
75+ return "x86_64-pc-windows-msvc"
76+ elif self .plat_name .startswith ("macosx-" ) and platform .machine () == "x86_64" :
77+ # x86_64 or arm64 macOS targeting x86_64
78+ return "x86_64-apple-darwin"
79+
6580 def run_for_extension (self , ext : RustExtension ):
81+ arch_flags = os .getenv ("ARCHFLAGS" )
82+ universal2 = False
83+ if self .plat_name .startswith ("macosx-" ) and arch_flags :
84+ universal2 = "x86_64" in arch_flags and "arm64" in arch_flags
85+ if universal2 :
86+ arm64_dylib_paths = self .build_extension (ext , "aarch64-apple-darwin" )
87+ x86_64_dylib_paths = self .build_extension (ext , "x86_64-apple-darwin" )
88+ dylib_paths = []
89+ for (target_fname , arm64_dylib ), (_ , x86_64_dylib ) in zip (arm64_dylib_paths , x86_64_dylib_paths ):
90+ fat_dylib_path = arm64_dylib .replace ("aarch64-apple-darwin/" , "" )
91+ self .create_universal2_binary (fat_dylib_path , [arm64_dylib , x86_64_dylib ])
92+ dylib_paths .append ((target_fname , fat_dylib_path ))
93+ else :
94+ dylib_paths = self .build_extension (ext )
95+ self .install_extension (ext , dylib_paths )
96+
97+ def build_extension (self , ext : RustExtension , target_triple = None ):
6698 executable = ext .binding == Binding .Exec
6799
68100 rust_target_info = get_rust_target_info ()
@@ -84,22 +116,8 @@ def run_for_extension(self, ext: RustExtension):
84116 )
85117 rustflags = ""
86118
87- # If we are on a 64-bit machine, but running a 32-bit Python, then
88- # we'll target a 32-bit Rust build.
89- # Automatic target detection can be overridden via the CARGO_BUILD_TARGET
90- # environment variable.
91- target_triple = None
119+ target_triple = target_triple or self .get_target_triple ()
92120 target_args = []
93- if os .getenv ("CARGO_BUILD_TARGET" ):
94- target_triple = os .environ ["CARGO_BUILD_TARGET" ]
95- elif self .plat_name == "win32" :
96- target_triple = "i686-pc-windows-msvc"
97- elif self .plat_name == "win-amd64" :
98- target_triple = "x86_64-pc-windows-msvc"
99- elif self .plat_name .startswith ("macosx-" ) and platform .machine () == "x86_64" :
100- # x86_64 or arm64 macOS targeting x86_64
101- target_triple = "x86_64-apple-darwin"
102-
103121 if target_triple is not None :
104122 target_args = ["--target" , target_triple ]
105123
@@ -264,7 +282,14 @@ def run_for_extension(self, ext: RustExtension):
264282 raise DistutilsExecError (
265283 f"Rust build failed; unable to find any { wildcard_so } in { artifactsdir } "
266284 )
285+ return dylib_paths
267286
287+ def install_extension (self , ext : RustExtension , dylib_paths ):
288+ executable = ext .binding == Binding .Exec
289+ debug_build = ext .debug if ext .debug is not None else self .inplace
290+ debug_build = self .debug if self .debug is not None else debug_build
291+ if self .release :
292+ debug_build = False
268293 # Ask build_ext where the shared library would go if it had built it,
269294 # then copy it there.
270295 build_ext = self .get_finalized_command ("build_ext" )
@@ -301,7 +326,7 @@ def run_for_extension(self, ext: RustExtension):
301326 args .insert (0 , "strip" )
302327 args .append (ext_path )
303328 try :
304- output = subprocess .check_output (args , env = env )
329+ output = subprocess .check_output (args )
305330 except subprocess .CalledProcessError :
306331 pass
307332
@@ -323,3 +348,31 @@ def get_dylib_ext_path(self, ext, target_fname):
323348 return build_ext .get_ext_fullpath (target_fname )
324349 finally :
325350 del build_ext .ext_map [modpath ]
351+
352+ @staticmethod
353+ def create_universal2_binary (output_path , input_paths ):
354+ # Try lipo first
355+ command = ["lipo" , "-create" , "-output" , output_path , * input_paths ]
356+ try :
357+ subprocess .check_output (command )
358+ except subprocess .CalledProcessError as e :
359+ output = e .output
360+ if isinstance (output , bytes ):
361+ output = e .output .decode ("latin-1" ).strip ()
362+ raise CompileError (
363+ "lipo failed with code: %d\n %s" % (e .returncode , output )
364+ )
365+ except OSError :
366+ # lipo not found, try using the fat-macho library
367+ try :
368+ from fat_macho import FatWriter
369+ except ImportError :
370+ raise DistutilsExecError (
371+ "failed to locate `lipo` or import `fat_macho.FatWriter`. "
372+ "Try installing with `pip install fat-macho` "
373+ )
374+ fat = FatWriter ()
375+ for input_path in input_paths :
376+ with open (input_path , "rb" ) as f :
377+ fat .add (f .read ())
378+ fat .write_to (output_path )
0 commit comments