#include "../include/GlobalLib.hpp" #include "../include/FileUtil.hpp" #include "../backend/ninja/Ninja.hpp" #include "../include/Conf.hpp" #include "../include/curl.hpp" #include "../include/Archive.hpp" #include "../include/CmakeModule.hpp" using namespace std; namespace sibs { Result GlobalLib::validatePackageExists(const FileString &globalLibRootDir, const std::string &name) { FileString packageDir = globalLibRootDir + TINYDIR_STRING("/"); #if OS_FAMILY == OS_FAMILY_POSIX packageDir += name; #else packageDir += utf8To16(name); #endif FileType packageDirFileType = getFileType(packageDir.c_str()); switch(packageDirFileType) { case FileType::FILE_NOT_FOUND: { string errMsg = "Global lib dependency not found: "; errMsg += toUtf8(packageDir); return Result::Err(errMsg, DependencyError::DEPENDENCY_NOT_FOUND); } case FileType::REGULAR: { string errMsg = "Corrupt library directory. "; errMsg += toUtf8(packageDir); errMsg += " is a file, expected it to be a directory"; return Result::Err(errMsg); } case FileType::DIRECTORY: { return Result::Ok(true); } default: { return Result::Err("Unexpected error!"); } } } const _tinydir_char_t *sourceFileExtensions[] = { TINYDIR_STRING("c"),TINYDIR_STRING("cc"), TINYDIR_STRING("cpp"), TINYDIR_STRING("cxx") }; bool isSourceFile(tinydir_file *file) { if(!file->is_reg) return false; for(const _tinydir_char_t *sourceFileExtension : sourceFileExtensions) { if(_tinydir_strcmp(sourceFileExtension, file->extension) == 0) return true; } return false; } bool isPathSubPathOf(const _tinydir_char_t *path, const FileString &subPathOf) { return _tinydir_strncmp(path, subPathOf.c_str(), subPathOf.size()) == 0; } Result GlobalLib::getLibs(const std::vector &libs, const SibsConfig &parentConfig, const FileString &globalLibRootDir, LinkerFlagCallbackFunc staticLinkerFlagCallbackFunc, LinkerFlagCallbackFunc dynamicLinkerFlagCallbackFunc, GlobalIncludeDirCallbackFunc globalIncludeDirCallback) { for(const Dependency &globalLibDependency : libs) { printf("Dependency %s is missing from pkg-config, trying global lib\n", globalLibDependency.name.c_str()); Result globalLibLinkerFlagsResult = GlobalLib::getLibsLinkerFlags(parentConfig, globalLibRootDir, globalLibDependency.name, globalLibDependency.version, staticLinkerFlagCallbackFunc, dynamicLinkerFlagCallbackFunc, globalIncludeDirCallback); if(globalLibLinkerFlagsResult.isErr()) { if(globalLibLinkerFlagsResult.getErrorCode() == GlobalLib::DependencyError::DEPENDENCY_NOT_FOUND || globalLibLinkerFlagsResult.getErrorCode() == GlobalLib::DependencyError::DEPENDENCY_VERSION_NO_MATCH) { printf("Dependency not found in global lib, trying to download from github\n"); // TODO: Download several dependencies at the same time by adding them to a list // and then iterate them and download them all using several threads. // All dependecies should be downloaded at the same time, this includes dependencies of dependencies. // If a dependency is missing, fail build BEFORE downloading dependencies and before compiling anything. // You do not want to possibly wait several minutes only for build to fail when there is no compilation error. // TODO: If return error is invalid url, then the message should be converted to // invalid package name/version. A check should be done if it is the name or version // that is invalid. Result downloadDependencyResult = GlobalLib::downloadDependency(globalLibDependency); if(downloadDependencyResult.isErr()) return downloadDependencyResult; globalLibLinkerFlagsResult = GlobalLib::getLibsLinkerFlags(parentConfig, globalLibRootDir, globalLibDependency.name, globalLibDependency.version, staticLinkerFlagCallbackFunc, dynamicLinkerFlagCallbackFunc, globalIncludeDirCallback); if(globalLibLinkerFlagsResult.isErr()) return Result::Err(globalLibLinkerFlagsResult); } else { return Result::Err(globalLibLinkerFlagsResult); } } } return Result::Ok(true); } Result GlobalLib::getLibsLinkerFlags(const SibsConfig &parentConfig, const FileString &globalLibRootDir, const std::string &name, const std::string &version, LinkerFlagCallbackFunc staticLinkerFlagCallbackFunc, LinkerFlagCallbackFunc dynamicLinkerFlagCallbackFunc, GlobalIncludeDirCallbackFunc globalIncludeDirCallback) { Result packageExistsResult = validatePackageExists(globalLibRootDir, name); if (packageExistsResult.isErr()) return packageExistsResult; #if OS_FAMILY == OS_FAMILY_POSIX FileString namePlatformNative = name; FileString versionPlatformNative = version; #else FileString namePlatformNative = utf8To16(name); FileString versionPlatformNative = utf8To16(version); #endif FileString packageDir = globalLibRootDir + TINYDIR_STRING("/"); packageDir += namePlatformNative; // TODO: Instead of checking if version is exact match, check if package has same major version // and same or newer minor version FileString foundVersion; walkDir(packageDir.c_str(), [&foundVersion, &versionPlatformNative](tinydir_file *file) { if(file->is_dir) { //printf("version: %s\n", file->name); if(_tinydir_strcmp(versionPlatformNative.c_str(), file->name) == 0) foundVersion = file->name; } }); if(foundVersion.empty()) return Result::Err("Global lib dependency found, but version doesn't match dependency version", DependencyError::DEPENDENCY_VERSION_NO_MATCH); packageDir += TINYDIR_STRING("/"); packageDir += versionPlatformNative; FileString projectConfFilePath = packageDir; projectConfFilePath += TINYDIR_STRING("/project.conf"); FileType projectConfFileType = getFileType(projectConfFilePath.c_str()); switch(projectConfFileType) { case FileType::FILE_NOT_FOUND: { string errMsg = "Global lib dependency found: "; errMsg += toUtf8(packageDir); errMsg += ", but it's missing a project.conf file"; return Result::Err(errMsg); } case FileType::DIRECTORY: { string errMsg = "Global lib dependency found: "; errMsg += toUtf8(packageDir); errMsg += ", but it's corrupt (Found directory instead of file)"; return Result::Err(errMsg); } } SibsConfig sibsConfig(parentConfig.getCompiler(), packageDir, parentConfig.getOptimizationLevel(), false); Result result = Config::readFromFile(projectConfFilePath.c_str(), sibsConfig); if (result.isErr()) return result; if(sibsConfig.getPackageName().empty()) return Result::Err("project.conf is missing required field package.name"); if(sibsConfig.getPackageType() == PackageType::EXECUTABLE) { string errMsg = "The dependency "; errMsg += name; errMsg += " is an executable. Only libraries can be dependencies"; return Result::Err(errMsg); } if (!containsPlatform(sibsConfig.getPlatforms(), SYSTEM_PLATFORM)) { string errMsg = "The dependency "; errMsg += name; errMsg += " does not support your platform ("; errMsg += asString(SYSTEM_PLATFORM); errMsg += ")"; return Result::Err(errMsg); } FileString buildPath = packageDir + TINYDIR_STRING("/sibs-build/"); switch (sibsConfig.getOptimizationLevel()) { case OPT_LEV_DEBUG: { buildPath += TINYDIR_STRING("debug"); // TODO: Check if this dependency is static or dynamic and decide which lib path to use from that for(const string &staticLib : sibsConfig.getDebugStaticLibs()) { string staticLibCmd = "\""; staticLibCmd += staticLib; staticLibCmd += "\""; staticLinkerFlagCallbackFunc(staticLibCmd); } break; } case OPT_LEV_RELEASE: { buildPath += TINYDIR_STRING("release"); // TODO: Check if this dependency is static or dynamic and decide which lib path to use from that for (const string &staticLib : sibsConfig.getReleaseStaticLibs()) { string staticLibCmd = "\""; staticLibCmd += staticLib; staticLibCmd += "\""; staticLinkerFlagCallbackFunc(staticLibCmd); } break; } } if(sibsConfig.shouldUseCmake()) { CmakeModule cmakeModule; return cmakeModule.compile(sibsConfig, buildPath, staticLinkerFlagCallbackFunc, dynamicLinkerFlagCallbackFunc, globalIncludeDirCallback); } else { backend::Ninja ninja; // TODO: Use same source file finder as in main.cpp FileWalkCallbackFunc collectSourceFiles = [&ninja, &sibsConfig, &collectSourceFiles](tinydir_file *file) { FileString pathNative = file->path; #if OS_FAMILY == OS_FAMILY_WINDOWS replaceChar(pathNative, L'/', L'\\'); #endif if(file->is_reg) { if (isSourceFile(file)) { string fileNameNative = toUtf8(pathNative.c_str() + sibsConfig.getProjectPath().size() + 1); ninja.addSourceFile(fileNameNative.c_str()); } else { //printf("Ignoring non-source file: %s\n", file->path + projectPath.size()); } } else { // TODO: If compiling without "test" option, do not add test source dir to ninja. Ninja logic will then not build tests... // OR I believe there is no reason to run tests in dependencies. The main projects tests should cover that? // But you might want to know exactly which dependency is causing issue and which part of it... if (!sibsConfig.getTestPath().empty() && isPathSubPathOf(pathNative.c_str(), sibsConfig.getTestPath())) { string filePathUtf8 = toUtf8(pathNative.c_str()); ninja.addTestSourceDir(filePathUtf8.c_str()); } else if(!directoryToIgnore(pathNative, sibsConfig.getIgnoreDirs())) walkDir(file->path, collectSourceFiles); } }; walkDir(packageDir.c_str(), collectSourceFiles); if (!ninja.getSourceFiles().empty()) { string libPath = toUtf8(buildPath); switch (sibsConfig.getCompiler()) { case Compiler::GCC: { libPath += "/lib"; libPath += name; if (sibsConfig.getPackageType() == PackageType::STATIC) { libPath += ".a"; string libPathCmd = "'"; libPathCmd += libPath; libPathCmd += "'"; staticLinkerFlagCallbackFunc(libPathCmd); } else { libPath += ".so"; string libPathCmd = "'"; libPathCmd += libPath; libPathCmd += "'"; dynamicLinkerFlagCallbackFunc(libPathCmd); } break; } case Compiler::MSVC: { libPath += "/"; libPath += name; if (sibsConfig.getPackageType() == PackageType::STATIC) { libPath += ".lib"; string libPathCmd = "\""; libPathCmd += libPath; libPathCmd += "\""; staticLinkerFlagCallbackFunc(libPathCmd); } else { libPath += ".lib"; string libPathCmd = "\""; libPathCmd += libPath; libPathCmd += "\""; dynamicLinkerFlagCallbackFunc(libPathCmd); } break; } } } return ninja.build(sibsConfig, buildPath.c_str(), staticLinkerFlagCallbackFunc, dynamicLinkerFlagCallbackFunc, globalIncludeDirCallback); } } Result GlobalLib::downloadDependency(const Dependency &dependency) { Result packageUrlResult = Package::getPackageUrl(dependency.name.c_str(), dependency.version.c_str(), SYSTEM_PLATFORM_NAME); if(!packageUrlResult) return Result::Err(packageUrlResult); const string &url = packageUrlResult.unwrap(); Result libPathResult = getHomeDir(); if (!libPathResult) return Result::Err(libPathResult); FileString libPath = libPathResult.unwrap(); libPath += TINYDIR_STRING("/.sibs/lib/"); libPath += toFileString(dependency.name); libPath += TINYDIR_STRING("/"); libPath += toFileString(dependency.version); FileString libArchivedFilePath = libPathResult.unwrap(); libArchivedFilePath += TINYDIR_STRING("/.sibs/archive/"); libArchivedFilePath += toFileString(dependency.name); Result createArchiveDirResult = createDirectoryRecursive(libArchivedFilePath.c_str()); if(createArchiveDirResult.isErr()) return createArchiveDirResult; libArchivedFilePath += TINYDIR_STRING("/"); libArchivedFilePath += toFileString(dependency.version); Result downloadResult = curl::downloadFile(url.c_str(), libArchivedFilePath.c_str()); if(downloadResult.isErr()) return downloadResult; // Create build path. This is done here because we dont want to create it if download fails Result createLibDirResult = createDirectoryRecursive(libPath.c_str()); if(createLibDirResult.isErr()) return createLibDirResult; return Archive::extract(libArchivedFilePath.c_str(), libPath.c_str()); } }