refactor: move command files to src/command/, remove scan_fuzzy (#366)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
ykiko
2026-03-25 23:54:32 +08:00
committed by GitHub
parent 020c2cb3cc
commit f7a8d104ce
16 changed files with 60 additions and 449 deletions

View File

@@ -131,8 +131,8 @@ add_custom_target(generate_flatbuffers_schema DEPENDS "${GENERATED_HEADER}")
# Temporary migration-only build graph.
add_library(clice-core STATIC
"${PROJECT_SOURCE_DIR}/src/compile/command.cpp"
"${PROJECT_SOURCE_DIR}/src/compile/toolchain.cpp"
"${PROJECT_SOURCE_DIR}/src/command/command.cpp"
"${PROJECT_SOURCE_DIR}/src/command/toolchain.cpp"
"${PROJECT_SOURCE_DIR}/src/compile/compilation.cpp"
"${PROJECT_SOURCE_DIR}/src/compile/compilation_unit.cpp"
"${PROJECT_SOURCE_DIR}/src/compile/diagnostic.cpp"

View File

@@ -1,11 +1,10 @@
#include <cstdint>
#include <iostream>
#include <print>
#include <sstream>
#include <string>
#include "eventide/async/async.h"
#include "eventide/deco/macro.h"
#include "eventide/deco/runtime.h"
#include "eventide/deco/deco.h"
#include "eventide/ipc/peer.h"
#include "eventide/ipc/transport.h"
#include "server/master_server.h"
@@ -61,10 +60,7 @@ int main(int argc, const char** argv) {
auto& opts = result->options;
if(opts.help.value_or(false)) {
auto dispatcher = deco::cli::Dispatcher<clice::Options>("clice [OPTIONS]");
std::ostringstream oss;
dispatcher.usage(oss, true);
std::print("{}", oss.str());
deco::cli::write_usage_for<clice::Options>(std::cout, "clice [OPTIONS]");
return 0;
}

View File

@@ -1,12 +1,12 @@
#include "compile/command.h"
#include "command/command.h"
#include <array>
#include <ranges>
#include <string_view>
#include <tuple>
#include "compile/driver.h"
#include "compile/toolchain.h"
#include "command/driver.h"
#include "command/toolchain.h"
#include "support/filesystem.h"
#include "support/logging.h"
#include "support/object_pool.h"
@@ -252,12 +252,20 @@ struct CompilationDatabase::Impl {
llvm::SmallVector<const char*, 32> arguments;
/// FIXME: We need a better way to handle this.
if(command.contains("cl.exe") || command.contains("clang-cl")) {
llvm::cl::TokenizeWindowsCommandLineFull(command, saver, arguments);
} else {
llvm::cl::TokenizeGNUCommandLine(command, saver, arguments);
}
/// On Windows, always use the Windows tokenizer regardless of the compiler
/// (MSVC, clang-cl, MinGW, etc.), because all programs are invoked through
/// the Windows API and paths use backslashes. The GNU tokenizer treats '\'
/// as an escape character, which corrupts Windows paths like C:\Users into
/// C:Users.
///
/// Note: this does NOT affect toolchain.cpp's query_clang_toolchain(), which
/// parses clang's -### output. That output uses shell-style escaping (\\),
/// so the GNU tokenizer is correct there.
#ifdef _WIN32
llvm::cl::TokenizeWindowsCommandLineFull(command, saver, arguments);
#else
llvm::cl::TokenizeGNUCommandLine(command, saver, arguments);
#endif
return self.save_compilation_info(file, directory, arguments);
}

View File

@@ -5,7 +5,7 @@
#include <memory>
#include <ranges>
#include "compile/command.h"
#include "command/command.h"
#include "llvm/Support/Allocator.h"
#include "clang/Driver/Driver.h"

View File

@@ -1,4 +1,4 @@
#include "compile/toolchain.h"
#include "command/toolchain.h"
#include <optional>
#include <string>

View File

@@ -1,6 +1,6 @@
#include "compile/compilation.h"
#include "compile/command.h"
#include "command/command.h"
#include "compile/diagnostic.h"
#include "compile/implement.h"
#include "semantic/ast_utility.h"

View File

@@ -4,7 +4,7 @@
#include <memory>
#include <string>
#include "compile/command.h"
#include "command/command.h"
#include "eventide/async/async.h"
#include "eventide/ipc/lsp/protocol.h"
#include "eventide/ipc/peer.h"

View File

@@ -4,7 +4,6 @@
#include "syntax/lexer.h"
#include "llvm/ADT/StringSet.h"
#include "llvm/Support/MemoryBuffer.h"
#include "clang/Basic/DiagnosticOptions.h"
#include "clang/Basic/FileEntry.h"
@@ -62,11 +61,13 @@ ScanResult scan(llvm::StringRef content) {
auto name = content.substr(tok.Offset, tok.Length);
// Strip <> or "" delimiters.
if(name.size() >= 2) {
result.includes.push_back({
std::string(name.substr(1, name.size() - 2)),
conditional_depth > 0,
false,
});
bool angled = name.front() == '<';
ScanResult::IncludeInfo info;
info.path = std::string(name.substr(1, name.size() - 2));
info.conditional = conditional_depth > 0;
info.is_angled = angled;
info.is_include_next = dir.Kind == dds::pp_include_next;
result.includes.push_back(std::move(info));
}
break;
}
@@ -118,53 +119,14 @@ ScanResult scan(llvm::StringRef content) {
namespace {
enum class ScanMode { Fuzzy, Precise };
/// Compute include_is_conditional from raw directives: for each pp_include
/// (and pp_include_next, pp___include_macros, pp_import), record whether
/// it is nested inside any conditional block.
void compute_include_conditionals(SharedScanCache::CachedEntry& entry) {
using namespace clang::dependency_directives_scan;
entry.include_is_conditional.clear();
int cond_depth = 0;
for(auto& dir: entry.directives) {
switch(dir.Kind) {
case pp_if:
case pp_ifdef:
case pp_ifndef: {
cond_depth++;
break;
}
case pp_endif: {
if(cond_depth > 0) {
cond_depth--;
}
break;
}
case pp_include:
case pp_include_next:
case pp___include_macros:
case pp_import: {
entry.include_is_conditional.push_back(cond_depth > 0);
break;
}
default: {
break;
}
}
}
}
class ScanDirectivesGetter : public clang::DependencyDirectivesGetter {
public:
ScanDirectivesGetter(ScanMode mode, SharedScanCache* cache, clang::FileManager& file_mgr) :
mode(mode), cache(cache), file_mgr(&file_mgr) {}
ScanDirectivesGetter(SharedScanCache* cache, clang::FileManager& file_mgr) :
cache(cache), file_mgr(&file_mgr) {}
std::unique_ptr<clang::DependencyDirectivesGetter>
cloneFor(clang::FileManager& new_file_mgr) override {
return std::make_unique<ScanDirectivesGetter>(mode, cache, new_file_mgr);
return std::make_unique<ScanDirectivesGetter>(cache, new_file_mgr);
}
std::optional<llvm::ArrayRef<clang::dependency_directives_scan::Directive>>
@@ -178,7 +140,7 @@ public:
if(cache) {
auto it = cache->entries.find(path);
if(it != cache->entries.end()) {
return get_directives(it->second);
return llvm::ArrayRef(it->second.directives);
}
}
@@ -216,149 +178,13 @@ public:
return std::nullopt;
}
compute_include_conditionals(*entry_ptr);
return get_directives(*entry_ptr);
return llvm::ArrayRef(entry_ptr->directives);
}
private:
using DirectiveVec = llvm::SmallVector<clang::dependency_directives_scan::Directive>;
llvm::ArrayRef<clang::dependency_directives_scan::Directive>
get_directives(SharedScanCache::CachedEntry& entry) {
if(mode == ScanMode::Precise) {
return entry.directives;
}
// Fuzzy mode: strip #define/#undef and ALL conditional directives,
// so every #include is processed unconditionally by the preprocessor.
auto& slot = filtered_directives[&entry];
if(slot && !slot->empty()) {
return *slot;
}
slot = std::make_unique<DirectiveVec>();
using namespace clang::dependency_directives_scan;
for(auto& dir: entry.directives) {
switch(dir.Kind) {
case pp_define:
case pp_undef:
case pp_if:
case pp_ifdef:
case pp_ifndef:
case pp_elif:
case pp_elifdef:
case pp_elifndef:
case pp_else:
case pp_endif:
case pp_pragma_push_macro:
case pp_pragma_pop_macro: {
break;
}
default: {
slot->push_back(dir);
break;
}
}
}
return *slot;
}
ScanMode mode;
SharedScanCache* cache;
clang::FileManager* file_mgr;
std::deque<SharedScanCache::CachedEntry> local_entries;
llvm::DenseMap<SharedScanCache::CachedEntry*, std::unique_ptr<DirectiveVec>>
filtered_directives;
};
/// PPCallbacks for fuzzy mode: tracks per-file includes with conditional
/// flags looked up from the SharedScanCache.
class FuzzyScanPPCallbacks : public clang::PPCallbacks {
public:
FuzzyScanPPCallbacks(llvm::StringMap<ScanResult>& results,
SharedScanCache& cache,
clang::SourceManager& source_mgr) :
results(results), cache(cache), source_mgr(source_mgr) {}
void FileChanged(clang::SourceLocation loc,
FileChangeReason reason,
clang::SrcMgr::CharacteristicKind,
clang::FileID) override {
if(reason == EnterFile) {
current_file = get_file_path(source_mgr.getFileID(loc));
}
}
bool FileNotFound(llvm::StringRef file_name) override {
// Record the not-found include and consume the include counter
// so conditional flag correlation stays in sync.
record_include(current_file, file_name.str(), true);
// Return true to suppress the diagnostic and continue scanning.
return true;
}
void InclusionDirective(clang::SourceLocation hash_loc,
const clang::Token&,
llvm::StringRef file_name,
bool,
clang::CharSourceRange,
clang::OptionalFileEntryRef file,
llvm::StringRef,
llvm::StringRef,
const clang::Module*,
bool,
clang::SrcMgr::CharacteristicKind) override {
// Determine which file this include is from via HashLoc.
auto from_file = get_file_path(source_mgr.getFileID(hash_loc));
std::string resolved_path;
if(file) {
resolved_path = file->getFileEntry().tryGetRealPathName().str();
if(resolved_path.empty()) {
resolved_path = file->getName().str();
}
} else {
resolved_path = file_name.str();
}
record_include(from_file, std::move(resolved_path), !file.has_value());
}
private:
llvm::StringRef get_file_path(clang::FileID fid) {
auto fe = source_mgr.getFileEntryRefForID(fid);
if(fe) {
auto path = fe->getFileEntry().tryGetRealPathName();
return path.empty() ? fe->getName() : path;
}
return "";
}
void record_include(llvm::StringRef from_file, std::string path, bool not_found) {
// Look up conditional flag from cache.
bool conditional = false;
auto cache_it = cache.entries.find(from_file);
if(cache_it != cache.entries.end()) {
unsigned idx = include_counters[from_file]++;
if(idx < cache_it->second.include_is_conditional.size()) {
conditional = cache_it->second.include_is_conditional[idx];
}
}
results[from_file].includes.push_back({
std::move(path),
conditional,
not_found,
});
}
llvm::StringMap<ScanResult>& results;
SharedScanCache& cache;
clang::SourceManager& source_mgr;
llvm::StringRef current_file;
llvm::StringMap<unsigned> include_counters;
};
/// PPCallbacks for precise mode: single ScanResult with accurate
@@ -368,9 +194,9 @@ public:
explicit PreciseScanPPCallbacks(ScanResult& result) : result(result) {}
void InclusionDirective(clang::SourceLocation,
const clang::Token&,
const clang::Token& include_tok,
llvm::StringRef file_name,
bool,
bool is_angled,
clang::CharSourceRange,
clang::OptionalFileEntryRef file,
llvm::StringRef,
@@ -386,11 +212,15 @@ public:
resolved_path = file_name.str();
}
result.includes.push_back({
std::move(resolved_path),
conditional_depth > 0,
not_found,
});
ScanResult::IncludeInfo info;
info.path = std::move(resolved_path);
info.conditional = conditional_depth > 0;
info.not_found = not_found;
info.is_angled = is_angled;
info.is_include_next =
include_tok.getIdentifierInfo() &&
include_tok.getIdentifierInfo()->getPPKeywordID() == clang::tok::pp_include_next;
result.includes.push_back(std::move(info));
}
void If(clang::SourceLocation, clang::SourceRange, ConditionValueKind) override {
@@ -494,54 +324,6 @@ std::unique_ptr<clang::CompilerInstance>
} // namespace
llvm::StringMap<ScanResult> scan_fuzzy(llvm::ArrayRef<const char*> arguments,
llvm::StringRef directory,
llvm::StringRef content,
SharedScanCache* cache,
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> vfs) {
llvm::StringMap<ScanResult> results;
if(!vfs) {
vfs = llvm::vfs::createPhysicalFileSystem();
}
auto instance = create_scan_instance(arguments, directory, content, vfs);
if(!instance) {
return results;
}
// Use a local cache if none provided, so we always have conditional flags.
SharedScanCache local_cache;
if(!cache) {
cache = &local_cache;
}
auto getter =
std::make_unique<ScanDirectivesGetter>(ScanMode::Fuzzy, cache, instance->getFileManager());
instance->setDependencyDirectivesGetter(std::move(getter));
if(!instance->createTarget()) {
return results;
}
auto action = std::make_unique<clang::PreprocessOnlyAction>();
if(!action->BeginSourceFile(*instance, instance->getFrontendOpts().Inputs[0])) {
return results;
}
instance->getPreprocessor().addPPCallbacks(
std::make_unique<FuzzyScanPPCallbacks>(results, *cache, instance->getSourceManager()));
if(auto error = action->Execute()) {
llvm::consumeError(std::move(error));
}
action->EndSourceFile();
return results;
}
ScanResult scan_precise(llvm::ArrayRef<const char*> arguments,
llvm::StringRef directory,
llvm::StringRef content,
@@ -558,9 +340,7 @@ ScanResult scan_precise(llvm::ArrayRef<const char*> arguments,
return result;
}
auto getter = std::make_unique<ScanDirectivesGetter>(ScanMode::Precise,
cache,
instance->getFileManager());
auto getter = std::make_unique<ScanDirectivesGetter>(cache, instance->getFileManager());
instance->setDependencyDirectivesGetter(std::move(getter));
if(!instance->createTarget()) {

View File

@@ -33,6 +33,12 @@ struct ScanResult {
/// Whether the included file was not found during resolution.
bool not_found = false;
/// Whether this is an angled include (<...>) vs quoted ("...").
bool is_angled = false;
/// Whether this is an #include_next directive.
bool is_include_next = false;
};
/// Include file names.
@@ -55,10 +61,6 @@ struct SharedScanCache {
/// Scanned directives (referencing tokens above).
llvm::SmallVector<clang::dependency_directives_scan::Directive> directives;
/// Whether each pp_include directive is inside a conditional block,
/// computed from the raw directive structure before filtering.
std::vector<bool> include_is_conditional;
};
/// path -> cached scan result.
@@ -70,17 +72,6 @@ struct SharedScanCache {
/// and module_name will be empty.
ScanResult scan(llvm::StringRef content);
/// Fuzzy preprocessing-based scan. Strips #define and conditional directives
/// so ALL #include are processed unconditionally. Each include is marked
/// with its structural conditional status from the raw directive scan.
/// Returns per-file results (main file + all transitively included files).
llvm::StringMap<ScanResult>
scan_fuzzy(llvm::ArrayRef<const char*> arguments,
llvm::StringRef directory,
llvm::StringRef content = {},
SharedScanCache* cache = nullptr,
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> vfs = nullptr);
/// Precise preprocessing-based scan. Keeps all directives including #define
/// and conditionals. Used for lazy module dependency resolution.
ScanResult scan_precise(llvm::ArrayRef<const char*> arguments,

View File

@@ -1,5 +1,5 @@
#include "test/test.h"
#include "compile/command.h"
#include "command/command.h"
#include "compile/compilation.h"
#include "llvm/ADT/ScopeExit.h"

View File

@@ -1,6 +1,6 @@
#include "test/test.h"
#include "command/toolchain.h"
#include "compile/compilation.h"
#include "compile/toolchain.h"
#include "support/logging.h"
#include "llvm/Support/Allocator.h"

View File

@@ -4,21 +4,8 @@
namespace clice::testing {
namespace {
/// Helper: find entry in StringMap whose key contains the given substring.
template <typename T>
auto find_by_substr(llvm::StringMap<T>& map, llvm::StringRef substr) {
for(auto it = map.begin(); it != map.end(); ++it) {
if(it->first().contains(substr)) {
return it;
}
}
return map.end();
}
TEST_SUITE(Scan) {
// === scan() tests ===
TEST_CASE(BasicIncludes) {
auto result = scan(R"(
#include <vector>
@@ -143,156 +130,6 @@ int main() {
EXPECT_TRUE(result.module_name.empty());
}
// === scan_fuzzy() tests ===
TEST_CASE(FuzzyBasic) {
auto vfs = llvm::makeIntrusiveRefCnt<TestVFS>();
auto main_path = TestVFS::path("main.cpp");
vfs->add("main.cpp", R"(
#include "header.h"
int main() {}
)");
vfs->add("header.h", R"(
#pragma once
int x = 1;
)");
auto args = std::vector<const char*>{"clang++", "-std=c++20", main_path.c_str()};
auto results = scan_fuzzy(args, TestVFS::root(), {}, nullptr, vfs);
auto main_it = find_by_substr(results, "main.cpp");
ASSERT_TRUE(main_it != results.end());
ASSERT_EQ(main_it->second.includes.size(), 1u);
EXPECT_FALSE(main_it->second.includes[0].not_found);
EXPECT_FALSE(main_it->second.includes[0].conditional);
}
TEST_CASE(FuzzyConditionalTracking) {
auto vfs = llvm::makeIntrusiveRefCnt<TestVFS>();
auto main_path = TestVFS::path("main.cpp");
vfs->add("main.cpp", R"(
#include "always.h"
#ifdef FOO
#include "conditional.h"
#endif
#include "after.h"
)");
vfs->add("always.h");
vfs->add("conditional.h");
vfs->add("after.h");
auto args = std::vector<const char*>{"clang++", "-std=c++20", main_path.c_str()};
auto results = scan_fuzzy(args, TestVFS::root(), {}, nullptr, vfs);
auto main_it = find_by_substr(results, "main.cpp");
ASSERT_TRUE(main_it != results.end());
auto& includes = main_it->second.includes;
ASSERT_EQ(includes.size(), 3u);
EXPECT_FALSE(includes[0].conditional); // always.h
EXPECT_TRUE(includes[1].conditional); // conditional.h
EXPECT_FALSE(includes[2].conditional); // after.h
}
TEST_CASE(FuzzyNotFound) {
auto vfs = llvm::makeIntrusiveRefCnt<TestVFS>();
auto main_path = TestVFS::path("main.cpp");
vfs->add("main.cpp", R"(
#include "exists.h"
#include "missing.h"
#include "also_exists.h"
)");
vfs->add("exists.h");
vfs->add("also_exists.h");
auto args = std::vector<const char*>{"clang++", "-std=c++20", main_path.c_str()};
auto results = scan_fuzzy(args, TestVFS::root(), {}, nullptr, vfs);
auto main_it = find_by_substr(results, "main.cpp");
ASSERT_TRUE(main_it != results.end());
auto& includes = main_it->second.includes;
ASSERT_EQ(includes.size(), 3u);
EXPECT_FALSE(includes[0].not_found); // exists.h
EXPECT_TRUE(includes[1].not_found); // missing.h
EXPECT_FALSE(includes[2].not_found); // also_exists.h
}
TEST_CASE(FuzzyTransitiveIncludes) {
auto vfs = llvm::makeIntrusiveRefCnt<TestVFS>();
auto main_path = TestVFS::path("main.cpp");
vfs->add("main.cpp", R"(
#include "a.h"
)");
vfs->add("a.h", R"(
#pragma once
#include "b.h"
)");
vfs->add("b.h", R"(
#pragma once
int b = 1;
)");
auto args = std::vector<const char*>{"clang++", "-std=c++20", main_path.c_str()};
auto results = scan_fuzzy(args, TestVFS::root(), {}, nullptr, vfs);
// main.cpp includes a.h
auto main_it = find_by_substr(results, "main.cpp");
ASSERT_TRUE(main_it != results.end());
ASSERT_EQ(main_it->second.includes.size(), 1u);
// a.h includes b.h
auto a_it = find_by_substr(results, "a.h");
ASSERT_TRUE(a_it != results.end());
ASSERT_EQ(a_it->second.includes.size(), 1u);
}
TEST_CASE(FuzzyWithCache) {
auto vfs = llvm::makeIntrusiveRefCnt<TestVFS>();
auto main_path = TestVFS::path("main.cpp");
auto other_path = TestVFS::path("other.cpp");
vfs->add("main.cpp", R"(
#include "shared.h"
)");
vfs->add("other.cpp", R"(
#include "shared.h"
)");
vfs->add("shared.h", R"(
#pragma once
int shared = 1;
)");
SharedScanCache cache;
auto args1 = std::vector<const char*>{"clang++", "-std=c++20", main_path.c_str()};
auto results1 = scan_fuzzy(args1, TestVFS::root(), {}, &cache, vfs);
// shared.h should be cached after first scan.
EXPECT_FALSE(cache.entries.empty());
auto args2 = std::vector<const char*>{"clang++", "-std=c++20", other_path.c_str()};
auto results2 = scan_fuzzy(args2, TestVFS::root(), {}, &cache, vfs);
// Both scans should find includes.
ASSERT_TRUE(find_by_substr(results1, "main.cpp") != results1.end());
ASSERT_TRUE(find_by_substr(results2, "other.cpp") != results2.end());
}
TEST_CASE(FuzzyWithContent) {
auto vfs = llvm::makeIntrusiveRefCnt<TestVFS>();
auto main_path = TestVFS::path("main.cpp");
vfs->add("main.cpp");
vfs->add("header.h");
auto args = std::vector<const char*>{"clang++", "-std=c++20", main_path.c_str()};
auto results = scan_fuzzy(args, TestVFS::root(), R"(#include "header.h")", nullptr, vfs);
auto main_it = find_by_substr(results, "main.cpp");
ASSERT_TRUE(main_it != results.end());
ASSERT_EQ(main_it->second.includes.size(), 1u);
EXPECT_FALSE(main_it->second.includes[0].not_found);
}
// === scan_precise() tests ===
TEST_CASE(PreciseBasic) {

View File

@@ -5,7 +5,7 @@
#include "test/annotation.h"
#include "test/test.h"
#include "compile/command.h"
#include "command/command.h"
#include "compile/compilation.h"
#include "eventide/ipc/lsp/protocol.h"
#include "support/logging.h"

View File

@@ -1,8 +1,7 @@
#include <string>
#include <string_view>
#include "eventide/deco/macro.h"
#include "eventide/deco/runtime.h"
#include "eventide/deco/deco.h"
#include "eventide/zest/zest.h"
#include "support/filesystem.h"