11from __future__ import annotations
22
33import os
4+ import platform
45import re
56import shutil
6- import tarfile
7+ import subprocess
78import tempfile
89from pathlib import Path
910from typing import Any
10- from urllib import request
1111
1212from hatchling .builders .hooks .plugin .interface import BuildHookInterface
1313
14+ PY_PLATFORM_MAPPING = {
15+ ("Linux" , "x86_64" ): ("musllinux_1_2" , "x86_64" ),
16+ ("Linux" , "i686" ): ("musllinux_1_2" , "i686" ),
17+ ("Linux" , "armv7l" ): ("musllinux_1_2" , "armv7l" ),
18+ ("Linux" , "aarch64" ): ("musllinux_1_2" , "aarch64" ),
19+ ("Darwin" , "x86_64" ): ("macosx_10_12" , "x86_64" ),
20+ ("Darwin" , "arm64" ): ("macosx_11_0" , "arm64" ),
21+ ("Windows" , "AMD64" ): ("win" , "amd64" ),
22+ ("Windows" , "ARM64" ): ("win" , "arm64" ),
23+ }
24+
1425# key 为 pypi 分发的系统和架构组合
1526BUILD_TARGET = {
16- ("musllinux_1_2" , "x86_64" ): {"download_file" : ("linux" , "x86_64" )},
17- ("musllinux_1_2" , "aarch64" ): {"download_file" : ("linux" , "arm64" )},
18- ("manylinux_2_17" , "x86_64" ): {"download_file" : ("linux" , "x86_64" )},
19- ("manylinux_2_17" , "aarch64" ): {"download_file" : ("linux" , "arm64" )},
20- ("macosx_10_9" , "x86_64" ): {"download_file" : ("darwin" , "x86_64" )},
21- ("macosx_11_0" , "arm64" ): {"download_file" : ("darwin" , "arm64" )},
22- ("win" , "amd64" ): {"download_file" : ("windows" , "x86_64" )},
23- ("win" , "arm64" ): {"download_file" : ("windows" , "arm64" )},
27+ ("musllinux_1_2" , "x86_64" ): ("linux" , "amd64" ),
28+ ("musllinux_1_2" , "i686" ): ("linux" , "386" ),
29+ ("musllinux_1_2" , "armv7l" ): ("linux" , "arm" ),
30+ ("musllinux_1_2" , "aarch64" ): ("linux" , "arm64" ),
31+ ("manylinux_2_17" , "x86_64" ): ("linux" , "amd64" ),
32+ ("manylinux_2_17" , "aarch64" ): ("linux" , "arm64" ),
33+ ("macosx_10_12" , "x86_64" ): ("darwin" , "amd64" ),
34+ ("macosx_11_0" , "arm64" ): ("darwin" , "arm64" ),
35+ ("win" , "amd64" ): ("windows" , "amd64" ),
36+ ("win" , "arm64" ): ("windows" , "arm64" ),
2437}
2538
2639
2740class SpecialBuildHook (BuildHookInterface ):
2841 BIN_NAME = "yamlfmt"
29- YAMLFMT_REPO = "https://github.com/google/yamlfmt/releases/download/v{version}/yamlfmt_{version}_{target_os_info}_{target_arch}.tar.gz "
42+ YAMLFMT_REPO = "https://github.com/google/yamlfmt.git "
3043
3144 def __init__ (self , * args , ** kwargs ):
3245 super ().__init__ (* args , ** kwargs )
@@ -37,56 +50,101 @@ def initialize(self, version: str, build_data: dict[str, Any]) -> None:
3750 if self .target_name != "wheel" :
3851 return
3952
40- target_arch = os .environ .get ("CIBW_ARCHS" , None )
41- target_os_info = os .environ .get ("CIBW_PLATFORM" , None )
53+ # 获取系统信息
54+ system_info = get_system_info ()
55+ default_os_mapping = (None , None )
4256
43- assert target_arch is not None , f"CIBW_ARCHS not set see: { BUILD_TARGET } "
44- assert target_os_info is not None , f"CIBW_PLATFORM not set see: { BUILD_TARGET } "
57+ # 获取目标架构和平台信息
58+ target_arch = os .environ .get ("CIBW_ARCHS" ) or PY_PLATFORM_MAPPING .get (system_info , default_os_mapping )[1 ]
59+ target_os_info = os .environ .get ("CIBW_PLATFORM" ) or PY_PLATFORM_MAPPING .get (system_info , default_os_mapping )[0 ]
4560
46- if (target_os_info , target_arch ) not in BUILD_TARGET :
47- raise ValueError (f"Unsupported target: { target_os_info } , { target_arch } " )
61+ # 确保目标架构和平台信息有效
62+ assert target_arch is not None , (
63+ f"CIBW_ARCHS not set and no mapping found in PY_PLATFORM_MAPPING for: { system_info } "
64+ )
65+ assert target_os_info is not None , (
66+ f"CIBW_PLATFORM not set and no mapping found in PY_PLATFORM_MAPPING for: { system_info } "
67+ )
68+
69+ assert (target_os_info , target_arch ) in BUILD_TARGET , f"Unsupported target: { target_os_info } , { target_arch } "
4870
4971 # 构建完整的 Wheel 标签
5072 full_wheel_tag = f"py3-none-{ target_os_info } _{ target_arch } "
5173 build_data ["tag" ] = full_wheel_tag
5274
53- # 下载 yamlfmt 可执行文件
54- tar_gz_file = self .download_yamlfmt (target_os_info , target_arch )
55-
56- # 解压缩文件
57- with tarfile .open (tar_gz_file , "r:gz" ) as tar :
58- if target_os_info == "win" :
59- # Windows 上的文件名是 yamlfmt.exe
60- assert f"{ self .BIN_NAME } .exe" in tar .getnames ()
61- tar .extract (f"{ self .BIN_NAME } .exe" , path = self .temp_dir )
62- # 重命名为 yamlfmt
63- (self .temp_dir / f"{ self .BIN_NAME } .exe" ).rename (self .temp_dir / self .BIN_NAME )
64- else :
65- assert self .BIN_NAME in tar .getnames ()
66- tar .extract (self .BIN_NAME , path = self .temp_dir )
67-
68- # TODO: 加一个 sum 校验
75+ # 构建 yamlfmt 二进制文件
76+ self .build_yamlfmt (target_os_info , target_arch )
77+
78+ # 将构建好的二进制文件添加到 wheel 中
6979 bin_path = self .temp_dir / self .BIN_NAME
70- assert bin_path .is_file (), f"{ self .BIN_NAME } not found"
71- build_data ["force_include" ][f"{ bin_path .resolve ()} " ] = f"yamlfmt/{ self .BIN_NAME } "
72-
73- def download_yamlfmt (self , target_os_info : str , target_arch : str ) -> None :
74- """Download the yamlfmt binary for the specified OS and architecture."""
75- download_target = BUILD_TARGET [(target_os_info , target_arch )]["download_file" ]
76- file_path = self .temp_dir / f"{ self .BIN_NAME } _{ download_target [0 ]} _{ download_target [1 ]} .tar.gz"
77- request .urlretrieve (
78- self .YAMLFMT_REPO .format (
79- version = re .sub (
80- r"(?:a|b|rc)\d+|\.post\d+|\.dev\d+$" , "" , self .metadata .version
81- ), # 去掉版本号中的后缀, alpha/beta/rc/post/dev
82- target_os_info = download_target [0 ],
83- target_arch = download_target [1 ],
84- ),
85- file_path ,
80+
81+ assert bin_path .is_file (), f"{ self .BIN_NAME } not found after build"
82+ build_data ["force_include" ][str (bin_path .resolve ())] = f"yamlfmt/{ self .BIN_NAME } "
83+
84+ def build_yamlfmt (self , target_os_info : str , target_arch : str ) -> None :
85+ """Build the yamlfmt binary for the specified OS and architecture."""
86+ # 确认环境安装
87+ for command in ["go" , "make" , "git" ]:
88+ assert shutil .which (command ), f"{ command } is not installed or not found in PATH"
89+
90+ build_target = BUILD_TARGET [(target_os_info , target_arch )]
91+
92+ # 编译逻辑可以在这里添加
93+ version = re .sub (
94+ r"(?:a|b|rc)\d+|\.post\d+|\.dev\d+$" , "" , self .metadata .version
95+ ) # 去掉版本号中的后缀, alpha/beta/rc/post/dev
96+
97+ # clone repo
98+ subprocess .run (
99+ [
100+ "git" ,
101+ "clone" ,
102+ "--depth" ,
103+ "1" ,
104+ "--branch" ,
105+ f"v{ version } " ,
106+ self .YAMLFMT_REPO ,
107+ str (self .temp_dir / f"yamlfmt-{ version } " ),
108+ ],
109+ check = True ,
86110 )
87- return file_path
111+
112+ # 编译
113+ env = os .environ .copy ()
114+ env .update ({"GOOS" : build_target [0 ], "GOARCH" : build_target [1 ]})
115+ if target_arch == "armv7l" :
116+ env .update ({"GOARM" : "7" })
117+
118+ # 检查工作目录是否存在
119+ work_dir = self .temp_dir / f"yamlfmt-{ version } "
120+ assert work_dir .exists (), f"Working directory { work_dir } does not exist"
121+
122+ subprocess .run (
123+ ["make" , "build" ],
124+ env = env ,
125+ cwd = work_dir ,
126+ capture_output = True ,
127+ text = True ,
128+ check = True ,
129+ )
130+
131+ # 检查生成的二进制文件是否存在
132+ bin_path = work_dir / "dist" / self .BIN_NAME
133+ assert bin_path .exists (), f"Binary file { bin_path } was not created"
134+
135+ # 将二进制文件复制到临时目录的根目录,供后续使用
136+ shutil .copy2 (bin_path , self .temp_dir / self .BIN_NAME )
88137
89138 def finalize (self , version , build_data , artifact_path ):
90139 # 清理临时目录
91- shutil .rmtree (self .temp_dir )
140+ try :
141+ shutil .rmtree (self .temp_dir )
142+ except (OSError , PermissionError ) as e :
143+ print (f"Warning: Failed to remove temp directory { self .temp_dir } : { e } " )
92144 super ().finalize (version , build_data , artifact_path )
145+
146+
147+ def get_system_info ():
148+ system = platform .system ()
149+ machine = platform .machine ()
150+ return system , machine
0 commit comments