33This hook downloads and embeds the pytauri-wheel native extension for the
44target platform, making pywry fully self-contained without requiring users
55to download pytauri-wheel separately.
6+
7+ It also sets the wheel tags to make this a platform-specific wheel,
8+ which is required since we bundle native binaries.
69"""
710
811# pylint: disable=too-many-locals
1114
1215import os
1316import platform
14- import subprocess
17+ import shutil
1518import sys
16- import tempfile
17- import zipfile
1819
1920from pathlib import Path
2021from typing import Any
2122
2223from hatchling .builders .hooks .plugin .interface import BuildHookInterface
2324
2425
25- def get_platform_tag () -> str :
26- """Get the platform tag for the current build target."""
26+ def get_wheel_platform_tag () -> str :
27+ """Get the platform tag for the output wheel.
28+
29+ This is the tag that will be used in the wheel filename.
30+ We use manylinux_2_28 for Linux for broad compatibility.
31+ """
2732 system = platform .system ().lower ()
2833 machine = platform .machine ().lower ()
2934
3035 if system == "darwin" :
31- # Use tags that match what pytauri-wheel publishes on PyPI
3236 if machine == "arm64" :
3337 return "macosx_14_0_arm64"
3438 return "macosx_13_0_x86_64"
3539 if system == "linux" :
3640 if machine == "aarch64" :
37- return "manylinux_2_35_aarch64 "
38- return "manylinux_2_35_x86_64 "
41+ return "manylinux_2_28_aarch64 "
42+ return "manylinux_2_28_x86_64 "
3943 if system == "windows" :
4044 if machine == "arm64" :
4145 return "win_arm64"
@@ -62,72 +66,54 @@ def initialize(self, version: str, build_data: dict[str, Any]) -> None:
6266
6367 # Skip for editable installs
6468 if version == "editable" :
65- self .app .display_info ("Skipping pytauri-wheel bundling for editable install" )
69+ self .app .display_info (
70+ "Skipping pytauri-wheel bundling for editable install"
71+ )
6672 return
6773
74+ python_tag = get_python_tag ()
75+ wheel_platform_tag = get_wheel_platform_tag ()
76+ # Set wheel tags to make this a platform-specific wheel
77+ # This is critical - without this, hatch generates a pure Python wheel
78+ build_data ["tag" ] = f"{ python_tag } -{ python_tag } -{ wheel_platform_tag } "
79+ build_data ["pure_python" ] = False
80+
6881 # Check if bundling is enabled (can be disabled for development)
6982 if os .environ .get ("PYWRY_SKIP_BUNDLE" , "" ).lower () in ("1" , "true" , "yes" ):
70- self .app .display_info ("Skipping pytauri-wheel bundling (PYWRY_SKIP_BUNDLE=1)" )
83+ self .app .display_info (
84+ "Skipping pytauri-wheel bundling (PYWRY_SKIP_BUNDLE=1)"
85+ )
7186 return
7287
73- pytauri_version = os .environ .get ("PYTAURI_WHEEL_VERSION" , "0.8.0" )
74- python_tag = get_python_tag ()
75- platform_tag = get_platform_tag ()
76-
7788 self .app .display_info (
78- f"Bundling pytauri-wheel { pytauri_version } for { python_tag } -{ platform_tag } "
89+ f"Bundling pytauri-wheel for { python_tag } -{ wheel_platform_tag } "
7990 )
8091
8192 # Create vendor directory in the package
8293 vendor_dir = Path (self .root ) / "pywry" / "_vendor" / "pytauri_wheel"
8394 vendor_dir .mkdir (parents = True , exist_ok = True )
8495
85- with tempfile .TemporaryDirectory () as tmpdir :
86- tmppath = Path (tmpdir )
87-
88- try :
89- subprocess .run ( # noqa: S603
90- [
91- sys .executable ,
92- "-m" ,
93- "pip" ,
94- "download" ,
95- "--no-deps" ,
96- "--only-binary=:all:" ,
97- f"--dest={ tmppath } " ,
98- f"--platform={ platform_tag } " ,
99- f"--python-version={ sys .version_info .major } .{ sys .version_info .minor } " ,
100- f"--abi={ python_tag } " ,
101- f"pytauri-wheel=={ pytauri_version } " ,
102- ],
103- check = True ,
104- capture_output = True ,
105- text = True ,
106- )
107- except subprocess .CalledProcessError as e :
108- self .app .display_error (f"Failed to download pytauri-wheel: { e .stderr } " )
109- raise
110-
111- # Find the downloaded wheel
112- wheels = list (tmppath .glob ("pytauri_wheel*.whl" ))
113- if not wheels :
114- raise RuntimeError (f"No pytauri-wheel found in { tmppath } " )
115-
116- wheel_path = wheels [0 ]
117- self .app .display_info (f"Downloaded: { wheel_path .name } " )
118-
119- # Extract the wheel (it's a zip file)
120- with zipfile .ZipFile (wheel_path , "r" ) as whl :
121- # Extract only the pytauri_wheel package contents
122- for member in whl .namelist ():
123- if member .startswith ("pytauri_wheel/" ) and not member .endswith ("/" ):
124- # Get the relative path within pytauri_wheel/
125- rel_path = member [len ("pytauri_wheel/" ) :]
126- if rel_path :
127- dest = vendor_dir / rel_path
128- dest .parent .mkdir (parents = True , exist_ok = True )
129- with whl .open (member ) as src , dest .open ("wb" ) as dst :
130- dst .write (src .read ())
96+ # Find the installed pytauri_wheel package location
97+ import importlib .util
98+
99+ spec = importlib .util .find_spec ("pytauri_wheel" )
100+ if spec is None or spec .origin is None :
101+ raise RuntimeError (
102+ "pytauri_wheel is not installed. Install it with: pip install pytauri-wheel"
103+ )
104+
105+ pytauri_wheel_dir = Path (spec .origin ).parent
106+ self .app .display_info (f"Found pytauri_wheel at: { pytauri_wheel_dir } " )
107+
108+ # Copy the entire pytauri_wheel package to vendor directory
109+ for item in pytauri_wheel_dir .iterdir ():
110+ dest = vendor_dir / item .name
111+ if item .is_dir ():
112+ if dest .exists ():
113+ shutil .rmtree (dest )
114+ shutil .copytree (item , dest )
115+ else :
116+ shutil .copy2 (item , dest )
131117
132118 # Create __init__.py that re-exports from vendor location
133119 init_content = '''"""Vendored pytauri_wheel package."""
@@ -140,4 +126,6 @@ def initialize(self, version: str, build_data: dict[str, Any]) -> None:
140126 # Add vendor directory to wheel
141127 build_data ["force_include" ][str (vendor_dir )] = "pywry/_vendor/pytauri_wheel"
142128
143- self .app .display_success ("Bundled pytauri-wheel into pywry/_vendor/pytauri_wheel" )
129+ self .app .display_success (
130+ "Bundled pytauri-wheel into pywry/_vendor/pytauri_wheel"
131+ )
0 commit comments