diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..36f971e --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +bin/* diff --git a/.ycm_extra_conf.py b/.ycm_extra_conf.py new file mode 100644 index 0000000..b38cf07 --- /dev/null +++ b/.ycm_extra_conf.py @@ -0,0 +1,172 @@ +import os +import os.path +import fnmatch +import logging +import ycm_core +import re + +BASE_FLAGS = [ + '-Wall', + '-Wuninitialized', + '-Wextra', + '-Wno-long-long', + '-Wno-variadic-macros', + '-fexceptions', + '-ferror-limit=10000', + '-DNDEBUG', + '-std=c++17', + '-xc++', + '-I/usr/lib/', + '-I/usr/include/', + ] + +SOURCE_EXTENSIONS = [ + '.cpp', + ] + +SOURCE_DIRECTORIES = [ + 'src' + ] + +HEADER_EXTENSIONS = [ + '.h', + ] + +HEADER_DIRECTORIES = [ + ] + +def IsHeaderFile(filename): + extension = os.path.splitext(filename)[1] + return extension in HEADER_EXTENSIONS + +def GetCompilationInfoForFile(database, filename): + if IsHeaderFile(filename): + basename = os.path.splitext(filename)[0] + for extension in SOURCE_EXTENSIONS: + # Get info from the source files by replacing the extension. + replacement_file = basename + extension + if os.path.exists(replacement_file): + compilation_info = database.GetCompilationInfoForFile(replacement_file) + if compilation_info.compiler_flags_: + return compilation_info + # If that wasn't successful, try replacing possible header directory with possible source directories. + for header_dir in HEADER_DIRECTORIES: + for source_dir in SOURCE_DIRECTORIES: + src_file = replacement_file.replace(header_dir, source_dir) + if os.path.exists(src_file): + compilation_info = database.GetCompilationInfoForFile(src_file) + if compilation_info.compiler_flags_: + return compilation_info + return None + return database.GetCompilationInfoForFile(filename) + +def FindNearest(path, target, build_folder): + candidate = os.path.join(path, target) + if(os.path.isfile(candidate) or os.path.isdir(candidate)): + logging.info("Found nearest " + target + " at " + candidate) + return candidate; + + parent = os.path.dirname(os.path.abspath(path)); + if(parent == path): + raise RuntimeError("Could not find " + target); + + if(build_folder): + candidate = os.path.join(parent, build_folder, target) + if(os.path.isfile(candidate) or os.path.isdir(candidate)): + logging.info("Found nearest " + target + " in build folder at " + candidate) + return candidate; + + return FindNearest(parent, target, build_folder) + +def MakeRelativePathsInFlagsAbsolute(flags, working_directory): + if not working_directory: + return list(flags) + new_flags = [] + make_next_absolute = False + path_flags = [ '-isystem', '-I', '-iquote', '--sysroot=' ] + for flag in flags: + new_flag = flag + + if make_next_absolute: + make_next_absolute = False + if not flag.startswith('/'): + new_flag = os.path.join(working_directory, flag) + + for path_flag in path_flags: + if flag == path_flag: + make_next_absolute = True + break + + if flag.startswith(path_flag): + path = flag[ len(path_flag): ] + new_flag = path_flag + os.path.join(working_directory, path) + break + + if new_flag: + new_flags.append(new_flag) + return new_flags + + +def FlagsForClangComplete(root): + try: + clang_complete_path = FindNearest(root, '.clang_complete') + clang_complete_flags = open(clang_complete_path, 'r').read().splitlines() + return clang_complete_flags + except: + return None + +def FlagsForInclude(root): + try: + include_path = FindNearest(root, 'include') + flags = [] + for dirroot, dirnames, filenames in os.walk(include_path): + for dir_path in dirnames: + real_path = os.path.join(dirroot, dir_path) + flags = flags + ["-I" + real_path] + return flags + except: + return None + +def FlagsForCompilationDatabase(root, filename): + try: + # Last argument of next function is the name of the build folder for + # out of source projects + compilation_db_path = FindNearest(root, 'compile_commands.json', 'build') + compilation_db_dir = os.path.dirname(compilation_db_path) + logging.info("Set compilation database directory to " + compilation_db_dir) + compilation_db = ycm_core.CompilationDatabase(compilation_db_dir) + if not compilation_db: + logging.info("Compilation database file found but unable to load") + return None + compilation_info = GetCompilationInfoForFile(compilation_db, filename) + if not compilation_info: + logging.info("No compilation info for " + filename + " in compilation database") + return None + return MakeRelativePathsInFlagsAbsolute( + compilation_info.compiler_flags_, + compilation_info.compiler_working_dir_) + except: + return None + +def FlagsForFile(filename): + root = os.path.realpath(filename); + compilation_db_flags = FlagsForCompilationDatabase(root, filename) + confPath = os.path.dirname(os.path.abspath(__file__)) + + for dir in HEADER_DIRECTORIES: + BASE_FLAGS.append('-I'+confPath+'/'+dir) + + if compilation_db_flags: + final_flags = compilation_db_flags + else: + final_flags = BASE_FLAGS + clang_flags = FlagsForClangComplete(root) + if clang_flags: + final_flags = final_flags + clang_flags + include_flags = FlagsForInclude(root) + if include_flags: + final_flags = final_flags + include_flags + return { + 'flags': final_flags, + 'do_cache': True + } diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..3f618d9 --- /dev/null +++ b/Makefile @@ -0,0 +1,34 @@ +CC=@g++ +CO=@g++ -o +BIN=bin/ +OBJPATH=$(BIN)intermediates +INCLUDES= +OBJECTS=$(OBJPATH)/ConfigFile.o $(OBJPATH)/IncludeDeps.o $(OBJPATH)/Makefile.o $(OBJPATH)/main.o +CFLAGS=$(INCLUDES) -std=c++17 -c -w -g3 -D_DEBUG +LIBS= +OUTPUT=$(BIN)makegen +all: $(OUTPUT) + $(info ------------------------) + $(info ---- Done Compiling ----) + $(info ------------------------) +rebuid: clean all +clean: + $(info Removing intermediates) + rm -rf $(OBJPATH)/*.o +$(OUTPUT): $(OBJECTS) + $(info Generating output file) + $(CO) $(OUTPUT) $(OBJECTS) $(LIBS) +install: all + cp $(OUTPUT) /usr/bin/makegen +$(OBJPATH)/ConfigFile.o : src/ConfigFile.cpp src/ConfigFile.h src/Logging.h + $(info ---- $<) + $(CC) $(CFLAGS) -o $@ $< +$(OBJPATH)/IncludeDeps.o : src/IncludeDeps.cpp src/IncludeDeps.h + $(info ---- $<) + $(CC) $(CFLAGS) -o $@ $< +$(OBJPATH)/Makefile.o : src/Makefile.cpp src/IncludeDeps.h src/Logging.h src/Makefile.h src/ConfigFile.h + $(info ---- $<) + $(CC) $(CFLAGS) -o $@ $< +$(OBJPATH)/main.o : src/main.cpp src/ConfigFile.h src/IncludeDeps.h src/Logging.h src/Makefile.h + $(info ---- $<) + $(CC) $(CFLAGS) -o $@ $< diff --git a/makegen.conf b/makegen.conf new file mode 100644 index 0000000..f1ca93a --- /dev/null +++ b/makegen.conf @@ -0,0 +1,8 @@ +#srcdirs +src/ +#outputdir +bin/ +#outputname +makegen +#defines +_DEBUG diff --git a/src/ConfigFile.cpp b/src/ConfigFile.cpp new file mode 100644 index 0000000..413d63b --- /dev/null +++ b/src/ConfigFile.cpp @@ -0,0 +1,98 @@ +#include "ConfigFile.h" + +#include +#include "Logging.h" + +#define FLAG_NONE 0 +#define FLAG_VECTOR 1 +#define FLAG_STRING 2 +#define FLAG_BOOL 3 + +ConfigFile::ConfigFile() + : outputdir("bin"), outputname("out.a"),executable(true) +{ + +} + +ConfigFile ConfigFile::Load() +{ + ConfigFile conf; + unsigned int loadFlag = 0; + + std::vector* vec; + std::string* s; + bool* b; + + std::ifstream file("makegen.conf"); + std::string line; + + while(std::getline(file,line)) + { + if(line[0]=='#') + { + if(line == "#libs") + { + vec = &conf.libs; + loadFlag = FLAG_VECTOR; + } + else if(line == "#libdirs") + { + vec = &conf.libdirs; + loadFlag = FLAG_VECTOR; + } + else if(line == "#includedirs") + { + vec = &conf.includedirs; + loadFlag = FLAG_VECTOR; + } + else if(line == "#srcdirs") + { + vec = &conf.srcdirs; + loadFlag = FLAG_VECTOR; + } + else if(line == "#defines") + { + vec = &conf.defines; + loadFlag = FLAG_VECTOR; + } + else if(line == "#outputdir") + { + s = &conf.outputdir; + loadFlag = FLAG_STRING; + } + else if(line == "#outputname") + { + s = &conf.outputname; + loadFlag = FLAG_STRING; + } + else if(line == "#executable") + { + b = &conf.executable; + loadFlag = FLAG_BOOL; + } + else + { + LOG_ERROR("Invalid flag"); + } + } + else + { + if(loadFlag == FLAG_STRING) + { + *s = line; + } + else if(loadFlag == FLAG_VECTOR) + { + vec->push_back(line); + } + else if(loadFlag == FLAG_BOOL) + { + if(line == "true") + *b = true; + else + *b = false; + } + } + } + return conf; +} diff --git a/src/ConfigFile.h b/src/ConfigFile.h new file mode 100644 index 0000000..2adbdea --- /dev/null +++ b/src/ConfigFile.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include + +class ConfigFile +{ + public: + std::vector libs; + std::vector libdirs; + std::vector includedirs; + std::vector srcdirs; + std::vector defines; + std::string outputdir; + std::string outputname; + bool executable; + public: + ConfigFile(); + void Save() const; + static ConfigFile Gen(); + static ConfigFile Load(); +}; diff --git a/src/IncludeDeps.cpp b/src/IncludeDeps.cpp new file mode 100644 index 0000000..3cd9d04 --- /dev/null +++ b/src/IncludeDeps.cpp @@ -0,0 +1,57 @@ +#include "IncludeDeps.h" + +std::set IncludeDeps::printSet; +int IncludeDeps::printCounter = 0; +IncludeDeps::IncludeDeps(const std::string& filename, const std::string& dir, const std::map& files, std::map& allDeps) + : filepath(dir+filename) + { + if(filename[filename.length() - 1] =='h') + { + allDeps.emplace(filepath, this); + } + std::ifstream file(filepath); + std::string line; + while(std::getline(file,line)) + { + size_t pos = line.find("#include"); + if(pos != std::string::npos) + { + std::string include = GetIncludeFile(line, pos, filename); + auto it = files.find(include); + if(it != files.end()) + { + auto itD = allDeps.find(it->second + it->first); + if(itD == allDeps.end()) + { + IncludeDeps* inc = new IncludeDeps(it->first, it->second,files,allDeps); + dependencies.emplace(it->second+ it->first, inc); + }else{ + dependencies.emplace(itD->first, itD->second); + } + } + } + } + } + + std::string IncludeDeps::GetIncludeFile(const std::string& line, size_t pos, const std::string& filename) + { + size_t bracket = line.find('<',pos); + if(bracket == std::string::npos) + { + bracket = line.find('\"',pos); + if(bracket == std::string::npos) + { + return ""; + } + size_t slash = filename.find_last_of("/"); + + std::string include = line.substr(bracket+1, line.find('\"',bracket+1)-bracket-1); + if(slash == std::string::npos) + slash = -1; + return filename.substr(0,slash+1)+include; + } + else + { + return line.substr(bracket+1, line.find('>',bracket+1)-bracket-1); + } + } diff --git a/src/IncludeDeps.h b/src/IncludeDeps.h new file mode 100644 index 0000000..9e11c4b --- /dev/null +++ b/src/IncludeDeps.h @@ -0,0 +1,60 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +struct CompareIncludeDeps; + +class IncludeDeps +{ + public: + std::map dependencies; + std::string filepath; + static std::set printSet; + static int printCounter; + + IncludeDeps(const std::string& filename, const std::string& dir, const std::map& files, std::map& allDeps); + + std::string GetIncludeFile(const std::string& line, size_t pos, const std::string& filename); + + friend std::ostream& operator<<(std::ostream& stream, const IncludeDeps& deps) + { + if(printSet.find(deps.filepath) != printSet.end()) + return stream; + printCounter++; + printSet.emplace(deps.filepath); + stream << deps.filepath; + for(auto it = deps.dependencies.begin();it!=deps.dependencies.end();++it) + { + stream << " " << *(it->second); + } + printCounter--; + if(printCounter == 0) + printSet.clear(); + return stream; + } + IncludeDeps(const std::string& filename, const std::string& dir) + : filepath(dir+filename){} + +}; + +struct CompareIncludeDeps +{ + using is_transparent = void; + bool operator()(const IncludeDeps* d1, const IncludeDeps* d2) const + { + return d1->filepath < d2->filepath; + } + bool operator()(const IncludeDeps* d, const std::string& filepath) const + { + return d->filepath < filepath; + } + bool operator()(const std::string& filepath, const IncludeDeps* d) const + { + return filepath < d->filepath; + } +}; diff --git a/src/Logging.h b/src/Logging.h new file mode 100644 index 0000000..114a417 --- /dev/null +++ b/src/Logging.h @@ -0,0 +1,21 @@ +#pragma once + +#include + +#define LOG_INFO(...) Log(__VA_ARGS__); std::cout << std::endl +#define LOG_WARNING(...) Log(__VA_ARGS__); std::cout << std::endl +#define LOG_ERROR(...) Log(__VA_ARGS__); std::cout << std::endl + +template +void Log(const T& var) +{ + std::cout << var; +} + +template +void Log(const T& var, const Ts& ...vars) +{ + Log(var); + Log(vars...); +} + diff --git a/src/Makefile.cpp b/src/Makefile.cpp new file mode 100644 index 0000000..2180e87 --- /dev/null +++ b/src/Makefile.cpp @@ -0,0 +1,135 @@ +#include "Makefile.h" +#include +#include +#include +#include +#include +#include "Logging.h" +#include "IncludeDeps.h" + +void Makefile::GetAllFiles(const std::string& folder, std::vector& files) +{ + DIR* dp; + struct dirent *dirp; + if((dp = opendir(folder.c_str())) == NULL){ + LOG_ERROR(errno); + return; + } + while((dirp = readdir(dp)) != NULL) + { + if(dirp->d_type == DT_DIR) + { + if(strcmp(dirp->d_name,".") == 0) + continue; + if(strcmp(dirp->d_name,"..") == 0) + continue; + GetAllFiles(folder+dirp->d_name+"/", files); + } + else + { + files.push_back(folder+dirp->d_name); + } + } + closedir(dp); +} + +void Makefile::Save(const ConfigFile& conf) +{ + std::map hFiles; + std::map cppFiles; + PreSave(conf,hFiles,cppFiles); + + std::ofstream outputFile("Makefile"); + outputFile << "CC=@g++" << std::endl; + if(!conf.executable) + outputFile << "CO=@ar rs" << std::endl; + else + outputFile << "CO=@g++ -o" << std::endl; + + outputFile << "BIN=" << conf.outputdir << std::endl; + outputFile << "OBJPATH=$(BIN)intermediates" << std::endl; + outputFile << "INCLUDES="; + for(auto it = conf.includedirs.begin();it!=conf.includedirs.end();++it) + { + outputFile << "-I./" << *it << " "; + } + outputFile << std::endl; + outputFile << "OBJECTS="; + for(auto it = cppFiles.begin();it!=cppFiles.end();++it) + { + size_t extensionPos = it->first.find_last_of("."); + size_t slash = it->first.find_last_of("/")+1; + outputFile << "$(OBJPATH)/" << it->first.substr(slash, extensionPos - slash) << ".o "; + } + outputFile << std::endl; + outputFile << "CFLAGS=$(INCLUDES) -std=c++17 -c -w -g3 "; + for(auto it = conf.defines.begin();it!=conf.defines.end();++it) + { + outputFile << "-D" << *it << " "; + } + outputFile << std::endl; + outputFile << "LIBS="; + for(auto it = conf.libs.begin();it!=conf.libs.end();++it) + { + outputFile << "-l:" << *it << " "; + } + outputFile << std::endl; + outputFile << "OUTPUT=$(BIN)" << conf.outputname << std::endl; + outputFile << "all: $(OUTPUT)" << std::endl; + outputFile << "\t$(info ------------------------)" << std::endl; + outputFile << "\t$(info ---- Done Compiling ----)" << std::endl; + outputFile << "\t$(info ------------------------)" << std::endl; + outputFile << "rebuid: clean all" << std::endl; + outputFile << "clean:" << std::endl; + outputFile << "\t$(info Removing intermediates)" << std::endl; + outputFile << "\trm -rf $(OBJPATH)/*.o" << std::endl; + outputFile << "$(OUTPUT): $(OBJECTS)" << std::endl; + outputFile << "\t$(info Generating output file)" << std::endl; + outputFile << "\t$(CO) $(OUTPUT) $(OBJECTS) $(LIBS)" << std::endl; + outputFile << "install: all" << std::endl; + outputFile << "\tcp $(OUTPUT) /usr/bin/" << conf.outputname << std::endl; + std::map dependencies; + for(auto it = cppFiles.begin(); it!=cppFiles.end();++it) + { + auto itD = dependencies.find(it->first+it->second); + if(itD == dependencies.end()) + { + IncludeDeps* deps = new IncludeDeps(it->first, it->second,hFiles,dependencies); + size_t extensionPos = it->first.find_last_of("."); + size_t slash = it->first.find_last_of("/")+1; + std::string oFile = it->first.substr(slash, extensionPos - slash)+".o "; + outputFile << "$(OBJPATH)/" << oFile << ": " << *deps << std::endl; + outputFile << "\t$(info ---- $<)" << std::endl; + outputFile << "\t$(CC) $(CFLAGS) -o $@ $<" << std::endl; + //std::cout << *deps << std::endl; + } + } +} + +void Makefile::PreSave(const ConfigFile& conf, std::map& hFiles, + std::map& cppFiles) +{ + for(auto itSrc = conf.srcdirs.begin();itSrc != conf.srcdirs.end();++itSrc) + { + std::vector files; + GetAllFiles(*itSrc,files); + // include paramenter with the path of the file + // For example src/graphics/Window.h -> graphics/Window.h if src is a src folder + for(auto it = files.begin(); it!=files.end();++it) + { + size_t extensionPos = it->find_last_of("."); + if(extensionPos != std::string::npos) + { + if(it->substr(extensionPos+1) == "cpp") + { + cppFiles.emplace(it->substr(itSrc->length()), *itSrc); + } + else + { + hFiles.emplace(it->substr(itSrc->length()), *itSrc); + } + } + } + } + +} diff --git a/src/Makefile.h b/src/Makefile.h new file mode 100644 index 0000000..7c5ba9a --- /dev/null +++ b/src/Makefile.h @@ -0,0 +1,13 @@ +#pragma once + +#include "ConfigFile.h" +#include + +class Makefile +{ + public: + static void Save(const ConfigFile& conf); + private: + static void PreSave(const ConfigFile& conf, std::map& hFiles, std::map& cppFiles); + static void GetAllFiles(const std::string& folder, std::vector& files); +}; diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..9149d8c --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,141 @@ +#include +#include +#include +#include +#include +#include +#include "IncludeDeps.h" +#include "ConfigFile.h" +#include "Makefile.h" +#include "Logging.h" + +#define BIT(x) (1< 1) + { + if(argv[i][0] == '-' && argv[i][1] == '-') + { + std::string flag(argv[i]); + if(flag == "--help") + { + flags |= FLAG_HELP; + } + else if(flag == "--gen") + { + flags |= FLAG_GEN; + } + } + } + } +} + +void InputMultiple(const std::string& inputText, std::vector& ret) +{ + std::string input; + while(true) + { + LOG_INFO(inputText); + std::getline(std::cin, input); + if(input == "") + break; + ret.push_back(input); + } + +} + +void GenConfFile() +{ + std::vector libs; + std::vector libdirs; + std::vector includedirs; + std::vector srcdirs; + std::string outputDir; + InputMultiple("Enter library:", libs); + InputMultiple("Enter library directory:", libdirs); + InputMultiple("Enter include directory:", includedirs); + InputMultiple("Enter source directories:", srcdirs); + LOG_INFO("Enter output directory (default: bin):"); + std::getline(std::cin, outputDir); + if(outputDir == "") + outputDir = "bin"; + + std::ofstream file("makegen.conf"); + file << "#libs" << std::endl; + for(auto it = libs.begin();it!=libs.end();++it) + { + file << *it << std::endl; + } + file << "#libdirs" << std::endl; + for(auto it = libdirs.begin();it!=libdirs.end();++it) + { + file << *it << std::endl; + } + file << "#includedirs" << std::endl; + for(auto it = includedirs.begin();it!=includedirs.end();++it) + { + file << *it << std::endl; + } + file << "#srcdirs" << std::endl; + for(auto it = srcdirs.begin();it!=srcdirs.end();++it) + { + file << *it << std::endl; + } + file << "#outputdir" << std::endl; + file << outputDir << std::endl; + file.close(); +} + +int main(int argc, char** argv) +{ + ReadFlags(argc,argv); + if((flags & FLAG_HELP)) + { + LOG_INFO("Usage: makegen [options]"); + LOG_INFO(" Options:"); + LOG_INFO(" -h, --help\tDisplays this information"); + LOG_INFO(" -g, --gen\tGenerate a config file for the project"); + return 0; + } + if(flags & FLAG_GEN) + { + GenConfFile(); + return 0; + } + LOG_INFO("Generating Makefile..."); + GenMakefile(); + LOG_INFO("Running Makefile..."); + for(int i = 1;i