UNPKG

@re-shell/cli

Version:

Full-stack development platform uniting microservices and microfrontends. Build complete applications with .NET (ASP.NET Core Web API, Minimal API), Java (Spring Boot, Quarkus, Micronaut, Vert.x), Rust (Actix-Web, Warp, Rocket, Axum), Python (FastAPI, Dja

1,673 lines (1,428 loc) 70.9 kB
"use strict"; /** * C++ Hot-Reload Development Generator * Generates file watching and hot-reload configuration for C++ projects */ Object.defineProperty(exports, "__esModule", { value: true }); exports.CppHotReloadGenerator = void 0; class CppHotReloadGenerator { static generate(config) { const { projectName, watcherTool = 'entr', buildTool = 'cmake', reloadStrategy = 'incremental', enableTests = true, enableBenchmarks = false, enableDebugger = false, customWatchPaths = [] } = config; return { 'scripts/hot-reload.sh': this.generateHotReloadScript(projectName, { watcherTool, buildTool, reloadStrategy, enableTests, enableBenchmarks, enableDebugger, customWatchPaths }), 'scripts/watch-and-build.py': this.generateWatchAndBuildScript(), 'hot-reload/entr-setup.sh': this.generateEntrSetup(), 'hot-reload/watchman-config.json': this.generateWatchmanConfig(projectName), 'hot-reload/inotify-setup.sh': this.generateInotifySetup(), 'hot-reload/fswatch-setup.sh': this.generateFswatchSetup(), '.watchmanconfig': this.generateWatchmanProjectConfig(), 'hot-reload/README.md': this.generateHotReloadReadme(projectName), 'cmake/HotReload.cmake': this.generateHotReloadCMake(), 'hot-reload/ccache.conf': this.generateCcacheConfig(), 'hot-reload/distcc.conf': this.generateDistccConfig(), '.clangd': this.generateClangdConfig(), 'scripts/development-server.sh': this.generateDevelopmentServer(projectName), 'docker/Dockerfile.dev': this.generateDevDockerfile(projectName), 'docker-compose.dev.yml': this.generateDevDockerCompose(projectName), '.vscode/tasks.json': this.generateVSCodeTasks(), '.vscode/launch.json': this.generateVSCodeLaunch(projectName) }; } static generateHotReloadScript(projectName, options) { return `#!/bin/bash # Hot-Reload Development Script for ${projectName} # Automatically rebuilds and reloads on file changes set -euo pipefail # Configuration PROJECT_NAME="${projectName}" BUILD_DIR="build" SOURCE_DIR="src" INCLUDE_DIR="include" TEST_DIR="tests" WATCHER_TOOL="${options.watcherTool}" BUILD_TOOL="${options.buildTool}" RELOAD_STRATEGY="${options.reloadStrategy}" ENABLE_TESTS=${options.enableTests} ENABLE_BENCHMARKS=${options.enableBenchmarks} ENABLE_DEBUGGER=${options.enableDebugger} # Colors for output RED='\\033[0;31m' GREEN='\\033[0;32m' BLUE='\\033[0;34m' YELLOW='\\033[1;33m' NC='\\033[0m' # No Color # Default watch paths WATCH_PATHS=( "\\${SOURCE_DIR}" "\\${INCLUDE_DIR}" "CMakeLists.txt" ) # Add test directory if enabled if [[ "\\${ENABLE_TESTS}" == "true" ]]; then WATCH_PATHS+=("\\${TEST_DIR}") fi # Add custom watch paths CUSTOM_PATHS=(${options.customWatchPaths.map(p => `"${p}"`).join(' ')}) WATCH_PATHS+=("\\${CUSTOM_PATHS[]}") echo -e "\\${BLUE}=== C++ Hot-Reload Development Server ===\\${NC}" echo "Project: \\${PROJECT_NAME}" echo "Watcher: \\${WATCHER_TOOL}" echo "Build Tool: \\${BUILD_TOOL}" echo "Strategy: \\${RELOAD_STRATEGY}" echo "" # Check dependencies check_dependencies() { local missing_deps=() # Check watcher tool case "\\${WATCHER_TOOL}" in entr) if ! command -v entr &> /dev/null; then missing_deps+=("entr") fi ;; watchman) if ! command -v watchman &> /dev/null; then missing_deps+=("watchman") fi ;; inotify) if ! command -v inotifywait &> /dev/null; then missing_deps+=("inotify-tools") fi ;; fswatch) if ! command -v fswatch &> /dev/null; then missing_deps+=("fswatch") fi ;; esac # Check build tool case "\\${BUILD_TOOL}" in cmake) if ! command -v cmake &> /dev/null; then missing_deps+=("cmake") fi ;; make) if ! command -v make &> /dev/null; then missing_deps+=("make") fi ;; ninja) if ! command -v ninja &> /dev/null; then missing_deps+=("ninja") fi ;; bazel) if ! command -v bazel &> /dev/null; then missing_deps+=("bazel") fi ;; esac # Check optional tools if command -v ccache &> /dev/null; then echo -e "\\${GREEN}✓ ccache detected - build caching enabled\\${NC}" export CC="ccache gcc" export CXX="ccache g++" fi if command -v distcc &> /dev/null; then echo -e "\\${GREEN}✓ distcc detected - distributed compilation available\\${NC}" fi if [ \\${[]} -gt 0 ]; then echo -e "\\${RED}Missing dependencies: \\${missing_deps[ * ]}\\${NC}" echo "Please install missing dependencies and try again." exit 1 fi } # Initial build initial_build() { echo -e "\\${YELLOW}Performing initial build...\\${NC}" case "\\${BUILD_TOOL}" in cmake) if [ ! -d "\\${BUILD_DIR}" ]; then cmake -B "\\${BUILD_DIR}" \\ -DCMAKE_BUILD_TYPE=Debug \\ -DCMAKE_EXPORT_COMPILE_COMMANDS=ON \\ -DENABLE_HOT_RELOAD=ON fi cmake --build "\\${BUILD_DIR}" --parallel ;; make) make -j\\$(nproc) ;; ninja) ninja -C "\\${BUILD_DIR}" ;; bazel) bazel build //:all ;; esac echo -e "\\${GREEN}✓ Initial build complete\\${NC}" } # Incremental build function incremental_build() { local changed_file="\\$1" echo -e "\\${YELLOW}File changed: \\${changed_file}\\${NC}" local start_time=\\$(date +%s.%N) case "\\${RELOAD_STRATEGY}" in rebuild) # Full rebuild case "\\${BUILD_TOOL}" in cmake) cmake --build "\\${BUILD_DIR}" --parallel ;; make) make -j\\$(nproc) ;; ninja) ninja -C "\\${BUILD_DIR}" ;; bazel) bazel build //:all ;; esac ;; incremental) # Incremental build (default for most build tools) case "\\${BUILD_TOOL}" in cmake) cmake --build "\\${BUILD_DIR}" --parallel --target \\${PROJECT_NAME} ;; make) make -j\\$(nproc) \\${PROJECT_NAME} ;; ninja) ninja -C "\\${BUILD_DIR}" \\${PROJECT_NAME} ;; bazel) bazel build //:${projectName} ;; esac ;; module) # Module-specific rebuild local module=\\$(determine_module "\\${changed_file}") case "\\${BUILD_TOOL}" in cmake) cmake --build "\\${BUILD_DIR}" --parallel --target "\\${module}" ;; *) # Fallback to incremental incremental_build "\\${changed_file}" ;; esac ;; esac local end_time=\\$(date +%s.%N) local build_time=\\$(echo "\\${end_time} - \\${start_time}" | bc) echo -e "\\${GREEN}✓ Build completed in \\${build_time}s\\${NC}" # Run tests if enabled if [[ "\\${ENABLE_TESTS}" == "true" ]]; then run_tests fi # Run benchmarks if enabled if [[ "\\${ENABLE_BENCHMARKS}" == "true" ]]; then run_benchmarks fi } # Determine module from file path determine_module() { local file="\\$1" # Simple heuristic - can be customized basename "\\${file % . * }" } # Run tests run_tests() { echo -e "\\${YELLOW}Running tests...\\${NC}" case "\\${BUILD_TOOL}" in cmake) cd "\\${BUILD_DIR}" && ctest --output-on-failure || true cd .. ;; make) make test || true ;; bazel) bazel test //:all || true ;; esac } # Run benchmarks run_benchmarks() { echo -e "\\${YELLOW}Running benchmarks...\\${NC}" if [ -f "\\${BUILD_DIR}/benchmarks/\\${PROJECT_NAME}_benchmark" ]; then "\\${BUILD_DIR}/benchmarks/\\${PROJECT_NAME}_benchmark" --benchmark_format=json || true fi } # File watcher functions watch_with_entr() { echo -e "\\${BLUE}Starting entr file watcher...\\${NC}" while true; do find \\${WATCH_PATHS[]} \\ \\( -name "*.cpp" -o -name "*.cc" -o -name "*.cxx" \\ -o -name "*.h" -o -name "*.hpp" -o -name "*.hxx" \\ -o -name "CMakeLists.txt" -o -name "*.cmake" \\) \\ 2>/dev/null | \\ entr -d -c bash -c "incremental_build \\$0" # entr exits when directory structure changes echo -e "\\${YELLOW}Directory structure changed, restarting watcher...\\${NC}" sleep 1 done } watch_with_watchman() { echo -e "\\${BLUE}Starting watchman file watcher...\\${NC}" # Start watchman watchman watch . # Subscribe to file changes watchman -j <<-EOT ["subscribe", ".", "hot-reload", { "expression": ["anyof", ["suffix", "cpp"], ["suffix", "cc"], ["suffix", "cxx"], ["suffix", "h"], ["suffix", "hpp"], ["suffix", "hxx"], ["name", "CMakeLists.txt"], ["suffix", "cmake"] ], "fields": ["name", "type"] }] EOT # Process watchman events watchman --json-command <<< '["since", ".", "c:0:0"]' | \\ while IFS= read -r line; do if echo "\\${line}" | jq -e '.files[]' > /dev/null 2>&1; then changed_file=\\$(echo "\\${line}" | jq -r '.files[0].name') incremental_build "\\${changed_file}" fi done } watch_with_inotify() { echo -e "\\${BLUE}Starting inotify file watcher...\\${NC}" inotifywait -mr --timefmt '%Y-%m-%d %H:%M:%S' --format '%T %w %f %e' \\ -e modify,create,delete,move \\ --include '.*\\.(cpp|cc|cxx|h|hpp|hxx|cmake)$|CMakeLists\\.txt$' \\ \\${WATCH_PATHS[]} | while read date time dir file event; do if [[ "\\${event}" =~ (MODIFY|CREATE|MOVED_TO) ]]; then incremental_build "\\${dir}\\${file}" fi done } watch_with_fswatch() { echo -e "\\${BLUE}Starting fswatch file watcher...\\${NC}" fswatch -r -e ".*" -i "\\\\.cpp$" -i "\\\\.cc$" -i "\\\\.cxx$" \\ -i "\\\\.h$" -i "\\\\.hpp$" -i "\\\\.hxx$" \\ -i "CMakeLists\\\\.txt$" -i "\\\\.cmake$" \\ \\${WATCH_PATHS[]} | while read changed_file; do incremental_build "\\${changed_file}" done } # Signal handlers cleanup() { echo -e "\\n\\${YELLOW}Shutting down hot-reload server...\\${NC}" # Stop watchman if running if [[ "\\${WATCHER_TOOL}" == "watchman" ]]; then watchman shutdown-server 2>/dev/null || true fi exit 0 } trap cleanup SIGINT SIGTERM # Main execution main() { check_dependencies initial_build # Start appropriate watcher case "\\${WATCHER_TOOL}" in entr) watch_with_entr ;; watchman) watch_with_watchman ;; inotify) watch_with_inotify ;; fswatch) watch_with_fswatch ;; *) echo -e "\\${RED}Unknown watcher tool: \\${WATCHER_TOOL}\\${NC}" exit 1 ;; esac } # Run main function main`; } static generateWatchAndBuildScript() { return `#!/usr/bin/env python3 """ Advanced Watch and Build Script for C++ Projects Provides intelligent file watching and incremental building """ import os import sys import time import subprocess import hashlib import json import threading import queue from pathlib import Path from datetime import datetime from typing import Dict, Set, List, Optional, Tuple import argparse try: from watchdog.observers import Observer from watchdog.events import FileSystemEventHandler, FileModifiedEvent except ImportError: print("Please install watchdog: pip install watchdog") sys.exit(1) class BuildCache: """Manages build cache for incremental compilation.""" def __init__(self, cache_file: str = ".build_cache.json"): self.cache_file = cache_file self.cache = self.load_cache() def load_cache(self) -> Dict[str, str]: """Load build cache from file.""" try: with open(self.cache_file, 'r') as f: return json.load(f) except (FileNotFoundError, json.JSONDecodeError): return {} def save_cache(self): """Save build cache to file.""" with open(self.cache_file, 'w') as f: json.dump(self.cache, f, indent=2) def get_file_hash(self, filepath: str) -> str: """Calculate hash of file contents.""" hasher = hashlib.sha256() try: with open(filepath, 'rb') as f: while chunk := f.read(8192): hasher.update(chunk) return hasher.hexdigest() except FileNotFoundError: return "" def has_changed(self, filepath: str) -> bool: """Check if file has changed since last build.""" current_hash = self.get_file_hash(filepath) cached_hash = self.cache.get(filepath, "") if current_hash != cached_hash: self.cache[filepath] = current_hash return True return False def update_file(self, filepath: str): """Update cache for a file.""" self.cache[filepath] = self.get_file_hash(filepath) def remove_file(self, filepath: str): """Remove file from cache.""" self.cache.pop(filepath, None) class DependencyGraph: """Manages C++ file dependencies.""" def __init__(self, build_dir: str = "build"): self.build_dir = build_dir self.dependencies = {} self.load_compile_commands() def load_compile_commands(self): """Load compile_commands.json for dependency analysis.""" compile_commands_path = os.path.join(self.build_dir, "compile_commands.json") try: with open(compile_commands_path, 'r') as f: self.compile_commands = json.load(f) except FileNotFoundError: self.compile_commands = [] def analyze_dependencies(self, source_file: str) -> Set[str]: """Analyze dependencies for a source file.""" dependencies = set() # Use compiler to get dependencies for command in self.compile_commands: if command.get('file') == source_file: # Extract include paths cmd_parts = command['command'].split() include_paths = [] for i, part in enumerate(cmd_parts): if part == '-I' and i + 1 < len(cmd_parts): include_paths.append(cmd_parts[i + 1]) elif part.startswith('-I'): include_paths.append(part[2:]) # Get dependencies using compiler try: result = subprocess.run( ['g++', '-MM', '-MG'] + [f'-I{path}' for path in include_paths] + [source_file], capture_output=True, text=True ) if result.returncode == 0: # Parse dependency output deps = result.stdout.replace('\\\\', '').replace('\\n', ' ').split()[1:] dependencies.update(deps) except subprocess.SubprocessError: pass break return dependencies def get_affected_files(self, changed_file: str) -> Set[str]: """Get all files affected by a change.""" affected = {changed_file} # Find all files that depend on the changed file for source_file in self.compile_commands: deps = self.analyze_dependencies(source_file.get('file', '')) if changed_file in deps: affected.add(source_file.get('file', '')) return affected class BuildQueue: """Manages build tasks with deduplication.""" def __init__(self): self.queue = queue.Queue() self.pending = set() self.lock = threading.Lock() def add_task(self, task: str): """Add a build task if not already pending.""" with self.lock: if task not in self.pending: self.pending.add(task) self.queue.put(task) def get_task(self) -> Optional[str]: """Get next build task.""" try: task = self.queue.get(timeout=0.1) with self.lock: self.pending.discard(task) return task except queue.Empty: return None def clear(self): """Clear all pending tasks.""" with self.lock: while not self.queue.empty(): try: self.queue.get_nowait() except queue.Empty: break self.pending.clear() class CppFileHandler(FileSystemEventHandler): """Handles file system events for C++ files.""" def __init__(self, build_queue: BuildQueue, cache: BuildCache, dependency_graph: DependencyGraph): self.build_queue = build_queue self.cache = cache self.dependency_graph = dependency_graph self.last_event_time = {} self.debounce_delay = 0.5 # seconds def should_process_file(self, filepath: str) -> bool: """Check if file should trigger a build.""" if not filepath: return False # Check file extensions extensions = {'.cpp', '.cc', '.cxx', '.c', '.h', '.hpp', '.hxx', '.cmake'} if Path(filepath).suffix not in extensions and not filepath.endswith('CMakeLists.txt'): return False # Debounce rapid events current_time = time.time() last_time = self.last_event_time.get(filepath, 0) if current_time - last_time < self.debounce_delay: return False self.last_event_time[filepath] = current_time return True def on_modified(self, event): """Handle file modification events.""" if not event.is_directory and self.should_process_file(event.src_path): if self.cache.has_changed(event.src_path): # Get affected files affected = self.dependency_graph.get_affected_files(event.src_path) for file in affected: self.build_queue.add_task(file) print(f"[{datetime.now().strftime('%H:%M:%S')}] File modified: {event.src_path}") print(f" Affected files: {len(affected)}") class IncrementalBuilder: """Manages incremental C++ builds.""" def __init__(self, project_name: str, build_dir: str = "build", build_tool: str = "cmake"): self.project_name = project_name self.build_dir = build_dir self.build_tool = build_tool self.build_times = [] self.success_count = 0 self.failure_count = 0 def build(self, target: Optional[str] = None) -> bool: """Perform incremental build.""" start_time = time.time() try: if self.build_tool == "cmake": cmd = ["cmake", "--build", self.build_dir] if target: cmd.extend(["--target", os.path.splitext(os.path.basename(target))[0]]) cmd.append("--parallel") elif self.build_tool == "make": cmd = ["make", "-C", self.build_dir, f"-j{os.cpu_count()}"] if target: cmd.append(os.path.splitext(os.path.basename(target))[0]) elif self.build_tool == "ninja": cmd = ["ninja", "-C", self.build_dir] if target: cmd.append(os.path.splitext(os.path.basename(target))[0]) else: print(f"Unsupported build tool: {self.build_tool}") return False result = subprocess.run(cmd, capture_output=True, text=True) build_time = time.time() - start_time self.build_times.append(build_time) if result.returncode == 0: self.success_count += 1 print(f"✓ Build successful in {build_time:.2f}s") return True else: self.failure_count += 1 print(f"✗ Build failed in {build_time:.2f}s") print("Error output:") print(result.stderr) return False except subprocess.SubprocessError as e: self.failure_count += 1 print(f"✗ Build error: {e}") return False def print_statistics(self): """Print build statistics.""" if self.build_times: avg_time = sum(self.build_times) / len(self.build_times) total_builds = self.success_count + self.failure_count success_rate = (self.success_count / total_builds * 100) if total_builds > 0 else 0 print("\\n=== Build Statistics ===") print(f"Total builds: {total_builds}") print(f"Successful: {self.success_count}") print(f"Failed: {self.failure_count}") print(f"Success rate: {success_rate:.1f}%") print(f"Average build time: {avg_time:.2f}s") print(f"Fastest build: {min(self.build_times):.2f}s") print(f"Slowest build: {max(self.build_times):.2f}s") class HotReloadServer: """Main hot-reload server.""" def __init__(self, project_name: str, watch_paths: List[str], build_tool: str = "cmake"): self.project_name = project_name self.watch_paths = watch_paths self.build_tool = build_tool self.cache = BuildCache() self.dependency_graph = DependencyGraph() self.build_queue = BuildQueue() self.builder = IncrementalBuilder(project_name, build_tool=build_tool) self.observer = None self.running = False def initial_build(self): """Perform initial build.""" print("Performing initial build...") # Configure build if needed if not os.path.exists(self.builder.build_dir): os.makedirs(self.builder.build_dir) subprocess.run([ "cmake", "-B", self.builder.build_dir, "-DCMAKE_BUILD_TYPE=Debug", "-DCMAKE_EXPORT_COMPILE_COMMANDS=ON" ]) # Full build if self.builder.build(): print("✓ Initial build complete") # Reload dependency graph self.dependency_graph.load_compile_commands() else: print("✗ Initial build failed") sys.exit(1) def build_worker(self): """Worker thread for processing build tasks.""" while self.running: task = self.build_queue.get_task() if task: print(f"\\nBuilding: {task}") if self.builder.build(task): self.cache.save_cache() # Run tests if configured self.run_tests() else: time.sleep(0.1) def run_tests(self): """Run tests after successful build.""" # Check if tests are configured test_executable = os.path.join(self.builder.build_dir, "tests", f"{self.project_name}_test") if os.path.exists(test_executable): print("Running tests...") result = subprocess.run([test_executable], capture_output=True, text=True) if result.returncode == 0: print("✓ All tests passed") else: print("✗ Tests failed") print(result.stdout) def start(self): """Start the hot-reload server.""" self.running = True # Perform initial build self.initial_build() # Start build worker thread build_thread = threading.Thread(target=self.build_worker, daemon=True) build_thread.start() # Setup file watcher event_handler = CppFileHandler(self.build_queue, self.cache, self.dependency_graph) self.observer = Observer() for path in self.watch_paths: if os.path.exists(path): self.observer.schedule(event_handler, path, recursive=True) print(f"Watching: {path}") self.observer.start() print(f"\\n🔥 Hot-reload server started for {self.project_name}") print("Press Ctrl+C to stop\\n") try: while self.running: time.sleep(1) except KeyboardInterrupt: self.stop() def stop(self): """Stop the hot-reload server.""" print("\\nShutting down hot-reload server...") self.running = False if self.observer: self.observer.stop() self.observer.join() self.builder.print_statistics() self.cache.save_cache() print("Hot-reload server stopped") def main(): parser = argparse.ArgumentParser(description='C++ Hot-Reload Development Server') parser.add_argument('project_name', help='Project name') parser.add_argument('--watch', '-w', nargs='+', default=['src', 'include', 'tests'], help='Paths to watch for changes') parser.add_argument('--build-tool', '-b', choices=['cmake', 'make', 'ninja'], default='cmake', help='Build tool to use') parser.add_argument('--build-dir', '-d', default='build', help='Build directory') args = parser.parse_args() server = HotReloadServer( project_name=args.project_name, watch_paths=args.watch, build_tool=args.build_tool ) server.start() if __name__ == '__main__': main()`; } static generateEntrSetup() { return `#!/bin/bash # Setup script for entr file watcher set -euo pipefail echo "Setting up entr for hot-reload development..." # Check if entr is installed if command -v entr &> /dev/null; then echo "✓ entr is already installed" entr_version=$(entr 2>&1 | head -n1 || echo "Unknown version") echo " Version: $entr_version" else echo "Installing entr..." # Detect OS and install entr if [[ "$OSTYPE" == "linux-gnu"* ]]; then # Linux if command -v apt-get &> /dev/null; then # Debian/Ubuntu sudo apt-get update sudo apt-get install -y entr elif command -v yum &> /dev/null; then # RHEL/CentOS/Fedora sudo yum install -y entr elif command -v pacman &> /dev/null; then # Arch Linux sudo pacman -S --noconfirm entr else echo "Please install entr manually for your Linux distribution" exit 1 fi elif [[ "$OSTYPE" == "darwin"* ]]; then # macOS if command -v brew &> /dev/null; then brew install entr else echo "Please install Homebrew first: https://brew.sh" exit 1 fi elif [[ "$OSTYPE" == "freebsd"* ]]; then # FreeBSD sudo pkg install -y entr else echo "Unsupported OS: $OSTYPE" echo "Please install entr manually" exit 1 fi fi # Create example entr usage script cat > entr_example.sh << 'EOF' #!/bin/bash # Example entr usage for C++ development # Watch C++ source files and rebuild on change find src include -name "*.cpp" -o -name "*.h" | entr -c make # Watch and run tests on change find src tests -name "*.cpp" -o -name "*.h" | entr -c make test # Watch with custom command find . -name "*.cpp" -o -name "*.h" | entr -c bash -c 'clear && make && ./build/app' # Watch with notification on success/failure find src -name "*.cpp" | entr -c bash -c 'make && notify-send "Build Success" || notify-send "Build Failed"' EOF chmod +x entr_example.sh echo "" echo "✓ entr setup complete!" echo "" echo "Example usage:" echo " ./entr_example.sh" echo "" echo "For more information:" echo " man entr" echo " http://eradman.com/entrproject/"`; } static generateWatchmanConfig(projectName) { return `{ "ignore_dirs": [ "build", ".git", ".cache", "node_modules", "cmake-build-debug", "cmake-build-release", ".idea", ".vscode" ], "ignore_vcs": ["git"], "settle": 500, "root_files": ["CMakeLists.txt", ".watchmanconfig"], "prefer_watchman_since": true, "gc_age_seconds": 3600, "gc_interval_seconds": 600, "hint_num_files_per_dir": 50, "subscriptions": { "cpp-hot-reload": { "expression": [ "anyof", ["suffix", "cpp"], ["suffix", "cc"], ["suffix", "cxx"], ["suffix", "c"], ["suffix", "h"], ["suffix", "hpp"], ["suffix", "hxx"], ["name", "CMakeLists.txt"], ["suffix", "cmake"] ], "fields": ["name", "size", "mtime_ms", "exists", "type"], "since": "c:0:0", "defer": ["hg.update"], "drop": ["hg.update"] } }, "triggers": [ { "name": "cpp-rebuild", "expression": [ "anyof", ["suffix", "cpp"], ["suffix", "cc"], ["suffix", "h"], ["suffix", "hpp"] ], "command": ["cmake", "--build", "build", "--parallel"], "append_files": false, "stdin": ["name"], "stdout": ">build.log", "stderr": ">build-error.log", "max_files_stdin": 100 } ] }`; } static generateInotifySetup() { return `#!/bin/bash # Setup script for inotify-tools set -euo pipefail echo "Setting up inotify-tools for hot-reload development..." # Check if inotify-tools is installed if command -v inotifywait &> /dev/null; then echo "✓ inotify-tools is already installed" inotifywait --version | head -n1 else echo "Installing inotify-tools..." # Only works on Linux if [[ "$OSTYPE" != "linux-gnu"* ]]; then echo "Error: inotify-tools is only available on Linux" echo "Consider using fswatch or entr on other platforms" exit 1 fi # Detect Linux distribution and install if command -v apt-get &> /dev/null; then # Debian/Ubuntu sudo apt-get update sudo apt-get install -y inotify-tools elif command -v yum &> /dev/null; then # RHEL/CentOS/Fedora sudo yum install -y inotify-tools elif command -v pacman &> /dev/null; then # Arch Linux sudo pacman -S --noconfirm inotify-tools elif command -v zypper &> /dev/null; then # openSUSE sudo zypper install -y inotify-tools else echo "Please install inotify-tools manually for your Linux distribution" exit 1 fi fi # Check and increase inotify watch limit if needed current_limit=$(cat /proc/sys/fs/inotify/max_user_watches) recommended_limit=524288 if [ "$current_limit" -lt "$recommended_limit" ]; then echo "Current inotify watch limit: $current_limit" echo "Increasing to recommended limit: $recommended_limit" # Temporary increase sudo sysctl fs.inotify.max_user_watches=$recommended_limit # Permanent increase echo "fs.inotify.max_user_watches=$recommended_limit" | sudo tee -a /etc/sysctl.conf echo "✓ Increased inotify watch limit" else echo "✓ inotify watch limit is sufficient: $current_limit" fi # Create example inotify usage script cat > inotify_example.sh << 'EOF' #!/bin/bash # Example inotify usage for C++ development # Watch single directory inotifywait -m -r -e modify,create,delete,move src/ | while read path action file; do echo "File $file in $path was $action" make done # Watch multiple directories with filtering inotifywait -m -r -e modify,create,delete \ --exclude '\\.(o|so|a|tmp)$' \ --format '%w%f %e %T' \ --timefmt '%Y-%m-%d %H:%M:%S' \ src/ include/ tests/ | while read file event timestamp; do echo "[$timestamp] $event: $file" if [[ "$file" =~ \\.(cpp|cc|h|hpp)$ ]]; then cmake --build build --parallel fi done # Watch with batch processing (wait for quiet period) batch_timeout=2 last_change=$(date +%s) inotifywait -m -r -e modify src/ include/ | while read path action file; do current_time=$(date +%s) last_change=$current_time # Start a background job to build after quiet period ( sleep $batch_timeout if [ $(date +%s) -ge $((last_change + batch_timeout)) ]; then echo "Building after batch timeout..." make fi ) & done EOF chmod +x inotify_example.sh echo "" echo "✓ inotify-tools setup complete!" echo "" echo "Example usage:" echo " ./inotify_example.sh" echo "" echo "For more information:" echo " man inotifywait" echo " man inotifywatch"`; } static generateFswatchSetup() { return `#!/bin/bash # Setup script for fswatch set -euo pipefail echo "Setting up fswatch for hot-reload development..." # Check if fswatch is installed if command -v fswatch &> /dev/null; then echo "✓ fswatch is already installed" fswatch --version else echo "Installing fswatch..." # Detect OS and install fswatch if [[ "$OSTYPE" == "linux-gnu"* ]]; then # Linux if command -v apt-get &> /dev/null; then # Debian/Ubuntu sudo apt-get update sudo apt-get install -y fswatch elif command -v yum &> /dev/null; then # RHEL/CentOS/Fedora - build from source sudo yum install -y gcc-c++ make autoconf automake libtool git clone https://github.com/emcrisostomo/fswatch.git cd fswatch ./autogen.sh ./configure make sudo make install cd .. rm -rf fswatch elif command -v pacman &> /dev/null; then # Arch Linux sudo pacman -S --noconfirm fswatch else echo "Please install fswatch manually for your Linux distribution" exit 1 fi elif [[ "$OSTYPE" == "darwin"* ]]; then # macOS if command -v brew &> /dev/null; then brew install fswatch else echo "Please install Homebrew first: https://brew.sh" exit 1 fi elif [[ "$OSTYPE" == "freebsd"* ]]; then # FreeBSD sudo pkg install -y fswatch else echo "Building fswatch from source..." git clone https://github.com/emcrisostomo/fswatch.git cd fswatch ./autogen.sh ./configure make sudo make install cd .. rm -rf fswatch fi fi # Create example fswatch usage script cat > fswatch_example.sh << 'EOF' #!/bin/bash # Example fswatch usage for C++ development # Basic file watching with rebuild fswatch -o src include | xargs -n1 -I{} make # Watch with file filters and custom action fswatch -r -e ".*" -i "\\\\.cpp$" -i "\\\\.h$" -i "\\\\.hpp$" src include | while read file; do echo "Changed: $file" cmake --build build --parallel done # Watch with multiple monitors (use best available) fswatch -r -m poll_monitor -l 0.5 src include | while read file; do make done # Watch with batch processing fswatch -r -o --batch-marker=EOF src include | while read line; do if [ "$line" = "EOF" ]; then echo "Batch complete, rebuilding..." make fi done # Watch with extended info fswatch -x -r src include | while read file event; do echo "File: $file" echo "Event: $event" case "$event" in *Created*|*Updated*|*Renamed*) cmake --build build --parallel ;; *Removed*) echo "File removed, full rebuild recommended" ;; esac done # Platform-specific optimized watching if [[ "$OSTYPE" == "darwin"* ]]; then # macOS - use FSEvents fswatch -r -m fsevents_monitor src include | xargs -n1 -I{} make elif [[ "$OSTYPE" == "linux-gnu"* ]]; then # Linux - use inotify fswatch -r -m inotify_monitor src include | xargs -n1 -I{} make else # Fallback to polling fswatch -r -m poll_monitor -l 1 src include | xargs -n1 -I{} make fi EOF chmod +x fswatch_example.sh # Create configuration file cat > .fswatch.conf << 'EOF' # fswatch configuration --recursive --extended --exclude=".*\\.o$" --exclude=".*\\.so$" --exclude=".*\\.a$" --exclude="^\\.git" --exclude="^build/" --exclude="^cmake-build-.*/" --include=".*\\.cpp$" --include=".*\\.cc$" --include=".*\\.cxx$" --include=".*\\.h$" --include=".*\\.hpp$" --include=".*\\.hxx$" --include="CMakeLists\\.txt$" --include=".*\\.cmake$" EOF echo "" echo "✓ fswatch setup complete!" echo "" echo "Example usage:" echo " ./fswatch_example.sh" echo "" echo "Configuration file created: .fswatch.conf" echo "" echo "For more information:" echo " man fswatch" echo " https://github.com/emcrisostomo/fswatch"`; } static generateWatchmanProjectConfig() { return `{ "ignore_dirs": [ ".git", "build", ".cache", "cmake-build-debug", "cmake-build-release" ] }`; } static generateHotReloadReadme(projectName) { return `# Hot-Reload Development for ${projectName} This directory contains configuration and tools for hot-reload development, enabling automatic rebuilding when source files change. ## Overview Hot-reload development improves productivity by automatically rebuilding your C++ project when source files are modified. This implementation supports multiple file watching tools and build systems. ## Quick Start \`\`\`bash # Using the main hot-reload script ./scripts/hot-reload.sh # Using Python-based watcher with advanced features python3 scripts/watch-and-build.py ${projectName} # Using Docker for consistent environment docker-compose -f docker-compose.dev.yml up \`\`\` ## Supported File Watchers ### 1. entr (Recommended for simplicity) - **Pros**: Simple, efficient, cross-platform - **Cons**: Requires restart when directories change - **Setup**: \`./hot-reload/entr-setup.sh\` ### 2. Watchman (Recommended for large projects) - **Pros**: Highly scalable, intelligent caching, Facebook's tool - **Cons**: More complex setup, requires daemon - **Setup**: Follow watchman installation guide ### 3. inotify (Linux only) - **Pros**: Native Linux kernel support, very efficient - **Cons**: Linux-only, watch limit constraints - **Setup**: \`./hot-reload/inotify-setup.sh\` ### 4. fswatch (Cross-platform) - **Pros**: Works on macOS, Linux, BSD, Windows - **Cons**: Different backends per platform - **Setup**: \`./hot-reload/fswatch-setup.sh\` ## Build Strategies ### 1. Rebuild Strategy - Rebuilds entire project on any change - Simple but slower for large projects - Best for small projects or major changes ### 2. Incremental Strategy (Default) - Only rebuilds changed targets - Faster for most use cases - Relies on build system dependency tracking ### 3. Module Strategy - Rebuilds only the affected module - Fastest for well-modularized projects - Requires CMake target organization ## Advanced Features ### Build Caching #### ccache Integration \`\`\`bash # Install ccache sudo apt-get install ccache # Debian/Ubuntu brew install ccache # macOS # Enable in hot-reload export CC="ccache gcc" export CXX="ccache g++" \`\`\` #### distcc for Distributed Builds \`\`\`bash # Setup distcc sudo apt-get install distcc # Configure hosts export DISTCC_HOSTS="localhost/4 192.168.1.100/8" \`\`\` ### Intelligent Dependency Analysis The Python-based watcher includes: - Dependency graph analysis - Affected file detection - Build task deduplication - Parallel build coordination ### Performance Monitoring - Build time tracking - Success/failure statistics - Memory usage monitoring - Cache hit rates ## IDE Integration ### Visual Studio Code Configured tasks and launch configurations are provided: - **Build Task**: Ctrl+Shift+B - **Debug with Hot-Reload**: F5 - **Watch Mode**: Task "Start Hot-Reload" ### CLion 1. Add External Tool for hot-reload script 2. Configure File Watchers plugin 3. Use built-in CMake reload ### Vim/Neovim \`\`\`vim " Add to .vimrc autocmd BufWritePost *.cpp,*.h silent! !touch .rebuild nnoremap <leader>hr :!./scripts/hot-reload.sh<CR> \`\`\` ## Configuration Options ### Environment Variables \`\`\`bash # Watcher tool selection export HOT_RELOAD_WATCHER=entr # entr|watchman|inotify|fswatch # Build tool export HOT_RELOAD_BUILD_TOOL=cmake # cmake|make|ninja|bazel # Reload strategy export HOT_RELOAD_STRATEGY=incremental # rebuild|incremental|module # Enable features export HOT_RELOAD_TESTS=true export HOT_RELOAD_BENCHMARKS=false export HOT_RELOAD_DEBUGGER=false \`\`\` ### Custom Watch Paths Edit \`hot-reload.sh\` to add custom paths: \`\`\`bash CUSTOM_PATHS=("config" "resources" "shaders") \`\`\` ## Troubleshooting ### Common Issues 1. **"Too many open files" error** \`\`\`bash # Increase file descriptor limit ulimit -n 4096 # For inotify, increase watch limit sudo sysctl fs.inotify.max_user_watches=524288 \`\`\` 2. **Slow rebuild times** - Enable ccache - Use incremental or module strategy - Consider using Ninja instead of Make - Check for unnecessary includes 3. **Watcher not detecting changes** - Check ignored directories in config - Verify file permissions - Ensure watcher is monitoring correct paths 4. **Build failures in hot-reload** - Check build logs in build directory - Ensure all dependencies are installed - Verify CMake configuration ### Debug Mode \`\`\`bash # Enable debug output export HOT_RELOAD_DEBUG=1 ./scripts/hot-reload.sh \`\`\` ## Performance Tips 1. **Use Precompiled Headers** \`\`\`cmake target_precompile_headers(${projectName} PRIVATE pch.h) \`\`\` 2. **Optimize Include Guards** Use \`#pragma once\` for faster preprocessing 3. **Module Organization** Split large files into smaller modules for faster incremental builds 4. **Build Parallelization** \`\`\`bash # Use all CPU cores cmake --build build -j$(nproc) \`\`\` 5. **Unity Builds** Enable CMake unity builds for faster full rebuilds ## Docker Development ### Using Docker Compose \`\`\`bash # Start development environment docker-compose -f docker-compose.dev.yml up # Rebuild container docker-compose -f docker-compose.dev.yml build # Enter container shell docker-compose -f docker-compose.dev.yml exec dev bash \`\`\` ### Benefits - Consistent environment - Isolated dependencies - Easy onboarding - CI/CD parity ## Best Practices 1. **Organize Code for Fast Builds** - Use forward declarations - Minimize header dependencies - Implement in source files, not headers 2. **Configure Ignore Patterns** - Exclude build directories - Ignore temporary files - Skip version control directories 3. **Use Appropriate Strategies** - Module strategy for large projects - Incremental for medium projects - Rebuild for small projects 4. **Monitor Performance** - Track build times - Identify slow modules - Optimize bottlenecks ## Resources - [entr Documentation](http://eradman.com/entrproject/) - [Watchman Documentation](https://facebook.github.io/watchman/) - [inotify Manual](https://man7.org/linux/man-pages/man7/inotify.7.html) - [fswatch Documentation](https://github.com/emcrisostomo/fswatch) - [ccache Documentation](https://ccache.dev/) - [distcc Documentation](https://distcc.github.io/)`; } static generateHotReloadCMake() { return `# Hot-Reload CMake Configuration # Optimizations and utilities for fast incremental builds # Hot-reload mode detection option(ENABLE_HOT_RELOAD "Enable hot-reload optimizations" OFF) if(ENABLE_HOT_RELOAD) message(STATUS "Hot-reload mode enabled") # Faster build settings for development set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Build type" FORCE) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) # Disable optimizations for faster compilation if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") add_compile_options(-O0 -g) endif() # Enable incremental linking if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") add_link_options(/INCREMENTAL) endif() endif() # ccache integration find_program(CCACHE_FOUND ccache) if(CCACHE_FOUND AND ENABLE_HOT_RELOAD) message(STATUS "ccache found, enabling compiler cache") set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache) set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ccache) # ccache configuration set(ENV{CCACHE_BASEDIR} \${CMAKE_SOURCE_DIR}) set(ENV{CCACHE_SLOPPINESS} "pch_defines,time_macros") endif() # distcc integration find_program(DISTCC_FOUND distcc) if(DISTCC_FOUND AND ENABLE_HOT_RELOAD) message(STATUS "distcc found, enabling distributed compilation") if(NOT CCACHE_FOUND) set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE distcc) else() # Use both ccache and distcc set(ENV{CCACHE_PREFIX} distcc) endif() endif() # Precompiled headers for faster builds function(target_enable_pch TARGET) if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.16" AND ENABLE_HOT_RELOAD) # Collect common headers set(PCH_HEADERS <algorithm> <array> <chrono> <cstddef> <cstdint> <cstdlib> <exception> <functional> <iostream> <iterator> <limits> <map> <memory> <numeric> <set> <sstream> <string> <string_view> <type_traits> <unordered_map> <unordered_set> <utility> <vector> ) target_precompile_headers(\${TARGET} PRIVATE \${PCH_HEADERS}) endif() endfunction() # Unity builds for faster full rebuilds function(target_enable_unity_build TARGET) if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.16" AND ENABLE_HOT_RELOAD) set_target_properties(\${TARGET} PROPERTIES UNITY_BUILD ON) set_target_properties(\${TARGET} PROPERTIES UNITY_BUILD_BATCH_SIZE 16) endif() endfunction() # Hot-reload friendly target configuration function(configure_hot_reload_target TARGET) if(ENABLE_HOT_RELOAD) # Enable fast builds target_enable_pch(\${TARGET}) # Disable certain warnings for faster compilation if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") target_compile_options(\${TARGET} PRIVATE -Wno-unused-parameter -Wno-unused-variable -Wno-unused-function ) endif() # Add hot-reload define target_compile_definitions(\${TARGET} PRIVATE HOT_RELOAD_ENABLED) # Create symlink for easy execution add_custom_command(TARGET \${TARGET} POST_BUILD COMMAND \${CMAKE_COMMAND} -E create_symlink \$<TARGET_FILE:\${TARGET}> \${CMAKE_BINARY_DIR}/\${TARGET}_hot COMMENT "Creating hot-reload symlink" ) endif() endfunction() # Dependency change detection function(add_hot_reload_deps TARGET) if(ENABLE_HOT_RELOAD) # Touch a timestamp file when target is built add_custom_command(TARGET \${TARGET} POST_BUILD COMMAND \${CMAKE_COMMAND} -E touch \${CMAKE_BINARY_DIR}/.hot_reload_\${TARGET} COMMENT "Updating hot-reload timestamp" ) endif() endfunction() # Test runner for hot-reload function(add_hot_reload_test TARGET) if(ENABLE_HOT_RELOAD AND BUILD_TESTING) add_custom_target(hot_test_\${TARGET} COMMAND \$<TARGET_FILE:\${TARGET}> DEPENDS \${TARGET} WORKING_DIRECTORY \${CMAKE_CURRENT_BINARY_DIR} COMMENT "Running hot-reload test: \${TARGET}" ) endif() endfunction() # Benchmark runner for hot-reload function(add_hot_reload_benchmark TARGET) if(ENABLE_HOT_RELOAD) add_custom_target(hot_bench_\${TARGET} COMMAND \$<TARGET_FILE:\${TARGET}> --benchmark_format=json DEPENDS \${TARGET} WORKING_DIRECTORY \${CMAKE_CURRENT_BINARY_DIR} COMMENT "Running hot-reload benchmark: \${TARGET}" ) endif() endfunction() # Module-based build organization function(create_hot_reload_module MODULE_NAME) if(ENABLE_HOT_RELOAD) # Create a library for the