ascii-chat 0.6.0
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
tool.cpp File Reference

Go to the source code of this file.

Functions

std::mutex & outputRegistryMutex ()
 
std::unordered_set< std::string > & outputRegistry ()
 
bool registerOutputPath (const std::string &path)
 
void unregisterOutputPath (const std::string &path)
 
int main (int argc, const char **argv)
 

Function Documentation

◆ main()

int main ( int  argc,
const char **  argv 
)

Definition at line 786 of file defer/tool.cpp.

786 {
787 InitLLVM InitLLVM(argc, argv);
788
789 // Capture CWD before anything else can change it
790 g_originalCwd = fs::current_path();
791
792 // Hide all the LLVM internal options that aren't relevant to our tool
793 cl::HideUnrelatedOptions(ToolCategory);
794
795 cl::ParseCommandLineOptions(argc, argv, "ascii-defer transformation tool\n");
796
797 fs::path outputDir = fs::path(OutputDirectoryOption.getValue());
798 // Make output directory absolute relative to original CWD
799 if (!outputDir.is_absolute()) {
800 outputDir = g_originalCwd / outputDir;
801 }
802 fs::path inputRoot;
803 if (!InputRootOption.getValue().empty()) {
804 inputRoot = fs::path(InputRootOption.getValue());
805 } else {
806 inputRoot = fs::current_path();
807 }
808
809 // Make input root absolute for reliable path computation
810 if (!inputRoot.is_absolute()) {
811 std::error_code ec;
812 inputRoot = fs::absolute(inputRoot, ec);
813 if (ec) {
814 llvm::errs() << "Failed to resolve input root path: " << ec.message() << "\n";
815 return 1;
816 }
817 }
818
819 std::vector<std::string> sourcePaths;
820 for (const auto &path : SourcePaths) {
821 if (!path.empty()) {
822 sourcePaths.push_back(path);
823 }
824 }
825
826 if (sourcePaths.empty()) {
827 llvm::errs() << "No translation units specified for transformation. Provide positional source paths.\n";
828 return 1;
829 }
830
831 if (fs::exists(outputDir)) {
832 if (!fs::is_directory(outputDir)) {
833 llvm::errs() << "Output path exists and is not a directory: " << outputDir.c_str() << "\n";
834 return 1;
835 }
836 } else {
837 std::error_code errorCode;
838 fs::create_directories(outputDir, errorCode);
839 if (errorCode) {
840 llvm::errs() << "Failed to create output directory: " << outputDir.c_str() << " - " << errorCode.message()
841 << "\n";
842 return 1;
843 }
844 }
845
846 // Load compilation database
847 std::string buildPath = BuildPath.getValue();
848 if (buildPath.empty()) {
849 buildPath = ".";
850 }
851 std::string errorMessage;
852 std::unique_ptr<tooling::CompilationDatabase> compilations =
853 tooling::CompilationDatabase::loadFromDirectory(buildPath, errorMessage);
854 if (!compilations) {
855 llvm::errs() << "Error loading compilation database from '" << buildPath << "': " << errorMessage << "\n";
856 return 1;
857 }
858
859 tooling::ClangTool tool(*compilations, sourcePaths);
860
861 // Build the list of arguments to prepend for system header resolution
862 // LibTooling uses CC1 mode internally which has different include path handling than
863 // the clang driver. We use -Xclang to pass CC1-level flags that properly configure
864 // system include paths for LibTooling's CompilerInvocation.
865 std::vector<std::string> prependArgs;
866
867 // Try to find clang resource directory at runtime
868 // Priority: 1) CLANG_RESOURCE_DIR compile-time path, 2) Runtime detection via common paths
869 std::string resourceDir;
870
871#ifdef CLANG_RESOURCE_DIR
872 if (llvm::sys::fs::exists(CLANG_RESOURCE_DIR)) {
873 resourceDir = CLANG_RESOURCE_DIR;
874 llvm::errs() << "Using embedded clang resource directory: " << resourceDir << "\n";
875 } else {
876 llvm::errs() << "Embedded clang resource directory not found: " << CLANG_RESOURCE_DIR << "\n";
877 }
878#endif
879
880 // Runtime detection if embedded path doesn't work
881 if (resourceDir.empty()) {
882 // Common locations for clang resource directories
883 std::vector<std::string> searchPaths;
884
885#ifdef __APPLE__
886 // Homebrew LLVM on Apple Silicon
887 searchPaths.push_back("/opt/homebrew/opt/llvm/lib/clang");
888 // Homebrew LLVM on Intel Mac
889 searchPaths.push_back("/usr/local/opt/llvm/lib/clang");
890 // Xcode's clang
891 searchPaths.push_back("/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang");
892 // CommandLineTools clang
893 searchPaths.push_back("/Library/Developer/CommandLineTools/usr/lib/clang");
894#endif
895#ifdef __linux__
896 // System LLVM installations
897 searchPaths.push_back("/usr/lib/llvm-22/lib/clang");
898 searchPaths.push_back("/usr/lib/llvm-21/lib/clang");
899 searchPaths.push_back("/usr/lib/llvm-20/lib/clang");
900 searchPaths.push_back("/usr/lib/clang");
901 searchPaths.push_back("/usr/local/lib/clang");
902#endif
903 // Universal fallback
904 searchPaths.push_back("/usr/local/lib/clang");
905
906 for (const auto& basePath : searchPaths) {
907 if (!llvm::sys::fs::exists(basePath)) {
908 continue;
909 }
910
911 // Find the highest version subdirectory
912 std::error_code ec;
913 std::string bestVersion;
914 int bestMajor = 0;
915
916 for (llvm::sys::fs::directory_iterator dir(basePath, ec), dirEnd;
917 !ec && dir != dirEnd; dir.increment(ec)) {
918 std::string name = llvm::sys::path::filename(dir->path()).str();
919 // Parse version number (e.g., "22", "21.1.0", "21")
920 int major = 0;
921 if (sscanf(name.c_str(), "%d", &major) == 1 && major > bestMajor) {
922 bestMajor = major;
923 bestVersion = dir->path();
924 }
925 }
926
927 if (!bestVersion.empty()) {
928 resourceDir = bestVersion;
929 llvm::errs() << "Found clang resource directory at runtime: " << resourceDir << "\n";
930 break;
931 }
932 }
933
934 if (resourceDir.empty()) {
935 llvm::errs() << "Warning: Could not find clang resource directory\n";
936 }
937 }
938
939 if (!resourceDir.empty()) {
940 prependArgs.push_back(std::string("-resource-dir=") + resourceDir);
941 }
942
943 // Add target triple - LibTooling needs this to validate architecture-specific flags
944 // Without a target, flags like -mavx2 cause "unsupported option for target ''" errors
945#ifdef __APPLE__
946 #ifdef __arm64__
947 prependArgs.push_back("-target");
948 prependArgs.push_back("arm64-apple-darwin");
949 llvm::errs() << "Using target: arm64-apple-darwin\n";
950 #else
951 prependArgs.push_back("-target");
952 prependArgs.push_back("x86_64-apple-darwin");
953 llvm::errs() << "Using target: x86_64-apple-darwin\n";
954 #endif
955#elif defined(__linux__)
956 #ifdef __aarch64__
957 prependArgs.push_back("-target");
958 prependArgs.push_back("aarch64-linux-gnu");
959 llvm::errs() << "Using target: aarch64-linux-gnu\n";
960 #else
961 prependArgs.push_back("-target");
962 prependArgs.push_back("x86_64-linux-gnu");
963 llvm::errs() << "Using target: x86_64-linux-gnu\n";
964 #endif
965#endif
966
967 // Override the sysroot for macOS. Homebrew's LLVM config file sets -isysroot
968 // to CommandLineTools SDK, but we strip that from compile_commands.json and
969 // set our own explicitly to ensure consistent behavior.
970 std::string selectedSDK;
971#ifdef __APPLE__
972 {
973 const char* sdkPaths[] = {
974 // Xcode SDK (preferred - most complete headers and frameworks)
975 "/Applications/Xcode.app/Contents/Developer/Platforms/"
976 "MacOSX.platform/Developer/SDKs/MacOSX.sdk",
977 // CommandLineTools SDK (fallback for users without Xcode)
978 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk",
979 };
980
981 for (const char* sdk : sdkPaths) {
982 if (llvm::sys::fs::exists(sdk)) {
983 selectedSDK = sdk;
984 break;
985 }
986 }
987
988 if (!selectedSDK.empty()) {
989 prependArgs.push_back("-isysroot");
990 prependArgs.push_back(selectedSDK);
991 llvm::errs() << "Using macOS SDK: " << selectedSDK << "\n";
992 } else {
993 llvm::errs() << "Warning: No macOS SDK found, system headers may not be available\n";
994 }
995 }
996#endif
997
998 // Build list of system include paths to add as -isystem paths.
999 // LibTooling (cc1 mode) doesn't automatically add system include paths like
1000 // the clang driver does, so we add them explicitly:
1001 // 1. Clang's builtin headers (stdbool.h, stddef.h, etc.) - FIRST so they shadow SDK builtins
1002 // 2. SDK's usr/include (stdio.h, stdlib.h, etc.) - for system headers
1003 std::vector<std::string> appendArgs;
1004 if (!resourceDir.empty()) {
1005 std::string builtinInclude = resourceDir + "/include";
1006 if (llvm::sys::fs::exists(builtinInclude)) {
1007 appendArgs.push_back("-isystem");
1008 appendArgs.push_back(builtinInclude);
1009 llvm::errs() << "Added clang builtin -isystem: " << builtinInclude << "\n";
1010 }
1011 }
1012 // NOTE: We intentionally do NOT add SDK's usr/include to the -isystem path.
1013 // LibTooling on LLVM 21 has a bug where __has_include_next() evaluates even
1014 // in non-taken preprocessor branches and creates VFS entries that can't be opened.
1015 // The clang builtin stdbool.h has:
1016 // #if defined(__MVS__) && __has_include_next(<stdbool.h>)
1017 // Even though __MVS__ is not defined on macOS, LLVM 21's LibTooling still tries
1018 // to resolve __has_include_next(<stdbool.h>), looks in SDK/usr/include, and fails
1019 // because stdbool.h doesn't exist there (it's a builtin header).
1020 //
1021 // By not adding SDK/usr/include, the __has_include_next won't find any entry
1022 // and will return false without trying to open a non-existent file.
1023 // The -isysroot flag still provides access to SDK headers through framework paths.
1024
1025 // Single consolidated argument adjuster that:
1026 // 1. Preserves compiler path (first arg)
1027 // 2. Inserts prepend args immediately after compiler (-nostdlibinc, -resource-dir, -isysroot)
1028 // 3. Strips unnecessary flags from remaining args
1029 // 4. Adds system include paths as -isystem at the end (after project -I paths)
1030 // 5. Adds defer tool parsing define BEFORE the "--" separator
1031 std::string inputRootStr = inputRoot.string();
1032 auto consolidatedAdjuster = [prependArgs, appendArgs, inputRootStr](const tooling::CommandLineArguments &args, StringRef) {
1033 // Helper to check if a path is under the project root
1034 auto isProjectPath = [&inputRootStr](const std::string &path) -> bool {
1035 if (inputRootStr.empty()) return false;
1036 // Normalize both paths for comparison
1037 std::error_code ec;
1038 auto normalizedPath = fs::canonical(path, ec);
1039 if (ec) return false; // Path doesn't exist or can't be resolved
1040 auto normalizedRoot = fs::canonical(inputRootStr, ec);
1041 if (ec) return false;
1042 // Check if the path starts with the root
1043 std::string pathStr = normalizedPath.string();
1044 std::string rootStr = normalizedRoot.string();
1045 if (pathStr.find(rootStr) != 0) return false;
1046 // Exclude .deps-cache directory - these are cached dependencies that use
1047 // angled includes (<header.h>) and need -isystem, not -iquote
1048 if (pathStr.find("/.deps-cache/") != std::string::npos) return false;
1049 return true;
1050 };
1051 tooling::CommandLineArguments result;
1052
1053 if (args.empty()) {
1054 return result;
1055 }
1056
1057 // First: preserve the compiler path (first argument)
1058 result.push_back(args[0]);
1059
1060 // Second: add the prepend args right after the compiler
1061 for (const auto &arg : prependArgs) {
1062 result.push_back(arg);
1063 }
1064
1065 // Third: process remaining arguments, stripping unnecessary flags
1066 // IMPORTANT: Convert -I to -iquote for project include paths.
1067 // Clang include search order for <header.h>: -I paths -> -isystem paths -> system paths
1068 // Clang include search order for "header.h": -iquote paths -> -I paths -> -isystem paths -> system paths
1069 //
1070 // The problem: Project -I paths are searched BEFORE -isystem paths for <stdio.h>.
1071 // This causes LibTooling to look for <stdio.h> in the project's lib/ directory first.
1072 //
1073 // The fix: Convert project -I to -iquote. Then <stdio.h> skips project paths entirely
1074 // and finds system headers in -isystem paths instead.
1075 //
1076 // ALSO: Collect all -isystem paths and reorder them so clang builtins come FIRST.
1077 // LibTooling on LLVM 21 has a VFS bug where it creates phantom entries for headers
1078 // in -isystem directories even when the header doesn't exist there.
1079 std::vector<std::string> collectedIsystemPaths;
1080 bool foundSeparator = false;
1081 size_t separatorIndex = 0;
1082 for (size_t i = 1; i < args.size(); ++i) {
1083 const std::string &arg = args[i];
1084
1085 // When we hit "--", note its position and break
1086 if (arg == "--") {
1087 foundSeparator = true;
1088 separatorIndex = i;
1089 break;
1090 }
1091
1092 // Skip sanitizer flags
1093 if (arg.find("-fsanitize") != std::string::npos)
1094 continue;
1095 if (arg.find("-fno-sanitize") != std::string::npos)
1096 continue;
1097 // Skip debug info flags (not needed for AST parsing)
1098 if (arg == "-g" || arg == "-g2" || arg == "-g3")
1099 continue;
1100 if (arg == "-fno-eliminate-unused-debug-types")
1101 continue;
1102 if (arg == "-fno-inline")
1103 continue;
1104 // Strip -resource-dir flags and their arguments - we added our embedded path
1105 if (arg == "-resource-dir") {
1106 ++i;
1107 continue;
1108 }
1109 if (arg.find("-resource-dir=") == 0)
1110 continue;
1111 // Strip -isysroot flags and their arguments - we added our embedded SDK path
1112 if (arg == "-isysroot") {
1113 ++i;
1114 continue;
1115 }
1116 if (arg.find("-isysroot=") == 0 || (arg.find("-isysroot") == 0 && arg.length() > 9))
1117 continue;
1118
1119 // Collect -isystem paths instead of passing them through
1120 // We'll add them at the end in the correct order (clang builtins first)
1121 if (arg == "-isystem" && i + 1 < args.size()) {
1122 collectedIsystemPaths.push_back(args[++i]);
1123 continue;
1124 }
1125 if (arg.find("-isystem") == 0 && arg.length() > 8) {
1126 collectedIsystemPaths.push_back(arg.substr(8));
1127 continue;
1128 }
1129
1130 // Convert -I to -iquote for project include paths
1131 // Convert -I to -isystem for dependency paths (so they come after our clang builtins)
1132 // This prevents <stdbool.h> from being searched in dependency directories
1133 // before our clang builtin path
1134 if (arg == "-I" && i + 1 < args.size()) {
1135 // -I /path/to/dir (separate argument)
1136 const std::string &includePath = args[++i];
1137 if (isProjectPath(includePath)) {
1138 result.push_back("-iquote");
1139 result.push_back(includePath);
1140 } else {
1141 // Collect dependency -I paths to add as -isystem after our builtins
1142 collectedIsystemPaths.push_back(includePath);
1143 }
1144 continue;
1145 }
1146 if (arg.find("-I") == 0 && arg.length() > 2) {
1147 // -I/path/to/dir (combined form)
1148 std::string includePath = arg.substr(2);
1149 if (isProjectPath(includePath)) {
1150 result.push_back("-iquote");
1151 result.push_back(includePath);
1152 } else {
1153 // Collect dependency -I paths to add as -isystem after our builtins
1154 collectedIsystemPaths.push_back(includePath);
1155 }
1156 continue;
1157 }
1158
1159 result.push_back(arg);
1160 }
1161
1162 // Fourth: add system include paths in the correct order
1163 // Order matters for LibTooling on LLVM 21 - clang builtins MUST come first
1164 // to shadow any phantom VFS entries that might be created for other paths
1165 for (const auto &arg : appendArgs) {
1166 result.push_back(arg);
1167 }
1168 // Then add the collected -isystem paths from the compilation database
1169 for (const auto &path : collectedIsystemPaths) {
1170 result.push_back("-isystem");
1171 result.push_back(path);
1172 }
1173
1174 // Fifth: add the defer tool define and separator
1175 result.push_back("-DASCIICHAT_DEFER_TOOL_PARSING");
1176 if (foundSeparator) {
1177 result.push_back("--");
1178 // Copy any remaining args after "--"
1179 for (size_t i = separatorIndex + 1; i < args.size(); ++i) {
1180 result.push_back(args[i]);
1181 }
1182 }
1183
1184 return result;
1185 };
1186 tool.appendArgumentsAdjuster(consolidatedAdjuster);
1187
1188 // Debug: Print the final command line arguments
1189 tool.appendArgumentsAdjuster([](const tooling::CommandLineArguments &args, StringRef filename) {
1190 llvm::errs() << "Final command for " << filename << ":\n";
1191 for (const auto &arg : args) {
1192 llvm::errs() << " " << arg << "\n";
1193 }
1194 return args;
1195 });
1196
1197 DeferActionFactory actionFactory(outputDir, inputRoot);
1198 const int executionResult = tool.run(&actionFactory);
1199 if (executionResult != 0) {
1200 llvm::errs() << "Defer transformation failed with code " << executionResult << "\n";
1201 }
1202 return executionResult;
1203}

◆ outputRegistry()

std::unordered_set< std::string > & outputRegistry ( )

Definition at line 39 of file defer/tool.cpp.

39 {
40 static std::unordered_set<std::string> registry;
41 return registry;
42}

Referenced by registerOutputPath(), and unregisterOutputPath().

◆ outputRegistryMutex()

std::mutex & outputRegistryMutex ( )

Definition at line 34 of file defer/tool.cpp.

34 {
35 static std::mutex mutex;
36 return mutex;
37}

Referenced by registerOutputPath(), and unregisterOutputPath().

◆ registerOutputPath()

bool registerOutputPath ( const std::string &  path)

Definition at line 44 of file defer/tool.cpp.

44 {
45 std::lock_guard<std::mutex> guard(outputRegistryMutex());
46 auto &registry = outputRegistry();
47 bool inserted = registry.insert(path).second;
48 return inserted;
49}
std::mutex & outputRegistryMutex()
std::unordered_set< std::string > & outputRegistry()

References outputRegistry(), and outputRegistryMutex().

◆ unregisterOutputPath()

void unregisterOutputPath ( const std::string &  path)

Definition at line 51 of file defer/tool.cpp.

51 {
52 std::lock_guard<std::mutex> guard(outputRegistryMutex());
53 outputRegistry().erase(path);
54}

References outputRegistry(), and outputRegistryMutex().