1+ from __future__ import annotations
2+
3+ import shutil
4+ import urllib .request
5+ import xml .etree .ElementTree as ET
6+ import zipfile
7+ from pathlib import Path
8+ from typing import Any
9+ import subprocess
10+ import platform
11+
12+ from hatchling .builders .hooks .plugin .interface import BuildHookInterface
13+
14+
15+ def download_nuget_package (
16+ package_id : str ,
17+ version : str ,
18+ output_dir : str | Path
19+ ) -> None :
20+ """
21+ Download a NuGet package (.nupkg) to output_dir.
22+ """
23+ package_id_lower : str = package_id .lower ()
24+ version_lower : str = version .lower ()
25+ url : str = f"https://api.nuget.org/v3-flatcontainer/{ package_id_lower } /{ version_lower } /{ package_id_lower } .{ version_lower } .nupkg"
26+ output_dir = Path (output_dir )
27+ nupkg_path : Path = output_dir / f"{ package_id } .{ version } .nupkg"
28+ extract_path : Path = output_dir / f"{ package_id } "
29+
30+ output_dir .mkdir (parents = True , exist_ok = True )
31+
32+ print (f"Downloading { package_id } { version } from { url } ..." )
33+ with urllib .request .urlopen (url ) as response , nupkg_path .open ("wb" ) as f :
34+ f .write (response .read ())
35+
36+ print (f"Extracting { nupkg_path } to { extract_path } ..." )
37+ with zipfile .ZipFile (nupkg_path , "r" ) as zip_ref :
38+ zip_ref .extractall (extract_path )
39+ print ("Done." )
40+
41+ def copy_native_libs_to_bin (packages_dir : str | Path , bin_dir : str | Path ) -> None :
42+ """
43+ Copy native shared libraries from NuGet packages to bin directory.
44+ """
45+ packages_dir = Path (packages_dir )
46+ bin_windows = Path (bin_dir ) / "windows"
47+ bin_linux = Path (bin_dir ) / "linux"
48+ bin_windows .mkdir (parents = True , exist_ok = True )
49+ bin_linux .mkdir (parents = True , exist_ok = True )
50+
51+ for package in packages_dir .iterdir ():
52+ win_native = package / "runtimes" / "win-x64" / "native"
53+ linux_native = package / "runtimes" / "linux-x64" / "native"
54+
55+ if win_native .exists ():
56+ for lib in win_native .glob ("*" ):
57+ dest = bin_windows / lib .name
58+ print (f"Copying { lib } to { dest } " )
59+ shutil .copy2 (lib , dest )
60+
61+ if linux_native .exists ():
62+ for lib in linux_native .glob ("*" ):
63+ dest = bin_linux / lib .name
64+ print (f"Copying { lib } to { dest } " )
65+ shutil .copy2 (lib , dest )
66+
67+ def read_packages_config (filepath : str | Path ) -> list [tuple [str , str ]]:
68+ """
69+ Reads NuGet packages.config and returns a list of (id, version) tuples.
70+ """
71+ tree = ET .parse (filepath )
72+ root = tree .getroot ()
73+ return [
74+ (pkg .attrib ["id" ], pkg .attrib ["version" ])
75+ for pkg in root .findall ("package" )
76+ ]
77+
78+ def modify_linux_so_rpath (bin_folder : str | Path ):
79+ patchelf_path = shutil .which ("patchelf" )
80+ for so in Path (bin_folder ).glob ("*.so*" ):
81+ print (f"Setting RUNPATH for { so } to '$ORIGIN'" )
82+ subprocess .run (
83+ [patchelf_path , "--set-rpath" , "$ORIGIN" , str (so .absolute ())],
84+ check = True ,
85+ )
86+
87+ def setup ():
88+ """Setup function to download NuGet packages and copy native libraries into bin folder.
89+ """
90+ packages = read_packages_config ("buildUtil/packages.config" )
91+ for name , version in packages :
92+ download_nuget_package (name , version , output_dir = "packages" )
93+ copy_native_libs_to_bin ("packages" , "mikecore/bin" )
94+
95+ if platform .system ().lower () == "linux" :
96+ modify_linux_so_rpath ("mikecore/bin/linux" )
97+
98+
99+ class BuildHook (BuildHookInterface ):
100+ """Custom build hook to run setup during the build process."""
101+
102+ def initialize (self , version : str , build_data : dict [str : Any ]) -> None :
103+ """Initialize the build hook."""
104+ setup ()
105+
106+ if __name__ == "__main__" :
107+ setup ()
0 commit comments