99
1010The Cython and Cythonize compilation is done automatically by the build backend"""
1111
12+ from __future__ import annotations
13+
1214import os
1315import pathlib
1416import platform as sysplatform
2123import urllib .request
2224from ctypes .util import find_library
2325from pathlib import Path
24- from typing import Optional , Tuple
2526
2627from Cython .Build import cythonize
2728from Cython .Distutils .build_ext import new_build_ext as build_ext
29+ from delocate .wheeltools import InWheel
2830from setuptools import Command , Extension , setup
2931
3032
@@ -42,7 +44,10 @@ class Config:
4244 apple_signing_keychain : str = os .getenv ("APPLE_SIGNING_KEYCHAIN_PATH" )
4345 apple_signing_keychain_profile : str = os .getenv ("APPLE_SIGNING_KEYCHAIN_PROFILE" )
4446
45- supported_platforms = {
47+ # windows
48+ _msvc_debug : bool = bool (os .getenv ("MSVC_DEBUG" ))
49+
50+ supported_platforms = { # noqa: RUF012
4651 "Darwin" : ["x86_64" , "arm64" ],
4752 "Linux" : ["x86_64" , "aarch64" ],
4853 "Linux-musl" : ["x86_64" , "aarch64" ],
@@ -52,8 +57,9 @@ class Config:
5257 base_dir : pathlib .Path = Path (__file__ ).parent
5358
5459 # Avoid running cythonize on `setup.py clean` and similar
55- buildless_commands : Tuple [str ] = (
60+ buildless_commands : tuple [str , ... ] = (
5661 "clean" ,
62+ "repair_win_wheel" ,
5763 "--help" ,
5864 "egg_info" ,
5965 "--version" ,
@@ -153,7 +159,15 @@ def wants_universal(self) -> bool:
153159 "universal2"
154160 )
155161
156- def get_download_filename (self , arch : Optional [str ] = None ) -> str :
162+ @property
163+ def use_msvc_debug (self ) -> bool :
164+ """whether to add _DEBUG define to compilation
165+
166+ requires having python debug binaries installed.
167+ mandatory for compiling against libzim nighlies"""
168+ return self ._msvc_debug or self .is_nightly
169+
170+ def get_download_filename (self , arch : str | None = None ) -> str :
157171 """filename to download to get binary libzim for platform/arch"""
158172 arch = arch or self .arch
159173
@@ -276,11 +290,11 @@ def _install_from(self, folder: pathlib.Path):
276290 print (f"{ fpath } -> { libzim_dir / fpath .name } " )
277291 os .replace (fpath , libzim_dir / fpath .name )
278292 # windows has different folder and name
279- for fpath in folder . joinpath ( "bin" ). rglob ( "zim-*.dll" ):
280- print ( f" { fpath } -> { libzim_dir / fpath . name } " )
281- os . replace ( fpath , libzim_dir / fpath . name )
282- # windows again, not sure its required at all
283- for fpath in folder . joinpath ( "lib" ). rglob ( "zim.lib" ):
293+ for fpath in (
294+ list ( folder . joinpath ( "bin" ). rglob ( "zim-*.dll" ) )
295+ + list ( folder . joinpath ( "bin" ). rglob ( "icu*.dll" ) )
296+ + list ( folder . joinpath ( "lib" ). rglob ( "zim.lib" ))
297+ ):
284298 print (f"{ fpath } -> { libzim_dir / fpath .name } " )
285299 os .replace (fpath , libzim_dir / fpath .name )
286300
@@ -320,6 +334,18 @@ def cleanup(self):
320334 print ("removing downloaded headers" )
321335 shutil .rmtree (self .header_file .parent , ignore_errors = True )
322336
337+ def repair_windows_wheel (self , wheel : Path , dest_dir : Path ):
338+ """opens windows wheels in target folder and moves all DLLs files inside
339+ subdirectories of the wheel to the root one (where wrapper is expected)"""
340+
341+ dest_wheel = dest_dir / wheel .name
342+ with InWheel (str (wheel ), str (dest_wheel )) as wheel_dir_path :
343+ print (f"repairing { wheel .name } for Windows (DLLs next to wrapper)" )
344+ wheel_dir = Path (wheel_dir_path )
345+ for dll in wheel_dir .joinpath ("libzim" ).rglob ("*.dll" ):
346+ print (f"> moving { dll } using { dll .relative_to (wheel_dir ).parent } " )
347+ dll .replace (wheel_dir / dll .name )
348+
323349 @property
324350 def header_file (self ) -> pathlib .Path :
325351 return self .base_dir / "include" / "zim" / "zim.h"
@@ -381,14 +407,19 @@ def get_cython_extension():
381407 print ("Using local libzim binary. Set `USE_SYSTEM_LIBZIM` otherwise." )
382408 include_dirs .append ("include" )
383409 library_dirs = ["libzim" ]
384- runtime_library_dirs = (
385- [f"@loader_path/libzim/{ config .libzim_fname } " ]
386- if sysplatform == "Darwin"
387- else ["$ORIGIN/libzim/" ]
388- )
410+
411+ if config .platform != "Windows" :
412+ runtime_library_dirs = (
413+ [f"@loader_path/libzim/{ config .libzim_fname } " ]
414+ if sysplatform == "Darwin"
415+ else ["$ORIGIN/libzim/" ]
416+ )
389417
390418 extra_compile_args = ["-std=c++11" , "-Wall" ]
391- if config .platform != "Windows" :
419+ if config .platform == "Windows" :
420+ extra_compile_args .append ("/MDd" if config .use_msvc_debug else "/MD" )
421+ ...
422+ else :
392423 extra_compile_args .append ("-Wextra" )
393424
394425 wrapper_extension = Extension (
@@ -542,7 +573,29 @@ def run(self):
542573 config .cleanup ()
543574
544575
545- if len (sys .argv ) == 2 and sys .argv [1 ] in config .buildless_commands :
576+ class RepairWindowsWheel (Command ):
577+ user_options = [ # noqa: RUF012
578+ ("wheel=" , None , "Wheel to repair" ),
579+ ("destdir=" , None , "Destination folder for repaired wheels" ),
580+ ]
581+
582+ def initialize_options (self ):
583+ self .wheel = None
584+ self .destdir = None
585+
586+ def finalize_options (self ):
587+ assert Path (self .wheel ).exists (), "wheel file does not exists"
588+ assert (
589+ Path (self .destdir ).exists () and Path (self .destdir ).is_dir ()
590+ ), "dest_dir does not exists"
591+
592+ def run (self ):
593+ config .repair_windows_wheel (wheel = Path (self .wheel ), dest_dir = Path (self .destdir ))
594+
595+
596+ if len (sys .argv ) == 1 or (
597+ len (sys .argv ) == 2 and sys .argv [1 ] in config .buildless_commands
598+ ):
546599 ext_modules = None
547600else :
548601 ext_modules = get_cython_extension ()
@@ -552,6 +605,7 @@ def run(self):
552605 "build_ext" : LibzimBuildExt ,
553606 "download_libzim" : DownloadLibzim ,
554607 "clean" : LibzimClean ,
608+ "repair_win_wheel" : RepairWindowsWheel ,
555609 },
556610 ext_modules = ext_modules ,
557611)
0 commit comments