|
|
|
|
#include "cmake.hpp"
|
|
|
|
|
#include "compile_commands.hpp"
|
|
|
|
|
#include "config.hpp"
|
|
|
|
|
#include "dialogs.hpp"
|
|
|
|
|
#include "filesystem.hpp"
|
|
|
|
|
#include "terminal.hpp"
|
|
|
|
|
#include "utility.hpp"
|
|
|
|
|
#include <boost/algorithm/string.hpp>
|
|
|
|
|
#include <boost/optional.hpp>
|
|
|
|
|
#include <regex>
|
|
|
|
|
|
|
|
|
|
CMake::CMake(const boost::filesystem::path &path) {
|
|
|
|
|
const auto find_cmake_project = [](const boost::filesystem::path &file_path) {
|
|
|
|
|
std::ifstream input(file_path.string(), std::ofstream::binary);
|
|
|
|
|
if(input) {
|
|
|
|
|
std::string line;
|
|
|
|
|
while(std::getline(input, line)) {
|
|
|
|
|
const static std::regex project_regex("^ *project *\\(.*$", std::regex::icase | std::regex::optimize);
|
|
|
|
|
std::smatch sm;
|
|
|
|
|
if(std::regex_match(line, sm, project_regex))
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
boost::system::error_code ec;
|
|
|
|
|
auto search_path = boost::filesystem::is_directory(path, ec) ? path : path.parent_path();
|
|
|
|
|
while(true) {
|
|
|
|
|
auto search_cmake_path = search_path / "CMakeLists.txt";
|
|
|
|
|
if(boost::filesystem::exists(search_cmake_path, ec)) {
|
|
|
|
|
paths.emplace(paths.begin(), search_cmake_path);
|
|
|
|
|
if(find_cmake_project(search_cmake_path)) {
|
|
|
|
|
project_path = search_path;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if(search_path == search_path.root_directory())
|
|
|
|
|
break;
|
|
|
|
|
search_path = search_path.parent_path();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool CMake::update_default_build(const boost::filesystem::path &default_build_path, bool force) {
|
|
|
|
|
boost::system::error_code ec;
|
|
|
|
|
if(project_path.empty() || !boost::filesystem::exists(project_path / "CMakeLists.txt", ec) || default_build_path.empty())
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
if(!boost::filesystem::exists(default_build_path, ec)) {
|
|
|
|
|
boost::system::error_code ec;
|
|
|
|
|
boost::filesystem::create_directories(default_build_path, ec);
|
|
|
|
|
if(ec) {
|
|
|
|
|
Terminal::get().print("Error: could not create " + default_build_path.string() + ": " + ec.message() + "\n", true);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(!force && boost::filesystem::exists(default_build_path / "compile_commands.json", ec))
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
auto compile_commands_path = default_build_path / "compile_commands.json";
|
|
|
|
|
Dialog::Message message("Creating/updating default build");
|
|
|
|
|
auto exit_status = Terminal::get().process(Config::get().project.cmake.command + ' ' +
|
|
|
|
|
filesystem::escape_argument(project_path.string()) + " -DCMAKE_EXPORT_COMPILE_COMMANDS=ON", default_build_path);
|
|
|
|
|
message.hide();
|
|
|
|
|
if(exit_status == EXIT_SUCCESS) {
|
|
|
|
|
#ifdef _WIN32 //Temporary fix to MSYS2's libclang
|
|
|
|
|
auto compile_commands_file = filesystem::read(compile_commands_path);
|
|
|
|
|
auto replace_drive = [&compile_commands_file](const std::string ¶m) {
|
|
|
|
|
size_t pos = 0;
|
|
|
|
|
auto param_size = param.length();
|
|
|
|
|
while((pos = compile_commands_file.find(param + "/", pos)) != std::string::npos) {
|
|
|
|
|
if(pos + param_size + 1 < compile_commands_file.size())
|
|
|
|
|
compile_commands_file.replace(pos, param_size + 2, param + compile_commands_file[pos + param_size + 1] + ":");
|
|
|
|
|
else
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
replace_drive("-I");
|
|
|
|
|
replace_drive("-isystem ");
|
|
|
|
|
filesystem::write(compile_commands_path, compile_commands_file);
|
|
|
|
|
#endif
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool CMake::update_debug_build(const boost::filesystem::path &debug_build_path, bool force) {
|
|
|
|
|
boost::system::error_code ec;
|
|
|
|
|
if(project_path.empty() || !boost::filesystem::exists(project_path / "CMakeLists.txt", ec) || debug_build_path.empty())
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
if(!boost::filesystem::exists(debug_build_path, ec)) {
|
|
|
|
|
boost::system::error_code ec;
|
|
|
|
|
boost::filesystem::create_directories(debug_build_path, ec);
|
|
|
|
|
if(ec) {
|
|
|
|
|
Terminal::get().print("Error: could not create " + debug_build_path.string() + ": " + ec.message() + "\n", true);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(!force && boost::filesystem::exists(debug_build_path / "CMakeCache.txt", ec))
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
Dialog::Message message("Creating/updating debug build");
|
|
|
|
|
auto exit_status = Terminal::get().process(Config::get().project.cmake.command + ' ' +
|
|
|
|
|
filesystem::escape_argument(project_path.string()) + " -DCMAKE_BUILD_TYPE=Debug", debug_build_path);
|
|
|
|
|
message.hide();
|
|
|
|
|
if(exit_status == EXIT_SUCCESS)
|
|
|
|
|
return true;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
boost::filesystem::path CMake::get_executable(const boost::filesystem::path &build_path, const boost::filesystem::path &file_path) {
|
|
|
|
|
// CMake does not store in compile_commands.json if an object is part of an executable or not.
|
|
|
|
|
// Therefore, executables are first attempted found in the cmake files. These executables
|
|
|
|
|
// are then used to identify if a file in compile_commands.json is part of an executable or not
|
|
|
|
|
|
|
|
|
|
CompileCommands compile_commands(build_path);
|
|
|
|
|
std::vector<std::pair<boost::filesystem::path, boost::filesystem::path>> command_files_and_maybe_executables;
|
|
|
|
|
for(auto &command : compile_commands.commands) {
|
|
|
|
|
auto command_file = filesystem::get_normal_path(command.file);
|
|
|
|
|
auto values = command.parameter_values("-o");
|
|
|
|
|
if(!values.empty()) {
|
|
|
|
|
size_t pos;
|
|
|
|
|
if((pos = values[0].find("CMakeFiles/")) != std::string::npos)
|
|
|
|
|
values[0].erase(pos, 11);
|
|
|
|
|
if((pos = values[0].find(".dir")) != std::string::npos) {
|
|
|
|
|
auto executable = command.directory / values[0].substr(0, pos);
|
|
|
|
|
command_files_and_maybe_executables.emplace_back(command_file, executable);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::vector<boost::filesystem::path> cmake_executables;
|
|
|
|
|
|
|
|
|
|
// Parse cmake files
|
|
|
|
|
std::map<std::string, std::list<std::string>> variables;
|
|
|
|
|
for(auto &path : paths) {
|
|
|
|
|
parse_file(filesystem::read(path), variables, [this, &build_path, &cmake_executables, &path](Function function) {
|
|
|
|
|
if(function.name == "add_executable") {
|
|
|
|
|
if(!function.parameters.empty() && !function.parameters.front().empty()) {
|
|
|
|
|
auto executable = (path.parent_path() / function.parameters.front()).string();
|
|
|
|
|
auto project_path_str = project_path.string();
|
|
|
|
|
size_t pos = executable.find(project_path_str);
|
|
|
|
|
if(pos != std::string::npos)
|
|
|
|
|
executable.replace(pos, project_path_str.size(), build_path.string());
|
|
|
|
|
cmake_executables.emplace_back(executable);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ssize_t best_match_size = -1;
|
|
|
|
|
boost::filesystem::path best_match_executable;
|
|
|
|
|
|
|
|
|
|
for(auto &cmake_executable : cmake_executables) {
|
|
|
|
|
for(auto &command_file_and_maybe_executable : command_files_and_maybe_executables) {
|
|
|
|
|
auto &command_file = command_file_and_maybe_executable.first;
|
|
|
|
|
auto &maybe_executable = command_file_and_maybe_executable.second;
|
|
|
|
|
if(cmake_executable == maybe_executable) {
|
|
|
|
|
if(command_file == file_path)
|
|
|
|
|
return maybe_executable;
|
|
|
|
|
auto command_file_directory = command_file.parent_path();
|
|
|
|
|
if(filesystem::file_in_path(file_path, command_file_directory)) {
|
|
|
|
|
auto size = std::distance(command_file_directory.begin(), command_file_directory.end());
|
|
|
|
|
if(size > best_match_size) {
|
|
|
|
|
best_match_size = size;
|
|
|
|
|
best_match_executable = maybe_executable;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if(!best_match_executable.empty())
|
|
|
|
|
return best_match_executable;
|
|
|
|
|
|
|
|
|
|
for(auto &command_file_and_maybe_executable : command_files_and_maybe_executables) {
|
|
|
|
|
auto &command_file = command_file_and_maybe_executable.first;
|
|
|
|
|
auto &maybe_executable = command_file_and_maybe_executable.second;
|
|
|
|
|
if(command_file == file_path)
|
|
|
|
|
return maybe_executable;
|
|
|
|
|
auto command_file_directory = command_file.parent_path();
|
|
|
|
|
if(filesystem::file_in_path(file_path, command_file_directory)) {
|
|
|
|
|
auto size = std::distance(command_file_directory.begin(), command_file_directory.end());
|
|
|
|
|
if(size > best_match_size) {
|
|
|
|
|
best_match_size = size;
|
|
|
|
|
best_match_executable = maybe_executable;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return best_match_executable;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CMake::parse_file(const std::string &src, std::map<std::string, std::list<std::string>> &variables, std::function<void(Function &&)> &&on_function) {
|
|
|
|
|
size_t i = 0;
|
|
|
|
|
|
|
|
|
|
auto parse_comment = [&] {
|
|
|
|
|
if(src[i] == '#') {
|
|
|
|
|
while(i < src.size() && src[i] != '\n')
|
|
|
|
|
++i;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
auto is_whitespace = [&] {
|
|
|
|
|
return src[i] == ' ' || src[i] == '\t' || src[i] == '\r' || src[i] == '\n';
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
auto forward_passed_whitespace = [&] {
|
|
|
|
|
while(i < src.size() && is_whitespace())
|
|
|
|
|
++i;
|
|
|
|
|
return i < src.size();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
auto parse_variable_name = [&]() -> boost::optional<std::string> {
|
|
|
|
|
if(src[i] == '$' && i + 1 < src.size() && src[i + 1] == '{') {
|
|
|
|
|
auto start = i + 2;
|
|
|
|
|
auto end = src.find('}', start);
|
|
|
|
|
if(end != std::string::npos) {
|
|
|
|
|
i = end;
|
|
|
|
|
auto variable_name = src.substr(start, end - start);
|
|
|
|
|
boost::algorithm::to_upper(variable_name);
|
|
|
|
|
return variable_name;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return {};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
auto parse_function = [&]() -> boost::optional<Function> {
|
|
|
|
|
Function function;
|
|
|
|
|
if((src[i] >= 'A' && src[i] <= 'Z') || (src[i] >= 'a' && src[i] <= 'z') || src[i] == '_') {
|
|
|
|
|
function.name += src[i++];
|
|
|
|
|
while(i < src.size() && ((src[i] >= 'A' && src[i] <= 'Z') || (src[i] >= 'a' && src[i] <= 'z') || (src[i] >= '0' && src[i] <= '9') || src[i] == '_'))
|
|
|
|
|
function.name += src[i++];
|
|
|
|
|
if(forward_passed_whitespace() && src[i] == '(') {
|
|
|
|
|
++i;
|
|
|
|
|
// Parse parameters
|
|
|
|
|
for(; forward_passed_whitespace(); ++i) {
|
|
|
|
|
if(src[i] == ')')
|
|
|
|
|
return function;
|
|
|
|
|
else if(src[i] == '"') { // Parse parameter within ""
|
|
|
|
|
std::string parameter;
|
|
|
|
|
++i;
|
|
|
|
|
for(; i < src.size(); ++i) {
|
|
|
|
|
if(src[i] == '\\' && i + 1 < src.size())
|
|
|
|
|
parameter += src[++i];
|
|
|
|
|
else if(src[i] == '"')
|
|
|
|
|
break;
|
|
|
|
|
else if(auto variable_name = parse_variable_name()) {
|
|
|
|
|
auto it = variables.find(*variable_name);
|
|
|
|
|
if(it != variables.end()) {
|
|
|
|
|
bool first = true;
|
|
|
|
|
for(auto &value : it->second) {
|
|
|
|
|
parameter += (first ? "" : ";") + value;
|
|
|
|
|
first = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
parameter += src[i];
|
|
|
|
|
}
|
|
|
|
|
function.parameters.emplace_back(std::move(parameter));
|
|
|
|
|
}
|
|
|
|
|
else { // Parse parameter not within ""
|
|
|
|
|
auto parameter_it = function.parameters.end();
|
|
|
|
|
for(; i < src.size() && !is_whitespace() && src[i] != ')'; ++i) {
|
|
|
|
|
if(parameter_it == function.parameters.end())
|
|
|
|
|
parameter_it = function.parameters.emplace(parameter_it);
|
|
|
|
|
if(src[i] == '\\' && i + 1 < src.size())
|
|
|
|
|
*parameter_it += src[++i];
|
|
|
|
|
else if(auto variable_name = parse_variable_name()) {
|
|
|
|
|
auto variable_it = variables.find(*variable_name);
|
|
|
|
|
if(variable_it != variables.end()) {
|
|
|
|
|
if(variable_it->second.size() == 1)
|
|
|
|
|
*parameter_it += variable_it->second.front();
|
|
|
|
|
else if(variable_it->second.size() > 1) {
|
|
|
|
|
*parameter_it += variable_it->second.front();
|
|
|
|
|
function.parameters.insert(function.parameters.end(), std::next(variable_it->second.begin()), variable_it->second.end());
|
|
|
|
|
parameter_it = std::prev(function.parameters.end());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
*parameter_it += src[i];
|
|
|
|
|
}
|
|
|
|
|
if(src[i] == ')')
|
|
|
|
|
return function;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return {};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
for(; forward_passed_whitespace(); ++i) {
|
|
|
|
|
if(parse_comment())
|
|
|
|
|
continue;
|
|
|
|
|
if(auto function = parse_function()) {
|
|
|
|
|
boost::algorithm::to_lower(function->name);
|
|
|
|
|
if(function->name == "set" && !function->parameters.empty() && !function->parameters.front().empty()) {
|
|
|
|
|
auto variable_name = std::move(function->parameters.front());
|
|
|
|
|
boost::algorithm::to_upper(variable_name);
|
|
|
|
|
function->parameters.erase(function->parameters.begin());
|
|
|
|
|
variables.emplace(std::move(variable_name), std::move(function->parameters));
|
|
|
|
|
}
|
|
|
|
|
else if(function->name == "project") {
|
|
|
|
|
if(!function->parameters.empty()) {
|
|
|
|
|
variables.emplace("CMAKE_PROJECT_NAME", function->parameters);
|
|
|
|
|
variables.emplace("PROJECT_NAME", function->parameters);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
on_function(std::move(*function));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|