clang-tool-chain 1.0.3__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.
- clang_tool_chain/__init__.py +0 -0
- clang_tool_chain/__version__.py +3 -0
- clang_tool_chain/checksums.py +270 -0
- clang_tool_chain/cli.py +572 -0
- clang_tool_chain/downloader.py +1325 -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 +1589 -0
- clang_tool_chain-1.0.3.dist-info/METADATA +1815 -0
- clang_tool_chain-1.0.3.dist-info/RECORD +14 -0
- clang_tool_chain-1.0.3.dist-info/WHEEL +4 -0
- clang_tool_chain-1.0.3.dist-info/entry_points.txt +31 -0
- clang_tool_chain-1.0.3.dist-info/licenses/LICENSE +204 -0
|
@@ -0,0 +1,1589 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Wrapper infrastructure for executing LLVM/Clang tools.
|
|
3
|
+
|
|
4
|
+
This module provides the core functionality for wrapping LLVM toolchain
|
|
5
|
+
binaries and forwarding commands to them with proper platform detection.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import hashlib
|
|
9
|
+
import logging
|
|
10
|
+
import os
|
|
11
|
+
import platform
|
|
12
|
+
import shutil
|
|
13
|
+
import subprocess
|
|
14
|
+
import sys
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import NoReturn
|
|
17
|
+
|
|
18
|
+
from . import downloader
|
|
19
|
+
|
|
20
|
+
# Configure logging for GitHub Actions and general debugging
|
|
21
|
+
logging.basicConfig(
|
|
22
|
+
level=logging.INFO,
|
|
23
|
+
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
|
24
|
+
handlers=[logging.StreamHandler(sys.stderr)],
|
|
25
|
+
)
|
|
26
|
+
logger = logging.getLogger(__name__)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _get_toolchain_directory_listing(platform_name: str) -> str:
|
|
30
|
+
"""
|
|
31
|
+
Get a directory listing of ~/.clang-tool-chain for debugging purposes.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
platform_name: Platform name ("win", "linux", "darwin")
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
Formatted directory listing string (2 levels deep)
|
|
38
|
+
"""
|
|
39
|
+
import subprocess as _subprocess
|
|
40
|
+
|
|
41
|
+
toolchain_dir = Path.home() / ".clang-tool-chain"
|
|
42
|
+
|
|
43
|
+
try:
|
|
44
|
+
if platform_name == "win":
|
|
45
|
+
# On Windows, manually walk the directory tree (2 levels)
|
|
46
|
+
lines = []
|
|
47
|
+
if toolchain_dir.exists():
|
|
48
|
+
lines.append(str(toolchain_dir))
|
|
49
|
+
for item in toolchain_dir.iterdir():
|
|
50
|
+
lines.append(f" {item.name}")
|
|
51
|
+
if item.is_dir():
|
|
52
|
+
try:
|
|
53
|
+
for subitem in item.iterdir():
|
|
54
|
+
lines.append(f" {item.name}/{subitem.name}")
|
|
55
|
+
except (PermissionError, OSError):
|
|
56
|
+
pass
|
|
57
|
+
return "\n".join(lines)
|
|
58
|
+
else:
|
|
59
|
+
# On Unix, use find
|
|
60
|
+
result = _subprocess.run(
|
|
61
|
+
["find", str(toolchain_dir), "-maxdepth", "2"], capture_output=True, text=True, timeout=5
|
|
62
|
+
)
|
|
63
|
+
return result.stdout
|
|
64
|
+
except Exception as e:
|
|
65
|
+
return f"Could not list directory: {e}"
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def get_platform_info() -> tuple[str, str]:
|
|
69
|
+
"""
|
|
70
|
+
Detect the current platform and architecture.
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
Tuple of (platform, architecture) strings
|
|
74
|
+
Platform: "win", "linux", or "darwin"
|
|
75
|
+
Architecture: "x86_64" or "aarch64"
|
|
76
|
+
"""
|
|
77
|
+
system = platform.system().lower()
|
|
78
|
+
machine = platform.machine().lower()
|
|
79
|
+
|
|
80
|
+
logger.debug(f"Detecting platform: system={system}, machine={machine}")
|
|
81
|
+
|
|
82
|
+
# Normalize platform name
|
|
83
|
+
if system == "windows":
|
|
84
|
+
platform_name = "win"
|
|
85
|
+
elif system == "linux":
|
|
86
|
+
platform_name = "linux"
|
|
87
|
+
elif system == "darwin":
|
|
88
|
+
platform_name = "darwin"
|
|
89
|
+
else:
|
|
90
|
+
logger.error(f"Unsupported platform detected: {system}")
|
|
91
|
+
raise RuntimeError(
|
|
92
|
+
f"Unsupported platform: {system}\n"
|
|
93
|
+
f"clang-tool-chain currently supports: Windows, Linux, and macOS (Darwin)\n"
|
|
94
|
+
f"Your system: {system}\n"
|
|
95
|
+
f"If you believe this platform should be supported, please report this at:\n"
|
|
96
|
+
f"https://github.com/zackees/clang-tool-chain/issues"
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
# Normalize architecture
|
|
100
|
+
if machine in ("x86_64", "amd64"):
|
|
101
|
+
arch = "x86_64"
|
|
102
|
+
elif machine in ("aarch64", "arm64"):
|
|
103
|
+
arch = "arm64"
|
|
104
|
+
else:
|
|
105
|
+
logger.error(f"Unsupported architecture detected: {machine}")
|
|
106
|
+
raise RuntimeError(
|
|
107
|
+
f"Unsupported architecture: {machine}\n"
|
|
108
|
+
f"clang-tool-chain currently supports: x86_64 (AMD64) and ARM64\n"
|
|
109
|
+
f"Your architecture: {machine}\n"
|
|
110
|
+
f"Supported architectures:\n"
|
|
111
|
+
f" - x86_64, amd64 (Intel/AMD 64-bit)\n"
|
|
112
|
+
f" - aarch64, arm64 (ARM 64-bit)\n"
|
|
113
|
+
f"If you believe this architecture should be supported, please report this at:\n"
|
|
114
|
+
f"https://github.com/zackees/clang-tool-chain/issues"
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
logger.info(f"Platform detected: {platform_name}/{arch}")
|
|
118
|
+
return platform_name, arch
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def get_assets_dir() -> Path:
|
|
122
|
+
"""
|
|
123
|
+
Get the path to the assets directory containing LLVM binaries.
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
Path to the assets directory
|
|
127
|
+
"""
|
|
128
|
+
# Get the package directory
|
|
129
|
+
package_dir = Path(__file__).parent
|
|
130
|
+
|
|
131
|
+
# Assets should be in the project root (two levels up from package)
|
|
132
|
+
project_root = package_dir.parent.parent
|
|
133
|
+
assets_dir = project_root / "assets"
|
|
134
|
+
|
|
135
|
+
return assets_dir
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def get_platform_binary_dir() -> Path:
|
|
139
|
+
"""
|
|
140
|
+
Get the directory containing binaries for the current platform.
|
|
141
|
+
|
|
142
|
+
This function ensures the toolchain is downloaded before returning the path.
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
Path to the platform-specific binary directory
|
|
146
|
+
|
|
147
|
+
Raises:
|
|
148
|
+
RuntimeError: If the platform is not supported or binaries cannot be installed
|
|
149
|
+
"""
|
|
150
|
+
logger.info("Getting platform binary directory")
|
|
151
|
+
platform_name, arch = get_platform_info()
|
|
152
|
+
|
|
153
|
+
# Ensure toolchain is downloaded and installed
|
|
154
|
+
logger.info(f"Ensuring toolchain is available for {platform_name}/{arch}")
|
|
155
|
+
downloader.ensure_toolchain(platform_name, arch)
|
|
156
|
+
|
|
157
|
+
# Get the installation directory
|
|
158
|
+
install_dir = downloader.get_install_dir(platform_name, arch)
|
|
159
|
+
bin_dir = install_dir / "bin"
|
|
160
|
+
logger.debug(f"Binary directory: {bin_dir}")
|
|
161
|
+
|
|
162
|
+
if not bin_dir.exists():
|
|
163
|
+
logger.error(f"Binary directory does not exist: {bin_dir}")
|
|
164
|
+
# Get directory listing for debugging
|
|
165
|
+
dir_listing = _get_toolchain_directory_listing(platform_name)
|
|
166
|
+
|
|
167
|
+
raise RuntimeError(
|
|
168
|
+
f"Binaries not found for {platform_name}-{arch}\n"
|
|
169
|
+
f"Expected location: {bin_dir}\n"
|
|
170
|
+
f"\n"
|
|
171
|
+
f"Directory structure of ~/.clang-tool-chain (2 levels deep):\n"
|
|
172
|
+
f"{dir_listing}\n"
|
|
173
|
+
f"\n"
|
|
174
|
+
f"The toolchain download may have failed. Please try again or report this issue at:\n"
|
|
175
|
+
f"https://github.com/zackees/clang-tool-chain/issues"
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
logger.info(f"Binary directory found: {bin_dir}")
|
|
179
|
+
return bin_dir
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def find_tool_binary(tool_name: str) -> Path:
|
|
183
|
+
"""
|
|
184
|
+
Find the path to a specific tool binary.
|
|
185
|
+
|
|
186
|
+
Args:
|
|
187
|
+
tool_name: Name of the tool (e.g., "clang", "llvm-ar")
|
|
188
|
+
|
|
189
|
+
Returns:
|
|
190
|
+
Path to the tool binary
|
|
191
|
+
|
|
192
|
+
Raises:
|
|
193
|
+
RuntimeError: If the tool binary is not found
|
|
194
|
+
"""
|
|
195
|
+
logger.info(f"Finding binary for tool: {tool_name}")
|
|
196
|
+
bin_dir = get_platform_binary_dir()
|
|
197
|
+
platform_name, _ = get_platform_info()
|
|
198
|
+
|
|
199
|
+
# Add .exe extension on Windows
|
|
200
|
+
tool_path = bin_dir / f"{tool_name}.exe" if platform_name == "win" else bin_dir / tool_name
|
|
201
|
+
logger.debug(f"Looking for tool at: {tool_path}")
|
|
202
|
+
|
|
203
|
+
if not tool_path.exists():
|
|
204
|
+
logger.warning(f"Tool not found at primary location: {tool_path}")
|
|
205
|
+
# Try alternative names for some tools
|
|
206
|
+
alternatives = {
|
|
207
|
+
"lld": ["lld-link", "ld.lld"],
|
|
208
|
+
"clang": ["clang++", "clang-cpp"],
|
|
209
|
+
"lld-link": ["lld", "ld.lld"],
|
|
210
|
+
"ld.lld": ["lld", "lld-link"],
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if tool_name in alternatives:
|
|
214
|
+
logger.debug(f"Trying alternative names for {tool_name}: {alternatives[tool_name]}")
|
|
215
|
+
for alt_name in alternatives[tool_name]:
|
|
216
|
+
alt_path = bin_dir / f"{alt_name}.exe" if platform_name == "win" else bin_dir / alt_name
|
|
217
|
+
logger.debug(f"Checking alternative: {alt_path}")
|
|
218
|
+
|
|
219
|
+
if alt_path.exists():
|
|
220
|
+
logger.info(f"Found alternative tool at: {alt_path}")
|
|
221
|
+
return alt_path
|
|
222
|
+
|
|
223
|
+
# List available tools
|
|
224
|
+
available_tools = [f.stem for f in bin_dir.iterdir() if f.is_file()]
|
|
225
|
+
logger.error(f"Tool '{tool_name}' not found. Available tools: {', '.join(sorted(available_tools)[:20])}")
|
|
226
|
+
|
|
227
|
+
# Get directory listing for debugging
|
|
228
|
+
dir_listing = _get_toolchain_directory_listing(platform_name)
|
|
229
|
+
|
|
230
|
+
raise RuntimeError(
|
|
231
|
+
f"Tool '{tool_name}' not found\n"
|
|
232
|
+
f"Expected location: {tool_path}\n"
|
|
233
|
+
f"\n"
|
|
234
|
+
f"This tool may not be included in your LLVM installation.\n"
|
|
235
|
+
f"\n"
|
|
236
|
+
f"Available tools in {bin_dir.name}/:\n"
|
|
237
|
+
f" {', '.join(sorted(available_tools)[:20])}\n"
|
|
238
|
+
f" {'... and more' if len(available_tools) > 20 else ''}\n"
|
|
239
|
+
f"\n"
|
|
240
|
+
f"Directory structure of ~/.clang-tool-chain (2 levels deep):\n"
|
|
241
|
+
f"{dir_listing}\n"
|
|
242
|
+
f"\n"
|
|
243
|
+
f"Troubleshooting:\n"
|
|
244
|
+
f" - Verify the tool name is correct\n"
|
|
245
|
+
f" - Check if the tool is part of LLVM {tool_name}\n"
|
|
246
|
+
f" - Re-download binaries: python scripts/download_binaries.py\n"
|
|
247
|
+
f" - Report issue: https://github.com/zackees/clang-tool-chain/issues"
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
logger.info(f"Tool binary found: {tool_path}")
|
|
251
|
+
return tool_path
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def find_sccache_binary() -> str:
|
|
255
|
+
"""
|
|
256
|
+
Find the sccache binary in PATH.
|
|
257
|
+
|
|
258
|
+
Returns:
|
|
259
|
+
Path to the sccache binary
|
|
260
|
+
|
|
261
|
+
Raises:
|
|
262
|
+
RuntimeError: If sccache is not found in PATH
|
|
263
|
+
"""
|
|
264
|
+
sccache_path = shutil.which("sccache")
|
|
265
|
+
|
|
266
|
+
if sccache_path is None:
|
|
267
|
+
raise RuntimeError(
|
|
268
|
+
"sccache not found in PATH\n"
|
|
269
|
+
"\n"
|
|
270
|
+
"sccache is required to use the sccache wrapper commands.\n"
|
|
271
|
+
"\n"
|
|
272
|
+
"Installation options:\n"
|
|
273
|
+
" - pip install clang-tool-chain[sccache]\n"
|
|
274
|
+
" - cargo install sccache\n"
|
|
275
|
+
" - Download from: https://github.com/mozilla/sccache/releases\n"
|
|
276
|
+
" - Linux: apt install sccache / yum install sccache\n"
|
|
277
|
+
" - macOS: brew install sccache\n"
|
|
278
|
+
"\n"
|
|
279
|
+
"After installation, ensure sccache is in your PATH.\n"
|
|
280
|
+
"Verify with: sccache --version"
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
return sccache_path
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def _detect_windows_sdk() -> dict[str, str] | None:
|
|
287
|
+
"""
|
|
288
|
+
Detect Windows SDK installation via environment variables.
|
|
289
|
+
|
|
290
|
+
This function checks for Visual Studio and Windows SDK environment variables
|
|
291
|
+
that are typically set by vcvars*.bat or Visual Studio Developer Command Prompt.
|
|
292
|
+
|
|
293
|
+
Returns:
|
|
294
|
+
Dictionary with SDK information if found, None otherwise.
|
|
295
|
+
Dictionary keys: 'sdk_dir', 'vc_tools_dir', 'sdk_version' (if available)
|
|
296
|
+
|
|
297
|
+
Note:
|
|
298
|
+
This function only checks environment variables. It does not search the
|
|
299
|
+
registry or filesystem for SDK installations. The goal is to detect if
|
|
300
|
+
the user has already set up their Visual Studio environment.
|
|
301
|
+
"""
|
|
302
|
+
sdk_info = {}
|
|
303
|
+
|
|
304
|
+
# Check for Windows SDK environment variables
|
|
305
|
+
# These are set by vcvarsall.bat and similar VS setup scripts
|
|
306
|
+
sdk_dir = os.environ.get("WindowsSdkDir") or os.environ.get("WindowsSDKDir") # noqa: SIM112
|
|
307
|
+
if sdk_dir:
|
|
308
|
+
sdk_info["sdk_dir"] = sdk_dir
|
|
309
|
+
logger.debug(f"Windows SDK found via environment: {sdk_dir}")
|
|
310
|
+
|
|
311
|
+
# Check for Universal CRT SDK (required for C runtime)
|
|
312
|
+
ucrt_sdk_dir = os.environ.get("UniversalCRTSdkDir") # noqa: SIM112
|
|
313
|
+
if ucrt_sdk_dir:
|
|
314
|
+
sdk_info["ucrt_dir"] = ucrt_sdk_dir
|
|
315
|
+
logger.debug(f"Universal CRT SDK found: {ucrt_sdk_dir}")
|
|
316
|
+
|
|
317
|
+
# Check for VC Tools (MSVC compiler toolchain)
|
|
318
|
+
vc_tools_dir = os.environ.get("VCToolsInstallDir") # noqa: SIM112
|
|
319
|
+
if vc_tools_dir:
|
|
320
|
+
sdk_info["vc_tools_dir"] = vc_tools_dir
|
|
321
|
+
logger.debug(f"VC Tools found: {vc_tools_dir}")
|
|
322
|
+
|
|
323
|
+
# Check for VS installation directory
|
|
324
|
+
vs_install_dir = os.environ.get("VSINSTALLDIR")
|
|
325
|
+
if vs_install_dir:
|
|
326
|
+
sdk_info["vs_install_dir"] = vs_install_dir
|
|
327
|
+
logger.debug(f"Visual Studio installation found: {vs_install_dir}")
|
|
328
|
+
|
|
329
|
+
# Check for Windows SDK version
|
|
330
|
+
sdk_version = os.environ.get("WindowsSDKVersion") # noqa: SIM112
|
|
331
|
+
if sdk_version:
|
|
332
|
+
sdk_info["sdk_version"] = sdk_version.rstrip("\\") # Remove trailing backslash if present
|
|
333
|
+
logger.debug(f"Windows SDK version: {sdk_version}")
|
|
334
|
+
|
|
335
|
+
# Return SDK info if we found at least the SDK directory or VC tools
|
|
336
|
+
if sdk_info:
|
|
337
|
+
logger.info(f"Windows SDK detected: {', '.join(sdk_info.keys())}")
|
|
338
|
+
return sdk_info
|
|
339
|
+
|
|
340
|
+
logger.debug("Windows SDK not detected in environment variables")
|
|
341
|
+
return None
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
def _print_msvc_sdk_warning() -> None:
|
|
345
|
+
"""
|
|
346
|
+
Print a helpful warning message to stderr when Windows SDK is not detected.
|
|
347
|
+
|
|
348
|
+
This is called when MSVC target is being used but we cannot detect the
|
|
349
|
+
Windows SDK via environment variables. The compilation may still succeed
|
|
350
|
+
if clang can find the SDK automatically, or it may fail with missing
|
|
351
|
+
headers/libraries errors.
|
|
352
|
+
"""
|
|
353
|
+
print("\n" + "=" * 70, file=sys.stderr)
|
|
354
|
+
print("⚠️ Windows SDK Not Detected in Environment", file=sys.stderr)
|
|
355
|
+
print("=" * 70, file=sys.stderr)
|
|
356
|
+
print("\nThe MSVC target requires Windows SDK for system headers and libraries.", file=sys.stderr)
|
|
357
|
+
print("\nNo SDK environment variables found. This may mean:", file=sys.stderr)
|
|
358
|
+
print(" • Visual Studio or Windows SDK is not installed", file=sys.stderr)
|
|
359
|
+
print(" • VS Developer Command Prompt is not being used", file=sys.stderr)
|
|
360
|
+
print(" • Environment variables are not set (vcvarsall.bat not run)", file=sys.stderr)
|
|
361
|
+
print("\n" + "-" * 70, file=sys.stderr)
|
|
362
|
+
print("Recommendation: Set up Visual Studio environment", file=sys.stderr)
|
|
363
|
+
print("-" * 70, file=sys.stderr)
|
|
364
|
+
print("\nOption 1: Use Visual Studio Developer Command Prompt", file=sys.stderr)
|
|
365
|
+
print(" • Search for 'Developer Command Prompt' in Start Menu", file=sys.stderr)
|
|
366
|
+
print(" • Run your build commands from that prompt", file=sys.stderr)
|
|
367
|
+
print("\nOption 2: Run vcvarsall.bat in your current shell", file=sys.stderr)
|
|
368
|
+
print(" • Typical location:", file=sys.stderr)
|
|
369
|
+
print(
|
|
370
|
+
" C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\VC\\Auxiliary\\Build\\vcvarsall.bat",
|
|
371
|
+
file=sys.stderr,
|
|
372
|
+
)
|
|
373
|
+
print(" • Run: vcvarsall.bat x64", file=sys.stderr)
|
|
374
|
+
print("\nOption 3: Install Visual Studio or Windows SDK", file=sys.stderr)
|
|
375
|
+
print(" • Visual Studio: https://visualstudio.microsoft.com/downloads/", file=sys.stderr)
|
|
376
|
+
print(" • Windows SDK only: https://developer.microsoft.com/windows/downloads/windows-sdk/", file=sys.stderr)
|
|
377
|
+
print("\n" + "-" * 70, file=sys.stderr)
|
|
378
|
+
print("Alternative: Use GNU ABI (MinGW) instead of MSVC", file=sys.stderr)
|
|
379
|
+
print("-" * 70, file=sys.stderr)
|
|
380
|
+
print("\nIf you don't need MSVC compatibility, use the default commands:", file=sys.stderr)
|
|
381
|
+
print(" • clang-tool-chain-c (uses GNU ABI, no SDK required)", file=sys.stderr)
|
|
382
|
+
print(" • clang-tool-chain-cpp (uses GNU ABI, no SDK required)", file=sys.stderr)
|
|
383
|
+
print("\n" + "=" * 70, file=sys.stderr)
|
|
384
|
+
print("Clang will attempt to find Windows SDK automatically...", file=sys.stderr)
|
|
385
|
+
print("=" * 70 + "\n", file=sys.stderr)
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
def _print_macos_sdk_error(reason: str) -> None:
|
|
389
|
+
"""
|
|
390
|
+
Print a helpful error message to stderr when macOS SDK detection fails.
|
|
391
|
+
|
|
392
|
+
This is called when compilation is about to proceed without SDK detection,
|
|
393
|
+
which will likely cause 'stdio.h' or 'iostream' not found errors.
|
|
394
|
+
|
|
395
|
+
Args:
|
|
396
|
+
reason: Brief description of why SDK detection failed
|
|
397
|
+
"""
|
|
398
|
+
print("\n" + "=" * 70, file=sys.stderr)
|
|
399
|
+
print("⚠️ macOS SDK Detection Failed", file=sys.stderr)
|
|
400
|
+
print("=" * 70, file=sys.stderr)
|
|
401
|
+
print(f"\nReason: {reason}", file=sys.stderr)
|
|
402
|
+
print("\nYour compilation may fail with errors like:", file=sys.stderr)
|
|
403
|
+
print(" fatal error: 'stdio.h' file not found", file=sys.stderr)
|
|
404
|
+
print(" fatal error: 'iostream' file not found", file=sys.stderr)
|
|
405
|
+
print("\n" + "-" * 70, file=sys.stderr)
|
|
406
|
+
print("Solution: Install Xcode Command Line Tools", file=sys.stderr)
|
|
407
|
+
print("-" * 70, file=sys.stderr)
|
|
408
|
+
print("\nRun this command in your terminal:", file=sys.stderr)
|
|
409
|
+
print("\n \033[1;36mxcode-select --install\033[0m", file=sys.stderr)
|
|
410
|
+
print("\nThen try compiling again.", file=sys.stderr)
|
|
411
|
+
print("\n" + "-" * 70, file=sys.stderr)
|
|
412
|
+
print("Alternative Solutions:", file=sys.stderr)
|
|
413
|
+
print("-" * 70, file=sys.stderr)
|
|
414
|
+
print("\n1. Specify SDK path manually:", file=sys.stderr)
|
|
415
|
+
print(" clang-tool-chain-c -isysroot /Library/Developer/.../MacOSX.sdk file.c", file=sys.stderr)
|
|
416
|
+
print("\n2. Set SDKROOT environment variable:", file=sys.stderr)
|
|
417
|
+
print(" export SDKROOT=$(xcrun --show-sdk-path) # if xcrun works", file=sys.stderr)
|
|
418
|
+
print("\n3. Use freestanding compilation (no standard library):", file=sys.stderr)
|
|
419
|
+
print(" clang-tool-chain-c -ffreestanding -nostdlib file.c", file=sys.stderr)
|
|
420
|
+
print("\n4. Disable automatic SDK detection:", file=sys.stderr)
|
|
421
|
+
print(" export CLANG_TOOL_CHAIN_NO_SYSROOT=1", file=sys.stderr)
|
|
422
|
+
print(" # Then specify SDK manually with -isysroot", file=sys.stderr)
|
|
423
|
+
print("\n" + "=" * 70, file=sys.stderr)
|
|
424
|
+
print("More info: https://github.com/zackees/clang-tool-chain#macos-sdk-detection-automatic", file=sys.stderr)
|
|
425
|
+
print("=" * 70 + "\n", file=sys.stderr)
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
def _add_macos_sysroot_if_needed(args: list[str]) -> list[str]:
|
|
429
|
+
"""
|
|
430
|
+
Add -isysroot flag for macOS if needed to find system headers.
|
|
431
|
+
|
|
432
|
+
On macOS, system headers (like stdio.h, iostream) are NOT in /usr/include.
|
|
433
|
+
Instead, they're only available in SDK bundles provided by Xcode or Command Line Tools.
|
|
434
|
+
Standalone clang binaries cannot automatically find these headers without help.
|
|
435
|
+
|
|
436
|
+
This function implements LLVM's official three-tier SDK detection strategy
|
|
437
|
+
(see LLVM patch D136315: https://reviews.llvm.org/D136315):
|
|
438
|
+
1. Explicit -isysroot flag (user override)
|
|
439
|
+
2. SDKROOT environment variable (Xcode/xcrun standard)
|
|
440
|
+
3. Automatic xcrun --show-sdk-path (fallback detection)
|
|
441
|
+
|
|
442
|
+
The function automatically detects the macOS SDK path and adds it to
|
|
443
|
+
the compiler arguments, unless:
|
|
444
|
+
- User has disabled it via CLANG_TOOL_CHAIN_NO_SYSROOT=1
|
|
445
|
+
- User has already specified -isysroot in the arguments
|
|
446
|
+
- SDKROOT environment variable is set (will be used by clang automatically)
|
|
447
|
+
- User specified flags indicating freestanding/no stdlib compilation:
|
|
448
|
+
-nostdinc, -nostdinc++, -nostdlib, -ffreestanding
|
|
449
|
+
|
|
450
|
+
Args:
|
|
451
|
+
args: Original compiler arguments
|
|
452
|
+
|
|
453
|
+
Returns:
|
|
454
|
+
Modified arguments with -isysroot prepended if needed
|
|
455
|
+
|
|
456
|
+
References:
|
|
457
|
+
- LLVM D136315: Try to guess SDK root with xcrun when unspecified
|
|
458
|
+
- Apple no longer ships headers in /usr/include since macOS 10.14 Mojave
|
|
459
|
+
"""
|
|
460
|
+
# Check if user wants to disable automatic sysroot
|
|
461
|
+
if os.environ.get("CLANG_TOOL_CHAIN_NO_SYSROOT") == "1":
|
|
462
|
+
return args
|
|
463
|
+
|
|
464
|
+
# Check if SDKROOT is already set (standard macOS environment variable)
|
|
465
|
+
if "SDKROOT" in os.environ:
|
|
466
|
+
return args
|
|
467
|
+
|
|
468
|
+
# Check if user already specified -isysroot
|
|
469
|
+
if "-isysroot" in args:
|
|
470
|
+
return args
|
|
471
|
+
|
|
472
|
+
# Check for flags that indicate freestanding or no-stdlib compilation
|
|
473
|
+
# In these cases, the user explicitly doesn't want system headers/libraries
|
|
474
|
+
no_sysroot_flags = {"-nostdinc", "-nostdinc++", "-nostdlib", "-ffreestanding"}
|
|
475
|
+
if any(flag in args for flag in no_sysroot_flags):
|
|
476
|
+
return args
|
|
477
|
+
|
|
478
|
+
# Try to detect the SDK path using xcrun
|
|
479
|
+
try:
|
|
480
|
+
result = subprocess.run(
|
|
481
|
+
["xcrun", "--show-sdk-path"],
|
|
482
|
+
capture_output=True,
|
|
483
|
+
text=True,
|
|
484
|
+
check=True,
|
|
485
|
+
timeout=5,
|
|
486
|
+
)
|
|
487
|
+
sdk_path = result.stdout.strip()
|
|
488
|
+
|
|
489
|
+
if sdk_path and Path(sdk_path).exists():
|
|
490
|
+
# Prepend -isysroot to arguments
|
|
491
|
+
logger.info(f"macOS SDK detected: {sdk_path}")
|
|
492
|
+
return ["-isysroot", sdk_path] + args
|
|
493
|
+
else:
|
|
494
|
+
# xcrun succeeded but returned invalid path
|
|
495
|
+
logger.warning(f"xcrun returned invalid SDK path: {sdk_path}")
|
|
496
|
+
_print_macos_sdk_error("xcrun returned invalid SDK path")
|
|
497
|
+
return args
|
|
498
|
+
|
|
499
|
+
except FileNotFoundError:
|
|
500
|
+
# xcrun command not found - Command Line Tools likely not installed
|
|
501
|
+
logger.error("xcrun command not found - Xcode Command Line Tools may not be installed")
|
|
502
|
+
_print_macos_sdk_error("xcrun command not found")
|
|
503
|
+
return args
|
|
504
|
+
|
|
505
|
+
except subprocess.CalledProcessError as e:
|
|
506
|
+
# xcrun failed with non-zero exit code
|
|
507
|
+
stderr_output = e.stderr.strip() if e.stderr else "No error output"
|
|
508
|
+
logger.error(f"xcrun failed: {stderr_output}")
|
|
509
|
+
_print_macos_sdk_error(f"xcrun failed: {stderr_output}")
|
|
510
|
+
return args
|
|
511
|
+
|
|
512
|
+
except subprocess.TimeoutExpired:
|
|
513
|
+
# xcrun took too long to respond
|
|
514
|
+
logger.warning("xcrun command timed out")
|
|
515
|
+
return args
|
|
516
|
+
|
|
517
|
+
except Exception as e:
|
|
518
|
+
# Unexpected error
|
|
519
|
+
logger.warning(f"Unexpected error detecting SDK: {e}")
|
|
520
|
+
return args
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
def _should_use_gnu_abi(platform_name: str, args: list[str]) -> bool:
|
|
524
|
+
"""
|
|
525
|
+
Determine if GNU ABI should be used based on platform and arguments.
|
|
526
|
+
|
|
527
|
+
Windows defaults to GNU ABI (MinGW) in v2.0+ for cross-platform consistency.
|
|
528
|
+
This matches the approach of zig cc and ensures consistent C++ ABI across platforms.
|
|
529
|
+
|
|
530
|
+
Args:
|
|
531
|
+
platform_name: Platform name ("win", "linux", "darwin")
|
|
532
|
+
args: Command-line arguments
|
|
533
|
+
|
|
534
|
+
Returns:
|
|
535
|
+
True if GNU ABI should be used (Windows + no explicit target), False otherwise
|
|
536
|
+
"""
|
|
537
|
+
# Non-Windows always uses default (which is GNU-like anyway)
|
|
538
|
+
if platform_name != "win":
|
|
539
|
+
return False
|
|
540
|
+
|
|
541
|
+
# Check if user explicitly specified target
|
|
542
|
+
args_str = " ".join(args)
|
|
543
|
+
if "--target=" in args_str or "--target " in args_str:
|
|
544
|
+
# User specified target explicitly, don't override
|
|
545
|
+
logger.debug("User specified explicit target, skipping GNU ABI injection")
|
|
546
|
+
return False
|
|
547
|
+
|
|
548
|
+
# Windows defaults to GNU ABI in v2.0+
|
|
549
|
+
logger.debug("Windows detected without explicit target, will use GNU ABI")
|
|
550
|
+
return True
|
|
551
|
+
|
|
552
|
+
|
|
553
|
+
def _get_gnu_target_args(platform_name: str, arch: str) -> list[str]:
|
|
554
|
+
"""
|
|
555
|
+
Get GNU ABI target arguments for Windows.
|
|
556
|
+
|
|
557
|
+
This function ensures the MinGW sysroot is installed and returns
|
|
558
|
+
the necessary compiler arguments to use GNU ABI instead of MSVC ABI.
|
|
559
|
+
|
|
560
|
+
Args:
|
|
561
|
+
platform_name: Platform name
|
|
562
|
+
arch: Architecture
|
|
563
|
+
|
|
564
|
+
Returns:
|
|
565
|
+
List of additional compiler arguments for GNU ABI
|
|
566
|
+
|
|
567
|
+
Raises:
|
|
568
|
+
RuntimeError: If MinGW sysroot installation fails or is not found
|
|
569
|
+
"""
|
|
570
|
+
if platform_name != "win":
|
|
571
|
+
return []
|
|
572
|
+
|
|
573
|
+
logger.info(f"Setting up GNU ABI for Windows {arch}")
|
|
574
|
+
|
|
575
|
+
# Ensure MinGW sysroot is installed
|
|
576
|
+
try:
|
|
577
|
+
sysroot_dir = downloader.ensure_mingw_sysroot_installed(platform_name, arch)
|
|
578
|
+
logger.debug(f"MinGW sysroot installed at: {sysroot_dir}")
|
|
579
|
+
except Exception as e:
|
|
580
|
+
logger.error(f"Failed to install MinGW sysroot: {e}")
|
|
581
|
+
raise RuntimeError(
|
|
582
|
+
f"Failed to install MinGW sysroot for Windows GNU ABI support\n"
|
|
583
|
+
f"Error: {e}\n"
|
|
584
|
+
f"\n"
|
|
585
|
+
f"This is required for GNU ABI support on Windows.\n"
|
|
586
|
+
f"If this persists, please report at:\n"
|
|
587
|
+
f"https://github.com/zackees/clang-tool-chain/issues"
|
|
588
|
+
) from e
|
|
589
|
+
|
|
590
|
+
# Determine target triple and sysroot path
|
|
591
|
+
if arch == "x86_64":
|
|
592
|
+
target = "x86_64-w64-mingw32"
|
|
593
|
+
elif arch == "arm64":
|
|
594
|
+
target = "aarch64-w64-mingw32"
|
|
595
|
+
else:
|
|
596
|
+
raise ValueError(f"Unsupported architecture for MinGW: {arch}")
|
|
597
|
+
|
|
598
|
+
# The sysroot is the directory containing include/ and the target subdirectory
|
|
599
|
+
sysroot_path = sysroot_dir
|
|
600
|
+
if not sysroot_path.exists():
|
|
601
|
+
logger.error(f"MinGW sysroot not found at expected location: {sysroot_path}")
|
|
602
|
+
raise RuntimeError(
|
|
603
|
+
f"MinGW sysroot not found: {sysroot_path}\n"
|
|
604
|
+
f"The sysroot was downloaded but the expected directory is missing.\n"
|
|
605
|
+
f"Please report this issue at:\n"
|
|
606
|
+
f"https://github.com/zackees/clang-tool-chain/issues"
|
|
607
|
+
)
|
|
608
|
+
|
|
609
|
+
logger.info(f"Using GNU target: {target} with sysroot: {sysroot_path}")
|
|
610
|
+
|
|
611
|
+
# Check if resource directory exists in the sysroot
|
|
612
|
+
# The archive should contain lib/clang/<version>/ with resource headers
|
|
613
|
+
resource_dir = sysroot_path / "lib" / "clang"
|
|
614
|
+
resource_dir_arg = []
|
|
615
|
+
if resource_dir.exists():
|
|
616
|
+
# Find the version directory (should be only one, e.g., "21")
|
|
617
|
+
version_dirs = [d for d in resource_dir.iterdir() if d.is_dir()]
|
|
618
|
+
if version_dirs:
|
|
619
|
+
# Use the first (and should be only) version directory
|
|
620
|
+
clang_version_dir = version_dirs[0]
|
|
621
|
+
resource_include = clang_version_dir / "include"
|
|
622
|
+
if resource_include.exists():
|
|
623
|
+
logger.info(f"Found clang resource directory at: {clang_version_dir}")
|
|
624
|
+
# Use -resource-dir to tell clang where to find its builtin headers
|
|
625
|
+
# This makes clang look in <resource-dir>/include/ for headers like stddef.h, mm_malloc.h
|
|
626
|
+
resource_dir_arg = [f"-resource-dir={clang_version_dir}"]
|
|
627
|
+
else:
|
|
628
|
+
logger.warning(f"Resource include directory not found: {resource_include}")
|
|
629
|
+
else:
|
|
630
|
+
logger.warning(f"No version directories found in: {resource_dir}")
|
|
631
|
+
else:
|
|
632
|
+
logger.warning(f"Resource directory not found: {resource_dir}")
|
|
633
|
+
|
|
634
|
+
# Add -stdlib=libc++ to use the libc++ standard library included in the sysroot
|
|
635
|
+
# Add -fuse-ld=lld to use LLVM's linker instead of system ld
|
|
636
|
+
# Add -rtlib=compiler-rt to use LLVM's compiler-rt instead of libgcc
|
|
637
|
+
# Add --unwindlib=libunwind to use LLVM's libunwind instead of libgcc_s
|
|
638
|
+
# Add -static-libgcc -static-libstdc++ to link runtime libraries statically
|
|
639
|
+
# This avoids DLL dependency issues at runtime
|
|
640
|
+
return [
|
|
641
|
+
f"--target={target}",
|
|
642
|
+
f"--sysroot={sysroot_path}",
|
|
643
|
+
"-stdlib=libc++",
|
|
644
|
+
"-rtlib=compiler-rt",
|
|
645
|
+
"-fuse-ld=lld",
|
|
646
|
+
"--unwindlib=libunwind",
|
|
647
|
+
"-static-libgcc",
|
|
648
|
+
"-static-libstdc++",
|
|
649
|
+
] + resource_dir_arg
|
|
650
|
+
|
|
651
|
+
|
|
652
|
+
def _should_use_msvc_abi(platform_name: str, args: list[str]) -> bool:
|
|
653
|
+
"""
|
|
654
|
+
Determine if MSVC ABI should be used based on platform and arguments.
|
|
655
|
+
|
|
656
|
+
MSVC ABI is explicitly requested via the *-msvc variant commands.
|
|
657
|
+
Unlike GNU ABI (which is the Windows default), MSVC ABI is opt-in.
|
|
658
|
+
|
|
659
|
+
This function checks if the user has explicitly provided a --target flag.
|
|
660
|
+
If so, we respect the user's choice and don't inject MSVC target.
|
|
661
|
+
|
|
662
|
+
Args:
|
|
663
|
+
platform_name: Platform name ("win", "linux", "darwin")
|
|
664
|
+
args: Command-line arguments
|
|
665
|
+
|
|
666
|
+
Returns:
|
|
667
|
+
True if MSVC ABI should be used (Windows + no explicit target), False otherwise
|
|
668
|
+
"""
|
|
669
|
+
# MSVC ABI only applies to Windows
|
|
670
|
+
if platform_name != "win":
|
|
671
|
+
logger.debug("Not Windows platform, MSVC ABI not applicable")
|
|
672
|
+
return False
|
|
673
|
+
|
|
674
|
+
# Check if user explicitly specified target
|
|
675
|
+
args_str = " ".join(args)
|
|
676
|
+
if "--target=" in args_str or "--target " in args_str:
|
|
677
|
+
# User specified target explicitly, don't override
|
|
678
|
+
logger.debug("User specified explicit target, skipping MSVC ABI injection")
|
|
679
|
+
return False
|
|
680
|
+
|
|
681
|
+
# MSVC variant was requested and no user override
|
|
682
|
+
logger.debug("MSVC ABI will be used (no user target override)")
|
|
683
|
+
return True
|
|
684
|
+
|
|
685
|
+
|
|
686
|
+
def _get_msvc_target_args(platform_name: str, arch: str) -> list[str]:
|
|
687
|
+
"""
|
|
688
|
+
Get MSVC ABI target arguments for Windows.
|
|
689
|
+
|
|
690
|
+
This function returns the necessary compiler arguments to use MSVC ABI
|
|
691
|
+
instead of GNU ABI. It also detects Windows SDK availability and shows
|
|
692
|
+
helpful warnings if the SDK is not found in environment variables.
|
|
693
|
+
|
|
694
|
+
Args:
|
|
695
|
+
platform_name: Platform name
|
|
696
|
+
arch: Architecture
|
|
697
|
+
|
|
698
|
+
Returns:
|
|
699
|
+
List of additional compiler arguments for MSVC ABI (just --target)
|
|
700
|
+
|
|
701
|
+
Note:
|
|
702
|
+
Unlike GNU ABI which requires downloading a MinGW sysroot, MSVC ABI
|
|
703
|
+
relies on the system's Visual Studio or Windows SDK installation.
|
|
704
|
+
We detect SDK presence via environment variables and warn if not found,
|
|
705
|
+
but still return the target triple and let clang attempt its own SDK detection.
|
|
706
|
+
"""
|
|
707
|
+
if platform_name != "win":
|
|
708
|
+
return []
|
|
709
|
+
|
|
710
|
+
logger.info(f"Setting up MSVC ABI for Windows {arch}")
|
|
711
|
+
|
|
712
|
+
# Detect Windows SDK and warn if not found
|
|
713
|
+
sdk_info = _detect_windows_sdk()
|
|
714
|
+
if sdk_info:
|
|
715
|
+
logger.info(f"Windows SDK detected with keys: {', '.join(sdk_info.keys())}")
|
|
716
|
+
else:
|
|
717
|
+
logger.warning("Windows SDK not detected in environment variables")
|
|
718
|
+
# Show helpful warning about SDK requirements
|
|
719
|
+
_print_msvc_sdk_warning()
|
|
720
|
+
|
|
721
|
+
# Determine target triple for MSVC ABI
|
|
722
|
+
if arch == "x86_64":
|
|
723
|
+
target = "x86_64-pc-windows-msvc"
|
|
724
|
+
elif arch == "arm64":
|
|
725
|
+
target = "aarch64-pc-windows-msvc"
|
|
726
|
+
else:
|
|
727
|
+
raise ValueError(f"Unsupported architecture for MSVC: {arch}")
|
|
728
|
+
|
|
729
|
+
logger.info(f"Using MSVC target: {target}")
|
|
730
|
+
|
|
731
|
+
# Return just the target triple
|
|
732
|
+
# Clang will automatically:
|
|
733
|
+
# - Select lld-link as the linker (MSVC-compatible)
|
|
734
|
+
# - Use MSVC name mangling for C++
|
|
735
|
+
# - Attempt to find Windows SDK via its own detection logic
|
|
736
|
+
return [f"--target={target}"]
|
|
737
|
+
|
|
738
|
+
|
|
739
|
+
def execute_tool(tool_name: str, args: list[str] | None = None, use_msvc: bool = False) -> NoReturn:
|
|
740
|
+
"""
|
|
741
|
+
Execute a tool with the given arguments and exit with its return code.
|
|
742
|
+
|
|
743
|
+
This function does not return - it replaces the current process with
|
|
744
|
+
the tool process (on Unix) or exits with the tool's return code (on Windows).
|
|
745
|
+
|
|
746
|
+
Args:
|
|
747
|
+
tool_name: Name of the tool to execute
|
|
748
|
+
args: Arguments to pass to the tool (defaults to sys.argv[1:])
|
|
749
|
+
use_msvc: If True on Windows, skip GNU ABI injection (use MSVC target)
|
|
750
|
+
|
|
751
|
+
Raises:
|
|
752
|
+
RuntimeError: If the tool cannot be found or executed
|
|
753
|
+
|
|
754
|
+
Environment Variables:
|
|
755
|
+
SDKROOT: Custom SDK path to use (macOS, standard macOS variable)
|
|
756
|
+
CLANG_TOOL_CHAIN_NO_SYSROOT: Set to '1' to disable automatic -isysroot injection (macOS)
|
|
757
|
+
"""
|
|
758
|
+
if args is None:
|
|
759
|
+
args = sys.argv[1:]
|
|
760
|
+
|
|
761
|
+
logger.info(f"Executing tool: {tool_name} with {len(args)} arguments")
|
|
762
|
+
logger.debug(f"Arguments: {args}")
|
|
763
|
+
|
|
764
|
+
try:
|
|
765
|
+
tool_path = find_tool_binary(tool_name)
|
|
766
|
+
except RuntimeError as e:
|
|
767
|
+
logger.error(f"Failed to find tool binary: {e}")
|
|
768
|
+
print(f"\n{'='*60}", file=sys.stderr)
|
|
769
|
+
print("clang-tool-chain Error", file=sys.stderr)
|
|
770
|
+
print(f"{'='*60}", file=sys.stderr)
|
|
771
|
+
print(f"{e}", file=sys.stderr)
|
|
772
|
+
print(f"{'='*60}\n", file=sys.stderr)
|
|
773
|
+
sys.exit(1)
|
|
774
|
+
|
|
775
|
+
# Add macOS SDK path automatically for clang/clang++ if not already specified
|
|
776
|
+
platform_name, arch = get_platform_info()
|
|
777
|
+
if platform_name == "darwin" and tool_name in ("clang", "clang++"):
|
|
778
|
+
logger.debug("Checking if macOS sysroot needs to be added")
|
|
779
|
+
args = _add_macos_sysroot_if_needed(args)
|
|
780
|
+
|
|
781
|
+
# Add Windows GNU ABI target automatically for clang/clang++ if not MSVC variant
|
|
782
|
+
if not use_msvc and tool_name in ("clang", "clang++") and _should_use_gnu_abi(platform_name, args):
|
|
783
|
+
try:
|
|
784
|
+
gnu_args = _get_gnu_target_args(platform_name, arch)
|
|
785
|
+
args = gnu_args + args
|
|
786
|
+
logger.info(f"Using GNU ABI with args: {gnu_args}")
|
|
787
|
+
except Exception as e:
|
|
788
|
+
# If GNU setup fails, let the tool try anyway (may fail at compile time)
|
|
789
|
+
logger.error(f"Failed to set up GNU ABI: {e}")
|
|
790
|
+
print(f"\nWarning: Failed to set up Windows GNU ABI: {e}", file=sys.stderr)
|
|
791
|
+
print("Continuing with default target (may fail)...\n", file=sys.stderr)
|
|
792
|
+
|
|
793
|
+
# Add Windows MSVC ABI target for clang/clang++ when using MSVC variant
|
|
794
|
+
if use_msvc and tool_name in ("clang", "clang++") and _should_use_msvc_abi(platform_name, args):
|
|
795
|
+
try:
|
|
796
|
+
msvc_args = _get_msvc_target_args(platform_name, arch)
|
|
797
|
+
args = msvc_args + args
|
|
798
|
+
logger.info(f"Using MSVC ABI with args: {msvc_args}")
|
|
799
|
+
except Exception as e:
|
|
800
|
+
# If MSVC setup fails, let the tool try anyway (may fail at compile time)
|
|
801
|
+
logger.error(f"Failed to set up MSVC ABI: {e}")
|
|
802
|
+
print(f"\nWarning: Failed to set up Windows MSVC ABI: {e}", file=sys.stderr)
|
|
803
|
+
print("Continuing with default target (may fail)...\n", file=sys.stderr)
|
|
804
|
+
|
|
805
|
+
# Build command
|
|
806
|
+
cmd = [str(tool_path)] + args
|
|
807
|
+
logger.info(f"Executing command: {tool_path} (with {len(args)} args)")
|
|
808
|
+
|
|
809
|
+
# On Unix systems, we can use exec to replace the current process
|
|
810
|
+
# On Windows, we need to use subprocess and exit with the return code
|
|
811
|
+
platform_name, _ = get_platform_info()
|
|
812
|
+
|
|
813
|
+
if platform_name == "win":
|
|
814
|
+
logger.debug("Using Windows subprocess execution")
|
|
815
|
+
# Windows: use subprocess
|
|
816
|
+
try:
|
|
817
|
+
result = subprocess.run(cmd)
|
|
818
|
+
sys.exit(result.returncode)
|
|
819
|
+
except FileNotFoundError:
|
|
820
|
+
print(f"\n{'='*60}", file=sys.stderr)
|
|
821
|
+
print("clang-tool-chain Error", file=sys.stderr)
|
|
822
|
+
print(f"{'='*60}", file=sys.stderr)
|
|
823
|
+
print(f"Tool not found: {tool_path}", file=sys.stderr)
|
|
824
|
+
print("\nThe binary exists in the package but cannot be executed.", file=sys.stderr)
|
|
825
|
+
print("This may be a permission or compatibility issue.", file=sys.stderr)
|
|
826
|
+
print("\nTroubleshooting:", file=sys.stderr)
|
|
827
|
+
print(" - Verify the binary is compatible with your Windows version", file=sys.stderr)
|
|
828
|
+
print(" - Check Windows Defender or antivirus isn't blocking it", file=sys.stderr)
|
|
829
|
+
print(" - Report issue: https://github.com/zackees/clang-tool-chain/issues", file=sys.stderr)
|
|
830
|
+
print(f"{'='*60}\n", file=sys.stderr)
|
|
831
|
+
sys.exit(1)
|
|
832
|
+
except Exception as e:
|
|
833
|
+
print(f"\n{'='*60}", file=sys.stderr)
|
|
834
|
+
print("clang-tool-chain Error", file=sys.stderr)
|
|
835
|
+
print(f"{'='*60}", file=sys.stderr)
|
|
836
|
+
print(f"Error executing tool: {e}", file=sys.stderr)
|
|
837
|
+
print(f"\nUnexpected error while running: {tool_path}", file=sys.stderr)
|
|
838
|
+
print(f"Arguments: {args}", file=sys.stderr)
|
|
839
|
+
print("\nPlease report this issue at:", file=sys.stderr)
|
|
840
|
+
print("https://github.com/zackees/clang-tool-chain/issues", file=sys.stderr)
|
|
841
|
+
print(f"{'='*60}\n", file=sys.stderr)
|
|
842
|
+
sys.exit(1)
|
|
843
|
+
else:
|
|
844
|
+
logger.debug("Using Unix exec replacement")
|
|
845
|
+
# Unix: use exec to replace current process
|
|
846
|
+
try:
|
|
847
|
+
logger.info(f"Replacing process with: {tool_path}")
|
|
848
|
+
os.execv(str(tool_path), cmd)
|
|
849
|
+
except FileNotFoundError:
|
|
850
|
+
print(f"\n{'='*60}", file=sys.stderr)
|
|
851
|
+
print("clang-tool-chain Error", file=sys.stderr)
|
|
852
|
+
print(f"{'='*60}", file=sys.stderr)
|
|
853
|
+
print(f"Tool not found: {tool_path}", file=sys.stderr)
|
|
854
|
+
print("\nThe binary exists in the package but cannot be executed.", file=sys.stderr)
|
|
855
|
+
print("This may be a permission or compatibility issue.", file=sys.stderr)
|
|
856
|
+
print("\nTroubleshooting:", file=sys.stderr)
|
|
857
|
+
print(f" - Check file permissions: chmod +x {tool_path}", file=sys.stderr)
|
|
858
|
+
print(" - Verify the binary is compatible with your system", file=sys.stderr)
|
|
859
|
+
print(" - On macOS: Right-click > Open, then allow in Security settings", file=sys.stderr)
|
|
860
|
+
print(" - Report issue: https://github.com/zackees/clang-tool-chain/issues", file=sys.stderr)
|
|
861
|
+
print(f"{'='*60}\n", file=sys.stderr)
|
|
862
|
+
sys.exit(1)
|
|
863
|
+
except Exception as e:
|
|
864
|
+
print(f"\n{'='*60}", file=sys.stderr)
|
|
865
|
+
print("clang-tool-chain Error", file=sys.stderr)
|
|
866
|
+
print(f"{'='*60}", file=sys.stderr)
|
|
867
|
+
print(f"Error executing tool: {e}", file=sys.stderr)
|
|
868
|
+
print(f"\nUnexpected error while running: {tool_path}", file=sys.stderr)
|
|
869
|
+
print(f"Arguments: {args}", file=sys.stderr)
|
|
870
|
+
print("\nPlease report this issue at:", file=sys.stderr)
|
|
871
|
+
print("https://github.com/zackees/clang-tool-chain/issues", file=sys.stderr)
|
|
872
|
+
print(f"{'='*60}\n", file=sys.stderr)
|
|
873
|
+
sys.exit(1)
|
|
874
|
+
|
|
875
|
+
|
|
876
|
+
def run_tool(tool_name: str, args: list[str] | None = None, use_msvc: bool = False) -> int:
|
|
877
|
+
"""
|
|
878
|
+
Run a tool with the given arguments and return its exit code.
|
|
879
|
+
|
|
880
|
+
Unlike execute_tool, this function returns to the caller with the
|
|
881
|
+
tool's exit code instead of exiting the process.
|
|
882
|
+
|
|
883
|
+
Args:
|
|
884
|
+
tool_name: Name of the tool to execute
|
|
885
|
+
args: Arguments to pass to the tool (defaults to sys.argv[1:])
|
|
886
|
+
use_msvc: If True on Windows, skip GNU ABI injection (use MSVC target)
|
|
887
|
+
|
|
888
|
+
Returns:
|
|
889
|
+
Exit code from the tool
|
|
890
|
+
|
|
891
|
+
Raises:
|
|
892
|
+
RuntimeError: If the tool cannot be found
|
|
893
|
+
|
|
894
|
+
Environment Variables:
|
|
895
|
+
SDKROOT: Custom SDK path to use (macOS, standard macOS variable)
|
|
896
|
+
CLANG_TOOL_CHAIN_NO_SYSROOT: Set to '1' to disable automatic -isysroot injection (macOS)
|
|
897
|
+
"""
|
|
898
|
+
if args is None:
|
|
899
|
+
args = sys.argv[1:]
|
|
900
|
+
|
|
901
|
+
tool_path = find_tool_binary(tool_name)
|
|
902
|
+
|
|
903
|
+
# Add macOS SDK path automatically for clang/clang++ if not already specified
|
|
904
|
+
platform_name, arch = get_platform_info()
|
|
905
|
+
if platform_name == "darwin" and tool_name in ("clang", "clang++"):
|
|
906
|
+
logger.debug("Checking if macOS sysroot needs to be added")
|
|
907
|
+
args = _add_macos_sysroot_if_needed(args)
|
|
908
|
+
|
|
909
|
+
# Add Windows GNU ABI target automatically for clang/clang++ if not MSVC variant
|
|
910
|
+
if not use_msvc and tool_name in ("clang", "clang++") and _should_use_gnu_abi(platform_name, args):
|
|
911
|
+
try:
|
|
912
|
+
gnu_args = _get_gnu_target_args(platform_name, arch)
|
|
913
|
+
args = gnu_args + args
|
|
914
|
+
logger.info(f"Using GNU ABI with args: {gnu_args}")
|
|
915
|
+
except Exception as e:
|
|
916
|
+
# If GNU setup fails, let the tool try anyway (may fail at compile time)
|
|
917
|
+
logger.error(f"Failed to set up GNU ABI: {e}")
|
|
918
|
+
print(f"\nWarning: Failed to set up Windows GNU ABI: {e}", file=sys.stderr)
|
|
919
|
+
print("Continuing with default target (may fail)...\n", file=sys.stderr)
|
|
920
|
+
|
|
921
|
+
# Add Windows MSVC ABI target for clang/clang++ when using MSVC variant
|
|
922
|
+
if use_msvc and tool_name in ("clang", "clang++") and _should_use_msvc_abi(platform_name, args):
|
|
923
|
+
try:
|
|
924
|
+
msvc_args = _get_msvc_target_args(platform_name, arch)
|
|
925
|
+
args = msvc_args + args
|
|
926
|
+
logger.info(f"Using MSVC ABI with args: {msvc_args}")
|
|
927
|
+
except Exception as e:
|
|
928
|
+
# If MSVC setup fails, let the tool try anyway (may fail at compile time)
|
|
929
|
+
logger.error(f"Failed to set up MSVC ABI: {e}")
|
|
930
|
+
print(f"\nWarning: Failed to set up Windows MSVC ABI: {e}", file=sys.stderr)
|
|
931
|
+
print("Continuing with default target (may fail)...\n", file=sys.stderr)
|
|
932
|
+
|
|
933
|
+
# Build command
|
|
934
|
+
cmd = [str(tool_path)] + args
|
|
935
|
+
|
|
936
|
+
# Run the tool
|
|
937
|
+
try:
|
|
938
|
+
result = subprocess.run(cmd)
|
|
939
|
+
return result.returncode
|
|
940
|
+
except FileNotFoundError as err:
|
|
941
|
+
raise RuntimeError(f"Tool not found: {tool_path}") from err
|
|
942
|
+
except Exception as e:
|
|
943
|
+
raise RuntimeError(f"Error executing tool: {e}") from e
|
|
944
|
+
|
|
945
|
+
|
|
946
|
+
# Wrapper functions for specific tools
|
|
947
|
+
def clang_main() -> NoReturn:
|
|
948
|
+
"""Entry point for clang wrapper (GNU ABI on Windows by default)."""
|
|
949
|
+
execute_tool("clang")
|
|
950
|
+
|
|
951
|
+
|
|
952
|
+
def clang_cpp_main() -> NoReturn:
|
|
953
|
+
"""Entry point for clang++ wrapper (GNU ABI on Windows by default)."""
|
|
954
|
+
execute_tool("clang++")
|
|
955
|
+
|
|
956
|
+
|
|
957
|
+
def clang_msvc_main() -> NoReturn:
|
|
958
|
+
"""Entry point for clang-tool-chain-c-msvc (MSVC ABI on Windows)."""
|
|
959
|
+
execute_tool("clang", use_msvc=True)
|
|
960
|
+
|
|
961
|
+
|
|
962
|
+
def clang_cpp_msvc_main() -> NoReturn:
|
|
963
|
+
"""Entry point for clang-tool-chain-cpp-msvc (MSVC ABI on Windows)."""
|
|
964
|
+
execute_tool("clang++", use_msvc=True)
|
|
965
|
+
|
|
966
|
+
|
|
967
|
+
def lld_main() -> NoReturn:
|
|
968
|
+
"""Entry point for lld linker wrapper."""
|
|
969
|
+
platform_name, _ = get_platform_info()
|
|
970
|
+
if platform_name == "win":
|
|
971
|
+
execute_tool("lld-link")
|
|
972
|
+
else:
|
|
973
|
+
execute_tool("lld")
|
|
974
|
+
|
|
975
|
+
|
|
976
|
+
def llvm_ar_main() -> NoReturn:
|
|
977
|
+
"""Entry point for llvm-ar wrapper."""
|
|
978
|
+
execute_tool("llvm-ar")
|
|
979
|
+
|
|
980
|
+
|
|
981
|
+
def llvm_nm_main() -> NoReturn:
|
|
982
|
+
"""Entry point for llvm-nm wrapper."""
|
|
983
|
+
execute_tool("llvm-nm")
|
|
984
|
+
|
|
985
|
+
|
|
986
|
+
def llvm_objdump_main() -> NoReturn:
|
|
987
|
+
"""Entry point for llvm-objdump wrapper."""
|
|
988
|
+
execute_tool("llvm-objdump")
|
|
989
|
+
|
|
990
|
+
|
|
991
|
+
def llvm_objcopy_main() -> NoReturn:
|
|
992
|
+
"""Entry point for llvm-objcopy wrapper."""
|
|
993
|
+
execute_tool("llvm-objcopy")
|
|
994
|
+
|
|
995
|
+
|
|
996
|
+
def llvm_ranlib_main() -> NoReturn:
|
|
997
|
+
"""Entry point for llvm-ranlib wrapper."""
|
|
998
|
+
execute_tool("llvm-ranlib")
|
|
999
|
+
|
|
1000
|
+
|
|
1001
|
+
def llvm_strip_main() -> NoReturn:
|
|
1002
|
+
"""Entry point for llvm-strip wrapper."""
|
|
1003
|
+
execute_tool("llvm-strip")
|
|
1004
|
+
|
|
1005
|
+
|
|
1006
|
+
def llvm_readelf_main() -> NoReturn:
|
|
1007
|
+
"""Entry point for llvm-readelf wrapper."""
|
|
1008
|
+
execute_tool("llvm-readelf")
|
|
1009
|
+
|
|
1010
|
+
|
|
1011
|
+
def llvm_as_main() -> NoReturn:
|
|
1012
|
+
"""Entry point for llvm-as wrapper."""
|
|
1013
|
+
execute_tool("llvm-as")
|
|
1014
|
+
|
|
1015
|
+
|
|
1016
|
+
def llvm_dis_main() -> NoReturn:
|
|
1017
|
+
"""Entry point for llvm-dis wrapper."""
|
|
1018
|
+
execute_tool("llvm-dis")
|
|
1019
|
+
|
|
1020
|
+
|
|
1021
|
+
def clang_format_main() -> NoReturn:
|
|
1022
|
+
"""Entry point for clang-format wrapper."""
|
|
1023
|
+
execute_tool("clang-format")
|
|
1024
|
+
|
|
1025
|
+
|
|
1026
|
+
def clang_tidy_main() -> NoReturn:
|
|
1027
|
+
"""Entry point for clang-tidy wrapper."""
|
|
1028
|
+
execute_tool("clang-tidy")
|
|
1029
|
+
|
|
1030
|
+
|
|
1031
|
+
def build_main() -> NoReturn:
|
|
1032
|
+
"""
|
|
1033
|
+
Entry point for build wrapper.
|
|
1034
|
+
|
|
1035
|
+
Simple build utility that compiles and links a C/C++ source file to an executable.
|
|
1036
|
+
|
|
1037
|
+
Usage:
|
|
1038
|
+
clang-tool-chain-build <source_file> <output_file> [additional_args...]
|
|
1039
|
+
|
|
1040
|
+
Examples:
|
|
1041
|
+
clang-tool-chain-build main.cpp main.exe
|
|
1042
|
+
clang-tool-chain-build main.c main -O2
|
|
1043
|
+
clang-tool-chain-build main.cpp app.exe -std=c++17 -Wall
|
|
1044
|
+
"""
|
|
1045
|
+
args = sys.argv[1:]
|
|
1046
|
+
|
|
1047
|
+
if len(args) < 2:
|
|
1048
|
+
print("\n" + "=" * 60, file=sys.stderr)
|
|
1049
|
+
print("clang-tool-chain-build - Build Utility", file=sys.stderr)
|
|
1050
|
+
print("=" * 60, file=sys.stderr)
|
|
1051
|
+
print("Usage: clang-tool-chain-build <source_file> <output_file> [compiler_flags...]", file=sys.stderr)
|
|
1052
|
+
print("\nExamples:", file=sys.stderr)
|
|
1053
|
+
print(" clang-tool-chain-build main.cpp main.exe", file=sys.stderr)
|
|
1054
|
+
print(" clang-tool-chain-build main.c main -O2", file=sys.stderr)
|
|
1055
|
+
print(" clang-tool-chain-build main.cpp app.exe -std=c++17 -Wall", file=sys.stderr)
|
|
1056
|
+
print("\nArguments:", file=sys.stderr)
|
|
1057
|
+
print(" source_file - C/C++ source file to compile (.c, .cpp, .cc, .cxx)", file=sys.stderr)
|
|
1058
|
+
print(" output_file - Output executable file", file=sys.stderr)
|
|
1059
|
+
print(" compiler_flags - Optional additional compiler flags", file=sys.stderr)
|
|
1060
|
+
print("=" * 60 + "\n", file=sys.stderr)
|
|
1061
|
+
sys.exit(1)
|
|
1062
|
+
|
|
1063
|
+
source_file = args[0]
|
|
1064
|
+
output_file = args[1]
|
|
1065
|
+
additional_flags = args[2:] if len(args) > 2 else []
|
|
1066
|
+
|
|
1067
|
+
# Determine if this is C or C++ based on file extension
|
|
1068
|
+
source_path = Path(source_file)
|
|
1069
|
+
cpp_extensions = {".cpp", ".cc", ".cxx", ".C", ".c++"}
|
|
1070
|
+
is_cpp = source_path.suffix.lower() in cpp_extensions
|
|
1071
|
+
|
|
1072
|
+
# Choose the appropriate compiler
|
|
1073
|
+
compiler = "clang++" if is_cpp else "clang"
|
|
1074
|
+
|
|
1075
|
+
# Build the compiler command
|
|
1076
|
+
compiler_args = [source_file, "-o", output_file] + additional_flags
|
|
1077
|
+
|
|
1078
|
+
# Execute the compiler
|
|
1079
|
+
execute_tool(compiler, compiler_args)
|
|
1080
|
+
|
|
1081
|
+
|
|
1082
|
+
def _compute_file_hash(file_path: Path) -> str:
|
|
1083
|
+
"""
|
|
1084
|
+
Compute SHA256 hash of a file.
|
|
1085
|
+
|
|
1086
|
+
Args:
|
|
1087
|
+
file_path: Path to the file to hash
|
|
1088
|
+
|
|
1089
|
+
Returns:
|
|
1090
|
+
Hexadecimal string representation of the SHA256 hash
|
|
1091
|
+
"""
|
|
1092
|
+
sha256_hash = hashlib.sha256()
|
|
1093
|
+
with open(file_path, "rb") as f:
|
|
1094
|
+
# Read in chunks to handle large files efficiently
|
|
1095
|
+
for byte_block in iter(lambda: f.read(4096), b""):
|
|
1096
|
+
sha256_hash.update(byte_block)
|
|
1097
|
+
return sha256_hash.hexdigest()
|
|
1098
|
+
|
|
1099
|
+
|
|
1100
|
+
def build_run_main() -> NoReturn:
|
|
1101
|
+
"""
|
|
1102
|
+
Entry point for build-run wrapper.
|
|
1103
|
+
|
|
1104
|
+
Simple build-and-run utility that:
|
|
1105
|
+
1. Takes a C/C++ source file (e.g., src.cpp)
|
|
1106
|
+
2. Compiles it to an executable (src or src.exe)
|
|
1107
|
+
3. Runs the executable
|
|
1108
|
+
|
|
1109
|
+
With --cached flag:
|
|
1110
|
+
1. Computes hash of source file
|
|
1111
|
+
2. Checks if cached hash matches (stored in src.hash)
|
|
1112
|
+
3. Skips compilation if hash matches and executable exists
|
|
1113
|
+
4. Runs the executable
|
|
1114
|
+
|
|
1115
|
+
Usage:
|
|
1116
|
+
clang-tool-chain-build-run [--cached] <source_file> [compiler_flags...] [-- program_args...]
|
|
1117
|
+
|
|
1118
|
+
Examples:
|
|
1119
|
+
clang-tool-chain-build-run main.cpp
|
|
1120
|
+
clang-tool-chain-build-run --cached main.c
|
|
1121
|
+
clang-tool-chain-build-run --cached main.cpp -O2
|
|
1122
|
+
clang-tool-chain-build-run main.cpp -std=c++17 -Wall
|
|
1123
|
+
clang-tool-chain-build-run --cached main.cpp -- arg1 arg2 # Pass args to program
|
|
1124
|
+
"""
|
|
1125
|
+
args = sys.argv[1:]
|
|
1126
|
+
|
|
1127
|
+
if len(args) < 1:
|
|
1128
|
+
print("\n" + "=" * 60, file=sys.stderr)
|
|
1129
|
+
print("clang-tool-chain-build-run - Build and Run Utility", file=sys.stderr)
|
|
1130
|
+
print("=" * 60, file=sys.stderr)
|
|
1131
|
+
print(
|
|
1132
|
+
"Usage: clang-tool-chain-build-run [--cached] <source_file> [compiler_flags...] [-- program_args...]",
|
|
1133
|
+
file=sys.stderr,
|
|
1134
|
+
)
|
|
1135
|
+
print("\nExamples:", file=sys.stderr)
|
|
1136
|
+
print(" clang-tool-chain-build-run main.cpp", file=sys.stderr)
|
|
1137
|
+
print(" clang-tool-chain-build-run --cached main.c", file=sys.stderr)
|
|
1138
|
+
print(" clang-tool-chain-build-run --cached main.cpp -O2", file=sys.stderr)
|
|
1139
|
+
print(" clang-tool-chain-build-run main.cpp -std=c++17 -Wall", file=sys.stderr)
|
|
1140
|
+
print(" clang-tool-chain-build-run --cached main.cpp -- arg1 arg2 # Pass args to program", file=sys.stderr)
|
|
1141
|
+
print("\nBehavior:", file=sys.stderr)
|
|
1142
|
+
print(" - Compiles source_file to an executable with the same base name", file=sys.stderr)
|
|
1143
|
+
print(" - On Windows: src.cpp -> src.exe", file=sys.stderr)
|
|
1144
|
+
print(" - On Unix: src.cpp -> src", file=sys.stderr)
|
|
1145
|
+
print(" - Runs the executable immediately after successful compilation", file=sys.stderr)
|
|
1146
|
+
print(" - Use '--' to separate compiler flags from program arguments", file=sys.stderr)
|
|
1147
|
+
print("\nCaching (--cached flag):", file=sys.stderr)
|
|
1148
|
+
print(" - Computes SHA256 hash of source file", file=sys.stderr)
|
|
1149
|
+
print(" - Stores hash in src.hash file", file=sys.stderr)
|
|
1150
|
+
print(" - Skips compilation if hash matches and executable exists", file=sys.stderr)
|
|
1151
|
+
print(" - Useful for quick development iterations", file=sys.stderr)
|
|
1152
|
+
print("\nArguments:", file=sys.stderr)
|
|
1153
|
+
print(" --cached - Enable hash-based compilation caching", file=sys.stderr)
|
|
1154
|
+
print(" source_file - C/C++ source file to compile (.c, .cpp, .cc, .cxx)", file=sys.stderr)
|
|
1155
|
+
print(" compiler_flags - Optional compiler flags (before '--')", file=sys.stderr)
|
|
1156
|
+
print(" program_args - Optional arguments to pass to the program (after '--')", file=sys.stderr)
|
|
1157
|
+
print("=" * 60 + "\n", file=sys.stderr)
|
|
1158
|
+
sys.exit(1)
|
|
1159
|
+
|
|
1160
|
+
# Check for --cached flag
|
|
1161
|
+
use_cache = False
|
|
1162
|
+
if args[0] == "--cached":
|
|
1163
|
+
use_cache = True
|
|
1164
|
+
args = args[1:]
|
|
1165
|
+
|
|
1166
|
+
if len(args) < 1:
|
|
1167
|
+
print("Error: source_file is required", file=sys.stderr)
|
|
1168
|
+
sys.exit(1)
|
|
1169
|
+
|
|
1170
|
+
# Split args into compiler flags and program args
|
|
1171
|
+
if "--" in args:
|
|
1172
|
+
separator_idx = args.index("--")
|
|
1173
|
+
compile_args = args[:separator_idx]
|
|
1174
|
+
program_args = args[separator_idx + 1 :]
|
|
1175
|
+
else:
|
|
1176
|
+
compile_args = args
|
|
1177
|
+
program_args = []
|
|
1178
|
+
|
|
1179
|
+
source_file = compile_args[0]
|
|
1180
|
+
compiler_flags = compile_args[1:] if len(compile_args) > 1 else []
|
|
1181
|
+
|
|
1182
|
+
# Determine output executable name from source file
|
|
1183
|
+
source_path = Path(source_file)
|
|
1184
|
+
|
|
1185
|
+
if not source_path.exists():
|
|
1186
|
+
print(f"Error: Source file not found: {source_file}", file=sys.stderr)
|
|
1187
|
+
sys.exit(1)
|
|
1188
|
+
|
|
1189
|
+
platform_name, _ = get_platform_info()
|
|
1190
|
+
|
|
1191
|
+
# Generate output filename: src.cpp -> src (or src.exe on Windows)
|
|
1192
|
+
if platform_name == "win":
|
|
1193
|
+
output_file = str(source_path.with_suffix(".exe"))
|
|
1194
|
+
else:
|
|
1195
|
+
output_file = str(source_path.with_suffix(""))
|
|
1196
|
+
|
|
1197
|
+
output_path = Path(output_file)
|
|
1198
|
+
hash_file = source_path.with_suffix(".hash")
|
|
1199
|
+
|
|
1200
|
+
# Determine if this is C or C++ based on file extension
|
|
1201
|
+
cpp_extensions = {".cpp", ".cc", ".cxx", ".C", ".c++"}
|
|
1202
|
+
is_cpp = source_path.suffix.lower() in cpp_extensions
|
|
1203
|
+
|
|
1204
|
+
# Choose the appropriate compiler
|
|
1205
|
+
compiler = "clang++" if is_cpp else "clang"
|
|
1206
|
+
|
|
1207
|
+
# Check cache if enabled
|
|
1208
|
+
should_compile = True
|
|
1209
|
+
if use_cache:
|
|
1210
|
+
print(f"Checking cache for {source_file}...", file=sys.stderr)
|
|
1211
|
+
|
|
1212
|
+
# Compute current hash
|
|
1213
|
+
current_hash = _compute_file_hash(source_path)
|
|
1214
|
+
|
|
1215
|
+
# Check if hash file exists and matches
|
|
1216
|
+
if hash_file.exists() and output_path.exists():
|
|
1217
|
+
try:
|
|
1218
|
+
stored_hash = hash_file.read_text().strip()
|
|
1219
|
+
if stored_hash == current_hash:
|
|
1220
|
+
print(f"Cache hit! Hash matches, skipping compilation.", file=sys.stderr)
|
|
1221
|
+
print(f"Using cached executable: {output_file}", file=sys.stderr)
|
|
1222
|
+
should_compile = False
|
|
1223
|
+
else:
|
|
1224
|
+
print(f"Cache miss: Hash mismatch, recompiling...", file=sys.stderr)
|
|
1225
|
+
except Exception as e:
|
|
1226
|
+
print(f"Warning: Could not read hash file: {e}", file=sys.stderr)
|
|
1227
|
+
print(f"Recompiling...", file=sys.stderr)
|
|
1228
|
+
else:
|
|
1229
|
+
if not output_path.exists():
|
|
1230
|
+
print(f"Cache miss: Executable not found, compiling...", file=sys.stderr)
|
|
1231
|
+
else:
|
|
1232
|
+
print(f"Cache miss: No hash file found, compiling...", file=sys.stderr)
|
|
1233
|
+
|
|
1234
|
+
# Compile if needed
|
|
1235
|
+
if should_compile:
|
|
1236
|
+
# Build the compiler command
|
|
1237
|
+
compiler_args = [source_file, "-o", output_file] + compiler_flags
|
|
1238
|
+
|
|
1239
|
+
print(f"Compiling: {source_file} -> {output_file}", file=sys.stderr)
|
|
1240
|
+
|
|
1241
|
+
# Run the compiler (returns exit code instead of calling sys.exit)
|
|
1242
|
+
exit_code = run_tool(compiler, compiler_args)
|
|
1243
|
+
|
|
1244
|
+
if exit_code != 0:
|
|
1245
|
+
print(f"\n{'='*60}", file=sys.stderr)
|
|
1246
|
+
print("Compilation failed", file=sys.stderr)
|
|
1247
|
+
print(f"{'='*60}\n", file=sys.stderr)
|
|
1248
|
+
sys.exit(exit_code)
|
|
1249
|
+
|
|
1250
|
+
# Update hash file if caching is enabled
|
|
1251
|
+
if use_cache:
|
|
1252
|
+
try:
|
|
1253
|
+
current_hash = _compute_file_hash(source_path)
|
|
1254
|
+
hash_file.write_text(current_hash)
|
|
1255
|
+
print(f"Updated cache hash: {hash_file}", file=sys.stderr)
|
|
1256
|
+
except Exception as e:
|
|
1257
|
+
print(f"Warning: Could not write hash file: {e}", file=sys.stderr)
|
|
1258
|
+
|
|
1259
|
+
print(f"\nRunning: {output_file}", file=sys.stderr)
|
|
1260
|
+
if program_args:
|
|
1261
|
+
print(f"Program arguments: {' '.join(program_args)}", file=sys.stderr)
|
|
1262
|
+
print("=" * 60, file=sys.stderr)
|
|
1263
|
+
|
|
1264
|
+
# Run the compiled executable
|
|
1265
|
+
try:
|
|
1266
|
+
# Use absolute path for Windows compatibility
|
|
1267
|
+
abs_output = output_path.absolute()
|
|
1268
|
+
result = subprocess.run([str(abs_output)] + program_args)
|
|
1269
|
+
sys.exit(result.returncode)
|
|
1270
|
+
except FileNotFoundError:
|
|
1271
|
+
print(f"\n{'='*60}", file=sys.stderr)
|
|
1272
|
+
print("Execution Error", file=sys.stderr)
|
|
1273
|
+
print(f"{'='*60}", file=sys.stderr)
|
|
1274
|
+
print(f"Compiled executable not found: {output_file}", file=sys.stderr)
|
|
1275
|
+
print("\nThe compilation appeared to succeed, but the output file cannot be found.", file=sys.stderr)
|
|
1276
|
+
print(f"{'='*60}\n", file=sys.stderr)
|
|
1277
|
+
sys.exit(1)
|
|
1278
|
+
except Exception as e:
|
|
1279
|
+
print(f"\n{'='*60}", file=sys.stderr)
|
|
1280
|
+
print("Execution Error", file=sys.stderr)
|
|
1281
|
+
print(f"{'='*60}", file=sys.stderr)
|
|
1282
|
+
print(f"Error running {output_file}: {e}", file=sys.stderr)
|
|
1283
|
+
print(f"{'='*60}\n", file=sys.stderr)
|
|
1284
|
+
sys.exit(1)
|
|
1285
|
+
|
|
1286
|
+
|
|
1287
|
+
# sccache wrapper functions
|
|
1288
|
+
def sccache_clang_main(use_msvc: bool = False) -> NoReturn:
|
|
1289
|
+
"""
|
|
1290
|
+
Entry point for sccache + clang wrapper.
|
|
1291
|
+
|
|
1292
|
+
Args:
|
|
1293
|
+
use_msvc: If True on Windows, use MSVC ABI instead of GNU ABI
|
|
1294
|
+
"""
|
|
1295
|
+
args = sys.argv[1:]
|
|
1296
|
+
|
|
1297
|
+
try:
|
|
1298
|
+
sccache_path = find_sccache_binary()
|
|
1299
|
+
clang_path = find_tool_binary("clang")
|
|
1300
|
+
except RuntimeError as e:
|
|
1301
|
+
print(f"\n{'='*60}", file=sys.stderr)
|
|
1302
|
+
print("clang-tool-chain Error", file=sys.stderr)
|
|
1303
|
+
print(f"{'='*60}", file=sys.stderr)
|
|
1304
|
+
print(f"{e}", file=sys.stderr)
|
|
1305
|
+
print(f"{'='*60}\n", file=sys.stderr)
|
|
1306
|
+
sys.exit(1)
|
|
1307
|
+
|
|
1308
|
+
# Add macOS SDK path automatically if needed
|
|
1309
|
+
platform_name, arch = get_platform_info()
|
|
1310
|
+
if platform_name == "darwin":
|
|
1311
|
+
args = _add_macos_sysroot_if_needed(args)
|
|
1312
|
+
|
|
1313
|
+
# Add Windows GNU ABI target automatically (if not using MSVC variant)
|
|
1314
|
+
if not use_msvc and _should_use_gnu_abi(platform_name, args):
|
|
1315
|
+
try:
|
|
1316
|
+
gnu_args = _get_gnu_target_args(platform_name, arch)
|
|
1317
|
+
args = gnu_args + args
|
|
1318
|
+
logger.info(f"Using GNU ABI with sccache: {gnu_args}")
|
|
1319
|
+
except Exception as e:
|
|
1320
|
+
logger.error(f"Failed to set up GNU ABI: {e}")
|
|
1321
|
+
print(f"\nWarning: Failed to set up Windows GNU ABI: {e}", file=sys.stderr)
|
|
1322
|
+
print("Continuing with default target (may fail)...\n", file=sys.stderr)
|
|
1323
|
+
|
|
1324
|
+
# Add Windows MSVC ABI target when using MSVC variant
|
|
1325
|
+
if use_msvc and _should_use_msvc_abi(platform_name, args):
|
|
1326
|
+
try:
|
|
1327
|
+
msvc_args = _get_msvc_target_args(platform_name, arch)
|
|
1328
|
+
args = msvc_args + args
|
|
1329
|
+
logger.info(f"Using MSVC ABI with sccache: {msvc_args}")
|
|
1330
|
+
except Exception as e:
|
|
1331
|
+
logger.error(f"Failed to set up MSVC ABI: {e}")
|
|
1332
|
+
print(f"\nWarning: Failed to set up Windows MSVC ABI: {e}", file=sys.stderr)
|
|
1333
|
+
print("Continuing with default target (may fail)...\n", file=sys.stderr)
|
|
1334
|
+
|
|
1335
|
+
# Build command: sccache <clang_path> <args>
|
|
1336
|
+
cmd = [sccache_path, str(clang_path)] + args
|
|
1337
|
+
|
|
1338
|
+
# Execute with platform-appropriate method
|
|
1339
|
+
platform_name, _ = get_platform_info()
|
|
1340
|
+
|
|
1341
|
+
if platform_name == "win":
|
|
1342
|
+
# Windows: use subprocess
|
|
1343
|
+
try:
|
|
1344
|
+
result = subprocess.run(cmd)
|
|
1345
|
+
sys.exit(result.returncode)
|
|
1346
|
+
except Exception as e:
|
|
1347
|
+
print(f"\n{'='*60}", file=sys.stderr)
|
|
1348
|
+
print("clang-tool-chain Error", file=sys.stderr)
|
|
1349
|
+
print(f"{'='*60}", file=sys.stderr)
|
|
1350
|
+
print(f"Error executing sccache: {e}", file=sys.stderr)
|
|
1351
|
+
print(f"{'='*60}\n", file=sys.stderr)
|
|
1352
|
+
sys.exit(1)
|
|
1353
|
+
else:
|
|
1354
|
+
# Unix: use exec to replace current process
|
|
1355
|
+
try:
|
|
1356
|
+
os.execv(sccache_path, cmd)
|
|
1357
|
+
except Exception as e:
|
|
1358
|
+
print(f"\n{'='*60}", file=sys.stderr)
|
|
1359
|
+
print("clang-tool-chain Error", file=sys.stderr)
|
|
1360
|
+
print(f"{'='*60}", file=sys.stderr)
|
|
1361
|
+
print(f"Error executing sccache: {e}", file=sys.stderr)
|
|
1362
|
+
print(f"{'='*60}\n", file=sys.stderr)
|
|
1363
|
+
sys.exit(1)
|
|
1364
|
+
|
|
1365
|
+
|
|
1366
|
+
def sccache_clang_cpp_main(use_msvc: bool = False) -> NoReturn:
|
|
1367
|
+
"""
|
|
1368
|
+
Entry point for sccache + clang++ wrapper.
|
|
1369
|
+
|
|
1370
|
+
Args:
|
|
1371
|
+
use_msvc: If True on Windows, use MSVC ABI instead of GNU ABI
|
|
1372
|
+
"""
|
|
1373
|
+
args = sys.argv[1:]
|
|
1374
|
+
|
|
1375
|
+
try:
|
|
1376
|
+
sccache_path = find_sccache_binary()
|
|
1377
|
+
clang_cpp_path = find_tool_binary("clang++")
|
|
1378
|
+
except RuntimeError as e:
|
|
1379
|
+
print(f"\n{'='*60}", file=sys.stderr)
|
|
1380
|
+
print("clang-tool-chain Error", file=sys.stderr)
|
|
1381
|
+
print(f"{'='*60}", file=sys.stderr)
|
|
1382
|
+
print(f"{e}", file=sys.stderr)
|
|
1383
|
+
print(f"{'='*60}\n", file=sys.stderr)
|
|
1384
|
+
sys.exit(1)
|
|
1385
|
+
|
|
1386
|
+
# Add macOS SDK path automatically if needed
|
|
1387
|
+
platform_name, arch = get_platform_info()
|
|
1388
|
+
if platform_name == "darwin":
|
|
1389
|
+
args = _add_macos_sysroot_if_needed(args)
|
|
1390
|
+
|
|
1391
|
+
# Add Windows GNU ABI target automatically (if not using MSVC variant)
|
|
1392
|
+
if not use_msvc and _should_use_gnu_abi(platform_name, args):
|
|
1393
|
+
try:
|
|
1394
|
+
gnu_args = _get_gnu_target_args(platform_name, arch)
|
|
1395
|
+
args = gnu_args + args
|
|
1396
|
+
logger.info(f"Using GNU ABI with sccache: {gnu_args}")
|
|
1397
|
+
except Exception as e:
|
|
1398
|
+
logger.error(f"Failed to set up GNU ABI: {e}")
|
|
1399
|
+
print(f"\nWarning: Failed to set up Windows GNU ABI: {e}", file=sys.stderr)
|
|
1400
|
+
print("Continuing with default target (may fail)...\n", file=sys.stderr)
|
|
1401
|
+
|
|
1402
|
+
# Add Windows MSVC ABI target when using MSVC variant
|
|
1403
|
+
if use_msvc and _should_use_msvc_abi(platform_name, args):
|
|
1404
|
+
try:
|
|
1405
|
+
msvc_args = _get_msvc_target_args(platform_name, arch)
|
|
1406
|
+
args = msvc_args + args
|
|
1407
|
+
logger.info(f"Using MSVC ABI with sccache: {msvc_args}")
|
|
1408
|
+
except Exception as e:
|
|
1409
|
+
logger.error(f"Failed to set up MSVC ABI: {e}")
|
|
1410
|
+
print(f"\nWarning: Failed to set up Windows MSVC ABI: {e}", file=sys.stderr)
|
|
1411
|
+
print("Continuing with default target (may fail)...\n", file=sys.stderr)
|
|
1412
|
+
|
|
1413
|
+
# Build command: sccache <clang++_path> <args>
|
|
1414
|
+
cmd = [sccache_path, str(clang_cpp_path)] + args
|
|
1415
|
+
|
|
1416
|
+
# Execute with platform-appropriate method
|
|
1417
|
+
platform_name, _ = get_platform_info()
|
|
1418
|
+
|
|
1419
|
+
if platform_name == "win":
|
|
1420
|
+
# Windows: use subprocess
|
|
1421
|
+
try:
|
|
1422
|
+
result = subprocess.run(cmd)
|
|
1423
|
+
sys.exit(result.returncode)
|
|
1424
|
+
except Exception as e:
|
|
1425
|
+
print(f"\n{'='*60}", file=sys.stderr)
|
|
1426
|
+
print("clang-tool-chain Error", file=sys.stderr)
|
|
1427
|
+
print(f"{'='*60}", file=sys.stderr)
|
|
1428
|
+
print(f"Error executing sccache: {e}", file=sys.stderr)
|
|
1429
|
+
print(f"{'='*60}\n", file=sys.stderr)
|
|
1430
|
+
sys.exit(1)
|
|
1431
|
+
else:
|
|
1432
|
+
# Unix: use exec to replace current process
|
|
1433
|
+
try:
|
|
1434
|
+
os.execv(sccache_path, cmd)
|
|
1435
|
+
except Exception as e:
|
|
1436
|
+
print(f"\n{'='*60}", file=sys.stderr)
|
|
1437
|
+
print("clang-tool-chain Error", file=sys.stderr)
|
|
1438
|
+
print(f"{'='*60}", file=sys.stderr)
|
|
1439
|
+
print(f"Error executing sccache: {e}", file=sys.stderr)
|
|
1440
|
+
print(f"{'='*60}\n", file=sys.stderr)
|
|
1441
|
+
sys.exit(1)
|
|
1442
|
+
|
|
1443
|
+
|
|
1444
|
+
# ============================================================================
|
|
1445
|
+
# IWYU (Include What You Use) Support
|
|
1446
|
+
# ============================================================================
|
|
1447
|
+
|
|
1448
|
+
|
|
1449
|
+
def get_iwyu_binary_dir() -> Path:
|
|
1450
|
+
"""
|
|
1451
|
+
Get the binary directory for IWYU.
|
|
1452
|
+
|
|
1453
|
+
Returns:
|
|
1454
|
+
Path to the IWYU binary directory
|
|
1455
|
+
|
|
1456
|
+
Raises:
|
|
1457
|
+
RuntimeError: If binary directory is not found
|
|
1458
|
+
"""
|
|
1459
|
+
platform_name, arch = get_platform_info()
|
|
1460
|
+
logger.info(f"Getting IWYU binary directory for {platform_name}/{arch}")
|
|
1461
|
+
|
|
1462
|
+
# Ensure IWYU is downloaded and installed
|
|
1463
|
+
logger.info(f"Ensuring IWYU is available for {platform_name}/{arch}")
|
|
1464
|
+
downloader.ensure_iwyu(platform_name, arch)
|
|
1465
|
+
|
|
1466
|
+
# Get the installation directory
|
|
1467
|
+
install_dir = downloader.get_iwyu_install_dir(platform_name, arch)
|
|
1468
|
+
bin_dir = install_dir / "bin"
|
|
1469
|
+
logger.debug(f"IWYU binary directory: {bin_dir}")
|
|
1470
|
+
|
|
1471
|
+
if not bin_dir.exists():
|
|
1472
|
+
logger.error(f"IWYU binary directory does not exist: {bin_dir}")
|
|
1473
|
+
raise RuntimeError(
|
|
1474
|
+
f"IWYU binaries not found for {platform_name}-{arch}\n"
|
|
1475
|
+
f"Expected location: {bin_dir}\n"
|
|
1476
|
+
f"\n"
|
|
1477
|
+
f"The IWYU download may have failed. Please try again or report this issue at:\n"
|
|
1478
|
+
f"https://github.com/zackees/clang-tool-chain/issues"
|
|
1479
|
+
)
|
|
1480
|
+
|
|
1481
|
+
logger.info(f"IWYU binary directory found: {bin_dir}")
|
|
1482
|
+
return bin_dir
|
|
1483
|
+
|
|
1484
|
+
|
|
1485
|
+
def find_iwyu_tool(tool_name: str) -> Path:
|
|
1486
|
+
"""
|
|
1487
|
+
Find the path to an IWYU tool.
|
|
1488
|
+
|
|
1489
|
+
Args:
|
|
1490
|
+
tool_name: Name of the tool (e.g., "include-what-you-use", "iwyu_tool.py")
|
|
1491
|
+
|
|
1492
|
+
Returns:
|
|
1493
|
+
Path to the tool
|
|
1494
|
+
|
|
1495
|
+
Raises:
|
|
1496
|
+
RuntimeError: If the tool is not found
|
|
1497
|
+
"""
|
|
1498
|
+
logger.info(f"Finding IWYU tool: {tool_name}")
|
|
1499
|
+
bin_dir = get_iwyu_binary_dir()
|
|
1500
|
+
platform_name, _ = get_platform_info()
|
|
1501
|
+
|
|
1502
|
+
# Add .exe extension on Windows for the binary
|
|
1503
|
+
if tool_name == "include-what-you-use" and platform_name == "win":
|
|
1504
|
+
tool_path = bin_dir / f"{tool_name}.exe"
|
|
1505
|
+
else:
|
|
1506
|
+
tool_path = bin_dir / tool_name
|
|
1507
|
+
|
|
1508
|
+
logger.debug(f"Looking for IWYU tool at: {tool_path}")
|
|
1509
|
+
|
|
1510
|
+
if not tool_path.exists():
|
|
1511
|
+
logger.error(f"IWYU tool not found: {tool_path}")
|
|
1512
|
+
# List available tools
|
|
1513
|
+
available_tools = [f.name for f in bin_dir.iterdir() if f.is_file()]
|
|
1514
|
+
raise RuntimeError(
|
|
1515
|
+
f"IWYU tool '{tool_name}' not found at: {tool_path}\n"
|
|
1516
|
+
f"Available tools in {bin_dir}:\n"
|
|
1517
|
+
f" {', '.join(available_tools)}"
|
|
1518
|
+
)
|
|
1519
|
+
|
|
1520
|
+
logger.info(f"Found IWYU tool: {tool_path}")
|
|
1521
|
+
return tool_path
|
|
1522
|
+
|
|
1523
|
+
|
|
1524
|
+
def execute_iwyu_tool(tool_name: str, args: list[str] | None = None) -> NoReturn:
|
|
1525
|
+
"""
|
|
1526
|
+
Execute an IWYU tool with the given arguments.
|
|
1527
|
+
|
|
1528
|
+
Args:
|
|
1529
|
+
tool_name: Name of the IWYU tool
|
|
1530
|
+
args: Command-line arguments (default: sys.argv[1:])
|
|
1531
|
+
|
|
1532
|
+
Raises:
|
|
1533
|
+
SystemExit: Always exits with the tool's return code
|
|
1534
|
+
"""
|
|
1535
|
+
if args is None:
|
|
1536
|
+
args = sys.argv[1:]
|
|
1537
|
+
|
|
1538
|
+
tool_path = find_iwyu_tool(tool_name)
|
|
1539
|
+
platform_name, _ = get_platform_info()
|
|
1540
|
+
|
|
1541
|
+
# For Python scripts, we need to run them with Python
|
|
1542
|
+
if tool_name.endswith(".py"):
|
|
1543
|
+
# Find Python executable
|
|
1544
|
+
python_exe = sys.executable
|
|
1545
|
+
cmd = [python_exe, str(tool_path)] + args
|
|
1546
|
+
else:
|
|
1547
|
+
cmd = [str(tool_path)] + args
|
|
1548
|
+
|
|
1549
|
+
logger.info(f"Executing IWYU tool: {' '.join(cmd)}")
|
|
1550
|
+
|
|
1551
|
+
# Execute tool
|
|
1552
|
+
if platform_name == "win":
|
|
1553
|
+
# Windows: use subprocess
|
|
1554
|
+
try:
|
|
1555
|
+
result = subprocess.run(cmd)
|
|
1556
|
+
sys.exit(result.returncode)
|
|
1557
|
+
except FileNotFoundError as err:
|
|
1558
|
+
raise RuntimeError(f"IWYU tool not found: {tool_path}") from err
|
|
1559
|
+
except Exception as e:
|
|
1560
|
+
raise RuntimeError(f"Error executing IWYU tool: {e}") from e
|
|
1561
|
+
else:
|
|
1562
|
+
# Unix: use exec to replace current process
|
|
1563
|
+
try:
|
|
1564
|
+
if tool_name.endswith(".py"):
|
|
1565
|
+
# For Python scripts, we can't use execv directly
|
|
1566
|
+
result = subprocess.run(cmd)
|
|
1567
|
+
sys.exit(result.returncode)
|
|
1568
|
+
else:
|
|
1569
|
+
os.execv(cmd[0], cmd)
|
|
1570
|
+
except FileNotFoundError as err:
|
|
1571
|
+
raise RuntimeError(f"IWYU tool not found: {tool_path}") from err
|
|
1572
|
+
except Exception as e:
|
|
1573
|
+
raise RuntimeError(f"Error executing IWYU tool: {e}") from e
|
|
1574
|
+
|
|
1575
|
+
|
|
1576
|
+
# IWYU wrapper entry points
|
|
1577
|
+
def iwyu_main() -> NoReturn:
|
|
1578
|
+
"""Entry point for include-what-you-use wrapper."""
|
|
1579
|
+
execute_iwyu_tool("include-what-you-use")
|
|
1580
|
+
|
|
1581
|
+
|
|
1582
|
+
def iwyu_tool_main() -> NoReturn:
|
|
1583
|
+
"""Entry point for iwyu_tool.py wrapper."""
|
|
1584
|
+
execute_iwyu_tool("iwyu_tool.py")
|
|
1585
|
+
|
|
1586
|
+
|
|
1587
|
+
def fix_includes_main() -> NoReturn:
|
|
1588
|
+
"""Entry point for fix_includes.py wrapper."""
|
|
1589
|
+
execute_iwyu_tool("fix_includes.py")
|