ascii-chat 0.11.33
Video chat in your terminal
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 813 of file defer/tool.cpp.

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

References args.

◆ 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}
mutex_t mutex

References mutex.

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().