// -*- c++ -*- // Distributed under the BSD 2-Clause License. // See accompanying file LICENSE for details. #include "configure.h" #include #include #include #include #include #include "execute.h" #include "ctor.h" #include "tasks.h" #include "rebuild.h" #include "externals.h" #include "tools.h" #include "util.h" std::filesystem::path configurationFile("configuration.cc"); std::filesystem::path configHeaderFile("config.h"); std::map external_includedir; std::map external_libdir; const ctor::configuration& __attribute__((weak)) ctor::get_configuration() { static ctor::configuration cfg; static bool initialised{false}; if(!initialised) { cfg.build_toolchain = getToolChain(cfg.get(ctor::cfg::build_cxx, "/usr/bin/g++")); initialised = true; } return cfg; } namespace ctor { std::optional includedir; std::optional libdir; std::optional builddir; std::map conf_values; } // ctor:: bool ctor::configuration::has(const std::string& key) const { if(key == ctor::cfg::ctor_includedir && ctor::includedir) { return true; } if(key == ctor::cfg::ctor_libdir && ctor::libdir) { return true; } if(key == ctor::cfg::builddir && ctor::builddir) { return true; } if(ctor::conf_values.find(key) != ctor::conf_values.end()) { return true; } return tools.find(key) != tools.end(); } const std::string& ctor::configuration::get(const std::string& key, const std::string& default_value) const { if(key == ctor::cfg::ctor_includedir && ctor::includedir) { return *ctor::includedir; } if(key == ctor::cfg::ctor_libdir && ctor::libdir) { return *ctor::libdir; } if(key == ctor::cfg::builddir && ctor::builddir) { return *ctor::builddir; } if(ctor::conf_values.find(key) != ctor::conf_values.end()) { return ctor::conf_values[key]; } if(has(key)) { return tools.at(key); } return default_value; } class Args : public std::vector { public: Args(const std::vector& args) { resize(args.size() + 1); (*this)[0] = strdup("./ctor"); for(std::size_t i = 0; i < size() - 1; ++i) { (*this)[i + 1] = strdup(args[i].data()); } } ~Args() { for(std::size_t i = 0; i < size(); ++i) { free((*this)[i]); } } }; namespace { std::ostream& operator<<(std::ostream& stream, const ctor::toolchain& toolchain) { switch(toolchain) { case ctor::toolchain::any: stream << "ctor::toolchain::any"; break; case ctor::toolchain::none: stream << "ctor::toolchain::none"; break; case ctor::toolchain::gcc: stream << "ctor::toolchain::gcc"; break; case ctor::toolchain::clang: stream << "ctor::toolchain::clang"; break; } return stream; } std::ostream& operator<<(std::ostream& stream, const ctor::arch& arch) { switch(arch) { case ctor::arch::unix: stream << "ctor::arch::unix"; break; case ctor::arch::apple: stream << "ctor::arch::apple"; break; case ctor::arch::windows: stream << "ctor::arch::windows"; break; case ctor::arch::unknown: stream << "ctor::arch::unknown"; break; } return stream; } std::ostream& operator<<(std::ostream& ostr, const ctor::c_flag& flag) { for(const auto& s : to_strings(ctor::toolchain::any, flag)) { ostr << s; } return ostr; } std::ostream& operator<<(std::ostream& ostr, const ctor::cxx_flag& flag) { for(const auto& s : to_strings(ctor::toolchain::any, flag)) { ostr << s; } return ostr; } std::ostream& operator<<(std::ostream& ostr, const ctor::ld_flag& flag) { for(const auto& s : to_strings(ctor::toolchain::any, flag)) { ostr << s; } return ostr; } std::ostream& operator<<(std::ostream& ostr, const ctor::asm_flag& flag) { for(const auto& s : to_strings(ctor::toolchain::any, flag)) { ostr << s; } return ostr; } } // helper constant for the visitor template inline constexpr bool always_false_v = false; int regenerateCache(ctor::settings& settings, const std::string& name, const std::vector& args, const std::map& env) { Args vargs(args); dg::Options opt; int key{128}; std::string build_arch_prefix; std::string build_path; std::string host_arch_prefix; std::string host_path; std::string cc_prog = "gcc"; std::string cxx_prog = "g++"; std::string ar_prog = "ar"; std::string ld_prog = "ld"; std::string ctor_includedir; std::string ctor_libdir; std::string builddir; opt.add("build-dir", required_argument, 'b', "Set output directory for build files (default: '" + settings.builddir + "').", [&]() { settings.builddir = optarg; builddir = optarg; return 0; }); opt.add("verbose", no_argument, 'v', "Be verbose. Add multiple times for more verbosity.", [&]() { settings.verbose++; return 0; }); opt.add("cc", required_argument, key++, "Use specified c-compiler instead of gcc.", [&]() { cc_prog = optarg; return 0; }); opt.add("cxx", required_argument, key++, "Use specified c++-compiler instead of g++.", [&]() { cxx_prog = optarg; return 0; }); opt.add("ar", required_argument, key++, "Use specified archiver instead of ar.", [&]() { ar_prog = optarg; return 0; }); opt.add("ld", required_argument, key++, "Use specified linker instead of ld.", [&]() { ld_prog = optarg; return 0; }); opt.add("build", required_argument, key++, "Configure for building on specified architecture.", [&]() { build_arch_prefix = optarg; return 0; }); opt.add("build-path", required_argument, key++, "Set path to build tool-chain.", [&]() { build_path = optarg; return 0; }); opt.add("host", required_argument, key++, "Cross-compile to build programs to run on specified architecture.", [&]() { host_arch_prefix = optarg; return 0; }); opt.add("host-path", required_argument, key++, "Set path to cross-compile tool-chain.", [&]() { host_path = optarg; return 0; }); opt.add("ctor-includedir", required_argument, key++, "Set path to ctor header file, used for re-compiling.", [&]() { ctor_includedir = optarg; return 0; }); opt.add("ctor-libdir", required_argument, key++, "Set path to ctor library file, used for re-compiling.", [&]() { ctor_libdir = optarg; return 0; }); // Resolv externals ctor::external_configurations externalConfigs; for(std::size_t i = 0; i < numExternalConfigFiles; ++i) { auto newExternalConfigs = externalConfigFiles[i].cb(settings); externalConfigs.insert(externalConfigs.end(), newExternalConfigs.begin(), newExternalConfigs.end()); } auto add_path_args = [&](const std::string& name) { opt.add(name + "-includedir", required_argument, key++, "Set path to " + name + " header file.", [&]() { external_includedir[name] = optarg; return 0; }); opt.add(name + "-libdir", required_argument, key++, "Set path to " + name + " libraries.", [&]() { external_libdir[name] = optarg; return 0; }); }; for(const auto& ext : externalConfigs) { std::visit([&](auto&& arg) { using T = std::decay_t; if constexpr (std::is_same_v) { add_path_args(ext.name); } else { static_assert(always_false_v, "non-exhaustive visitor!"); } }, ext.external); } opt.add("help", no_argument, 'h', "Print this help text.", [&]() { std::cout << "Configure how to build with " << name << "\n"; std::cout << "Usage: " << name << " configure [options]\n\n"; std::cout << "Options:\n"; opt.help(); exit(0); return 0; }); opt.process(vargs.size(), vargs.data()); if(host_arch_prefix.empty()) { host_arch_prefix = build_arch_prefix; } auto tasks = getTasks(settings, {}, false); bool needs_build{true}; // we always need to compile ctor itself bool needs_build_c{false}; bool needs_build_cxx{true}; // we always need to compile ctor itself bool needs_build_ld{true}; // we always need to compile ctor itself bool needs_build_ar{false}; bool needs_build_asm{false}; bool needs_host_c{false}; bool needs_host{false}; bool needs_host_cxx{false}; bool needs_host_ld{false}; bool needs_host_ar{false}; bool needs_host_asm{false}; for(const auto& task :tasks) { switch(task->outputSystem()) { case ctor::output_system::build: needs_build = true; switch(task->targetType()) { case ctor::target_type::executable: case ctor::target_type::unit_test: case ctor::target_type::dynamic_library: needs_build_ld = true; break; case ctor::target_type::static_library: case ctor::target_type::unit_test_library: needs_build_ar = true; break; case ctor::target_type::object: switch(task->sourceLanguage()) { case ctor::language::automatic: std::cerr << "TargetLanguage not deduced!\n"; exit(1); break; case ctor::language::c: needs_build_c = true; break; case ctor::language::cpp: needs_build_cxx = true; break; case ctor::language::assembler: needs_build_asm = true; break; } break; case ctor::target_type::function: case ctor::target_type::automatic: case ctor::target_type::unknown: break; } break; case ctor::output_system::host: needs_host = true; switch(task->targetType()) { case ctor::target_type::executable: case ctor::target_type::unit_test: case ctor::target_type::dynamic_library: needs_host_ld = true; break; case ctor::target_type::static_library: case ctor::target_type::unit_test_library: needs_host_ar = true; break; case ctor::target_type::object: switch(task->sourceLanguage()) { case ctor::language::automatic: std::cerr << "TargetLanguage not deduced!\n"; exit(1); break; case ctor::language::c: needs_host_c = true; break; case ctor::language::cpp: needs_host_cxx = true; break; case ctor::language::assembler: needs_host_asm = true; break; } break; case ctor::target_type::function: case ctor::target_type::automatic: case ctor::target_type::unknown: break; } break; } } auto cc_env = env.find("CC"); if(cc_env != env.end()) { cc_prog = cc_env->second; } auto cxx_env = env.find("CXX"); if(cxx_env != env.end()) { cxx_prog = cxx_env->second; } auto ar_env = env.find("AR"); if(ar_env != env.end()) { ar_prog = ar_env->second; } auto ld_env = env.find("LD"); if(ld_env != env.end()) { ld_prog = ld_env->second; } auto paths = get_paths(); auto path_env = env.find("PATH"); if(path_env != env.end()) { paths = get_paths(path_env->second); } std::string host_cc; std::string host_cxx; std::string host_ld; std::string host_ar; ctor::toolchain host_toolchain{ctor::toolchain::none}; ctor::arch host_arch{ctor::arch::unknown}; if(needs_host) { // Host detection if(needs_host_c) { host_cc = locate(cc_prog, paths, host_arch_prefix); if(host_cc.empty()) { std::cerr << "Could not locate host_cc prog" << std::endl; return 1; } } if(needs_host_cxx) { host_cxx = locate(cxx_prog, paths, host_arch_prefix); if(host_cxx.empty()) { std::cerr << "Could not locate host_cxx prog" << std::endl; return 1; } } if(needs_host_ar) { host_ar = locate(ar_prog, paths, host_arch_prefix); if(host_ar.empty()) { std::cerr << "Could not locate host_ar prog" << std::endl; return 1; } } if(needs_host_ld) { host_ld = locate(ld_prog, paths, host_arch_prefix); if(host_ld.empty()) { std::cerr << "Could not locate host_ld prog" << std::endl; return 1; } } if(needs_host_asm) { // TODO } host_toolchain = getToolChain(host_cxx); auto host_arch_str = get_arch(ctor::output_system::host); host_arch = get_arch(ctor::output_system::host, host_arch_str); std::cout << "** Host architecture '" << host_arch_str << "': " << host_arch << std::endl; if(host_arch == ctor::arch::unknown) { std::cerr << "Could not detect host architecture" << std::endl; return 1; } } std::string build_cc; std::string build_cxx; std::string build_ld; std::string build_ar; ctor::toolchain build_toolchain{ctor::toolchain::none}; ctor::arch build_arch{ctor::arch::unknown}; if(needs_build) { // Build detection if(needs_build_c) { build_cc = locate(cc_prog, paths, build_arch_prefix); if(build_cc.empty()) { std::cerr << "Could not locate build_cc prog" << std::endl; return 1; } } if(needs_build_cxx) { build_cxx = locate(cxx_prog, paths, build_arch_prefix); if(build_cxx.empty()) { std::cerr << "Could not locate build_cxx prog" << std::endl; return 1; } } if(needs_build_ar) { build_ar = locate(ar_prog, paths, build_arch_prefix); if(build_ar.empty()) { std::cerr << "Could not locate build_ar prog" << std::endl; return 1; } } if(needs_build_ld) { build_ld = locate(ld_prog, paths, build_arch_prefix); if(build_ld.empty()) { std::cerr << "Could not locate build_ld prog" << std::endl; return 1; } } if(needs_build_asm) { // TODO } build_toolchain = getToolChain(build_cxx); auto build_arch_str = get_arch(ctor::output_system::build); build_arch = get_arch(ctor::output_system::build, build_arch_str); std::cout << "** Build architecture '" << build_arch_str << "': " << build_arch << std::endl; if(build_arch == ctor::arch::unknown) { std::cerr << "Could not detect build architecture" << std::endl; return 1; } } // Store current values for execution in this execution context. if(!ctor_includedir.empty()) { ctor::conf_values[ctor::cfg::ctor_includedir] = ctor_includedir; } if(!ctor_libdir.empty()) { ctor::conf_values[ctor::cfg::ctor_libdir] = ctor_libdir; } if(!builddir.empty()) { ctor::conf_values[ctor::cfg::builddir] = builddir; } ctor::conf_values[ctor::cfg::host_cxx] = host_cxx; ctor::conf_values[ctor::cfg::build_cxx] = build_cxx; std::cout << "Writing results to: " << configurationFile.string() << "\n"; { std::ofstream istr(configurationFile); istr << "#include \n\n"; istr << "const ctor::configuration& ctor::get_configuration()\n"; istr << "{\n"; istr << " static ctor::configuration cfg =\n"; istr << " {\n"; if(needs_host) { istr << " .host_toolchain = " << host_toolchain << ",\n"; istr << " .host_arch = " << host_arch << ",\n"; } if(needs_build) { istr << " .build_toolchain = " << build_toolchain << ",\n"; istr << " .build_arch = " << build_arch << ",\n"; } istr << " .args = {"; for(const auto& arg : args) { istr << "\"" << esc(arg) << "\","; } istr << "},\n"; istr << " .env = {\n"; for(const auto& e : env) { istr << " {\"" << esc(e.first) << "\", \"" << esc(e.second) << "\"},\n"; } istr << " },\n"; istr << " .tools = {\n"; if(!builddir.empty()) { istr << " { \"" << ctor::cfg::builddir << "\", \"" << esc(builddir) << "\" },\n"; ctor::builddir = builddir; } if(needs_host) { if(needs_host_c) { istr << " { \"" << ctor::cfg::host_cc << "\", \"" << esc(host_cc) << "\" },\n"; } if(needs_host_cxx) { istr << " { \"" << ctor::cfg::host_cxx << "\", \"" << esc(host_cxx) << "\" },\n"; } if(needs_host_ar) { istr << " { \"" << ctor::cfg::host_ar << "\", \"" << esc(host_ar) << "\" },\n"; } if(needs_host_ld) { istr << " { \"" << ctor::cfg::host_ld << "\", \"" << esc(host_ld) << "\" },\n"; } } if(needs_build) { if(needs_build_c) { istr << " { \"" << ctor::cfg::build_cc << "\", \"" << esc(build_cc) << "\" },\n"; } if(needs_build_cxx) { istr << " { \"" << ctor::cfg::build_cxx << "\", \"" << esc(build_cxx) << "\" },\n"; } if(needs_build_ar) { istr << " { \"" << ctor::cfg::build_ar << "\", \"" << esc(build_ar) << "\" },\n"; } if(needs_build_ld) { istr << " { \"" << ctor::cfg::build_ld << "\", \"" << esc(build_ld) << "\" },\n"; } } if(!ctor_includedir.empty()) { istr << " { \"" << ctor::cfg::ctor_includedir << "\", \"" << esc(ctor_includedir) << "\" },\n"; ctor::includedir = ctor_includedir; } if(!ctor_libdir.empty()) { istr << " { \"" << ctor::cfg::ctor_libdir << "\", \"" << esc(ctor_libdir) << "\" },\n"; ctor::libdir = ctor_libdir; } istr << " },\n"; istr << " .externals = {\n"; for(const auto& ext : externalConfigs) { istr << " { \"" << esc(ext.name) << "\", {\n"; ctor::flags resolved_flags; if(std::holds_alternative(ext.external)) { if(auto ret = resolv(settings, ext, std::get(ext.external), resolved_flags)) { return ret; } } else { std::cout << "Unknown external type\n"; return 1; } if(!resolved_flags.cflags.empty()) { istr << " .cflags = {"; for(const auto& flag : resolved_flags.cflags) { istr << flag << ","; } istr << "},\n"; } if(!resolved_flags.cxxflags.empty()) { istr << " .cxxflags = {"; for(const auto& flag : resolved_flags.cxxflags) { istr << flag << ","; } istr << "},\n"; } if(!resolved_flags.ldflags.empty()) { istr << " .ldflags = {"; for(const auto& flag : resolved_flags.ldflags) { istr << flag << ","; } istr << "},\n"; } if(!resolved_flags.asmflags.empty()) { istr << " .asmflags = {"; for(const auto& flag : resolved_flags.asmflags) { istr << flag << ","; } istr << "},\n"; } istr << " }},\n"; } istr << " },\n"; istr << " };\n"; istr << " return cfg;\n"; istr << "}\n"; } { std::ofstream istr(configHeaderFile); istr << "#pragma once\n\n"; istr << "#define HAS_FOO 1\n"; istr << "//#define HAS_BAR 1\n"; } return 0; } int configure(const ctor::settings& global_settings, int argc, char* argv[]) { ctor::settings settings{global_settings}; std::vector args; for(int i = 2; i < argc; ++i) // skip command and the first 'configure' arg { args.emplace_back(argv[i]); } std::map env; auto cc_env = getenv("CC"); if(cc_env) { env["CC"] = cc_env; } auto cxx_env = getenv("CXX"); if(cxx_env) { env["CXX"] = cxx_env; } auto ar_env = getenv("AR"); if(ar_env) { env["AR"] = ar_env; } auto ld_env = getenv("LD"); if(ld_env) { env["LD"] = ld_env; } auto path_env = getenv("PATH"); if(path_env) { env["PATH"] = path_env; } auto ret = regenerateCache(settings, argv[0], args, env); if(ret != 0) { return ret; } recompileCheck(settings, argc, argv, false); return 0; } int reconfigure(const ctor::settings& global_settings, int argc, char* argv[]) { ctor::settings settings{global_settings}; bool no_rerun{false}; std::vector args; for(int i = 2; i < argc; ++i) // skip executable name and 'reconfigure' arg { if(i == 2 && std::string(argv[i]) == "--no-rerun") { no_rerun = true; continue; } args.emplace_back(argv[i]); } const auto& cfg = ctor::get_configuration(); std::cout << "Re-running configure:\n"; for(const auto& e : cfg.env) { std::cout << e.first << "=\"" << e.second << "\" "; } std::cout << argv[0] << " configure "; for(const auto& arg : cfg.args) { std::cout << arg << " "; } std::cout << "\n"; auto ret = regenerateCache(settings, argv[0], cfg.args, cfg.env); if(ret != 0) { return ret; } recompileCheck(settings, 1, argv, false); if(no_rerun) { return 0; // this was originally invoked by configure, don't loop } return execute(settings, argv[0], args); }