#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "libcppbuild.h" #include "task_cc.h" #include "task_ld.h" #include "task_ar.h" #include "task_so.h" #include "settings.h" #include "execute.h" #include namespace { std::filesystem::path configurationFile("configuration.cc"); const std::map default_configuration{}; } const std::map& __attribute__((weak)) configuration() { return default_configuration; } bool hasConfiguration(const std::string& key) { const auto& c = configuration(); return c.find(key) != c.end(); } const std::string& getConfiguration(const std::string& key, const std::string& defaultValue) { const auto& c = configuration(); if(hasConfiguration(key)) { return c.at(key); } return defaultValue; } using namespace std::chrono_literals; std::list> taskFactory(const BuildConfiguration& config, const Settings& settings, const std::string& sourceDir) { std::filesystem::path targetFile(config.target); TargetType target_type{config.type}; if(target_type == TargetType::Auto) { if(targetFile.extension() == ".a") { target_type = TargetType::StaticLibrary; } else if(targetFile.extension() == ".so") { target_type = TargetType::DynamicLibrary; } else if(targetFile.extension() == "") { target_type = TargetType::Executable; } else { std::cerr << "Could not deduce target type from target " << targetFile.string() << " please specify.\n"; exit(1); } } std::vector objects; std::list> tasks; for(const auto& file : config.sources) { tasks.emplace_back(std::make_shared(config, settings, sourceDir, file)); objects.push_back(tasks.back()->target()); } switch(target_type) { case TargetType::StaticLibrary: tasks.emplace_back(std::make_shared(config, settings, config.target, objects)); break; case TargetType::DynamicLibrary: if(targetFile.stem().string().substr(0, 3) != "lib") { std::cerr << "Dynamic library target must have 'lib' prefix\n"; exit(1); } tasks.emplace_back(std::make_shared(config, settings, config.target, objects)); break; case TargetType::Executable: tasks.emplace_back(std::make_shared(config, settings, config.target, objects)); break; } return tasks; } std::shared_ptr getNextTask(const std::list>& allTasks, std::list>& dirtyTasks) { for(auto dirtyTask = dirtyTasks.begin(); dirtyTask != dirtyTasks.end(); ++dirtyTask) { //std::cout << "Examining target " << (*dirtyTask)->target() << "\n"; if((*dirtyTask)->ready()) { dirtyTasks.erase(dirtyTask); return *dirtyTask; } } //std::cout << "No task ready ... \n"; return nullptr; } namespace { struct BuildConfigurationEntry { const char* file; std::vector (*cb)(); }; std::array configFiles; int numConfigFiles{0}; } // TODO: Use c++20 when ready, somehing like this: //int reg(const std::source_location location = std::source_location::current()) int reg(const char* location, std::vector (*cb)()) { // NOTE: std::cout cannot be used here if(numConfigFiles >= configFiles.size()) { fprintf(stderr, "Max %d build configurations currently supported.\n", (int)configFiles.size()); exit(1); } configFiles[numConfigFiles].file = location; configFiles[numConfigFiles].cb = cb; ++numConfigFiles; return 0; } void recompileCheck(const Settings& settings, int argc, char* argv[]) { bool dirty{false}; std::vector args; args.push_back("-s"); args.push_back("-O3"); args.push_back("-std=c++17"); args.push_back("-pthread"); std::filesystem::path binFile("cppbuild"); if(std::filesystem::exists(configurationFile)) { args.push_back(configurationFile.string()); if(std::filesystem::last_write_time(binFile) <= std::filesystem::last_write_time(configurationFile)) { dirty = true; } const auto& c = configuration(); if(&c == &default_configuration) { // configuration.cc exists, but currently compiled with the default one. dirty = true; } } if(settings.verbose > 1) { std::cout << "Recompile check (" << numConfigFiles << "):\n"; } for(int i = 0; i < numConfigFiles; ++i) { std::string location = configFiles[i].file; if(settings.verbose > 1) { std::cout << " - " << location << "\n"; } std::filesystem::path configFile(location); if(std::filesystem::last_write_time(binFile) <= std::filesystem::last_write_time(configFile)) { dirty = true; } // Support adding multiple config functions from the same file if(std::find(args.begin(), args.end(), location) == std::end(args)) { args.push_back(location); } } args.push_back("libcppbuild.a"); args.push_back("-o"); args.push_back(binFile.string()); if(dirty) { std::cout << "Rebuilding config\n"; auto tool = getConfiguration(cfg::host_cpp, "/usr/bin/g++"); auto ret = execute(tool, args, settings.verbose > 0); if(ret != 0) { std::cerr << "Failed: ." << ret << "\n"; exit(1); } else { std::cout << "Re-launch\n"; std::vector args; for(int i = 1; i < argc; ++i) { args.push_back(argv[i]); } exit(execute(argv[0], args, settings.verbose > 0)); } } } std::list> getTasks(const Settings& settings) { static std::deque build_configs; std::list> tasks; for(int i = 0; i < numConfigFiles; ++i) { std::string path = std::filesystem::path(configFiles[i].file).parent_path(); if(settings.verbose > 1) { std::cout << configFiles[i].file << " in path " << path << "\n"; } auto configs = configFiles[i].cb(); for(const auto& config : configs) { build_configs.push_back(config); const auto& build_config = build_configs.back(); std::vector objects; auto t = taskFactory(build_config, settings, path); tasks.insert(tasks.end(), t.begin(), t.end()); } } return tasks; } /* int configure(int argc, char* argv[]) { Settings settings; settings.builddir = "build"; std::string cmd_str; for(int i = 0; i < argc; ++i) { if(i > 0) { cmd_str += " "; } cmd_str += argv[i]; } dg::Options opt; int key{256}; std::string build_arch; std::string host_arch; opt.add("build-dir", required_argument, 'b', "Set output directory for build files (default: '" + settings.builddir + "').", [&]() { settings.builddir = optarg; return 0; }); opt.add("verbose", no_argument, 'v', "Be verbose. Add multiple times for more verbosity.", [&]() { settings.verbose++; return 0; }); opt.add("build", required_argument, key++, "Configure for building on specified architecture.", [&]() { build_arch = optarg; return 0; }); opt.add("host", required_argument, key++, "Cross-compile to build programs to run on specified architecture.", [&]() { host_arch = optarg; return 0; }); opt.add("help", no_argument, 'h', "Print this help text.", [&]() { std::cout << "configure usage stuff\n"; opt.help(); exit(0); return 0; }); opt.process(argc, argv); if(host_arch.empty()) { host_arch = build_arch; } auto tasks = getTasks(settings); bool needs_cpp{false}; bool needs_c{false}; bool needs_ar{false}; for(const auto& task :tasks) { switch(task->sourceLanguage()) { case Language::Auto: std::cerr << "TargetLanguage not deduced!\n"; exit(1); break; case Language::C: needs_cpp = false; break; case Language::Cpp: needs_c = true; break; } } { std::ofstream istr(configurationFile); istr << "#include \"libcppbuild.h\"\n\n"; istr << "const std::map& configuration()\n"; istr << "{\n"; istr << " static std::map c =\n"; istr << " {\n"; istr << " { \"cmd\", \"" << cmd_str << "\" },\n"; istr << " { \"" << cfg::builddir << "\", \"" << settings.builddir << "\" },\n"; istr << " { \"" << cfg::target_cc << "\", \"/usr/bin/gcc\" },\n"; istr << " { \"" << cfg::target_cpp << "\", \"/usr/bin/g++\" },\n"; istr << " { \"" << cfg::target_ar << "\", \"/usr/bin/ar\" },\n"; istr << " { \"" << cfg::target_ld << "\", \"/usr/bin/ld\" },\n"; istr << " { \"" << cfg::host_cc << "\", \"/usr/bin/gcc\" },\n"; istr << " { \"" << cfg::host_cpp << "\", \"/usr/bin/g++\" },\n"; istr << " { \"" << cfg::host_ar << "\", \"/usr/bin/ar\" },\n"; istr << " { \"" << cfg::host_ld << "\", \"/usr/bin/ld\" },\n"; istr << " };\n"; istr << " return c;\n"; istr << "}\n"; } return 0; } int main(int argc, char* argv[]) { if(argc > 1 && std::string(argv[1]) == "configure") { return configure(argc, argv); } Settings settings{}; settings.builddir = getConfiguration(cfg::builddir, "build"); settings.parallel_processes = std::max(1u, std::thread::hardware_concurrency() * 2 - 1); settings.verbose = 0; bool write_compilation_database{false}; std::string compilation_database; bool print_configure_cmd{false}; bool print_configure_db{false}; dg::Options opt; int key{256}; opt.add("jobs", required_argument, 'j', "Number of parallel jobs. (default: cpucount * 2 - 1 )", [&]() { try { settings.parallel_processes = std::stoi(optarg); } catch(...) { std::cerr << "Not a number\n"; return 1; } return 0; }); opt.add("build-dir", required_argument, 'b', "Overload output directory for build files (default: '" + settings.builddir + "').", [&]() { settings.builddir = optarg; return 0; }); opt.add("verbose", no_argument, 'v', "Be verbose. Add multiple times for more verbosity.", [&]() { settings.verbose++; return 0; }); opt.add("configure-cmd", no_argument, key++, "Print commandline for last configure.", [&]() { print_configure_cmd = true; return 0; }); opt.add("configure-db", no_argument, key++, "Print entire configure parameter database.", [&]() { print_configure_db = true; return 0; }); opt.add("database", required_argument, 'd', "Write compilation database json file.", [&]() { write_compilation_database = true; compilation_database = optarg; return 0; }); opt.add("help", no_argument, 'h', "Print this help text.", [&]() { std::cout << "usage stuff\n"; opt.help(); exit(0); return 0; }); opt.process(argc, argv); recompileCheck(settings, argc, argv); std::filesystem::path builddir(settings.builddir); std::filesystem::create_directories(builddir); auto tasks = getTasks(settings); if(write_compilation_database) { std::ofstream istr(compilation_database); istr << "["; bool first{true}; for(auto task : tasks) { auto s = task->toJSON(); if(!s.empty()) { if(!first) { istr << ",\n"; } else { istr << "\n"; } first = false; istr << s; } } istr << "\n]\n"; } if(print_configure_cmd) { std::cout << getConfiguration("cmd") << "\n"; return 0; } if(print_configure_db) { const auto& c = configuration(); for(const auto& config : c) { std::cout << config.first << ": " << config.second << "\n"; } return 0; } for(auto task : tasks) { if(task->registerDepTasks(tasks)) { return 1; } } std::list> dirtyTasks; for(auto task : tasks) { if(task->dirty()) { dirtyTasks.push_back(task); } } for(auto const &arg : opt.arguments()) { if(arg == "clean") { std::cout << "Cleaning\n"; for(auto& task : tasks) { if(task->clean() != 0) { return 1; } } return 0; } if(arg == "configure") { std::cerr << "The 'configure' target must be the first argument.\n"; return 1; } } if(dirtyTasks.empty()) { return 0; } std::cout << "Building\n"; std::list> processes; // Start all tasks bool done{false}; while(!done) { bool started_one{false}; while(processes.size() < settings.parallel_processes) { if(dirtyTasks.empty()) { done = true; break; } auto task = getNextTask(tasks, dirtyTasks); if(task == nullptr) { break; //return 1; } processes.emplace_back( std::async(std::launch::async, [task]() { return task->run(); })); started_one = true; std::this_thread::sleep_for(2ms); } for(auto process = processes.begin(); process != processes.end(); ++process) { if(process->valid()) { if(process->get() != 0) { return 1; } processes.erase(process); break; } } if(started_one) { std::this_thread::sleep_for(2ms); } else { std::this_thread::sleep_for(200ms); } } for(auto process = processes.begin(); process != processes.end(); ++process) { process->wait(); auto ret = process->get(); if(ret != 0) { return 1; } } return 0; }