From 20b49e4e45967bf884a40cb4265dff49c33979e8 Mon Sep 17 00:00:00 2001 From: whaffman Date: Wed, 24 Sep 2025 11:01:50 +0200 Subject: [PATCH] feat(iwyu): enhance IWYU mappings and add automatic fix scripts --- .iwyu.imp | 89 +++++++++++++++++++++++++++--- check_iwyu.sh | 11 +++- fix_iwyu_auto.sh | 137 ++++++++++++++++++++++++++++++++++++++++++++++ safe_iwyu_fix.sh | 138 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 367 insertions(+), 8 deletions(-) create mode 100755 fix_iwyu_auto.sh create mode 100755 safe_iwyu_fix.sh diff --git a/.iwyu.imp b/.iwyu.imp index ed824ed..8949600 100644 --- a/.iwyu.imp +++ b/.iwyu.imp @@ -1,17 +1,92 @@ [ - # Standard C++ library mappings + # ============================================================================ + # WEBSERV PROJECT IWYU MAPPINGS + # Based on preferences: Forward declarations in headers, central common header, + # umbrella headers for modules, strict standard library includes, stdexcept + # ============================================================================ + + # Standard C++ library private implementation mappings { include: ["", "private", "", "public"] }, { include: ["", "private", "", "public"] }, { include: ["", "private", "", "public"] }, + { include: ["", "private", "", "public"] }, + { include: ["", "private", "", "public"] }, + { include: ["", "private", "", "public"] }, + { include: ["", "private", "", "public"] }, + { include: ["", "private", "", "public"] }, - # System headers + # Standard library strict mappings (preference 5C - very strict) + { include: ["", "public", "", "public"] }, + { symbol: ["std::cout", "private", "", "public"] }, + { symbol: ["std::cerr", "private", "", "public"] }, + { symbol: ["std::cin", "private", "", "public"] }, + { symbol: ["std::endl", "private", "", "public"] }, + { symbol: ["std::vector", "private", "", "public"] }, + { symbol: ["std::string", "private", "", "public"] }, + { symbol: ["std::map", "private", "", "public"] }, + { symbol: ["std::unordered_map", "private", "", "public"] }, + { symbol: ["std::unique_ptr", "private", "", "public"] }, + { symbol: ["std::shared_ptr", "private", "", "public"] }, + { symbol: ["std::make_unique", "private", "", "public"] }, + { symbol: ["std::make_shared", "private", "", "public"] }, + + # System headers for socket programming { include: ["", "public", "", "public"] }, { include: ["", "public", "", "public"] }, { include: ["", "public", "", "public"] }, + { include: ["", "public", "", "public"] }, + { include: ["", "public", "", "public"] }, + { include: ["", "public", "", "public"] }, - # Project mappings - adjust these to your actual header structure - { include: ["\"webserv/server/Server.hpp\"", "public", "\"webserv/server/Server.hpp\"", "public"] }, - { include: ["\"webserv/log/Log.hpp\"", "public", "\"webserv/log/Log.hpp\"", "public"] }, - { include: ["\"webserv/socket/Socket.hpp\"", "public", "\"webserv/socket/Socket.hpp\"", "public"] }, - { include: ["\"webserv/client/Client.hpp\"", "public", "\"webserv/client/Client.hpp\"", "public"] } + # Error handling (preference 6A - stdexcept) + { symbol: ["std::runtime_error", "private", "", "public"] }, + { symbol: ["std::logic_error", "private", "", "public"] }, + { symbol: ["std::invalid_argument", "private", "", "public"] }, + { symbol: ["std::out_of_range", "private", "", "public"] }, + + # ============================================================================ + # PROJECT HEADER MAPPINGS - Standardized on <> angle brackets + # ============================================================================ + + # Convert quoted includes to angle brackets for consistency + { include: ["\"webserv/log/Log.hpp\"", "public", "", "public"] }, + { include: ["\"webserv/log/Channel.hpp\"", "public", "", "public"] }, + { include: ["\"webserv/log/StdoutChannel.hpp\"", "public", "", "public"] }, + { include: ["\"webserv/log/FileChannel.hpp\"", "public", "", "public"] }, + + { include: ["\"webserv/http/HttpRequest.hpp\"", "public", "", "public"] }, + { include: ["\"webserv/http/HttpResponse.hpp\"", "public", "", "public"] }, + { include: ["\"webserv/http/HttpHeaders.hpp\"", "public", "", "public"] }, + { include: ["\"webserv/http/HttpConstants.hpp\"", "public", "", "public"] }, + + { include: ["\"webserv/config/ConfigManager.hpp\"", "public", "", "public"] }, + { include: ["\"webserv/config/ServerConfig.hpp\"", "public", "", "public"] }, + { include: ["\"webserv/config/LocationConfig.hpp\"", "public", "", "public"] }, + { include: ["\"webserv/config/utils.hpp\"", "public", "", "public"] }, + + { include: ["\"webserv/server/Server.hpp\"", "public", "", "public"] }, + { include: ["\"webserv/client/Client.hpp\"", "public", "", "public"] }, + { include: ["\"webserv/socket/Socket.hpp\"", "public", "", "public"] }, + { include: ["\"webserv/router/Router.hpp\"", "public", "", "public"] }, + + { include: ["\"webserv/handler/CgiHandler.hpp\"", "public", "", "public"] }, + { include: ["\"webserv/handler/FileHandler.hpp\"", "public", "", "public"] }, + { include: ["\"webserv/handler/ErrorHandler.hpp\"", "public", "", "public"] }, + + # ============================================================================ + # FORWARD DECLARATION PREFERENCES + # ============================================================================ + + # Suggest forward declarations instead of full includes in headers where possible + { symbol: ["Client", "private", "", "public"] }, + { symbol: ["Server", "private", "", "public"] }, + { symbol: ["HttpRequest", "private", "", "public"] }, + { symbol: ["HttpResponse", "private", "", "public"] }, + { symbol: ["HttpHeaders", "private", "", "public"] }, + { symbol: ["ServerConfig", "private", "", "public"] }, + { symbol: ["ConfigManager", "private", "", "public"] }, + { symbol: ["LocationConfig", "private", "", "public"] }, + { symbol: ["Socket", "private", "", "public"] }, + { symbol: ["Router", "private", "", "public"] }, + { symbol: ["Channel", "private", "", "public"] } ] \ No newline at end of file diff --git a/check_iwyu.sh b/check_iwyu.sh index 08efe55..119e2f4 100755 --- a/check_iwyu.sh +++ b/check_iwyu.sh @@ -52,6 +52,14 @@ fi echo -e "${BLUE}🛠️ Using IWYU command: $IWYU_CMD${NC}" +# Check if mapping file exists +if [ -f "$IWYU_MAPPING" ]; then + echo -e "${GREEN}📋 Using IWYU mapping file: $IWYU_MAPPING${NC}" +else + echo -e "${YELLOW}⚠️ No IWYU mapping file found at: $IWYU_MAPPING${NC}" + echo -e "${YELLOW}💡 Consider creating one for better IWYU suggestions${NC}" +fi + # Check if we found a build directory if [ -z "$BUILD_DIR" ]; then echo -e "${YELLOW}⚠️ No build directory with compile_commands.json found.${NC}" @@ -98,13 +106,14 @@ run_iwyu_on_file() { echo -e "${BLUE}Analyzing: ${relative_path}${NC}" - # Run IWYU with compile commands + # Run IWYU with compile commands and mapping file if "$IWYU_CMD" \ -I"$PROJECT_ROOT" \ -std=c++20 \ -Xiwyu --verbose=3 \ -Xiwyu --quoted_includes_first \ -Xiwyu --cxx17ns \ + -Xiwyu --mapping_file="$IWYU_MAPPING" \ "$file" \ 2>&1 | tee "$output_file"; then diff --git a/fix_iwyu_auto.sh b/fix_iwyu_auto.sh new file mode 100755 index 0000000..0889d1c --- /dev/null +++ b/fix_iwyu_auto.sh @@ -0,0 +1,137 @@ +#!/bin/bash + +# Automatic IWYU Fix using iwyu-fix-includes +# Uses the official iwyu-fix-includes tool to automatically apply IWYU suggestions + +set -e + +# Find project root (directory containing this script) +PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +RESULTS_DIR="$PROJECT_ROOT/iwyu_results" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +echo -e "${BLUE}🔧 Automatic IWYU Fix using iwyu-fix-includes${NC}" +echo -e "${YELLOW}⚠️ This will automatically apply all IWYU suggestions${NC}" + +# Check if iwyu-fix-includes is available +if ! command -v iwyu-fix-includes &> /dev/null; then + echo -e "${RED}❌ iwyu-fix-includes not found. Please install it:${NC}" + echo -e "${YELLOW} # On Ubuntu/Debian:${NC}" + echo -e "${YELLOW} sudo apt install iwyu${NC}" + echo -e "${YELLOW} # On Arch Linux:${NC}" + echo -e "${YELLOW} sudo pacman -S include-what-you-use${NC}" + echo -e "${YELLOW} # Or build from source${NC}" + exit 1 +fi + +if [ ! -d "$RESULTS_DIR" ]; then + echo -e "${RED}❌ No IWYU results found. Run './check_iwyu.sh' first.${NC}" + exit 1 +fi + +# Make sure we can build first +echo -e "${BLUE}🔍 Testing initial build...${NC}" +if ! make -j$(nproc) release >/dev/null 2>&1; then + echo -e "${RED}❌ Project doesn't build currently. Fix build issues first.${NC}" + exit 1 +fi +echo -e "${GREEN}✅ Initial build successful${NC}" + +# Count IWYU result files +result_count=$(find "$RESULTS_DIR" -name "*.iwyu" -type f | wc -l) +if [ "$result_count" -eq 0 ]; then + echo -e "${YELLOW}⚠️ No .iwyu files found in $RESULTS_DIR${NC}" + exit 1 +fi + +echo -e "${BLUE}📁 Found $result_count IWYU result files${NC}" + +# Apply all fixes using iwyu-fix-includes +echo -e "${BLUE}🔧 Applying IWYU fixes...${NC}" + +# iwyu-fix-includes options: +# --comments: Add comments explaining why headers are included +# --update_comments: Update existing IWYU comments +# --safe_headers: Only remove headers that are definitely safe to remove +iwyu_fix_options="" + +# Ask user for options +echo -e "${YELLOW}Choose fix options:${NC}" +echo -e " 1) ${GREEN}Safe mode${NC} (conservative, only safe changes)" +echo -e " 2) ${BLUE}Standard mode${NC} (recommended)" +echo -e " 3) ${YELLOW}Aggressive mode${NC} (with comments, updates existing)" +echo -n "Choose (1-3) [default: 2]: " + +if [ -t 0 ]; then # Only read input if running interactively + read -r choice +else + choice="2" +fi + +case "${choice:-2}" in + 1) + iwyu_fix_options="--safe_headers" + echo -e "${GREEN}Using safe mode${NC}" + ;; + 3) + iwyu_fix_options="--comments --update_comments" + echo -e "${YELLOW}Using aggressive mode with comments${NC}" + ;; + *) + iwyu_fix_options="" + echo -e "${BLUE}Using standard mode${NC}" + ;; +esac + +# Apply the fixes +echo -e "${BLUE}🚀 Running iwyu-fix-includes...${NC}" + +# iwyu-fix-includes expects all IWYU output concatenated on stdin +temp_output=$(mktemp) +cat "$RESULTS_DIR"/*.iwyu > "$temp_output" + +if cat "$temp_output" | iwyu-fix-includes $iwyu_fix_options; then + echo -e "${GREEN}✅ IWYU fixes applied successfully!${NC}" + + # Clean up + rm -f "$temp_output" + + # Test build after fixes + echo -e "${BLUE}🔍 Testing build after fixes...${NC}" + if make -j$(nproc) release >/dev/null 2>&1; then + echo -e "${GREEN}✅ Build successful after applying fixes!${NC}" + + # Show what changed + if command -v git &> /dev/null && git rev-parse --git-dir > /dev/null 2>&1; then + echo -e "${BLUE}📋 Files modified:${NC}" + git diff --name-only | head -10 + if [ "$(git diff --name-only | wc -l)" -gt 10 ]; then + echo -e "${YELLOW} ... and $(( $(git diff --name-only | wc -l) - 10 )) more files${NC}" + fi + + echo -e "${BLUE}💡 Next steps:${NC}" + echo -e " • Review changes: ${BLUE}git diff${NC}" + echo -e " • Run tests: ${BLUE}make test${NC} (if available)" + echo -e " • Commit: ${BLUE}git add -A && git commit -m 'fix: apply IWYU suggestions'${NC}" + fi + else + echo -e "${RED}❌ Build failed after applying fixes!${NC}" + echo -e "${YELLOW}💡 You may need to manually fix some issues or revert changes${NC}" + rm -f "$temp_output" + exit 1 + fi + +else + echo -e "${RED}❌ iwyu-fix-includes failed!${NC}" + echo -e "${YELLOW}💡 Check the error output above for details${NC}" + rm -f "$temp_output" + exit 1 +fi + +echo -e "${GREEN}🎉 IWYU fix process completed!${NC}" \ No newline at end of file diff --git a/safe_iwyu_fix.sh b/safe_iwyu_fix.sh new file mode 100755 index 0000000..4428e7b --- /dev/null +++ b/safe_iwyu_fix.sh @@ -0,0 +1,138 @@ +#!/bin/bash + +# Safe automatic IWYU fix with build system validation +# This version applies fixes and validates using your actual build system + +# Detect project root +if [ -d "/workspace" ]; then + PROJECT_ROOT="/workspace" +else + PROJECT_ROOT="$(pwd)" +fi + +RESULTS_DIR="$PROJECT_ROOT/iwyu_results" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +echo -e "${BLUE}🔧 Safe IWYU Auto-Fix with Build Validation${NC}" +echo -e "${YELLOW}⚠️ This will apply fixes one file at a time and validate with your build system${NC}" + +if [ ! -d "$RESULTS_DIR" ]; then + echo -e "${RED}❌ No IWYU results found. Run './check_iwyu.sh' first.${NC}" + exit 1 +fi + +# Make sure we can build first +echo -e "${BLUE}🔍 Testing initial build...${NC}" +if ! make -j$(nproc) release >/dev/null 2>&1; then + echo -e "${RED}❌ Project doesn't build currently. Fix build issues first.${NC}" + exit 1 +fi +echo -e "${GREEN}✅ Initial build successful${NC}" + +files_fixed=0 +files_processed=0 + +# Process each .iwyu result file +for result_file in "$RESULTS_DIR"/*.iwyu; do + [ ! -f "$result_file" ] && continue + + # Get the corresponding source file + base_name=$(basename "$result_file" .iwyu) + source_file="" + + # Find the actual source file + while IFS= read -r -d '' file; do + if [[ "$(basename "$file" .cpp)" == "$base_name" ]]; then + source_file="$file" + break + fi + done < <(find "$PROJECT_ROOT/webserv" -name "*.cpp" -print0) + + if [ -z "$source_file" ]; then + continue + fi + + ((files_processed++)) + relative_path="${source_file#$PROJECT_ROOT/}" + + echo -e "\n${BLUE}[$files_processed] Processing: $relative_path${NC}" + + # Check if there are actual suggestions + if ! grep -q "should add these lines:" "$result_file"; then + echo -e "${GREEN} ✅ No additions needed${NC}" + continue + fi + + # Create backup + backup_file="${source_file}.backup" + cp "$source_file" "$backup_file" + + # Extract and apply only the additions (safer than removals) + additions_made=false + + # Get the lines to add and store in temp file to avoid subshell issues + temp_includes=$(mktemp) + awk '/should add these lines:/{flag=1; next} /should remove these lines:|^$/{flag=0} flag && /^#include/{print}' "$result_file" > "$temp_includes" + + # Process each include line + while IFS= read -r include_line; do + [ -z "$include_line" ] && continue + + # Clean up the line (remove any trailing whitespace/comments after //) + clean_include=$(echo "$include_line" | sed 's|//.*$||' | sed 's/[[:space:]]*$//') + + # Check if this exact include is already present (be more strict) + if grep -F "$clean_include" "$source_file" >/dev/null; then + echo -e "${YELLOW} ~ Already present: $clean_include${NC}" + continue + fi + + # Add the include after the first existing #include + if sed -i "1,/^#include/ { /^#include/ a\\ +$include_line +}" "$source_file"; then + echo -e "${GREEN} + Added: $include_line${NC}" + additions_made=true + fi + done < "$temp_includes" + + rm -f "$temp_includes" + + if [ "$additions_made" = true ]; then + # Test build with changes + echo -e "${BLUE} 🔨 Testing build...${NC}" + if make -j$(nproc) release >/dev/null 2>&1; then + echo -e "${GREEN} ✅ Build successful with changes${NC}" + rm "$backup_file" + ((files_fixed++)) + else + echo -e "${RED} ❌ Build failed, reverting changes${NC}" + mv "$backup_file" "$source_file" + fi + else + echo -e "${GREEN} ✅ No new includes to add${NC}" + rm "$backup_file" + fi +done + +echo -e "\n${BLUE}📊 Safe Auto-fix Summary:${NC}" +echo -e "Files processed: $files_processed" +echo -e "Files successfully modified: $files_fixed" + +if [ $files_fixed -gt 0 ]; then + echo -e "${GREEN}🎉 Applied $files_fixed successful fixes!${NC}" + echo -e "${BLUE}💡 Next steps:${NC}" + echo -e " • Review changes: ${BLUE}git diff${NC}" + echo -e " • Run full test: ${BLUE}make clean && make all${NC}" + echo -e " • Commit: ${BLUE}git add -A && git commit -m 'fix: add missing includes (IWYU)'${NC}" +else + echo -e "${GREEN}🎉 No fixes needed - all includes are already optimal!${NC}" +fi + +exit 0 \ No newline at end of file