// -*- c++ -*- // Distributed under the BSD 2-Clause License. // See accompanying file LICENSE for details. #include "tools.h" #include #include #include #include #include #include "util.h" std::ostream& operator<<(std::ostream& stream, const ctor::c_opt& opt) { // Adding to this enum should also imply adding to the unit-tests below switch(opt) { case ctor::c_opt::output: stream << "ctor::c_opt::output"; break; case ctor::c_opt::debug: stream << "ctor::c_opt::debug"; break; case ctor::c_opt::warn_all: stream << "ctor::c_opt::warn_all"; break; case ctor::c_opt::warnings_as_errors: stream << "ctor::c_opt::warnings_as_errors"; break; case ctor::c_opt::generate_dep_tree: stream << "ctor::c_opt::generate_dep_tree"; break; case ctor::c_opt::no_link: stream << "ctor::c_opt::no_link"; break; case ctor::c_opt::include_path: stream << "ctor::c_opt::include_path"; break; case ctor::c_opt::c_std: stream << "ctor::c_opt::c_std"; break; case ctor::c_opt::optimization: stream << "ctor::c_opt::optimization"; break; case ctor::c_opt::position_independent_code: stream << "ctor::c_opt::position_independent_code"; break; case ctor::c_opt::position_independent_executable: stream << "ctor::c_opt::position_independent_executable"; break; case ctor::c_opt::custom: stream << "ctor::c_opt::custom"; break; } return stream; } std::ostream& operator<<(std::ostream& stream, const ctor::cxx_opt& opt) { // Adding to this enum should also imply adding to the unit-tests below switch(opt) { case ctor::cxx_opt::output: stream << "ctor::cxx_opt::output"; break; case ctor::cxx_opt::debug: stream << "ctor::cxx_opt::debug"; break; case ctor::cxx_opt::warn_all: stream << "ctor::cxx_opt::warn_all"; break; case ctor::cxx_opt::warnings_as_errors: stream << "ctor::cxx_opt::warnings_as_errors"; break; case ctor::cxx_opt::generate_dep_tree: stream << "ctor::cxx_opt::generate_dep_tree"; break; case ctor::cxx_opt::no_link: stream << "ctor::cxx_opt::no_link"; break; case ctor::cxx_opt::include_path: stream << "ctor::cxx_opt::include_path"; break; case ctor::cxx_opt::cpp_std: stream << "ctor::cxx_opt::cpp_std"; break; case ctor::cxx_opt::optimization: stream << "ctor::cxx_opt::optimization"; break; case ctor::cxx_opt::position_independent_code: stream << "ctor::cxx_opt::position_independent_code"; break; case ctor::cxx_opt::position_independent_executable: stream << "ctor::cxx_opt::position_independent_executable"; break; case ctor::cxx_opt::custom: stream << "ctor::cxx_opt::custom"; break; } return stream; } std::ostream& operator<<(std::ostream& stream, const ctor::ld_opt& opt) { // Adding to this enum should also imply adding to the unit-tests below switch(opt) { case ctor::ld_opt::output: stream << "ctor::ld_opt::output"; break; case ctor::ld_opt::strip: stream << "ctor::ld_opt::strip"; break; case ctor::ld_opt::warn_all: stream << "ctor::ld_opt::warn_all"; break; case ctor::ld_opt::warnings_as_errors: stream << "ctor::ld_opt::warnings_as_errors"; break; case ctor::ld_opt::library_path: stream << "ctor::ld_opt::library_path"; break; case ctor::ld_opt::link: stream << "ctor::ld_opt::link"; break; case ctor::ld_opt::cpp_std: stream << "ctor::ld_opt::cpp_std"; break; case ctor::ld_opt::build_shared: stream << "ctor::ld_opt::build_shared"; break; case ctor::ld_opt::threads: stream << "ctor::ld_opt::threads"; break; case ctor::ld_opt::position_independent_code: stream << "ctor::ld_opt::position_independent_code"; break; case ctor::ld_opt::position_independent_executable: stream << "ctor::ld_opt::position_independent_executable"; break; case ctor::ld_opt::custom: stream << "ctor::ld_opt::custom"; break; } return stream; } std::ostream& operator<<(std::ostream& stream, const ctor::ar_opt& opt) { // Adding to this enum should also imply adding to the unit-tests below switch(opt) { case ctor::ar_opt::replace: stream << "ctor::ar_opt::replace"; break; case ctor::ar_opt::add_index: stream << "ctor::ar_opt::add_index"; break; case ctor::ar_opt::create: stream << "ctor::ar_opt::create"; break; case ctor::ar_opt::output: stream << "ctor::ar_opt::output"; break; case ctor::ar_opt::custom: stream << "ctor::ar_opt::custom"; break; } return stream; } std::ostream& operator<<(std::ostream& stream, const ctor::asm_opt& opt) { // Adding to this enum should also imply adding to the unit-tests below switch(opt) { case ctor::asm_opt::custom: stream << "ctor::asm_opt::custom"; break; } return stream; } ctor::toolchain getToolChain(const std::string& compiler) { std::filesystem::path cc(compiler); auto cc_cmd = cc.stem().string(); // Note: "g++" is a substring of "clang++" so "clang++" must be tested first. if(cc_cmd.find("clang++") != std::string::npos || cc_cmd.find("clang") != std::string::npos) { return ctor::toolchain::clang; } else if(cc_cmd.find("g++") != std::string::npos || cc_cmd.find("gcc") != std::string::npos || cc_cmd.find("mingw") != std::string::npos) { return ctor::toolchain::gcc; } std::cerr << "Unsupported output system.\n"; return ctor::toolchain::gcc; } ctor::toolchain getToolChain(ctor::output_system system) { const auto& cfg = ctor::get_configuration(); if(system == ctor::output_system::host) { if(cfg.host_toolchain != ctor::toolchain::none) { return cfg.host_toolchain; } return getToolChain(cfg.get(ctor::cfg::host_cxx, "g++")); } else { if(cfg.build_toolchain != ctor::toolchain::none) { return cfg.build_toolchain; } return getToolChain(cfg.get(ctor::cfg::build_cxx, "g++")); } } namespace gcc { std::string get_arch(ctor::output_system system) { std::string cmd; const auto& c = ctor::get_configuration(); switch(system) { case ctor::output_system::host: cmd = c.get(ctor::cfg::host_cxx, "/usr/bin/g++"); break; case ctor::output_system::build: cmd = c.get(ctor::cfg::build_cxx, "/usr/bin/g++"); break; } cmd += " -v"; cmd += " 2>&1"; auto pipe = popen(cmd.data(), "r"); if(!pipe) { std::cerr << "Could not run compiler " << cmd << "\n"; return {};//ctor::arch::unknown; } std::string arch; while(!feof(pipe)) { constexpr auto buffer_size{1024}; std::array buf{}; if(fgets(buf.data(), buf.size(), pipe) != nullptr) { arch = buf.data(); if(arch.starts_with("Target:")) { break; } } } pclose(pipe); // Remove trailing newline if(arch.ends_with("\n")) { arch = arch.substr(0, arch.length() - 1); } // Remove 'Target: ' prefix constexpr auto prefix_length{8}; arch = arch.substr(prefix_length); return arch; } ctor::arch get_arch(const std::string& str) { // gcc -v 2>&1 | grep Target // Target: x86_64-apple-darwin19.6.0 // Target: x86_64-pc-linux-gnu // Target: arm-linux-gnueabihf // Target: x86_64-linux-gnu // Target: x86_64-unknown-freebsd13.1 // # x86_64-w64-mingw32-c++-win32 -v 2>&1 | grep Target // Target: x86_64-w64-mingw32 // # i686-w64-mingw32-c++-win32 -v 2>&1 | grep Target // Target: i686-w64-mingw32 if(str.find("apple") != std::string::npos || str.find("darwin") != std::string::npos) { return ctor::arch::apple; } if(str.find("linux") != std::string::npos || str.find("bsd") != std::string::npos) { return ctor::arch::unix; } if(str.find("mingw") != std::string::npos) { return ctor::arch::windows; } std::cerr << "Could not deduce gcc arch from '" << str << "'" << std::endl; return ctor::arch::unknown; } ctor::c_flag c_option(const std::string& flag) { if(flag.starts_with("-I")) { std::string path = flag.substr(2); path.erase(0, path.find_first_not_of(' ')); return { ctor::c_opt::include_path, path }; } return { ctor::c_opt::custom, flag }; } ctor::cxx_flag cxx_option(const std::string& flag) { if(flag.starts_with("-I")) { std::string path = flag.substr(2); path.erase(0, path.find_first_not_of(' ')); return { ctor::cxx_opt::include_path, path }; } return { ctor::cxx_opt::custom, flag }; } ctor::ld_flag ld_option(const std::string& flag) { if(flag.starts_with("-L")) { std::string path = flag.substr(2); path.erase(0, path.find_first_not_of(' ')); return { ctor::ld_opt::library_path, path }; } return { ctor::ld_opt::custom, flag }; } ctor::ar_flag ar_option(const std::string& flag) { return { ctor::ar_opt::custom, flag }; } std::vector cxx_option(ctor::cxx_opt opt, const std::string& arg) { switch(opt) { case ctor::cxx_opt::output: return {"-o", arg}; case ctor::cxx_opt::debug: return {"-g"}; case ctor::cxx_opt::warn_all: return {"-Wall"}; case ctor::cxx_opt::warnings_as_errors: return {"-Werror"}; case ctor::cxx_opt::generate_dep_tree: return {"-MMD"}; case ctor::cxx_opt::no_link: return {"-c"}; case ctor::cxx_opt::include_path: return {"-I" + arg}; case ctor::cxx_opt::cpp_std: return {"-std=" + arg}; case ctor::cxx_opt::optimization: return {"-O" + arg}; case ctor::cxx_opt::position_independent_code: return {"-fPIC"}; case ctor::cxx_opt::position_independent_executable: return {"-fPIE"}; case ctor::cxx_opt::custom: return {arg}; } std::cerr << "Unsupported compiler option.\n"; return {}; } std::vector c_option(ctor::c_opt opt, const std::string& arg) { switch(opt) { case ctor::c_opt::output: return {"-o", arg}; case ctor::c_opt::debug: return {"-g"}; case ctor::c_opt::warn_all: return {"-Wall"}; case ctor::c_opt::warnings_as_errors: return {"-Werror"}; case ctor::c_opt::generate_dep_tree: return {"-MMD"}; case ctor::c_opt::no_link: return {"-c"}; case ctor::c_opt::include_path: return {"-I" + arg}; case ctor::c_opt::c_std: return {"-std=" + arg}; case ctor::c_opt::optimization: return {"-O" + arg}; case ctor::c_opt::position_independent_code: return {"-fPIC"}; case ctor::c_opt::position_independent_executable: return {"-fPIE"}; case ctor::c_opt::custom: return {arg}; } std::cerr << "Unsupported compiler option.\n"; return {}; } std::vector ld_option(ctor::ld_opt opt, const std::string& arg) { switch(opt) { case ctor::ld_opt::output: return {"-o", arg}; case ctor::ld_opt::strip: return {"-s"}; case ctor::ld_opt::warn_all: return {"-Wall"}; case ctor::ld_opt::warnings_as_errors: return {"-Werror"}; case ctor::ld_opt::library_path: return {"-L" + arg}; case ctor::ld_opt::link: return {"-l" + arg}; case ctor::ld_opt::cpp_std: return {"-std=" + arg}; case ctor::ld_opt::build_shared: return {"-shared"}; case ctor::ld_opt::threads: return {"-pthread"}; case ctor::ld_opt::position_independent_code: return {"-fPIC"}; case ctor::ld_opt::position_independent_executable: return {"-fPIE"}; case ctor::ld_opt::custom: return {arg}; } std::cerr << "Unsupported compiler option.\n"; return {}; } std::vector ar_option(ctor::ar_opt opt, const std::string& arg) { switch(opt) { case ctor::ar_opt::replace: return {"-r"}; case ctor::ar_opt::add_index: return {"-s"}; case ctor::ar_opt::create: return {"-c"}; case ctor::ar_opt::output: return {arg}; case ctor::ar_opt::custom: return {arg}; } std::cerr << "Unsupported compiler option.\n"; return {}; } std::vector asm_option(ctor::asm_opt opt, const std::string& arg) { switch(opt) { case ctor::asm_opt::custom: return {arg}; } std::cerr << "Unsupported compiler option.\n"; return {}; } } // gcc:: std::string get_arch(ctor::output_system system) { auto toolchain = getToolChain(system); switch(toolchain) { case ctor::toolchain::clang: case ctor::toolchain::gcc: return gcc::get_arch(system); case ctor::toolchain::any: case ctor::toolchain::none: break; } return {}; } ctor::arch get_arch(ctor::output_system system, const std::string& str) { auto toolchain = getToolChain(system); switch(toolchain) { case ctor::toolchain::clang: case ctor::toolchain::gcc: return gcc::get_arch(str); case ctor::toolchain::any: case ctor::toolchain::none: break; } return ctor::arch::unknown; } std::vector c_option(ctor::toolchain toolchain, ctor::c_opt opt, const std::string& arg) { switch(toolchain) { case ctor::toolchain::gcc: case ctor::toolchain::clang: return gcc::c_option(opt, arg); case ctor::toolchain::any: { std::ostringstream ss; ss << "{" << opt; if(!arg.empty()) { ss << ", \"" << arg << "\""; } ss << "}"; return { ss.str() }; } case ctor::toolchain::none: break; } std::cerr << "Unsupported tool-chain.\n"; return {}; } std::vector cxx_option(ctor::toolchain toolchain, ctor::cxx_opt opt, const std::string& arg) { switch(toolchain) { case ctor::toolchain::gcc: case ctor::toolchain::clang: return gcc::cxx_option(opt, arg); case ctor::toolchain::any: { std::ostringstream ss; ss << "{" << opt; if(!arg.empty()) { ss << ", \"" << arg << "\""; } ss << "}"; return { ss.str() }; } case ctor::toolchain::none: break; } std::cerr << "Unsupported tool-chain.\n"; return {}; } std::vector ld_option(ctor::toolchain toolchain, ctor::ld_opt opt, const std::string& arg) { switch(toolchain) { case ctor::toolchain::gcc: case ctor::toolchain::clang: return gcc::ld_option(opt, arg); case ctor::toolchain::any: { std::ostringstream ss; ss << "{" << opt; if(!arg.empty()) { ss << ", \"" << arg << "\""; } ss << "}"; return { ss.str() }; } case ctor::toolchain::none: break; } std::cerr << "Unsupported tool-chain.\n"; return {}; } std::vector ar_option(ctor::toolchain toolchain, ctor::ar_opt opt, const std::string& arg) { switch(toolchain) { case ctor::toolchain::gcc: case ctor::toolchain::clang: return gcc::ar_option(opt, arg); case ctor::toolchain::any: { std::ostringstream ss; ss << "{" << opt; if(!arg.empty()) { ss << ", \"" << arg << "\""; } ss << "}"; return { ss.str() }; } case ctor::toolchain::none: break; } std::cerr << "Unsupported tool-chain.\n"; return {}; } std::vector asm_option(ctor::toolchain toolchain, ctor::asm_opt opt, const std::string& arg) { switch(toolchain) { case ctor::toolchain::gcc: case ctor::toolchain::clang: return gcc::asm_option(opt, arg); case ctor::toolchain::any: { std::ostringstream ss; ss << "{" << opt; if(!arg.empty()) { ss << ", \"" << arg << "\""; } ss << "}"; return { ss.str() }; } case ctor::toolchain::none: break; } std::cerr << "Unsupported tool-chain.\n"; return {}; } ctor::c_flag c_option(const std::string& flag, ctor::toolchain toolchain) { switch(toolchain) { case ctor::toolchain::gcc: case ctor::toolchain::clang: return gcc::c_option(flag); case ctor::toolchain::any: case ctor::toolchain::none: break; } return { ctor::c_opt::custom, flag }; } ctor::cxx_flag cxx_option(const std::string& flag, ctor::toolchain toolchain) { switch(toolchain) { case ctor::toolchain::gcc: case ctor::toolchain::clang: return gcc::cxx_option(flag); case ctor::toolchain::any: case ctor::toolchain::none: break; } return { ctor::cxx_opt::custom, flag }; } ctor::ld_flag ld_option(const std::string& flag, ctor::toolchain toolchain) { switch(toolchain) { case ctor::toolchain::gcc: case ctor::toolchain::clang: return gcc::ld_option(flag); case ctor::toolchain::any: case ctor::toolchain::none: break; } return { ctor::ld_opt::custom, flag }; } ctor::ar_flag ar_option(const std::string& flag, ctor::toolchain toolchain) { switch(toolchain) { case ctor::toolchain::gcc: case ctor::toolchain::clang: return gcc::ar_option(flag); case ctor::toolchain::any: case ctor::toolchain::none: break; } return { ctor::ar_opt::custom, flag }; } ctor::asm_flag asm_option(const std::string& flag, ctor::toolchain toolchain) { switch(toolchain) { case ctor::toolchain::gcc: case ctor::toolchain::clang: case ctor::toolchain::any: case ctor::toolchain::none: break; } return { ctor::asm_opt::custom, flag }; } // Flag to string coversions std::vector to_strings(ctor::toolchain toolchain, const ctor::c_flag& flag) { if(flag.toolchain == ctor::toolchain::any || flag.toolchain == toolchain) { return c_option(toolchain, flag.opt, flag.arg); } return {}; } std::vector to_strings(ctor::toolchain toolchain, const ctor::cxx_flag& flag) { if(flag.toolchain == ctor::toolchain::any || flag.toolchain == toolchain) { return cxx_option(toolchain, flag.opt, flag.arg); } return {}; } std::vector to_strings(ctor::toolchain toolchain, const ctor::ld_flag& flag) { if(flag.toolchain == ctor::toolchain::any || flag.toolchain == toolchain) { return ld_option(toolchain, flag.opt, flag.arg); } return {}; } std::vector to_strings(ctor::toolchain toolchain, const ctor::ar_flag& flag) { if(flag.toolchain == ctor::toolchain::any || flag.toolchain == toolchain) { return ar_option(toolchain, flag.opt, flag.arg); } return {}; } std::vector to_strings(ctor::toolchain toolchain, const ctor::asm_flag& flag) { if(flag.toolchain == ctor::toolchain::any || flag.toolchain == toolchain) { return asm_option(toolchain, flag.opt, flag.arg); } return {}; } namespace { ctor::toolchain guess_toolchain(const std::string& opt) { if(opt.empty()) { return ctor::toolchain::any; } if(opt[0] == '-') { return ctor::toolchain::gcc; } //if(opt[0] == '/') //{ // return ctor::toolchain::msvc; //} return ctor::toolchain::any; } } template<> ctor::flag::flag(const char* str) { *this = c_option(str, guess_toolchain(str)); } template<> ctor::flag::flag(const char* str) { *this = cxx_option(str, guess_toolchain(str)); } template<> ctor::flag::flag(const char* str) { *this = ld_option(str, guess_toolchain(str)); } template<> ctor::flag::flag(const char* str) { *this = ar_option(str, guess_toolchain(str)); } template<> ctor::flag::flag(const char* str) { *this = asm_option(str, guess_toolchain(str)); } ctor::target_type target_type_from_extension(ctor::toolchain toolchain, const std::filesystem::path& file) { auto ext = to_lower(file.extension().string()); // Loosely based on: // https://en.wikipedia.org/wiki/List_of_file_formats#Object_code,_executable_files,_shared_and_dynamically_linked_libraries if(toolchain == ctor::toolchain::any || toolchain == ctor::toolchain::gcc || toolchain == ctor::toolchain::clang) { if(ext == ".a") { return ctor::target_type::static_library; } if(ext == ".so" || ext == ".dylib") { return ctor::target_type::dynamic_library; } if(ext == ".o") { return ctor::target_type::object; } if(ext == "" || ext == ".bin" || ext == ".run" || ext == ".out") { return ctor::target_type::executable; } } if(toolchain == ctor::toolchain::any// || //toolchain == ctor::toolchain::msvc || //toolchain == ctor::toolchain::mingw || ) { if(ext == ".lib") { return ctor::target_type::static_library; } if(ext == ".dll") { return ctor::target_type::dynamic_library; } if(ext == ".obj") { return ctor::target_type::object; } if(ext == ".exe" || ext == ".com") { return ctor::target_type::executable; } } return ctor::target_type::unknown; } std::filesystem::path extension(ctor::toolchain toolchain, ctor::target_type target_type, ctor::output_system system, const std::filesystem::path& file) { auto type = target_type_from_extension(toolchain, file); if(type == target_type) { // File already has the correct extension return file; } const auto& c = ctor::get_configuration(); ctor::arch arch{}; switch(system) { case ctor::output_system::host: arch = c.host_arch; break; case ctor::output_system::build: arch = c.build_arch; break; } // This might be before configure - so detection is needed for boostrap if(arch == ctor::arch::unknown) { arch = get_arch(system, get_arch(system)); } std::string ext{file.extension().string()}; switch(target_type) { case ctor::target_type::automatic: break; case ctor::target_type::executable: case ctor::target_type::unit_test: switch(arch) { case ctor::arch::unix: case ctor::arch::apple: ext = ""; break; case ctor::arch::windows: ext = ".exe"; break; case ctor::arch::unknown: break; } break; case ctor::target_type::static_library: case ctor::target_type::unit_test_library: switch(arch) { case ctor::arch::unix: case ctor::arch::apple: ext = ".a"; break; case ctor::arch::windows: ext = ".lib"; break; case ctor::arch::unknown: break; } break; case ctor::target_type::dynamic_library: switch(arch) { case ctor::arch::unix: ext = ".so"; break; case ctor::arch::apple: ext = ".dylib"; break; case ctor::arch::windows: ext = ".dll"; break; case ctor::arch::unknown: break; } break; case ctor::target_type::object: switch(arch) { case ctor::arch::unix: case ctor::arch::apple: ext = ".o"; break; case ctor::arch::windows: ext = ".obj"; break; case ctor::arch::unknown: break; } break; case ctor::target_type::function: break; case ctor::target_type::unknown: break; } auto output{file}; output.replace_extension(ext); return output; }