1616import shutil
1717import subprocess
1818import sys
19+ import sysconfig
1920import urllib .request
2021from ctypes .util import find_library
2122from pathlib import Path
22- from typing import Tuple
23+ from typing import Optional , Tuple
2324
2425from Cython .Build import cythonize
2526from Cython .Distutils .build_ext import new_build_ext as build_ext
@@ -73,10 +74,6 @@ def is_nightly(self) -> bool:
7374 @property
7475 def platform (self ) -> str :
7576 """Platform building for: Darwin, Linux"""
76- # import json
77-
78- # with open("environ.txt", "w") as fh:
79- # json.dump(dict(os.environ), fh, indent=4)
8077 return sysplatform .system ()
8178
8279 @property
@@ -90,10 +87,14 @@ def platform_libc(self) -> str:
9087 def arch (self ) -> str :
9188 # macOS x86_64|arm64 - linux x86_64|aarch64
9289
93- # when using cibuildwheel on macOS to cross-compile, `PLAT` contains
90+ # when using cibuildwheel on macOS to cross-compile,
91+ # `_PYTHON_HOST_PLATFORM` contains
9492 # a platform string like macosx-11.0-arm64
9593 # we extract the cross-compile arch from it
96- return os .getenv ("PLAT" , "" ).rsplit ("-" , 1 )[- 1 ] or sysplatform .machine ()
94+ return (
95+ os .getenv ("_PYTHON_HOST_PLATFORM" , "" ).rsplit ("-" , 1 )[- 1 ]
96+ or sysplatform .machine ()
97+ )
9798
9899 def check_platform (self ):
99100 if (
@@ -124,20 +125,71 @@ def is_musl(self) -> bool:
124125 return False
125126
126127 @property
127- def download_filename (self ) -> str :
128+ def wants_universal (self ) -> bool :
129+ """whether requesting a macOS universal build"""
130+ return self .platform == "Darwin" and sysconfig .get_platform ().endswith (
131+ "universal2"
132+ )
133+
134+ def get_download_filename (self , arch : Optional [str ] = None ) -> str :
128135 """filename to download to get binary libzim for platform/arch"""
136+ arch = arch or self .arch
137+
129138 lzplatform = {"Darwin" : "macos" , "Linux" : "linux" }.get (self .platform )
130139 if lzplatform == "linux" and self .is_musl :
131140 lzplatform = "linux_musl"
132141
133142 return pathlib .Path (
134- f"libzim_{ lzplatform } -{ self . arch } -{ self .libzim_dl_version } .tar.gz"
143+ f"libzim_{ lzplatform } -{ arch } -{ self .libzim_dl_version } .tar.gz"
135144 ).name
136145
137146 def download_to_dest (self ):
138147 """download expected libzim binary into libzim/ and libzim/include/ folders"""
139- libzim_dir = self .base_dir / "libzim"
140- fpath = self .base_dir / self .download_filename
148+ if self .wants_universal :
149+ folders = {}
150+ for arch in self .supported_platforms ["Darwin" ]:
151+ folders [arch ] = self ._download_and_extract (
152+ self .get_download_filename (arch )
153+ )
154+
155+ try :
156+ # duplicate x86_64 tree as placeholder (removing first)
157+ folder = folders ["x86_64" ].with_name (
158+ folders ["x86_64" ].name .replace ("x86_64" , "universal" )
159+ )
160+ shutil .rmtree (folder , ignore_errors = True )
161+ shutil .copytree (
162+ folders ["x86_64" ],
163+ folder ,
164+ symlinks = True ,
165+ ignore_dangling_symlinks = True ,
166+ )
167+ # delete libzim from copied tree
168+ dest = folder / "lib" / self .libzim_fname
169+ dest .unlink ()
170+ # create universal from all archs
171+ subprocess .run (
172+ ["lipo" ]
173+ + [
174+ str (folder / "lib" / self .libzim_fname )
175+ for folder in folders .values ()
176+ ]
177+ + ["-output" , str (dest ), "-create" ],
178+ check = True ,
179+ )
180+ finally :
181+ # clean-up temp folders
182+ for _folder in folders .values ():
183+ shutil .rmtree (_folder , ignore_errors = True )
184+ else :
185+ folder = self ._download_and_extract (self .get_download_filename ())
186+
187+ self ._install_from (folder )
188+
189+ def _download_and_extract (self , filename : str ) -> pathlib .Path :
190+ """folder it downloaded and extracted libzim dist to"""
191+
192+ fpath = self .base_dir / filename
141193 source_url = "http://download.openzim.org/release/libzim"
142194 if self .is_nightly :
143195 source_url = f"http://download.openzim.org/nightly/{ self .libzim_dl_version } "
@@ -158,6 +210,12 @@ def download_to_dest(self):
158210 shutil .unpack_archive (fpath , self .base_dir , "gztar" )
159211 folder = fpath .with_name (fpath .name .replace (".tar.gz" , "" ))
160212
213+ return folder
214+
215+ def _install_from (self , folder : pathlib .Path ):
216+ """move headers and libzim binary from dist folder to expected location"""
217+ libzim_dir = self .base_dir / "libzim"
218+
161219 # remove existing headers if present
162220 self .base_dir .joinpath ("include" ).mkdir (parents = True , exist_ok = True )
163221 shutil .rmtree (self .base_dir / "include" / "zim" , ignore_errors = True )
@@ -167,6 +225,7 @@ def download_to_dest(self):
167225
168226 # copy new libs
169227 for fpath in folder .joinpath ("lib" ).rglob ("libzim.*" ):
228+ print (f"{ fpath } -> { libzim_dir / fpath .name } " )
170229 os .replace (fpath , libzim_dir / fpath .name )
171230
172231 # remove temp folder
0 commit comments