diff --git a/Cargo.lock b/Cargo.lock index 4ab141f463a..4d2392a4b03 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1917,6 +1917,7 @@ dependencies = [ "tracing", "tracing-subscriber", "unicode-xid", + "walkdir", ] [[package]] diff --git a/forc-pkg/src/manifest.rs b/forc-pkg/src/manifest.rs index 1ee8bb95b46..67100bc5694 100644 --- a/forc-pkg/src/manifest.rs +++ b/forc-pkg/src/manifest.rs @@ -1,7 +1,7 @@ use crate::pkg::{manifest_file_missing, parsing_failed, wrong_program_type}; use anyhow::{anyhow, bail, Context, Result}; use forc_tracing::println_yellow_err; -use forc_util::{find_manifest_dir, validate_name}; +use forc_util::{find_nested_manifest_dir, find_parent_manifest_dir, validate_name}; use serde::{Deserialize, Serialize}; use std::{ collections::{BTreeMap, HashMap}, @@ -249,7 +249,7 @@ impl PackageManifestFile { /// This is short for `PackageManifest::from_file`, but takes care of constructing the path to the /// file. pub fn from_dir(manifest_dir: &Path) -> Result { - let dir = forc_util::find_manifest_dir(manifest_dir) + let dir = forc_util::find_parent_manifest_dir(manifest_dir) .ok_or_else(|| manifest_file_missing(manifest_dir))?; let path = dir.join(constants::MANIFEST_FILE_NAME); Self::from_file(path) @@ -273,6 +273,18 @@ impl PackageManifestFile { self.project.entry ) } + + // Check for nested packages. + // + // `path` is the path to manifest file. To start nested package search we need to start + // from manifest's directory. So, last part of the path (the filename, "/forc.toml") needs + // to be removed. + let mut pkg_dir = self.path.to_path_buf(); + pkg_dir.pop(); + if let Some(nested_package) = find_nested_manifest_dir(&pkg_dir) { + // remove file name from nested_package_manifest + bail!("Nested packages are not supported, please consider seperating the nested package at {} from the package at {}, or if it makes sense consider creating a workspace.", nested_package.display(), pkg_dir.display()) + } Ok(()) } @@ -450,7 +462,8 @@ impl PackageManifest { /// This is short for `PackageManifest::from_file`, but takes care of constructing the path to the /// file. pub fn from_dir(dir: &Path) -> Result { - let manifest_dir = find_manifest_dir(dir).ok_or_else(|| manifest_file_missing(dir))?; + let manifest_dir = + find_parent_manifest_dir(dir).ok_or_else(|| manifest_file_missing(dir))?; let file_path = manifest_dir.join(constants::MANIFEST_FILE_NAME); Self::from_file(&file_path) } @@ -734,24 +747,25 @@ impl WorkspaceManifestFile { /// This is short for `PackageManifest::from_file`, but takes care of constructing the path to the /// file. pub fn from_dir(manifest_dir: &Path) -> Result { - let dir = forc_util::find_manifest_dir_with_check(manifest_dir, |possible_manifest_dir| { - // Check if the found manifest file is a workspace manifest file or a standalone - // package manifest file. - let possible_path = possible_manifest_dir.join(constants::MANIFEST_FILE_NAME); - // We should not continue to search if the given manifest is a workspace manifest with - // some issues. - // - // If the error is missing field `workspace` (which happens when trying to read a - // package manifest as a workspace manifest), look into the parent directories for a - // legitimate workspace manifest. If the error returned is something else this is a - // workspace manifest with errors, classify this as a workspace manifest but with - // errors so that the erros will be displayed to the user. - Self::from_file(possible_path) - .err() - .map(|e| !e.to_string().contains("missing field `workspace`")) - .unwrap_or_else(|| true) - }) - .ok_or_else(|| manifest_file_missing(manifest_dir))?; + let dir = + forc_util::find_parent_manifest_dir_with_check(manifest_dir, |possible_manifest_dir| { + // Check if the found manifest file is a workspace manifest file or a standalone + // package manifest file. + let possible_path = possible_manifest_dir.join(constants::MANIFEST_FILE_NAME); + // We should not continue to search if the given manifest is a workspace manifest with + // some issues. + // + // If the error is missing field `workspace` (which happens when trying to read a + // package manifest as a workspace manifest), look into the parent directories for a + // legitimate workspace manifest. If the error returned is something else this is a + // workspace manifest with errors, classify this as a workspace manifest but with + // errors so that the erros will be displayed to the user. + Self::from_file(possible_path) + .err() + .map(|e| !e.to_string().contains("missing field `workspace`")) + .unwrap_or_else(|| true) + }) + .ok_or_else(|| manifest_file_missing(manifest_dir))?; let path = dir.join(constants::MANIFEST_FILE_NAME); Self::from_file(path) } diff --git a/forc-plugins/forc-fmt/src/main.rs b/forc-plugins/forc-fmt/src/main.rs index 9cd8010fa27..747e9cd98cd 100644 --- a/forc-plugins/forc-fmt/src/main.rs +++ b/forc-plugins/forc-fmt/src/main.rs @@ -14,7 +14,7 @@ use taplo::formatter as taplo_fmt; use tracing::{error, info}; use forc_tracing::{init_tracing_subscriber, println_green, println_red}; -use forc_util::{find_manifest_dir, is_sway_file}; +use forc_util::{find_parent_manifest_dir, is_sway_file}; use sway_core::{BuildConfig, BuildTarget}; use sway_utils::{constants, get_sway_files}; use swayfmt::Formatter; @@ -62,8 +62,8 @@ fn run() -> Result<()> { // If we're formatting a single file, find the nearest manifest if within a project. // Otherwise, we simply provide 'None' to format_file(). - let manifest_file = - find_manifest_dir(file_path).map(|path| path.join(constants::MANIFEST_FILE_NAME)); + let manifest_file = find_parent_manifest_dir(file_path) + .map(|path| path.join(constants::MANIFEST_FILE_NAME)); if is_sway_file(file_path) { format_file(&app, file_path.to_path_buf(), manifest_file, &mut formatter)?; @@ -252,7 +252,7 @@ fn format_manifest(app: &App, manifest_file: PathBuf) -> Result { /// Format the package at the given directory. fn format_pkg_at_dir(app: &App, dir: &Path, formatter: &mut Formatter) -> Result<()> { - match find_manifest_dir(dir) { + match find_parent_manifest_dir(dir) { Some(path) => { let manifest_path = path.clone(); let manifest_file = manifest_path.join(constants::MANIFEST_FILE_NAME); diff --git a/forc-util/Cargo.toml b/forc-util/Cargo.toml index 7b4de2d675c..7aead3a6781 100644 --- a/forc-util/Cargo.toml +++ b/forc-util/Cargo.toml @@ -26,3 +26,4 @@ sway-utils = { version = "0.37.0", path = "../sway-utils" } tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["ansi", "env-filter", "json"] } unicode-xid = "0.2.2" +walkdir = "2.3.3" diff --git a/forc-util/src/lib.rs b/forc-util/src/lib.rs index 0b867632b3f..b2b3057c7d6 100644 --- a/forc-util/src/lib.rs +++ b/forc-util/src/lib.rs @@ -64,7 +64,37 @@ pub fn format_log_receipts(receipts: &[fuel_tx::Receipt], pretty_print: bool) -> } } +/// Continually go down in the file tree until a Forc manifest file is found. +pub fn find_nested_manifest_dir(starter_path: &Path) -> Option { + find_nested_dir_with_file(starter_path, constants::MANIFEST_FILE_NAME) +} + +/// Continually go down in the file tree until a specified file is found. +/// +/// Starts the search from child dirs of `starter_path`. +pub fn find_nested_dir_with_file(starter_path: &Path, file_name: &str) -> Option { + use walkdir::WalkDir; + let starter_dir = if starter_path.is_dir() { + starter_path + } else { + starter_path.parent()? + }; + WalkDir::new(starter_path) + .into_iter() + .filter_map(|e| e.ok()) + .filter(|entry| entry.path() != starter_dir.join(file_name)) + .filter(|entry| entry.file_name().to_string_lossy() == file_name) + .map(|entry| { + let mut entry = entry.path().to_path_buf(); + entry.pop(); + entry + }) + .next() +} + /// Continually go up in the file tree until a specified file is found. +/// +/// Starts the search from `starter_path`. #[allow(clippy::branches_sharing_code)] pub fn find_parent_dir_with_file(starter_path: &Path, file_name: &str) -> Option { let mut path = std::fs::canonicalize(starter_path).ok()?; @@ -82,22 +112,22 @@ pub fn find_parent_dir_with_file(starter_path: &Path, file_name: &str) -> Option None } /// Continually go up in the file tree until a Forc manifest file is found. -pub fn find_manifest_dir(starter_path: &Path) -> Option { +pub fn find_parent_manifest_dir(starter_path: &Path) -> Option { find_parent_dir_with_file(starter_path, constants::MANIFEST_FILE_NAME) } /// Continually go up in the file tree until a Forc manifest file is found and given predicate /// returns true. -pub fn find_manifest_dir_with_check(starter_path: &Path, f: F) -> Option +pub fn find_parent_manifest_dir_with_check(starter_path: &Path, f: F) -> Option where F: Fn(&Path) -> bool, { - find_manifest_dir(starter_path).and_then(|manifest_dir| { + find_parent_manifest_dir(starter_path).and_then(|manifest_dir| { // If given check satisifies return current dir otherwise start searching from the parent. if f(&manifest_dir) { Some(manifest_dir) } else if let Some(parent_dir) = manifest_dir.parent() { - find_manifest_dir_with_check(parent_dir, f) + find_parent_manifest_dir_with_check(parent_dir, f) } else { None } diff --git a/forc/src/ops/forc_clean.rs b/forc/src/ops/forc_clean.rs index f07ef7b80db..83f3b919e09 100644 --- a/forc/src/ops/forc_clean.rs +++ b/forc/src/ops/forc_clean.rs @@ -1,7 +1,7 @@ use crate::cli::CleanCommand; use anyhow::{anyhow, bail, Result}; use forc_pkg::manifest::ManifestFile; -use forc_util::{default_output_directory, find_manifest_dir}; +use forc_util::{default_output_directory, find_parent_manifest_dir}; use std::path::PathBuf; use sway_utils::MANIFEST_FILE_NAME; @@ -15,7 +15,7 @@ pub fn clean(command: CleanCommand) -> Result<()> { std::env::current_dir().map_err(|e| anyhow!("{:?}", e))? }; - let manifest_dir = match find_manifest_dir(&this_dir) { + let manifest_dir = match find_parent_manifest_dir(&this_dir) { Some(dir) => dir, None => { bail!( diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/forc/nested_package/.gitignore b/test/src/e2e_vm_tests/test_programs/should_fail/forc/nested_package/.gitignore new file mode 100644 index 00000000000..77d3844f58c --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/forc/nested_package/.gitignore @@ -0,0 +1,2 @@ +out +target diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/forc/nested_package/Forc.toml b/test/src/e2e_vm_tests/test_programs/should_fail/forc/nested_package/Forc.toml new file mode 100644 index 00000000000..d3ea8b1a28e --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/forc/nested_package/Forc.toml @@ -0,0 +1,6 @@ +[project] +authors = ["Fuel Labs "] +entry = "main.sw" +license = "Apache-2.0" +name = "test_contract" +implicit-std = false diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/forc/nested_package/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_fail/forc/nested_package/src/main.sw new file mode 100644 index 00000000000..841cc78e0ec --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/forc/nested_package/src/main.sw @@ -0,0 +1 @@ +contract; diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/forc/nested_package/src/sub_package/.gitignore b/test/src/e2e_vm_tests/test_programs/should_fail/forc/nested_package/src/sub_package/.gitignore new file mode 100644 index 00000000000..77d3844f58c --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/forc/nested_package/src/sub_package/.gitignore @@ -0,0 +1,2 @@ +out +target diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/forc/nested_package/src/sub_package/Forc.toml b/test/src/e2e_vm_tests/test_programs/should_fail/forc/nested_package/src/sub_package/Forc.toml new file mode 100644 index 00000000000..66ea37cbf23 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/forc/nested_package/src/sub_package/Forc.toml @@ -0,0 +1,6 @@ +[project] +authors = ["Fuel Labs "] +entry = "main.sw" +license = "Apache-2.0" +name = "sub_package" +implicit-std = false diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/forc/nested_package/src/sub_package/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_fail/forc/nested_package/src/sub_package/src/main.sw new file mode 100644 index 00000000000..841cc78e0ec --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/forc/nested_package/src/sub_package/src/main.sw @@ -0,0 +1 @@ +contract; diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/forc/nested_package/test.toml b/test/src/e2e_vm_tests/test_programs/should_fail/forc/nested_package/test.toml new file mode 100644 index 00000000000..2f91f447506 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/forc/nested_package/test.toml @@ -0,0 +1,5 @@ +category = "fail" + +# check: $()Nested packages are not supported, please consider seperating the nested package at +# check: $() from the package at +# check: $(), or if it makes sense consider creating a workspace.