Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Guess compile commands in irony-server. #270

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "server/src/rapidjson"]
path = server/src/rapidjson
url = https://github.com/miloyip/rapidjson
70 changes: 0 additions & 70 deletions irony-cdb-libclang.el

This file was deleted.

87 changes: 87 additions & 0 deletions irony-cdb-server.el
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
;;; irony-cdb-server.el --- Compilation Database querying irony-server

;; Copyright (C) 2015 Karl Hylén

;; Author: Karl Hylén <[email protected]>
;; Keywords: c, convenience, tools

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <http://www.gnu.org/licenses/>.

;;; Code:

(require 'irony-cdb)
(require 'irony-cdb-json)

(require 'cl-lib)

;;;###autoload
(defun irony-cdb-server (command &rest args)
(cl-case command
(get-compile-options (irony-cdb-server--get-compile-options))))

(defun irony-cdb-server--has-database ()
"Return t if database is enabled."
(irony--send-request-sync "has-compile-db"))

(defun irony-cdb-server--get-compile-options ()
(irony--awhen (irony-cdb-json--locate-db)
(or (irony-cdb-server--server-exact-flags buffer-file-name it)
(irony-cdb-server--server-guess-flags buffer-file-name it))))

(defun irony-cdb-server--server-exact-flags (src-file db-file)
"Get compilation options from irony-server.

The parameter SRC-FILE is the source file we seek the compile command of and
DB-FILE is the database file."
(irony-cdb-server--adjust-options-and-remove-compiler
src-file
(irony--send-request-sync "get-compile-options"
db-file
src-file)))

(defun irony-cdb-server--server-guess-flags (src-file db-file)
"Make irony-server guess compilation arguments of a file.

The parameter SRC-FILE is the source file we seek the compile command of and
DB-FILE is the database file."
(let* ((guess
(irony--send-request-sync "guess-compile-options"
db-file
src-file))
(guessed-file (car guess))
(guessed-cmd (cdr guess)))
(irony-cdb-server--adjust-options-and-remove-compiler
guessed-file
(list guessed-cmd))))

(defun irony-cdb-server--adjust-options-and-remove-compiler (file cmds)
"Remove compiler, target file FILE and output file from CMDS.

The parameter CMDS is a list of conses. In each cons, the car holds the options
and the cdr holds the working directory where the compile command was issued."
(mapcar (lambda (cmd)
(let ((opt (irony-cdb--remove-compiler-from-flags (car cmd)))
(wdir (cdr cmd)))
(cons
(irony-cdb-json--adjust-compile-options opt file wdir)
wdir)))
cmds))

(provide 'irony-cdb-server)

;; Local Variables:
;; byte-compile-warnings: (not cl-functions)
;; End:

;;; irony-cdb-server ends here
4 changes: 2 additions & 2 deletions irony-cdb.el
Original file line number Diff line number Diff line change
@@ -36,7 +36,7 @@

(autoload 'irony-cdb-clang-complete "irony-cdb-clang-complete")
(autoload 'irony-cdb-json "irony-cdb-json")
(autoload 'irony-cdb-libclang "irony-cdb-libclang")
(autoload 'irony-cdb-server "irony-cdb-server")


;;
@@ -48,7 +48,7 @@
:group 'irony)

(defcustom irony-cdb-compilation-databases '(irony-cdb-clang-complete
irony-cdb-libclang
irony-cdb-server
irony-cdb-json)
"List of active compilation databases.

20 changes: 17 additions & 3 deletions irony.el
Original file line number Diff line number Diff line change
@@ -544,22 +544,26 @@ The installation requires CMake and the libclang developpement package."

When using a leading space, the buffer is hidden from the buffer
list (and undo information is not kept).")
(defvar irony--server-log nil
"The log file of irony-server.")

(defun irony--start-server-process ()
(when (setq irony--server-executable (or irony--server-executable
(irony--locate-server-executable)))
(let ((process-connection-type nil)
(process-adaptive-read-buffering nil)
process)
(setq irony--server-log (expand-file-name
(format-time-string
"irony.%Y-%m-%d_%Hh-%Mm-%Ss.log")
temporary-file-directory))
(setq process
(start-process-shell-command
"Irony" ;process name
irony--server-buffer ;buffer
(format "%s -i 2> %s" ;command
(shell-quote-argument irony--server-executable)
(expand-file-name
(format-time-string "irony.%Y-%m-%d_%Hh-%Mm-%Ss.log")
temporary-file-directory))))
irony--server-log)))
(buffer-disable-undo irony--server-buffer)
(set-process-query-on-exit-flag process nil)
(set-process-sentinel process 'irony--server-process-sentinel)
@@ -574,6 +578,16 @@ list (and undo information is not kept).")
(kill-process irony--server-process)
(setq irony--server-process nil)))

;;;###autoload
(defun irony-open-log-file ()
"Open irony server log file"
(interactive)
(if (and irony--server-log (file-exists-p irony--server-log))
(progn
(find-file irony--server-log)
(auto-revert-tail-mode))
(message "Log file doesn't exist yet!")))

(defun irony--get-server-process-create ()
(if (and irony--server-process
(process-live-p irony--server-process))
2 changes: 1 addition & 1 deletion server/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -27,7 +27,7 @@ elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
endif()

if(IRONY_COMPILER_IS_GCC_COMPATIBLE)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-exceptions")
# set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-exceptions")
elseif(MSVC)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /EHs-c- /D_HAS_EXCEPTIONS=0")
# irony-server uses some code that breaks when iterator debugging is enabled
19 changes: 18 additions & 1 deletion server/src/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -2,8 +2,11 @@ include(CheckLibClangBuiltinHeadersDir)

find_package(LibClang REQUIRED)

find_package(Boost 1.60.0 COMPONENTS system filesystem)

include_directories(${LIBCLANG_INCLUDE_DIRS})
include_directories(${CMAKE_CURRENT_SOURCE_DIR})
include_directories(rapidjson/include)

check_libclang_builtin_headers_dir()

@@ -15,7 +18,7 @@ endif()
# not to be taken as a module-definition file to link on Windows
set_source_files_properties(Commands.def PROPERTIES HEADER_FILE_ONLY TRUE)

add_executable(irony-server
set(IRONY_SOURCES
support/arraysize.h
support/CommandLineParser.cpp
support/CommandLineParser.h
@@ -35,6 +38,14 @@ add_executable(irony-server

main.cpp)

if (Boost_FOUND)
message("-- Found boost_filesystem, enabling server compilation database!")
add_definitions(-DHAS_BOOST_FILESYSTEM)
set(IRONY_SOURCES ${IRONY_SOURCES} CompilationDatabase.cpp)
endif()

add_executable(irony-server ${IRONY_SOURCES})

# retrieve the package version from irony.el
function(irony_find_package_version OUTPUT_VAR)
# this is a hack that force CMake to reconfigure, it is necessary to see if
@@ -67,4 +78,10 @@ set_source_files_properties(main.cpp

target_link_libraries(irony-server ${LIBCLANG_LIBRARIES})

if (Boost_FOUND)
target_link_libraries(irony-server ${Boost_LIBRARIES})
endif()

# add_subdirectory(json-test)

install(TARGETS irony-server DESTINATION bin)
6 changes: 4 additions & 2 deletions server/src/Command.cpp
Original file line number Diff line number Diff line change
@@ -99,7 +99,7 @@ std::ostream &operator<<(std::ostream &os, const Command::Action &action) {
std::ostream &operator<<(std::ostream &os, const Command &command) {
os << "Command{action=" << command.action << ", "
<< "file='" << command.file << "', "
<< "dir='" << command.dir << "', "
<< "db='" << command.db << "', "
<< "line=" << command.line << ", "
<< "column=" << command.column << ", "
<< "flags=[";
@@ -169,11 +169,13 @@ Command *CommandParser::parse(const std::vector<std::string> &argv) {
case Command::Diagnostics:
case Command::Help:
case Command::Exit:
case Command::HasCompilationDatabase:
// no-arguments commands
break;

case Command::GetCompileOptions:
positionalArgs.push_back(StringConverter(&command_.dir));
case Command::GuessCompileOptions:
positionalArgs.push_back(StringConverter(&command_.db));
positionalArgs.push_back(StringConverter(&command_.file));
break;

4 changes: 2 additions & 2 deletions server/src/Command.h
Original file line number Diff line number Diff line change
@@ -29,7 +29,7 @@ struct Command {
action = Unknown;
flags.clear();
file.clear();
dir.clear();
db.clear();
line = 0;
column = 0;
unsavedFiles.clear();
@@ -44,7 +44,7 @@ struct Command {

std::vector<std::string> flags;
std::string file;
std::string dir;
std::string db;
unsigned line;
unsigned column;
// pair of (filename, content)
3 changes: 3 additions & 0 deletions server/src/Commands.def
Original file line number Diff line number Diff line change
@@ -17,6 +17,9 @@ X(Diagnostics, "diagnostics", "print the diagnostics of the last parse")
X(Exit, "exit", "exit interactive mode, print nothing")
X(GetCompileOptions, "get-compile-options", "BUILD_DIR FILE - "
"get compile options for FILE from JSON database in PROJECT_ROOT")
X(GuessCompileOptions, "guess-compile-options", "BUILD_DIR FILE - "
"guess compile options for FILE from JSON database in PROJECT_ROOT")
X(HasCompilationDatabase, "has-compile-db", "Print t if database is enabled.")
X(Help, "help", "show this message")
X(Parse, "parse", "FILE - parse the given file")
X(SetDebug, "set-debug", "[on|off] - enable or disable verbose logging")
252 changes: 252 additions & 0 deletions server/src/CompilationDatabase.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
/** -*- C++ -*-
* \file
* \author Karl Hylén <karl.hylen@gmail.com>
*
* This file is distributed under the GNU General Public License. See
* COPYING for details.
*/

#include "CompilationDatabase.h"

#include <boost/filesystem.hpp>

#include "rapidjson/document.h"
#include "rapidjson/filereadstream.h"
#include "rapidjson/encodedstream.h"

// TODO: ifdef linux/mac
#include <sys/stat.h>
// endif

#include <algorithm>
#include <cstdio>
#include <fstream>
#include <iostream>

struct FileHandle {
FileHandle(const std::string &fileName, const char *mode)
: fp_(fopen(fileName.c_str(), mode)) {
}
~FileHandle() {
if (fp_)
fclose(fp_);
}

FILE *fp_;
};

std::vector<std::string>
CompileCommand::splitCommand(const std::string &Command) {
std::vector<std::string> cmdSplit;
std::istringstream iss(Command);

std::string cmdArg;
while (iss >> cmdArg)
cmdSplit.emplace_back(cmdArg);

return cmdSplit;
}

time_t CompilationDatabase::getModTime(const std::string &fileName) {
boost::system::error_code error;
time_t time = boost::filesystem::last_write_time(fileName, error);

// Print warning and return 0
if (error) {
std::clog << "Warning: Couldn't get modification time for : " << fileName
<< ".\n"
<< "Got error code " << error << "\n";
return 0u;
}

return time;
}

void CompilationDatabase::readOrUpdateDatabase(const std::string &fileName) {
if (databaseFile_ != fileName ||
difftime(getModTime(fileName), readTime_) >= 0.0) {
std::clog << "I: Reloading database.\n";
readDatabase(fileName);
}
}

void CompilationDatabase::readDatabase(const std::string &fileName) {
FileHandle file(fileName, "r");

if (!file.fp_) {
std::clog << "I: Couldn't open compilation database file!\n";
return;
}

char buffer[bufferSize];
rapidjson::FileReadStream stream{file.fp_, buffer, sizeof(buffer)};
// TODO: What if the database isn't encoded as UTF8?
rapidjson::EncodedInputStream<rapidjson::UTF8<>, rapidjson::FileReadStream>
encStream{stream};

rapidjson::Document doc;
doc.ParseStream(encStream);

if (doc.HasParseError()) {
std::clog << "I: Error parsing compilation database file!\n";
return;
}

if (!doc.IsArray()) {
std::clog << "I: Expected top level array when reading compilation "
"database!\n";
return;
}

// If we've gotten this far without failure, consider the database read. Set
// the name of the database file, record the time of reading and clear the
// compile commands.
databaseFile_ = fileName;
readTime_ = time(nullptr);
cmdMap_.clear();

for (unsigned i = 0, e = doc.Size(); i != e; ++i) {
rapidjson::Value &compileCmd = doc[i];

if (!compileCmd.IsObject() || !compileCmd.HasMember("file") ||
!compileCmd.HasMember("directory") ||
!compileCmd.HasMember("command")) {
std::clog << "I: Badly formatted compile command in database!\n";
continue;
}

std::string file{compileCmd["file"].GetString()};
CompileCommand cmd{std::string{compileCmd["directory"].GetString()},
std::string{compileCmd["command"].GetString()}};
cmdMap_.emplace(std::move(file), std::move(cmd));
}
}

void CompilationDatabase::printDatabase() const {
for (const auto &cmd_pair : cmdMap_) {
std::cout << "file: " << cmd_pair.first << "\n"
<< "directory: " << cmd_pair.second.dir_ << "\n";

std::cout << "command:";
for (const std::string &cmdArg : cmd_pair.second.cmd_)
std::cout << " " << cmdArg;
std::cout << "\n";
}
}

std::vector<const CompileCommand *>
CompilationDatabase::getCommands(const std::string &srcFile) const {
std::vector<const CompileCommand*> cmds;

auto range = cmdMap_.equal_range(srcFile);
cmds.reserve(std::distance(range.first, range.second));

std::transform(range.first, range.second, std::back_inserter(cmds),
[] (const FileMapType::value_type &v) { return &v.second; });

return cmds;
}

static unsigned calcDirScore(boost::filesystem::path &left,
boost::filesystem::path right) {
using path = boost::filesystem::path;

path leftDir{left.parent_path()};
path rightDir{right.parent_path()};

path::iterator leftItr = leftDir.end(), leftBeg = leftDir.begin();
path::iterator rightItr = rightDir.end(), rightBeg = rightDir.begin();

unsigned score = 0;
while (leftItr != leftBeg && rightItr != rightBeg) {
--leftItr;
--rightItr;

if (*leftItr == *rightItr)
++score;
else
break;
}

return score;
}

static unsigned levenshteinDistance(const std::string &left,
const std::string &right) {
std::vector<std::vector<unsigned>> matrix;

// Construct matrix
matrix.reserve(left.size() + 1);
for (unsigned i = 0; i < left.size() + 1; ++i)
matrix.emplace_back(right.size() + 1);

// Initialize first column and row
for (unsigned i = 0; i < left.size() + 1; ++i)
matrix[i][0] = i;
for (unsigned j = 0; j < right.size() + 1; ++j)
matrix[0][j] = j;

// Fill the matrix
for (unsigned i = 1; i < left.size() + 1; ++i) {
for (unsigned j = 1; j < right.size() + 1; ++j) {
matrix[i][j] = std::min(
{matrix[i - 1][j ] + 1,
matrix[i ][j - 1] + 1,
matrix[i - 1][j - 1] + (left[i - 1] == right[j - 1] ? 0 : 1)});
}
}

return matrix[left.size()][right.size()];
}

std::pair<const std::string *, const CompileCommand *>
CompilationDatabase::guessCommand(const std::string &srcFile) const {
using path = boost::filesystem::path;
path srcPath{srcFile};

if (cmdMap_.empty())
return std::make_pair(nullptr, nullptr);

// Step 1: Match exact without file extension
for (const auto &pair : cmdMap_) {
path dbFilePath{pair.first};

if (srcPath.stem() == dbFilePath.stem())
return std::make_pair(&pair.first, &pair.second);
}

// Step 2: Filter out entries that have as many direct parents in the path as
// possible in common with srcFile
using CmdPair = std::pair<const std::string *, const CompileCommand *>;
std::vector<CmdPair> filteredCmds;
filteredCmds.reserve(size());

unsigned maxScore = 0;
for (const auto &pair : cmdMap_) {
unsigned score = calcDirScore(srcPath, path{pair.first});

if (score > maxScore) {
filteredCmds.clear();
maxScore = score;
}

if (score == maxScore)
filteredCmds.emplace_back(&pair.first, &pair.second);
}

// Step 3: Get best matching filename using levenshtein distance
unsigned minScore = std::numeric_limits<unsigned>::max();
CmdPair minElem{nullptr, nullptr};
for (const CmdPair scoreEntry : filteredCmds) {
path entryPath{*scoreEntry.first};
unsigned score = levenshteinDistance(entryPath.stem().native(),
srcPath.stem().native());

if (score < minScore) {
minScore = score;
minElem = scoreEntry;
}
}

return minElem;
}
82 changes: 82 additions & 0 deletions server/src/CompilationDatabase.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/** -*- C++ -*-
* \file
* \author Karl Hylén <karl.hylen@gmail.com>
*
* \brief Classes for reading JSON compilation database.
*
* This file is distributed under the GNU General Public License. See
* COPYING for details.
*/

#ifndef COMPILATION_DATABASE_H
#define COMPILATION_DATABASE_H

#include <string>
#include <vector>
#include <sstream>
#include <unordered_map>

struct CompileCommand {
static std::vector<std::string> splitCommand(const std::string &command);

CompileCommand(const std::string &directory, const std::string &command)
: dir_(directory), cmd_(splitCommand(command)) {
}
CompileCommand(std::string &&directory, std::string &&command)
: dir_(directory), cmd_(splitCommand(command)) {
}

// Data members
std::string dir_;
std::vector<std::string> cmd_;
};

class CompilationDatabase {
public:
// TODO: Use something more optimal for storing file names?
using FileMapType = std::unordered_multimap<std::string, CompileCommand>;
using size_type = FileMapType::size_type;
using iterator = FileMapType::iterator;
using const_iterator = FileMapType::const_iterator;

CompilationDatabase() = default;

// Don't allow copy or move
CompilationDatabase(const CompilationDatabase&) = delete;
CompilationDatabase &operator=(const CompilationDatabase&) = delete;
CompilationDatabase(CompilationDatabase&&) = delete;
CompilationDatabase &operator=(CompilationDatabase&&) = delete;

~CompilationDatabase() = default;

iterator begin() { return cmdMap_.begin(); }
const_iterator begin() const { return cmdMap_.begin(); }
iterator end() { return cmdMap_.end(); }
const_iterator end() const { return cmdMap_.end(); }

size_type size() const { return cmdMap_.size(); }

// Get all compile commands of a file
std::vector<const CompileCommand *>
getCommands(const std::string &fileName) const;

// Guess the compilation command of a file. Return a pair of the name of the
// source file from which the compile command is taken and the command
// itself.
std::pair<const std::string *, const CompileCommand *>
guessCommand(const std::string &srcFile) const;

void printDatabase() const;
void readOrUpdateDatabase(const std::string &srcFile);

private:
void readDatabase(const std::string &fileName);
static time_t getModTime(const std::string &fileName);

static constexpr unsigned bufferSize = 65536;
std::string databaseFile_{};
time_t readTime_{0};
FileMapType cmdMap_;
};

#endif
99 changes: 51 additions & 48 deletions server/src/Irony.cpp
Original file line number Diff line number Diff line change
@@ -333,66 +333,69 @@ void Irony::complete(const std::string &file,

}

void Irony::getCompileOptions(const std::string &buildDir,
const std::string &file) const {
#if !(HAS_COMPILATION_DATABASE)
#ifdef HAS_BOOST_FILESYSTEM

(void)buildDir;
(void)file;
static void printCompileCommand(const CompileCommand &cmd) {
std::cout << "( (";

std::cout << "nil\n";
return;
for (const std::string &cmdArg : cmd.cmd_)
std::cout << support::quoted(cmdArg) << " ";

#else
CXCompilationDatabase_Error error;
CXCompilationDatabase db =
clang_CompilationDatabase_fromDirectory(buildDir.c_str(), &error);

switch (error) {
case CXCompilationDatabase_CanNotLoadDatabase:
std::clog << "I: could not load compilation database in '" << buildDir
<< "'\n";
std::cout << "nil\n";
return;
std::cout << ") . " << support::quoted(cmd.dir_) << ")\n";
}

case CXCompilationDatabase_NoError:
break;
}
void Irony::hasCompilationDatabase() {
std::cout << "t\n";
}

CXCompileCommands compileCommands =
clang_CompilationDatabase_getCompileCommands(db, file.c_str());
void Irony::getCompileOptions(const std::string &databaseFile,
const std::string &file) {
database.readOrUpdateDatabase(databaseFile);
std::vector<const CompileCommand*> cmds = database.getCommands(file);

std::cout << "(\n";
for (const CompileCommand *cmd : cmds)
printCompileCommand(*cmd);
std::cout << ")\n";
}

for (unsigned i = 0, numCompileCommands =
clang_CompileCommands_getSize(compileCommands);
i < numCompileCommands; ++i) {
CXCompileCommand compileCommand =
clang_CompileCommands_getCommand(compileCommands, i);

std::cout << "("
<< "(";
for (unsigned j = 0,
numArgs = clang_CompileCommand_getNumArgs(compileCommand);
j < numArgs; ++j) {
CXString arg = clang_CompileCommand_getArg(compileCommand, j);
std::cout << support::quoted(clang_getCString(arg)) << " ";
clang_disposeString(arg);
}

std::cout << ")"
<< " . ";
void Irony::guessCompileOptions(const std::string &databaseFile,
const std::string &srcFile) {
database.readOrUpdateDatabase(databaseFile);

CXString directory = clang_CompileCommand_getDirectory(compileCommand);
std::cout << support::quoted(clang_getCString(directory));
clang_disposeString(directory);
std::pair<const std::string *, const CompileCommand *> cmdPair =
database.guessCommand(srcFile);

std::cout << ")\n";
// TODO: Assert here when ready
if (!cmdPair.second) {
std::clog << "I: Couldn't guess compilation command for file " << srcFile
<< ".\n";
std::cout << "nil\n";
return;
}

if (debug_)
std::clog << "I: Using compilation command for " << *cmdPair.first << "\n";

std::cout << "( " << support::quoted(*cmdPair.first) << " . ";
printCompileCommand(*cmdPair.second);
std::cout << ")\n";
}

clang_CompileCommands_dispose(compileCommands);
clang_CompilationDatabase_dispose(db);
#endif
#else // HAS_BOOST_FILESYSTEM

void Irony::hasCompilationDatabase() {
std::cout << "nil\n";
}

void Irony::getCompileOptions(const std::string &databaseFile,
const std::string &file) {
std::cout << "nil\n";
}

void Irony::guessCompileOptions(const std::string &databaseFile,
const std::string &srcFile) {
std::cout << "nil\n";
}

#endif // HAS_BOOST_FILESYSTEM
33 changes: 28 additions & 5 deletions server/src/Irony.h
Original file line number Diff line number Diff line change
@@ -17,6 +17,7 @@
#define IRONY_MODE_SERVER_IRONY_H_

#include "TUManager.h"
#include "CompilationDatabase.h"

#include <string>
#include <vector>
@@ -78,29 +79,51 @@ class Irony {
const std::vector<std::string> &flags,
const std::vector<CXUnsavedFile> &unsavedFiles);

/// \brief Prints t if the compilation database is activated, nil otherwise.
static void hasCompilationDatabase();

/// \brief Get compile options from JSON database.
///
/// \param buildDir Directory containing compile_commands.json
/// \param file File to obtain compile commands for.
/// \param DatabaseFile File containing the compilation commands.
/// \param File File to obtain compile commands for.
///
/// Example output:
///
/// \code{.el}
/// (
/// (("-Wfoo" "-DBAR" "-Iqux") . "/path/to/working/directory")
/// (("-Wfoo-alt" "-DBAR_ALT" "-Iqux/alt") . "/alt/working/directory")
/// )
/// \endcode
///
void getCompileOptions(const std::string &buildDir,
const std::string &file) const;
void getCompileOptions(const std::string &DatabaseFile,
const std::string &File);

/// \}

/// \brief Guess compile options of file not present in the database, e.g. a
/// header.
///
/// \param DatabaseFile File containing the compilation commands.
/// \param File File to guess compile commands for.
///
/// The guessed command and the name of the source file are printed in Elisp
/// format to stdout.
///
/// Example output:
/// \code{.el}
/// ("/path/to/guessed/file.cpp" .
/// (("-Wfoo" "-DBAR" "-Iqux") . "/path/to/working/directory")))
/// )
/// \endcode
///
void guessCompileOptions(const std::string &DatabaseFile,
const std::string &File);

private:
TUManager tuManager_;
CXTranslationUnit activeTu_;
bool debug_;
CompilationDatabase database;
};

#endif // IRONY_MODE_SERVER_IRONY_H_
10 changes: 9 additions & 1 deletion server/src/main.cpp
Original file line number Diff line number Diff line change
@@ -216,7 +216,15 @@ int main(int ac, const char *av[]) {
break;

case Command::GetCompileOptions:
irony.getCompileOptions(c->dir, c->file);
irony.getCompileOptions(c->db, c->file);
break;

case Command::GuessCompileOptions:
irony.guessCompileOptions(c->db, c->file);
break;

case Command::HasCompilationDatabase:
irony.hasCompilationDatabase();
break;

case Command::Unknown:
1 change: 1 addition & 0 deletions server/src/rapidjson
Submodule rapidjson added at 3ede21
7 changes: 0 additions & 7 deletions server/src/support/CIndex.h
Original file line number Diff line number Diff line change
@@ -23,11 +23,4 @@
#define HAS_BRIEF_COMMENTS_IN_COMPLETION 0
#endif

#if CINDEX_VERSION >= 6
#define HAS_COMPILATION_DATABASE 1
#include <clang-c/CXCompilationDatabase.h>
#else
#define HAS_COMPILATION_DATABASE 0
#endif

#endif /* !IRONY_MODE_SERVER_SUPPORT_CINDEXVERSION_H_ */
2 changes: 2 additions & 0 deletions server/test/elisp/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -35,9 +35,11 @@ function(add_ert_test test_file)
${EMACS_EXECUTABLE} -batch
-l package
--eval "(package-initialize) (unless (require 'cl-lib nil t) (package-refresh-contents) (package-install 'cl-lib))"
--eval "(setq irony-elisp-test-dir \"${CMAKE_CURRENT_SOURCE_DIR}\")"
-l ${CMAKE_CURRENT_SOURCE_DIR}/${test_file}
-f ert-run-tests-batch-and-exit)
endfunction()

add_ert_test(irony.el)
add_ert_test(irony-cdb-json.el)
add_ert_test(irony-cdb-server.el)
7 changes: 7 additions & 0 deletions server/test/elisp/after.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[
{
"directory": "/home/user/directory",
"command": "g++ changed",
"file": "/home/user/directory/SrcFile.cpp"
}
]
7 changes: 7 additions & 0 deletions server/test/elisp/before.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[
{
"directory": "/home/user/directory",
"command": "g++ original",
"file": "/home/user/directory/SrcFile.cpp"
}
]
457 changes: 457 additions & 0 deletions server/test/elisp/compile_commands.json

Large diffs are not rendered by default.

87 changes: 87 additions & 0 deletions server/test/elisp/irony-cdb-server.el
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
;; -*-no-byte-compile: t; -*-
(load
(concat (file-name-directory (or load-file-name buffer-file-name))
"test-config"))

(require 'irony-cdb-server)

;; Set the directory
(unless (boundp 'irony-elisp-test-dir)
(setq irony-elisp-test-dir default-directory))
(message (concat "Source dir: " irony-elisp-test-dir))

(defconst llvm-db-file
(expand-file-name "compile_commands.json" irony-elisp-test-dir))
(defconst src-file-names (mapcar #'(lambda (entry) (cdr (assoc 'file entry)))
(json-read-file llvm-db-file)))

(defun irony-cdb-json--exact-flags-for-testing (file file-cdb)
(mapcar #'(lambda (e)
(cons (nth 1 e) (nth 2 e)))
(irony--assoc-all file file-cdb)))

(ert-deftest test-same-output-json-and-server ()
"Tests that irony-cdb-json and irony-cdb-server give the same result."
(unless (irony-cdb-server--has-database)
(return))
(let ((db (irony-cdb-json--load-db llvm-db-file)))
;; Database should be loaded
(should db)
(dolist (src-file src-file-names)
(let ((json-cmds
(irony-cdb-json--exact-flags-for-testing src-file db))
(server-cmds
(irony-cdb-server--server-exact-flags src-file llvm-db-file)))
;; If one file occurs several times, the commands might not be in
;; order. Therefore, do not use equal directly.
(should (equal (length json-cmds) (length server-cmds)))
(dolist (json-cmd json-cmds)
(should (member json-cmd server-cmds)))))))

(defconst before-db (expand-file-name "before.json" irony-elisp-test-dir))
(defconst after-db (expand-file-name "after.json" irony-elisp-test-dir))
(defconst reload-db (expand-file-name "reload.json" default-directory))

(ert-deftest test-database-reload ()
"Test that the database is reloaded if updated."
(unless (irony-cdb-server--has-database)
(return))
(let ((src-file "/home/user/directory/SrcFile.cpp"))
(copy-file before-db reload-db t)
(should (equal
(caar (irony-cdb-server--server-exact-flags src-file reload-db))
'( "original" )))
(copy-file after-db reload-db t)
(should (equal
(caar (irony-cdb-server--server-exact-flags src-file reload-db))
'( "changed" )))
(delete-file reload-db)
))

(ert-deftest test-database-noreload ()
"Test that the database isn't reloaded if not modified."
(unless (irony-cdb-server--has-database)
(return))
(let ((src-file "/home/user/directory/SrcFile.cpp"))
(irony-cdb-server--server-exact-flags src-file reload-db)
(sleep-for 1)
(irony-cdb-server--server-exact-flags src-file reload-db))
(irony-open-log-file)
(should (equal (count-matches "^I: Reloading database.$") 1)))

(defconst quote-db (expand-file-name "with_quote.json" irony-elisp-test-dir))

(ert-deftest test-database-quoted-arg ()
"Test that args with quotes are read correctly."
(unless (irony-cdb-server--has-database)
(return))
(let ((db-data (irony-cdb-server--server-exact-flags
"/home/user/dev/llvm/tools/llvm-config/llvm-config.cpp"
quote-db)))
;; Database should have been read
(message "Reading %s" quote-db)
(should db-data)
;; Check the first argument
(should (equal
(cl-caaar db-data)
"-DCMAKE_CFG_INTDIR=\\\".\\\""))))
7 changes: 7 additions & 0 deletions server/test/elisp/with_quote.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[
{
"directory": "/home/user/dev/llvm-build/tools/llvm-config",
"command": "/usr/bin/c++ -DCMAKE_CFG_INTDIR=\\\".\\\" -DGTEST_HAS_RTTI=0 -D_GNU_SOURCE -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS -fPIC -fvisibility-inlines-hidden -Wall -W -Wno-unused-parameter -Wwrite-strings -Wcast-qual -Wno-missing-field-initializers -pedantic -Wno-long-long -Wno-maybe-uninitialized -Wno-comment -std=c++11 -g -I/home/user/dev/llvm-build/tools/llvm-config -I/home/user/dev/llvm/tools/llvm-config -I/home/user/dev/llvm-build/include -I/home/user/dev/llvm/include -fno-exceptions -fno-rtti -o CMakeFiles/llvm-config.dir/llvm-config.cpp.o -c /home/user/dev/llvm/tools/llvm-config/llvm-config.cpp",
"file": "/home/user/dev/llvm/tools/llvm-config/llvm-config.cpp"
}
]