Skip to content

Commit 027ac5a

Browse files
Fetch binary before building distribution
1 parent e2213cf commit 027ac5a

File tree

4 files changed

+249
-55
lines changed

4 files changed

+249
-55
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,3 +134,6 @@ prober/snowflake_prober.egg-info/
134134
ci/wif/parameters/rsa_wif_aws_azure
135135
ci/wif/parameters/rsa_wif_gcp
136136
ci/wif/parameters/parameters_wif.json
137+
138+
# Mini core
139+
src/snowflake/connector/minicore/*.h

ci/download_minicore.py

Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Download minicore binary for the current platform.
4+
Designed to be used by cibuildwheel during wheel building.
5+
6+
Usage:
7+
python scripts/download_minicore.py [VERSION]
8+
9+
Environment variables:
10+
MINICORE_VERSION - Version to download (default: 0.0.1)
11+
MINICORE_OUTPUT_DIR - Output directory (default: src/snowflake/connector/minicore)
12+
"""
13+
14+
from __future__ import annotations
15+
16+
import os
17+
import platform
18+
import sys
19+
import tarfile
20+
import tempfile
21+
from pathlib import Path
22+
from urllib.error import HTTPError, URLError
23+
from urllib.request import Request, urlopen
24+
25+
# Configuration
26+
BASE_URL = "https://sfc-repo.snowflakecomputing.com/minicore"
27+
DEFAULT_VERSION = "0.0.1"
28+
29+
# Target directory for minicore module (relative to repo root)
30+
MINICORE_MODULE_PATH = Path("src/snowflake/connector/minicore")
31+
32+
33+
def get_repo_root() -> Path:
34+
"""Get the repository root directory."""
35+
current = Path(__file__).resolve().parent
36+
while current != current.parent:
37+
if (current / "pyproject.toml").exists() or (current / "setup.py").exists():
38+
return current
39+
current = current.parent
40+
return Path(__file__).resolve().parent.parent
41+
42+
43+
def detect_os() -> str:
44+
"""Detect the operating system."""
45+
system = platform.system().lower()
46+
if system == "linux":
47+
return "linux"
48+
elif system == "darwin":
49+
return "macos"
50+
elif system == "windows":
51+
return "windows"
52+
elif system == "aix":
53+
return "aix"
54+
else:
55+
return "unknown"
56+
57+
58+
def detect_arch() -> str:
59+
"""Detect the CPU architecture."""
60+
machine = platform.machine().lower()
61+
if machine in ("x86_64", "amd64"):
62+
return "x86_64"
63+
elif machine in ("aarch64", "arm64"):
64+
return "aarch64"
65+
elif machine in ("i686", "i386", "x86"):
66+
return "i686"
67+
elif machine == "ppc64":
68+
return "ppc64"
69+
else:
70+
return "unknown"
71+
72+
73+
def detect_libc() -> str:
74+
"""Detect libc type on Linux (glibc vs musl)."""
75+
if detect_os() != "linux":
76+
return ""
77+
78+
# Check if we're on Alpine/musl
79+
if Path("/etc/alpine-release").exists():
80+
return "musl"
81+
82+
# Check for musl by looking at the libc library
83+
try:
84+
import subprocess
85+
86+
result = subprocess.run(
87+
["ldd", "--version"],
88+
capture_output=True,
89+
text=True,
90+
)
91+
if "musl" in result.stdout.lower() or "musl" in result.stderr.lower():
92+
return "musl"
93+
except Exception:
94+
pass
95+
96+
# Default to glibc
97+
return "glibc"
98+
99+
100+
def get_platform_dir(os_name: str, arch: str) -> str:
101+
"""Build platform directory name for URL."""
102+
if os_name == "linux":
103+
return f"linux_{arch}"
104+
elif os_name == "macos":
105+
return f"mac_{arch}"
106+
elif os_name == "windows":
107+
return f"windows_{arch}"
108+
elif os_name == "aix":
109+
return f"aix_{arch}"
110+
else:
111+
return ""
112+
113+
114+
def get_filename_arch(os_name: str, arch: str, libc: str) -> str:
115+
"""Build filename architecture component."""
116+
if os_name == "linux":
117+
return f"linux-{arch}-{libc}"
118+
elif os_name == "macos":
119+
return f"macos-{arch}"
120+
elif os_name == "windows":
121+
return f"windows-{arch}"
122+
elif os_name == "aix":
123+
return f"aix-{arch}"
124+
else:
125+
return ""
126+
127+
128+
def build_download_url(platform_dir: str, filename_arch: str, version: str) -> str:
129+
"""Build the download URL."""
130+
filename = f"sf_mini_core_{filename_arch}_{version}.tar.gz"
131+
return f"{BASE_URL}/{platform_dir}/{version}/{filename}"
132+
133+
134+
def download_file(url: str, dest_path: Path) -> None:
135+
"""Download a file from URL to destination path."""
136+
print(f"Downloading: {url}")
137+
request = Request(url, headers={"User-Agent": "Python/minicore-downloader"})
138+
try:
139+
with urlopen(request, timeout=60) as response:
140+
content = response.read()
141+
dest_path.write_bytes(content)
142+
file_size_mb = len(content) / (1024 * 1024)
143+
print(f"Downloaded {file_size_mb:.2f} MB")
144+
except HTTPError as e:
145+
print(f"HTTP Error {e.code}: {e.reason}", file=sys.stderr)
146+
raise
147+
except URLError as e:
148+
print(f"URL Error: {e.reason}", file=sys.stderr)
149+
raise
150+
151+
152+
def extract_tar_gz(tar_path: Path, extract_to: Path) -> None:
153+
"""Extract a tar.gz file to the specified directory."""
154+
print(f"Extracting to: {extract_to}")
155+
extract_to.mkdir(parents=True, exist_ok=True)
156+
157+
with tarfile.open(tar_path, "r:gz") as tar:
158+
# Security check: prevent path traversal attacks
159+
for member in tar.getmembers():
160+
member_path = extract_to / member.name
161+
try:
162+
member_path.resolve().relative_to(extract_to.resolve())
163+
except ValueError:
164+
print(
165+
f"Skipping potentially unsafe path: {member.name}", file=sys.stderr
166+
)
167+
continue
168+
169+
tar.extractall(path=extract_to, filter="data")
170+
171+
172+
def main() -> int:
173+
# Get version from environment or command line
174+
version = os.environ.get("MINICORE_VERSION")
175+
if not version and len(sys.argv) > 1:
176+
version = sys.argv[1]
177+
if not version:
178+
version = DEFAULT_VERSION
179+
180+
# Get output directory
181+
output_dir_env = os.environ.get("MINICORE_OUTPUT_DIR")
182+
if output_dir_env:
183+
output_dir = Path(output_dir_env)
184+
else:
185+
repo_root = get_repo_root()
186+
output_dir = repo_root / MINICORE_MODULE_PATH
187+
188+
# Detect platform
189+
os_name = detect_os()
190+
arch = detect_arch()
191+
libc = detect_libc()
192+
193+
print(f"Detected OS: {os_name}")
194+
print(f"Detected architecture: {arch}")
195+
if libc:
196+
print(f"Detected libc: {libc}")
197+
198+
if os_name == "unknown" or arch == "unknown":
199+
print(
200+
f"Error: Unsupported platform: OS={os_name}, ARCH={arch}", file=sys.stderr
201+
)
202+
return 1
203+
204+
# Build URL components
205+
platform_dir = get_platform_dir(os_name, arch)
206+
filename_arch = get_filename_arch(os_name, arch, libc)
207+
208+
if not platform_dir or not filename_arch:
209+
print(
210+
"Error: Could not determine platform/architecture mapping", file=sys.stderr
211+
)
212+
return 1
213+
214+
url = build_download_url(platform_dir, filename_arch, version)
215+
216+
print(f"Version: {version}")
217+
print(f"Download URL: {url}")
218+
print(f"Output directory: {output_dir}")
219+
220+
# Download to temp file and extract
221+
with tempfile.TemporaryDirectory() as temp_dir:
222+
temp_path = Path(temp_dir) / f"sf_mini_core_{filename_arch}_{version}.tar.gz"
223+
224+
try:
225+
download_file(url, temp_path)
226+
extract_tar_gz(temp_path, output_dir)
227+
except Exception as e:
228+
print(f"Error: {e}", file=sys.stderr)
229+
return 1
230+
231+
print("Done!")
232+
233+
# List extracted files
234+
for item in sorted(output_dir.iterdir()):
235+
if not item.name.startswith("__"):
236+
print(f" {item.name}")
237+
238+
return 0
239+
240+
241+
if __name__ == "__main__":
242+
sys.exit(main())

pyproject.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,12 @@ requires = [
1010
[tool.cibuildwheel]
1111
test-skip = "*"
1212
manylinux-x86_64-image = "manylinux2014"
13-
environment = {AUDITWHEEL_PLAT="manylinux2014_$(uname -m)"}
13+
environment = {AUDITWHEEL_PLAT="manylinux2014_$(uname -m)", SNOWFLAKE_DOWNLOAD_MINICORE="true"}
1414
build-verbosity = 1
1515

16+
# Common for all platforms
17+
before-build = "python ci/download_minicore.py"
18+
1619
[tool.cibuildwheel.linux]
1720
archs = ["x86_64", "aarch64"]
1821

src/snowflake/connector/minicore/sf_mini_core.h

Lines changed: 0 additions & 54 deletions
This file was deleted.

0 commit comments

Comments
 (0)