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

WIP: Compiler and linker detection for the C++ plugin. #12

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
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
2 changes: 1 addition & 1 deletion LICENCE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Despayre Licence

Copyright © 2013-2014, 2016 Michał "Griwes" Dominiak
Copyright © 2013-2014, 2016-2017 Michał "Griwes" Dominiak

This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
Expand Down
2 changes: 1 addition & 1 deletion buildfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ cxx.version = "c++1z"
cxx.flags = "-Wall -I./include/reaver" // and a temporary workaround for reverse caps
+ " -fPIC"
cxx.gcc.flags = ""
cxx.clang.flags = "-Wextra -Wpedantic -Weffc++ -Werror"
cxx.clang.flags = "-Wextra -Wpedantic -Weffc++ -Werror -Wno-unused-parameter"

cxx.ldflags = "-pthread" // and this is a temporary workaround
+ " -lboost_filesystem -lboost_system -lboost_iostreams -ldl"
Expand Down
2 changes: 1 addition & 1 deletion ci
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,5 @@ install_package mayfly develop
make test -j${core_number} -l${core_number}
./tests/test -j ${core_number} -l 3 -r teamcity

CC=cc CXX=c++ LD=c++ ./build-multistage || echo "##teamcity[message text='Bootstrap failed.' status='ERROR']"
CC=cc CXX=c++ LD=c++ ./build-multistage

2 changes: 1 addition & 1 deletion doc/licence-cpp-formatted
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* Despayre License
*
* Copyright © 2016 Michał "Griwes" Dominiak
* Copyright © 2017 Michał "Griwes" Dominiak
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
Expand Down
3 changes: 2 additions & 1 deletion include/reaver/despayre/runtime/compiler.h
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* Despayre License
*
* Copyright © 2016 Michał "Griwes" Dominiak
* Copyright © 2016-2017 Michał "Griwes" Dominiak
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
Expand Down Expand Up @@ -41,6 +41,7 @@ namespace reaver

virtual std::vector<boost::filesystem::path> inputs(context_ptr, const boost::filesystem::path &) const = 0;
virtual std::vector<boost::filesystem::path> outputs(context_ptr, const boost::filesystem::path &) const = 0;
virtual bool needs_rebuild(context_ptr, const boost::filesystem::path &) const = 0;

virtual void build(context_ptr, const boost::filesystem::path &) const = 0;
virtual const std::vector<linker_capability> & linker_caps(context_ptr, const boost::filesystem::path &) const = 0;
Expand Down
7 changes: 6 additions & 1 deletion include/reaver/despayre/runtime/files.h
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* Despayre License
*
* Copyright © 2016 Michał "Griwes" Dominiak
* Copyright © 2016-2017 Michał "Griwes" Dominiak
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
Expand Down Expand Up @@ -89,6 +89,11 @@ namespace reaver
return ctx->compilers.get_compiler(_path)->outputs(ctx, _path);
}

virtual bool needs_rebuild(context_ptr ctx) override
{
return ctx->compilers.get_compiler(_path)->needs_rebuild(ctx, _path);
}

virtual const std::vector<std::shared_ptr<target>> & dependencies(context_ptr ctx) override
{
if (!_deps || ctx != _cached_context)
Expand Down
9 changes: 7 additions & 2 deletions include/reaver/despayre/semantics/target.h
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* Despayre License
*
* Copyright © 2016 Michał "Griwes" Dominiak
* Copyright © 2016-2017 Michał "Griwes" Dominiak
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
Expand Down Expand Up @@ -85,7 +85,7 @@ namespace reaver
return boost::filesystem::last_write_time(path);
});

return *std::max_element(input_times.begin(), input_times.end()) <= *std::min_element(output_times.begin(), output_times.end());
return *std::max_element(input_times.begin(), input_times.end()) <= *std::min_element(output_times.begin(), output_times.end()) && !needs_rebuild(ctx);
}

future<> build(context_ptr ctx)
Expand Down Expand Up @@ -136,6 +136,11 @@ namespace reaver
return {};
}

virtual bool needs_rebuild(context_ptr)
{
return false;
}

protected:
virtual void _build(context_ptr) = 0;
};
Expand Down
224 changes: 205 additions & 19 deletions plugins/c++/compiler.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* Despayre License
*
* Copyright © 2016 Michał "Griwes" Dominiak
* Copyright © 2016-2017 Michał "Griwes" Dominiak
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
Expand All @@ -21,6 +21,8 @@
**/

#include <fstream>
#include <cstdlib>
#include <regex>

#include <reaver/filesystem.h>

Expand Down Expand Up @@ -50,16 +52,193 @@ namespace
output += ".deps";
return output;
}

const char * compiler_detector =
#include "compiler_detector.h"
;
}

void reaver::despayre::cxx::_v1::cxx_compiler::_detect_compiler()
{
const char * env_cxx = std::getenv("CXX");
if (!env_cxx)
{
env_cxx = "c++";
}

_compiler_path = env_cxx;

if (!_compiler_path.is_absolute())
{
assert(_compiler_path == _compiler_path.filename());

auto path_cstr = std::getenv("PATH");
assert(path_cstr);

auto env_path = std::string{ path_cstr };

std::vector<std::string> paths;
paths.reserve(std::count(env_path.begin(), env_path.end(), ':'));
boost::algorithm::split(paths, env_path, boost::is_any_of(":"));

for (auto && path : paths)
{
if (boost::filesystem::exists(path / _compiler_path))
{
_compiler_path = path / _compiler_path;
break;
}
}
}

assert(boost::filesystem::exists(_compiler_path) && "error out nicely about not being able to find the specified compiler");

logger::dlog() << " -- Found a C++ compiler: " << _compiler_path.string();

std::vector<std::string> args = { "/bin/sh", "-c", "exec " + _compiler_path.string() + " -x c++ -E - <<'EOF'\n" + compiler_detector + "\nEOF" };

using namespace boost::process::initializers;
boost::process::pipe p = boost::process::create_pipe();

int exit_code = 0;

{
boost::iostreams::file_descriptor_sink sink{ p.sink, boost::iostreams::close_handle };
auto child = boost::process::execute(set_args(args), inherit_env(), bind_stdout(sink), close_stdin());
auto exit_status = wait_for_exit(child);
exit_code = WEXITSTATUS(exit_status);
}

if (!exit_code)
{
boost::iostreams::file_descriptor_source source{ p.source, boost::iostreams::close_handle };
boost::iostreams::stream<boost::iostreams::file_descriptor_source> is(source);

std::string buffer(std::istreambuf_iterator<char>(is.rdbuf()), std::istreambuf_iterator<char>());

_parse_detection_output(std::move(buffer));
}

static std::unordered_map<vendor, const char *> compiler_names = {
{ vendor::gcc, "G++" },
{ vendor::clang, "Clang" },
{ vendor::unknown, "unknown" }
};

logger::dlog() << " -- C++ compiler identification: " << compiler_names.at(_vendor) << " " << _version;
}

void reaver::despayre::cxx::_v1::cxx_compiler::_parse_detection_output(std::string output)
{
std::stringstream stream{ std::move(output) };
std::string buffer;

std::regex message_pattern{ R"(^despayre: ([^:]+): (.*)$)" };
std::smatch match;

std::unordered_map<std::string, std::string> messages;

while (std::getline(stream, buffer))
{
if (std::regex_match(buffer, match, message_pattern))
{
if (match[1] == "error")
{
throw std::runtime_error{ match[2] };
}

auto it = messages.find(match[1]);
if (it != messages.end())
{
assert(!"a duplicate message from the detector!");
}

messages.emplace(match[1], match[2]);
}
}

static std::unordered_map<std::string, vendor> compiler_ids = {
{ "G++", vendor::gcc },
{ "Clang++", vendor::clang },
{ "unknown", vendor::unknown }
};

_vendor = compiler_ids.at(messages.at("compiler-id"));

if (_vendor == vendor::unknown)
{
return;
}

_version = messages.at("compiler-version");
_default_cxx_version = messages.at("c++");
_is_strict_by_default = messages.at("compiler-strict") == "true";
}

std::vector<std::string> reaver::despayre::cxx::_v1::cxx_compiler::_build_command(context_ptr ctx, const boost::filesystem::path & path) const
{
auto out = filesystem::make_relative(output_path(ctx, path));

// need a better way to do this
auto flags = [&]{
try
{
return _arguments->get_property(U"flags")->as<string>()->value();
}
catch (...)
{
return std::u32string{};
}
}();

auto compiler_specific_flags = [&]() -> std::u32string {
if (_vendor == vendor::unknown)
{
return U"";
}

std::unordered_map<vendor, const char32_t *> nss = {
{ vendor::gcc, U"gcc" },
{ vendor::clang, U"clang" }
};

try
{
return _arguments->get_property(nss.at(_vendor))->get_property(U"flags")->as<string>()->value();
}
catch (...)
{
return std::u32string{};
}
}();

auto deps_flags = " -MD -MF " + dependencies_path(ctx, path).string() + " ";

auto cxxflags = []() -> std::string {
auto cxxflags_env = std::getenv("CXXFLAGS");
if (!cxxflags_env)
{
return {};
}
return cxxflags_env;
}();

std::vector<std::string> args = { "/bin/sh", "-c",
"exec " + _compiler_path.string() + " -c " + " -std=c++1z -o '"
+ out.string() + "' '" + path.string() + "' "
+ utf8(flags) + " " + utf8(compiler_specific_flags) + deps_flags + cxxflags };

return args;
}

std::vector<boost::filesystem::path> reaver::despayre::cxx::_v1::cxx_compiler::inputs(context_ptr ctx, const boost::filesystem::path & path) const
{
auto inputs = filesystem::all_symlinked_paths(_compiler_path);

auto deps_path = dependencies_path(ctx, path);

if (boost::filesystem::exists(deps_path))
{
std::vector<boost::filesystem::path> inputs;

std::fstream file{ deps_path.string(), std::ios::in };
std::string buffer{ std::istreambuf_iterator<char>{ file.rdbuf() }, {} };
auto start = buffer.begin();
Expand Down Expand Up @@ -114,7 +293,8 @@ std::vector<boost::filesystem::path> reaver::despayre::cxx::_v1::cxx_compiler::i
return inputs;
}

return { path };
inputs.push_back(path);
return inputs;
}

std::vector<boost::filesystem::path> reaver::despayre::cxx::_v1::cxx_compiler::outputs(context_ptr ctx, const boost::filesystem::path & path) const
Expand All @@ -123,6 +303,20 @@ std::vector<boost::filesystem::path> reaver::despayre::cxx::_v1::cxx_compiler::o
return { output_path(ctx, path) };
}

bool reaver::despayre::cxx::_v1::cxx_compiler::needs_rebuild(context_ptr ctx, const boost::filesystem::path & path) const
{
if (!boost::filesystem::exists(output_path(ctx, path).string() + ".command"))
{
return true;
}

auto args = _build_command(ctx, path);
std::ifstream command_file{ output_path(ctx, path).string() + ".command" };
std::string last_command{ std::istreambuf_iterator<char>{ command_file.rdbuf() }, std::istreambuf_iterator<char>{} };

return args.back() != last_command;
}

void reaver::despayre::cxx::_v1::cxx_compiler::build(context_ptr ctx, const boost::filesystem::path & path) const
{
auto out = filesystem::make_relative(output_path(ctx, path));
Expand All @@ -131,21 +325,12 @@ void reaver::despayre::cxx::_v1::cxx_compiler::build(context_ptr ctx, const boos

boost::filesystem::create_directories(out.parent_path());

// need a better way to do this
auto flags = [&]{
try
{
return _arguments->get_property(U"flags")->as<string>()->value();
}
catch (...)
{
return std::u32string{};
}
}();

auto deps_flags = " -MD -MF " + dependencies_path(ctx, path).string() + " ";
auto args = _build_command(ctx, path);

std::vector<std::string> args = { "/bin/sh", "-c", "exec ${CXX} -c ${CXXFLAGS} -std=c++1z -o '" + out.string() + "' '" + path.string() + "' " + utf8(flags) + deps_flags };
{
std::ofstream command_file{ output_path(ctx, path).string() + ".command" };
command_file << args.back();
}

using namespace boost::process::initializers;
boost::process::pipe p = boost::process::create_pipe();
Expand All @@ -168,9 +353,10 @@ void reaver::despayre::cxx::_v1::cxx_compiler::build(context_ptr ctx, const boos
logger::dlog() << buffer;
}

if (!exit_code)
if (exit_code)
{
// TODO :P
throw 1;
}
}

Loading