Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,8 @@ endif()

set(FBS_SCHEMA_FILE "${PROJECT_SOURCE_DIR}/src/index/schema.fbs")
set(GENERATED_HEADER "${PROJECT_BINARY_DIR}/generated/schema_generated.h")
set(CLANG_TIDY_CONFIG_SOURCE_FILE "${PROJECT_SOURCE_DIR}/config/clang-tidy-config.h")
set(CLANG_TIDY_CONFIG_GENERATED_FILE "${PROJECT_BINARY_DIR}/generated/clang-tidy-config.h")

if(CMAKE_CROSSCOMPILING)
find_program(FLATC_EXECUTABLE flatc REQUIRED)
Expand All @@ -143,10 +145,21 @@ add_custom_command(

add_custom_target(generate_flatbuffers_schema DEPENDS "${GENERATED_HEADER}")

add_custom_command(
OUTPUT "${CLANG_TIDY_CONFIG_GENERATED_FILE}"
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${CLANG_TIDY_CONFIG_SOURCE_FILE}"
"${CLANG_TIDY_CONFIG_GENERATED_FILE}"
DEPENDS "${CLANG_TIDY_CONFIG_SOURCE_FILE}"
COMMENT "Generating C++ header from ${CLANG_TIDY_CONFIG_SOURCE_FILE}"
)

add_custom_target(generate_clang_tidy_config DEPENDS "${CLANG_TIDY_CONFIG_GENERATED_FILE}")

file(GLOB_RECURSE CLICE_CORE_SOURCES CONFIGURE_DEPENDS "${PROJECT_SOURCE_DIR}/src/*.cpp")
add_library(clice-core STATIC ${CLICE_CORE_SOURCES})
add_library(clice::core ALIAS clice-core)
add_dependencies(clice-core generate_flatbuffers_schema)
add_dependencies(clice-core generate_flatbuffers_schema generate_clang_tidy_config)

target_include_directories(clice-core PUBLIC
"${PROJECT_SOURCE_DIR}/src"
Expand Down
68 changes: 45 additions & 23 deletions cmake/llvm.cmake
Original file line number Diff line number Diff line change
@@ -1,5 +1,34 @@
include_guard()

set(CLICE_CLANG_TIDY_MODULE_COMPONENTS
# Keep this in sync with scripts/llvm-components.json and the old
# ALL_CLANG_TIDY_CHECKS list. MPIModule is intentionally excluded because
# clice disables static analyzer checks in ClangTidyForceLinker.h.
clangTidyAndroidModule
clangTidyAbseilModule
clangTidyAlteraModule
clangTidyBoostModule
clangTidyBugproneModule
clangTidyCERTModule
clangTidyConcurrencyModule
clangTidyCppCoreGuidelinesModule
clangTidyDarwinModule
clangTidyFuchsiaModule
clangTidyGoogleModule
clangTidyHICPPModule
clangTidyLinuxKernelModule
clangTidyLLVMModule
clangTidyLLVMLibcModule
clangTidyMiscModule
clangTidyModernizeModule
clangTidyObjCModule
clangTidyOpenMPModule
clangTidyPerformanceModule
clangTidyPortabilityModule
clangTidyReadabilityModule
clangTidyZirconModule
)

function(setup_llvm LLVM_VERSION)
find_package(Python3 COMPONENTS Interpreter REQUIRED)

Expand Down Expand Up @@ -69,6 +98,21 @@ function(setup_llvm LLVM_VERSION)
# add to include directories
target_include_directories(llvm-libs INTERFACE "${LLVM_INSTALL_PATH}/include")

set(CLICE_MISSING_CLANG_TIDY_MODULES)
foreach(module IN LISTS CLICE_CLANG_TIDY_MODULE_COMPONENTS)
set(module_library "${LLVM_INSTALL_PATH}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}${module}${CMAKE_STATIC_LIBRARY_SUFFIX}")
if(NOT EXISTS "${module_library}")
list(APPEND CLICE_MISSING_CLANG_TIDY_MODULES "${module}")
endif()
endforeach()

if(CLICE_MISSING_CLANG_TIDY_MODULES)
message(STATUS "Clang-tidy module libraries not available: ${CLICE_MISSING_CLANG_TIDY_MODULES}")
else()
target_compile_definitions(llvm-libs INTERFACE CLICE_HAS_CLANG_TIDY_MODULES=1)
set(CLICE_DEBUG_CLANG_TIDY_MODULE_LIBRARIES ${CLICE_CLANG_TIDY_MODULE_COMPONENTS})
endif()

if(CMAKE_BUILD_TYPE STREQUAL "Debug" AND NOT WIN32)
target_link_directories(llvm-libs INTERFACE "${LLVM_INSTALL_PATH}/lib")
target_link_libraries(llvm-libs INTERFACE
Expand All @@ -87,29 +131,7 @@ function(setup_llvm LLVM_VERSION)
clangSerialization
clangTidy
clangTidyUtils
clangTidyAndroidModule
clangTidyAbseilModule
clangTidyAlteraModule
clangTidyBoostModule
clangTidyBugproneModule
clangTidyCERTModule
clangTidyConcurrencyModule
clangTidyCppCoreGuidelinesModule
clangTidyDarwinModule
clangTidyFuchsiaModule
clangTidyGoogleModule
clangTidyHICPPModule
clangTidyLinuxKernelModule
clangTidyLLVMModule
clangTidyLLVMLibcModule
clangTidyMiscModule
clangTidyModernizeModule
clangTidyObjCModule
clangTidyOpenMPModule
clangTidyPerformanceModule
clangTidyPortabilityModule
clangTidyReadabilityModule
clangTidyZirconModule
${CLICE_DEBUG_CLANG_TIDY_MODULE_LIBRARIES}
clangTooling
clangToolingCore
clangToolingInclusions
Expand Down
30 changes: 29 additions & 1 deletion scripts/prune-llvm-bin.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@
import time
from datetime import datetime, timezone
from pathlib import Path
from typing import Iterable, List, Optional
from typing import Iterable, List, Optional, Set


LLVM_COMPONENTS_FILE = Path(__file__).with_name("llvm-components.json")


def parse_args() -> argparse.Namespace:
Expand Down Expand Up @@ -102,12 +105,33 @@ def run_build(build_dir: Path) -> bool:
return False


def protected_library_names() -> Set[str]:
data = json.loads(LLVM_COMPONENTS_FILE.read_text())
components = data.get("components", [])
if not isinstance(components, list):
raise ValueError(f"{LLVM_COMPONENTS_FILE} missing 'components' list")

names: Set[str] = set()
for component in components:
if not isinstance(component, str):
continue
if not (component.startswith("clangTidy") and component.endswith("Module")):
continue
names.add(f"lib{component}.a")
names.add(f"{component}.lib")
return names


def candidate_files(install_dir: Path) -> Iterable[Path]:
if not install_dir.is_dir():
raise FileNotFoundError(f"lib dir not found: {install_dir}")
protected = protected_library_names()
for path in sorted(install_dir.iterdir()):
if not path.is_file():
continue
if path.name in protected:
print(f"Keeping protected clang-tidy module library: {path.name}")
continue
if path.suffix.lower() in {".a", ".lib"}:
yield path
else:
Expand Down Expand Up @@ -156,7 +180,11 @@ def apply_manifest(manifest: Path, install_dir: Path) -> None:
removed = data.get("removed", [])
if not isinstance(removed, list):
raise ValueError("Manifest missing 'removed' list")
protected = protected_library_names()
for name in removed:
if name in protected:
print(f"Keeping protected clang-tidy module library from manifest: {name}")
continue
target = install_dir / name
if target.exists():
print(f"Deleting {target}")
Expand Down
2 changes: 1 addition & 1 deletion src/clice.cc
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ struct Options {
help =
"Agentic method (compileCommand, symbolSearch, definition, references, "
"documentSymbols, readSymbol, callGraph, typeHierarchy, projectFiles, "
"fileDeps, impactAnalysis, status, shutdown)",
"lint, fileDeps, impactAnalysis, status, shutdown)",
required = false)
<std::string> method;

Expand Down
2 changes: 2 additions & 0 deletions src/compile/compilation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,8 @@ CompilationUnit compile(CompilationParams& params, PCMInfo& out) {
}

CompilationUnit complete(CompilationParams& params, clang::CodeCompleteConsumer* consumer) {
params.kind = CompilationKind::Completion;

auto& [file, offset] = params.completion;

/// The location of clang is 1-1 based.
Expand Down
2 changes: 1 addition & 1 deletion src/compile/compilation.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ struct PCMInfo : ModuleInfo {

struct CompilationParams {
/// The kind of this compilation.
CompilationKind kind;
CompilationKind kind = CompilationKind::Content;

/// Whether to run clang-tidy.
bool clang_tidy = false;
Expand Down
4 changes: 4 additions & 0 deletions src/compile/tidy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@
#include "clang-tidy/ClangTidyDiagnosticConsumer.h"
#include "clang-tidy/ClangTidyModuleRegistry.h"
#include "clang-tidy/ClangTidyOptions.h"
#ifdef CLICE_HAS_CLANG_TIDY_MODULES
#define CLANG_TIDY_DISABLE_STATIC_ANALYZER_CHECKS
#include "clang-tidy/ClangTidyForceLinker.h"
#endif

namespace clice::tidy {

Expand Down
1 change: 1 addition & 0 deletions src/server/compiler/compiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -669,6 +669,7 @@ kota::task<> Compiler::run_compile(std::uint32_t pid, std::shared_ptr<Session::P
params.path = file_path;
params.version = sess->version;
params.text = sess->text;
params.clang_tidy = workspace.config.project.clang_tidy.value;
if(!fill_compile_args(file_path, params.directory, params.arguments, sess)) {
finish_compile();
co_return;
Expand Down
14 changes: 14 additions & 0 deletions src/server/protocol/agentic.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <string>
#include <vector>

#include "kota/ipc/lsp/protocol.h"
#include "kota/ipc/protocol.h"

namespace clice::agentic {
Expand Down Expand Up @@ -202,6 +203,13 @@ struct TypeHierarchyResult {
std::vector<TypeHierarchyEntry> subtypes;
};

struct LintParams {
std::string path;
std::optional<int> line;
};

using LintResult = std::vector<kota::ipc::protocol::Diagnostic>;

struct StatusParams {};

struct StatusResult {
Expand Down Expand Up @@ -283,6 +291,12 @@ struct RequestTraits<clice::agentic::TypeHierarchyParams> {
constexpr inline static std::string_view method = "agentic/typeHierarchy";
};

template <>
struct RequestTraits<clice::agentic::LintParams> {
using Result = clice::agentic::LintResult;
constexpr inline static std::string_view method = "agentic/lint";
};

template <>
struct RequestTraits<clice::agentic::StatusParams> {
using Result = clice::agentic::StatusResult;
Expand Down
1 change: 1 addition & 0 deletions src/server/protocol/worker.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ struct CompileParams {
std::string text;
std::string directory;
std::vector<std::string> arguments;
bool clang_tidy = false;
std::pair<std::string, uint32_t> pch;
std::unordered_map<std::string, std::string> pcms;
};
Expand Down
33 changes: 33 additions & 0 deletions src/server/service/agent_client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,14 @@
#include <string>
#include <vector>

#include "compile/compilation.h"
#include "feature/feature.h"
#include "server/protocol/agentic.h"
#include "server/service/master_server.h"
#include "support/filesystem.h"
#include "support/logging.h"

#include "kota/async/async.h"
#include "kota/ipc/lsp/uri.h"
#include "kota/meta/enum.h"
#include "llvm/ADT/DenseSet.h"
Expand Down Expand Up @@ -769,6 +772,36 @@ AgentClient::AgentClient(MasterServer& server, kota::ipc::JsonPeer& peer) :
co_return result;
});

peer.on_request([&srv](RequestContext&, const LintParams& params) -> RequestResult<LintParams> {
std::string directory;
std::vector<std::string> arguments;
if(!srv.compiler.fill_compile_args(params.path, directory, arguments)) {
co_return kota::outcome_error(
kota::ipc::Error{std::format("no compile command found for {}", params.path)});
}

auto result = co_await kota::queue([path = params.path,
directory = std::move(directory),
arguments = std::move(arguments)]() mutable {
CompilationParams cp;
cp.kind = CompilationKind::Content;
cp.clang_tidy = true;
cp.directory = std::move(directory);
for(auto& arg: arguments) {
cp.arguments.push_back(arg.c_str());
}

auto unit = compile(cp);
if(!unit.completed() && !unit.fatal_error()) {
LOG_WARN("Lint compilation failed: {}", path);
return LintResult{};
}

return feature::diagnostics(unit);
});
co_return result.value();
});

peer.on_request([&srv](RequestContext&, const StatusParams&) -> RequestResult<StatusParams> {
StatusResult result;
result.idle = srv.indexer.is_idle();
Expand Down
3 changes: 3 additions & 0 deletions src/server/service/agentic.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ static kota::task<> agentic_request(kota::ipc::JsonPeer& peer,
.line = line,
.direction = dir,
});
} else if(opts.method == "lint") {
auto line = opts.line > 0 ? std::optional(opts.line) : std::nullopt;
ok = co_await send_and_print(peer, agentic::LintParams{.path = opts.path, .line = line});
} else if(opts.method == "fileDeps") {
auto dir = opts.direction.empty() ? std::nullopt : std::optional(opts.direction);
ok = co_await send_and_print(peer,
Expand Down
1 change: 1 addition & 0 deletions src/server/worker/stateful_worker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ void StatefulWorker::register_handlers() {

CompilationParams cp;
cp.kind = CompilationKind::Content;
cp.clang_tidy = params.clang_tidy;
fill_args(cp, doc->directory, doc->arguments);
if(!doc->pch.first.empty()) {
cp.pch = doc->pch;
Expand Down
Loading
Loading