Skip to content

Commit

Permalink
fix: conflicted error msg (#235)
Browse files Browse the repository at this point in the history
Co-authored-by: Hu Yueh-Wei <[email protected]>
  • Loading branch information
leoadonia and halajohn authored Nov 7, 2024
1 parent aa32951 commit aace72f
Show file tree
Hide file tree
Showing 6 changed files with 243 additions and 48 deletions.
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

0 comments on commit aace72f

Please sign in to comment.