clang-tool-chain 1.0.44__py3-none-any.whl → 1.0.47__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 +31 -0
- clang_tool_chain/__version__.py +1 -1
- clang_tool_chain/deployment/dll_deployer.py +16 -23
- clang_tool_chain/deployment/dylib_deployer.py +8 -8
- clang_tool_chain/deployment/libdeploy.py +287 -0
- clang_tool_chain/deployment/so_deployer.py +13 -5
- clang_tool_chain/env_utils.py +137 -0
- clang_tool_chain/execution/__init__.py +6 -1
- clang_tool_chain/execution/arg_transformers.py +128 -36
- clang_tool_chain/execution/build.py +10 -4
- clang_tool_chain/execution/build_pipeline.py +7 -2
- clang_tool_chain/execution/sanitizer_env.py +77 -12
- clang_tool_chain/linker/lld.py +42 -12
- clang_tool_chain/wrapper.py +13 -0
- {clang_tool_chain-1.0.44.dist-info → clang_tool_chain-1.0.47.dist-info}/METADATA +222 -25
- {clang_tool_chain-1.0.44.dist-info → clang_tool_chain-1.0.47.dist-info}/RECORD +19 -17
- {clang_tool_chain-1.0.44.dist-info → clang_tool_chain-1.0.47.dist-info}/entry_points.txt +1 -0
- {clang_tool_chain-1.0.44.dist-info → clang_tool_chain-1.0.47.dist-info}/WHEEL +0 -0
- {clang_tool_chain-1.0.44.dist-info → clang_tool_chain-1.0.47.dist-info}/licenses/LICENSE +0 -0
clang_tool_chain/__init__.py
CHANGED
|
@@ -1 +1,32 @@
|
|
|
1
1
|
__version__ = "1.0.30"
|
|
2
|
+
|
|
3
|
+
# Re-export commonly used functions for convenient access
|
|
4
|
+
from clang_tool_chain.env_utils import (
|
|
5
|
+
CONTROLLABLE_FEATURES,
|
|
6
|
+
get_disabled_features,
|
|
7
|
+
is_auto_disabled,
|
|
8
|
+
is_feature_disabled,
|
|
9
|
+
)
|
|
10
|
+
from clang_tool_chain.execution.sanitizer_env import (
|
|
11
|
+
detect_sanitizers_from_flags,
|
|
12
|
+
get_symbolizer_path,
|
|
13
|
+
prepare_sanitizer_environment,
|
|
14
|
+
)
|
|
15
|
+
from clang_tool_chain.platform import find_tool_binary, get_platform_binary_dir, get_platform_info
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"__version__",
|
|
19
|
+
# Platform utilities
|
|
20
|
+
"get_platform_info",
|
|
21
|
+
"get_platform_binary_dir",
|
|
22
|
+
"find_tool_binary",
|
|
23
|
+
# Environment utilities
|
|
24
|
+
"is_feature_disabled",
|
|
25
|
+
"is_auto_disabled",
|
|
26
|
+
"get_disabled_features",
|
|
27
|
+
"CONTROLLABLE_FEATURES",
|
|
28
|
+
# Sanitizer environment
|
|
29
|
+
"prepare_sanitizer_environment",
|
|
30
|
+
"get_symbolizer_path",
|
|
31
|
+
"detect_sanitizers_from_flags",
|
|
32
|
+
]
|
clang_tool_chain/__version__.py
CHANGED
|
@@ -12,6 +12,7 @@ import re
|
|
|
12
12
|
import subprocess
|
|
13
13
|
from pathlib import Path
|
|
14
14
|
|
|
15
|
+
from clang_tool_chain.env_utils import is_feature_disabled
|
|
15
16
|
from clang_tool_chain.interrupt_utils import handle_keyboard_interrupt_properly
|
|
16
17
|
|
|
17
18
|
from .base_deployer import BaseLibraryDeployer
|
|
@@ -510,9 +511,10 @@ def post_link_dll_deployment(output_exe_path: Path, platform_name: str, use_gnu_
|
|
|
510
511
|
None
|
|
511
512
|
|
|
512
513
|
Environment Variables:
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
514
|
+
CLANG_TOOL_CHAIN_NO_DEPLOY_LIBS: Set to "1" to disable all library deployment
|
|
515
|
+
CLANG_TOOL_CHAIN_NO_DEPLOY_SHARED_LIB: Set to "1" to disable deployment for shared library outputs only
|
|
516
|
+
CLANG_TOOL_CHAIN_NO_AUTO: Set to "1" to disable all automatic features
|
|
517
|
+
CLANG_TOOL_CHAIN_LIB_DEPLOY_VERBOSE: Set to "1" for verbose logging
|
|
516
518
|
|
|
517
519
|
Examples:
|
|
518
520
|
>>> post_link_dll_deployment(Path("test.exe"), "win", True)
|
|
@@ -520,13 +522,12 @@ def post_link_dll_deployment(output_exe_path: Path, platform_name: str, use_gnu_
|
|
|
520
522
|
>>> post_link_dll_deployment(Path("mylib.dll"), "win", True)
|
|
521
523
|
# Deploys runtime DLLs alongside the shared library
|
|
522
524
|
"""
|
|
523
|
-
# Check opt-out environment variable
|
|
524
|
-
if
|
|
525
|
-
logger.debug("DLL deployment disabled via CLANG_TOOL_CHAIN_NO_DEPLOY_DLLS")
|
|
525
|
+
# Check opt-out environment variable (via NO_DEPLOY_LIBS or NO_AUTO)
|
|
526
|
+
if is_feature_disabled("DEPLOY_LIBS"):
|
|
526
527
|
return
|
|
527
528
|
|
|
528
529
|
# Enable verbose logging if requested
|
|
529
|
-
if os.environ.get("
|
|
530
|
+
if os.environ.get("CLANG_TOOL_CHAIN_LIB_DEPLOY_VERBOSE") == "1":
|
|
530
531
|
logger.setLevel(logging.DEBUG)
|
|
531
532
|
|
|
532
533
|
# Guard: only deploy on Windows
|
|
@@ -542,9 +543,8 @@ def post_link_dll_deployment(output_exe_path: Path, platform_name: str, use_gnu_
|
|
|
542
543
|
# Guard: only deploy for .exe and .dll files
|
|
543
544
|
suffix = output_exe_path.suffix.lower()
|
|
544
545
|
if suffix == ".dll":
|
|
545
|
-
# Check for
|
|
546
|
-
if
|
|
547
|
-
logger.debug("DLL deployment for .dll outputs disabled via CLANG_TOOL_CHAIN_NO_DEPLOY_DLLS_FOR_DLLS")
|
|
546
|
+
# Check for shared library-specific opt-out (via NO_DEPLOY_SHARED_LIB or NO_AUTO)
|
|
547
|
+
if is_feature_disabled("DEPLOY_SHARED_LIB"):
|
|
548
548
|
return
|
|
549
549
|
elif suffix != ".exe":
|
|
550
550
|
logger.debug(f"DLL deployment skipped: not .exe or .dll file (suffix={output_exe_path.suffix})")
|
|
@@ -596,10 +596,9 @@ def post_link_dependency_deployment(output_path: Path, platform_name: str, use_g
|
|
|
596
596
|
None
|
|
597
597
|
|
|
598
598
|
Environment Variables:
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
CLANG_TOOL_CHAIN_LIB_DEPLOY_VERBOSE: Set to "1" for verbose logging (alias)
|
|
599
|
+
CLANG_TOOL_CHAIN_NO_DEPLOY_LIBS: Set to "1" to disable all library deployment
|
|
600
|
+
CLANG_TOOL_CHAIN_NO_AUTO: Set to "1" to disable all automatic features
|
|
601
|
+
CLANG_TOOL_CHAIN_LIB_DEPLOY_VERBOSE: Set to "1" for verbose logging
|
|
603
602
|
|
|
604
603
|
Examples:
|
|
605
604
|
>>> post_link_dependency_deployment(Path("mylib.dll"), "win", True)
|
|
@@ -609,17 +608,11 @@ def post_link_dependency_deployment(output_path: Path, platform_name: str, use_g
|
|
|
609
608
|
>>> post_link_dependency_deployment(Path("mylib.dylib"), "darwin", False)
|
|
610
609
|
# Deploys libunwind.1.dylib, libc++.1.dylib, etc. to mylib.dylib directory
|
|
611
610
|
"""
|
|
612
|
-
# Check opt-out environment variables (
|
|
613
|
-
if
|
|
614
|
-
logger.debug("Dependency deployment disabled via CLANG_TOOL_CHAIN_NO_DEPLOY_DLLS")
|
|
615
|
-
return
|
|
616
|
-
if os.environ.get("CLANG_TOOL_CHAIN_NO_DEPLOY_LIBS") == "1":
|
|
617
|
-
logger.debug("Dependency deployment disabled via CLANG_TOOL_CHAIN_NO_DEPLOY_LIBS")
|
|
611
|
+
# Check opt-out environment variables (NO_DEPLOY_LIBS or NO_AUTO)
|
|
612
|
+
if is_feature_disabled("DEPLOY_LIBS"):
|
|
618
613
|
return
|
|
619
614
|
|
|
620
|
-
# Enable verbose logging if requested
|
|
621
|
-
if os.environ.get("CLANG_TOOL_CHAIN_DLL_DEPLOY_VERBOSE") == "1":
|
|
622
|
-
logger.setLevel(logging.DEBUG)
|
|
615
|
+
# Enable verbose logging if requested
|
|
623
616
|
if os.environ.get("CLANG_TOOL_CHAIN_LIB_DEPLOY_VERBOSE") == "1":
|
|
624
617
|
logger.setLevel(logging.DEBUG)
|
|
625
618
|
|
|
@@ -18,11 +18,12 @@ Example:
|
|
|
18
18
|
"""
|
|
19
19
|
|
|
20
20
|
import logging
|
|
21
|
-
import os
|
|
22
21
|
import re
|
|
23
22
|
import subprocess
|
|
24
23
|
from pathlib import Path
|
|
25
24
|
|
|
25
|
+
from clang_tool_chain.env_utils import is_feature_disabled
|
|
26
|
+
|
|
26
27
|
from .base_deployer import BaseLibraryDeployer
|
|
27
28
|
|
|
28
29
|
logger = logging.getLogger(__name__)
|
|
@@ -499,7 +500,8 @@ def post_link_dylib_deployment(output_path: Path, arch: str = "x86_64") -> int:
|
|
|
499
500
|
|
|
500
501
|
Environment variables:
|
|
501
502
|
- CLANG_TOOL_CHAIN_NO_DEPLOY_LIBS=1 - Disable all library deployment
|
|
502
|
-
-
|
|
503
|
+
- CLANG_TOOL_CHAIN_NO_DEPLOY_SHARED_LIB=1 - Disable deployment for shared library outputs
|
|
504
|
+
- CLANG_TOOL_CHAIN_NO_AUTO=1 - Disable all automatic features
|
|
503
505
|
|
|
504
506
|
Args:
|
|
505
507
|
output_path: Path to executable or shared library
|
|
@@ -512,14 +514,12 @@ def post_link_dylib_deployment(output_path: Path, arch: str = "x86_64") -> int:
|
|
|
512
514
|
>>> # Called automatically after linking
|
|
513
515
|
>>> count = post_link_dylib_deployment(Path("program"), arch="arm64")
|
|
514
516
|
"""
|
|
515
|
-
# Check
|
|
516
|
-
if
|
|
517
|
-
logger.debug("dylib deployment disabled (CLANG_TOOL_CHAIN_NO_DEPLOY_LIBS=1)")
|
|
517
|
+
# Check environment variables (NO_DEPLOY_LIBS or NO_AUTO)
|
|
518
|
+
if is_feature_disabled("DEPLOY_LIBS"):
|
|
518
519
|
return 0
|
|
519
520
|
|
|
520
|
-
# Check
|
|
521
|
-
if
|
|
522
|
-
logger.debug("dylib deployment disabled (CLANG_TOOL_CHAIN_NO_DEPLOY_DYLIBS=1)")
|
|
521
|
+
# Check if output is a shared library (.dylib) - if so, check NO_DEPLOY_SHARED_LIB
|
|
522
|
+
if output_path.suffix == ".dylib" and is_feature_disabled("DEPLOY_SHARED_LIB"):
|
|
523
523
|
return 0
|
|
524
524
|
|
|
525
525
|
# Check if output is executable or .dylib
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Library deployment CLI for deploying runtime dependencies after the fact.
|
|
3
|
+
|
|
4
|
+
This module provides a command-line interface to deploy library dependencies
|
|
5
|
+
for already-built executables and shared libraries. It supports:
|
|
6
|
+
- Windows: .exe and .dll files (MinGW runtime DLLs)
|
|
7
|
+
- Linux: executables and .so files (libc++, libunwind, etc.)
|
|
8
|
+
- macOS: executables and .dylib files (libc++, libunwind, etc.)
|
|
9
|
+
|
|
10
|
+
Usage:
|
|
11
|
+
clang-tool-chain-libdeploy myprogram.exe
|
|
12
|
+
clang-tool-chain-libdeploy mylib.dll
|
|
13
|
+
clang-tool-chain-libdeploy myprogram
|
|
14
|
+
clang-tool-chain-libdeploy mylib.so
|
|
15
|
+
clang-tool-chain-libdeploy mylib.dylib
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
import argparse
|
|
19
|
+
import logging
|
|
20
|
+
import sys
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
|
|
23
|
+
from clang_tool_chain.interrupt_utils import handle_keyboard_interrupt_properly
|
|
24
|
+
|
|
25
|
+
logger = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _setup_logging(verbose: bool) -> None:
|
|
29
|
+
"""Configure logging based on verbosity."""
|
|
30
|
+
level = logging.DEBUG if verbose else logging.INFO
|
|
31
|
+
logging.basicConfig(
|
|
32
|
+
level=level,
|
|
33
|
+
format="%(message)s",
|
|
34
|
+
stream=sys.stderr,
|
|
35
|
+
)
|
|
36
|
+
# Also set the deployment module loggers
|
|
37
|
+
logging.getLogger("clang_tool_chain.deployment").setLevel(level)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _detect_binary_type(binary_path: Path) -> tuple[str, str]:
|
|
41
|
+
"""
|
|
42
|
+
Detect the binary type and platform from file extension and magic bytes.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
binary_path: Path to the binary file
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
Tuple of (platform, binary_type) where:
|
|
49
|
+
- platform: "windows", "linux", or "darwin"
|
|
50
|
+
- binary_type: "executable" or "shared_library"
|
|
51
|
+
|
|
52
|
+
Raises:
|
|
53
|
+
ValueError: If binary type cannot be determined
|
|
54
|
+
"""
|
|
55
|
+
suffix = binary_path.suffix.lower()
|
|
56
|
+
|
|
57
|
+
# Windows binaries
|
|
58
|
+
if suffix == ".exe":
|
|
59
|
+
return ("windows", "executable")
|
|
60
|
+
if suffix == ".dll":
|
|
61
|
+
return ("windows", "shared_library")
|
|
62
|
+
|
|
63
|
+
# macOS binaries
|
|
64
|
+
if suffix == ".dylib":
|
|
65
|
+
return ("darwin", "shared_library")
|
|
66
|
+
|
|
67
|
+
# Linux shared libraries (including versioned: .so.1, .so.1.2.3)
|
|
68
|
+
if suffix == ".so" or ".so." in binary_path.name:
|
|
69
|
+
return ("linux", "shared_library")
|
|
70
|
+
|
|
71
|
+
# No extension - could be Linux/macOS executable or a .so with version
|
|
72
|
+
# Try to detect from magic bytes
|
|
73
|
+
if binary_path.exists() and binary_path.is_file():
|
|
74
|
+
try:
|
|
75
|
+
with open(binary_path, "rb") as f:
|
|
76
|
+
magic = f.read(4)
|
|
77
|
+
|
|
78
|
+
# ELF magic: 0x7f 'E' 'L' 'F'
|
|
79
|
+
if magic[:4] == b"\x7fELF":
|
|
80
|
+
return ("linux", "executable")
|
|
81
|
+
|
|
82
|
+
# Mach-O magic numbers
|
|
83
|
+
# MH_MAGIC (32-bit): 0xfeedface
|
|
84
|
+
# MH_MAGIC_64 (64-bit): 0xfeedfacf
|
|
85
|
+
# Fat binary: 0xcafebabe
|
|
86
|
+
if magic[:4] in (b"\xfe\xed\xfa\xce", b"\xfe\xed\xfa\xcf", b"\xcf\xfa\xed\xfe", b"\xce\xfa\xed\xfe"):
|
|
87
|
+
return ("darwin", "executable")
|
|
88
|
+
if magic[:4] == b"\xca\xfe\xba\xbe":
|
|
89
|
+
return ("darwin", "executable")
|
|
90
|
+
|
|
91
|
+
# PE magic: 'MZ'
|
|
92
|
+
if magic[:2] == b"MZ":
|
|
93
|
+
return ("windows", "executable")
|
|
94
|
+
|
|
95
|
+
except (OSError, PermissionError) as e:
|
|
96
|
+
logger.debug(f"Could not read magic bytes: {e}")
|
|
97
|
+
|
|
98
|
+
raise ValueError(
|
|
99
|
+
f"Cannot determine binary type for: {binary_path}\n"
|
|
100
|
+
"Supported formats: .exe, .dll (Windows), .so (Linux), .dylib (macOS), "
|
|
101
|
+
"or ELF/Mach-O executables without extension"
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def deploy_dependencies(
|
|
106
|
+
binary_path: Path,
|
|
107
|
+
platform_override: str | None = None,
|
|
108
|
+
arch: str | None = None,
|
|
109
|
+
verbose: bool = False,
|
|
110
|
+
dry_run: bool = False,
|
|
111
|
+
) -> int:
|
|
112
|
+
"""
|
|
113
|
+
Deploy library dependencies for a binary.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
binary_path: Path to executable or shared library
|
|
117
|
+
platform_override: Override auto-detected platform ("windows", "linux", "darwin")
|
|
118
|
+
arch: Target architecture (default: auto-detect from current platform)
|
|
119
|
+
verbose: Enable verbose output
|
|
120
|
+
dry_run: If True, only show what would be deployed without actually copying
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
Number of libraries deployed, or -1 on error
|
|
124
|
+
"""
|
|
125
|
+
from clang_tool_chain.deployment.factory import create_deployer
|
|
126
|
+
from clang_tool_chain.platform.detection import get_platform_info
|
|
127
|
+
|
|
128
|
+
# Get architecture if not specified
|
|
129
|
+
if arch is None:
|
|
130
|
+
_, arch = get_platform_info()
|
|
131
|
+
|
|
132
|
+
# Detect or use override for platform
|
|
133
|
+
if platform_override:
|
|
134
|
+
platform = platform_override.lower()
|
|
135
|
+
if platform not in ("windows", "linux", "darwin", "win", "win32", "macos"):
|
|
136
|
+
logger.error(f"Invalid platform: {platform_override}")
|
|
137
|
+
return -1
|
|
138
|
+
# Normalize platform names
|
|
139
|
+
if platform in ("win", "win32"):
|
|
140
|
+
platform = "windows"
|
|
141
|
+
elif platform == "macos":
|
|
142
|
+
platform = "darwin"
|
|
143
|
+
else:
|
|
144
|
+
try:
|
|
145
|
+
platform, _ = _detect_binary_type(binary_path)
|
|
146
|
+
except ValueError as e:
|
|
147
|
+
logger.error(str(e))
|
|
148
|
+
return -1
|
|
149
|
+
|
|
150
|
+
# Validate binary exists
|
|
151
|
+
if not binary_path.exists():
|
|
152
|
+
logger.error(f"Binary not found: {binary_path}")
|
|
153
|
+
return -1
|
|
154
|
+
|
|
155
|
+
if not binary_path.is_file():
|
|
156
|
+
logger.error(f"Not a file: {binary_path}")
|
|
157
|
+
return -1
|
|
158
|
+
|
|
159
|
+
# Create appropriate deployer
|
|
160
|
+
deployer = create_deployer(platform, arch)
|
|
161
|
+
if deployer is None:
|
|
162
|
+
logger.error(f"No deployer available for platform: {platform}")
|
|
163
|
+
return -1
|
|
164
|
+
|
|
165
|
+
if verbose:
|
|
166
|
+
logger.info(f"Deploying dependencies for: {binary_path}")
|
|
167
|
+
logger.info(f"Platform: {platform}, Architecture: {arch}")
|
|
168
|
+
|
|
169
|
+
if dry_run:
|
|
170
|
+
# Dry run - just show what would be deployed
|
|
171
|
+
dependencies = deployer.detect_all_dependencies(binary_path, recursive=True)
|
|
172
|
+
if not dependencies:
|
|
173
|
+
logger.info("No deployable dependencies found")
|
|
174
|
+
return 0
|
|
175
|
+
|
|
176
|
+
logger.info(f"Would deploy {len(dependencies)} libraries:")
|
|
177
|
+
for dep in sorted(dependencies):
|
|
178
|
+
src = deployer.find_library_in_toolchain(dep)
|
|
179
|
+
if src:
|
|
180
|
+
logger.info(f" {dep} <- {src}")
|
|
181
|
+
else:
|
|
182
|
+
logger.info(f" {dep} (source not found)")
|
|
183
|
+
return len(dependencies)
|
|
184
|
+
|
|
185
|
+
# Actually deploy
|
|
186
|
+
try:
|
|
187
|
+
deployed_count = deployer.deploy_all(binary_path)
|
|
188
|
+
if deployed_count == 0 and verbose:
|
|
189
|
+
logger.info("No libraries needed deployment (all up-to-date or none required)")
|
|
190
|
+
return deployed_count
|
|
191
|
+
except Exception as e:
|
|
192
|
+
logger.error(f"Deployment failed: {e}")
|
|
193
|
+
return -1
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def main() -> int:
|
|
197
|
+
"""
|
|
198
|
+
Main entry point for clang-tool-chain-libdeploy.
|
|
199
|
+
|
|
200
|
+
Returns:
|
|
201
|
+
Exit code (0 for success, non-zero for failure)
|
|
202
|
+
"""
|
|
203
|
+
parser = argparse.ArgumentParser(
|
|
204
|
+
prog="clang-tool-chain-libdeploy",
|
|
205
|
+
description="Deploy runtime library dependencies for executables and shared libraries",
|
|
206
|
+
epilog="""
|
|
207
|
+
Examples:
|
|
208
|
+
clang-tool-chain-libdeploy myprogram.exe # Windows executable
|
|
209
|
+
clang-tool-chain-libdeploy mylib.dll # Windows DLL
|
|
210
|
+
clang-tool-chain-libdeploy myprogram # Linux/macOS executable
|
|
211
|
+
clang-tool-chain-libdeploy mylib.so # Linux shared library
|
|
212
|
+
clang-tool-chain-libdeploy mylib.dylib # macOS dynamic library
|
|
213
|
+
clang-tool-chain-libdeploy --dry-run myprogram # Show what would be deployed
|
|
214
|
+
|
|
215
|
+
Supported platforms:
|
|
216
|
+
Windows: Deploys MinGW runtime DLLs (libwinpthread, libgcc_s, libstdc++, etc.)
|
|
217
|
+
Linux: Deploys libc++, libunwind, sanitizer runtimes
|
|
218
|
+
macOS: Deploys libc++, libunwind, sanitizer runtimes
|
|
219
|
+
""",
|
|
220
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
parser.add_argument(
|
|
224
|
+
"binary",
|
|
225
|
+
type=Path,
|
|
226
|
+
help="Path to executable (.exe) or shared library (.dll, .so, .dylib)",
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
parser.add_argument(
|
|
230
|
+
"-v",
|
|
231
|
+
"--verbose",
|
|
232
|
+
action="store_true",
|
|
233
|
+
help="Enable verbose output",
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
parser.add_argument(
|
|
237
|
+
"-n",
|
|
238
|
+
"--dry-run",
|
|
239
|
+
action="store_true",
|
|
240
|
+
help="Show what would be deployed without copying files",
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
parser.add_argument(
|
|
244
|
+
"-p",
|
|
245
|
+
"--platform",
|
|
246
|
+
choices=["windows", "linux", "darwin"],
|
|
247
|
+
help="Override auto-detected platform",
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
parser.add_argument(
|
|
251
|
+
"-a",
|
|
252
|
+
"--arch",
|
|
253
|
+
help="Target architecture (default: auto-detect)",
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
args = parser.parse_args()
|
|
257
|
+
|
|
258
|
+
# Setup logging
|
|
259
|
+
_setup_logging(args.verbose)
|
|
260
|
+
|
|
261
|
+
try:
|
|
262
|
+
result = deploy_dependencies(
|
|
263
|
+
binary_path=args.binary.resolve(),
|
|
264
|
+
platform_override=args.platform,
|
|
265
|
+
arch=args.arch,
|
|
266
|
+
verbose=args.verbose,
|
|
267
|
+
dry_run=args.dry_run,
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
if result < 0:
|
|
271
|
+
return 1
|
|
272
|
+
|
|
273
|
+
if result == 0 and not args.dry_run:
|
|
274
|
+
# No libraries deployed is not necessarily an error
|
|
275
|
+
return 0
|
|
276
|
+
|
|
277
|
+
return 0
|
|
278
|
+
|
|
279
|
+
except KeyboardInterrupt as ke:
|
|
280
|
+
handle_keyboard_interrupt_properly(ke) # NoReturn - re-raises
|
|
281
|
+
except Exception as e:
|
|
282
|
+
logger.error(f"Error: {e}")
|
|
283
|
+
return 1
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
if __name__ == "__main__":
|
|
287
|
+
sys.exit(main())
|
|
@@ -12,6 +12,7 @@ import re
|
|
|
12
12
|
import subprocess
|
|
13
13
|
from pathlib import Path
|
|
14
14
|
|
|
15
|
+
from clang_tool_chain.env_utils import is_feature_disabled
|
|
15
16
|
from clang_tool_chain.interrupt_utils import handle_keyboard_interrupt_properly
|
|
16
17
|
|
|
17
18
|
from .base_deployer import BaseLibraryDeployer
|
|
@@ -35,6 +36,8 @@ class SoDeployer(BaseLibraryDeployer):
|
|
|
35
36
|
r"libc\+\+\.so[.\d]*",
|
|
36
37
|
r"libc\+\+abi\.so[.\d]*",
|
|
37
38
|
r"libunwind\.so[.\d]*",
|
|
39
|
+
r"libunwind-x86_64\.so[.\d]*", # Platform-specific libunwind (bundled)
|
|
40
|
+
r"libunwind-aarch64\.so[.\d]*", # Platform-specific libunwind (bundled)
|
|
38
41
|
r"libclang_rt\..*\.so", # Sanitizer runtimes
|
|
39
42
|
]
|
|
40
43
|
|
|
@@ -309,14 +312,19 @@ def post_link_so_deployment(
|
|
|
309
312
|
|
|
310
313
|
Returns:
|
|
311
314
|
Number of .so files deployed
|
|
315
|
+
|
|
316
|
+
Environment Variables:
|
|
317
|
+
CLANG_TOOL_CHAIN_NO_DEPLOY_LIBS: Set to "1" to disable all library deployment
|
|
318
|
+
CLANG_TOOL_CHAIN_NO_DEPLOY_SHARED_LIB: Set to "1" to disable deployment for shared library outputs
|
|
319
|
+
CLANG_TOOL_CHAIN_NO_AUTO: Set to "1" to disable all automatic features
|
|
312
320
|
"""
|
|
313
|
-
# Check environment variables
|
|
314
|
-
if
|
|
315
|
-
logger.debug("Linux .so deployment disabled by CLANG_TOOL_CHAIN_NO_DEPLOY_LIBS")
|
|
321
|
+
# Check environment variables (NO_DEPLOY_LIBS or NO_AUTO)
|
|
322
|
+
if is_feature_disabled("DEPLOY_LIBS"):
|
|
316
323
|
return 0
|
|
317
324
|
|
|
318
|
-
if
|
|
319
|
-
|
|
325
|
+
# Check if output is a shared library (.so) - if so, check NO_DEPLOY_SHARED_LIB
|
|
326
|
+
is_shared_lib = output_path.suffix == ".so" or ".so." in output_path.name
|
|
327
|
+
if is_shared_lib and is_feature_disabled("DEPLOY_SHARED_LIB"):
|
|
320
328
|
return 0
|
|
321
329
|
|
|
322
330
|
# Check if output is a deployable binary
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Environment variable utilities for clang-tool-chain.
|
|
3
|
+
|
|
4
|
+
This module provides centralized handling of environment variable checks,
|
|
5
|
+
including support for the aggregate CLANG_TOOL_CHAIN_NO_AUTO variable
|
|
6
|
+
that disables all automatic features.
|
|
7
|
+
|
|
8
|
+
Example:
|
|
9
|
+
>>> from clang_tool_chain.env_utils import is_feature_disabled
|
|
10
|
+
>>> if is_feature_disabled("DIRECTIVES"):
|
|
11
|
+
... # Skip directive parsing
|
|
12
|
+
... pass
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import logging
|
|
16
|
+
import os
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
# Truthy values for boolean environment variables
|
|
21
|
+
_TRUTHY_VALUES = ("1", "true", "yes")
|
|
22
|
+
|
|
23
|
+
# All features that can be disabled via NO_AUTO
|
|
24
|
+
# Maps feature name to the specific environment variable suffix
|
|
25
|
+
CONTROLLABLE_FEATURES = {
|
|
26
|
+
"DIRECTIVES": "Inlined build directives (@link, @std, @cflags)",
|
|
27
|
+
"SHARED_ASAN": "Automatic -shared-libasan injection (Linux)",
|
|
28
|
+
"SANITIZER_ENV": "Automatic ASAN_OPTIONS/LSAN_OPTIONS injection",
|
|
29
|
+
"SANITIZER_NOTE": "Sanitizer flag injection note to stderr",
|
|
30
|
+
"RPATH": "Automatic rpath injection for library loading",
|
|
31
|
+
"SYSROOT": "Automatic macOS SDK detection (-isysroot)",
|
|
32
|
+
"DEPLOY_LIBS": "Cross-platform library deployment (all outputs)",
|
|
33
|
+
"DEPLOY_SHARED_LIB": "Library deployment for shared library outputs only (.dll, .so, .dylib)",
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _is_truthy(value: str | None) -> bool:
|
|
38
|
+
"""Check if an environment variable value is truthy."""
|
|
39
|
+
if value is None:
|
|
40
|
+
return False
|
|
41
|
+
return value.lower() in _TRUTHY_VALUES
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def is_auto_disabled() -> bool:
|
|
45
|
+
"""
|
|
46
|
+
Check if all automatic features are disabled via CLANG_TOOL_CHAIN_NO_AUTO.
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
True if CLANG_TOOL_CHAIN_NO_AUTO is set to a truthy value.
|
|
50
|
+
|
|
51
|
+
Example:
|
|
52
|
+
>>> os.environ["CLANG_TOOL_CHAIN_NO_AUTO"] = "1"
|
|
53
|
+
>>> is_auto_disabled()
|
|
54
|
+
True
|
|
55
|
+
"""
|
|
56
|
+
return _is_truthy(os.environ.get("CLANG_TOOL_CHAIN_NO_AUTO"))
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def is_feature_disabled(feature: str) -> bool:
|
|
60
|
+
"""
|
|
61
|
+
Check if a specific feature is disabled.
|
|
62
|
+
|
|
63
|
+
A feature is disabled if either:
|
|
64
|
+
1. The aggregate CLANG_TOOL_CHAIN_NO_AUTO=1 is set, OR
|
|
65
|
+
2. The specific CLANG_TOOL_CHAIN_NO_{feature}=1 is set
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
feature: The feature name (e.g., "DIRECTIVES", "SHARED_ASAN", "DEPLOY_LIBS").
|
|
69
|
+
Should match one of the keys in CONTROLLABLE_FEATURES.
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
True if the feature is disabled, False otherwise.
|
|
73
|
+
|
|
74
|
+
Example:
|
|
75
|
+
>>> os.environ["CLANG_TOOL_CHAIN_NO_DIRECTIVES"] = "1"
|
|
76
|
+
>>> is_feature_disabled("DIRECTIVES")
|
|
77
|
+
True
|
|
78
|
+
|
|
79
|
+
>>> os.environ["CLANG_TOOL_CHAIN_NO_AUTO"] = "1"
|
|
80
|
+
>>> is_feature_disabled("SHARED_ASAN") # Disabled by NO_AUTO
|
|
81
|
+
True
|
|
82
|
+
"""
|
|
83
|
+
# Check the aggregate NO_AUTO variable first
|
|
84
|
+
if is_auto_disabled():
|
|
85
|
+
logger.debug(f"Feature {feature} disabled via CLANG_TOOL_CHAIN_NO_AUTO=1")
|
|
86
|
+
return True
|
|
87
|
+
|
|
88
|
+
# Check the specific feature variable
|
|
89
|
+
var_name = f"CLANG_TOOL_CHAIN_NO_{feature}"
|
|
90
|
+
if _is_truthy(os.environ.get(var_name)):
|
|
91
|
+
logger.debug(f"Feature {feature} disabled via {var_name}=1")
|
|
92
|
+
return True
|
|
93
|
+
|
|
94
|
+
return False
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def get_disabled_features() -> list[str]:
|
|
98
|
+
"""
|
|
99
|
+
Get a list of all currently disabled features.
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
List of feature names that are disabled.
|
|
103
|
+
|
|
104
|
+
Example:
|
|
105
|
+
>>> os.environ["CLANG_TOOL_CHAIN_NO_AUTO"] = "1"
|
|
106
|
+
>>> get_disabled_features()
|
|
107
|
+
['DIRECTIVES', 'SHARED_ASAN', 'SANITIZER_ENV', ...]
|
|
108
|
+
"""
|
|
109
|
+
disabled = []
|
|
110
|
+
|
|
111
|
+
# If NO_AUTO is set, all features are disabled
|
|
112
|
+
if is_auto_disabled():
|
|
113
|
+
return list(CONTROLLABLE_FEATURES.keys())
|
|
114
|
+
|
|
115
|
+
# Check each feature individually
|
|
116
|
+
for feature in CONTROLLABLE_FEATURES:
|
|
117
|
+
var_name = f"CLANG_TOOL_CHAIN_NO_{feature}"
|
|
118
|
+
if _is_truthy(os.environ.get(var_name)):
|
|
119
|
+
disabled.append(feature)
|
|
120
|
+
|
|
121
|
+
return disabled
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def log_disabled_features_summary() -> None:
|
|
125
|
+
"""
|
|
126
|
+
Log a summary of disabled features (useful for debugging).
|
|
127
|
+
|
|
128
|
+
Only logs if at least one feature is disabled.
|
|
129
|
+
"""
|
|
130
|
+
disabled = get_disabled_features()
|
|
131
|
+
if not disabled:
|
|
132
|
+
return
|
|
133
|
+
|
|
134
|
+
if is_auto_disabled():
|
|
135
|
+
logger.info("All automatic features disabled via CLANG_TOOL_CHAIN_NO_AUTO=1")
|
|
136
|
+
else:
|
|
137
|
+
logger.info(f"Disabled features: {', '.join(disabled)}")
|
|
@@ -10,7 +10,11 @@ This package provides:
|
|
|
10
10
|
|
|
11
11
|
from clang_tool_chain.execution.build import build_main, build_run_main
|
|
12
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
|
|
13
|
+
from clang_tool_chain.execution.sanitizer_env import (
|
|
14
|
+
detect_sanitizers_from_flags,
|
|
15
|
+
get_symbolizer_path,
|
|
16
|
+
prepare_sanitizer_environment,
|
|
17
|
+
)
|
|
14
18
|
|
|
15
19
|
__all__ = [
|
|
16
20
|
# Core execution
|
|
@@ -25,4 +29,5 @@ __all__ = [
|
|
|
25
29
|
# Sanitizer utilities
|
|
26
30
|
"prepare_sanitizer_environment",
|
|
27
31
|
"detect_sanitizers_from_flags",
|
|
32
|
+
"get_symbolizer_path",
|
|
28
33
|
]
|