From d853ae12bb9931fc90c9ff0abfdfa3675f690f81 Mon Sep 17 00:00:00 2001 From: Coal Date: Tue, 8 Mar 2022 14:46:55 -0600 Subject: [PATCH] Implement package downloading and module installation - The next step is to implement the sqlite db and read/write functions for it --- .gitignore | 5 +- CMakeLists.txt | 14 +- dev/samplelpmdir/cache/packages/somepkg.zip | 0 .../repositories/repositoryid.toml | 0 dev/samplelpmdir/lpm.toml | 14 +- .../Lua5.1/somemodule/1.0/modulesources | 0 dev/samplepackage/module.toml | 27 +- src/handlers/install.cpp | 258 +++++++++++++++++- src/library | 2 +- src/main.cpp | 1 - 10 files changed, 281 insertions(+), 40 deletions(-) create mode 100644 dev/samplelpmdir/cache/packages/somepkg.zip rename dev/samplelpmdir/{ => cache}/repositories/repositoryid.toml (100%) create mode 100644 dev/samplelpmdir/modules/Lua5.1/somemodule/1.0/modulesources diff --git a/.gitignore b/.gitignore index 9f57d4d..6573c4e 100644 --- a/.gitignore +++ b/.gitignore @@ -13,4 +13,7 @@ Makefile # Other .vscode !build/*.sh -!lpm/ \ No newline at end of file +!lpm/ + +# LPM +lpm_modules \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index f88800f..4d88718 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,10 +10,12 @@ endif () project(lpm-cli) -include_directories(./src ./src/library) - # Specify executable and target properties add_executable(lpm-cli src/main.cpp) + +# Add lpm-lib +include_directories(./src ./src/library) + set_target_properties( lpm-cli PROPERTIES @@ -21,9 +23,6 @@ set_target_properties( OUTPUT_NAME lpm ) -# Include the framework -add_subdirectory(src/library) - # Include source files add_library(lpm-cli-src ./src/command.cpp @@ -34,10 +33,13 @@ add_library(lpm-handlers ./src/handlers/install.cpp ) +# Include the library +add_subdirectory(src/library) + # Add install target install(TARGETS lpm-cli RUNTIME DESTINATION ${BIN_INSTALL_DIR} ) # Link the libraries to the target -target_link_libraries(lpm-cli PRIVATE lpm-cli-src lpm-handlers lpm-lib) \ No newline at end of file +target_link_libraries(lpm-cli PRIVATE lpm-cli-src lpm-handlers lpm-lib curl zip) \ No newline at end of file diff --git a/dev/samplelpmdir/cache/packages/somepkg.zip b/dev/samplelpmdir/cache/packages/somepkg.zip new file mode 100644 index 0000000..e69de29 diff --git a/dev/samplelpmdir/repositories/repositoryid.toml b/dev/samplelpmdir/cache/repositories/repositoryid.toml similarity index 100% rename from dev/samplelpmdir/repositories/repositoryid.toml rename to dev/samplelpmdir/cache/repositories/repositoryid.toml diff --git a/dev/samplelpmdir/lpm.toml b/dev/samplelpmdir/lpm.toml index 741edab..1fe5000 100644 --- a/dev/samplelpmdir/lpm.toml +++ b/dev/samplelpmdir/lpm.toml @@ -1,12 +1,16 @@ [lpm] db_backend = "sqlite" -packages_db = "./packages.sqlite" +packages_db = "${LPM_ROOT}/packages.sqlite" +# modules_path doesnt use ${?} because it describes a directory, not an individual file +modules_path = "${LPM_ROOT}/modules/${lua_version}/${module_name}/${module_version}" +repositories_cache = "${LPM_ROOT}/cache/repositories/${?}.toml" +packages_cache = "${LPM_ROOT}/cache/packages/${package_name}/${?}" [luas] "Lua5.1" = "/usr/bin/lua5.1" "Lua5.3" = "/usr/bin/lua5.3" +"default" = "/usr/bin/lua5.3" -[sources.repositoryid] -permission = "trusted" -cache = "${LPM_ROOT}/repositories/repositoryid.toml" -url = "https://tabe.me/repositories/coal.toml" \ No newline at end of file +[sources.me-at-the-zoo] +url = "https://raw.githubusercontent.com/coalio/me-at-the-zoo/master/me-at-the-zoo.toml" +permission = "trusted" \ No newline at end of file diff --git a/dev/samplelpmdir/modules/Lua5.1/somemodule/1.0/modulesources b/dev/samplelpmdir/modules/Lua5.1/somemodule/1.0/modulesources new file mode 100644 index 0000000..e69de29 diff --git a/dev/samplepackage/module.toml b/dev/samplepackage/module.toml index d1905c4..acd2d07 100644 --- a/dev/samplepackage/module.toml +++ b/dev/samplepackage/module.toml @@ -13,22 +13,11 @@ lua_version = "*" main="hamming_demo.lua" lua_version=["LuaJIT2.0", "Lua5.3", "*"] -# [support] -# if lua_version isnt present, its assumed to be compatible with all versions -# lua_version = ["Lua5.1", "Lua5.2", "Lua5.3", "LuaJIT2.0"] -# if platform isnt present, its assumed to be compatible with all platforms -# platform = ["Windows", "Linux", "OSX", "BSD", "Unix"] - -# [dependencies] -# exampledependency = ">= 1.0, < 2.0" - -[pre-build] -type = "cmake" -args = ["."] - -[build] -type = "make" - -[post-build] -type = "make" -args = ["install"] \ No newline at end of file +[support] +# lua_version is obligatory, but you can use "*" if you want +# however, that will throw a warning when installing as its possible that +# this package is not compatible with the most recent Lua version +lua_version = ["*"] +# platform is also obligatory, but you can also use "*" if you want +# in this case, it wont throw a warning but its not recommended +platform = ["*"] \ No newline at end of file diff --git a/src/handlers/install.cpp b/src/handlers/install.cpp index 4fe50d7..389e20a 100644 --- a/src/handlers/install.cpp +++ b/src/handlers/install.cpp @@ -1,16 +1,36 @@ -#include "install.h" +#include +#include #include "lpm/macros.h" #include "lpm/manifests.h" #include "lpm/pathfinder.h" +#include "lpm/scope_destructor.h" +#include "lpm/requests.h" #include "lpm/env.h" +#include "lpm/utils.h" +#include "lpm/dependencies.h" +#include "lpm/errors.h" + +#include "install.h" using namespace LPM; +using Response = Requests::Response; void Handlers::install(args_t& args) { - Manifests::Packages package( - LPM_DEFAULT_PACKAGES_PATH + Errors::ErrorList errors; + Manifests::Packages packages( + LPM_PACKAGES_MANIFEST_NAME ); + // Copy packages.dependencies into missing_dependencies + auto missing_dependencies = packages.dependencies; + + // Check if dependencies are installed + for (auto& dependency : missing_dependencies) { + if (Dependencies::is_installed(dependency)) { + missing_dependencies.erase(dependency.first); + } + } + // Locate and load the lpm.toml file std::string config_path = PathFinder::locate_config(); if (config_path.empty()) { @@ -23,19 +43,243 @@ void Handlers::install(args_t& args) { // If they dont, download it and then continue for (auto& repository : config.repositories) { auto& cache = repository.second["cache"]; - - std::string cache_path = Env::fill_env_vars(cache); + std::string cache_path = cache; + Env::fill_env_vars(cache); if (cache_path.empty()) { LPM_PRINT_DEBUG("No cache path found for repository: " << repository.first); LPM_PRINT_DEBUG("Downloading manifest from: " << repository.second["url"]); - // TODO: Download with curl and set cache_path to the downloaded file + // Create a new response object, we'll use this to + // store the response from the request. + Response response; + + try { + // Initialize a new curl handle and give it a scope_destructor, so + // we can call curl_easy_cleanup() on it when it goes out of scope + scope_destructor curl_handle(curl_easy_init(), curl_easy_cleanup); + response = Requests::get(repository.second["url"], curl_handle.get()); + + if (response.status_code != 200) { + errors.add( + "repository fetching", + "(" + repository.first + ") cant download from: " + repository.second["url"] + + ", status code " + std::to_string(response.status_code) + ); + + continue; + } + } catch (const std::exception& e) { + errors.add( + "repository fetching", + "(" + repository.first + ") cant download from: " + repository.second["url"] + + ", error: " + e.what() + ); + + continue; + } + + // Copy config.repositories_cache to cache_path + cache_path = config.repositories_cache; + + // Apply env vars without replacing empty values + Env::fill_env_vars(cache_path, false); + + // Apply the repository name + Utils::format(cache_path, { + {"?", repository.first} + }); + + LPM_PRINT_DEBUG("Writing manifest to: " << cache_path); + + // Write the manifest to the cache path + if (!Utils::write_file(cache_path, response.body)) { + errors.add( + "repository fetching", + "(" + repository.first + ") cant write to: " + cache_path + ); + + continue; + } + + // Update the config file + config.repositories[repository.first]["cache"] = cache_path; + config.save(); } - LPM_PRINT_DEBUG("Cache path found for repository: " << repository.first); LPM_PRINT_DEBUG("Loading manifest from: " << cache_path); Manifests::Repository repository_manifest(cache_path); + + // Once we have the manifest, we can check if the package we need is defined in it + // So, lets iterate through missing dependencies + for (auto& missing_dependency : missing_dependencies) { + // If the package is defined in the manifest, we can install it + // and then remove it from the missing_dependencies + if ( + repository_manifest.packages.find(missing_dependency.first) != + repository_manifest.packages.end() + ) { + // Install the package + + LPM_PRINT_DEBUG("Package " << missing_dependency.first << " is defined in repository " << repository.first); + + auto& package = + repository_manifest.packages[missing_dependency.first]; + + // Check if this package has any versions + if (package.versions.empty()) { + errors.add( + "package search", + "repository " + repository.first + " has no versions of " + missing_dependency.first + ); + + continue; + } + + // Check if the version we need is defined in the manifest + std::string package_version, package_url; + if (missing_dependency.second == "latest") { + // Sort the keys of package.versions in descending order + // so we can get the latest version + std::vector sorted_versions(package.versions.size()); + std::transform( + package.versions.begin(), + package.versions.end(), + sorted_versions.begin(), + [](const auto& pair) { + return pair.first; + } + ); + + std::sort( + sorted_versions.begin(), + sorted_versions.end(), + [](const auto& a, const auto& b) { + // Get the major, minor and patch versions + // of the versions + std::string token; + std::istringstream stream_a(a); + std::istringstream stream_b(b); + + std::vector tokens_a; + while (std::getline(stream_a, token, '.')) { + if (!token.empty()) { + tokens_a.push_back(token); + } + } + + std::vector tokens_b; + while (std::getline(stream_b, token, '.')) { + if (!token.empty()) { + tokens_b.push_back(token); + } + } + + // Compare the major, minor and patch versions + // of the versions + size_t max_size = std::max(tokens_a.size(), tokens_b.size()); + for (size_t i = 0; i < max_size; i++) { + if (tokens_a.size() - 1 < i || tokens_b.size() - 1 < i) { + return tokens_a.size() > tokens_b.size(); + } + + if (tokens_a[i] != tokens_b[i]) { + return std::stoi(tokens_a[i]) > std::stoi(tokens_b[i]); + } + } + + return false; + } + ); + + // Get the latest version + const auto& latest_version = sorted_versions.front(); + package_url = package.versions[latest_version]; + package_version = latest_version; + + LPM_PRINT_DEBUG( + "Latest version of " << missing_dependency.first << ":" + << latest_version + ); + } else { + // Check if the dependency version is defined in the package + if (package.versions.find(missing_dependency.second) == package.versions.end()) { + errors.add( + "package search", + "Dependency " + missing_dependency.first + ":" + + missing_dependency.second + " is not defined in the package" + ); + + continue; + } + + package_url = package.versions[missing_dependency.second]; + package_version = missing_dependency.second; + + LPM_PRINT_DEBUG("Version " << missing_dependency.second << " of " << missing_dependency.first << " is defined"); + } + + // Install the package + bool success; + std::string error; + + // We need to copy and format packages_cache and modules_path + // to cache_path and module_path respectively. + std::string cache_path = config.packages_cache; + std::string module_path; + if (args.contains("global")) { + // config.modules_path specifies the directory for global modules + module_path = config.modules_path; + } else { + // whereover LPM_DEFAULT_LOCAL_MODULES_PATH specifies the directory for project modules + module_path = LPM_DEFAULT_LOCAL_MODULES_PATH; + } + + // Dependencies::install doesn't format the paths, so we do it here + Env::fill_env_vars(cache_path, false); + Utils::format(cache_path, { + {"package_name", package.name}, + {"package_version", package_version}, + {"?", package.name + "-" + package_version} + }); + + Env::fill_env_vars(module_path, false); + Utils::format(module_path, { + {"module_name", package.name}, + {"module_version", package_version}, + {"lua_version", config.luas["default"]} + }); + + + + success = Dependencies::install( + {package.name, package_version}, + + package, + cache_path, + module_path, + error + ); + + if (!success) { + errors.add( + "dependency installation", + "(" + + missing_dependency.first + ":" + missing_dependency.second + + ") " + error + ); + + continue; + } else { + LPM_PRINT_DEBUG("Installed dependency " << missing_dependency.first << ":" << missing_dependency.second); + + missing_dependencies.erase(missing_dependency.first); + } + + } + } } + + errors.print(); } \ No newline at end of file diff --git a/src/library b/src/library index 7003b56..c20a929 160000 --- a/src/library +++ b/src/library @@ -1 +1 @@ -Subproject commit 7003b56bbaad7d42ed6854175621c9eedff8e5a3 +Subproject commit c20a929400ded33afc2643f0637882ae5df5ebc6 diff --git a/src/main.cpp b/src/main.cpp index 60cfc94..0d76715 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -9,7 +9,6 @@ #include "argparser.h" #include "static/messages.h" - int main(int argc, char *argv[]) { if (argc < 2) { LPM_PRINT(MESSAGES_HELP << "\n");