} | } | ||||
void link_executable_plan::link(build_env_ref env, const library_plan& lib) const { | void link_executable_plan::link(build_env_ref env, const library_plan& lib) const { | ||||
const auto out_path = calc_executable_path(env); | |||||
// Build up the link command | |||||
link_exe_spec spec; | link_exe_spec spec; | ||||
spec.output = out_path; | |||||
spec.output = calc_executable_path(env); | |||||
spec.inputs = _input_libs; | spec.inputs = _input_libs; | ||||
if (lib.create_archive()) { | if (lib.create_archive()) { | ||||
// The associated library has compiled components. Add the static library a as a linker | |||||
// input | |||||
spec.inputs.push_back(lib.create_archive()->calc_archive_file_path(env)); | spec.inputs.push_back(lib.create_archive()->calc_archive_file_path(env)); | ||||
} | } | ||||
// The main object should be a linker input, of course. | |||||
auto main_obj = _main_compile.calc_object_file_path(env); | auto main_obj = _main_compile.calc_object_file_path(env); | ||||
spec.inputs.push_back(std::move(main_obj)); | spec.inputs.push_back(std::move(main_obj)); | ||||
// Linker inputs are order-dependent in some cases. The top-most input should appear first, and | |||||
// its dependencies should appear later. Because of the way inputs were generated, they appear | |||||
// sorted with the dependencies coming earlier than the dependees. We can simply reverse the | |||||
// order and linking will work. | |||||
std::reverse(spec.inputs.begin(), spec.inputs.end()); | std::reverse(spec.inputs.begin(), spec.inputs.end()); | ||||
// Do it! | |||||
const auto link_command = env.toolchain.create_link_executable_command(spec); | const auto link_command = env.toolchain.create_link_executable_command(spec); | ||||
fs::create_directories(out_path.parent_path()); | |||||
fs::create_directories(spec.output.parent_path()); | |||||
auto msg = fmt::format("[{}] Link: {:30}", | auto msg = fmt::format("[{}] Link: {:30}", | ||||
lib.name(), | lib.name(), | ||||
fs::relative(spec.output, env.output_root).string()); | fs::relative(spec.output, env.output_root).string()); | ||||
auto [dur_ms, proc_res] | auto [dur_ms, proc_res] | ||||
= timed<std::chrono::milliseconds>([&] { return run_proc(link_command); }); | = timed<std::chrono::milliseconds>([&] { return run_proc(link_command); }); | ||||
spdlog::info("{} - {:>6n}ms", msg, dur_ms.count()); | spdlog::info("{} - {:>6n}ms", msg, dur_ms.count()); | ||||
// Check and throw if errant | |||||
if (!proc_res.okay()) { | if (!proc_res.okay()) { | ||||
throw compile_failure( | throw compile_failure( | ||||
fmt::format("Failed to link test executable '{}'. Link command [{}] returned {}:\n{}", | fmt::format("Failed to link test executable '{}'. Link command [{}] returned {}:\n{}", |
class library_plan; | class library_plan; | ||||
/** | |||||
* Represents information about a test failure. | |||||
*/ | |||||
struct test_failure { | struct test_failure { | ||||
fs::path executable_path; | fs::path executable_path; | ||||
std::string output; | std::string output; | ||||
int retc; | int retc; | ||||
}; | }; | ||||
/** | |||||
* Stores information about an executable that should be linked. An executable in DDS consists of a | |||||
* single source file defines the entry point and some set of linker inputs. | |||||
*/ | |||||
class link_executable_plan { | class link_executable_plan { | ||||
/// The linker inputs that should be linked into the executable | |||||
std::vector<fs::path> _input_libs; | std::vector<fs::path> _input_libs; | ||||
compile_file_plan _main_compile; | |||||
fs::path _out_subdir; | |||||
std::string _name; | |||||
/// The compilation plan for the entry-point source file | |||||
compile_file_plan _main_compile; | |||||
/// The subdirectory in which the executable should be generated | |||||
fs::path _out_subdir; | |||||
/// The name of the executable | |||||
std::string _name; | |||||
public: | public: | ||||
/** | |||||
* Create a new instance | |||||
* @param in_libs Linker inputs for the executable | |||||
* @param cfp The file compilation that defines the entrypoint of the application | |||||
* @param out_subdir The subdirectory of the build root in which the executable should be placed | |||||
* @param name_ The name of the executable | |||||
*/ | |||||
link_executable_plan(std::vector<fs::path> in_libs, | link_executable_plan(std::vector<fs::path> in_libs, | ||||
compile_file_plan cfp, | compile_file_plan cfp, | ||||
path_ref out_subdir, | path_ref out_subdir, | ||||
, _out_subdir(out_subdir) | , _out_subdir(out_subdir) | ||||
, _name(std::move(name_)) {} | , _name(std::move(name_)) {} | ||||
/** | |||||
* Get the compilation of the main source file | |||||
*/ | |||||
auto& main_compile_file() const noexcept { return _main_compile; } | auto& main_compile_file() const noexcept { return _main_compile; } | ||||
/** | |||||
* Calculate the output path of the executable for the given build environment | |||||
*/ | |||||
fs::path calc_executable_path(const build_env& env) const noexcept; | fs::path calc_executable_path(const build_env& env) const noexcept; | ||||
void link(const build_env&, const library_plan&) const; | |||||
/** | |||||
* Perform the link of the executable | |||||
* @param env The build environment to use. | |||||
* @param lib The library that owns this executable. If it defines an archive library, it will | |||||
* be added as a linker input. | |||||
*/ | |||||
void link(const build_env& env, const library_plan& lib) const; | |||||
/** | |||||
* Run the executable as a test. If the test fails, then that failure information will be | |||||
* returned. | |||||
*/ | |||||
std::optional<test_failure> run_test(build_env_ref) const; | std::optional<test_failure> run_test(build_env_ref) const; | ||||
bool is_test() const noexcept; | bool is_test() const noexcept; |