8#include <unordered_map>
9#include <unordered_set>
12#include "clang/AST/ASTConsumer.h"
13#include "clang/AST/ASTContext.h"
14#include "clang/AST/RecursiveASTVisitor.h"
15#include "clang/Frontend/CompilerInstance.h"
16#include "clang/Lex/Lexer.h"
17#include "clang/Lex/Preprocessor.h"
18#include "clang/Rewrite/Core/Rewriter.h"
19#include "clang/Driver/Driver.h"
20#include "clang/Tooling/ArgumentsAdjusters.h"
21#include "clang/Tooling/CommonOptionsParser.h"
22#include "clang/Tooling/Tooling.h"
23#include "llvm/Support/CommandLine.h"
24#include "llvm/Support/FileSystem.h"
25#include "llvm/Support/InitLLVM.h"
26#include "llvm/Support/Path.h"
27#include "llvm/Support/raw_ostream.h"
32namespace fs = std::filesystem;
35 static std::mutex mutex;
40 static std::unordered_set<std::string> registry;
47 bool inserted = registry.insert(path).second;
57static cl::OptionCategory ToolCategory(
"ascii-defer transformation options");
58static cl::extrahelp CommonHelp(tooling::CommonOptionsParser::HelpMessage);
59static cl::extrahelp MoreHelp(
"\nDefer transformation tool for ascii-chat\n");
61static cl::opt<std::string> OutputDirectoryOption(
"output-dir",
62 cl::desc(
"Directory where transformed sources will be written"),
63 cl::value_desc(
"path"), cl::Required, cl::cat(ToolCategory));
65static cl::opt<std::string>
66 InputRootOption(
"input-root", cl::desc(
"Root directory of original sources (used to compute relative paths)"),
67 cl::value_desc(
"path"), cl::init(
""), cl::cat(ToolCategory));
69static cl::opt<std::string> BuildPath(
"p", cl::desc(
"Build path (directory containing compile_commands.json)"),
70 cl::Optional, cl::cat(ToolCategory));
72static cl::list<std::string> SourcePaths(cl::Positional, cl::desc(
"<source0> [... <sourceN>]"), cl::cat(ToolCategory));
78 CompoundStmt *stmt =
nullptr;
81 bool hasDefers =
false;
82 bool endsWithReturn =
false;
83 SourceLocation startLoc;
84 SourceLocation endLoc;
89 SourceLocation location;
90 SourceLocation endLocation;
92 std::string expression;
98 SourceLocation location;
100 std::vector<unsigned> activeScopeIds;
104struct FunctionTransformState {
105 FunctionDecl *funcDecl =
nullptr;
106 std::vector<DeferCall> deferCalls;
107 std::vector<ReturnInfo> returnInfos;
108 std::map<unsigned, BlockScope> blockScopes;
109 std::vector<unsigned> currentScopeStack;
110 bool needsTransformation =
false;
111 unsigned nextScopeId = 0;
114class DeferVisitor :
public RecursiveASTVisitor<DeferVisitor> {
116 DeferVisitor(ASTContext &context, Rewriter &rewriter,
const fs::path &outputDir,
const fs::path &inputRoot)
117 : context_(context), rewriter_(rewriter), outputDir_(outputDir), inputRoot_(inputRoot) {}
119 bool TraverseFunctionDecl(FunctionDecl *funcDecl) {
120 if (!funcDecl || funcDecl->isImplicit()) {
121 return RecursiveASTVisitor<DeferVisitor>::TraverseFunctionDecl(funcDecl);
124 SourceManager &sourceManager = context_.getSourceManager();
125 SourceLocation location = funcDecl->getLocation();
126 location = sourceManager.getExpansionLoc(location);
127 if (!location.isValid() || !sourceManager.isWrittenInMainFile(location)) {
128 return RecursiveASTVisitor<DeferVisitor>::TraverseFunctionDecl(funcDecl);
132 currentFunction_ = FunctionTransformState();
133 currentFunction_.funcDecl = funcDecl;
135 bool result = RecursiveASTVisitor<DeferVisitor>::TraverseFunctionDecl(funcDecl);
138 if (currentFunction_.needsTransformation && !currentFunction_.deferCalls.empty()) {
139 transformFunction(currentFunction_);
142 currentFunction_ = FunctionTransformState();
146 bool TraverseCompoundStmt(CompoundStmt *compoundStmt) {
147 if (!compoundStmt || !currentFunction_.funcDecl) {
148 return RecursiveASTVisitor<DeferVisitor>::TraverseCompoundStmt(compoundStmt);
151 SourceManager &sourceManager = context_.getSourceManager();
152 SourceLocation lbracLoc = compoundStmt->getLBracLoc();
153 if (!lbracLoc.isValid() || !sourceManager.isWrittenInMainFile(lbracLoc)) {
154 return RecursiveASTVisitor<DeferVisitor>::TraverseCompoundStmt(compoundStmt);
158 unsigned scopeId = currentFunction_.nextScopeId++;
159 unsigned depth = currentFunction_.currentScopeStack.size();
161 BlockScope blockScope;
162 blockScope.stmt = compoundStmt;
163 blockScope.scopeId = scopeId;
164 blockScope.depth = depth;
165 blockScope.hasDefers =
false;
166 blockScope.endsWithReturn =
false;
167 blockScope.startLoc = compoundStmt->getLBracLoc().getLocWithOffset(1);
168 blockScope.endLoc = compoundStmt->getRBracLoc();
171 if (!compoundStmt->body_empty()) {
172 Stmt *lastStmt = compoundStmt->body_back();
173 if (isa<ReturnStmt>(lastStmt)) {
174 blockScope.endsWithReturn =
true;
178 currentFunction_.blockScopes[scopeId] = blockScope;
179 currentFunction_.currentScopeStack.push_back(scopeId);
182 bool result = RecursiveASTVisitor<DeferVisitor>::TraverseCompoundStmt(compoundStmt);
185 currentFunction_.currentScopeStack.pop_back();
190 bool TraverseStmt(Stmt *stmt) {
191 if (!stmt || !currentFunction_.funcDecl) {
192 return RecursiveASTVisitor<DeferVisitor>::TraverseStmt(stmt);
200 if (isa<CompoundStmt>(stmt) || isa<IfStmt>(stmt) || isa<ForStmt>(stmt) || isa<WhileStmt>(stmt) ||
201 isa<SwitchStmt>(stmt)) {
202 return RecursiveASTVisitor<DeferVisitor>::TraverseStmt(stmt);
205 SourceManager &sourceManager = context_.getSourceManager();
206 SourceLocation stmtLoc = stmt->getBeginLoc();
209 SourceLocation checkLoc = stmtLoc;
210 if (stmtLoc.isMacroID()) {
211 checkLoc = sourceManager.getExpansionLoc(stmtLoc);
214 if (checkLoc.isValid() && sourceManager.isWrittenInMainFile(checkLoc)) {
215 CharSourceRange range;
216 bool isMacro = stmtLoc.isMacroID();
221 CharSourceRange macroRange = sourceManager.getImmediateExpansionRange(stmtLoc);
224 SourceLocation begin = stmt->getBeginLoc();
225 SourceLocation end = stmt->getEndLoc();
226 if (!begin.isValid() || !end.isValid()) {
227 return RecursiveASTVisitor<DeferVisitor>::TraverseStmt(stmt);
229 range = CharSourceRange::getTokenRange(begin, end);
232 bool invalid =
false;
233 StringRef stmtText = Lexer::getSourceText(range, sourceManager, context_.getLangOpts(), &invalid);
236 SourceLocation begin = isMacro ? range.getBegin() : stmt->getBeginLoc();
240 bool shouldProcess = !isMacro || isa<DoStmt>(stmt);
242 if (shouldProcess && !invalid && stmtText.contains(
"defer(")) {
244 size_t deferPos = stmtText.find(
"defer(");
245 if (deferPos != StringRef::npos) {
247 size_t openParen = deferPos + 5;
248 size_t closeParen = findMatchingParen(stmtText, openParen);
250 if (closeParen != StringRef::npos) {
252 StringRef expression = stmtText.substr(openParen + 1, closeParen - openParen - 1);
255 SourceLocation deferLoc = begin.getLocWithOffset(deferPos);
258 SourceLocation deferEndLoc = begin.getLocWithOffset(closeParen + 1);
261 unsigned currentScopeId = 0;
262 if (!currentFunction_.currentScopeStack.empty()) {
263 currentScopeId = currentFunction_.currentScopeStack.back();
267 std::string exprStr = expression.str();
270 size_t firstNonSpace = exprStr.find_first_not_of(
" \t\n\r");
271 size_t lastNonSpace = exprStr.find_last_not_of(
" \t\n\r");
272 if (firstNonSpace != std::string::npos && lastNonSpace != std::string::npos) {
273 exprStr = exprStr.substr(firstNonSpace, lastNonSpace - firstNonSpace + 1);
277 deferCall.location = deferLoc;
278 deferCall.endLocation = deferEndLoc;
279 deferCall.fileOffset = sourceManager.getFileOffset(deferLoc);
280 deferCall.expression = exprStr;
281 deferCall.scopeId = currentScopeId;
284 if (currentFunction_.blockScopes.count(currentScopeId)) {
285 currentFunction_.blockScopes[currentScopeId].hasDefers =
true;
288 currentFunction_.deferCalls.push_back(deferCall);
289 currentFunction_.needsTransformation =
true;
295 return RecursiveASTVisitor<DeferVisitor>::TraverseStmt(stmt);
298 bool TraverseReturnStmt(ReturnStmt *returnStmt) {
299 if (returnStmt && currentFunction_.funcDecl) {
300 SourceManager &sourceManager = context_.getSourceManager();
301 SourceLocation location = returnStmt->getReturnLoc();
302 if (location.isValid()) {
303 SourceLocation expansionLocation = sourceManager.getExpansionLoc(location);
304 if (expansionLocation.isValid() && sourceManager.isWrittenInMainFile(expansionLocation)) {
306 ReturnInfo returnInfo;
307 returnInfo.location = expansionLocation;
308 returnInfo.fileOffset = sourceManager.getFileOffset(expansionLocation);
309 returnInfo.activeScopeIds = currentFunction_.currentScopeStack;
310 currentFunction_.returnInfos.push_back(returnInfo);
314 return RecursiveASTVisitor<DeferVisitor>::TraverseReturnStmt(returnStmt);
317 std::string makeRelativePath(
const fs::path &absolutePath)
const {
318 if (inputRoot_.empty()) {
319 return absolutePath.generic_string();
322 std::error_code errorCode;
323 fs::path relative = fs::relative(absolutePath, inputRoot_, errorCode);
325 return absolutePath.generic_string();
327 return relative.generic_string();
331 size_t findMatchingParen(StringRef text,
size_t openPos)
const {
332 if (openPos >= text.size() || text[openPos] !=
'(') {
333 return StringRef::npos;
337 for (
size_t i = openPos + 1; i < text.size(); i++) {
338 if (text[i] ==
'(') {
340 }
else if (text[i] ==
')') {
348 return StringRef::npos;
352 std::vector<const DeferCall *> getDefersForScope(
unsigned scopeId,
const std::vector<DeferCall> &deferCalls)
const {
353 std::vector<const DeferCall *> result;
354 for (
const auto &dc : deferCalls) {
355 if (dc.scopeId == scopeId) {
356 result.push_back(&dc);
360 std::reverse(result.begin(), result.end());
365 std::string formatDeferExpression(
const std::string &expr)
const {
367 if (!expr.empty() && expr[0] ==
'{') {
369 return "do " + expr +
" while(0); ";
372 std::string result = expr;
374 while (!result.empty() && (result.back() ==
';' || result.back() ==
' ' || result.back() ==
'\t' ||
375 result.back() ==
'\n' || result.back() ==
'\r')) {
378 return result +
"; ";
384 std::string generateInlineCleanupForReturn(
const ReturnInfo &returnInfo,
const FunctionTransformState &state)
const {
387 for (
auto scopeIt = returnInfo.activeScopeIds.rbegin(); scopeIt != returnInfo.activeScopeIds.rend(); ++scopeIt) {
388 unsigned scopeId = *scopeIt;
389 auto blockIt = state.blockScopes.find(scopeId);
390 if (blockIt == state.blockScopes.end() || !blockIt->second.hasDefers) {
394 auto defers = getDefersForScopeBeforeOffset(scopeId, state.deferCalls, returnInfo.fileOffset);
395 for (
const auto *dc : defers) {
396 code += formatDeferExpression(dc->expression);
403 std::vector<const DeferCall *>
404 getDefersForScopeBeforeOffset(
unsigned scopeId,
const std::vector<DeferCall> &deferCalls,
unsigned maxOffset)
const {
405 std::vector<const DeferCall *> result;
406 for (
const auto &dc : deferCalls) {
407 if (dc.scopeId == scopeId && dc.fileOffset < maxOffset) {
408 result.push_back(&dc);
412 std::reverse(result.begin(), result.end());
417 std::string generateInlineCleanupAtBlockEnd(
unsigned scopeId,
const FunctionTransformState &state)
const {
419 auto defers = getDefersForScope(scopeId, state.deferCalls);
420 for (
const auto *dc : defers) {
421 code +=
" " + formatDeferExpression(dc->expression) +
"\n";
426 void transformFunction(FunctionTransformState &state) {
427 if (!state.funcDecl || state.deferCalls.empty()) {
431 Stmt *body = state.funcDecl->getBody();
436 CompoundStmt *compoundBody = dyn_cast<CompoundStmt>(body);
442 for (
const DeferCall &deferCall : state.deferCalls) {
443 removeDeferStatement(deferCall);
447 for (
const ReturnInfo &returnInfo : state.returnInfos) {
448 std::string cleanup = generateInlineCleanupForReturn(returnInfo, state);
449 if (!cleanup.empty()) {
450 rewriter_.InsertText(returnInfo.location, cleanup,
true,
true);
456 for (
const auto &pair : state.blockScopes) {
457 const BlockScope &blockScope = pair.second;
458 if (blockScope.hasDefers && blockScope.endLoc.isValid() && !blockScope.endsWithReturn) {
459 std::string cleanup = generateInlineCleanupAtBlockEnd(blockScope.scopeId, state);
460 if (!cleanup.empty()) {
461 rewriter_.InsertText(blockScope.endLoc, cleanup,
true,
true);
467 void removeDeferStatement(
const DeferCall &deferCall) {
468 SourceManager &sourceManager = context_.getSourceManager();
470 SourceLocation macroLoc = deferCall.location;
471 if (!macroLoc.isValid()) {
476 FileID fileId = sourceManager.getFileID(macroLoc);
477 bool invalid =
false;
478 StringRef fileData = sourceManager.getBufferData(fileId, &invalid);
483 unsigned offset = sourceManager.getFileOffset(macroLoc);
486 size_t deferStart = fileData.find(
"defer(", offset);
487 if (deferStart == StringRef::npos || deferStart != offset) {
492 size_t openParen = deferStart + 5;
493 size_t closeParen = findMatchingParenInFile(fileData, openParen);
494 if (closeParen == StringRef::npos) {
499 size_t semicolonPos = closeParen + 1;
500 while (semicolonPos < fileData.size() && (fileData[semicolonPos] ==
' ' || fileData[semicolonPos] ==
'\t' ||
501 fileData[semicolonPos] ==
'\n' || fileData[semicolonPos] ==
'\r')) {
504 if (semicolonPos >= fileData.size() || fileData[semicolonPos] !=
';') {
508 SourceLocation semicolonLoc = macroLoc.getLocWithOffset(semicolonPos - offset);
509 CharSourceRange deferRange = CharSourceRange::getCharRange(macroLoc, semicolonLoc.getLocWithOffset(1));
513 std::string exprSummary = deferCall.expression;
514 bool isBlockDefer = !exprSummary.empty() && exprSummary[0] ==
'{';
516 exprSummary =
"{...}";
518 std::string comment =
"/* defer: " + exprSummary +
" (moved to scope exit) */";
519 rewriter_.ReplaceText(deferRange, comment);
522 size_t findMatchingParenInFile(StringRef fileData,
size_t openPos)
const {
523 if (openPos >= fileData.size() || fileData[openPos] !=
'(') {
524 return StringRef::npos;
528 size_t i = openPos + 1;
529 while (i < fileData.size()) {
530 char c = fileData[i];
534 }
else if (c ==
')') {
540 }
else if (c ==
'"') {
543 while (i < fileData.size() && fileData[i] !=
'"') {
544 if (fileData[i] ==
'\\' && i + 1 < fileData.size()) {
549 if (i < fileData.size()) {
552 }
else if (c ==
'\'') {
555 while (i < fileData.size() && fileData[i] !=
'\'') {
556 if (fileData[i] ==
'\\' && i + 1 < fileData.size()) {
561 if (i < fileData.size()) {
569 return StringRef::npos;
572 ASTContext &context_;
576 FunctionTransformState currentFunction_;
579class DeferASTConsumer :
public ASTConsumer {
581 explicit DeferASTConsumer(DeferVisitor &visitor) : visitor_(visitor) {}
583 void HandleTranslationUnit(ASTContext &context)
override {
584 visitor_.TraverseDecl(context.getTranslationUnitDecl());
588 DeferVisitor &visitor_;
591class DeferFrontendAction :
public ASTFrontendAction {
593 explicit DeferFrontendAction(
const fs::path &outputDir,
const fs::path &inputRoot)
594 : outputDir_(outputDir), inputRoot_(inputRoot) {
595 initializeProtectedDirectories();
598 void EndSourceFileAction()
override {
599 SourceManager &sourceManager = rewriter_.getSourceMgr();
600 const FileEntry *fileEntry = sourceManager.getFileEntryForID(sourceManager.getMainFileID());
606 llvm::errs() <<
"Defer visitor not initialized; skipping file output\n";
607 hadWriteError_ =
true;
611 const StringRef filePathRef = fileEntry->tryGetRealPathName();
612 if (filePathRef.empty()) {
613 llvm::errs() <<
"Unable to resolve file path for transformed output\n";
617 const fs::path originalPath = fs::path(filePathRef.str());
618 const std::string relativePath = visitor_->makeRelativePath(originalPath);
619 fs::path destinationPath = outputDir_ / relativePath;
623 fs::path canonicalOriginal = fs::canonical(originalPath, ec);
625 fs::path canonicalDest = fs::weakly_canonical(destinationPath, ec);
626 if (!ec && canonicalOriginal == canonicalDest) {
627 llvm::errs() <<
"ERROR: Output path is the same as source file! Refusing to overwrite source.\n";
628 llvm::errs() <<
" Source: " << canonicalOriginal.string() <<
"\n";
629 llvm::errs() <<
" Output: " << canonicalDest.string() <<
"\n";
630 hadWriteError_ =
true;
638 const std::string destinationString = destinationPath.generic_string();
645 bool fileExists = llvm::sys::fs::exists(llvm::Twine(destinationString));
647 if (fileExists && isInProtectedSourceTree(destinationPath)) {
648 llvm::errs() <<
"Refusing to overwrite existing file in protected source tree: " << destinationString <<
"\n";
650 hadWriteError_ =
true;
654 const fs::path parent = destinationPath.parent_path();
655 std::error_code directoryError;
656 fs::create_directories(parent, directoryError);
657 if (directoryError) {
658 llvm::errs() <<
"Failed to create output directory: " << parent.string() <<
" - " << directoryError.message()
661 hadWriteError_ =
true;
665 std::string rewrittenContents;
666 if (
const RewriteBuffer *buffer = rewriter_.getRewriteBufferFor(sourceManager.getMainFileID())) {
667 rewrittenContents.assign(buffer->begin(), buffer->end());
670 rewrittenContents = sourceManager.getBufferData(sourceManager.getMainFileID()).str();
673 std::error_code fileError;
674 llvm::raw_fd_ostream outputStream(destinationPath.string(), fileError, llvm::sys::fs::OF_Text);
676 llvm::errs() <<
"Failed to open output file: " << destinationString <<
" - " << fileError.message() <<
"\n";
678 hadWriteError_ =
true;
682 outputStream << rewrittenContents;
683 outputStream.close();
684 if (outputStream.has_error()) {
685 llvm::errs() <<
"Error while writing transformed file: " << destinationString <<
"\n";
687 hadWriteError_ =
true;
691 bool hadWriteError()
const {
692 return hadWriteError_;
696 std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &compiler, StringRef)
override {
697 rewriter_.setSourceMgr(compiler.getSourceManager(), compiler.getLangOpts());
698 visitor_ = std::make_unique<DeferVisitor>(compiler.getASTContext(), rewriter_, outputDir_, inputRoot_);
699 return std::make_unique<DeferASTConsumer>(*visitor_);
706 fs::path inputRootCanonical_;
707 fs::path protectedSrcDir_;
708 fs::path protectedLibDir_;
709 std::unique_ptr<DeferVisitor> visitor_;
710 bool hadWriteError_ =
false;
712 void initializeProtectedDirectories() {
714 fs::path normalizedRoot = inputRoot_;
716 if (normalizedRoot.empty()) {
717 normalizedRoot = fs::current_path(ec);
721 if (!normalizedRoot.is_absolute()) {
722 normalizedRoot = fs::absolute(normalizedRoot, ec);
726 inputRootCanonical_ = fs::weakly_canonical(normalizedRoot, ec);
728 inputRootCanonical_.clear();
732 protectedSrcDir_ = inputRootCanonical_ /
"src";
733 protectedLibDir_ = inputRootCanonical_ /
"lib";
736 static bool pathStartsWith(
const fs::path &path,
const fs::path &prefix) {
737 if (prefix.empty()) {
741 auto pathIter = path.begin();
742 for (
auto prefixIter = prefix.begin(); prefixIter != prefix.end(); ++prefixIter) {
743 if (pathIter == path.end() || *pathIter != *prefixIter) {
752 bool isInProtectedSourceTree(
const fs::path &path)
const {
753 if (inputRootCanonical_.empty()) {
758 fs::path canonicalPath = fs::weakly_canonical(path, ec);
763 return pathStartsWith(canonicalPath, protectedSrcDir_) || pathStartsWith(canonicalPath, protectedLibDir_);
767class DeferActionFactory :
public tooling::FrontendActionFactory {
769 DeferActionFactory(
const fs::path &outputDir,
const fs::path &inputRoot)
770 : outputDir_(outputDir), inputRoot_(inputRoot) {}
772 std::unique_ptr<FrontendAction> create() {
773 return std::make_unique<DeferFrontendAction>(outputDir_, inputRoot_);
784static fs::path g_originalCwd;
786int main(
int argc,
const char **argv) {
787 InitLLVM InitLLVM(argc, argv);
790 g_originalCwd = fs::current_path();
793 cl::HideUnrelatedOptions(ToolCategory);
795 cl::ParseCommandLineOptions(argc, argv,
"ascii-defer transformation tool\n");
797 fs::path outputDir = fs::path(OutputDirectoryOption.getValue());
799 if (!outputDir.is_absolute()) {
800 outputDir = g_originalCwd / outputDir;
803 if (!InputRootOption.getValue().empty()) {
804 inputRoot = fs::path(InputRootOption.getValue());
806 inputRoot = fs::current_path();
810 if (!inputRoot.is_absolute()) {
812 inputRoot = fs::absolute(inputRoot, ec);
814 llvm::errs() <<
"Failed to resolve input root path: " << ec.message() <<
"\n";
819 std::vector<std::string> sourcePaths;
820 for (
const auto &path : SourcePaths) {
822 sourcePaths.push_back(path);
826 if (sourcePaths.empty()) {
827 llvm::errs() <<
"No translation units specified for transformation. Provide positional source paths.\n";
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";
837 std::error_code errorCode;
838 fs::create_directories(outputDir, errorCode);
840 llvm::errs() <<
"Failed to create output directory: " << outputDir.c_str() <<
" - " << errorCode.message()
847 std::string buildPath = BuildPath.getValue();
848 if (buildPath.empty()) {
851 std::string errorMessage;
852 std::unique_ptr<tooling::CompilationDatabase> compilations =
853 tooling::CompilationDatabase::loadFromDirectory(buildPath, errorMessage);
855 llvm::errs() <<
"Error loading compilation database from '" << buildPath <<
"': " << errorMessage <<
"\n";
859 tooling::ClangTool tool(*compilations, sourcePaths);
865 std::vector<std::string> prependArgs;
869 std::string resourceDir;
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";
876 llvm::errs() <<
"Embedded clang resource directory not found: " << CLANG_RESOURCE_DIR <<
"\n";
881 if (resourceDir.empty()) {
883 std::vector<std::string> searchPaths;
887 searchPaths.push_back(
"/opt/homebrew/opt/llvm/lib/clang");
889 searchPaths.push_back(
"/usr/local/opt/llvm/lib/clang");
891 searchPaths.push_back(
"/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang");
893 searchPaths.push_back(
"/Library/Developer/CommandLineTools/usr/lib/clang");
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");
904 searchPaths.push_back(
"/usr/local/lib/clang");
906 for (
const auto& basePath : searchPaths) {
907 if (!llvm::sys::fs::exists(basePath)) {
913 std::string bestVersion;
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();
921 if (sscanf(name.c_str(),
"%d", &major) == 1 && major > bestMajor) {
923 bestVersion = dir->path();
927 if (!bestVersion.empty()) {
928 resourceDir = bestVersion;
929 llvm::errs() <<
"Found clang resource directory at runtime: " << resourceDir <<
"\n";
934 if (resourceDir.empty()) {
935 llvm::errs() <<
"Warning: Could not find clang resource directory\n";
939 if (!resourceDir.empty()) {
940 prependArgs.push_back(std::string(
"-resource-dir=") + resourceDir);
947 prependArgs.push_back(
"-target");
948 prependArgs.push_back(
"arm64-apple-darwin");
949 llvm::errs() <<
"Using target: arm64-apple-darwin\n";
951 prependArgs.push_back(
"-target");
952 prependArgs.push_back(
"x86_64-apple-darwin");
953 llvm::errs() <<
"Using target: x86_64-apple-darwin\n";
955#elif defined(__linux__)
957 prependArgs.push_back(
"-target");
958 prependArgs.push_back(
"aarch64-linux-gnu");
959 llvm::errs() <<
"Using target: aarch64-linux-gnu\n";
961 prependArgs.push_back(
"-target");
962 prependArgs.push_back(
"x86_64-linux-gnu");
963 llvm::errs() <<
"Using target: x86_64-linux-gnu\n";
970 std::string selectedSDK;
973 const char* sdkPaths[] = {
975 "/Applications/Xcode.app/Contents/Developer/Platforms/"
976 "MacOSX.platform/Developer/SDKs/MacOSX.sdk",
978 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk",
981 for (
const char* sdk : sdkPaths) {
982 if (llvm::sys::fs::exists(sdk)) {
988 if (!selectedSDK.empty()) {
989 prependArgs.push_back(
"-isysroot");
990 prependArgs.push_back(selectedSDK);
991 llvm::errs() <<
"Using macOS SDK: " << selectedSDK <<
"\n";
993 llvm::errs() <<
"Warning: No macOS SDK found, system headers may not be available\n";
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";
1031 std::string inputRootStr = inputRoot.string();
1032 auto consolidatedAdjuster = [prependArgs, appendArgs, inputRootStr](
const tooling::CommandLineArguments &args, StringRef) {
1034 auto isProjectPath = [&inputRootStr](
const std::string &path) ->
bool {
1035 if (inputRootStr.empty())
return false;
1038 auto normalizedPath = fs::canonical(path, ec);
1039 if (ec)
return false;
1040 auto normalizedRoot = fs::canonical(inputRootStr, ec);
1041 if (ec)
return false;
1043 std::string pathStr = normalizedPath.string();
1044 std::string rootStr = normalizedRoot.string();
1045 if (pathStr.find(rootStr) != 0)
return false;
1048 if (pathStr.find(
"/.deps-cache/") != std::string::npos)
return false;
1051 tooling::CommandLineArguments result;
1058 result.push_back(args[0]);
1061 for (
const auto &arg : prependArgs) {
1062 result.push_back(arg);
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];
1087 foundSeparator =
true;
1093 if (arg.find(
"-fsanitize") != std::string::npos)
1095 if (arg.find(
"-fno-sanitize") != std::string::npos)
1098 if (arg ==
"-g" || arg ==
"-g2" || arg ==
"-g3")
1100 if (arg ==
"-fno-eliminate-unused-debug-types")
1102 if (arg ==
"-fno-inline")
1105 if (arg ==
"-resource-dir") {
1109 if (arg.find(
"-resource-dir=") == 0)
1112 if (arg ==
"-isysroot") {
1116 if (arg.find(
"-isysroot=") == 0 || (arg.find(
"-isysroot") == 0 && arg.length() > 9))
1121 if (arg ==
"-isystem" && i + 1 < args.size()) {
1122 collectedIsystemPaths.push_back(args[++i]);
1125 if (arg.find(
"-isystem") == 0 && arg.length() > 8) {
1126 collectedIsystemPaths.push_back(arg.substr(8));
1134 if (arg ==
"-I" && i + 1 < args.size()) {
1136 const std::string &includePath = args[++i];
1137 if (isProjectPath(includePath)) {
1138 result.push_back(
"-iquote");
1139 result.push_back(includePath);
1142 collectedIsystemPaths.push_back(includePath);
1146 if (arg.find(
"-I") == 0 && arg.length() > 2) {
1148 std::string includePath = arg.substr(2);
1149 if (isProjectPath(includePath)) {
1150 result.push_back(
"-iquote");
1151 result.push_back(includePath);
1154 collectedIsystemPaths.push_back(includePath);
1159 result.push_back(arg);
1165 for (
const auto &arg : appendArgs) {
1166 result.push_back(arg);
1169 for (
const auto &path : collectedIsystemPaths) {
1170 result.push_back(
"-isystem");
1171 result.push_back(path);
1175 result.push_back(
"-DASCIICHAT_DEFER_TOOL_PARSING");
1176 if (foundSeparator) {
1177 result.push_back(
"--");
1179 for (
size_t i = separatorIndex + 1; i < args.size(); ++i) {
1180 result.push_back(args[i]);
1186 tool.appendArgumentsAdjuster(consolidatedAdjuster);
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";
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";
1202 return executionResult;