mirror of
https://github.com/compiler-explorer/compiler-explorer.git
synced 2025-12-27 07:04:04 -05:00
284 lines
9.0 KiB
Python
Executable File
284 lines
9.0 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
CE Compiler Auto-Discovery Tool
|
|
|
|
Automatically discovers compilers in PATH directories and adds them using
|
|
the CE Properties Wizard.
|
|
"""
|
|
|
|
import argparse
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
from typing import Dict, List, Set
|
|
|
|
|
|
# Compiler patterns for each language
|
|
COMPILER_PATTERNS = {
|
|
'c++': ['g++', 'g++-*', 'clang++', 'clang++-*', 'icpc*', 'icx*'],
|
|
'c': ['gcc', 'gcc-[0-9]*', 'clang', 'clang-[0-9]*', 'icc*', 'cc'],
|
|
'cuda': ['nvcc*'],
|
|
'rust': ['rustc*'],
|
|
'go': ['go', 'gccgo*'],
|
|
'python': ['python*', 'python3*', 'pypy*'],
|
|
'java': ['javac*', 'java'],
|
|
'fortran': ['gfortran*', 'ifort*', 'ifx*'],
|
|
'pascal': ['fpc'],
|
|
'kotlin': ['kotlin*', 'kotlinc*'],
|
|
'zig': ['zig'],
|
|
'dart': ['dart'],
|
|
'd': ['dmd*', 'ldc*', 'ldc2*', 'gdc*'],
|
|
'swift': ['swift*', 'swiftc*'],
|
|
'nim': ['nim'],
|
|
'crystal': ['crystal'],
|
|
'v': ['v'],
|
|
'haskell': ['ghc*'],
|
|
'ocaml': ['ocaml*'],
|
|
'scala': ['scala*', 'scalac*'],
|
|
'csharp': ['csc*', 'mcs*', 'dotnet'],
|
|
'fsharp': ['fsharpc*', 'dotnet'],
|
|
'ruby': ['ruby*'],
|
|
'julia': ['julia'],
|
|
'elixir': ['elixir*'],
|
|
'erlang': ['erlc*', 'erl'],
|
|
'assembly': ['nasm*', 'yasm*', 'as'],
|
|
'carbon': ['carbon*'],
|
|
'mojo': ['mojo*'],
|
|
'odin': ['odin*'],
|
|
'ada': ['gnatmake*', 'gprbuild*', 'gnat*'],
|
|
'cobol': ['cobc*', 'gnucobol*', 'gcobol*'],
|
|
}
|
|
|
|
# Default exclude patterns
|
|
DEFAULT_EXCLUDES = {
|
|
'wrapper', 'distcc', 'ccache', '-config', 'config-',
|
|
'-ar', '-nm', '-ranlib', '-strip', 'filt', 'format',
|
|
'calls', 'flow', 'stat', '-gdb', 'argcomplete', 'build',
|
|
'ldconfig', 'ldconfig.real', '-bpfcc', 'bpfcc', 'scalar',
|
|
'pythongc-bpfcc', 'pythonflow-bpfcc', 'pythoncalls-bpfcc', 'pythonstat-bpfcc'
|
|
}
|
|
|
|
|
|
def get_path_dirs() -> List[Path]:
|
|
"""Get all directories from PATH environment variable."""
|
|
path = os.environ.get('PATH', '')
|
|
return [Path(p) for p in path.split(':') if p.strip()]
|
|
|
|
|
|
def should_exclude(name: str, excludes: Set[str]) -> bool:
|
|
"""Check if a compiler name should be excluded."""
|
|
return any(exclude in name for exclude in excludes)
|
|
|
|
|
|
def find_compilers_in_dir(directory: Path, patterns: List[str], excludes: Set[str]) -> List[Path]:
|
|
"""Find compilers matching patterns in a directory."""
|
|
compilers = []
|
|
|
|
if not directory.exists() or not directory.is_dir():
|
|
return compilers
|
|
|
|
for pattern in patterns:
|
|
# Simple glob matching
|
|
for compiler in directory.glob(pattern):
|
|
if (compiler.is_file() or compiler.is_symlink()) and \
|
|
os.access(compiler, os.X_OK) and \
|
|
not should_exclude(compiler.name, excludes):
|
|
compilers.append(compiler)
|
|
|
|
return compilers
|
|
|
|
|
|
def resolve_duplicates(compilers: List[Path]) -> List[Path]:
|
|
"""Remove duplicate compilers (same resolved path)."""
|
|
seen = set()
|
|
unique_compilers = []
|
|
|
|
for compiler in compilers:
|
|
try:
|
|
resolved = compiler.resolve()
|
|
if resolved not in seen:
|
|
seen.add(resolved)
|
|
unique_compilers.append(compiler)
|
|
except (OSError, RuntimeError):
|
|
# If we can't resolve, keep the original
|
|
unique_compilers.append(compiler)
|
|
|
|
return unique_compilers
|
|
|
|
|
|
def discover_compilers(languages: List[str], search_dirs: List[Path] = None,
|
|
excludes: Set[str] = None) -> Dict[str, List[Path]]:
|
|
"""Discover compilers for specified languages."""
|
|
if search_dirs is None:
|
|
search_dirs = get_path_dirs()
|
|
|
|
if excludes is None:
|
|
excludes = DEFAULT_EXCLUDES
|
|
|
|
discovered = {}
|
|
|
|
for language in languages:
|
|
if language not in COMPILER_PATTERNS:
|
|
print(f"Warning: Unknown language '{language}'", file=sys.stderr)
|
|
continue
|
|
|
|
patterns = COMPILER_PATTERNS[language]
|
|
compilers = []
|
|
|
|
for directory in search_dirs:
|
|
compilers.extend(find_compilers_in_dir(directory, patterns, excludes))
|
|
|
|
if compilers:
|
|
# Remove duplicates and sort
|
|
unique_compilers = resolve_duplicates(compilers)
|
|
discovered[language] = sorted(unique_compilers, key=lambda x: x.name)
|
|
|
|
return discovered
|
|
|
|
|
|
def add_compiler_with_wizard(compiler: Path, language: str, script_dir: Path,
|
|
wizard_args: List[str], dry_run: bool) -> bool:
|
|
"""Add a compiler using the CE Properties Wizard."""
|
|
if dry_run:
|
|
return True
|
|
|
|
cmd = [
|
|
str(script_dir / 'run.sh'),
|
|
str(compiler),
|
|
'--yes',
|
|
'--language', language
|
|
] + wizard_args
|
|
|
|
try:
|
|
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
return result.returncode == 0
|
|
except Exception:
|
|
return False
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(
|
|
description='CE Compiler Auto-Discovery Tool',
|
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
epilog="""
|
|
Examples:
|
|
%(prog)s # Interactive discovery of all languages
|
|
%(prog)s --dry-run # Preview what would be discovered
|
|
%(prog)s --languages c++,rust,go # Only discover C++, Rust, and Go
|
|
%(prog)s --yes --languages c++,c # Non-interactive C/C++ discovery
|
|
""")
|
|
|
|
parser.add_argument('--languages',
|
|
help='Comma-separated list of languages to discover (default: all)')
|
|
parser.add_argument('--search-dirs',
|
|
help='Colon-separated search directories (default: PATH dirs)')
|
|
parser.add_argument('--exclude',
|
|
help='Comma-separated exclude patterns')
|
|
parser.add_argument('--dry-run', action='store_true',
|
|
help='Show what would be added without making changes')
|
|
parser.add_argument('--yes', '-y', action='store_true',
|
|
help='Skip confirmation prompts')
|
|
parser.add_argument('--config-dir',
|
|
help='Path to etc/config directory')
|
|
parser.add_argument('--env', default='local',
|
|
help='Environment to target (local, amazon, etc.)')
|
|
|
|
args = parser.parse_args()
|
|
|
|
# Get script directory
|
|
script_dir = Path(__file__).parent
|
|
wizard_script = script_dir / 'run.sh'
|
|
|
|
if not wizard_script.exists():
|
|
print(f"Error: CE Properties Wizard not found at {wizard_script}", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
# Parse languages
|
|
if args.languages:
|
|
languages = [lang.strip() for lang in args.languages.split(',')]
|
|
else:
|
|
languages = list(COMPILER_PATTERNS.keys())
|
|
|
|
# Parse search directories
|
|
search_dirs = None
|
|
if args.search_dirs:
|
|
search_dirs = [Path(d) for d in args.search_dirs.split(':') if d.strip()]
|
|
|
|
# Parse excludes
|
|
excludes = DEFAULT_EXCLUDES.copy()
|
|
if args.exclude:
|
|
excludes.update(args.exclude.split(','))
|
|
|
|
# Discover compilers
|
|
print("CE Compiler Auto-Discovery Tool")
|
|
print("=" * 35)
|
|
print()
|
|
|
|
if args.dry_run:
|
|
print("DRY RUN MODE - No compilers will actually be added")
|
|
print()
|
|
|
|
discovered = discover_compilers(languages, search_dirs, excludes)
|
|
|
|
if not discovered:
|
|
print("No compilers found matching the specified criteria")
|
|
sys.exit(1)
|
|
|
|
# Show results
|
|
total_count = sum(len(compilers) for compilers in discovered.values())
|
|
print(f"Found {total_count} compilers:")
|
|
print()
|
|
|
|
for language, compilers in discovered.items():
|
|
print(f"{language.upper()} ({len(compilers)} compilers):")
|
|
for compiler in compilers:
|
|
print(f" ✓ {compiler}")
|
|
print()
|
|
|
|
# Confirm before adding
|
|
if not args.dry_run and not args.yes:
|
|
response = input("Add these compilers? [y/N] ")
|
|
if not response.lower().startswith('y'):
|
|
print("Operation cancelled")
|
|
sys.exit(0)
|
|
|
|
if args.dry_run:
|
|
print("Dry run complete - no changes made")
|
|
sys.exit(0)
|
|
|
|
# Add compilers
|
|
print("Adding compilers using CE Properties Wizard...")
|
|
print()
|
|
|
|
wizard_args = []
|
|
if args.config_dir:
|
|
wizard_args.extend(['--config-dir', args.config_dir])
|
|
if args.env != 'local':
|
|
wizard_args.extend(['--env', args.env])
|
|
|
|
added_count = 0
|
|
failed_count = 0
|
|
|
|
for language, compilers in discovered.items():
|
|
for compiler in compilers:
|
|
print(f"Adding {compiler} ({language})...", end=' ')
|
|
|
|
if add_compiler_with_wizard(compiler, language, script_dir, wizard_args, args.dry_run):
|
|
print("✓")
|
|
added_count += 1
|
|
else:
|
|
print("✗")
|
|
failed_count += 1
|
|
|
|
print()
|
|
print("Summary:")
|
|
print(f" ✓ Successfully added: {added_count} compilers")
|
|
if failed_count > 0:
|
|
print(f" ✗ Failed to add: {failed_count} compilers")
|
|
print()
|
|
print("Auto-discovery complete!")
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main() |