clang-tool-chain 1.0.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of clang-tool-chain might be problematic. Click here for more details.
- clang_tool_chain/__init__.py +0 -0
- clang_tool_chain/__version__.py +4 -0
- clang_tool_chain/checksums.py +270 -0
- clang_tool_chain/cli.py +575 -0
- clang_tool_chain/downloader.py +1325 -0
- clang_tool_chain/downloads/README.md +144 -0
- clang_tool_chain/downloads/__init__.py +22 -0
- clang_tool_chain/downloads/__main__.py +11 -0
- clang_tool_chain/downloads/create_hardlink_archive.py +390 -0
- clang_tool_chain/downloads/create_iwyu_archives.py +330 -0
- clang_tool_chain/downloads/deduplicate_binaries.py +217 -0
- clang_tool_chain/downloads/download_binaries.py +463 -0
- clang_tool_chain/downloads/expand_archive.py +260 -0
- clang_tool_chain/downloads/extract_mingw_sysroot.py +349 -0
- clang_tool_chain/downloads/fetch_and_archive.py +1376 -0
- clang_tool_chain/downloads/strip_binaries.py +436 -0
- clang_tool_chain/downloads/test_compression.py +259 -0
- clang_tool_chain/fetch.py +158 -0
- clang_tool_chain/paths.py +93 -0
- clang_tool_chain/sccache_runner.py +160 -0
- clang_tool_chain/wrapper.py +1383 -0
- clang_tool_chain-1.0.2.dist-info/METADATA +1766 -0
- clang_tool_chain-1.0.2.dist-info/RECORD +26 -0
- clang_tool_chain-1.0.2.dist-info/WHEEL +4 -0
- clang_tool_chain-1.0.2.dist-info/entry_points.txt +31 -0
- clang_tool_chain-1.0.2.dist-info/licenses/LICENSE +204 -0
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Expand tar.zst archive created with hard links.
|
|
4
|
+
|
|
5
|
+
This script:
|
|
6
|
+
1. Decompresses zstd archive
|
|
7
|
+
2. Extracts tar (tar automatically restores hard links as regular files)
|
|
8
|
+
3. Copies/moves binaries to target location
|
|
9
|
+
|
|
10
|
+
The tar format preserves hard links, but when extracted, they become
|
|
11
|
+
regular files (duplicates) which is what we want for distribution.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import shutil
|
|
15
|
+
import sys
|
|
16
|
+
import tarfile
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import Any
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def expand_zst_archive(archive_path: Path | str, output_dir: Path | str, keep_hardlinks: bool = False) -> Path:
|
|
22
|
+
"""
|
|
23
|
+
Expand a tar.zst archive.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
archive_path: Path to .tar.zst file
|
|
27
|
+
output_dir: Directory to extract to
|
|
28
|
+
keep_hardlinks: If True, preserve hard links; if False, copy to separate files
|
|
29
|
+
"""
|
|
30
|
+
try:
|
|
31
|
+
import zstandard as zstd
|
|
32
|
+
except ImportError:
|
|
33
|
+
print("Error: zstandard module not installed")
|
|
34
|
+
print("Install with: pip install zstandard")
|
|
35
|
+
sys.exit(1)
|
|
36
|
+
|
|
37
|
+
archive_path = Path(archive_path)
|
|
38
|
+
output_dir = Path(output_dir)
|
|
39
|
+
|
|
40
|
+
if not archive_path.exists():
|
|
41
|
+
print(f"Error: Archive not found: {archive_path}")
|
|
42
|
+
sys.exit(1)
|
|
43
|
+
|
|
44
|
+
print("=" * 70)
|
|
45
|
+
print("EXPANDING ARCHIVE")
|
|
46
|
+
print("=" * 70)
|
|
47
|
+
print(f"Archive: {archive_path}")
|
|
48
|
+
print(f"Output: {output_dir}")
|
|
49
|
+
print(f"Size: {archive_path.stat().st_size / (1024*1024):.2f} MB")
|
|
50
|
+
print()
|
|
51
|
+
|
|
52
|
+
# Step 1: Decompress zstd
|
|
53
|
+
print("Step 1: Decompressing zstd...")
|
|
54
|
+
import time
|
|
55
|
+
|
|
56
|
+
start = time.time()
|
|
57
|
+
|
|
58
|
+
with open(archive_path, "rb") as f:
|
|
59
|
+
compressed_data = f.read()
|
|
60
|
+
|
|
61
|
+
dctx = zstd.ZstdDecompressor()
|
|
62
|
+
tar_data = dctx.decompress(compressed_data)
|
|
63
|
+
|
|
64
|
+
elapsed = time.time() - start
|
|
65
|
+
print(
|
|
66
|
+
f" Decompressed {len(compressed_data) / (1024*1024):.2f} MB -> {len(tar_data) / (1024*1024):.2f} MB in {elapsed:.2f}s"
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
# Step 2: Extract tar
|
|
70
|
+
print("\nStep 2: Extracting tar archive...")
|
|
71
|
+
import io
|
|
72
|
+
|
|
73
|
+
tar_buffer = io.BytesIO(tar_data)
|
|
74
|
+
|
|
75
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
76
|
+
|
|
77
|
+
with tarfile.open(fileobj=tar_buffer, mode="r") as tar:
|
|
78
|
+
# Get list of members
|
|
79
|
+
members = tar.getmembers()
|
|
80
|
+
print(f" Archive contains {len(members)} items")
|
|
81
|
+
|
|
82
|
+
# Extract
|
|
83
|
+
tar.extractall(path=output_dir)
|
|
84
|
+
|
|
85
|
+
print(f" Extracted to: {output_dir}")
|
|
86
|
+
|
|
87
|
+
# Step 3: Check for hard links
|
|
88
|
+
print("\nStep 3: Analyzing extracted files...")
|
|
89
|
+
|
|
90
|
+
extracted_root = output_dir / "win_hardlinked"
|
|
91
|
+
if not extracted_root.exists():
|
|
92
|
+
# Find the actual extraction root
|
|
93
|
+
subdirs = list(output_dir.iterdir())
|
|
94
|
+
if len(subdirs) == 1 and subdirs[0].is_dir():
|
|
95
|
+
extracted_root = subdirs[0]
|
|
96
|
+
|
|
97
|
+
bin_dir = extracted_root / "bin"
|
|
98
|
+
if bin_dir.exists():
|
|
99
|
+
exe_files = list(bin_dir.glob("*.exe"))
|
|
100
|
+
print(f" Found {len(exe_files)} .exe files")
|
|
101
|
+
|
|
102
|
+
# Check if hard links were preserved
|
|
103
|
+
inode_to_files = {}
|
|
104
|
+
for exe_file in exe_files:
|
|
105
|
+
stat = exe_file.stat()
|
|
106
|
+
inode = stat.st_ino
|
|
107
|
+
nlink = stat.st_nlink
|
|
108
|
+
|
|
109
|
+
if inode not in inode_to_files:
|
|
110
|
+
inode_to_files[inode] = []
|
|
111
|
+
inode_to_files[inode].append((exe_file.name, nlink))
|
|
112
|
+
|
|
113
|
+
hardlink_count = sum(1 for files in inode_to_files.values() if len(files) > 1)
|
|
114
|
+
|
|
115
|
+
if hardlink_count > 0:
|
|
116
|
+
print(f" ✓ Hard links preserved: {hardlink_count} groups")
|
|
117
|
+
|
|
118
|
+
if not keep_hardlinks:
|
|
119
|
+
print("\n Converting hard links to independent files...")
|
|
120
|
+
convert_hardlinks_to_files(bin_dir, inode_to_files)
|
|
121
|
+
else:
|
|
122
|
+
print(" ✓ Files are independent copies")
|
|
123
|
+
|
|
124
|
+
print("\n" + "=" * 70)
|
|
125
|
+
print("EXTRACTION COMPLETE")
|
|
126
|
+
print("=" * 70)
|
|
127
|
+
print(f"Location: {extracted_root}")
|
|
128
|
+
|
|
129
|
+
return extracted_root
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def convert_hardlinks_to_files(bin_dir: Path, inode_to_files: dict[int, list[Any]]) -> None:
|
|
133
|
+
"""Convert hard links to independent file copies."""
|
|
134
|
+
import tempfile
|
|
135
|
+
|
|
136
|
+
for _inode, files in inode_to_files.items():
|
|
137
|
+
if len(files) <= 1:
|
|
138
|
+
continue # Not a hard link group
|
|
139
|
+
|
|
140
|
+
print(f"\n Processing hard link group ({len(files)} files):")
|
|
141
|
+
|
|
142
|
+
# Keep the first file as-is, copy the rest
|
|
143
|
+
first_file = bin_dir / files[0][0]
|
|
144
|
+
|
|
145
|
+
for filename, _nlink in files[1:]:
|
|
146
|
+
target_file = bin_dir / filename
|
|
147
|
+
|
|
148
|
+
# Copy to temp, delete original, move temp to original
|
|
149
|
+
# This breaks the hard link
|
|
150
|
+
with tempfile.NamedTemporaryFile(delete=False, dir=bin_dir) as tmp:
|
|
151
|
+
tmp_path = Path(tmp.name)
|
|
152
|
+
|
|
153
|
+
shutil.copy2(first_file, tmp_path)
|
|
154
|
+
target_file.unlink()
|
|
155
|
+
tmp_path.rename(target_file)
|
|
156
|
+
|
|
157
|
+
print(f" - {filename} (converted to independent file)")
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def verify_extraction(extracted_dir: Path | str, original_dir: Path | str | None = None) -> bool:
|
|
161
|
+
"""Verify extracted files."""
|
|
162
|
+
import hashlib
|
|
163
|
+
|
|
164
|
+
extracted_dir = Path(extracted_dir)
|
|
165
|
+
bin_dir = extracted_dir / "bin"
|
|
166
|
+
|
|
167
|
+
if not bin_dir.exists():
|
|
168
|
+
print(f"Warning: bin directory not found in {extracted_dir}")
|
|
169
|
+
return False
|
|
170
|
+
|
|
171
|
+
print("\n" + "=" * 70)
|
|
172
|
+
print("VERIFICATION")
|
|
173
|
+
print("=" * 70)
|
|
174
|
+
|
|
175
|
+
exe_files = sorted(bin_dir.glob("*.exe"))
|
|
176
|
+
print(f"Extracted {len(exe_files)} .exe files:")
|
|
177
|
+
|
|
178
|
+
hashes = {}
|
|
179
|
+
for exe_file in exe_files:
|
|
180
|
+
md5 = hashlib.md5()
|
|
181
|
+
with open(exe_file, "rb") as f:
|
|
182
|
+
for chunk in iter(lambda: f.read(8192), b""):
|
|
183
|
+
md5.update(chunk)
|
|
184
|
+
|
|
185
|
+
file_hash = md5.hexdigest()
|
|
186
|
+
hashes[exe_file.name] = file_hash
|
|
187
|
+
size_mb = exe_file.stat().st_size / (1024 * 1024)
|
|
188
|
+
print(f" {exe_file.name:<25} {size_mb:6.1f} MB {file_hash[:16]}...")
|
|
189
|
+
|
|
190
|
+
# Compare with original if provided
|
|
191
|
+
if original_dir:
|
|
192
|
+
print("\n" + "=" * 70)
|
|
193
|
+
print("COMPARING WITH ORIGINAL")
|
|
194
|
+
print("=" * 70)
|
|
195
|
+
|
|
196
|
+
original_dir = Path(original_dir)
|
|
197
|
+
original_bin = original_dir / "bin"
|
|
198
|
+
|
|
199
|
+
if not original_bin.exists():
|
|
200
|
+
print(f"Warning: Original bin directory not found: {original_bin}")
|
|
201
|
+
return False
|
|
202
|
+
|
|
203
|
+
all_match = True
|
|
204
|
+
for filename, extracted_hash in sorted(hashes.items()):
|
|
205
|
+
original_file = original_bin / filename
|
|
206
|
+
|
|
207
|
+
if not original_file.exists():
|
|
208
|
+
print(f" ✗ {filename}: NOT FOUND in original")
|
|
209
|
+
all_match = False
|
|
210
|
+
continue
|
|
211
|
+
|
|
212
|
+
# Calculate original hash
|
|
213
|
+
md5 = hashlib.md5()
|
|
214
|
+
with open(original_file, "rb") as f:
|
|
215
|
+
for chunk in iter(lambda: f.read(8192), b""):
|
|
216
|
+
md5.update(chunk)
|
|
217
|
+
original_hash = md5.hexdigest()
|
|
218
|
+
|
|
219
|
+
if original_hash == extracted_hash:
|
|
220
|
+
print(f" ✓ {filename}")
|
|
221
|
+
else:
|
|
222
|
+
print(f" ✗ {filename}: HASH MISMATCH")
|
|
223
|
+
print(f" Original: {original_hash}")
|
|
224
|
+
print(f" Extracted: {extracted_hash}")
|
|
225
|
+
all_match = False
|
|
226
|
+
|
|
227
|
+
print()
|
|
228
|
+
if all_match:
|
|
229
|
+
print("✅ ALL FILES MATCH ORIGINAL!")
|
|
230
|
+
else:
|
|
231
|
+
print("❌ SOME FILES DO NOT MATCH")
|
|
232
|
+
|
|
233
|
+
return all_match
|
|
234
|
+
|
|
235
|
+
return True
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def main() -> None:
|
|
239
|
+
import argparse
|
|
240
|
+
|
|
241
|
+
parser = argparse.ArgumentParser(description="Expand tar.zst archive")
|
|
242
|
+
parser.add_argument("archive", help="Path to .tar.zst archive")
|
|
243
|
+
parser.add_argument("output_dir", help="Output directory")
|
|
244
|
+
parser.add_argument("--verify", help="Original directory to verify against")
|
|
245
|
+
parser.add_argument(
|
|
246
|
+
"--keep-hardlinks", action="store_true", help="Keep hard links instead of converting to independent files"
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
args = parser.parse_args()
|
|
250
|
+
|
|
251
|
+
extracted_root = expand_zst_archive(args.archive, args.output_dir, args.keep_hardlinks)
|
|
252
|
+
|
|
253
|
+
if args.verify:
|
|
254
|
+
verify_extraction(extracted_root, args.verify)
|
|
255
|
+
else:
|
|
256
|
+
verify_extraction(extracted_root)
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
if __name__ == "__main__":
|
|
260
|
+
main()
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Extract MinGW sysroot from LLVM-MinGW release.
|
|
4
|
+
|
|
5
|
+
This script downloads LLVM-MinGW and extracts only the sysroot directory
|
|
6
|
+
(x86_64-w64-mingw32/) which contains headers and libraries for GNU ABI support.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import argparse
|
|
10
|
+
import hashlib
|
|
11
|
+
import json
|
|
12
|
+
import shutil
|
|
13
|
+
import sys
|
|
14
|
+
import tarfile
|
|
15
|
+
import urllib.request
|
|
16
|
+
import zipfile
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
# LLVM-MinGW version and download URLs
|
|
20
|
+
LLVM_MINGW_VERSION = "20251104" # Release date format (latest as of Nov 2024)
|
|
21
|
+
LLVM_VERSION = "21.1.5"
|
|
22
|
+
|
|
23
|
+
LLVM_MINGW_URLS = {
|
|
24
|
+
"x86_64": f"https://github.com/mstorsjo/llvm-mingw/releases/download/"
|
|
25
|
+
f"{LLVM_MINGW_VERSION}/llvm-mingw-{LLVM_MINGW_VERSION}-ucrt-x86_64.zip",
|
|
26
|
+
"arm64": f"https://github.com/mstorsjo/llvm-mingw/releases/download/"
|
|
27
|
+
f"{LLVM_MINGW_VERSION}/llvm-mingw-{LLVM_MINGW_VERSION}-ucrt-aarch64.zip",
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
# Expected SHA256 checksums (to be updated after first download)
|
|
31
|
+
CHECKSUMS = {
|
|
32
|
+
"x86_64": "TBD", # Update after first download
|
|
33
|
+
"arm64": "TBD", # Update after first download
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def download_llvm_mingw(arch: str, output_dir: Path) -> Path:
|
|
38
|
+
"""Download LLVM-MinGW release."""
|
|
39
|
+
url = LLVM_MINGW_URLS.get(arch)
|
|
40
|
+
if not url:
|
|
41
|
+
raise ValueError(f"Unsupported architecture: {arch}")
|
|
42
|
+
|
|
43
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
44
|
+
filename = Path(url).name
|
|
45
|
+
output_path = output_dir / filename
|
|
46
|
+
|
|
47
|
+
if output_path.exists():
|
|
48
|
+
print(f"Already downloaded: {output_path}")
|
|
49
|
+
return output_path
|
|
50
|
+
|
|
51
|
+
print(f"Downloading: {url}")
|
|
52
|
+
print(f"To: {output_path}")
|
|
53
|
+
|
|
54
|
+
try:
|
|
55
|
+
urllib.request.urlretrieve(url, output_path)
|
|
56
|
+
print(f"Downloaded: {output_path.stat().st_size / (1024*1024):.2f} MB")
|
|
57
|
+
except Exception as e:
|
|
58
|
+
print(f"Error downloading: {e}")
|
|
59
|
+
if output_path.exists():
|
|
60
|
+
output_path.unlink()
|
|
61
|
+
raise
|
|
62
|
+
|
|
63
|
+
return output_path
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def extract_sysroot(archive_path: Path, extract_dir: Path, arch: str) -> Path:
|
|
67
|
+
"""Extract only the sysroot directory from LLVM-MinGW."""
|
|
68
|
+
print(f"\nExtracting sysroot from: {archive_path}")
|
|
69
|
+
|
|
70
|
+
# Determine target triple based on architecture
|
|
71
|
+
if arch == "x86_64":
|
|
72
|
+
sysroot_name = "x86_64-w64-mingw32"
|
|
73
|
+
elif arch == "arm64":
|
|
74
|
+
sysroot_name = "aarch64-w64-mingw32"
|
|
75
|
+
else:
|
|
76
|
+
raise ValueError(f"Unknown architecture: {arch}")
|
|
77
|
+
|
|
78
|
+
# Extract entire archive first (LLVM-MinGW structure)
|
|
79
|
+
temp_extract = extract_dir / "temp"
|
|
80
|
+
temp_extract.mkdir(parents=True, exist_ok=True)
|
|
81
|
+
|
|
82
|
+
print("Extracting archive...")
|
|
83
|
+
try:
|
|
84
|
+
# Check if it's a ZIP or tar.xz archive
|
|
85
|
+
if archive_path.suffix == ".zip":
|
|
86
|
+
with zipfile.ZipFile(archive_path, "r") as zf:
|
|
87
|
+
zf.extractall(path=temp_extract)
|
|
88
|
+
else:
|
|
89
|
+
with tarfile.open(archive_path, "r:xz") as tar:
|
|
90
|
+
tar.extractall(path=temp_extract)
|
|
91
|
+
except Exception as e:
|
|
92
|
+
print(f"Error extracting archive: {e}")
|
|
93
|
+
raise
|
|
94
|
+
|
|
95
|
+
# Find the llvm-mingw root directory
|
|
96
|
+
llvm_mingw_root = None
|
|
97
|
+
for item in temp_extract.iterdir():
|
|
98
|
+
if item.is_dir() and item.name.startswith("llvm-mingw"):
|
|
99
|
+
llvm_mingw_root = item
|
|
100
|
+
break
|
|
101
|
+
|
|
102
|
+
if not llvm_mingw_root:
|
|
103
|
+
raise RuntimeError(f"Could not find llvm-mingw root directory in {temp_extract}")
|
|
104
|
+
|
|
105
|
+
print(f"Found LLVM-MinGW root: {llvm_mingw_root}")
|
|
106
|
+
|
|
107
|
+
# Copy sysroot directory
|
|
108
|
+
sysroot_src = llvm_mingw_root / sysroot_name
|
|
109
|
+
if not sysroot_src.exists():
|
|
110
|
+
raise RuntimeError(f"Sysroot not found: {sysroot_src}")
|
|
111
|
+
|
|
112
|
+
sysroot_dst = extract_dir / sysroot_name
|
|
113
|
+
print(f"Copying sysroot: {sysroot_src} -> {sysroot_dst}")
|
|
114
|
+
|
|
115
|
+
if sysroot_dst.exists():
|
|
116
|
+
shutil.rmtree(sysroot_dst)
|
|
117
|
+
|
|
118
|
+
shutil.copytree(sysroot_src, sysroot_dst, symlinks=True)
|
|
119
|
+
|
|
120
|
+
# Copy top-level include directory (contains C/C++ headers)
|
|
121
|
+
include_src = llvm_mingw_root / "include"
|
|
122
|
+
if include_src.exists():
|
|
123
|
+
include_dst = extract_dir / "include"
|
|
124
|
+
print(f"Copying headers: {include_src} -> {include_dst}")
|
|
125
|
+
if include_dst.exists():
|
|
126
|
+
shutil.rmtree(include_dst)
|
|
127
|
+
shutil.copytree(include_src, include_dst, symlinks=True)
|
|
128
|
+
|
|
129
|
+
# Also copy generic headers if they exist
|
|
130
|
+
generic_headers = llvm_mingw_root / "generic-w64-mingw32"
|
|
131
|
+
if generic_headers.exists():
|
|
132
|
+
generic_dst = extract_dir / "generic-w64-mingw32"
|
|
133
|
+
print(f"Copying generic headers: {generic_headers} -> {generic_dst}")
|
|
134
|
+
if generic_dst.exists():
|
|
135
|
+
shutil.rmtree(generic_dst)
|
|
136
|
+
shutil.copytree(generic_headers, generic_dst, symlinks=True)
|
|
137
|
+
|
|
138
|
+
# Copy clang resource headers (mm_malloc.h, intrinsics, etc.)
|
|
139
|
+
# These are compiler builtin headers needed for compilation
|
|
140
|
+
clang_resource_src = llvm_mingw_root / "lib" / "clang"
|
|
141
|
+
if clang_resource_src.exists():
|
|
142
|
+
# Find the version directory (e.g., "21")
|
|
143
|
+
version_dirs = [d for d in clang_resource_src.iterdir() if d.is_dir()]
|
|
144
|
+
if version_dirs:
|
|
145
|
+
clang_version_dir = version_dirs[0] # Should only be one
|
|
146
|
+
resource_include_src = clang_version_dir / "include"
|
|
147
|
+
if resource_include_src.exists():
|
|
148
|
+
# Copy to lib/clang/<version>/include in sysroot
|
|
149
|
+
resource_dst = extract_dir / "lib" / "clang" / clang_version_dir.name / "include"
|
|
150
|
+
print(f"Copying clang resource headers: {resource_include_src} -> {resource_dst}")
|
|
151
|
+
resource_dst.parent.mkdir(parents=True, exist_ok=True)
|
|
152
|
+
if resource_dst.exists():
|
|
153
|
+
shutil.rmtree(resource_dst)
|
|
154
|
+
shutil.copytree(resource_include_src, resource_dst, symlinks=True)
|
|
155
|
+
print(f"Copied {len(list(resource_dst.glob('*.h')))} resource headers")
|
|
156
|
+
|
|
157
|
+
# Copy compiler-rt runtime libraries (libclang_rt.builtins.a, etc.)
|
|
158
|
+
# These are needed for linking to provide runtime support functions
|
|
159
|
+
resource_lib_src = clang_version_dir / "lib"
|
|
160
|
+
if resource_lib_src.exists():
|
|
161
|
+
resource_lib_dst = extract_dir / "lib" / "clang" / clang_version_dir.name / "lib"
|
|
162
|
+
print(f"Copying compiler-rt libraries: {resource_lib_src} -> {resource_lib_dst}")
|
|
163
|
+
resource_lib_dst.parent.mkdir(parents=True, exist_ok=True)
|
|
164
|
+
if resource_lib_dst.exists():
|
|
165
|
+
shutil.rmtree(resource_lib_dst)
|
|
166
|
+
shutil.copytree(resource_lib_src, resource_lib_dst, symlinks=True)
|
|
167
|
+
|
|
168
|
+
# Count library files
|
|
169
|
+
lib_count = len(list(resource_lib_dst.glob("**/*.a")))
|
|
170
|
+
print(f"Copied {lib_count} compiler-rt library files")
|
|
171
|
+
|
|
172
|
+
# Clean up temp directory
|
|
173
|
+
print("Cleaning up temporary files...")
|
|
174
|
+
shutil.rmtree(temp_extract)
|
|
175
|
+
|
|
176
|
+
print(f"\n✓ Sysroot extracted to: {sysroot_dst}")
|
|
177
|
+
return sysroot_dst
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def create_archive(sysroot_dir: Path, output_dir: Path, arch: str) -> Path:
|
|
181
|
+
"""Create compressed archive of sysroot."""
|
|
182
|
+
try:
|
|
183
|
+
import pyzstd
|
|
184
|
+
except ImportError:
|
|
185
|
+
print("Error: pyzstd module not installed")
|
|
186
|
+
print("Install with: pip install pyzstd")
|
|
187
|
+
sys.exit(1)
|
|
188
|
+
|
|
189
|
+
archive_name = f"mingw-sysroot-{LLVM_VERSION}-win-{arch}.tar.zst"
|
|
190
|
+
archive_path = output_dir / archive_name
|
|
191
|
+
|
|
192
|
+
print(f"\nCreating archive: {archive_path}")
|
|
193
|
+
|
|
194
|
+
# Create tar archive in memory, then compress
|
|
195
|
+
import io
|
|
196
|
+
|
|
197
|
+
tar_buffer = io.BytesIO()
|
|
198
|
+
|
|
199
|
+
with tarfile.open(fileobj=tar_buffer, mode="w") as tar:
|
|
200
|
+
# Determine what to archive
|
|
201
|
+
sysroot_name = "x86_64-w64-mingw32" if arch == "x86_64" else "aarch64-w64-mingw32"
|
|
202
|
+
|
|
203
|
+
sysroot_path = sysroot_dir.parent / sysroot_name
|
|
204
|
+
include_path = sysroot_dir.parent / "include"
|
|
205
|
+
generic_path = sysroot_dir.parent / "generic-w64-mingw32"
|
|
206
|
+
lib_clang_path = sysroot_dir.parent / "lib" / "clang"
|
|
207
|
+
|
|
208
|
+
if sysroot_path.exists():
|
|
209
|
+
print(f"Adding to archive: {sysroot_name}/")
|
|
210
|
+
tar.add(sysroot_path, arcname=sysroot_name)
|
|
211
|
+
|
|
212
|
+
if include_path.exists():
|
|
213
|
+
print("Adding to archive: include/")
|
|
214
|
+
tar.add(include_path, arcname="include")
|
|
215
|
+
|
|
216
|
+
if generic_path.exists():
|
|
217
|
+
print("Adding to archive: generic-w64-mingw32/")
|
|
218
|
+
tar.add(generic_path, arcname="generic-w64-mingw32")
|
|
219
|
+
|
|
220
|
+
if lib_clang_path.exists():
|
|
221
|
+
print("Adding to archive: lib/clang/ (resource headers)")
|
|
222
|
+
tar.add(lib_clang_path, arcname="lib/clang")
|
|
223
|
+
|
|
224
|
+
tar_data = tar_buffer.getvalue()
|
|
225
|
+
tar_size = len(tar_data)
|
|
226
|
+
print(f"Tar size: {tar_size / (1024*1024):.2f} MB")
|
|
227
|
+
|
|
228
|
+
# Compress with zstd level 22
|
|
229
|
+
print("Compressing with zstd level 22...")
|
|
230
|
+
compressed_data = pyzstd.compress(tar_data, level_or_option=22)
|
|
231
|
+
|
|
232
|
+
with open(archive_path, "wb") as f:
|
|
233
|
+
f.write(compressed_data)
|
|
234
|
+
|
|
235
|
+
compressed_size = archive_path.stat().st_size
|
|
236
|
+
ratio = (1 - compressed_size / tar_size) * 100
|
|
237
|
+
|
|
238
|
+
print(f"Compressed size: {compressed_size / (1024*1024):.2f} MB")
|
|
239
|
+
print(f"Compression ratio: {ratio:.1f}%")
|
|
240
|
+
|
|
241
|
+
return archive_path
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def generate_checksums(archive_path: Path) -> dict[str, str]:
|
|
245
|
+
"""Generate SHA256 and MD5 checksums."""
|
|
246
|
+
print("\nGenerating checksums...")
|
|
247
|
+
|
|
248
|
+
sha256_hash = hashlib.sha256()
|
|
249
|
+
md5_hash = hashlib.md5()
|
|
250
|
+
|
|
251
|
+
with open(archive_path, "rb") as f:
|
|
252
|
+
while True:
|
|
253
|
+
chunk = f.read(8192)
|
|
254
|
+
if not chunk:
|
|
255
|
+
break
|
|
256
|
+
sha256_hash.update(chunk)
|
|
257
|
+
md5_hash.update(chunk)
|
|
258
|
+
|
|
259
|
+
checksums = {
|
|
260
|
+
"sha256": sha256_hash.hexdigest(),
|
|
261
|
+
"md5": md5_hash.hexdigest(),
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
# Write checksum files
|
|
265
|
+
sha256_file = Path(str(archive_path) + ".sha256")
|
|
266
|
+
md5_file = Path(str(archive_path) + ".md5")
|
|
267
|
+
|
|
268
|
+
with open(sha256_file, "w") as f:
|
|
269
|
+
f.write(f"{checksums['sha256']} {archive_path.name}\n")
|
|
270
|
+
|
|
271
|
+
with open(md5_file, "w") as f:
|
|
272
|
+
f.write(f"{checksums['md5']} {archive_path.name}\n")
|
|
273
|
+
|
|
274
|
+
print(f"SHA256: {checksums['sha256']}")
|
|
275
|
+
print(f"MD5: {checksums['md5']}")
|
|
276
|
+
|
|
277
|
+
return checksums
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def main() -> None:
|
|
281
|
+
"""Main entry point."""
|
|
282
|
+
parser = argparse.ArgumentParser(description="Extract MinGW sysroot from LLVM-MinGW release")
|
|
283
|
+
parser.add_argument("--arch", required=True, choices=["x86_64", "arm64"], help="Target architecture")
|
|
284
|
+
parser.add_argument(
|
|
285
|
+
"--work-dir",
|
|
286
|
+
type=Path,
|
|
287
|
+
default=Path("work"),
|
|
288
|
+
help="Working directory for downloads and extraction",
|
|
289
|
+
)
|
|
290
|
+
parser.add_argument(
|
|
291
|
+
"--output-dir",
|
|
292
|
+
type=Path,
|
|
293
|
+
default=Path("downloads-bins/assets/mingw/win"),
|
|
294
|
+
help="Output directory for final archives",
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
args = parser.parse_args()
|
|
298
|
+
|
|
299
|
+
work_dir = args.work_dir / args.arch
|
|
300
|
+
output_dir = args.output_dir / args.arch
|
|
301
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
302
|
+
|
|
303
|
+
print("=" * 70)
|
|
304
|
+
print("MINGW SYSROOT EXTRACTION")
|
|
305
|
+
print("=" * 70)
|
|
306
|
+
print(f"Architecture: {args.arch}")
|
|
307
|
+
print(f"Work directory: {work_dir}")
|
|
308
|
+
print(f"Output directory: {output_dir}")
|
|
309
|
+
print()
|
|
310
|
+
|
|
311
|
+
# Step 1: Download
|
|
312
|
+
archive_path = download_llvm_mingw(args.arch, work_dir)
|
|
313
|
+
|
|
314
|
+
# Step 2: Extract sysroot
|
|
315
|
+
sysroot_dir = extract_sysroot(archive_path, work_dir / "extracted", args.arch)
|
|
316
|
+
|
|
317
|
+
# Step 3: Create compressed archive
|
|
318
|
+
final_archive = create_archive(sysroot_dir, output_dir, args.arch)
|
|
319
|
+
|
|
320
|
+
# Step 4: Generate checksums
|
|
321
|
+
checksums = generate_checksums(final_archive)
|
|
322
|
+
|
|
323
|
+
# Step 5: Update manifest
|
|
324
|
+
manifest_path = output_dir / "manifest.json"
|
|
325
|
+
manifest_data = {
|
|
326
|
+
"latest": LLVM_VERSION,
|
|
327
|
+
"versions": {
|
|
328
|
+
LLVM_VERSION: {
|
|
329
|
+
"version": LLVM_VERSION,
|
|
330
|
+
"href": f"./mingw-sysroot-{LLVM_VERSION}-win-{args.arch}.tar.zst",
|
|
331
|
+
"sha256": checksums["sha256"],
|
|
332
|
+
}
|
|
333
|
+
},
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
with open(manifest_path, "w") as f:
|
|
337
|
+
json.dump(manifest_data, f, indent=2)
|
|
338
|
+
|
|
339
|
+
print(f"\n✓ Manifest written to: {manifest_path}")
|
|
340
|
+
print("\n" + "=" * 70)
|
|
341
|
+
print("COMPLETE")
|
|
342
|
+
print("=" * 70)
|
|
343
|
+
print(f"Archive: {final_archive}")
|
|
344
|
+
print(f"Size: {final_archive.stat().st_size / (1024*1024):.2f} MB")
|
|
345
|
+
print(f"SHA256: {checksums['sha256']}")
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
if __name__ == "__main__":
|
|
349
|
+
main()
|