#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" #include "../include/Dependency.hpp" #include "../include/GitRepository.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(PackageListDependency *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) { 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) return downloadDependencyResult; globalLibLinkerFlagsResult = GlobalLib::getLibsLinkerFlags(parentConfig, globalLibRootDir, globalLibDependency->name, globalLibDependency->version, staticLinkerFlagCallbackFunc, dynamicLinkerFlagCallbackFunc, globalIncludeDirCallback); if(!globalLibLinkerFlagsResult) return globalLibLinkerFlagsResult; } else { return globalLibLinkerFlagsResult; } } } for(GitDependency *gitDependency : parentConfig.getGitDependencies()) { Result gitLibLinkerFlagsResult = GlobalLib::getLibsLinkerFlags(parentConfig, globalLibRootDir, gitDependency, staticLinkerFlagCallbackFunc, dynamicLinkerFlagCallbackFunc, globalIncludeDirCallback); if(!gitLibLinkerFlagsResult) { if(gitLibLinkerFlagsResult.getErrorCode() == GlobalLib::DependencyError::DEPENDENCY_NOT_FOUND) { printf("Dependency %s not found in global lib, trying to download from git\n", gitDependency->name.c_str()); // 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(gitDependency); if(!downloadDependencyResult) return downloadDependencyResult; gitLibLinkerFlagsResult = GlobalLib::getLibsLinkerFlags(parentConfig, globalLibRootDir, gitDependency, staticLinkerFlagCallbackFunc, dynamicLinkerFlagCallbackFunc, globalIncludeDirCallback); if(!gitLibLinkerFlagsResult) return gitLibLinkerFlagsResult; } else { return gitLibLinkerFlagsResult; } } } 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; return GlobalLib::getLibsLinkerFlagsCommon(parentConfig, packageDir, name, staticLinkerFlagCallbackFunc, dynamicLinkerFlagCallbackFunc, globalIncludeDirCallback); } Result GlobalLib::getLibsLinkerFlags(const SibsConfig &parentConfig, const FileString &globalLibRootDir, GitDependency *gitDependency, LinkerFlagCallbackFunc staticLinkerFlagCallbackFunc, LinkerFlagCallbackFunc dynamicLinkerFlagCallbackFunc, GlobalIncludeDirCallbackFunc globalIncludeDirCallback) { Result packageExistsResult = validatePackageExists(globalLibRootDir, gitDependency->name); if (packageExistsResult.isErr()) return packageExistsResult; #if OS_FAMILY == OS_FAMILY_POSIX FileString namePlatformNative = gitDependency->name; FileString versionPlatformNative = gitDependency->revision; #else FileString namePlatformNative = utf8To16(name); FileString versionPlatformNative = utf8To16(version); #endif FileString packageDir = globalLibRootDir + TINYDIR_STRING("/"); packageDir += namePlatformNative; packageDir += TINYDIR_STRING("/"); packageDir += versionPlatformNative; // TODO: Check path is valid git repository by using git_repository_open_ext // TODO: Pull if revision == HEAD, fail build if there are conflicts. // TODO: When building a sibs project, put a symlink in libs directory. // This allows you to have dependency on a project and make changes to it without pushing // to remote before the dependant project can see the changes. //GitRepository gitRepo; //gitRepo.pull(gitDependency, packageDir); return GlobalLib::getLibsLinkerFlagsCommon(parentConfig, packageDir, gitDependency->name, staticLinkerFlagCallbackFunc, dynamicLinkerFlagCallbackFunc, globalIncludeDirCallback); } Result GlobalLib::getLibsLinkerFlagsCommon(const SibsConfig &parentConfig, const FileString &packageDir, const string &dependencyName, LinkerFlagCallbackFunc staticLinkerFlagCallbackFunc, LinkerFlagCallbackFunc dynamicLinkerFlagCallbackFunc, GlobalIncludeDirCallbackFunc globalIncludeDirCallback) { FileString projectConfFilePath = packageDir; projectConfFilePath += TINYDIR_STRING("/project.conf"); FileType projectConfFileType = getFileType(projectConfFilePath.c_str()); if(projectConfFileType != FileType::REGULAR) { string errMsg = "Dependency not found: "; errMsg += toUtf8(packageDir); return Result::Err(errMsg, DependencyError::DEPENDENCY_NOT_FOUND); } 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 += dependencyName; errMsg += " is an executable. Only libraries can be dependencies"; return Result::Err(errMsg); } if (!containsPlatform(sibsConfig.getPlatforms(), SYSTEM_PLATFORM)) { string errMsg = "The dependency "; errMsg += dependencyName; 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 += dependencyName; 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 += dependencyName; 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(PackageListDependency *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) return createArchiveDirResult; libArchivedFilePath += TINYDIR_STRING("/"); libArchivedFilePath += toFileString(dependency->version); Result downloadResult = curl::downloadFile(url.c_str(), libArchivedFilePath.c_str()); if(!downloadResult) 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) return createLibDirResult; return Archive::extract(libArchivedFilePath.c_str(), libPath.c_str()); } Result GlobalLib::downloadDependency(GitDependency *dependency) { Result libPathResult = getHomeDir(); if (!libPathResult) return Result::Err(libPathResult); FileString libPath = libPathResult.unwrap(); libPath += TINYDIR_STRING("/.sibs/lib/"); libPath += toFileString(dependency->name); // We dont care if the directory already exists. Nothing will happen if it does Result createLibDirResult = createDirectoryRecursive(libPath.c_str()); if(!createLibDirResult) return createLibDirResult; libPath += TINYDIR_STRING("/"); libPath += toFileString(dependency->revision); GitRepository gitRepo; return gitRepo.clone(dependency, libPath); } }