diff --git a/Cargo.lock b/Cargo.lock index 71118ec258d..e14f62e4046 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1127,6 +1127,7 @@ dependencies = [ "sway-utils", "toml", "url", + "walkdir", ] [[package]] @@ -2728,6 +2729,15 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "schemafy_core" version = "0.5.2" @@ -3881,6 +3891,17 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" +[[package]] +name = "walkdir" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +dependencies = [ + "same-file", + "winapi", + "winapi-util", +] + [[package]] name = "want" version = "0.3.0" diff --git a/forc-pkg/Cargo.toml b/forc-pkg/Cargo.toml index e3465b1c5ce..4107aa4376f 100644 --- a/forc-pkg/Cargo.toml +++ b/forc-pkg/Cargo.toml @@ -21,3 +21,4 @@ sway-types = { version = "0.8.0", path = "../sway-types" } sway-utils = { version = "0.8.0", path = "../sway-utils" } toml = "0.5" url = { version = "2.2", features = ["serde"] } +walkdir = "2" diff --git a/forc-pkg/src/pkg.rs b/forc-pkg/src/pkg.rs index ef2dcf6fa1e..803b01aad85 100644 --- a/forc-pkg/src/pkg.rs +++ b/forc-pkg/src/pkg.rs @@ -20,6 +20,7 @@ use sway_core::{ NamespaceWrapper, TreeType, TypedParseTree, }; use sway_types::JsonABI; +use sway_utils::constants; use url::Url; type GraphIx = u32; @@ -513,7 +514,18 @@ pub fn graph_to_path_map( let dep = &graph[dep_node]; let dep_path = match &dep.source { SourcePinned::Git(git) => { - git_commit_path(&dep.name, &git.source.repo, &git.commit_hash) + let repo_path = git_commit_path(&dep.name, &git.source.repo, &git.commit_hash); + if !repo_path.exists() { + println!(" Fetching {}", git.to_string()); + fetch_git(fetch_id, &dep.name, git)?; + } + find_dir_within(&repo_path, &dep.name).ok_or_else(|| { + anyhow!( + "failed to find package `{}` in {}", + dep.name, + git.to_string() + ) + })? } SourcePinned::Path => { let parent_node = graph @@ -536,26 +548,16 @@ pub fn graph_to_path_map( .path .as_ref() .ok_or_else(|| anyhow!("missing path info for dependency: {}", dep.name))?; - parent_path.join(rel_dep_path) + let path = parent_path.join(rel_dep_path); + if !path.exists() { + bail!("pinned `path` dependency \"{}\" source missing", dep.name); + } + path } SourcePinned::Registry(_reg) => { bail!("registry dependencies are not yet supported"); } }; - if !dep_path.exists() { - match &dep.source { - SourcePinned::Path => { - bail!("pinned `path` dependency \"{}\" source missing", dep.name); - } - SourcePinned::Git(git) => { - println!(" Fetching {}", git.to_string()); - fetch_git(fetch_id, &dep.name, git)?; - } - SourcePinned::Registry(_reg) => { - bail!("registry dependencies are not yet supported"); - } - } - } path_map.insert(dep.id(), dep_path); } @@ -795,9 +797,10 @@ fn pin_pkg(fetch_id: u64, pkg: &Pkg, path_map: &mut PathMap) -> Result { path_map.insert(id, path.clone()); pinned } - Source::Git(ref source) => { - let pinned_git = pin_git(fetch_id, &name, source.clone())?; - let path = git_commit_path(&name, &pinned_git.source.repo, &pinned_git.commit_hash); + Source::Git(ref git_source) => { + let pinned_git = pin_git(fetch_id, &name, git_source.clone())?; + let repo_path = + git_commit_path(&name, &pinned_git.source.repo, &pinned_git.commit_hash); let source = SourcePinned::Git(pinned_git.clone()); let pinned = Pinned { name, source }; let id = pinned.id(); @@ -807,10 +810,17 @@ fn pin_pkg(fetch_id: u64, pkg: &Pkg, path_map: &mut PathMap) -> Result { // cases as users should never be touching these directories, however we should add some code // to validate this. E.g. can we recreate the git hash by hashing the directory or something // along these lines using git? - if !path.exists() { + if !repo_path.exists() { println!(" Fetching {}", pinned_git.to_string()); fetch_git(fetch_id, &pinned.name, &pinned_git)?; } + let path = find_dir_within(&repo_path, &pinned.name).ok_or_else(|| { + anyhow!( + "failed to find package `{}` in {}", + pinned.name, + pinned_git.to_string() + ) + })?; entry.insert(path); } pinned @@ -1071,6 +1081,30 @@ pub fn build(plan: &BuildPlan, conf: &BuildConfig) -> anyhow::Result<(Compiled, Ok((compiled, source_map)) } +/// Attempt to find a `Forc.toml` with the given project name within the given directory. +/// +/// Returns the path to the package on success, or `None` in the case it could not be found. +pub fn find_within(dir: &Path, pkg_name: &str) -> Option { + walkdir::WalkDir::new(dir) + .into_iter() + .filter_map(Result::ok) + .filter(|entry| entry.path().ends_with(constants::MANIFEST_FILE_NAME)) + .find_map(|entry| { + let path = entry.path(); + let manifest = Manifest::from_file(path).ok()?; + if manifest.project.name == pkg_name { + Some(path.to_path_buf()) + } else { + None + } + }) +} + +/// The same as [find_within], but returns the package's project directory. +pub fn find_dir_within(dir: &Path, pkg_name: &str) -> Option { + find_within(dir, pkg_name).and_then(|path| path.parent().map(Path::to_path_buf)) +} + // TODO: Update this to match behaviour described in the `compile` doc comment above. fn generate_json_abi(ast: &TypedParseTree) -> JsonABI { match ast {