#include "../include/GlobalLib.hpp" #include "../include/FileUtil.hpp" #include "../backend/BackendUtils.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" #include "../include/VersionParser.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: { break; } default: { return Result::Err("Unexpected error!"); } } // We also need to verify that the archive was removed after extracting it, otherwise the extracted // package could be corrupted. The archive is only removed after extracting is done. // TODO: Find a way to verify that the git repo was cloned fully without interruption, or add .finished files // for every download Result libPathResult = getHomeDir(); if (!libPathResult) return Result::Err(libPathResult); FileString libArchivedFilePath = libPathResult.unwrap(); libArchivedFilePath += TINYDIR_STRING("/.cache/sibs/archive/"); libArchivedFilePath += toFileString(name); if(getFileType(libArchivedFilePath.c_str()) != FileType::FILE_NOT_FOUND) return Result::Err("A previous download of package is corrupt, attempting to redownload..."); return Result::Ok(true); } Result GlobalLib::getLibs(const std::vector &libs, const SibsConfig &parentConfig, const FileString &globalLibRootDir, LinkerFlagCallbackFunc staticLinkerFlagCallbackFunc, LinkerFlagCallbackFunc dynamicLinkerFlagCallbackFunc, GlobalIncludeDirCallbackFunc globalIncludeDirCallback) { for(PackageListDependency *globalLibDependency : libs) { if(!parentConfig.packaging) printf("Dependency %s in version range %s is missing from pkg-config, trying global lib\n", globalLibDependency->name.c_str(), globalLibDependency->version.toString().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, parentConfig.platform); 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, parentConfig.platform); 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 PackageVersionRange &versionRange, 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; #else FileString namePlatformNative = utf8To16(name); #endif FileString packageDir = globalLibRootDir + TINYDIR_STRING("/"); packageDir += namePlatformNative; FileString foundVersion; walkDir(packageDir.c_str(), [&foundVersion, &versionRange](tinydir_file *file) { if(file->is_dir) { string versionUtf8 = toUtf8(file->name); Result versionResult = parsePackageVersion({ versionUtf8.data(), versionUtf8.size() }, nullptr); if(versionResult && versionRange.isInRange(versionResult.unwrap())) { foundVersion = file->name; return false; } } return true; }); if(foundVersion.empty()) return Result::Err("Global lib dependency found, but version isn't in range of version", DependencyError::DEPENDENCY_VERSION_NO_MATCH); packageDir += TINYDIR_STRING("/"); packageDir += foundVersion; 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(gitDependency->name); FileString versionPlatformNative = utf8To16(gitDependency->revision); #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); sibsConfig.platform = parentConfig.platform; sibsConfig.packaging = parentConfig.packaging; sibsConfig.bundling = parentConfig.bundling; Result result = Config::readFromFile(projectConfFilePath.c_str(), sibsConfig); if (result.isErr()) return result; if(sibsConfig.getPackageType() == PackageType::EXECUTABLE) { string errMsg = "The dependency "; errMsg += dependencyName; errMsg += " is an executable. Only libraries can be dependencies"; return Result::Err(errMsg); } FileString buildPath = packageDir + TINYDIR_STRING("/sibs-build/") + toFileString(asString(sibsConfig.platform)) + TINYDIR_STRING("/"); 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; } } for(const std::string &lib : sibsConfig.getLibs()) { string staticLibCmd = "\""; staticLibCmd += lib; staticLibCmd += "\""; staticLinkerFlagCallbackFunc(staticLibCmd); } if(sibsConfig.shouldUseCmake()) { CmakeModule cmakeModule; return cmakeModule.compile(sibsConfig, buildPath, staticLinkerFlagCallbackFunc, dynamicLinkerFlagCallbackFunc, globalIncludeDirCallback); } else { backend::Ninja ninja; backend::BackendUtils::collectSourceFiles(packageDir.c_str(), &ninja, sibsConfig); return ninja.build(sibsConfig, buildPath.c_str(), staticLinkerFlagCallbackFunc, dynamicLinkerFlagCallbackFunc, globalIncludeDirCallback); } } Result GlobalLib::downloadDependency(PackageListDependency *dependency, Platform platform) { Result packageResult = Package::getPackage(dependency->name.c_str(), dependency->version, platform); if(!packageResult) return Result::Err(packageResult); const PackageMetadata &package = packageResult.unwrap(); Result libPathResult = getHomeDir(); if (!libPathResult) return Result::Err(libPathResult); FileString libPath = libPathResult.unwrap(); libPath += TINYDIR_STRING("/.cache/sibs/lib/"); libPath += toFileString(asString(platform)); libPath += TINYDIR_STRING("/"); libPath += toFileString(dependency->name); libPath += TINYDIR_STRING("/"); libPath += toFileString(package.version.toString()); FileString libArchivedFilePath = libPathResult.unwrap(); libArchivedFilePath += TINYDIR_STRING("/.cache/sibs/archive/"); libArchivedFilePath += toFileString(dependency->name); Result createArchiveDirResult = createDirectoryRecursive(libArchivedFilePath.c_str()); if(!createArchiveDirResult) return createArchiveDirResult; libArchivedFilePath += TINYDIR_STRING("/"); libArchivedFilePath += toFileString(package.version.toString()); Result downloadResult = curl::downloadFile(package.urls[0].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; Result archiveExtractResult = Archive::extract(libArchivedFilePath.c_str(), libPath.c_str()); // We have extracted the archive, we dont need to cache it. If remove fails, it doesn't really matter, user can remove it himself #if OS_FAMILY == OS_FAMILY_POSIX remove(libArchivedFilePath.c_str()); #else _wremove(libArchivedFilePath.c_str()); #endif return archiveExtractResult; } Result GlobalLib::downloadDependency(GitDependency *dependency, Platform platform) { Result libPathResult = getHomeDir(); if (!libPathResult) return Result::Err(libPathResult); FileString libPath = libPathResult.unwrap(); libPath += TINYDIR_STRING("/.cache/sibs/lib/"); libPath += toFileString(asString(platform)); libPath += TINYDIR_STRING("/"); 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); } }