559 lines
16 KiB
C++
559 lines
16 KiB
C++
#include <string>
|
|
#include <iostream>
|
|
#include <fstream>
|
|
#include <sstream>
|
|
#include <filesystem>
|
|
#include <vector>
|
|
#include <unordered_map>
|
|
#include <chrono>
|
|
#include <algorithm>
|
|
#include <string.h>
|
|
#include <thread>
|
|
#include <mutex>
|
|
#include <queue>
|
|
#include <condition_variable>
|
|
#include <atomic>
|
|
|
|
#include "version.h"
|
|
|
|
std::string libs = "";
|
|
std::string cppflags = "";
|
|
std::string executable = "out";
|
|
std::string source_path = "";
|
|
std::vector<std::string> source_paths;
|
|
std::string build_path = "";
|
|
std::string compiler = "g++";
|
|
#ifdef _WIN32
|
|
char folder_char = '\\';
|
|
#else
|
|
char folder_char = '/';
|
|
#endif
|
|
std::string loaded_section = "";
|
|
|
|
std::vector<std::string> exclude;
|
|
std::vector<std::string> keys = {"libs", "cppflags", "executable", "sourcepath", "buildpath", "exclude", "compiler"};
|
|
enum tokens {LIBS, CPPFLAGS, EXECUTABLE, SOURCEPATH, BUILDPATH, EXCLUDE, COMPILER};
|
|
|
|
bool must_link = false;
|
|
bool must_recompile_all = false;
|
|
|
|
std::mutex progress_mtx;
|
|
|
|
struct FileInfo {
|
|
std::string filename;
|
|
};
|
|
|
|
std::vector<FileInfo> cpp_files;
|
|
std::unordered_map<std::string, int> cpp_index;
|
|
|
|
bool has_cpp(const std::string& name) {
|
|
return cpp_index.find(name) != cpp_index.end();
|
|
}
|
|
int get_cpp_index(const std::string& name) {
|
|
auto it = cpp_index.find(name);
|
|
return (it != cpp_index.end()) ? it->second : -1;
|
|
}
|
|
int add_cpp(const std::string& name) {
|
|
auto it = cpp_index.find(name);
|
|
if (it != cpp_index.end())
|
|
return it->second;
|
|
|
|
int index = cpp_files.size();
|
|
cpp_files.push_back({name});
|
|
cpp_index[name] = index;
|
|
return index;
|
|
}
|
|
|
|
bool contains(const std::vector<std::string>& v, const std::string& s) { return std::find(v.begin(), v.end(), s) != v.end(); }
|
|
|
|
std::vector<std::string> split(std::string str)
|
|
{
|
|
std::vector<std::string> strings;
|
|
char tmp[100];
|
|
int tmp_p = 0, str_p = 0;
|
|
while (str[str_p]!=0)
|
|
{
|
|
if (str[str_p]!=32)
|
|
tmp[tmp_p++] = str[str_p++];
|
|
else
|
|
{
|
|
tmp[tmp_p]=0;
|
|
strings.push_back(tmp);
|
|
tmp_p=0; while (str[str_p]==32) str_p++;
|
|
}
|
|
}
|
|
tmp[tmp_p]=0;
|
|
strings.push_back(tmp);
|
|
|
|
return strings;
|
|
}
|
|
|
|
char *getBufferFromFile(const char* filename)
|
|
{
|
|
FILE *f = fopen(filename, "rb");
|
|
if (!f) {
|
|
perror("Error opening file");
|
|
exit(-1);
|
|
}
|
|
fseek(f, 0, SEEK_END);
|
|
long size = ftell(f);
|
|
fseek(f, 0, SEEK_SET);
|
|
char *buffer = (char*)malloc(size+1);
|
|
fread(buffer, size, 1, f);
|
|
buffer[size] = 0;
|
|
fclose(f);
|
|
return buffer;
|
|
}
|
|
|
|
static inline void trim(std::string &s) {
|
|
while (!s.empty() && isspace(s.front())) s.erase(s.begin());
|
|
while (!s.empty() && isspace(s.back())) s.pop_back();
|
|
}
|
|
|
|
static inline std::string trim_copy(std::string s) {
|
|
trim(s);
|
|
return s;
|
|
}
|
|
|
|
bool loadLagueirtoFile(const std::string §ion_to_load)
|
|
{
|
|
std::ifstream f("lagueirtofile");
|
|
if (!f) { std::cerr << "Cannot open lagueirtofile\n"; exit(1); }
|
|
|
|
std::string line;
|
|
std::string current_section {"@none@"};
|
|
bool found_any_section = false;
|
|
bool found_default = false;
|
|
bool active = false;
|
|
|
|
while (std::getline(f, line))
|
|
{
|
|
// Quitar espacios al inicio y final
|
|
trim(line);
|
|
|
|
if (line.empty() || line[0] == '#')
|
|
continue;
|
|
|
|
// Detectar sección
|
|
if (line.front() == '[')
|
|
{
|
|
auto end = line.find(']');
|
|
if (end == std::string::npos) continue;
|
|
|
|
current_section = line.substr(1, end - 1);
|
|
// Detectar si es default
|
|
std::string rest = trim_copy(line.substr(end + 1));
|
|
found_any_section = true;
|
|
if (rest == "default") found_default = true;
|
|
if (section_to_load.empty() && rest == "default") active = true;
|
|
else active = (current_section == section_to_load);
|
|
if (active) {
|
|
loaded_section = current_section;
|
|
std::cout << " > " << current_section << " < " << std::endl;
|
|
} else {
|
|
std::cout << " - " << current_section << std::endl;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// Clave = valor
|
|
auto pos = line.find('=');
|
|
if (pos == std::string::npos)
|
|
continue;
|
|
|
|
std::string key = trim_copy(line.substr(0, pos));
|
|
std::string value = trim_copy(line.substr(pos + 1));
|
|
|
|
if (!active && current_section != "@none@") continue;
|
|
|
|
if (key == "libs") libs = value;
|
|
else if (key == "cppflags") cppflags = value;
|
|
else if (key == "executable") executable = value;
|
|
else if (key == "sourcepath") source_path = value;
|
|
else if (key == "buildpath") build_path = value;
|
|
else if (key == "exclude") exclude = split(value);
|
|
else if (key == "compiler") compiler = value;
|
|
}
|
|
return !(found_any_section && section_to_load.empty() && !found_default);
|
|
}
|
|
|
|
std::string getFileExtension(std::string path)
|
|
{
|
|
std::size_t dotpos = path.find_last_of(".");
|
|
return dotpos <= 0 ? "" : path.substr(dotpos + 1);
|
|
}
|
|
|
|
std::string getFileNameWithoutExtension(std::string path)
|
|
{
|
|
std::size_t slashpos = path.find_last_of(folder_char);
|
|
std::string filename = path.substr(slashpos+1);
|
|
|
|
std::size_t dotpos = filename.find_last_of(".");
|
|
if (dotpos <= 0) return filename;
|
|
|
|
return filename.substr(0, dotpos);
|
|
}
|
|
|
|
|
|
const bool textFound(char *buffer, const char *text)
|
|
{
|
|
const int strsize = strlen(text);
|
|
int i = 0;
|
|
bool equal=true;
|
|
while (equal && i<strsize)
|
|
{
|
|
if (buffer[i]!=text[i]) equal = false;
|
|
++i;
|
|
}
|
|
return equal;
|
|
}
|
|
|
|
std::string generate_object_file_name(std::string filename) {
|
|
std::filesystem::path cwd = std::filesystem::weakly_canonical(std::filesystem::current_path());
|
|
std::filesystem::path target = std::filesystem::weakly_canonical(filename);
|
|
std::string out = std::filesystem::relative(target, cwd).replace_extension("o").generic_string();
|
|
for (char& c : out) if (c == '/') c = '.';
|
|
return build_path + folder_char + out;
|
|
}
|
|
|
|
|
|
void Recompile(std::string source_file) {
|
|
std::string object_file = generate_object_file_name(source_file);
|
|
must_link = true;
|
|
std::string command = compiler + " " + source_file + " " + cppflags + " -MMD -MP -c -o " + object_file;
|
|
//std::cout << command << std::endl;
|
|
|
|
if (system(command.c_str()) != 0) {
|
|
std::cout << "Compilation failed! Aborting..." << std::endl;
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
std::string generate_dependency_file_name(
|
|
const std::string& source_file)
|
|
{
|
|
std::string name = generate_object_file_name(source_file);
|
|
|
|
name.replace(name.size() - 1, 1, "d");
|
|
|
|
return name;
|
|
}
|
|
|
|
std::vector<std::string> parse_dependency_file(const std::string& filename)
|
|
{
|
|
std::ifstream file(filename);
|
|
|
|
if (!file) return {};
|
|
|
|
std::string merged;
|
|
std::string line;
|
|
|
|
while (std::getline(file, line))
|
|
{
|
|
if (!line.empty() && line.back() == '\\') {
|
|
line.pop_back();
|
|
merged += line;
|
|
merged += ' ';
|
|
} else {
|
|
merged += line;
|
|
merged += '\n';
|
|
}
|
|
}
|
|
|
|
auto colon = merged.find(':');
|
|
if (colon == std::string::npos) return {};
|
|
|
|
std::string deps = merged.substr(colon + 1);
|
|
auto newline = deps.find('\n');
|
|
if (newline != std::string::npos) deps.resize(newline);
|
|
|
|
std::vector<std::string> result;
|
|
std::stringstream ss(deps);
|
|
std::string dep;
|
|
while (ss >> dep) result.push_back(dep);
|
|
return result;
|
|
}
|
|
|
|
bool DependenciesNeedRecompile(
|
|
const std::string& object_file,
|
|
const std::string& dependency_file)
|
|
{
|
|
if (!std::filesystem::exists(object_file)) return true;
|
|
if (!std::filesystem::exists(dependency_file)) return true;
|
|
|
|
auto object_time = std::filesystem::last_write_time(object_file);
|
|
|
|
auto dependencies = parse_dependency_file( dependency_file);
|
|
|
|
for (auto dep : dependencies) {
|
|
try {
|
|
dep = std::filesystem::weakly_canonical(dep).string();
|
|
} catch (...) {
|
|
return true; // dependency disappeared
|
|
}
|
|
if (!std::filesystem::exists(dep)) return true;
|
|
if (std::filesystem::last_write_time(dep) > object_time) return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool MustRecompile(const FileInfo& file)
|
|
{
|
|
std::string object_file = generate_object_file_name(file.filename);
|
|
std::string dependency_file = generate_dependency_file_name(file.filename);
|
|
return DependenciesNeedRecompile( object_file, dependency_file);
|
|
}
|
|
|
|
void process_cpp(std::string& file)
|
|
{
|
|
std::string absolute_path = std::filesystem::weakly_canonical(file).string();
|
|
//std::cout << absolute_path << std::endl;
|
|
add_cpp(absolute_path);
|
|
}
|
|
|
|
void progress_bar(int percent) {
|
|
const int width = 50; // ancho de la barra
|
|
int filled = (percent * width) / 100;
|
|
|
|
printf("\r[");
|
|
for (int i = 0; i < width; i++) {
|
|
if (i < filled) printf("#");
|
|
else printf(" ");
|
|
}
|
|
printf("] %3d%%", percent);
|
|
|
|
fflush(stdout);
|
|
}
|
|
|
|
void processCommand(std::string arg) {
|
|
std::cout << "command: '" << arg << "'" << std::endl;
|
|
if (arg == "-r") must_recompile_all = true;
|
|
}
|
|
|
|
void parallel_compile(int thread_count)
|
|
{
|
|
std::queue<std::string> work;
|
|
std::mutex mtx;
|
|
std::condition_variable cv;
|
|
bool done = false;
|
|
|
|
// Omplim la cua amb els arxius que necessiten recompilarse
|
|
for (auto &file : cpp_files) {
|
|
if (MustRecompile(file)) {
|
|
work.push(file.filename);
|
|
must_link = true;
|
|
}
|
|
}
|
|
|
|
std::atomic<int> completed = 0;
|
|
int total = work.size();
|
|
progress_bar(0);
|
|
|
|
auto worker = [&]() {
|
|
while (true) {
|
|
std::string job;
|
|
|
|
// Extraure treball
|
|
{
|
|
std::unique_lock<std::mutex> lock(mtx);
|
|
cv.wait(lock, [&]() { return !work.empty() || done; });
|
|
|
|
if (work.empty()) return; // ja no hi ha res més que fer
|
|
|
|
job = work.front();
|
|
work.pop();
|
|
}
|
|
|
|
// Compilar
|
|
Recompile(job);
|
|
|
|
{
|
|
std::lock_guard<std::mutex> lock(progress_mtx);
|
|
int done_now = ++completed;
|
|
progress_bar(100 * done_now / total);
|
|
}
|
|
}
|
|
};
|
|
|
|
// Llançar threads
|
|
std::vector<std::thread> threads;
|
|
for (int i = 0; i < thread_count; i++)
|
|
threads.emplace_back(worker);
|
|
|
|
// Notificar als threads
|
|
{
|
|
std::lock_guard<std::mutex> lock(mtx);
|
|
done = true;
|
|
}
|
|
cv.notify_all();
|
|
|
|
// Esperem a que acaben
|
|
for (auto &t : threads)
|
|
t.join();
|
|
|
|
progress_bar(100);
|
|
printf("\n");
|
|
}
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
std::cout << " _ _ _ " << std::endl;
|
|
std::cout << "| | __ _ __ _ _ _ ___(_)_ __| |_ ___ " << std::endl;
|
|
std::cout << "| |/ _` |/ _` | | | |/ _ \\ | '__| __/ _ \\ " << std::endl;
|
|
std::cout << "| | (_| | (_| | |_| | __/ | | | || (_) |" << std::endl;
|
|
std::cout << "|_|\\__,_|\\__, |\\__,_|\\___|_|_| \\__\\___/ " << std::endl;
|
|
std::cout << " |___/ v" << LAGUEIRTO_VERSION << std::endl;
|
|
|
|
std::string configuration_to_use;
|
|
for (int i = 1; i < argc; ++i)
|
|
{
|
|
std::string arg = argv[i];
|
|
if (!arg.empty() && arg[0] == '-') processCommand(arg);
|
|
else configuration_to_use = arg;
|
|
}
|
|
|
|
if (!loadLagueirtoFile(configuration_to_use)) {
|
|
std::cerr << "No default section found.\n";
|
|
exit(1);
|
|
}
|
|
|
|
std::string last_config_file = build_path + folder_char + "last_config";
|
|
if (!std::filesystem::exists(last_config_file)) {
|
|
must_recompile_all = true;
|
|
} else {
|
|
std::ifstream in(last_config_file);
|
|
if (in) {
|
|
std::string word;
|
|
in >> word;
|
|
if (word != loaded_section) must_recompile_all = true;
|
|
}
|
|
}
|
|
|
|
if (must_recompile_all) {
|
|
std::cout << "Doing a full rebuild" << std::endl;
|
|
std::filesystem::remove_all(build_path);
|
|
}
|
|
|
|
if (!std::filesystem::is_directory(build_path)) std::filesystem::create_directory(build_path);
|
|
std::ofstream out(last_config_file);
|
|
if (out) out << loaded_section;
|
|
|
|
std::chrono::steady_clock::time_point begin_all = std::chrono::steady_clock::now();
|
|
|
|
source_paths = split(source_path);
|
|
|
|
// Recopilem tots els arxius cpp i capçaleres
|
|
// ===================================================================
|
|
for (auto &src_path : source_paths) {
|
|
bool recursive = false;
|
|
|
|
if (!src_path.empty() && src_path.back() == '+') {
|
|
recursive = true;
|
|
src_path.pop_back();
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
std::replace(src_path.begin(), src_path.end(), '/', '\\');
|
|
#endif
|
|
|
|
if (!std::filesystem::is_directory(src_path)) {
|
|
if (std::filesystem::is_regular_file(src_path)) {
|
|
std::string ext = getFileExtension(src_path);
|
|
|
|
if (ext == "cpp" || ext == "c") {
|
|
process_cpp(src_path);
|
|
//MaybeRecompile(src_path);
|
|
} else {
|
|
std::cout << "ERROR: '" << src_path << "' is not a .c/.cpp file." << std::endl;
|
|
exit(1);
|
|
}
|
|
} else {
|
|
std::cout << "ERROR: '" << src_path << "' does not exist." << std::endl;
|
|
exit(1);
|
|
}
|
|
} else {
|
|
std::string path = "." + folder_char + src_path;
|
|
|
|
if (recursive) {
|
|
for (const auto &entry : std::filesystem::recursive_directory_iterator(path)) {
|
|
if (!entry.is_regular_file()) continue;
|
|
|
|
std::string source_file = entry.path().string();
|
|
std::string ext = getFileExtension(source_file);
|
|
|
|
if ((ext == "cpp" || ext == "c") && !contains(exclude, entry.path().filename().string())) {
|
|
process_cpp(source_file);
|
|
//MaybeRecompile(source_file);
|
|
}
|
|
}
|
|
} else {
|
|
for (const auto &entry : std::filesystem::directory_iterator(path)) {
|
|
if (!entry.is_regular_file()) continue;
|
|
|
|
std::string source_file = entry.path().string();
|
|
std::string ext = getFileExtension(source_file);
|
|
|
|
if ((ext == "cpp" || ext == "c") && !contains(exclude, entry.path().filename().string())) {
|
|
process_cpp(source_file);
|
|
//MaybeRecompile(source_file);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//print_file_tree(0, cpp_files);
|
|
|
|
int threads = std::thread::hardware_concurrency();
|
|
//printf("threads: %i\n", threads);
|
|
if (threads == 0) threads = 4; // fallback
|
|
|
|
parallel_compile(threads);
|
|
|
|
// int i = 0;
|
|
// int total = cpp_files.size();
|
|
// progress_bar(0);
|
|
// for (auto& file : cpp_files) {
|
|
// if (MustRecompile(file)) Recompile(file.filename);
|
|
// progress_bar(100*float(float(i)/float(total)));
|
|
// i++;
|
|
// }
|
|
// progress_bar(100);
|
|
// std::cout << std::endl;
|
|
|
|
if (must_link) {
|
|
std::string command = compiler + " " + build_path + folder_char + "*.o " + libs + " -o " + executable;
|
|
//std::cout << command << std::endl;
|
|
std::cout << "Linking..." << std::endl; fflush(stdout);
|
|
|
|
int status = system(command.c_str());
|
|
|
|
if (status == -1) {
|
|
std::cerr << "system() failed\n";
|
|
}
|
|
else if (WIFEXITED(status)) {
|
|
int code = WEXITSTATUS(status);
|
|
|
|
std::cout << "exit code = " << code << '\n';
|
|
|
|
if (code != 0) {
|
|
std::cerr << "ABORTED!\n";
|
|
exit(code);
|
|
}
|
|
}
|
|
// int result = system(command.c_str());
|
|
// if (result != 0) {
|
|
// std::cout << "ABORTED!" << std::endl;
|
|
// exit(result);
|
|
// }
|
|
|
|
|
|
std::cout << "DONE!" << std::endl;
|
|
std::chrono::steady_clock::time_point end_all = std::chrono::steady_clock::now();
|
|
float t = float(std::chrono::duration_cast<std::chrono::microseconds>(end_all - begin_all).count())/1000000;
|
|
std::cout << "(" << t << " seconds)" << std::endl;
|
|
} else {
|
|
std::cout << "Everything is up to date. Nothing to do." << std::endl;
|
|
}
|
|
} |