diff --git a/core/src/ten_manager/src/cmd/cmd_install.rs b/core/src/ten_manager/src/cmd/cmd_install.rs index 3adadb693..38428c1f4 100644 --- a/core/src/ten_manager/src/cmd/cmd_install.rs +++ b/core/src/ten_manager/src/cmd/cmd_install.rs @@ -36,7 +36,6 @@ use crate::{ constants::{APP_DIR_IN_DOT_TEN_DIR, DOT_TEN_DIR}, dep_and_candidate::get_all_candidates_from_deps, error::TmanError, - fs::check_is_app_folder, install::{ compare_solver_results_with_existed_pkgs, filter_compatible_pkgs_to_candidates, is_installing_package_standalone, @@ -303,13 +302,15 @@ pub async fn execute_cmd( match affected_pkg_type { PkgType::App => { - check_is_app_folder(&cwd)?; - - // Push the app itself into the initial_input_pkgs. + // The TEN app itself is also a package. Extensions can declare + // dependencies on a specific version of an app, so the app also + // needs to be included in the package list for dependency tree + // calculation. initial_input_pkgs.push(get_pkg_info_from_path(&cwd, true)?); all_existing_local_pkgs = tman_get_all_existed_pkgs_info_of_app(tman_config, &cwd)?; + filter_compatible_pkgs_to_candidates( tman_config, &all_existing_local_pkgs, diff --git a/core/src/ten_rust/src/pkg_info/constants.rs b/core/src/ten_rust/src/pkg_info/constants.rs index 705b1e71b..d648cd422 100644 --- a/core/src/ten_rust/src/pkg_info/constants.rs +++ b/core/src/ten_rust/src/pkg_info/constants.rs @@ -13,6 +13,3 @@ pub const EXTENSION_DIR: &str = "extension"; pub const PROTOCOL_DIR: &str = "protocol"; pub const ADDON_LOADER_DIR: &str = "addon_loader"; pub const SYSTEM_DIR: &str = "system"; - -pub const ERR_STR_NOT_APP_DIR: &str = - "The current working directory does not belong to the `app`."; diff --git a/core/src/ten_rust/src/pkg_info/manifest/mod.rs b/core/src/ten_rust/src/pkg_info/manifest/mod.rs index aa3e4dd8b..a7a0a516c 100644 --- a/core/src/ten_rust/src/pkg_info/manifest/mod.rs +++ b/core/src/ten_rust/src/pkg_info/manifest/mod.rs @@ -11,7 +11,7 @@ pub mod support; use std::{fmt, fs, path::Path, str::FromStr}; -use anyhow::{Context, Result}; +use anyhow::{anyhow, Context, Result}; use serde::{Deserialize, Serialize}; use crate::pkg_info::utils::read_file_to_string; @@ -72,6 +72,41 @@ impl Manifest { pub fn validate_and_complete(&mut self) -> Result<()> { Ok(()) } + + pub fn check_fs_location( + &self, + addon_type_folder_name: Option<&str>, + addon_folder_name: Option<&str>, + ) -> Result<()> { + if let Some(addon_folder_name) = addon_folder_name { + // The package `Foo` must be located inside the folder + // `Foo/`, so that during runtime dynamic loading, the + // desired package can be identified simply by searching + // for the folder name. Additionally, the unique nature + // of package names can be ensured through the file + // system's restriction that prevents duplicate folder + // names within the same directory. + if self.type_and_name.name != addon_folder_name { + return Err(anyhow!(format!( + "the name of the folder '{}' and the package '{}' are different", + addon_folder_name, self.type_and_name.name + ))); + } + } + + if let Some(addon_type_folder_name) = addon_type_folder_name { + if self.type_and_name.pkg_type.to_string() != addon_type_folder_name + { + return Err(anyhow!(format!( + "The folder name '{}' does not match the expected package type '{}'", + addon_type_folder_name, + self.type_and_name.pkg_type.to_string(), + ))); + } + } + + Ok(()) + } } pub fn dump_manifest_str_to_file>( diff --git a/core/src/ten_rust/src/pkg_info/mod.rs b/core/src/ten_rust/src/pkg_info/mod.rs index 153a314b6..a0b893f34 100644 --- a/core/src/ten_rust/src/pkg_info/mod.rs +++ b/core/src/ten_rust/src/pkg_info/mod.rs @@ -27,7 +27,7 @@ use std::{ path::{Path, PathBuf}, }; -use anyhow::Result; +use anyhow::{anyhow, Result}; use graph::Graph; use pkg_basic_info::PkgBasicInfo; use pkg_type_and_name::PkgTypeAndName; @@ -35,8 +35,8 @@ use pkg_type_and_name::PkgTypeAndName; use crate::schema::store::SchemaStore; use api::PkgApi; use constants::{ - ADDON_LOADER_DIR, ERR_STR_NOT_APP_DIR, EXTENSION_DIR, - MANIFEST_JSON_FILENAME, PROTOCOL_DIR, SYSTEM_DIR, TEN_PACKAGES_DIR, + ADDON_LOADER_DIR, EXTENSION_DIR, MANIFEST_JSON_FILENAME, PROTOCOL_DIR, + SYSTEM_DIR, TEN_PACKAGES_DIR, }; use dependencies::{get_pkg_dependencies_from_manifest, PkgDependency}; use manifest::{parse_manifest_from_file, parse_manifest_in_folder, Manifest}; @@ -188,41 +188,47 @@ impl PkgInfo { } } +/// Retrieve the package represented by the specified path from the information +/// within that path. pub fn get_pkg_info_from_path( - pkg_path: &Path, + path: &Path, is_installed: bool, ) -> Result { - let manifest = parse_manifest_in_folder(pkg_path)?; - let property = parse_property_in_folder(pkg_path)?; + let manifest = parse_manifest_in_folder(path)?; + let property = parse_property_in_folder(path)?; let mut pkg_info: PkgInfo = PkgInfo::from_metadata(&manifest, &property)?; pkg_info.is_installed = is_installed; - pkg_info.url = pkg_path.to_string_lossy().to_string(); + + // This package comes from a path, not from a registry, so the value of + // `url` will be the path. + pkg_info.url = path.to_string_lossy().to_string(); Ok(pkg_info) } -fn collect_pkg_info_from_path( - base_path: &Path, - pkgs_info: &mut HashMap, -) -> Result { - let pkg_info = get_pkg_info_from_path(base_path, true)?; - +/// Collect the corresponding package from the information within the specified +/// path, add it to the collection provided as a parameter, and return the newly +/// collected package. +fn collect_pkg_info_from_path<'a>( + path: &Path, + pkgs_info: &'a mut HashMap, +) -> Result<&'a PkgInfo> { + let pkg_info = get_pkg_info_from_path(path, true)?; let pkg_type_name = PkgTypeAndName::from(&pkg_info); - if pkgs_info.contains_key(&pkg_type_name) { - return Err(anyhow::anyhow!( + + match pkgs_info.entry(pkg_type_name) { + std::collections::hash_map::Entry::Occupied(_) => Err(anyhow!( "Duplicated package, type: {}, name: {}", pkg_info.basic_info.type_and_name.pkg_type, pkg_info.basic_info.type_and_name.name - )); + )), + std::collections::hash_map::Entry::Vacant(entry) => { + let inserted = entry.insert(pkg_info); + Ok(inserted) + } } - - let manifest = pkg_info.manifest.clone().unwrap().clone(); - - pkgs_info.insert(pkg_type_name, pkg_info); - - Ok(manifest) } pub fn get_all_existed_pkgs_info_of_app_to_hashmap( @@ -231,64 +237,44 @@ pub fn get_all_existed_pkgs_info_of_app_to_hashmap( let mut pkgs_info: HashMap = HashMap::new(); // Process the manifest.json file in the root path. - let app_pkg_manifest = - collect_pkg_info_from_path(app_path, &mut pkgs_info)?; - if app_pkg_manifest.type_and_name.pkg_type != PkgType::App { - return Err(anyhow::anyhow!(ERR_STR_NOT_APP_DIR)); + let app_pkg = collect_pkg_info_from_path(app_path, &mut pkgs_info)?; + if app_pkg.basic_info.type_and_name.pkg_type != PkgType::App { + return Err(anyhow!( + "The current working directory does not belong to the `app`." + )); } - // Define paths to include manifest.json files from. + // Define the sub-folders for searching packages. let addon_type_dirs = vec![EXTENSION_DIR, PROTOCOL_DIR, ADDON_LOADER_DIR, SYSTEM_DIR]; for addon_type_dir in addon_type_dirs { - let allowed_path = app_path.join(TEN_PACKAGES_DIR).join(addon_type_dir); + let addon_type_dir_path = + app_path.join(TEN_PACKAGES_DIR).join(addon_type_dir); - if allowed_path.exists() && allowed_path.is_dir() { - for entry in allowed_path.read_dir()?.flatten() { + if addon_type_dir_path.exists() && addon_type_dir_path.is_dir() { + for entry in addon_type_dir_path.read_dir()?.flatten() { let path = entry.path(); if path.is_dir() { + // An essential component of a TEN package folder is its + // `manifest.json` file that adheres to the TEN standard. let manifest_path = path.join(MANIFEST_JSON_FILENAME); if manifest_path.exists() && manifest_path.is_file() { let manifest = parse_manifest_from_file(&manifest_path)?; - // Do some simple checks. - if manifest.type_and_name.name - != path.file_name().unwrap().to_str().unwrap() - { - return Err(anyhow::anyhow!( - "The path '{}' is not valid: {}.", - format!("{}/{}",addon_type_dir, manifest.type_and_name.name), - format!( - "the path '{}' and the name '{}' of the package are different", - path.file_name().unwrap().to_str().unwrap(), manifest.type_and_name.name - ))); - } - - if manifest.type_and_name.pkg_type.to_string() - != addon_type_dir - { - return Err(anyhow::anyhow!( - "The path '{}' is not valid: {}.", - format!( - "{}/{}", - addon_type_dir, - manifest.type_and_name.name - ), - format!( - "the package type '{}' is not as expected '{}'", - manifest.type_and_name.pkg_type.to_string(), addon_type_dir) - )); - } + manifest.check_fs_location( + Some(addon_type_dir), + path.file_name().and_then(|os_str| os_str.to_str()), + )?; // This folder contains a manifest.json file and // that manifest.json file is a correct TEN - // manifest.json file, and this package is a - // dependency of app, so read it to treat it as a - // local dependency. + // manifest.json file, so that it means the folder + // represents a valid TEN package, and this package is a + // _local_ dependency of app. collect_pkg_info_from_path(&path, &mut pkgs_info)?; }