clang-tool-chain 1.0.42__py3-none-any.whl → 1.0.43__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/__version__.py +1 -1
- clang_tool_chain/execution/__init__.py +5 -0
- clang_tool_chain/execution/build_pipeline.py +8 -2
- clang_tool_chain/execution/sanitizer_env.py +120 -0
- clang_tool_chain/linker/lld.py +16 -98
- clang_tool_chain/llvm_versions.py +101 -0
- {clang_tool_chain-1.0.42.dist-info → clang_tool_chain-1.0.43.dist-info}/METADATA +53 -1
- {clang_tool_chain-1.0.42.dist-info → clang_tool_chain-1.0.43.dist-info}/RECORD +11 -9
- {clang_tool_chain-1.0.42.dist-info → clang_tool_chain-1.0.43.dist-info}/WHEEL +0 -0
- {clang_tool_chain-1.0.42.dist-info → clang_tool_chain-1.0.43.dist-info}/entry_points.txt +0 -0
- {clang_tool_chain-1.0.42.dist-info → clang_tool_chain-1.0.43.dist-info}/licenses/LICENSE +0 -0
clang_tool_chain/__version__.py
CHANGED
|
@@ -5,10 +5,12 @@ This package provides:
|
|
|
5
5
|
- Core tool execution: execute_tool, run_tool
|
|
6
6
|
- Build utilities: build_main, build_run_main
|
|
7
7
|
- sccache wrappers for compilation caching
|
|
8
|
+
- Sanitizer environment configuration
|
|
8
9
|
"""
|
|
9
10
|
|
|
10
11
|
from clang_tool_chain.execution.build import build_main, build_run_main
|
|
11
12
|
from clang_tool_chain.execution.core import execute_tool, run_tool, sccache_clang_cpp_main, sccache_clang_main
|
|
13
|
+
from clang_tool_chain.execution.sanitizer_env import detect_sanitizers_from_flags, prepare_sanitizer_environment
|
|
12
14
|
|
|
13
15
|
__all__ = [
|
|
14
16
|
# Core execution
|
|
@@ -20,4 +22,7 @@ __all__ = [
|
|
|
20
22
|
# sccache wrappers
|
|
21
23
|
"sccache_clang_main",
|
|
22
24
|
"sccache_clang_cpp_main",
|
|
25
|
+
# Sanitizer utilities
|
|
26
|
+
"prepare_sanitizer_environment",
|
|
27
|
+
"detect_sanitizers_from_flags",
|
|
23
28
|
]
|
|
@@ -137,7 +137,7 @@ def _get_directive_args(source_path: Path) -> list[str]:
|
|
|
137
137
|
print(f" Effective args: {' '.join(all_args)}", file=sys.stderr)
|
|
138
138
|
|
|
139
139
|
return all_args
|
|
140
|
-
except KeyboardInterrupt:
|
|
140
|
+
except KeyboardInterrupt:
|
|
141
141
|
# Re-raise KeyboardInterrupt to allow clean exit
|
|
142
142
|
raise
|
|
143
143
|
except Exception as e:
|
|
@@ -249,6 +249,8 @@ class BuildPipeline(ABC):
|
|
|
249
249
|
Raises:
|
|
250
250
|
SystemExit: Always exits with the executable's return code
|
|
251
251
|
"""
|
|
252
|
+
from clang_tool_chain.execution.sanitizer_env import prepare_sanitizer_environment
|
|
253
|
+
|
|
252
254
|
program_args = self.config.program_args or []
|
|
253
255
|
|
|
254
256
|
print(f"\nRunning: {self.config.output_file}", file=sys.stderr)
|
|
@@ -256,11 +258,15 @@ class BuildPipeline(ABC):
|
|
|
256
258
|
print(f"Program arguments: {' '.join(program_args)}", file=sys.stderr)
|
|
257
259
|
print("=" * 60, file=sys.stderr)
|
|
258
260
|
|
|
261
|
+
# Prepare environment with sanitizer options for better stack traces
|
|
262
|
+
# Only inject options if the corresponding sanitizer was used during compilation
|
|
263
|
+
env = prepare_sanitizer_environment(compiler_flags=self.config.compiler_flags)
|
|
264
|
+
|
|
259
265
|
# Run the compiled executable
|
|
260
266
|
try:
|
|
261
267
|
# Use absolute path for Windows compatibility
|
|
262
268
|
abs_output = self.output_path.absolute()
|
|
263
|
-
result = subprocess.run([str(abs_output)] + program_args)
|
|
269
|
+
result = subprocess.run([str(abs_output)] + program_args, env=env)
|
|
264
270
|
sys.exit(result.returncode)
|
|
265
271
|
except FileNotFoundError:
|
|
266
272
|
print(f"\n{'=' * 60}", file=sys.stderr)
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Sanitizer runtime environment configuration.
|
|
3
|
+
|
|
4
|
+
This module provides automatic injection of ASAN_OPTIONS and LSAN_OPTIONS
|
|
5
|
+
environment variables to improve stack trace quality when running executables
|
|
6
|
+
compiled with Address Sanitizer or Leak Sanitizer.
|
|
7
|
+
|
|
8
|
+
The default options fix <unknown module> entries in stack traces from
|
|
9
|
+
dlopen()'d shared libraries by enabling slow unwinding and symbolization.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import logging
|
|
13
|
+
import os
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
# Default options to inject for optimal stack traces
|
|
18
|
+
# fast_unwind_on_malloc=0: Use slow but accurate unwinding (fixes <unknown module>)
|
|
19
|
+
# symbolize=1: Enable symbolization for readable stack traces
|
|
20
|
+
# detect_leaks=1: Enable leak detection (ASAN only)
|
|
21
|
+
DEFAULT_ASAN_OPTIONS = "fast_unwind_on_malloc=0:symbolize=1:detect_leaks=1"
|
|
22
|
+
DEFAULT_LSAN_OPTIONS = "fast_unwind_on_malloc=0:symbolize=1"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def detect_sanitizers_from_flags(compiler_flags: list[str]) -> tuple[bool, bool]:
|
|
26
|
+
"""
|
|
27
|
+
Detect which sanitizers are enabled from compiler flags.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
compiler_flags: List of compiler flags passed to clang.
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
Tuple of (asan_enabled, lsan_enabled).
|
|
34
|
+
|
|
35
|
+
Example:
|
|
36
|
+
>>> detect_sanitizers_from_flags(["-fsanitize=address", "-O2"])
|
|
37
|
+
(True, True) # ASAN implies LSAN by default
|
|
38
|
+
>>> detect_sanitizers_from_flags(["-fsanitize=leak"])
|
|
39
|
+
(False, True)
|
|
40
|
+
>>> detect_sanitizers_from_flags(["-O2", "-Wall"])
|
|
41
|
+
(False, False)
|
|
42
|
+
"""
|
|
43
|
+
asan_enabled = False
|
|
44
|
+
lsan_enabled = False
|
|
45
|
+
|
|
46
|
+
for flag in compiler_flags:
|
|
47
|
+
if flag.startswith("-fsanitize="):
|
|
48
|
+
# Extract sanitizer list (e.g., "-fsanitize=address,undefined" -> "address,undefined")
|
|
49
|
+
sanitizers = flag.split("=", 1)[1].split(",")
|
|
50
|
+
for sanitizer in sanitizers:
|
|
51
|
+
sanitizer = sanitizer.strip()
|
|
52
|
+
if sanitizer == "address":
|
|
53
|
+
asan_enabled = True
|
|
54
|
+
# ASAN includes LSAN by default (unless detect_leaks=0)
|
|
55
|
+
lsan_enabled = True
|
|
56
|
+
elif sanitizer == "leak":
|
|
57
|
+
lsan_enabled = True
|
|
58
|
+
|
|
59
|
+
return asan_enabled, lsan_enabled
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def prepare_sanitizer_environment(
|
|
63
|
+
base_env: dict[str, str] | None = None,
|
|
64
|
+
compiler_flags: list[str] | None = None,
|
|
65
|
+
) -> dict[str, str]:
|
|
66
|
+
"""
|
|
67
|
+
Prepare environment with optimal sanitizer options.
|
|
68
|
+
|
|
69
|
+
This function injects ASAN_OPTIONS and/or LSAN_OPTIONS environment variables
|
|
70
|
+
if they are not already set by the user AND the corresponding sanitizer was
|
|
71
|
+
enabled during compilation. The injected options improve stack trace quality
|
|
72
|
+
for executables using dlopen()'d shared libraries.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
base_env: Base environment dictionary to modify. If None, uses os.environ.
|
|
76
|
+
compiler_flags: List of compiler flags used to build the executable.
|
|
77
|
+
Used to detect which sanitizers are enabled. If None, no options
|
|
78
|
+
are injected (safe default).
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
Environment dictionary with sanitizer options injected as appropriate.
|
|
82
|
+
|
|
83
|
+
Environment Variables:
|
|
84
|
+
CLANG_TOOL_CHAIN_NO_SANITIZER_ENV: Set to "1", "true", or "yes" to
|
|
85
|
+
disable automatic injection of sanitizer options.
|
|
86
|
+
ASAN_OPTIONS: If already set, preserved as-is (user config takes priority).
|
|
87
|
+
LSAN_OPTIONS: If already set, preserved as-is (user config takes priority).
|
|
88
|
+
|
|
89
|
+
Example:
|
|
90
|
+
>>> env = prepare_sanitizer_environment(compiler_flags=["-fsanitize=address"])
|
|
91
|
+
>>> # env now contains ASAN_OPTIONS and LSAN_OPTIONS
|
|
92
|
+
>>> env = prepare_sanitizer_environment(compiler_flags=["-O2"])
|
|
93
|
+
>>> # env unchanged - no sanitizers enabled
|
|
94
|
+
"""
|
|
95
|
+
env = base_env.copy() if base_env is not None else os.environ.copy()
|
|
96
|
+
|
|
97
|
+
# Check if disabled via environment variable
|
|
98
|
+
if os.environ.get("CLANG_TOOL_CHAIN_NO_SANITIZER_ENV", "").lower() in ("1", "true", "yes"):
|
|
99
|
+
logger.debug("Sanitizer environment injection disabled via CLANG_TOOL_CHAIN_NO_SANITIZER_ENV")
|
|
100
|
+
return env
|
|
101
|
+
|
|
102
|
+
# If no compiler flags provided, don't inject anything (safe default)
|
|
103
|
+
if compiler_flags is None:
|
|
104
|
+
logger.debug("No compiler flags provided, skipping sanitizer environment injection")
|
|
105
|
+
return env
|
|
106
|
+
|
|
107
|
+
# Detect which sanitizers are enabled
|
|
108
|
+
asan_enabled, lsan_enabled = detect_sanitizers_from_flags(compiler_flags)
|
|
109
|
+
|
|
110
|
+
# Inject ASAN_OPTIONS if ASAN is enabled and not already set by user
|
|
111
|
+
if asan_enabled and "ASAN_OPTIONS" not in env:
|
|
112
|
+
env["ASAN_OPTIONS"] = DEFAULT_ASAN_OPTIONS
|
|
113
|
+
logger.info(f"Injecting ASAN_OPTIONS={DEFAULT_ASAN_OPTIONS}")
|
|
114
|
+
|
|
115
|
+
# Inject LSAN_OPTIONS if LSAN is enabled and not already set by user
|
|
116
|
+
if lsan_enabled and "LSAN_OPTIONS" not in env:
|
|
117
|
+
env["LSAN_OPTIONS"] = DEFAULT_LSAN_OPTIONS
|
|
118
|
+
logger.info(f"Injecting LSAN_OPTIONS={DEFAULT_LSAN_OPTIONS}")
|
|
119
|
+
|
|
120
|
+
return env
|
clang_tool_chain/linker/lld.py
CHANGED
|
@@ -6,129 +6,47 @@ This module provides functions for:
|
|
|
6
6
|
- Translating GNU ld flags to ld64.lld equivalents on macOS
|
|
7
7
|
- Managing linker selection based on platform and user preferences
|
|
8
8
|
- Ensuring ld64.lld symlink exists on macOS (runtime fallback)
|
|
9
|
-
- LLVM version detection with caching for compatibility decisions
|
|
10
9
|
"""
|
|
11
10
|
|
|
12
11
|
import logging
|
|
13
12
|
import os
|
|
14
|
-
import re
|
|
15
|
-
import subprocess
|
|
16
13
|
import sys
|
|
17
14
|
|
|
18
15
|
from clang_tool_chain.interrupt_utils import handle_keyboard_interrupt_properly
|
|
16
|
+
from clang_tool_chain.llvm_versions import get_llvm_version_tuple, supports_ld64_lld_flag
|
|
19
17
|
|
|
20
18
|
logger = logging.getLogger(__name__)
|
|
21
19
|
|
|
22
|
-
# Module-level cache for LLVM version (avoids repeated subprocess calls)
|
|
23
|
-
_cached_llvm_version: tuple[int, int, int] | None = None
|
|
24
20
|
|
|
25
|
-
|
|
26
|
-
def _get_llvm_version() -> tuple[int, int, int] | None:
|
|
21
|
+
def _get_llvm_version_for_platform() -> tuple[int, int, int]:
|
|
27
22
|
"""
|
|
28
|
-
Get the LLVM version
|
|
29
|
-
|
|
30
|
-
This runs `clang --version` and parses the version number. The result
|
|
31
|
-
is cached in memory for the duration of the process, and also cached
|
|
32
|
-
to a file in the install directory for persistence across invocations.
|
|
23
|
+
Get the LLVM version for the current platform from centralized configuration.
|
|
33
24
|
|
|
34
25
|
Returns:
|
|
35
|
-
Tuple of (major, minor, patch) version numbers
|
|
26
|
+
Tuple of (major, minor, patch) version numbers.
|
|
36
27
|
"""
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
# Return memory-cached version if available
|
|
40
|
-
if _cached_llvm_version is not None:
|
|
41
|
-
return _cached_llvm_version
|
|
42
|
-
|
|
43
|
-
# Try to read from file cache
|
|
44
|
-
try:
|
|
45
|
-
from ..platform.detection import get_platform_binary_dir
|
|
46
|
-
|
|
47
|
-
bin_dir = get_platform_binary_dir()
|
|
48
|
-
cache_file = bin_dir.parent / ".llvm_version_cache"
|
|
49
|
-
|
|
50
|
-
if cache_file.exists():
|
|
51
|
-
cached = cache_file.read_text().strip()
|
|
52
|
-
parts = cached.split(".")
|
|
53
|
-
if len(parts) >= 3:
|
|
54
|
-
_cached_llvm_version = (int(parts[0]), int(parts[1]), int(parts[2]))
|
|
55
|
-
logger.debug(f"Loaded LLVM version from cache: {_cached_llvm_version}")
|
|
56
|
-
return _cached_llvm_version
|
|
57
|
-
except KeyboardInterrupt as ke:
|
|
58
|
-
handle_keyboard_interrupt_properly(ke)
|
|
59
|
-
except Exception as e:
|
|
60
|
-
logger.debug(f"Could not read LLVM version cache: {e}")
|
|
61
|
-
|
|
62
|
-
# Detect version by running clang --version
|
|
63
|
-
try:
|
|
64
|
-
from ..platform.detection import get_platform_binary_dir
|
|
28
|
+
from ..platform.detection import get_platform_info
|
|
65
29
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
if not clang_path.exists():
|
|
70
|
-
logger.debug(f"Clang binary not found at {clang_path}")
|
|
71
|
-
return None
|
|
72
|
-
|
|
73
|
-
result = subprocess.run(
|
|
74
|
-
[str(clang_path), "--version"],
|
|
75
|
-
capture_output=True,
|
|
76
|
-
text=True,
|
|
77
|
-
timeout=10,
|
|
78
|
-
)
|
|
79
|
-
|
|
80
|
-
if result.returncode != 0:
|
|
81
|
-
logger.debug(f"clang --version failed: {result.stderr}")
|
|
82
|
-
return None
|
|
83
|
-
|
|
84
|
-
# Parse version from output like "clang version 19.1.7 (...)"
|
|
85
|
-
version_match = re.search(r"clang version (\d+)\.(\d+)\.(\d+)", result.stdout)
|
|
86
|
-
if version_match:
|
|
87
|
-
major, minor, patch = int(version_match.group(1)), int(version_match.group(2)), int(version_match.group(3))
|
|
88
|
-
_cached_llvm_version = (major, minor, patch)
|
|
89
|
-
logger.debug(f"Detected LLVM version: {_cached_llvm_version}")
|
|
90
|
-
|
|
91
|
-
# Save to file cache for persistence
|
|
92
|
-
try:
|
|
93
|
-
cache_file = bin_dir.parent / ".llvm_version_cache"
|
|
94
|
-
cache_file.write_text(f"{major}.{minor}.{patch}")
|
|
95
|
-
logger.debug(f"Saved LLVM version to cache: {cache_file}")
|
|
96
|
-
except KeyboardInterrupt as ke:
|
|
97
|
-
handle_keyboard_interrupt_properly(ke)
|
|
98
|
-
except Exception as e:
|
|
99
|
-
logger.debug(f"Could not write LLVM version cache: {e}")
|
|
100
|
-
|
|
101
|
-
return _cached_llvm_version
|
|
102
|
-
|
|
103
|
-
logger.debug(f"Could not parse LLVM version from: {result.stdout[:200]}")
|
|
104
|
-
return None
|
|
105
|
-
|
|
106
|
-
except KeyboardInterrupt as ke:
|
|
107
|
-
handle_keyboard_interrupt_properly(ke)
|
|
108
|
-
except Exception as e:
|
|
109
|
-
logger.debug(f"LLVM version detection failed: {e}")
|
|
110
|
-
return None
|
|
30
|
+
platform_name, _ = get_platform_info()
|
|
31
|
+
return get_llvm_version_tuple(platform_name)
|
|
111
32
|
|
|
112
33
|
|
|
113
34
|
def _llvm_supports_ld64_lld_flag() -> bool:
|
|
114
35
|
"""
|
|
115
|
-
Check if the
|
|
36
|
+
Check if the current platform's LLVM version supports -fuse-ld=ld64.lld.
|
|
116
37
|
|
|
117
38
|
The -fuse-ld=ld64.lld flag is only recognized by LLVM 21.x and later.
|
|
118
39
|
Earlier versions require -fuse-ld=lld (which auto-detects Mach-O from target).
|
|
119
40
|
|
|
120
41
|
Returns:
|
|
121
|
-
True if LLVM >= 21.x, False otherwise
|
|
42
|
+
True if LLVM >= 21.x, False otherwise.
|
|
122
43
|
"""
|
|
123
|
-
|
|
124
|
-
if version is None:
|
|
125
|
-
# If we can't detect version, assume older LLVM for safety
|
|
126
|
-
logger.debug("Could not detect LLVM version, assuming < 21.x for ld64.lld compatibility")
|
|
127
|
-
return False
|
|
44
|
+
from ..platform.detection import get_platform_info
|
|
128
45
|
|
|
129
|
-
|
|
130
|
-
supports =
|
|
131
|
-
|
|
46
|
+
platform_name, _ = get_platform_info()
|
|
47
|
+
supports = supports_ld64_lld_flag(platform_name)
|
|
48
|
+
version = get_llvm_version_tuple(platform_name)
|
|
49
|
+
logger.debug(f"LLVM {version[0]}.x {'supports' if supports else 'does not support'} -fuse-ld=ld64.lld")
|
|
132
50
|
return supports
|
|
133
51
|
|
|
134
52
|
|
|
@@ -410,8 +328,8 @@ def _add_lld_linker_if_needed(platform_name: str, args: list[str]) -> list[str]:
|
|
|
410
328
|
return ["-fuse-ld=ld64.lld"] + args
|
|
411
329
|
else:
|
|
412
330
|
# LLVM < 21.x: rewrite to -fuse-ld=lld with compatibility notice
|
|
413
|
-
version =
|
|
414
|
-
version_str = f"{version[0]}.{version[1]}.{version[2]}"
|
|
331
|
+
version = _get_llvm_version_for_platform()
|
|
332
|
+
version_str = f"{version[0]}.{version[1]}.{version[2]}"
|
|
415
333
|
print(
|
|
416
334
|
f"[clang-tool-chain] LLVM {version_str} does not support -fuse-ld=ld64.lld, "
|
|
417
335
|
f"using -fuse-ld=lld instead (lld auto-detects Mach-O from target)",
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Centralized LLVM version configuration for clang-tool-chain.
|
|
3
|
+
|
|
4
|
+
This module provides a single source of truth for LLVM versions used by the toolchain.
|
|
5
|
+
When updating LLVM versions, only this file needs to be modified.
|
|
6
|
+
|
|
7
|
+
Version history:
|
|
8
|
+
- 21.1.6: macOS (darwin) x86_64 and arm64
|
|
9
|
+
- 21.1.5: Windows x86_64, Linux x86_64 and arm64
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from typing import NamedTuple
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class LLVMVersion(NamedTuple):
|
|
16
|
+
"""LLVM version as a tuple of (major, minor, patch)."""
|
|
17
|
+
|
|
18
|
+
major: int
|
|
19
|
+
minor: int
|
|
20
|
+
patch: int
|
|
21
|
+
|
|
22
|
+
def __str__(self) -> str:
|
|
23
|
+
return f"{self.major}.{self.minor}.{self.patch}"
|
|
24
|
+
|
|
25
|
+
@classmethod
|
|
26
|
+
def from_string(cls, version_str: str) -> "LLVMVersion":
|
|
27
|
+
"""Parse a version string like '21.1.6' into an LLVMVersion."""
|
|
28
|
+
parts = version_str.split(".")
|
|
29
|
+
if len(parts) != 3:
|
|
30
|
+
raise ValueError(f"Invalid version string: {version_str}")
|
|
31
|
+
return cls(int(parts[0]), int(parts[1]), int(parts[2]))
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
# Centralized LLVM version configuration by platform
|
|
35
|
+
# Format: {platform_name: LLVMVersion}
|
|
36
|
+
# Platform names match detection.py: "darwin", "linux", "win"
|
|
37
|
+
LLVM_VERSIONS: dict[str, LLVMVersion] = {
|
|
38
|
+
"darwin": LLVMVersion(21, 1, 6), # macOS x86_64 and arm64
|
|
39
|
+
"linux": LLVMVersion(21, 1, 5), # Linux x86_64 and arm64
|
|
40
|
+
"win": LLVMVersion(21, 1, 5), # Windows x86_64
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
# Default version when platform is unknown (should not happen in practice)
|
|
44
|
+
DEFAULT_LLVM_VERSION = LLVMVersion(21, 1, 5)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def get_llvm_version(platform_name: str) -> LLVMVersion:
|
|
48
|
+
"""
|
|
49
|
+
Get the LLVM version for a specific platform.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
platform_name: Platform name ("darwin", "linux", "win")
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
LLVMVersion tuple for the platform
|
|
56
|
+
"""
|
|
57
|
+
return LLVM_VERSIONS.get(platform_name, DEFAULT_LLVM_VERSION)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def get_llvm_version_string(platform_name: str) -> str:
|
|
61
|
+
"""
|
|
62
|
+
Get the LLVM version string for a specific platform.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
platform_name: Platform name ("darwin", "linux", "win")
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
Version string like "21.1.6"
|
|
69
|
+
"""
|
|
70
|
+
return str(get_llvm_version(platform_name))
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def get_llvm_version_tuple(platform_name: str) -> tuple[int, int, int]:
|
|
74
|
+
"""
|
|
75
|
+
Get the LLVM version as a tuple for a specific platform.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
platform_name: Platform name ("darwin", "linux", "win")
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
Version tuple like (21, 1, 6)
|
|
82
|
+
"""
|
|
83
|
+
version = get_llvm_version(platform_name)
|
|
84
|
+
return (version.major, version.minor, version.patch)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def supports_ld64_lld_flag(platform_name: str) -> bool:
|
|
88
|
+
"""
|
|
89
|
+
Check if the platform's LLVM version supports -fuse-ld=ld64.lld.
|
|
90
|
+
|
|
91
|
+
The -fuse-ld=ld64.lld flag is only recognized by LLVM 21.x and later.
|
|
92
|
+
Earlier versions require -fuse-ld=lld (which auto-detects Mach-O from target).
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
platform_name: Platform name ("darwin", "linux", "win")
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
True if LLVM >= 21.x, False otherwise
|
|
99
|
+
"""
|
|
100
|
+
version = get_llvm_version(platform_name)
|
|
101
|
+
return version.major >= 21
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: clang-tool-chain
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.43
|
|
4
4
|
Summary: Clang Tool Chain - C/C++ compilation toolchain utilities
|
|
5
5
|
Project-URL: Homepage, https://github.com/zackees/clang-tool-chain
|
|
6
6
|
Project-URL: Repository, https://github.com/zackees/clang-tool-chain
|
|
@@ -240,6 +240,7 @@ Comprehensive reference of all available commands organized by category.
|
|
|
240
240
|
- [Inlined Build Directives](#-inlined-build-directives)
|
|
241
241
|
- [Executable C++ Scripts](#-executable-c-scripts-shebang-support)
|
|
242
242
|
- [Windows DLL Deployment](#-windows-dll-deployment)
|
|
243
|
+
- [Address Sanitizer (ASAN)](#️-address-sanitizer-asan-support)
|
|
243
244
|
- [sccache Integration](#-sccache-integration)
|
|
244
245
|
|
|
245
246
|
### Platform & Configuration
|
|
@@ -794,6 +795,7 @@ jobs:
|
|
|
794
795
|
- `CLANG_TOOL_CHAIN_LIB_DEPLOY_VERBOSE` - Enable verbose library deployment logging
|
|
795
796
|
- `CLANG_TOOL_CHAIN_USE_SYSTEM_LD` - Use system linker instead of LLD
|
|
796
797
|
- `CLANG_TOOL_CHAIN_NO_DIRECTIVES` - Disable inlined build directives
|
|
798
|
+
- `CLANG_TOOL_CHAIN_NO_SANITIZER_ENV` - Disable automatic ASAN/LSAN options injection at runtime
|
|
797
799
|
- `SDKROOT` - Custom macOS SDK path (auto-detected by default)
|
|
798
800
|
|
|
799
801
|
**📖 [Complete Documentation](docs/CONFIGURATION.md)** - All environment variables, macOS SDK, Windows DLL settings, sccache backends.
|
|
@@ -838,6 +840,56 @@ Automatic MinGW runtime DLL deployment for Windows executables (GNU ABI). Progra
|
|
|
838
840
|
|
|
839
841
|
---
|
|
840
842
|
|
|
843
|
+
## 🛡️ Address Sanitizer (ASAN) Support
|
|
844
|
+
|
|
845
|
+
Full ASAN support with automatic runtime configuration for better stack traces.
|
|
846
|
+
|
|
847
|
+
### Compilation
|
|
848
|
+
|
|
849
|
+
```bash
|
|
850
|
+
# Compile with ASAN
|
|
851
|
+
clang-tool-chain-cpp -fsanitize=address test.cpp -o test
|
|
852
|
+
|
|
853
|
+
# With automatic library deployment (recommended)
|
|
854
|
+
clang-tool-chain-cpp -fsanitize=address test.cpp -o test --deploy-dependencies
|
|
855
|
+
|
|
856
|
+
# Run - ASAN errors will be detected
|
|
857
|
+
./test
|
|
858
|
+
```
|
|
859
|
+
|
|
860
|
+
### Runtime Environment (Automatic)
|
|
861
|
+
|
|
862
|
+
When running executables via `clang-tool-chain-build-run`, optimal sanitizer options are **automatically injected** to improve stack trace quality - but **only when the corresponding sanitizer was used during compilation**:
|
|
863
|
+
|
|
864
|
+
- `ASAN_OPTIONS=fast_unwind_on_malloc=0:symbolize=1:detect_leaks=1` (when `-fsanitize=address` is used)
|
|
865
|
+
- `LSAN_OPTIONS=fast_unwind_on_malloc=0:symbolize=1` (when `-fsanitize=address` or `-fsanitize=leak` is used)
|
|
866
|
+
|
|
867
|
+
**What these options fix:**
|
|
868
|
+
- `<unknown module>` entries in stack traces from `dlopen()`'d shared libraries
|
|
869
|
+
- Missing function names in crash reports
|
|
870
|
+
- Incomplete leak detection
|
|
871
|
+
|
|
872
|
+
**Your options are always preserved** - if you set `ASAN_OPTIONS` or `LSAN_OPTIONS` yourself, clang-tool-chain won't override them.
|
|
873
|
+
|
|
874
|
+
**Regular builds are unaffected** - sanitizer options are only injected when the compiler flags indicate sanitizers are being used.
|
|
875
|
+
|
|
876
|
+
### Configuration
|
|
877
|
+
|
|
878
|
+
```bash
|
|
879
|
+
# Disable automatic sanitizer environment injection
|
|
880
|
+
export CLANG_TOOL_CHAIN_NO_SANITIZER_ENV=1
|
|
881
|
+
|
|
882
|
+
# Disable automatic -shared-libasan on Linux (use static ASAN)
|
|
883
|
+
export CLANG_TOOL_CHAIN_NO_SHARED_ASAN=1
|
|
884
|
+
```
|
|
885
|
+
|
|
886
|
+
**Platform Notes:**
|
|
887
|
+
- **Linux**: Automatically uses `-shared-libasan` for proper runtime linking
|
|
888
|
+
- **Windows**: Works with both GNU and MSVC ABIs
|
|
889
|
+
- **macOS**: Uses bundled LLVM ASAN runtime
|
|
890
|
+
|
|
891
|
+
---
|
|
892
|
+
|
|
841
893
|
## 🔧 How It Works
|
|
842
894
|
|
|
843
895
|
Auto-downloads on first use (~71-91 MB, 10-60 seconds). Subsequent uses are instant.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
clang_tool_chain/__init__.py,sha256=w3f1hdA72SH-WsfwesNTQbTeiXIlhV8ignySXrV3SfU,23
|
|
2
|
-
clang_tool_chain/__version__.py,sha256=
|
|
2
|
+
clang_tool_chain/__version__.py,sha256=6gmxA-SFNIuF2QL2LcQn3fYERY3gD95uxieCn2hdE7o,72
|
|
3
3
|
clang_tool_chain/archive.py,sha256=t3reh7cm5XP2rhTqfRIDAQZv5XQq7SsstyiROYg8wFA,27697
|
|
4
4
|
clang_tool_chain/archive_cache.py,sha256=5ZmlwXIJZDcrDFkTbdgBQYN9sulGn0WyI6qwWqC4HEU,6806
|
|
5
5
|
clang_tool_chain/checksums.py,sha256=KFXeAeDz5ZlcZVOxsHDpNDCrm9UDoJ8bMA4PeNhuzdA,9868
|
|
@@ -12,6 +12,7 @@ clang_tool_chain/env_breadcrumbs.py,sha256=bvPTz8xABILhzrXTEBzdGrSbpEXLf2YVgcYEe
|
|
|
12
12
|
clang_tool_chain/fetch.py,sha256=DwsNl5DZkNqEYXL-FbCTnp6IA2iCAa9pMl5oPjyuOS4,4696
|
|
13
13
|
clang_tool_chain/installer.py,sha256=GuGeUvVcAw4HMj9jrub-I11ixmksw-vgtSOFrlupmPA,3323
|
|
14
14
|
clang_tool_chain/interrupt_utils.py,sha256=7YvazvGzyItRVDZ_pzUSK6at8PCw-Dgih69HLSz0tT4,1153
|
|
15
|
+
clang_tool_chain/llvm_versions.py,sha256=lJuMmrRwOpXJxkrNL6HPbZOCemhZA88t0O0SET3VASo,2963
|
|
15
16
|
clang_tool_chain/logging_config.py,sha256=HS0AMgIiyx2fwdl24mZ1oJDCW_MD5VeLxkTwT3d3v9o,2059
|
|
16
17
|
clang_tool_chain/manifest.py,sha256=kYtUDQLY8RHpKeKRoIlzzA52bDnTG0Thx1rp19LvdNg,22138
|
|
17
18
|
clang_tool_chain/parallel_download.py,sha256=nIyiTzSO8c3NA787TQztHRKko2GtQrZbiNbQiepjbDk,19144
|
|
@@ -35,10 +36,10 @@ clang_tool_chain/deployment/factory.py,sha256=Mq6cMMUctoYBZ81I16_hXUmDLQGSAngCmB
|
|
|
35
36
|
clang_tool_chain/deployment/so_deployer.py,sha256=9mRKcJUXKV-8vXWJGdsUkB5cTPWjmToFUcHTVTCHdVk,11527
|
|
36
37
|
clang_tool_chain/directives/__init__.py,sha256=MJDNYL_MD2MF0HFsrTsSTX645bYo6vtjq2pOTtfykaU,198
|
|
37
38
|
clang_tool_chain/directives/parser.py,sha256=6J7mO1JtvuHkkKS0Xges5b_jT9b3uTF6ULI0ZiwGAdw,11179
|
|
38
|
-
clang_tool_chain/execution/__init__.py,sha256=
|
|
39
|
+
clang_tool_chain/execution/__init__.py,sha256=wSYnlGmfTlaUPEKwW6PgwlxKTti6Wa5yyVjXVipdpcY,875
|
|
39
40
|
clang_tool_chain/execution/arg_transformers.py,sha256=YkGqz_vbfNE-yFXHnzZREbS1T545AfQcV0DJTkU59YM,18254
|
|
40
41
|
clang_tool_chain/execution/build.py,sha256=PCtHw31WXbjCp2K_qaf1liaCiIjD49036cknqdQyhJM,13040
|
|
41
|
-
clang_tool_chain/execution/build_pipeline.py,sha256=
|
|
42
|
+
clang_tool_chain/execution/build_pipeline.py,sha256=piqh7P2-GR0mVOik3OUGRFqWFIRr8LQM3PU7TcBsujk,17622
|
|
42
43
|
clang_tool_chain/execution/core.py,sha256=7CJ0azznC5lq5bw8amk2kwCIN2I_OnDiKytpapkvrdY,25273
|
|
43
44
|
clang_tool_chain/execution/cosmocc.py,sha256=oGlaPK6Jpz3FaohEkWpRz8sRNu2sT-HYCoIf9xBxJHk,13319
|
|
44
45
|
clang_tool_chain/execution/emscripten.py,sha256=lgxPQpeB1_wWxNILgeyyrW5lEn117dHS9dQ3ikHRn1w,44235
|
|
@@ -46,6 +47,7 @@ clang_tool_chain/execution/iwyu.py,sha256=bmP0d_PZObA1JfmFYp3qIOKCb7y32AWPm2_ReF
|
|
|
46
47
|
clang_tool_chain/execution/lldb.py,sha256=VpxkWTPS6PsyskaKTALeziR5Z5NLwarW174Fm1SMX9k,20718
|
|
47
48
|
clang_tool_chain/execution/nodejs_resolver.py,sha256=8QsJWvIfmt5mBDV7n0ypSjsPyXS-eZTizhBli937I-g,11172
|
|
48
49
|
clang_tool_chain/execution/platform_executor.py,sha256=sF4GyW0ujy2EewG8y2Eo1gUWGzss5G5iIkv02w7-2_o,14362
|
|
50
|
+
clang_tool_chain/execution/sanitizer_env.py,sha256=lrGX17BriqYPsHCCrQnwjw0ttnp9mezsq0eYAOLfBag,4853
|
|
49
51
|
clang_tool_chain/installers/__init__.py,sha256=NAV5woPCEDKSbFr1UmfQsrg4Ua5UdghN4q7H3ymvRsg,279
|
|
50
52
|
clang_tool_chain/installers/base.py,sha256=OS78bau9zoYPitmhla7pKsfCPEj-zLY0DkvVzjE31Tw,15437
|
|
51
53
|
clang_tool_chain/installers/clang.py,sha256=rUtheVRF7mq_1YdmQ3XzIybrJqsHbm2Xf0cbhRbH7RQ,16994
|
|
@@ -55,7 +57,7 @@ clang_tool_chain/installers/iwyu.py,sha256=9aAhdGtOTY6BrLuPtladY8Y2mz1i7FjgbMxZf
|
|
|
55
57
|
clang_tool_chain/installers/lldb.py,sha256=FpG8NMNQk8PoNfg6aeU_plmSQrVET7zo-pTvoK8z838,2261
|
|
56
58
|
clang_tool_chain/installers/nodejs.py,sha256=5N07rotgmCfUaDm1uJfBlIAFKC1iTpgZT0HBRuoYwKI,9343
|
|
57
59
|
clang_tool_chain/linker/__init__.py,sha256=ghzDFpZ2-gPmdDO6K05C7yNbY6pZLANPuUks9TaQwVY,537
|
|
58
|
-
clang_tool_chain/linker/lld.py,sha256=
|
|
60
|
+
clang_tool_chain/linker/lld.py,sha256=J8XXtz0DaabnNFReSJCXgk37NloUvrIkjp5TzIZ7Tg8,12468
|
|
59
61
|
clang_tool_chain/platform/__init__.py,sha256=WkV9Y25ua0mtzEGcsIxF-qExtroSTAMKkcElWuQF2BE,342
|
|
60
62
|
clang_tool_chain/platform/detection.py,sha256=PLHyUfmQ5xuohhpz0KSXJWK3d0u0fCsjx1DbM8f1CxQ,5470
|
|
61
63
|
clang_tool_chain/platform/paths.py,sha256=K0IjeVwbmgPlAWQO8mS3r1WS4C2dN6IYrSqPpckeT5c,6088
|
|
@@ -65,8 +67,8 @@ clang_tool_chain/sdk/windows.py,sha256=8zMLAoFz2OKMz-w6Kqxr3A-6Cofto2VWZvCvRi7kn
|
|
|
65
67
|
clang_tool_chain/testing/__init__.py,sha256=-sYqOOCuTV_u-MkmExrD4uKdTHG4RmMwR3D1kIG281Q,208
|
|
66
68
|
clang_tool_chain/testing/diagnostic_runner.py,sha256=mnmFUEOQulY3-Ggu6hKVGZwjrKQNmV6kY80PRTUu2qU,5293
|
|
67
69
|
clang_tool_chain/testing/diagnostic_tests.py,sha256=GmtKWrDcddZTpx9_yIKfhRAy6YOde8dj7SksCWVEME4,6019
|
|
68
|
-
clang_tool_chain-1.0.
|
|
69
|
-
clang_tool_chain-1.0.
|
|
70
|
-
clang_tool_chain-1.0.
|
|
71
|
-
clang_tool_chain-1.0.
|
|
72
|
-
clang_tool_chain-1.0.
|
|
70
|
+
clang_tool_chain-1.0.43.dist-info/METADATA,sha256=BEMrWFJ8AIyAAdlb5v0CfKWkfKr1I-BNYkzoNAv2H6w,50425
|
|
71
|
+
clang_tool_chain-1.0.43.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
72
|
+
clang_tool_chain-1.0.43.dist-info/entry_points.txt,sha256=DBfnqZJzHFoO6rldGY3ESaX-z2ydAvIueUPM9ss9M-w,2728
|
|
73
|
+
clang_tool_chain-1.0.43.dist-info/licenses/LICENSE,sha256=51FO1oc2pZbQNI0v0_THnznnZIF4iFgawG1xnQ58kKo,10997
|
|
74
|
+
clang_tool_chain-1.0.43.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|