Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: conflicted error msg #235

Merged
merged 8 commits into from
Nov 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 66 additions & 6 deletions core/src/ten_manager/src/cmd/cmd_install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -282,12 +282,16 @@ fn write_pkgs_into_lock(pkgs: &Vec<&PkgInfo>, app_dir: &Path) -> Result<()> {

fn parse_pkg_name_version(
pkg_name_version: &str,
) -> Result<(String, VersionReq)> {
) -> Result<(String, Option<String>, VersionReq)> {
let parts: Vec<&str> = pkg_name_version.split('@').collect();
if parts.len() == 2 {
Ok((parts[0].to_string(), VersionReq::parse(parts[1])?))
Ok((
parts[0].to_string(),
Some(parts[1].to_string()),
VersionReq::parse(parts[1])?,
))
} else {
Ok((pkg_name_version.to_string(), VersionReq::STAR))
Ok((pkg_name_version.to_string(), None, VersionReq::STAR))
}
}

Expand Down Expand Up @@ -499,8 +503,11 @@ pub async fn execute_cmd(
// Case 1: tman install <package_type> <package_name>

let desired_pkg_type_: PkgType = package_type_str.parse()?;
let (desired_pkg_src_name_, desired_pkg_src_version_) =
parse_pkg_name_version(&command_data.package_name.unwrap())?;
let (
desired_pkg_src_name_,
desired_pkg_src_version_str_,
desired_pkg_src_version_,
) = parse_pkg_name_version(&command_data.package_name.unwrap())?;

desired_pkg_type = Some(desired_pkg_type_.clone());
desired_pkg_src_name = Some(desired_pkg_src_name_.clone());
Expand Down Expand Up @@ -552,6 +559,7 @@ pub async fn execute_cmd(
name: desired_pkg_src_name_.clone(),
},
version_req: desired_pkg_src_version_.clone(),
version_req_str: desired_pkg_src_version_str_,
},
};
extra_dependency_relationships.push(extra_dependency_relationship);
Expand Down Expand Up @@ -853,7 +861,59 @@ do you want to continue?",

Ok(())
} else {
if let Some(non_usable_model) = non_usable_models.first() {
// The last model always represents the optimal version.
//
// The first error messages (i.e., non_usable_models.first()) might be
// changed when we run `tman install` in an app folder, if the app
// contains a conflicted dependency which has many candidates. The
// different error messages are as follows.
//
// ```
// ❌ Error: Select more than 1 version of '[system]ten_runtime':
// '@0.2.0' introduced by '[extension][email protected]', and '@0.3.1'
// introduced by '[system][email protected]'
// ```
//
// ```
// ❌ Error: Select more than 1 version of '[system]ten_runtime':
// '@0.2.0' introduced by '[extension][email protected]', and '@0.3.0'
// introduced by '[system][email protected]'
// ```
//
// The introducer pkg are different in the two error messages,
// `[email protected]` vs `[email protected]`.
//
// The reason is that the candidates are stored in a HashSet, and when
// they are traversed and written to the `depends_on_declared` field in
// `input.lp``, the order of writing is not guaranteed. Ex, the
// `agora_rtm` has three candidates, 0.1.0, 0.1.1, 0.1.2. The
// content in the input.ip might be:
//
// case 1:
// ```
// depends_on_declared("app", "ten_agent", "0.4.0", "extension", "agora_rtm", "0.1.0").
// depends_on_declared("app", "ten_agent", "0.4.0", "extension", "agora_rtm", "0.1.1").
// depends_on_declared("app", "ten_agent", "0.4.0", "extension", "agora_rtm", "0.1.2").
// ```
//
// or
//
// case 2:
// ```
// depends_on_declared("app", "ten_agent", "0.4.0", "extension", "agora_rtm", "0.1.2").
// depends_on_declared("app", "ten_agent", "0.4.0", "extension", "agora_rtm", "0.1.0").
// depends_on_declared("app", "ten_agent", "0.4.0", "extension", "agora_rtm", "0.1.1").
// ```
// Due to the different order of input data, clingo may start searching
// from different points. However, clingo will only output increasingly
// better models. Therefore, if we select the first `non_usable_models`,
// it is often inconsistent. But if we select the last model, it is
// usually consistent, representing the optimal error model that clingo
// can find. This phenomenon is similar to the gradient descent process
// in neural networks that seeks local optima. Thus, we should select
// the last model to obtain the optimal error model and achieve stable
// results.
if let Some(non_usable_model) = non_usable_models.last() {
// Extract introducer relations, and parse the error message.
if let Ok(conflict_info) = parse_error_statement(non_usable_model) {
// Print the error message and dependency chains.
Expand Down
1 change: 1 addition & 0 deletions core/src/ten_manager/src/manifest_lock/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,7 @@ impl<'a> From<&'a ManifestLockItem> for PkgInfo {
name: dep.name,
},
version_req: VersionReq::STAR,
version_req_str: None,
})
.collect()
})
Expand Down
74 changes: 63 additions & 11 deletions core/src/ten_manager/src/solver/introducer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,17 @@ use ten_rust::pkg_info::{pkg_identity::PkgIdentity, PkgInfo};

use crate::dep_and_candidate::get_pkg_info_from_candidates;

/// Returns a map from a package to its introducer and the requested version.
pub fn extract_introducer_relations_from_raw_solver_results(
results: &[String],
all_candidates: &HashMap<PkgIdentity, HashSet<PkgInfo>>,
) -> Result<HashMap<PkgInfo, Option<PkgInfo>>> {
) -> Result<HashMap<PkgInfo, (String, Option<PkgInfo>)>> {
let re = Regex::new(
r#"introducer\("([^"]+)","([^"]+)","([^"]+)","([^"]+)","([^"]*)","([^"]*)"\)"#,
)
.unwrap();

let mut introducer_relations: HashMap<PkgInfo, Option<PkgInfo>> =
let mut introducer_relations: HashMap<PkgInfo, (String, Option<PkgInfo>)> =
HashMap::new();

for result in results.iter() {
Expand All @@ -35,6 +36,10 @@ pub fn extract_introducer_relations_from_raw_solver_results(
let introducer_name = caps.get(5).unwrap().as_str();
let introducer_version_str = caps.get(6).unwrap().as_str();

// Using the requested version (i.e., the version field declared in
// manifest.json) in the dependency chain is more user-friendly.
let mut requested_version_str = version_str.to_string();

let pkg_info = get_pkg_info_from_candidates(
pkg_type_str,
name,
Expand All @@ -52,38 +57,85 @@ pub fn extract_introducer_relations_from_raw_solver_results(
all_candidates,
)?;

let requested_dep_in_introducer = introducer_info
.get_dependency_by_type_and_name(pkg_type_str, name);
if let Some(requested_dep_in_introducer) =
requested_dep_in_introducer
{
// The `version` declared in the `dependencies` section in
// manifest.json is always present.
requested_version_str = requested_dep_in_introducer
.version_req_str
.as_ref()
.unwrap()
.clone();
} else {
return Err(anyhow::anyhow!(
"Requested dependency [{}]{} not found in introducer, should not happen.", pkg_type_str, name
));
}

Some(introducer_info.clone())
};

introducer_relations.insert(pkg_info.clone(), introducer);
introducer_relations
.insert(pkg_info.clone(), (requested_version_str, introducer));
}
}

Ok(introducer_relations)
}

pub fn get_dependency_chain(
pkg_info: &PkgInfo,
introducer_relations: &HashMap<PkgInfo, Option<PkgInfo>>,
) -> Vec<PkgInfo> {
introducer: &PkgInfo,
conflict_pkg_identity: &PkgIdentity,
introducer_relations: &HashMap<PkgInfo, (String, Option<PkgInfo>)>,
) -> Vec<(String, PkgIdentity)> {
let mut chain = Vec::new();
let mut current_pkg = pkg_info.clone();
let mut current_pkg = introducer.clone();
let mut visited = HashSet::new();

// Add the leaf node (i.e., `introducer` depends on `pkg_name`) to the
// dependency chain first.
let requested_dep_in_introducer = introducer
.get_dependency_by_type_and_name(
&conflict_pkg_identity.pkg_type.to_string(),
&conflict_pkg_identity.name,
)
.unwrap();
chain.push((
requested_dep_in_introducer
.version_req_str
.as_ref()
.unwrap()
.clone(),
conflict_pkg_identity.clone(),
));

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(&current_pkg) {
Some(Some(introducer_pkg)) => {
Some((requested_version, Some(introducer_pkg))) => {
// The dependency chain is that `introducer_pkg` depends on
// `current_pkg`@`requested_version`, i.e., the
// `requested_version` is used to declared the `current_pkg` in
// the manifest.json of `introducer_pkg`.
chain.push((
requested_version.clone(),
current_pkg.pkg_identity.clone(),
));
current_pkg = introducer_pkg.clone();
}
Some(None) => {
Some((requested_version, None)) => {
// Reached the root.
chain.push((
requested_version.clone(),
current_pkg.pkg_identity.clone(),
));
break;
}
None => {
Expand Down
Loading
Loading