405 lines
13 KiB
C++
405 lines
13 KiB
C++
#include <string>
|
|
#include <iostream>
|
|
#include <fstream>
|
|
#include <filesystem>
|
|
#include <vector>
|
|
#include <chrono>
|
|
#include <algorithm>
|
|
|
|
#include <string.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 = "";
|
|
#ifdef _WIN32
|
|
std::string folder_char = "\\";
|
|
#else
|
|
std::string folder_char = "/";
|
|
#endif
|
|
|
|
std::vector<std::string> exclude;
|
|
std::vector<std::string> keys = {"libs", "cppflags", "executable", "sourcepath", "buildpath", "exclude"};
|
|
enum tokens {LIBS, CPPFLAGS, EXECUTABLE, SOURCEPATH, BUILDPATH, EXCLUDE};
|
|
|
|
bool must_link = false;
|
|
|
|
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);
|
|
|
|
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);
|
|
}
|
|
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::vector<std::string> getIncludes(const std::string &filename) {
|
|
std::ifstream f(filename);
|
|
if (!f) return {};
|
|
|
|
std::vector<std::string> includes;
|
|
std::string line;
|
|
bool inBlockComment = false;
|
|
|
|
while (std::getline(f, line)) {
|
|
std::string processed;
|
|
processed.reserve(line.size());
|
|
|
|
for (size_t i = 0; i < line.size(); ++i) {
|
|
// Detectar inicio de comentario de bloque
|
|
if (!inBlockComment && i + 1 < line.size() && line[i] == '/' && line[i+1] == '*') {
|
|
inBlockComment = true;
|
|
i++; // saltar '*'
|
|
continue;
|
|
}
|
|
|
|
// Detectar fin de comentario de bloque
|
|
if (inBlockComment && i + 1 < line.size() && line[i] == '*' && line[i+1] == '/') {
|
|
inBlockComment = false;
|
|
i++; // saltar '/'
|
|
continue;
|
|
}
|
|
|
|
// Si estamos dentro de un bloque, ignorar todo
|
|
if (inBlockComment)
|
|
continue;
|
|
|
|
// Detectar comentario de línea
|
|
if (i + 1 < line.size() && line[i] == '/' && line[i+1] == '/') {
|
|
break; // ignorar el resto de la línea
|
|
}
|
|
|
|
processed.push_back(line[i]);
|
|
}
|
|
|
|
// Si la línea quedó vacía tras limpiar comentarios, saltar
|
|
if (processed.find("#include") == std::string::npos)
|
|
continue;
|
|
|
|
// Buscar #include
|
|
size_t pos = processed.find("#include");
|
|
pos += 8;
|
|
|
|
// Saltar espacios
|
|
while (pos < processed.size() && (processed[pos] == ' ' || processed[pos] == '\t'))
|
|
pos++;
|
|
|
|
// Solo includes con comillas
|
|
if (pos >= processed.size() || processed[pos] != '"')
|
|
continue;
|
|
|
|
pos++; // saltar comilla inicial
|
|
size_t start = pos;
|
|
|
|
// Buscar comilla final
|
|
while (pos < processed.size() && processed[pos] != '"')
|
|
pos++;
|
|
|
|
if (pos < processed.size())
|
|
includes.push_back(processed.substr(start, pos - start));
|
|
}
|
|
|
|
return includes;
|
|
}
|
|
|
|
bool HeadersNeedRecompile(std::string file, std::filesystem::file_time_type object_file_write_time) {
|
|
auto include_files = getIncludes(file);
|
|
for (auto include : include_files) {
|
|
std::filesystem::path fullpath(file);
|
|
auto path_without_filename = fullpath.remove_filename();
|
|
std::string src_path = path_without_filename.string();
|
|
|
|
std::string include_file = src_path + include;
|
|
bool found = std::filesystem::exists(include_file);
|
|
if (!found) {
|
|
for (auto path : source_paths) {
|
|
include_file = path + "/" + include;
|
|
if (std::filesystem::exists(include_file)) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (!found) {
|
|
std::cout << "WARNING: Include file '" << include << "' not found in '" << file << "'." << std::endl;
|
|
} else {
|
|
auto include_file_write_time = std::filesystem::last_write_time(include_file);
|
|
if (include_file_write_time > object_file_write_time) {
|
|
return true;
|
|
} else {
|
|
if (HeadersNeedRecompile(include_file, object_file_write_time)) return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool MustRecompile(std::string source_file) {
|
|
std::string object_file = build_path + folder_char + getFileNameWithoutExtension(source_file)+".o";
|
|
|
|
// si el objecte no existeix, fa falta recompilar
|
|
if (!std::filesystem::exists(object_file)) {
|
|
return true;
|
|
} else {
|
|
// Si sí que existeix, agafem la data de modificació
|
|
auto object_file_write_time = std::filesystem::last_write_time(object_file);
|
|
|
|
// Si la data de modificació del cpp es major que la del objecte, fa falta recompilar
|
|
auto source_file_write_time = std::filesystem::last_write_time(source_file);
|
|
if (source_file_write_time > object_file_write_time) {
|
|
return true;
|
|
} else {
|
|
if (HeadersNeedRecompile(source_file, object_file_write_time)) return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void MaybeRecompile(std::string source_file) {
|
|
if (MustRecompile(source_file)) {
|
|
std::string object_file = build_path + folder_char + getFileNameWithoutExtension(source_file)+".o";
|
|
must_link = true;
|
|
std::string command = "g++ " + source_file + " " + cppflags + " -c -o " + object_file;
|
|
std::cout << command << std::endl;
|
|
|
|
std::chrono::steady_clock::time_point begin = std::chrono::steady_clock::now();
|
|
int result = system(command.c_str());
|
|
std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now();
|
|
|
|
float t = float(std::chrono::duration_cast<std::chrono::microseconds>(end - begin).count())/1000000;
|
|
std::cout << "(" << t << " seconds)" << std::endl;
|
|
|
|
if (result != 0) {
|
|
std::cout << "Compilation failed! Aborting..." << std::endl;
|
|
exit(1);
|
|
}
|
|
} else {
|
|
//std::cout << object_file << " està actualitzat" << std::endl;
|
|
}
|
|
}
|
|
|
|
void processCommand(std::string arg) {
|
|
// Do nothing for now
|
|
}
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
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::chrono::steady_clock::time_point begin_all = std::chrono::steady_clock::now();
|
|
|
|
if (!std::filesystem::is_directory(build_path)) std::filesystem::create_directory(build_path);
|
|
source_paths = split(source_path);
|
|
|
|
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") {
|
|
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())) {
|
|
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())) {
|
|
MaybeRecompile(source_file);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (must_link) {
|
|
std::string command = "g++ " + build_path + folder_char + "*.o " + libs + " -o " + executable;
|
|
std::cout << command << std::endl;
|
|
if (system(command.c_str()) != 0) {
|
|
std::cout << "ABORTED!" << std::endl;
|
|
exit(1);
|
|
}
|
|
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;
|
|
}
|
|
} |