From 15671be66498c6d1e4cb6d7f469d05f205c3e2f8 Mon Sep 17 00:00:00 2001 From: Hu Yueh-Wei Date: Sun, 3 Nov 2024 16:12:56 +0800 Subject: [PATCH] feat: add conflict error information to tman --- .../src/cmd/cmd_check/cmd_check_graph.rs | 1 + core/src/ten_manager/src/cmd/cmd_delete.rs | 5 +- core/src/ten_manager/src/cmd/cmd_install.rs | 56 +++-- core/src/ten_manager/src/cmd/cmd_package.rs | 3 +- core/src/ten_manager/src/cmd/cmd_publish.rs | 3 +- .../ten_manager/src/dep_and_candidate/mod.rs | 38 +++- .../src/dev_server/addons/extensions.rs | 4 +- core/src/ten_manager/src/dev_server/common.rs | 7 +- .../src/dev_server/graphs/connections.rs | 5 +- .../ten_manager/src/dev_server/graphs/mod.rs | 3 +- .../src/dev_server/graphs/nodes.rs | 15 +- .../src/dev_server/graphs/update.rs | 5 +- .../src/dev_server/manifest/check.rs | 3 +- .../src/dev_server/manifest/dump.rs | 5 +- .../src/dev_server/messages/compatible.rs | 14 +- core/src/ten_manager/src/dev_server/mock.rs | 3 +- core/src/ten_manager/src/dev_server/mod.rs | 3 +- .../src/dev_server/property/check.rs | 3 +- .../src/dev_server/property/dump.rs | 12 +- core/src/ten_manager/src/install/mod.rs | 7 +- core/src/ten_manager/src/log.rs | 9 + core/src/ten_manager/src/main.rs | 2 +- core/src/ten_manager/src/manifest_lock/mod.rs | 3 +- core/src/ten_manager/src/package_file/mod.rs | 7 +- .../predefined_graphs/connection.rs | 7 +- .../package_info/predefined_graphs/node.rs | 3 +- core/src/ten_manager/src/registry/local.rs | 9 +- core/src/ten_manager/src/registry/mod.rs | 4 +- core/src/ten_manager/src/registry/remote.rs | 9 +- core/src/ten_manager/src/solver/display.lp | 7 +- core/src/ten_manager/src/solver/introducer.rs | 102 +++++++++ core/src/ten_manager/src/solver/main.lp | 57 +++-- core/src/ten_manager/src/solver/mod.rs | 2 + core/src/ten_manager/src/solver/solve.rs | 194 +++++++++++++++--- .../ten_manager/src/solver/solver_error.rs | 145 +++++++++++++ .../ten_manager/src/solver/solver_result.rs | 16 +- docs/ten_framework/ten_manager/check_graph.md | 28 +-- 37 files changed, 651 insertions(+), 148 deletions(-) create mode 100644 core/src/ten_manager/src/solver/introducer.rs create mode 100644 core/src/ten_manager/src/solver/solver_error.rs diff --git a/core/src/ten_manager/src/cmd/cmd_check/cmd_check_graph.rs b/core/src/ten_manager/src/cmd/cmd_check/cmd_check_graph.rs index 216efe417b..1ba3131c56 100644 --- a/core/src/ten_manager/src/cmd/cmd_check/cmd_check_graph.rs +++ b/core/src/ten_manager/src/cmd/cmd_check/cmd_check_graph.rs @@ -10,6 +10,7 @@ use std::{collections::HashMap, fs, path, str::FromStr}; use anyhow::{Context, Result}; use clap::{Arg, ArgMatches, Command}; use console::Emoji; + use ten_rust::pkg_info::{ get_all_existed_pkgs_info_of_app, graph::Graph, localhost, property::parse_property_in_folder, PkgInfo, diff --git a/core/src/ten_manager/src/cmd/cmd_delete.rs b/core/src/ten_manager/src/cmd/cmd_delete.rs index 4fad770c7c..f4ba975186 100644 --- a/core/src/ten_manager/src/cmd/cmd_delete.rs +++ b/core/src/ten_manager/src/cmd/cmd_delete.rs @@ -13,11 +13,12 @@ use console::Emoji; use indicatif::HumanDuration; use semver::Version; -use crate::log::tman_verbose_println; -use crate::{config::TmanConfig, registry::delete_package}; use ten_rust::pkg_info::pkg_identity::PkgIdentity; use ten_rust::pkg_info::pkg_type::PkgType; +use crate::log::tman_verbose_println; +use crate::{config::TmanConfig, registry::delete_package}; + #[derive(Debug)] pub struct DeleteCommand { pub package_type: String, diff --git a/core/src/ten_manager/src/cmd/cmd_install.rs b/core/src/ten_manager/src/cmd/cmd_install.rs index afa81b5bbb..24a0721762 100644 --- a/core/src/ten_manager/src/cmd/cmd_install.rs +++ b/core/src/ten_manager/src/cmd/cmd_install.rs @@ -13,13 +13,26 @@ use std::{ time::Instant, }; -use anyhow::{Ok, Result}; +use anyhow::Result; use clap::{Arg, ArgAction, ArgMatches, Command}; use console::Emoji; use indicatif::HumanDuration; use inquire::Confirm; use semver::VersionReq; +use ten_rust::pkg_info::manifest::{ + dependency::ManifestDependency, parse_manifest_in_folder, +}; +use ten_rust::pkg_info::{ + dependencies::{DependencyRelationship, PkgDependency}, + find_to_be_replaced_local_pkgs, find_untracked_local_packages, + get_pkg_info_from_path, + pkg_identity::PkgIdentity, + pkg_type::PkgType, + supports::{is_pkg_supports_compatible_with, Arch, Os, PkgSupport}, + PkgInfo, +}; + use crate::{ config::TmanConfig, constants::{APP_DIR_IN_DOT_TEN_DIR, DOT_TEN_DIR, MANIFEST_JSON_FILENAME}, @@ -32,7 +45,9 @@ use crate::{ }, package_info::tman_get_all_existed_pkgs_info_of_app, solver::{ + introducer::extract_introducer_relations_from_raw_solver_results, solve::solve_all, + solver_error::{parse_error_statement, print_conflict_info}, solver_result::{ extract_solver_results_from_raw_solver_results, filter_solver_results_by_type_and_name, @@ -42,18 +57,6 @@ use crate::{ }, utils::{check_is_app_folder, check_is_package_folder}, }; -use ten_rust::pkg_info::manifest::{ - dependency::ManifestDependency, parse_manifest_in_folder, -}; -use ten_rust::pkg_info::{ - dependencies::{DependencyRelationship, PkgDependency}, - find_to_be_replaced_local_pkgs, find_untracked_local_packages, - get_pkg_info_from_path, - pkg_identity::PkgIdentity, - pkg_type::PkgType, - supports::{is_pkg_supports_compatible_with, Arch, Os, PkgSupport}, - PkgInfo, -}; #[derive(Debug)] pub struct InstallCommand { @@ -669,12 +672,39 @@ pub async fn execute_cmd( )?; // Print out the answer. + tman_verbose_println!(tman_config, "\n"); tman_verbose_println!(tman_config, "Result:"); for result in &results { tman_verbose_println!(tman_config, " {:?}", result); } tman_verbose_println!(tman_config, ""); + // Extract introducer relations. + let introducer_relations = + extract_introducer_relations_from_raw_solver_results( + &results, + &all_candidates, + )?; + + // Parse the error message. + if let Ok(conflict_info) = parse_error_statement(&results) { + // Print the error message and dependency chains. + print_conflict_info( + tman_config, + &conflict_info, + &introducer_relations, + &all_candidates, + )?; + + // Since there is an error, we need to exit. + return Err(TmanError::Custom( + "Dependency resolution failed.".to_string(), + ) + .into()); + } + + // If there is no error message, proceed. + // Get the information of the resultant packages. let solver_results = extract_solver_results_from_raw_solver_results( &results, diff --git a/core/src/ten_manager/src/cmd/cmd_package.rs b/core/src/ten_manager/src/cmd/cmd_package.rs index b13aee38a8..8ac4f38856 100644 --- a/core/src/ten_manager/src/cmd/cmd_package.rs +++ b/core/src/ten_manager/src/cmd/cmd_package.rs @@ -11,12 +11,13 @@ use clap::{ArgMatches, Command}; use console::Emoji; use indicatif::HumanDuration; +use ten_rust::pkg_info::get_pkg_info_from_path; + use crate::{ config::TmanConfig, log::tman_verbose_println, package_file::{create_package_zip_file, get_package_zip_file_name}, }; -use ten_rust::pkg_info::get_pkg_info_from_path; #[derive(Debug)] pub struct PackageCommand {} diff --git a/core/src/ten_manager/src/cmd/cmd_publish.rs b/core/src/ten_manager/src/cmd/cmd_publish.rs index 80d3ada8c1..1b9a6b71e0 100644 --- a/core/src/ten_manager/src/cmd/cmd_publish.rs +++ b/core/src/ten_manager/src/cmd/cmd_publish.rs @@ -11,12 +11,13 @@ use clap::{ArgMatches, Command}; use console::Emoji; use indicatif::HumanDuration; +use ten_rust::pkg_info::get_pkg_info_from_path; + use crate::config::TmanConfig; use crate::log::tman_verbose_println; use crate::package_file::create_package_zip_file; use crate::package_file::get_package_zip_file_name; use crate::registry::upload_package; -use ten_rust::pkg_info::get_pkg_info_from_path; #[derive(Debug)] pub struct PublishCommand {} diff --git a/core/src/ten_manager/src/dep_and_candidate/mod.rs b/core/src/ten_manager/src/dep_and_candidate/mod.rs index 1c877c7362..91bd910622 100644 --- a/core/src/ten_manager/src/dep_and_candidate/mod.rs +++ b/core/src/ten_manager/src/dep_and_candidate/mod.rs @@ -7,20 +7,22 @@ use std::collections::{HashMap, HashSet}; use std::hash::{Hash, Hasher}; -use anyhow::Result; +use anyhow::{anyhow, Result}; use semver::{Version, VersionReq}; -use super::config::TmanConfig; -use super::registry::{get_package_list, SearchCriteria}; -use super::utils::pathbuf_to_string; -use crate::package_info::pkg_info_from_find_package_data; use ten_rust::pkg_info::dependencies::PkgDependency; use ten_rust::pkg_info::pkg_identity::PkgIdentity; +use ten_rust::pkg_info::pkg_type::PkgType; use ten_rust::pkg_info::supports::{ is_pkg_supports_compatible_with, PkgSupport, }; use ten_rust::pkg_info::PkgInfo; +use super::config::TmanConfig; +use super::registry::{get_package_list, SearchCriteria}; +use super::utils::pathbuf_to_string; +use crate::package_info::pkg_info_from_find_package_data; + // TODO(Wei): Should use the union of the semantic versioning rather than the // union of all version requirements. #[derive(Default)] @@ -319,3 +321,29 @@ pub async fn get_all_candidates_from_deps( Ok(all_candidates) } + +pub fn get_pkg_info_from_candidates( + pkg_type: &str, + pkg_name: &str, + version: &str, + all_candidates: &HashMap>, +) -> Result { + let pkg_type_enum = pkg_type.parse::()?; + let pkg_identity = PkgIdentity { + pkg_type: pkg_type_enum, + name: pkg_name.to_string(), + }; + let version_parsed = Version::parse(version)?; + let pkg_info = all_candidates + .get(&pkg_identity) + .and_then(|set| set.iter().find(|pkg| pkg.version == version_parsed)) + .ok_or_else(|| { + anyhow!( + "PkgInfo not found for [{}]{}@{}", + pkg_type, + pkg_name, + version + ) + })?; + Ok(pkg_info.clone()) +} diff --git a/core/src/ten_manager/src/dev_server/addons/extensions.rs b/core/src/ten_manager/src/dev_server/addons/extensions.rs index a8e8ca7c0a..e1eb792d62 100644 --- a/core/src/ten_manager/src/dev_server/addons/extensions.rs +++ b/core/src/ten_manager/src/dev_server/addons/extensions.rs @@ -9,6 +9,8 @@ use std::sync::{Arc, RwLock}; use actix_web::{web, HttpResponse, Responder}; use serde::{Deserialize, Serialize}; +use ten_rust::pkg_info::pkg_type::PkgType; + use crate::dev_server::{ common::{ get_dev_server_api_cmd_likes_from_pkg, @@ -20,7 +22,7 @@ use crate::dev_server::{ response::{ApiResponse, ErrorResponse, Status}, DevServerState, }; -use ten_rust::pkg_info::pkg_type::PkgType; + #[derive(Serialize, Deserialize, Debug, PartialEq)] struct DevServerExtensionAddon { diff --git a/core/src/ten_manager/src/dev_server/common.rs b/core/src/ten_manager/src/dev_server/common.rs index 491107ec79..12e8054544 100644 --- a/core/src/ten_manager/src/dev_server/common.rs +++ b/core/src/ten_manager/src/dev_server/common.rs @@ -8,13 +8,14 @@ use std::collections::HashMap; use serde::Deserialize; -use super::graphs::nodes::{ - DevServerApiCmdLike, DevServerApiDataLike, DevServerPropertyAttributes, -}; use ten_rust::pkg_info::api::{ PkgApiCmdLike, PkgApiDataLike, PkgPropertyAttributes, }; +use super::graphs::nodes::{ + DevServerApiCmdLike, DevServerApiDataLike, DevServerPropertyAttributes, +}; + pub fn get_dev_server_property_hashmap_from_pkg( items: HashMap, ) -> HashMap { diff --git a/core/src/ten_manager/src/dev_server/graphs/connections.rs b/core/src/ten_manager/src/dev_server/graphs/connections.rs index c2e5c0a441..d619ba50bc 100644 --- a/core/src/ten_manager/src/dev_server/graphs/connections.rs +++ b/core/src/ten_manager/src/dev_server/graphs/connections.rs @@ -8,16 +8,17 @@ use std::sync::{Arc, RwLock}; use actix_web::{web, HttpResponse, Responder}; use serde::{Deserialize, Serialize}; + use ten_rust::pkg_info::graph::msg_conversion::MsgAndResultConversion; use ten_rust::pkg_info::graph::{ GraphConnection, GraphDestination, GraphMessageFlow, }; +use ten_rust::pkg_info::pkg_type::PkgType; +use ten_rust::pkg_info::predefined_graphs::pkg_predefined_graphs_find; use crate::dev_server::get_all_pkgs::get_all_pkgs; use crate::dev_server::response::{ApiResponse, ErrorResponse, Status}; use crate::dev_server::DevServerState; -use ten_rust::pkg_info::pkg_type::PkgType; -use ten_rust::pkg_info::predefined_graphs::pkg_predefined_graphs_find; #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] pub struct DevServerConnection { diff --git a/core/src/ten_manager/src/dev_server/graphs/mod.rs b/core/src/ten_manager/src/dev_server/graphs/mod.rs index b8126cb9b2..750088e0e7 100644 --- a/core/src/ten_manager/src/dev_server/graphs/mod.rs +++ b/core/src/ten_manager/src/dev_server/graphs/mod.rs @@ -13,12 +13,13 @@ use std::sync::{Arc, RwLock}; use actix_web::{web, HttpResponse, Responder}; use serde::{Deserialize, Serialize}; +use ten_rust::pkg_info::pkg_type::PkgType; + use super::{ get_all_pkgs::get_all_pkgs, response::{ApiResponse, ErrorResponse, Status}, DevServerState, }; -use ten_rust::pkg_info::pkg_type::PkgType; #[derive(Serialize, Deserialize, Debug, PartialEq)] pub struct RespGraph { diff --git a/core/src/ten_manager/src/dev_server/graphs/nodes.rs b/core/src/ten_manager/src/dev_server/graphs/nodes.rs index c8d4121a37..d3484db2dd 100644 --- a/core/src/ten_manager/src/dev_server/graphs/nodes.rs +++ b/core/src/ten_manager/src/dev_server/graphs/nodes.rs @@ -12,6 +12,14 @@ use std::{ use actix_web::{web, HttpResponse, Responder}; use serde::{Deserialize, Serialize}; +use ten_rust::pkg_info::api::{ + PkgApiCmdLike, PkgApiDataLike, PkgPropertyAttributes, PkgPropertyItem, +}; +use ten_rust::pkg_info::predefined_graphs::extension::get_extension_nodes_in_graph; +use ten_rust::pkg_info::{ + api::PkgCmdResult, predefined_graphs::extension::get_pkg_info_for_extension, +}; + use crate::dev_server::common::{ get_dev_server_api_cmd_likes_from_pkg, get_dev_server_api_data_likes_from_pkg, @@ -20,13 +28,6 @@ use crate::dev_server::common::{ use crate::dev_server::get_all_pkgs::get_all_pkgs; use crate::dev_server::response::{ApiResponse, ErrorResponse, Status}; use crate::dev_server::DevServerState; -use ten_rust::pkg_info::api::{ - PkgApiCmdLike, PkgApiDataLike, PkgPropertyAttributes, PkgPropertyItem, -}; -use ten_rust::pkg_info::predefined_graphs::extension::get_extension_nodes_in_graph; -use ten_rust::pkg_info::{ - api::PkgCmdResult, predefined_graphs::extension::get_pkg_info_for_extension, -}; #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] pub struct DevServerExtension { diff --git a/core/src/ten_manager/src/dev_server/graphs/update.rs b/core/src/ten_manager/src/dev_server/graphs/update.rs index aa1a5c92b0..7440da451a 100644 --- a/core/src/ten_manager/src/dev_server/graphs/update.rs +++ b/core/src/ten_manager/src/dev_server/graphs/update.rs @@ -9,11 +9,12 @@ use std::sync::{Arc, RwLock}; use actix_web::{web, HttpResponse, Responder}; use serde::{Deserialize, Serialize}; +use ten_rust::pkg_info::pkg_type::PkgType; +use ten_rust::pkg_info::predefined_graphs::get_pkg_predefined_graph_from_nodes_and_connections; + use super::{connections::DevServerConnection, nodes::DevServerExtension}; use crate::dev_server::response::{ApiResponse, ErrorResponse, Status}; use crate::dev_server::{get_all_pkgs::get_all_pkgs, DevServerState}; -use ten_rust::pkg_info::pkg_type::PkgType; -use ten_rust::pkg_info::predefined_graphs::get_pkg_predefined_graph_from_nodes_and_connections; #[derive(Deserialize, Serialize, Debug, PartialEq, Clone)] pub struct GraphUpdateRequest { diff --git a/core/src/ten_manager/src/dev_server/manifest/check.rs b/core/src/ten_manager/src/dev_server/manifest/check.rs index 985a9f779a..78c07aff12 100644 --- a/core/src/ten_manager/src/dev_server/manifest/check.rs +++ b/core/src/ten_manager/src/dev_server/manifest/check.rs @@ -9,13 +9,14 @@ use std::sync::{Arc, RwLock}; use actix_web::{web, HttpResponse, Responder}; use serde::Serialize; +use ten_rust::pkg_info::pkg_type::PkgType; + use crate::dev_server::{ common::CheckTypeQuery, get_all_pkgs::get_all_pkgs, response::{ApiResponse, ErrorResponse, Status}, DevServerState, }; -use ten_rust::pkg_info::pkg_type::PkgType; #[derive(Serialize)] struct CheckResponse { diff --git a/core/src/ten_manager/src/dev_server/manifest/dump.rs b/core/src/ten_manager/src/dev_server/manifest/dump.rs index 38ca948e05..1132795d8d 100644 --- a/core/src/ten_manager/src/dev_server/manifest/dump.rs +++ b/core/src/ten_manager/src/dev_server/manifest/dump.rs @@ -12,6 +12,9 @@ use std::{ use actix_web::{web, HttpResponse, Responder}; use serde::Serialize; +use ten_rust::pkg_info::manifest::dump_manifest_str_to_file; +use ten_rust::pkg_info::pkg_type::PkgType; + use crate::{ constants::MANIFEST_JSON_FILENAME, dev_server::{ @@ -20,8 +23,6 @@ use crate::{ DevServerState, }, }; -use ten_rust::pkg_info::manifest::dump_manifest_str_to_file; -use ten_rust::pkg_info::pkg_type::PkgType; #[derive(Serialize, Debug, PartialEq)] struct DumpResponse { diff --git a/core/src/ten_manager/src/dev_server/messages/compatible.rs b/core/src/ten_manager/src/dev_server/messages/compatible.rs index 9565dd90bd..dd130a4434 100644 --- a/core/src/ten_manager/src/dev_server/messages/compatible.rs +++ b/core/src/ten_manager/src/dev_server/messages/compatible.rs @@ -12,11 +12,6 @@ use std::{ use actix_web::{web, HttpMessage, HttpRequest, HttpResponse, Responder}; use serde::{Deserialize, Serialize}; -use crate::dev_server::{ - get_all_pkgs::get_all_pkgs, - response::{ApiResponse, ErrorResponse, Status}, - DevServerState, -}; use ten_rust::pkg_info::{ message::{MsgDirection, MsgType}, predefined_graphs::extension::{ @@ -26,6 +21,12 @@ use ten_rust::pkg_info::{ }, }; +use crate::dev_server::{ + get_all_pkgs::get_all_pkgs, + response::{ApiResponse, ErrorResponse, Status}, + DevServerState, +}; + #[derive(Debug, Deserialize, Serialize)] pub struct InputData { pub app: String, @@ -304,11 +305,12 @@ mod tests { use actix_web::{test, App}; use serde_json::json; + use ten_rust::pkg_info::localhost; + use super::*; use crate::{ config::TmanConfig, dev_server::mock::tests::inject_all_pkgs_for_mock, }; - use ten_rust::pkg_info::localhost; #[actix_web::test] async fn test_get_compatible_messages_success() { diff --git a/core/src/ten_manager/src/dev_server/mock.rs b/core/src/ten_manager/src/dev_server/mock.rs index e3251b8afd..49c71879a5 100644 --- a/core/src/ten_manager/src/dev_server/mock.rs +++ b/core/src/ten_manager/src/dev_server/mock.rs @@ -10,10 +10,11 @@ pub mod tests { use anyhow::Result; - use crate::dev_server::DevServerState; use ten_rust::pkg_info::PkgInfo; use ten_rust::pkg_info::{manifest::Manifest, property::Property}; + use crate::dev_server::DevServerState; + pub fn inject_all_pkgs_for_mock( state: &mut DevServerState, all_pkgs_manifest_json: Vec<(String, String)>, diff --git a/core/src/ten_manager/src/dev_server/mod.rs b/core/src/ten_manager/src/dev_server/mod.rs index f5672bb2ae..9a6555fd17 100644 --- a/core/src/ten_manager/src/dev_server/mod.rs +++ b/core/src/ten_manager/src/dev_server/mod.rs @@ -20,8 +20,9 @@ use std::sync::{Arc, RwLock}; use actix_web::{web, HttpRequest, HttpResponse}; -use super::config::TmanConfig; use ten_rust::pkg_info::PkgInfo; + +use super::config::TmanConfig; use version::get_version; pub struct DevServerState { diff --git a/core/src/ten_manager/src/dev_server/property/check.rs b/core/src/ten_manager/src/dev_server/property/check.rs index 4b93a42d1b..68ff667351 100644 --- a/core/src/ten_manager/src/dev_server/property/check.rs +++ b/core/src/ten_manager/src/dev_server/property/check.rs @@ -9,13 +9,14 @@ use std::sync::{Arc, RwLock}; use actix_web::{web, HttpResponse, Responder}; use serde::{Deserialize, Serialize}; +use ten_rust::pkg_info::pkg_type::PkgType; + use crate::dev_server::{ common::CheckTypeQuery, get_all_pkgs::get_all_pkgs, response::{ApiResponse, ErrorResponse, Status}, DevServerState, }; -use ten_rust::pkg_info::pkg_type::PkgType; #[derive(Serialize, Deserialize)] struct CheckResponse { diff --git a/core/src/ten_manager/src/dev_server/property/dump.rs b/core/src/ten_manager/src/dev_server/property/dump.rs index 910e96fa58..cdaef6d0a4 100644 --- a/core/src/ten_manager/src/dev_server/property/dump.rs +++ b/core/src/ten_manager/src/dev_server/property/dump.rs @@ -12,6 +12,8 @@ use std::{ use actix_web::{web, HttpResponse, Responder}; use serde::Serialize; +use ten_rust::pkg_info::pkg_type::PkgType; + use crate::{ constants::PROPERTY_JSON_FILENAME, dev_server::{ @@ -20,7 +22,6 @@ use crate::{ DevServerState, }, }; -use ten_rust::pkg_info::pkg_type::PkgType; #[derive(Serialize, Debug, PartialEq)] struct DumpResponse { @@ -91,6 +92,12 @@ pub async fn dump_property( #[cfg(test)] mod tests { + use std::{env, fs}; + + use actix_web::{test, App}; + + use ten_rust::pkg_info::property::{parse_property_from_file, Property}; + use super::*; use crate::{ config::TmanConfig, @@ -100,9 +107,6 @@ mod tests { }, utils::read_file_to_string, }; - use actix_web::{test, App}; - use std::{env, fs}; - use ten_rust::pkg_info::property::{parse_property_from_file, Property}; #[actix_web::test] async fn test_dump_property_success() { diff --git a/core/src/ten_manager/src/install/mod.rs b/core/src/ten_manager/src/install/mod.rs index 0ee218a79c..307ceab484 100644 --- a/core/src/ten_manager/src/install/mod.rs +++ b/core/src/ten_manager/src/install/mod.rs @@ -15,13 +15,14 @@ use installed_paths::{ }; use tempfile::NamedTempFile; +use ten_rust::pkg_info::{ + pkg_identity::PkgIdentity, pkg_type::PkgType, PkgInfo, +}; + use super::{config::TmanConfig, fs::merge_folders, registry::get_package}; use crate::{ log::tman_verbose_println, package_file::unzip::extract_and_process_zip, }; -use ten_rust::pkg_info::{ - pkg_identity::PkgIdentity, pkg_type::PkgType, PkgInfo, -}; pub struct PkgIdentityMapping { pub src_pkg_identity: PkgIdentity, diff --git a/core/src/ten_manager/src/log.rs b/core/src/ten_manager/src/log.rs index 860e393b5a..683986a38d 100644 --- a/core/src/ten_manager/src/log.rs +++ b/core/src/ten_manager/src/log.rs @@ -12,4 +12,13 @@ macro_rules! tman_verbose_println { }; } +macro_rules! tman_verbose_print { + ($tman_config:expr, $($arg:tt)*) => { + if $tman_config.verbose { + print!($($arg)*); + } + }; +} + +pub(crate) use tman_verbose_print; pub(crate) use tman_verbose_println; diff --git a/core/src/ten_manager/src/main.rs b/core/src/ten_manager/src/main.rs index 9d823fbbcc..7e79525b59 100644 --- a/core/src/ten_manager/src/main.rs +++ b/core/src/ten_manager/src/main.rs @@ -43,7 +43,7 @@ fn main() { let rt = Runtime::new().unwrap(); let result = rt.block_on(cmd::execute_cmd(&tman_config, command_data)); if let Err(e) = result { - println!("{} Error: {:?}", Emoji("💔", ":-("), e); + println!("{} Error: {:?}", Emoji("❌", ":-("), e); process::exit(1); } diff --git a/core/src/ten_manager/src/manifest_lock/mod.rs b/core/src/ten_manager/src/manifest_lock/mod.rs index 8ff3e72bea..60b4e915fe 100644 --- a/core/src/ten_manager/src/manifest_lock/mod.rs +++ b/core/src/ten_manager/src/manifest_lock/mod.rs @@ -11,7 +11,6 @@ use console::Emoji; use semver::{Version, VersionReq}; use serde::{Deserialize, Serialize}; -use super::constants::MANIFEST_LOCK_JSON_FILENAME; use ten_rust::json_schema::validate_manifest_lock_json_string; use ten_rust::pkg_info::dependencies::PkgDependency; use ten_rust::pkg_info::manifest::support::ManifestSupport; @@ -22,6 +21,8 @@ use ten_rust::pkg_info::{ pkg_identity::PkgIdentity, pkg_type::PkgType, PkgInfo, }; +use super::constants::MANIFEST_LOCK_JSON_FILENAME; + // The `manifest-lock.json` structure. #[derive(Serialize, Deserialize, Debug, Clone)] pub struct ManifestLock { diff --git a/core/src/ten_manager/src/package_file/mod.rs b/core/src/ten_manager/src/package_file/mod.rs index 2a03cab793..250482baa2 100644 --- a/core/src/ten_manager/src/package_file/mod.rs +++ b/core/src/ten_manager/src/package_file/mod.rs @@ -13,6 +13,10 @@ use anyhow::Result; use console::Emoji; use globset::{GlobBuilder, GlobSetBuilder}; use ignore::{overrides::OverrideBuilder, WalkBuilder}; +use zip::zip_files_to_file; + +use ten_rust::pkg_info::manifest::parse_manifest_in_folder; +use ten_rust::pkg_info::PkgInfo; use super::{config::TmanConfig, constants::TEN_PACKAGE_FILE_EXTENSION}; use crate::{ @@ -20,9 +24,6 @@ use crate::{ log::tman_verbose_println, utils::pathbuf_to_string_lossy, }; -use ten_rust::pkg_info::manifest::parse_manifest_in_folder; -use ten_rust::pkg_info::PkgInfo; -use zip::zip_files_to_file; pub fn get_package_zip_file_name(pkg_info: &PkgInfo) -> Result { let output_zip_file_name = format!( diff --git a/core/src/ten_manager/src/package_info/predefined_graphs/connection.rs b/core/src/ten_manager/src/package_info/predefined_graphs/connection.rs index 598f1ac20e..fb3ceafe9a 100644 --- a/core/src/ten_manager/src/package_info/predefined_graphs/connection.rs +++ b/core/src/ten_manager/src/package_info/predefined_graphs/connection.rs @@ -4,13 +4,14 @@ // Licensed under the Apache License, Version 2.0, with certain conditions. // Refer to the "LICENSE" file in the root directory for more information. // -use crate::dev_server::graphs::connections::{ - DevServerConnection, DevServerDestination, DevServerMessageFlow, -}; use ten_rust::pkg_info::graph::{ GraphConnection, GraphDestination, GraphMessageFlow, }; +use crate::dev_server::graphs::connections::{ + DevServerConnection, DevServerDestination, DevServerMessageFlow, +}; + impl From for GraphConnection { fn from(dev_server_connection: DevServerConnection) -> Self { GraphConnection { diff --git a/core/src/ten_manager/src/package_info/predefined_graphs/node.rs b/core/src/ten_manager/src/package_info/predefined_graphs/node.rs index 67da57b080..4d40736b97 100644 --- a/core/src/ten_manager/src/package_info/predefined_graphs/node.rs +++ b/core/src/ten_manager/src/package_info/predefined_graphs/node.rs @@ -4,9 +4,10 @@ // Licensed under the Apache License, Version 2.0, with certain conditions. // Refer to the "LICENSE" file in the root directory for more information. // -use crate::dev_server::graphs::nodes::DevServerExtension; use ten_rust::pkg_info::{graph::GraphNode, pkg_type::PkgType}; +use crate::dev_server::graphs::nodes::DevServerExtension; + impl From for GraphNode { fn from(dev_server_extension: DevServerExtension) -> Self { GraphNode { diff --git a/core/src/ten_manager/src/registry/local.rs b/core/src/ten_manager/src/registry/local.rs index 531457ff51..83e9186d22 100644 --- a/core/src/ten_manager/src/registry/local.rs +++ b/core/src/ten_manager/src/registry/local.rs @@ -15,15 +15,16 @@ use tempfile::NamedTempFile; use walkdir::WalkDir; use zip::ZipArchive; -use super::found_result::RegistryPackageData; -use super::{FoundResult, SearchCriteria}; -use crate::config::TmanConfig; -use crate::constants::{MANIFEST_JSON_FILENAME, TEN_PACKAGE_FILE_EXTENSION}; use ten_rust::pkg_info::manifest::Manifest; use ten_rust::pkg_info::pkg_identity::PkgIdentity; use ten_rust::pkg_info::pkg_type::PkgType; use ten_rust::pkg_info::PkgInfo; +use super::found_result::RegistryPackageData; +use super::{FoundResult, SearchCriteria}; +use crate::config::TmanConfig; +use crate::constants::{MANIFEST_JSON_FILENAME, TEN_PACKAGE_FILE_EXTENSION}; + pub async fn upload_package( base_url: &str, package_file_path: &str, diff --git a/core/src/ten_manager/src/registry/mod.rs b/core/src/ten_manager/src/registry/mod.rs index 9761583f9d..f861c655f0 100644 --- a/core/src/ten_manager/src/registry/mod.rs +++ b/core/src/ten_manager/src/registry/mod.rs @@ -12,9 +12,11 @@ use anyhow::{anyhow, Result}; use semver::{Version, VersionReq}; use tempfile::NamedTempFile; +use ten_rust::pkg_info::{pkg_identity::PkgIdentity, PkgInfo}; + use super::config::TmanConfig; use found_result::FoundResult; -use ten_rust::pkg_info::{pkg_identity::PkgIdentity, PkgInfo}; + pub struct SearchCriteria { pub version_req: VersionReq, diff --git a/core/src/ten_manager/src/registry/remote.rs b/core/src/ten_manager/src/registry/remote.rs index 95bc1f81e4..27453ec07b 100644 --- a/core/src/ten_manager/src/registry/remote.rs +++ b/core/src/ten_manager/src/registry/remote.rs @@ -16,15 +16,16 @@ use tempfile::NamedTempFile; use tokio::sync::RwLock; use tokio::time::sleep; +use ten_rust::pkg_info::{ + pkg_identity::PkgIdentity, supports::get_manifest_supports_from_pkg, + PkgInfo, +}; + use super::{FoundResult, SearchCriteria}; use crate::{ config::TmanConfig, error::TmanError, log::tman_verbose_println, registry::found_result::RegistryPackageData, }; -use ten_rust::pkg_info::{ - pkg_identity::PkgIdentity, supports::get_manifest_supports_from_pkg, - PkgInfo, -}; async fn retry_async<'a, F, T>( tman_config: &TmanConfig, diff --git a/core/src/ten_manager/src/solver/display.lp b/core/src/ten_manager/src/solver/display.lp index aac6105c7b..952ba63f0f 100644 --- a/core/src/ten_manager/src/solver/display.lp +++ b/core/src/ten_manager/src/solver/display.lp @@ -8,13 +8,12 @@ % Used to build the result of the solve. #show selected_pkg_version/3. +#show introducer/6. + % error types -#show error/2. -#show error/3. #show error/4. #show error/5. -#show error/6. -#show error/7. +#show error/12. % debug % #show selected_pkg_weight/3. diff --git a/core/src/ten_manager/src/solver/introducer.rs b/core/src/ten_manager/src/solver/introducer.rs new file mode 100644 index 0000000000..868a402864 --- /dev/null +++ b/core/src/ten_manager/src/solver/introducer.rs @@ -0,0 +1,102 @@ +// +// Copyright © 2024 Agora +// This file is part of TEN Framework, an open source project. +// Licensed under the Apache License, Version 2.0, with certain conditions. +// Refer to the "LICENSE" file in the root directory for more information. +// +use std::collections::{HashMap, HashSet}; + +use anyhow::{anyhow, Result}; +use regex::Regex; +use semver::Version; + +use ten_rust::pkg_info::{ + pkg_identity::PkgIdentity, pkg_type::PkgType, PkgInfo, +}; + +use crate::dep_and_candidate::get_pkg_info_from_candidates; + +pub fn extract_introducer_relations_from_raw_solver_results( + results: &[String], + all_candidates: &HashMap>, +) -> Result>> { + let re = Regex::new( + r#"introducer\("([^"]+)","([^"]+)","([^"]+)","([^"]+)","([^"]*)","([^"]*)"\)"#, + ) + .unwrap(); + + let mut introducer_relations: HashMap> = + HashMap::new(); + + for result in results.iter() { + if let Some(caps) = re.captures(result) { + let pkg_type_str = caps.get(1).unwrap().as_str(); + let name = caps.get(2).unwrap().as_str(); + let version_str = caps.get(3).unwrap().as_str(); + + let introducer_type_str = caps.get(4).unwrap().as_str(); + let introducer_name = caps.get(5).unwrap().as_str(); + let introducer_version_str = caps.get(6).unwrap().as_str(); + + let pkg_info = get_pkg_info_from_candidates( + pkg_type_str, + name, + version_str, + all_candidates, + )?; + + let introducer = if introducer_type_str == "root" { + None + } else { + let introducer_info = get_pkg_info_from_candidates( + introducer_type_str, + introducer_name, + introducer_version_str, + all_candidates, + )?; + + Some(introducer_info.clone()) + }; + + introducer_relations.insert(pkg_info.clone(), introducer); + } + } + + Ok(introducer_relations) +} + +pub fn get_dependency_chain( + pkg_info: &PkgInfo, + introducer_relations: &HashMap>, +) -> Vec { + let mut chain = Vec::new(); + let mut current_pkg = pkg_info.clone(); + let mut visited = HashSet::new(); + + loop { + if !visited.insert(current_pkg.clone()) { + // Detected a cycle, break to prevent infinite loop. + break; + } + + chain.push(current_pkg.clone()); + + match introducer_relations.get(¤t_pkg) { + Some(Some(introducer_pkg)) => { + current_pkg = introducer_pkg.clone(); + } + Some(None) => { + // Reached the root. + break; + } + None => { + panic!("Introducer not found, possibly an error."); + break; + } + } + } + + // Reverse the chain to start from the root. + chain.reverse(); + chain +} diff --git a/core/src/ten_manager/src/solver/main.lp b/core/src/ten_manager/src/solver/main.lp index c60ac4bed6..b7ce2f0c74 100644 --- a/core/src/ten_manager/src/solver/main.lp +++ b/core/src/ten_manager/src/solver/main.lp @@ -9,17 +9,27 @@ % 'error' is intentionally left undefined within the logic program. #defined error/4. #defined error/5. +#defined error/12. %----------------------------------------------------------------------------- % Version semantics %----------------------------------------------------------------------------- -% If a package is selected (selected_pkg == true), then choose one version -% (selected_pkg_version) from all available versions of that package +% If a package is selected (selected_pkg == true), then choose at least 1 +% versions (selected_pkg_version) from all available versions of that package % (version_declared). -1 { selected_pkg_version(PkgType, PkgName, PkgVersion) : version_declared(PkgType, PkgName, PkgVersion, PkgWeight) } 1 +1 <= { selected_pkg_version(PkgType, PkgName, PkgVersion) : version_declared(PkgType, PkgName, PkgVersion, PkgWeight) } :- selected_pkg(PkgType, PkgName). +error(100, "Select more than 1 version of '[{0}]{1}': '@{2}' introduced by '[{4}]{5}@{6}', and '@{3}' introduced by '[{7}]{8}@{9}'", + PkgType, PkgName, PkgVersion1, PkgVersion2, IntroducerType1, IntroducerName1, IntroducerVersion1, IntroducerType2, IntroducerName2, IntroducerVersion2) + :- selected_pkg(PkgType, PkgName), + selected_pkg_version(PkgType, PkgName, PkgVersion1), + selected_pkg_version(PkgType, PkgName, PkgVersion2), + PkgVersion1 < PkgVersion2, + introducer(PkgType, PkgName, PkgVersion1, IntroducerType1, IntroducerName1, IntroducerVersion1), + introducer(PkgType, PkgName, PkgVersion2, IntroducerType2, IntroducerName2, IntroducerVersion2). + % Get the weight of the selected version of the selected package. This rule % declares that only one selected_pkg_weight fact can be derived based on the % conditions provided. @@ -31,12 +41,33 @@ selected_pkg(PkgType, PkgName) :- selected_pkg_version(PkgType, PkgName, PkgVersion). +%----------------------------------------------------------------------------- +% Tracking Introducers +%----------------------------------------------------------------------------- + +% The introducer predicate is used to keep track of how each package version was +% introduced into the selection - either as a root package or as a dependency of +% another package. + +introducer(PkgType, PkgName, PkgVersion, IntroducerType, IntroducerName, IntroducerVersion) + :- depends_on(IntroducerType, IntroducerName, IntroducerVersion, PkgType, PkgName, PkgVersion), + selected_pkg_version(IntroducerType, IntroducerName, IntroducerVersion), + selected_pkg_version(PkgType, PkgName, PkgVersion). + +% This rule serves to record that a package version is introduced into the +% selection directly as a root package, rather than as a dependency of another +% package. Specifically, it defines an introducer fact for packages that are +% declared as roots. +introducer(PkgType, PkgName, PkgVersion, "root", "", "") + :- root_declared(PkgType, PkgName), + selected_pkg_version(PkgType, PkgName, PkgVersion). + %----------------------------------------------------------------------------- % Dependency semantics %----------------------------------------------------------------------------- % It's an error when depending on a version which is not declared. -error(0, "Depends on an undeclared version '@{2}' of '{0}::{1}'", PkgType, PkgName, PkgVersion) +error(100, "Depends on an undeclared version '@{2}' of '{0}::{1}'", PkgType, PkgName, PkgVersion) :- not version_declared(PkgType, PkgName, PkgVersion, _), depends_on_declared(_, _, PkgType, PkgName, PkgVersion). @@ -60,7 +91,7 @@ needed(DepType, DepName) :- needed(PkgType, PkgName), depends_on(PkgType, PkgName, _, DepType, DepName, _). -error(0, "'{0}::{1}' is not a valid dependency for any package in the DAG", PkgType, PkgName) +error(100, "'{0}::{1}' is not a valid dependency for any package in the DAG", PkgType, PkgName) :- selected_pkg(PkgType, PkgName), not needed(PkgType, PkgName). % @} @@ -70,7 +101,7 @@ error(0, "'{0}::{1}' is not a valid dependency for any package in the DAG", PkgT %----------------------------------------------------------------------------- % @{ -% Enable clingo to solve selected_pkg_version/2 and selected_pkg_weight/2 first. +% Enable clingo to solve selected_pkg_version/3 and selected_pkg_weight/3 first. #heuristic selected_pkg_version(PkgType, PkgName, PkgVersion) : version_declared(PkgType, PkgName, PkgVersion, _), selected_pkg(PkgType, PkgName). [10, true] #heuristic selected_pkg_weight(PkgType, PkgName, _) : version_declared(PkgType, PkgName, PkgVersion, _), selected_pkg(PkgType, PkgName). [10, true] @@ -89,7 +120,8 @@ error(0, "'{0}::{1}' is not a valid dependency for any package in the DAG", PkgT % First minimize the weight of packages declared as root packages. PkgWeight@998,PkgType,PkgName : root_declared(PkgType, PkgName), selected_pkg_weight(PkgType, PkgName, PkgWeight); - % Then minimize the weight of all selected packages,regardless of whether they are root packages. + % Then minimize the weight of all selected packages, regardless of whether + % they are root packages. PkgWeight@999,PkgType,PkgName : selected_pkg_weight(PkgType, PkgName, PkgWeight) }. @@ -101,9 +133,8 @@ error(0, "'{0}::{1}' is not a valid dependency for any package in the DAG", PkgT % to explain why something failed. Here we optimize HEAVILY against the facts % generated by those rules. -%{ -% The following line said that to minimize the count of 'error(Priority, Msg)' -% in each 'Priority'. -#minimize{ 1000@1000+Priority,Msg,Arg1,Arg2: error(Priority, Msg, Arg1, Arg2) }. -#minimize{ 1000@1000+Priority,Msg,Arg1,Arg2,Arg3: error(Priority, Msg, Arg1, Arg2, Arg3) }. -%} +#minimize{ 0@1000: #true}. + +#minimize{ ErrWeight@1000,Msg,Arg1,Arg2: error(ErrWeight, Msg, Arg1, Arg2) }. +#minimize{ ErrWeight@1000,Msg,Arg1,Arg2,Arg3: error(ErrWeight, Msg, Arg1, Arg2, Arg3) }. +#minimize{ ErrWeight@1000,Msg,Arg1,Arg2,Arg3,Arg4,Arg5,Arg6,Arg7,Arg8,Arg9,Arg10: error(ErrWeight, Msg, Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7, Arg8, Arg9, Arg10) }. diff --git a/core/src/ten_manager/src/solver/mod.rs b/core/src/ten_manager/src/solver/mod.rs index 395f5585c7..d41cf2041a 100644 --- a/core/src/ten_manager/src/solver/mod.rs +++ b/core/src/ten_manager/src/solver/mod.rs @@ -4,5 +4,7 @@ // Licensed under the Apache License, Version 2.0, with certain conditions. // Refer to the "LICENSE" file in the root directory for more information. // +pub mod introducer; pub mod solve; +pub mod solver_error; pub mod solver_result; diff --git a/core/src/ten_manager/src/solver/solve.rs b/core/src/ten_manager/src/solver/solve.rs index 5b5c0a3131..1f528d638a 100644 --- a/core/src/ten_manager/src/solver/solve.rs +++ b/core/src/ten_manager/src/solver/solve.rs @@ -7,48 +7,170 @@ use std::collections::{HashMap, HashSet}; use anyhow::Result; +use clingo::{ + control, Configuration, ConfigurationType, Id, Model, Part, ShowType, + SolveMode, Statistics, StatisticsType, +}; -use crate::{config::TmanConfig, error::TmanError, log::tman_verbose_println}; use ten_rust::pkg_info::{ dependencies::DependencyRelationship, pkg_identity::PkgIdentity, pkg_type::PkgType, PkgInfo, }; -fn get_model( - tman_config: &TmanConfig, - model: &clingo::Model, -) -> Option> { +use crate::{ + config::TmanConfig, + error::TmanError, + log::{tman_verbose_print, tman_verbose_println}, +}; + +fn get_model(tman_config: &TmanConfig, model: &Model) -> Option> { // Retrieve the symbols in the model. let atoms = model - .symbols(clingo::ShowType::SHOWN) + .symbols(ShowType::SHOWN) .expect("Failed to retrieve symbols in the model."); tman_verbose_println!(tman_config, "Model:"); let mut result = Vec::new(); - let mut can_be_used = true; for symbol in atoms { tman_verbose_println!(tman_config, " {}", symbol); result.push(symbol.to_string()); - if symbol.to_string().starts_with("error(") { - can_be_used = false; - } } tman_verbose_println!(tman_config, ""); - if !can_be_used { - return None; - } Some(result) } +fn print_prefix(tman_config: &TmanConfig, depth: u8) { + tman_verbose_println!(tman_config, ""); + for _ in 0..depth { + tman_verbose_print!(tman_config, " "); + } +} + +// Recursively print the configuration object. +fn print_configuration( + tman_config: &TmanConfig, + conf: &Configuration, + key: Id, + depth: u8, +) { + let configuration_type = conf.configuration_type(key).unwrap(); + if configuration_type.contains(ConfigurationType::VALUE) { + let value = conf + .value_get(key) + .expect("Failed to retrieve statistics value."); + + tman_verbose_print!(tman_config, "{}", value); + } else if configuration_type.contains(ConfigurationType::ARRAY) { + let size = conf + .array_size(key) + .expect("Failed to retrieve statistics array size."); + for i in 0..size { + let subkey = conf + .array_at(key, i) + .expect("Failed to retrieve statistics array."); + print_prefix(tman_config, depth); + tman_verbose_print!(tman_config, "{}: ", i); + + print_configuration(tman_config, conf, subkey, depth + 1); + } + } else if configuration_type.contains(ConfigurationType::MAP) { + let size = conf.map_size(key).unwrap(); + for i in 0..size { + let name = conf.map_subkey_name(key, i).unwrap(); + let subkey = conf.map_at(key, name).unwrap(); + print_prefix(tman_config, depth); + tman_verbose_print!(tman_config, "{}: ", name); + + print_configuration(tman_config, conf, subkey, depth + 1); + } + } else { + tman_verbose_println!(tman_config, "Unknown ConfigurationType"); + unreachable!() + } +} + +// recursively print the statistics object +fn print_statistics( + tman_config: &TmanConfig, + stats: &Statistics, + key: u64, + depth: u8, +) { + // Get the type of an entry and switch over its various values. + let statistics_type = stats.statistics_type(key).unwrap(); + match statistics_type { + StatisticsType::Value => { + let value = stats + .value_get(key) + .expect("Failed to retrieve statistics value."); + + tman_verbose_print!(tman_config, " {}", value); + } + + StatisticsType::Array => { + let size = stats + .array_size(key) + .expect("Failed to retrieve statistics array size."); + for i in 0..size { + let subkey = stats + .array_at(key, i) + .expect("Failed to retrieve statistics array."); + print_prefix(tman_config, depth); + tman_verbose_print!(tman_config, "{} zu:", i); + + print_statistics(tman_config, stats, subkey, depth + 1); + } + } + + StatisticsType::Map => { + let size = stats.map_size(key).unwrap(); + for i in 0..size { + let name = stats.map_subkey_name(key, i).unwrap(); + let subkey = stats.map_at(key, name).unwrap(); + print_prefix(tman_config, depth); + tman_verbose_print!(tman_config, "{}:", name); + + print_statistics(tman_config, stats, subkey, depth + 1); + } + } + + StatisticsType::Empty => { + tman_verbose_println!(tman_config, "StatisticsType::Empty"); + } + } +} + fn solve(tman_config: &TmanConfig, input: &str) -> Result> { // Create a control object. // i.e., clingo_control_new - let mut ctl = - clingo::control([].to_vec()).expect("Failed creating Control."); + let mut ctl = control({ + let mut args = vec![ + "--opt-mode=opt".to_string(), + "--heuristic=berkmin".to_string(), + // "-n".to_string(), + // "0".to_string(), + ]; + + if tman_config.verbose { + args.push("--verbose".to_string()); + } + + args + }) + .expect("Failed creating Control."); + + { + // Get the configuration object and its root key. + let conf = ctl.configuration_mut().unwrap(); + let root_key = conf.root().unwrap(); + + print_configuration(tman_config, conf, root_key, 0); + tman_verbose_println!(tman_config, ""); + } let main_program = include_str!("main.lp"); let display_program = include_str!("display.lp"); @@ -63,45 +185,37 @@ fn solve(tman_config: &TmanConfig, input: &str) -> Result> { // Ground the parts. // i.e., clingo_control_ground - let main_part = clingo::Part::new("main", vec![]).unwrap(); - let display_part = clingo::Part::new("display", vec![]).unwrap(); - let base_part = clingo::Part::new("base", vec![]).unwrap(); + let main_part = Part::new("main", vec![]).unwrap(); + let display_part = Part::new("display", vec![]).unwrap(); + let base_part = Part::new("base", vec![]).unwrap(); let parts = vec![main_part, display_part, base_part]; ctl.ground(&parts) .expect("Failed to ground a logic program."); - let conf = ctl.configuration_mut()?; - - // Navigate to the solve.models setting and modify it. - let root_key = conf.root()?; - let solve_key = conf.map_at(root_key, "solve")?; - let models_key = conf.map_at(solve_key, "models")?; - // Setting it to "0" means finding all models. - conf.value_set(models_key, "0")?; - // Solving. Get a solve handle. // i.e., clingo_control_solve let mut handle = ctl - .solve(clingo::SolveMode::YIELD, &[]) + .solve(SolveMode::YIELD, &[]) .expect("Failed retrieving solve handle."); let mut result_model = Vec::new(); - // Loop over all models + // Loop over all models. loop { // i.e., clingo_solve_handle_resume handle.resume().expect("Failed resume on solve handle."); // i.e., clingo_solve_handle_model match handle.model() { - // print the model + // Get the model. Ok(Some(model)) => { if let Some(m) = get_model(tman_config, model) { result_model = m; + break; } } - // stop if there are no more models + // Stop if there are no more models. Ok(None) => { tman_verbose_println!(tman_config, "No more models"); break; @@ -110,6 +224,14 @@ fn solve(tman_config: &TmanConfig, input: &str) -> Result> { } } + // If no models were found, return an error. + if result_model.is_empty() { + return Err(TmanError::Custom( + "Unable to resolve dependencies successfully, please report the dependency issue to ten framework github issue.".to_string(), + ) + .into()); + } + // Close the solve handle. // i.e., clingo_solve_handle_get let result = handle @@ -120,7 +242,13 @@ fn solve(tman_config: &TmanConfig, input: &str) -> Result> { // Free the solve handle. // i.e., clingo_solve_handle_close - handle.close().expect("Failed to close solve handle."); + ctl = handle.close().expect("Failed to close solve handle."); + + // Get the statistics object, get the root key, then print the statistics + // recursively. + let stats = ctl.statistics().unwrap(); + let stats_key = stats.root().unwrap(); + print_statistics(tman_config, stats, stats_key, 0); Ok(result_model) } @@ -213,7 +341,7 @@ fn create_input_str_for_pkg_info_dependencies( if !found_matched { return Err(TmanError::Custom( format!( - "Failed to find candidates for {}::{}({})", + "Failed to find candidates for [{}]{}({})", dependency.pkg_identity.pkg_type, dependency.pkg_identity.name, dependency.version_req diff --git a/core/src/ten_manager/src/solver/solver_error.rs b/core/src/ten_manager/src/solver/solver_error.rs new file mode 100644 index 0000000000..6495d5de66 --- /dev/null +++ b/core/src/ten_manager/src/solver/solver_error.rs @@ -0,0 +1,145 @@ +// +// Copyright © 2024 Agora +// This file is part of TEN Framework, an open source project. +// Licensed under the Apache License, Version 2.0, with certain conditions. +// Refer to the "LICENSE" file in the root directory for more information. +// +use std::collections::{HashMap, HashSet}; + +use anyhow::{anyhow, Result}; +use console::Emoji; +use regex::Regex; + +use ten_rust::pkg_info::{pkg_identity::PkgIdentity, PkgInfo}; + +use crate::{ + config::TmanConfig, dep_and_candidate::get_pkg_info_from_candidates, + solver::introducer::get_dependency_chain, +}; + +#[derive(Debug)] +pub struct ConflictInfo { + pub pkg_type: String, + pub pkg_name: String, + pub version1: String, + pub version2: String, + pub error_message: String, +} + +pub fn parse_error_statement(clingo_output: &[String]) -> Result { + // Regular expression to match the error statement. + let error_re = Regex::new( + r#"error\(\s*\d+,\s*"([^"]+)",\s*"([^"]+)",\s*"([^"]+)",\s*"([^"]+)",\s*"([^"]+)",\s*"([^"]+)",\s*"([^"]+)",\s*"([^"]+)",\s*"([^"]+)",\s*"([^"]+)",\s*"([^"]+)"\)"#, + )?; + + for line in clingo_output { + if let Some(caps) = error_re.captures(line) { + let error_template = caps.get(1).unwrap().as_str(); + let pkg_type = caps.get(2).unwrap().as_str().to_string(); + let pkg_name = caps.get(3).unwrap().as_str().to_string(); + let version1 = caps.get(4).unwrap().as_str().to_string(); + let version2 = caps.get(5).unwrap().as_str().to_string(); + let introducer1_type = caps.get(6).unwrap().as_str().to_string(); + let introducer1_name = caps.get(7).unwrap().as_str().to_string(); + let introducer1_version = caps.get(8).unwrap().as_str().to_string(); + let introducer2_type = caps.get(9).unwrap().as_str().to_string(); + let introducer2_name = caps.get(10).unwrap().as_str().to_string(); + let introducer2_version = + caps.get(11).unwrap().as_str().to_string(); + + // Format the error message with the extracted arguments. + let error_message = error_template + .replace("{0}", &pkg_type) + .replace("{1}", &pkg_name) + .replace("{2}", &version1) + .replace("{3}", &version2) + .replace("{4}", &introducer1_type) + .replace("{5}", &introducer1_name) + .replace("{6}", &introducer1_version) + .replace("{7}", &introducer2_type) + .replace("{8}", &introducer2_name) + .replace("{9}", &introducer2_version); + + return Ok(ConflictInfo { + pkg_type, + pkg_name, + version1, + version2, + error_message, + }); + } + } + + Err(anyhow!("No error message found in the Clingo output.")) +} + +fn print_dependency_chain( + chain: &[PkgInfo], + pkg_type: &str, + pkg_name: &str, + version: &str, +) { + println!( + "Dependency chain leading to [{}]{}@{}:", + pkg_type, pkg_name, version + ); + for (i, pkg) in chain.iter().enumerate() { + println!( + "{:indent$}└─ [{}]{}@{}", + "", + pkg.pkg_identity.pkg_type, + pkg.pkg_identity.name, + pkg.version, + indent = i * 3 + ); + } + println!(); +} + +pub fn print_conflict_info( + tman_config: &TmanConfig, + conflict_info: &ConflictInfo, + introducer_relations: &HashMap>, + all_candidates: &HashMap>, +) -> Result<()> { + println!( + "{} Error: {}", + Emoji("❌", ":-("), + conflict_info.error_message + ); + println!(); + + // Get PkgInfo for both versions + let pkg_info1 = get_pkg_info_from_candidates( + &conflict_info.pkg_type, + &conflict_info.pkg_name, + &conflict_info.version1, + all_candidates, + )?; + let pkg_info2 = get_pkg_info_from_candidates( + &conflict_info.pkg_type, + &conflict_info.pkg_name, + &conflict_info.version2, + all_candidates, + )?; + + // Get dependency chains + let chain1 = get_dependency_chain(&pkg_info1, introducer_relations); + let chain2 = get_dependency_chain(&pkg_info2, introducer_relations); + + print_dependency_chain( + &chain1, + &conflict_info.pkg_type, + &conflict_info.pkg_name, + &conflict_info.version1, + ); + + print_dependency_chain( + &chain2, + &conflict_info.pkg_type, + &conflict_info.pkg_name, + &conflict_info.version2, + ); + + Ok(()) +} diff --git a/core/src/ten_manager/src/solver/solver_result.rs b/core/src/ten_manager/src/solver/solver_result.rs index 509ebd8101..31bfb9f966 100644 --- a/core/src/ten_manager/src/solver/solver_result.rs +++ b/core/src/ten_manager/src/solver/solver_result.rs @@ -15,6 +15,10 @@ use indicatif::{ProgressBar, ProgressStyle}; use regex::Regex; use semver::Version; +use ten_rust::pkg_info::{ + pkg_identity::PkgIdentity, pkg_type::PkgType, PkgInfo, +}; + use crate::{ config::TmanConfig, constants::{ @@ -23,9 +27,6 @@ use crate::{ }, install::{install_pkg_info, PkgIdentityMapping}, }; -use ten_rust::pkg_info::{ - pkg_identity::PkgIdentity, pkg_type::PkgType, PkgInfo, -}; pub fn extract_solver_results_from_raw_solver_results( results: &[String], @@ -46,14 +47,13 @@ pub fn extract_solver_results_from_raw_solver_results( let pkg_type = pkg_type_str.parse::()?; let semver = semver_str.parse::()?; - let candidates = all_candidates + for candidate in all_candidates .get(&PkgIdentity { pkg_type: pkg_type.clone(), name: name.to_string(), }) - .unwrap(); - - for candidate in candidates { + .unwrap() + { if candidate.pkg_identity.pkg_type != pkg_type || candidate.pkg_identity.name != name { @@ -65,8 +65,6 @@ pub fn extract_solver_results_from_raw_solver_results( continue 'outer; } } - } else { - panic!("Should not happen. No match found."); } } diff --git a/docs/ten_framework/ten_manager/check_graph.md b/docs/ten_framework/ten_manager/check_graph.md index 36aa9e9b29..5a4209144d 100644 --- a/docs/ten_framework/ten_manager/check_graph.md +++ b/docs/ten_framework/ten_manager/check_graph.md @@ -91,7 +91,7 @@ The `nodes` array is required in any graph definition. If absent, an error will No extension node is defined in graph. All is done. - 💔 Error: 1/1 graphs failed. + ❌ Error: 1/1 graphs failed. ``` ### 2. Uniqueness of Nodes @@ -130,7 +130,7 @@ Each node in the `nodes` array represents a specific extension instance within a Duplicated extension was found in nodes[1], addon: basic_hello_world_1__test_extension, name: test_extension. All is done. - 💔 Error: 1/1 graphs failed. + ❌ Error: 1/1 graphs failed. ``` ### 3. Extensions used in connections should be defined in nodes @@ -190,7 +190,7 @@ All extension instances referenced in the `connections` field, whether as a sour The extension declared in connections[0] is not defined in nodes, extension_group: producer, extension: some_extension. All is done. - 💔 Error: 1/1 graphs failed. + ❌ Error: 1/1 graphs failed. ``` - **Example (Destination extension not defined)**: @@ -265,7 +265,7 @@ All extension instances referenced in the `connections` field, whether as a sour The extension declared in connections[0].cmd[1] is not defined in nodes extension_group: some_group, extension: consumer. All is done. - 💔 Error: 1/1 graphs failed. + ❌ Error: 1/1 graphs failed. ``` ### 4. The addons declared in the `nodes` must be installed in the app @@ -307,7 +307,7 @@ All extension instances referenced in the `connections` field, whether as a sour The following packages are declared in nodes but not installed: [("localhost", Extension, "default_extension_go")]. All is done. - 💔 Error: 1/1 graphs failed. + ❌ Error: 1/1 graphs failed. ``` The problem is all packages in the app will be stored in a map which key is the `uri` of the app, and each node in the graph is retrieved by the `app` field (which is `localhost` by default). The `app` in node (i.e., localhost) is mismatch with the `uri` of app (i.e., ). @@ -350,7 +350,7 @@ All extension instances referenced in the `connections` field, whether as a sour The following packages are declared in nodes but not installed: [("localhost", Extension, "default_extension_go")]. All is done. - 💔 Error: 1/1 graphs failed. + ❌ Error: 1/1 graphs failed. ``` ### 5. In connections, messages sent from one extension should be defined in the same section @@ -417,7 +417,7 @@ All extension instances referenced in the `connections` field, whether as a sour extension 'some_extension' is defined in connection[0] and connection[1], merge them into one section. All is done. - 💔 Error: 1/1 graphs failed. + ❌ Error: 1/1 graphs failed. ``` ### 6. In connections, the messages sent out from one extension should have a unique name in each type @@ -480,7 +480,7 @@ All extension instances referenced in the `connections` field, whether as a sour 'hello' is defined in flow[0] and flow[1]. All is done. - 💔 Error: 1/1 graphs failed. + ❌ Error: 1/1 graphs failed. ``` ### 7. The messages declared in the connections should be compatible @@ -609,7 +609,7 @@ The message declared in each message flow in the connections will be checked if - cmd[0]: Schema incompatible to [extension_group: some_group, extension: another_ext], { .foo: type is incompatible, source is [string], but target is [int8] } All is done. - 💔 Error: 1/1 graphs failed. + ❌ Error: 1/1 graphs failed. ``` ### 8. The `app` in node must be unambiguous @@ -648,7 +648,7 @@ The `app` field in each node must met the following rules. **Output**: ```text - 💔 Error: The graph json string is invalid + ❌ Error: The graph json string is invalid Caused by: Either all nodes should have 'app' declared, or none should, but not a mix of both. @@ -698,7 +698,7 @@ The `app` field in each node must met the following rules. **Output**: ```text - 💔 Error: The graph json string is invalid + ❌ Error: The graph json string is invalid Caused by: connections[0].the 'app' should not be declared, as not any node has declared it @@ -748,7 +748,7 @@ The `app` field in each node must met the following rules. **Output**: ```text - 💔 Error: The graph json string is invalid + ❌ Error: The graph json string is invalid Caused by: connections[0].cmd[0].dest[0]: the 'app' should not be declared, as not any node has declared it @@ -802,7 +802,7 @@ The `app` field in each node must met the following rules. **Output**: ```text - 💔 Error: Failed to parse graph string, nodes[1]: 'localhost' is not allowed in graph definition, and the graph seems to be a single-app graph, just remove the 'app' field + ❌ Error: Failed to parse graph string, nodes[1]: 'localhost' is not allowed in graph definition, and the graph seems to be a single-app graph, just remove the 'app' field ``` - **Example (The `app` field is `localhost` in a multi-app graph)**: @@ -851,5 +851,5 @@ The `app` field in each node must met the following rules. **Output**: ```text - 💔 Error: Failed to parse graph string, nodes[1]: 'localhost' is not allowed in graph definition, change the content of 'app' field to be consistent with '_ten::uri' + ❌ Error: Failed to parse graph string, nodes[1]: 'localhost' is not allowed in graph definition, change the content of 'app' field to be consistent with '_ten::uri' ```