// -*- c++ -*- // Distributed under the BSD 2-Clause License. // See accompanying file LICENSE for details. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ctor.h" #include "configure.h" #include "rebuild.h" #include "tasks.h" #include "build.h" #include "unittest.h" #include "argparser.h" #include "util.h" extern "C" { #include #include #include #include } int main(int argc, char* argv[]) { auto args = std::span(argv, static_cast(argc)); ctor::settings settings{}; const auto& c = ctor::get_configuration(); settings.builddir = c.get(ctor::cfg::builddir, settings.builddir); settings.parallel_processes = std::max(1u, std::thread::hardware_concurrency()) * 2 - 1; if(args.size() > 1 && std::string(args[1]) == "configure") { return configure(settings, argc, argv); } if(args.size() > 1 && std::string(args[1]) == "reconfigure") { return reconfigure(settings, argc, argv); } bool write_compilation_database{false}; std::string compilation_database; bool print_configure_cmd{false}; bool print_configure_db{false}; std::vector add_files; std::vector remove_files; bool no_default_build{false}; // set to true to prevent empty arg list building 'all' target. bool list_files{false}; bool list_targets{false}; bool no_relaunch{false}; // true means no re-launch after rebuild. bool run_unittests{false}; bool install{false}; std::vector arguments; arg::Parser opt(argc, argv); opt.set_pos_cb( [&](std::string_view arg) { arguments.emplace_back(std::string(arg)); return 0; }); opt.add('j', "--jobs", std::function([&](int jobs) { settings.parallel_processes = static_cast(jobs); return 0; }), "Number of parallel jobs. (default: cpucount * 2 - 1)"); opt.add('b', "--build-dir", std::function([&](std::string builddir) { settings.builddir = builddir; return 0; }), "Overload output directory for build files (default: '" + settings.builddir + "')."); opt.add('v', "--verbose", std::function([&]() { settings.verbose++; return 0; }), "Be verbose. Add multiple times for more verbosity."); opt.add('q', "--quiet", std::function([&]() { settings.verbose = -1; return 0; }), "Be completely silent."); opt.add('a', "--add", std::function([&](std::string filename) { no_relaunch = true; add_files.emplace_back(filename); return 0; }), "Add specified file to the build configurations."); opt.add('r', "--remove", std::function([&](std::string filename) { no_relaunch = true; remove_files.emplace_back(filename); return 0; }), "Remove specified file from the build configurations."); opt.add('L', "--list-files", std::function([&]() { no_relaunch = true; list_files = true; return 0; }), "List files in the build configurations."); opt.add('l', "--list-targets", std::function([&]() { no_relaunch = true; list_targets = true; return 0; }), "List targets."); opt.add('n', "--dry-run", std::function([&]() { settings.dry_run = true; return 0; }), "Print the commands to be executed, but do not execute them."); opt.add({}, "--configure-cmd", std::function([&]() { no_relaunch = true; print_configure_cmd = true; return 0; }), "Print commandline for last configure."); opt.add({}, "--configure-db", std::function([&]() { no_relaunch = true; print_configure_db = true; return 0; }), "Print entire configure parameter database."); opt.add('d', "--database", std::function([&](std::string database) { no_relaunch = true; write_compilation_database = true; compilation_database = database; return 0; }), "Write compilation database json file."); opt.add('h', "--help", std::function([&]() -> int { std::cout << "Usage: " << args[0] << " [options] [target] ...\n"; std::cout << R"_( where target can be either: configure - run configuration step (cannot be used with other targets). reconfigure - rerun configuration step with the same arguments as last (cannot be used with other targets). clean - clean all generated files. all - build all targets (default) or the name of a target which will be built along with its dependencies. Use '-l' to see a list of possible target names. Options: )_"; opt.help(); exit(0); }), "Print this help text."); opt.set_err_cb( [&](arg::error err, std::string_view arg) { switch(err) { case arg::error::invalid_arg: std::cerr << opt.prog_name() << ": invalid argument for option '" << arg << "'\n"; std::cerr << "Type '" << opt.prog_name() << " -h' for more information.\n"; break; case arg::error::missing_arg: std::cerr << opt.prog_name() << ": option requires and argument '" << arg << "'\n"; std::cerr << "Type '" << opt.prog_name() << " -h' for more information.\n"; break; case arg::error::invalid_opt: std::cerr << opt.prog_name() << ": invalid option '" << arg << "'\n"; std::cerr << "Type '" << opt.prog_name() << " -h' for more information.\n"; break; } }); auto res = opt.parse(); if(res != 0) { return res; } if(std::string value; get_env("V", value)) { try { settings.verbose = std::stoi(value); } catch(...) { // not an integer } } if(list_files) { no_default_build = true; std::vector files; const auto& configFiles = getConfigFileList(); for(const auto& configFile : configFiles) { files.emplace_back(configFile.file); } const auto& externalConfigFiles = getExternalConfigFileList(); for(const auto& externalConfigFile : externalConfigFiles) { files.emplace_back(externalConfigFile.file); } std::sort(files.begin(), files.end()); for(const auto& file : files) { std::cout << file << "\n"; } } if(!add_files.empty() || !remove_files.empty()) { no_default_build = true; for(const auto& add_file : add_files) { reg(add_file.data()); } for(const auto& remove_file : remove_files) { unreg(remove_file.data()); } // Force rebuild if files were added recompileCheck(settings, 1, argv, no_relaunch == false); } recompileCheck(settings, argc, argv); std::filesystem::path builddir(settings.builddir); std::filesystem::create_directories(builddir); auto all_tasks = getTasks(settings); if(list_targets) { no_default_build = true; auto& targets = getTargets(settings); for(const auto& target : targets) { std::cout << target.config.target << "\n"; } } if(write_compilation_database) { std::ofstream istr(compilation_database); istr << "["; bool first{true}; for(auto task : all_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) { no_default_build = true; std::cout << c.get("cmd") << "\n"; } if(print_configure_db) { no_default_build = true; for(const auto& config : c.tools) { std::cout << config.first << ": " << config.second << "\n"; } } for(auto task : all_tasks) { if(task->registerDepTasks(all_tasks)) { return 1; } } bool build_all{!no_default_build}; for(const auto& arg : arguments) { if(arg == "configure") { std::cerr << "The 'configure' target must be the first argument.\n"; return 1; } if(arg == "clean") { build_all = false; std::cout << "Cleaning\n"; for(auto& task : all_tasks) { if(task->clean() != 0) { return 1; } } } else if(arg == "check") { build_all = false; run_unittests = true; std::vector unittest_targets; auto& targets = getTargets(settings); for(const auto& target : targets) { if(target.config.type == ctor::target_type::unit_test || target.config.type == ctor::target_type::unit_test_library) { unittest_targets.push_back(target); } } auto ret = build(settings, "check", unittest_targets, all_tasks); if(ret != 0) { return ret; } } else if(arg == "dist") { ctor::project project{}; std::set dist_files; auto& targets = getTargets(settings); for(const auto& target : targets) { if(!target.config.project.name.empty()) { project = target.config.project; } for(const auto& source : target.config.sources) { if(source.nodist) { continue; } std::filesystem::path p = target.path; p /= source.file; dist_files.insert(p.lexically_normal().string()); } for(const auto& header : target.config.headers) { if(header.nodist) { continue; } std::filesystem::path p = target.path; p /= header.file; dist_files.insert(p.lexically_normal().string()); } for(const auto& extra_dist_file : target.config.extra_dist) { if(extra_dist_file.nodist) { continue; } std::filesystem::path p = target.path; p /= extra_dist_file.file; dist_files.insert(p.lexically_normal().string()); } } const auto& configFiles = getConfigFileList(); for(const auto& configFile : configFiles) { std::filesystem::path p = configFile.file; dist_files.insert(p.lexically_normal().string()); } const auto& externalConfigFiles = getExternalConfigFileList(); for(const auto& externalConfigFile : externalConfigFiles) { std::filesystem::path p = externalConfigFile.file; dist_files.insert(p.lexically_normal().string()); } if(project.name.empty()) { std::cerr << "Missing ctor::project description." " Cannot create dist-file.\n"; return 1; } std::string outname = project.name + "-" + std::to_string(project.version.major) + "." + std::to_string(project.version.minor) + "." + std::to_string(project.version.patch); if(!project.version.tweak.empty()) { outname += "-" + project.version.tweak; } outname += ".tar.gz"; { struct archive *a; struct archive_entry *entry; struct stat st; char buff[8192]; ssize_t len; int fd; a = archive_write_new(); // Filters: https://man.archlinux.org/man/archive_write_filter.3.en archive_write_add_filter_gzip(a); archive_write_set_format_pax_restricted(a); // Note 1 archive_write_open_filename(a, outname.data()); for(const auto& dist_file : dist_files) { const char*filename = dist_file.data(); stat(filename, &st); auto mode = st.st_mode & 0xfff;//0644 std::cout << dist_file << std::oct << mode << '\n'; entry = archive_entry_new(); // Note 2 archive_entry_set_pathname(entry, filename); archive_entry_set_size(entry, st.st_size); // Note 3 archive_entry_set_mtime(entry, st.st_mtime, 0); archive_entry_set_filetype(entry, AE_IFREG); archive_entry_set_perm(entry, mode); archive_write_header(a, entry); fd = open(filename, O_RDONLY); len = read(fd, buff, sizeof(buff)); while ( len > 0 ) { archive_write_data(a, buff, len); len = read(fd, buff, sizeof(buff)); } close(fd); archive_entry_free(entry); } archive_write_close(a); // Note 4 archive_write_free(a); // Note 5 } } else if(arg == "install") { install = true; build_all = true; } else { build_all = false; if(arg == "all") { std::vector non_unittest_targets; auto& targets = getTargets(settings); for(const auto& target : targets) { if(target.config.type != ctor::target_type::unit_test && target.config.type != ctor::target_type::unit_test_library) { non_unittest_targets.push_back(target); } } auto ret = build(settings, "all", non_unittest_targets, all_tasks); if(ret != 0) { return ret; } } else { auto ret = build(settings, arg, all_tasks); if(ret != 0) { return ret; } } } } if(build_all) { std::vector non_unittest_targets; auto& targets = getTargets(settings); for(const auto& target : targets) { if(target.config.type != ctor::target_type::unit_test && target.config.type != ctor::target_type::unit_test_library) { non_unittest_targets.push_back(target); } } auto ret = build(settings, "all", non_unittest_targets, all_tasks); if(ret != 0) { return ret; } } if(run_unittests) { auto ret = runUnitTests(all_tasks, settings); if(ret != 0) { return ret; } } if(install) { auto destdir_env = std::getenv("DESTDIR"); if(!destdir_env) { std::cerr << "No DESTDIR provided!\n"; return 1; } std::filesystem::path destdir = destdir_env; if(!destdir.is_absolute()) { std::cerr << "DESTDIR must be absolute!\n"; return 1; } std::cout << "Installing in DESTDIR: " << destdir.string() << "\n"; std::string prefix = "/usr"; for(const auto& task : all_tasks) { auto install_target = task->buildConfig().install; if(!task->derived() && install_target.do_install) { std::string target_dir{}; if(!install_target.path.empty()) { target_dir = install_target.path; } else { switch(task->targetType()) { case ctor::target_type::unit_test: case ctor::target_type::unit_test_library: case ctor::target_type::object: case ctor::target_type::function: case ctor::target_type::automatic: case ctor::target_type::unknown: // Ignore continue; break; case ctor::target_type::executable: target_dir = "bin"; break; case ctor::target_type::shared_library: case ctor::target_type::static_library: case ctor::target_type::module: target_dir = "lib"; break; } } std::filesystem::path target; target = prefix; target /= target_dir; target /= task->target(); if(destdir_env) { if(target.is_absolute()) { target = destdir / target.relative_path(); } else { target = destdir / target; } } std::cout << "Bin install " << task->target() << " -> " << target.string() << "\n"; } } auto& targets = getTargets(settings); for(const auto& target : targets) { for(const auto& header : target.config.headers) { if(header.install.do_install) { std::filesystem::path target_file = prefix; target_file /= "include"; if(!header.install.path.empty()) { target_file /= header.install.path; } target_file /= std::filesystem::path(header.file).filename(); if(destdir_env) { if(target_file.is_absolute()) { target_file = destdir / target_file.relative_path(); } else { target_file = destdir / target_file; } } std::cout << "Include/header install " << header.file << " -> " << target_file.string() << "\n"; } } } } return 0; }