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.
@@ -0,0 +1,572 @@
1
+ """
2
+ Main entry point for clang-tool-chain CLI.
3
+
4
+ Provides commands for managing and using the LLVM toolchain.
5
+ """
6
+
7
+ import argparse
8
+ import subprocess
9
+ import sys
10
+ from typing import NoReturn
11
+
12
+ from . import sccache_runner, wrapper
13
+
14
+ try:
15
+ from .__version__ import __version__
16
+ except ImportError:
17
+ __version__ = "unknown"
18
+
19
+
20
+ def cmd_info(args: argparse.Namespace) -> int:
21
+ """Display information about the toolchain installation."""
22
+ print("Clang Tool Chain - LLVM/Clang Distribution")
23
+ print("=" * 60)
24
+ print()
25
+
26
+ # Platform information
27
+ try:
28
+ platform_name, arch = wrapper.get_platform_info()
29
+ print(f"Platform: {platform_name}")
30
+ print(f"Architecture: {arch}")
31
+ print()
32
+ except RuntimeError as e:
33
+ print(f"Error detecting platform: {e}")
34
+ return 1
35
+
36
+ # Windows GNU ABI information
37
+ if platform_name == "win":
38
+ print("Windows Target Configuration:")
39
+ print(" Default ABI: GNU (x86_64-w64-mingw32)")
40
+ print(" MSVC ABI: Available via clang-tool-chain-c-msvc")
41
+ print(" and clang-tool-chain-cpp-msvc")
42
+ print()
43
+ print("Why GNU ABI is default:")
44
+ print(" - Cross-platform consistency (same ABI on Linux/macOS/Windows)")
45
+ print(" - C++11 strict mode support (MSVC headers require C++14+)")
46
+ print(" - Arduino/embedded compatibility (matches GCC toolchain)")
47
+ print()
48
+
49
+ # Assets directory
50
+ assets_dir = wrapper.get_assets_dir()
51
+ print(f"Assets directory: {assets_dir}")
52
+ print(f"Assets exist: {assets_dir.exists()}")
53
+ print()
54
+
55
+ # Binary directory
56
+ try:
57
+ bin_dir = wrapper.get_platform_binary_dir()
58
+ print(f"Binary directory: {bin_dir}")
59
+ print("Binaries installed: Yes")
60
+ print()
61
+
62
+ # List available tools
63
+ if bin_dir.exists():
64
+ binaries = sorted(
65
+ [f.stem for f in bin_dir.iterdir() if f.is_file()],
66
+ key=str.lower,
67
+ )
68
+ print(f"Available tools ({len(binaries)}):")
69
+ for binary in binaries:
70
+ print(f" - {binary}")
71
+ except RuntimeError:
72
+ print("Binaries installed: No")
73
+ print()
74
+ print("To install binaries, run:")
75
+ print(" python scripts/download_binaries.py --current-only")
76
+ print(" python scripts/strip_binaries.py <extracted_dir> <output_dir> --platform <platform>")
77
+
78
+ return 0
79
+
80
+
81
+ def cmd_version(args: argparse.Namespace) -> int:
82
+ """Display version of a specific tool."""
83
+ tool_name: str = args.tool
84
+
85
+ # Map common names to actual tool names
86
+ tool_map = {
87
+ "c": "clang",
88
+ "cpp": "clang++",
89
+ "c++": "clang++",
90
+ "ld": "lld",
91
+ }
92
+
93
+ actual_tool: str = tool_map.get(tool_name, tool_name)
94
+
95
+ try:
96
+ wrapper.find_tool_binary(actual_tool)
97
+ result = wrapper.run_tool(actual_tool, ["--version"])
98
+ return result
99
+ except RuntimeError as e:
100
+ print(f"Error: {e}", file=sys.stderr)
101
+ return 1
102
+
103
+
104
+ def cmd_list_tools(args: argparse.Namespace) -> int:
105
+ """List all available wrapper tools."""
106
+ print("Available clang-tool-chain commands:")
107
+ print("=" * 60)
108
+ print()
109
+
110
+ tools = [
111
+ ("clang-tool-chain-c", "C compiler (clang) - GNU ABI on Windows"),
112
+ ("clang-tool-chain-cpp", "C++ compiler (clang++) - GNU ABI on Windows"),
113
+ ("clang-tool-chain-c-msvc", "C compiler (clang) - MSVC ABI (Windows only)"),
114
+ ("clang-tool-chain-cpp-msvc", "C++ compiler (clang++) - MSVC ABI (Windows only)"),
115
+ ("clang-tool-chain-ld", "LLVM linker (lld/lld-link)"),
116
+ ("clang-tool-chain-ar", "Archive tool (llvm-ar)"),
117
+ ("clang-tool-chain-nm", "Symbol table viewer (llvm-nm)"),
118
+ ("clang-tool-chain-objdump", "Object file dumper (llvm-objdump)"),
119
+ ("clang-tool-chain-objcopy", "Object copying tool (llvm-objcopy)"),
120
+ ("clang-tool-chain-ranlib", "Archive index generator (llvm-ranlib)"),
121
+ ("clang-tool-chain-strip", "Symbol stripper (llvm-strip)"),
122
+ ("clang-tool-chain-readelf", "ELF file reader (llvm-readelf)"),
123
+ ("clang-tool-chain-as", "LLVM assembler (llvm-as)"),
124
+ ("clang-tool-chain-dis", "LLVM disassembler (llvm-dis)"),
125
+ ("clang-tool-chain-format", "Code formatter (clang-format)"),
126
+ ("clang-tool-chain-tidy", "Static analyzer (clang-tidy)"),
127
+ ]
128
+
129
+ for cmd, desc in tools:
130
+ print(f" {cmd:30s} - {desc}")
131
+
132
+ print()
133
+ print("sccache integration (requires sccache in PATH):")
134
+ print("=" * 60)
135
+ print()
136
+
137
+ sccache_tools = [
138
+ ("clang-tool-chain-sccache", "Direct sccache access (stats, management)"),
139
+ ("clang-tool-chain-sccache-c", "sccache + C compiler (clang)"),
140
+ ("clang-tool-chain-sccache-cpp", "sccache + C++ compiler (clang++)"),
141
+ ]
142
+
143
+ for cmd, desc in sccache_tools:
144
+ print(f" {cmd:30s} - {desc}")
145
+
146
+ print()
147
+ print("For more information, run:")
148
+ print(" clang-tool-chain info")
149
+
150
+ return 0
151
+
152
+
153
+ def cmd_path(args: argparse.Namespace) -> int:
154
+ """Display the path to the binary directory or a specific tool."""
155
+ try:
156
+ if args.tool:
157
+ tool_name: str = args.tool
158
+ # Map common names to actual tool names
159
+ tool_map = {
160
+ "c": "clang",
161
+ "cpp": "clang++",
162
+ "c++": "clang++",
163
+ "ld": "lld",
164
+ }
165
+
166
+ actual_tool: str = tool_map.get(tool_name, tool_name)
167
+ tool_path = wrapper.find_tool_binary(actual_tool)
168
+ print(tool_path)
169
+ else:
170
+ bin_dir = wrapper.get_platform_binary_dir()
171
+ print(bin_dir)
172
+ return 0
173
+ except RuntimeError as e:
174
+ print(f"Error: {e}", file=sys.stderr)
175
+ return 1
176
+
177
+
178
+ def cmd_package_version(args: argparse.Namespace) -> int:
179
+ """Display the package version."""
180
+ print(f"clang-tool-chain version: {__version__}")
181
+ print()
182
+
183
+ if args.verbose:
184
+ # Show more detailed version information
185
+ print("Package Information:")
186
+ print("=" * 60)
187
+ print(" Package: clang-tool-chain")
188
+ print(f" Version: {__version__}")
189
+ print()
190
+
191
+ # Try to get actual clang version from installed binaries
192
+ try:
193
+ platform_name, arch = wrapper.get_platform_info()
194
+ print("Platform Information:")
195
+ print(f" Platform: {platform_name}")
196
+ print(f" Architecture: {arch}")
197
+ print()
198
+
199
+ # Try to get installed clang version
200
+ try:
201
+ wrapper.find_tool_binary("clang")
202
+ print("Installed LLVM/Clang:")
203
+ result = wrapper.run_tool("clang", ["--version"])
204
+ if result != 0:
205
+ print(" (Unable to determine version)")
206
+ except RuntimeError:
207
+ print("Installed LLVM/Clang:")
208
+ print(" Not installed yet")
209
+ print()
210
+ print("To install binaries, run:")
211
+ print(" python scripts/download_binaries.py --current-only")
212
+ print(" python scripts/strip_binaries.py <extracted_dir> <output_dir> --platform <platform>")
213
+ except RuntimeError as e:
214
+ print(f"Error: {e}", file=sys.stderr)
215
+
216
+ return 0
217
+
218
+
219
+ def cmd_test(args: argparse.Namespace) -> int:
220
+ """Run diagnostic tests to verify the toolchain installation."""
221
+ import tempfile
222
+ from pathlib import Path
223
+
224
+ print("Clang Tool Chain - Diagnostic Tests")
225
+ print("=" * 70)
226
+ print()
227
+
228
+ # Test 1: Platform Detection
229
+ print("[1/7] Testing platform detection...")
230
+ try:
231
+ platform_name, arch = wrapper.get_platform_info()
232
+ print(f" Platform: {platform_name}/{arch}")
233
+ print(" ✓ PASSED")
234
+ except Exception as e:
235
+ print(f" ✗ FAILED: {e}")
236
+ return 1
237
+ print()
238
+
239
+ # Test 2: Toolchain Download/Installation
240
+ print("[2/7] Testing toolchain installation...")
241
+ try:
242
+ bin_dir = wrapper.get_platform_binary_dir()
243
+ if bin_dir.exists():
244
+ print(f" Binary directory: {bin_dir}")
245
+ print(" ✓ PASSED")
246
+ else:
247
+ print(f" ✗ FAILED: Binary directory does not exist: {bin_dir}")
248
+ return 1
249
+ except Exception as e:
250
+ print(f" ✗ FAILED: {e}")
251
+ return 1
252
+ print()
253
+
254
+ # Test 3: Finding clang binary
255
+ print("[3/7] Testing binary resolution (clang)...")
256
+ try:
257
+ clang_path = wrapper.find_tool_binary("clang")
258
+ print(f" Found: {clang_path}")
259
+ if not clang_path.exists():
260
+ print(f" ✗ FAILED: Binary does not exist: {clang_path}")
261
+ return 1
262
+ print(" ✓ PASSED")
263
+ except Exception as e:
264
+ print(f" ✗ FAILED: {e}")
265
+ return 1
266
+ print()
267
+
268
+ # Test 4: Finding clang++ binary
269
+ print("[4/7] Testing binary resolution (clang++)...")
270
+ try:
271
+ clang_cpp_path = wrapper.find_tool_binary("clang++")
272
+ print(f" Found: {clang_cpp_path}")
273
+ if not clang_cpp_path.exists():
274
+ print(f" ✗ FAILED: Binary does not exist: {clang_cpp_path}")
275
+ return 1
276
+ print(" ✓ PASSED")
277
+ except Exception as e:
278
+ print(f" ✗ FAILED: {e}")
279
+ return 1
280
+ print()
281
+
282
+ # Test 5: Version check for clang
283
+ print("[5/7] Testing clang version...")
284
+ try:
285
+ result = subprocess.run([str(clang_path), "--version"], capture_output=True, text=True, timeout=10)
286
+ if result.returncode == 0:
287
+ version_line = result.stdout.split("\n")[0]
288
+ print(f" {version_line}")
289
+ print(" ✓ PASSED")
290
+ else:
291
+ print(f" ✗ FAILED: clang --version returned {result.returncode}")
292
+ print(f" stderr: {result.stderr}")
293
+ return 1
294
+ except Exception as e:
295
+ print(f" ✗ FAILED: {e}")
296
+ return 1
297
+ print()
298
+
299
+ # Test 6: Simple compilation test
300
+ print("[6/7] Testing C compilation...")
301
+ with tempfile.TemporaryDirectory() as tmpdir:
302
+ tmpdir_path = Path(tmpdir)
303
+ test_c = tmpdir_path / "test.c"
304
+ test_out = tmpdir_path / "test"
305
+ if platform_name == "win":
306
+ test_out = test_out.with_suffix(".exe")
307
+
308
+ # Write simple C program
309
+ test_c.write_text(
310
+ """
311
+ #include <stdio.h>
312
+ int main() {
313
+ printf("Hello from clang-tool-chain!\\n");
314
+ return 0;
315
+ }
316
+ """
317
+ )
318
+
319
+ try:
320
+ # Compile
321
+ result = subprocess.run(
322
+ [str(clang_path), str(test_c), "-o", str(test_out)], capture_output=True, text=True, timeout=30
323
+ )
324
+ if result.returncode != 0:
325
+ print(" ✗ FAILED: Compilation failed")
326
+ print(f" stdout: {result.stdout}")
327
+ print(f" stderr: {result.stderr}")
328
+ return 1
329
+
330
+ # Verify output file was created
331
+ if not test_out.exists():
332
+ print(f" ✗ FAILED: Output binary not created: {test_out}")
333
+ return 1
334
+
335
+ print(f" Compiled: {test_out}")
336
+ print(" ✓ PASSED")
337
+ except Exception as e:
338
+ print(f" ✗ FAILED: {e}")
339
+ return 1
340
+ print()
341
+
342
+ # Test 7: Simple C++ compilation test
343
+ print("[7/7] Testing C++ compilation...")
344
+ with tempfile.TemporaryDirectory() as tmpdir:
345
+ tmpdir_path = Path(tmpdir)
346
+ test_cpp = tmpdir_path / "test.cpp"
347
+ test_out = tmpdir_path / "test"
348
+ if platform_name == "win":
349
+ test_out = test_out.with_suffix(".exe")
350
+
351
+ # Write simple C++ program
352
+ test_cpp.write_text(
353
+ """
354
+ #include <iostream>
355
+ int main() {
356
+ std::cout << "Hello from clang-tool-chain C++!" << std::endl;
357
+ return 0;
358
+ }
359
+ """
360
+ )
361
+
362
+ try:
363
+ # Compile
364
+ result = subprocess.run(
365
+ [str(clang_cpp_path), str(test_cpp), "-o", str(test_out)], capture_output=True, text=True, timeout=30
366
+ )
367
+ if result.returncode != 0:
368
+ print(" ✗ FAILED: Compilation failed")
369
+ print(f" stdout: {result.stdout}")
370
+ print(f" stderr: {result.stderr}")
371
+ return 1
372
+
373
+ # Verify output file was created
374
+ if not test_out.exists():
375
+ print(f" ✗ FAILED: Output binary not created: {test_out}")
376
+ return 1
377
+
378
+ print(f" Compiled: {test_out}")
379
+ print(" ✓ PASSED")
380
+ except Exception as e:
381
+ print(f" ✗ FAILED: {e}")
382
+ return 1
383
+ print()
384
+
385
+ print("=" * 70)
386
+ print("All tests passed! ✓")
387
+ print()
388
+ return 0
389
+
390
+
391
+ def main() -> int:
392
+ """Main entry point for the clang-tool-chain CLI."""
393
+ parser = argparse.ArgumentParser(
394
+ prog="clang-tool-chain",
395
+ description="LLVM/Clang toolchain management and wrapper utilities",
396
+ epilog="For more information, visit: https://github.com/your-repo/clang-tool-chain",
397
+ )
398
+
399
+ subparsers = parser.add_subparsers(
400
+ dest="command",
401
+ help="Available commands",
402
+ )
403
+
404
+ # info command
405
+ parser_info = subparsers.add_parser(
406
+ "info",
407
+ help="Display information about the toolchain installation",
408
+ )
409
+ parser_info.set_defaults(func=cmd_info)
410
+
411
+ # version command
412
+ parser_version = subparsers.add_parser(
413
+ "version",
414
+ help="Display version of a specific tool",
415
+ )
416
+ parser_version.add_argument(
417
+ "tool",
418
+ help="Tool name (e.g., clang, clang++, lld)",
419
+ )
420
+ parser_version.set_defaults(func=cmd_version)
421
+
422
+ # list-tools command
423
+ parser_list = subparsers.add_parser(
424
+ "list-tools",
425
+ help="List all available wrapper tools",
426
+ )
427
+ parser_list.set_defaults(func=cmd_list_tools)
428
+
429
+ # path command
430
+ parser_path = subparsers.add_parser(
431
+ "path",
432
+ help="Display the path to the binary directory or a specific tool",
433
+ )
434
+ parser_path.add_argument(
435
+ "tool",
436
+ nargs="?",
437
+ help="Tool name (optional, prints binary directory if not specified)",
438
+ )
439
+ parser_path.set_defaults(func=cmd_path)
440
+
441
+ # package-version command
442
+ parser_pkg_version = subparsers.add_parser(
443
+ "package-version",
444
+ help="Display the package version and target LLVM version",
445
+ )
446
+ parser_pkg_version.add_argument(
447
+ "-v",
448
+ "--verbose",
449
+ action="store_true",
450
+ help="Show detailed version information including installed LLVM version",
451
+ )
452
+ parser_pkg_version.set_defaults(func=cmd_package_version)
453
+
454
+ # test command
455
+ parser_test = subparsers.add_parser(
456
+ "test",
457
+ help="Run diagnostic tests to verify the toolchain installation",
458
+ )
459
+ parser_test.set_defaults(func=cmd_test)
460
+
461
+ # Parse arguments
462
+ args = parser.parse_args()
463
+
464
+ # If no command specified, show help
465
+ if not args.command:
466
+ parser.print_help()
467
+ return 0
468
+
469
+ # Execute command
470
+ return args.func(args)
471
+
472
+
473
+ def test_main() -> int:
474
+ """
475
+ Standalone entry point for the test command.
476
+
477
+ This allows running: clang-tool-chain-test
478
+ """
479
+ import argparse
480
+
481
+ args = argparse.Namespace() # Empty namespace
482
+ return cmd_test(args)
483
+
484
+
485
+ def sccache_main() -> int:
486
+ """
487
+ Entry point for direct sccache passthrough command.
488
+
489
+ This command allows users to run sccache directly for commands like:
490
+ - clang-tool-chain-sccache --show-stats
491
+ - clang-tool-chain-sccache --zero-stats
492
+ - clang-tool-chain-sccache --start-server
493
+ - clang-tool-chain-sccache --stop-server
494
+
495
+ If sccache is not found in PATH, automatically uses iso-env to run it in an isolated environment.
496
+ """
497
+ args = sys.argv[1:]
498
+ return sccache_runner.run_sccache(args)
499
+
500
+
501
+ def sccache_c_main() -> int:
502
+ """
503
+ Entry point for sccache + clang C compiler wrapper.
504
+
505
+ This command wraps the clang C compiler with sccache for compilation caching.
506
+ If sccache is not found in PATH, automatically uses iso-env to run it in an isolated environment.
507
+ """
508
+ args = sys.argv[1:]
509
+
510
+ # Find the clang binary from clang-tool-chain
511
+ try:
512
+ clang_path = wrapper.find_tool_binary("clang")
513
+ except RuntimeError as e:
514
+ print("=" * 70, file=sys.stderr)
515
+ print("ERROR: Failed to locate clang binary", file=sys.stderr)
516
+ print("=" * 70, file=sys.stderr)
517
+ print(file=sys.stderr)
518
+ print(str(e), file=sys.stderr)
519
+ print(file=sys.stderr)
520
+ print("=" * 70, file=sys.stderr)
521
+ return 1
522
+
523
+ return sccache_runner.run_sccache_with_compiler(str(clang_path), args)
524
+
525
+
526
+ def sccache_cpp_main() -> int:
527
+ """
528
+ Entry point for sccache + clang++ C++ compiler wrapper.
529
+
530
+ This command wraps the clang++ C++ compiler with sccache for compilation caching.
531
+ If sccache is not found in PATH, automatically uses iso-env to run it in an isolated environment.
532
+ """
533
+ args = sys.argv[1:]
534
+
535
+ # Find the clang++ binary from clang-tool-chain
536
+ try:
537
+ clang_cpp_path = wrapper.find_tool_binary("clang++")
538
+ except RuntimeError as e:
539
+ print("=" * 70, file=sys.stderr)
540
+ print("ERROR: Failed to locate clang++ binary", file=sys.stderr)
541
+ print("=" * 70, file=sys.stderr)
542
+ print(file=sys.stderr)
543
+ print(str(e), file=sys.stderr)
544
+ print(file=sys.stderr)
545
+ print("=" * 70, file=sys.stderr)
546
+ return 1
547
+
548
+ return sccache_runner.run_sccache_with_compiler(str(clang_cpp_path), args)
549
+
550
+
551
+ def sccache_c_msvc_main() -> NoReturn:
552
+ """
553
+ Entry point for sccache + clang C compiler wrapper with MSVC ABI.
554
+
555
+ This command wraps the clang C compiler with sccache for compilation caching,
556
+ using MSVC ABI target on Windows.
557
+ """
558
+ wrapper.sccache_clang_main(use_msvc=True)
559
+
560
+
561
+ def sccache_cpp_msvc_main() -> NoReturn:
562
+ """
563
+ Entry point for sccache + clang++ C++ compiler wrapper with MSVC ABI.
564
+
565
+ This command wraps the clang++ C++ compiler with sccache for compilation caching,
566
+ using MSVC ABI target on Windows.
567
+ """
568
+ wrapper.sccache_clang_cpp_main(use_msvc=True)
569
+
570
+
571
+ if __name__ == "__main__":
572
+ sys.exit(main())