@@ -225,7 +225,7 @@ def build_python_path() -> Path:
225225
226226@contextmanager
227227def group (text : str ):
228- """A context manager that output a log marker around a section of a build.
228+ """A context manager that outputs a log marker around a section of a build.
229229
230230 If running in a GitHub Actions environment, the GitHub syntax for
231231 collapsible log sections is used.
@@ -460,6 +460,43 @@ def package_version(prefix_path: Path) -> str:
460460 sys .exit ("Unable to determine Python version being packaged." )
461461
462462
463+ def lib_platform_files (dirname , names ):
464+ """A file filter that ignores platform-specific files in the lib directory.
465+ """
466+ path = Path (dirname )
467+ if (
468+ path .parts [- 3 ] == "lib"
469+ and path .parts [- 2 ].startswith ("python" )
470+ and path .parts [- 1 ] == "lib-dynload"
471+ ):
472+ return names
473+ elif path .parts [- 2 ] == "lib" and path .parts [- 1 ].startswith ("python" ):
474+ ignored_names = set (
475+ name
476+ for name in names
477+ if (
478+ name .startswith ("_sysconfigdata_" )
479+ or name .startswith ("_sysconfig_vars_" )
480+ or name == "build-details.json"
481+ )
482+ )
483+ else :
484+ ignored_names = set ()
485+
486+ return ignored_names
487+
488+
489+ def lib_non_platform_files (dirname , names ):
490+ """A file filter that ignores anything *except* platform-specific files
491+ in the lib directory.
492+ """
493+ path = Path (dirname )
494+ if path .parts [- 2 ] == "lib" and path .parts [- 1 ].startswith ("python" ):
495+ return set (names ) - lib_platform_files (dirname , names ) - {"lib-dynload" }
496+ else :
497+ return set ()
498+
499+
463500def create_xcframework (platform : str ) -> str :
464501 """Build an XCframework from the component parts for the platform.
465502
@@ -547,6 +584,7 @@ def create_xcframework(platform: str) -> str:
547584 # to be copied in separately.
548585 print ()
549586 print ("Copy additional resources..." )
587+ has_common_stdlib = False
550588 for slice_name , slice_parts in HOSTS [platform ].items ():
551589 # Some parts are the same across all slices, so we can any of the
552590 # host frameworks as the source for the merged version.
@@ -575,71 +613,26 @@ def create_xcframework(platform: str) -> str:
575613 slice_framework / "Headers/pyconfig.h" ,
576614 )
577615
578- # Copy the lib folder. If there's only one slice, we can copy the .so
579- # binary modules as is. Otherwise, we ignore .so files, and merge them
580- # into fat binaries in the next step.
581- print (f" - { slice_name } standard library" )
582- shutil .copytree (
583- first_path / "lib" ,
584- slice_path / "lib" ,
585- ignore = (
586- None
587- if len (slice_parts ) == 1
588- else shutil .ignore_patterns ("*.so" )
589- ),
590- )
591-
592- # If there's more than one slice, merge binary .so modules.
593- if len (slice_parts ) > 1 :
594- print (f" - { slice_name } merging binary modules" )
595- lib_dirs = [
596- CROSS_BUILD_DIR
597- / f"{ host_triple } /Apple/iOS/Frameworks"
598- / f"{ multiarch } /lib"
599- for host_triple , multiarch in slice_parts .items ()
600- ]
601-
602- # The list of .so binary modules should be the same in each slice.
603- # Find all .so files in each slice; then sort and zip those lists.
604- # Zipping with strict=True means any length discrepancy will raise
605- # an error.
606- for lib_set in zip (
607- * (sorted (lib_dir .glob ("**/*.so" )) for lib_dir in lib_dirs ),
608- strict = True ,
609- ):
610- # An additional safety check - not only must the two lists of
611- # libraries be the same length, but they must have the same
612- # module names. Raise an error if there's any discrepancy.
613- relative_libs = set (
614- lib .relative_to (lib_dir .parent )
615- for lib_dir , lib in zip (lib_dirs , lib_set )
616- )
617- if len (relative_libs ) != 1 :
618- raise RuntimeError (
619- f"Cannot merge non-matching libraries: { relative_libs } "
620- ) from None
621-
622- # Merge the per-arch .so files into a single "fat" binary.
623- relative_lib = next (iter (relative_libs ))
624- run (
625- [
626- "lipo" ,
627- "-create" ,
628- "-output" ,
629- slice_path / relative_lib ,
630- ]
631- + [
632- (
633- CROSS_BUILD_DIR
634- / f"{ host_triple } /Apple/iOS/Frameworks/{ multiarch } "
635- / relative_lib
636- )
637- for host_triple , multiarch in slice_parts .items ()
638- ]
639- )
640-
641616 print (f" - { slice_name } architecture-specific files" )
642617 for host_triple , multiarch in slice_parts .items ():
618+ print (f" - { multiarch } standard library" )
619+ arch , _ = multiarch .split ("-" , 1 )
620+
621+ if not has_common_stdlib :
622+ print (" - using this architecture as the common stdlib" )
623+ shutil .copytree (
624+ framework_path (host_triple , multiarch ) / "lib" ,
625+ package_path / "Python.xcframework/lib" ,
626+ ignore = lib_platform_files ,
627+ )
628+ has_common_stdlib = True
629+
630+ shutil .copytree (
631+ framework_path (host_triple , multiarch ) / "lib" ,
632+ slice_path / f"lib-{ arch } " ,
633+ ignore = lib_non_platform_files ,
634+ )
635+
643636 # Copy the host's pyconfig.h to an architecture-specific name.
644637 arch = multiarch .split ("-" )[0 ]
645638 host_path = (
@@ -654,12 +647,11 @@ def create_xcframework(platform: str) -> str:
654647 slice_framework / f"Headers/pyconfig-{ arch } .h" ,
655648 )
656649
657- # Copy any files (such as sysconfig) that are multiarch-specific.
658- for path in host_path .glob (f"lib/**/*_{ multiarch } .*" ):
659- shutil .copy (
660- path ,
661- slice_path / (path .relative_to (host_path )),
662- )
650+ print (" - build tools" )
651+ shutil .copytree (
652+ PYTHON_DIR / "Apple/iOS/Resources/build" ,
653+ package_path / "Python.xcframework/build" ,
654+ )
663655
664656 return version
665657
@@ -925,6 +917,11 @@ def parse_args() -> argparse.Namespace:
925917 "an ARM64 iPhone 16 Pro simulator running iOS 26.0."
926918 ),
927919 )
920+ cmd .add_argument (
921+ "--slow" ,
922+ action = "store_true" ,
923+ help = "Run tests with --slow-ci options." ,
924+ )
928925
929926 for subcommand in [configure_build , configure_host , build , ci ]:
930927 subcommand .add_argument (
0 commit comments