diff --git a/forc-pkg/src/lib.rs b/forc-pkg/src/lib.rs index aea32ba77b..13c7bba182 100644 --- a/forc-pkg/src/lib.rs +++ b/forc-pkg/src/lib.rs @@ -9,7 +9,9 @@ pub mod manifest; mod pkg; pub use lock::Lock; -pub use manifest::{BuildProfile, Manifest, ManifestFile}; +pub use manifest::{ + BuildProfile, PackageManifest, PackageManifestFile, WorkspaceManifest, WorkspaceManifestFile, +}; #[doc(inline)] pub use pkg::*; diff --git a/forc-pkg/src/manifest.rs b/forc-pkg/src/manifest.rs index 5f266c1755..db024df28c 100644 --- a/forc-pkg/src/manifest.rs +++ b/forc-pkg/src/manifest.rs @@ -14,11 +14,11 @@ use sway_utils::constants; type PatchMap = BTreeMap; -/// A [Manifest] that was deserialized from a file at a particular path. +/// A [PackageManifest] that was deserialized from a file at a particular path. #[derive(Clone, Debug)] -pub struct ManifestFile { +pub struct PackageManifestFile { /// The deserialized `Forc.toml`. - manifest: Manifest, + manifest: PackageManifest, /// The path from which the `Forc.toml` file was read. path: PathBuf, } @@ -26,7 +26,7 @@ pub struct ManifestFile { /// A direct mapping to a `Forc.toml`. #[derive(Serialize, Deserialize, Clone, Debug)] #[serde(rename_all = "kebab-case")] -pub struct Manifest { +pub struct PackageManifest { pub project: Project, pub network: Option, pub dependencies: Option>, @@ -103,8 +103,8 @@ impl Dependency { } } -impl ManifestFile { - /// Given a path to a `Forc.toml`, read it and construct a `Manifest`. +impl PackageManifestFile { + /// Given a path to a `Forc.toml`, read it and construct a `PackageManifest`. /// /// This also `validate`s the manifest, returning an `Err` in the case that invalid names, /// fields were used. @@ -114,14 +114,14 @@ impl ManifestFile { /// specify the pinned commit at which we fetch `std`. pub fn from_file(path: PathBuf) -> Result { let path = path.canonicalize()?; - let manifest = Manifest::from_file(&path)?; + let manifest = PackageManifest::from_file(&path)?; Ok(Self { manifest, path }) } /// Read the manifest from the `Forc.toml` in the directory specified by the given `path` or /// any of its parent directories. /// - /// This is short for `Manifest::from_file`, but takes care of constructing the path to the + /// 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) @@ -130,7 +130,7 @@ impl ManifestFile { Self::from_file(path) } - /// Validate the `Manifest`. + /// Validate the `PackageManifest`. /// /// This checks the project and organization names against a set of reserved/restricted /// keywords and patterns, and if a given entry point exists. @@ -166,7 +166,7 @@ impl ManifestFile { .expect("failed to retrieve manifest directory") } - /// Given the directory in which the file associated with this `Manifest` resides, produce the + /// Given the directory in which the file associated with this `PackageManifest` resides, produce the /// path to the entry file as specified in the manifest. /// /// This will always be a canonical path. @@ -232,10 +232,10 @@ impl ManifestFile { } } -impl Manifest { +impl PackageManifest { pub const DEFAULT_ENTRY_FILE_NAME: &'static str = "main.sw"; - /// Given a path to a `Forc.toml`, read it and construct a `Manifest`. + /// Given a path to a `Forc.toml`, read it and construct a `PackageManifest`. /// /// This also `validate`s the manifest, returning an `Err` in the case that invalid names, /// fields were used. @@ -258,7 +258,7 @@ impl Manifest { Ok(manifest) } - /// Validate the `Manifest`. + /// Validate the `PackageManifest`. /// /// This checks the project and organization names against a set of reserved/restricted /// keywords and patterns. @@ -272,7 +272,7 @@ impl Manifest { /// Given a directory to a forc project containing a `Forc.toml`, read the manifest. /// - /// This is short for `Manifest::from_file`, but takes care of constructing the path to the + /// 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))?; @@ -422,8 +422,8 @@ impl BuildProfile { } } -impl std::ops::Deref for ManifestFile { - type Target = Manifest; +impl std::ops::Deref for PackageManifestFile { + type Target = PackageManifest; fn deref(&self) -> &Self::Target { &self.manifest } @@ -440,7 +440,7 @@ fn implicit_std_dep() -> Dependency { // Here, we use the `forc-pkg` crate version formatted with the `v` prefix (e.g. "v1.2.3"), // or the revision commit hash (e.g. "abcdefg"). // - // This git tag or revision is used during `Manifest` construction to pin the version of the + // This git tag or revision is used during `PackageManifest` construction to pin the version of the // implicit `std` dependency to the `forc-pkg` version. // // This is important to ensure that the version of `sway-core` that is baked into `forc-pkg` is @@ -474,9 +474,104 @@ fn implicit_std_dep() -> Dependency { } fn default_entry() -> String { - Manifest::DEFAULT_ENTRY_FILE_NAME.to_string() + PackageManifest::DEFAULT_ENTRY_FILE_NAME.to_string() } fn default_url() -> String { constants::DEFAULT_NODE_URL.into() } + +/// A [WorkspaceManifest] that was deserialized from a file at a particular path. +#[derive(Clone, Debug)] +pub struct WorkspaceManifestFile { + /// The derserialized `Forc.toml` + manifest: WorkspaceManifest, + /// The path from which the `Forc.toml` file was read. + path: PathBuf, +} + +/// A direct mapping to `Forc.toml` if it is a WorkspaceManifest +#[derive(Serialize, Deserialize, Clone, Debug)] +#[serde(rename_all = "kebab-case")] +pub struct WorkspaceManifest { + pub members: Vec, +} + +impl WorkspaceManifestFile { + /// Given a path to a `Forc.toml`, read it and construct a `PackageManifest` + /// + /// This also `validate`s the manifest, returning an `Err` in the case that given members are + /// not present in the manifest dir. + pub fn from_file(path: PathBuf) -> Result { + let path = path.canonicalize()?; + let parent = path + .parent() + .ok_or_else(|| anyhow!("Cannot get parent dir of {:?}", path))?; + let manifest = WorkspaceManifest::from_file(&path)?; + manifest.validate(parent)?; + Ok(Self { manifest, path }) + } + + /// Read the manifest from the `Forc.toml` in the directory specified by the given `path` or + /// any of its parent directories. + /// + /// 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) + .ok_or_else(|| manifest_file_missing(manifest_dir))?; + let path = dir.join(constants::MANIFEST_FILE_NAME); + Self::from_file(path) + } + + pub fn members(&self) -> impl Iterator + '_ { + self.members.iter() + } + + pub fn member_paths(&self) -> Result + '_> { + let parent = self + .path + .parent() + .ok_or_else(|| anyhow!("Cannot get parent dir of {:?}", self.path))?; + Ok(self.members.iter().map(|member| parent.join(member))) + } +} + +impl WorkspaceManifest { + /// Given a path to a `Forc.toml`, read it and construct a `WorkspaceManifest`. + pub fn from_file(path: &Path) -> Result { + let manifest_str = std::fs::read_to_string(path) + .map_err(|e| anyhow!("failed to read manifest at {:?}: {}", path, e))?; + let toml_de = &mut toml::de::Deserializer::new(&manifest_str); + let manifest: Self = serde_ignored::deserialize(toml_de, |path| { + let warning = format!(" WARNING! unused manifest key: {}", path); + println_yellow_err(&warning); + }) + .map_err(|e| anyhow!("failed to parse manifest: {}.", e))?; + Ok(manifest) + } + + /// Validate the `WorkspaceManifest` + /// + /// This checks if the listed members in the `WorkspaceManifest` are indeed in the given `Forc.toml`'s directory. + pub fn validate(&self, path: &Path) -> Result<()> { + for member in self.members.iter() { + let member_path = path.join(&member).join("Forc.toml"); + if !member_path.exists() { + bail!( + "{:?} is listed as a member of the workspace but {:?} does not exists", + &member, + member_path + ); + } + } + Ok(()) + } +} + +impl std::ops::Deref for WorkspaceManifestFile { + type Target = WorkspaceManifest; + fn deref(&self) -> &Self::Target { + &self.manifest + } +} diff --git a/forc-pkg/src/pkg.rs b/forc-pkg/src/pkg.rs index 2f6ebaad79..ed52c92aae 100644 --- a/forc-pkg/src/pkg.rs +++ b/forc-pkg/src/pkg.rs @@ -1,6 +1,8 @@ use crate::{ lock::Lock, - manifest::{BuildProfile, ConfigTimeConstant, Dependency, Manifest, ManifestFile}, + manifest::{ + BuildProfile, ConfigTimeConstant, Dependency, PackageManifest, PackageManifestFile, + }, CORE, PRELUDE, STD, }; use anyhow::{anyhow, bail, Context, Error, Result}; @@ -40,7 +42,7 @@ type Edge = DependencyName; pub type Graph = petgraph::stable_graph::StableGraph; pub type EdgeIx = petgraph::graph::EdgeIndex; pub type NodeIx = petgraph::graph::NodeIndex; -pub type ManifestMap = HashMap; +pub type ManifestMap = HashMap; /// A unique ID for a pinned package. /// @@ -243,7 +245,7 @@ impl BuildPlan { /// Create a new build plan for the project by fetching and pinning all dependenies. /// /// To account for an existing lock file, use `from_lock_and_manifest` instead. - pub fn from_manifest(manifest: &ManifestFile, offline: bool) -> Result { + pub fn from_manifest(manifest: &PackageManifestFile, offline: bool) -> Result { // Check toolchain version validate_version(manifest)?; let mut graph = Graph::default(); @@ -260,11 +262,11 @@ impl BuildPlan { }) } - /// Create a new build plan taking into account the state of both the Manifest and the existing + /// Create a new build plan taking into account the state of both the PackageManifest and the existing /// lock file if there is one. /// /// This will first attempt to load a build plan from the lock file and validate the resulting - /// graph using the current state of the Manifest. + /// graph using the current state of the PackageManifest. /// /// This includes checking if the [dependencies] or [patch] tables have changed and checking /// the validity of the local path dependencies. If any changes are detected, the graph is @@ -277,7 +279,7 @@ impl BuildPlan { // the manifest alongside some lock diff type that can be used to optionally write the updated // lock file and print the diff. pub fn from_lock_and_manifest( - manifest: &ManifestFile, + manifest: &PackageManifestFile, locked: bool, offline: bool, ) -> Result { @@ -401,7 +403,7 @@ fn find_proj_node(graph: &Graph, proj_name: &str) -> Result { /// /// If required minimum forc version is higher than current forc version return an error with /// upgrade instructions -fn validate_version(pkg_manifest: &ManifestFile) -> Result<()> { +fn validate_version(pkg_manifest: &PackageManifestFile) -> Result<()> { match &pkg_manifest.project.forc_version { Some(min_forc_version) => { // Get the current version of the toolchain @@ -425,7 +427,7 @@ fn validate_version(pkg_manifest: &ManifestFile) -> Result<()> { /// Validates the state of the pinned package graph against the given project manifest. /// /// Returns the set of invalid dependency edges. -fn validate_graph(graph: &Graph, proj_manifest: &ManifestFile) -> BTreeSet { +fn validate_graph(graph: &Graph, proj_manifest: &PackageManifestFile) -> BTreeSet { // If we don't have a project node, remove everything as we can't validate dependencies // without knowing where to start. let proj_node = match find_proj_node(graph, &proj_manifest.project.name) { @@ -443,7 +445,7 @@ fn validate_graph(graph: &Graph, proj_manifest: &ManifestFile) -> BTreeSet, ) -> BTreeSet { let mut remove = BTreeSet::default(); @@ -471,10 +473,10 @@ fn validate_deps( /// Returns the `ManifestFile` in the case that the dependency is valid. fn validate_dep( graph: &Graph, - node_manifest: &ManifestFile, + node_manifest: &PackageManifestFile, dep_name: &str, dep_node: NodeIx, -) -> Result { +) -> Result { // Check the validity of the dependency path, including its path root. let dep_path = dep_path(graph, node_manifest, dep_name, dep_node).map_err(|e| { anyhow!( @@ -485,7 +487,7 @@ fn validate_dep( })?; // Ensure the manifest is accessible. - let dep_manifest = ManifestFile::from_dir(&dep_path)?; + let dep_manifest = PackageManifestFile::from_dir(&dep_path)?; // Check that the dependency's source matches the entry in the parent manifest. let dep_entry = node_manifest @@ -502,7 +504,7 @@ fn validate_dep( Ok(dep_manifest) } /// Part of dependency validation, any checks related to the depenency's manifest content. -fn validate_dep_manifest(dep: &Pinned, dep_manifest: &ManifestFile) -> Result<()> { +fn validate_dep_manifest(dep: &Pinned, dep_manifest: &PackageManifestFile) -> Result<()> { // Ensure that the dependency is a library. if !matches!(dep_manifest.program_type()?, TreeType::Library { .. }) { bail!( @@ -531,7 +533,7 @@ fn validate_dep_manifest(dep: &Pinned, dep_manifest: &ManifestFile) -> Result<() /// invalid. fn dep_path( graph: &Graph, - node_manifest: &ManifestFile, + node_manifest: &PackageManifestFile, dep_name: &str, dep_node: NodeIx, ) -> Result { @@ -930,7 +932,7 @@ pub fn compilation_order(graph: &Graph) -> Result> { /// manifest of for every node in the graph. /// /// Assumes the given `graph` only contains valid dependencies (see `validate_graph`). -fn graph_to_manifest_map(proj_manifest: ManifestFile, graph: &Graph) -> Result { +fn graph_to_manifest_map(proj_manifest: PackageManifestFile, graph: &Graph) -> Result { let mut manifest_map = ManifestMap::new(); // Traverse the graph from the project node. @@ -965,7 +967,7 @@ fn graph_to_manifest_map(proj_manifest: ManifestFile, graph: &Graph) -> Result u64 { /// /// Upon success, returns the set of nodes that were added to the graph during traversal. fn fetch_graph( - proj_manifest: &ManifestFile, + proj_manifest: &PackageManifestFile, offline: bool, graph: &mut Graph, manifest_map: &mut ManifestMap, @@ -1307,7 +1309,7 @@ fn pin_pkg( let source = SourcePinned::Root; let pinned = Pinned { name, source }; let id = pinned.id(); - let manifest = ManifestFile::from_dir(path)?; + let manifest = PackageManifestFile::from_dir(path)?; manifest_map.insert(id, manifest); pinned } @@ -1316,7 +1318,7 @@ fn pin_pkg( let source = SourcePinned::Path(path_pinned); let pinned = Pinned { name, source }; let id = pinned.id(); - let manifest = ManifestFile::from_dir(path)?; + let manifest = PackageManifestFile::from_dir(path)?; manifest_map.insert(id, manifest); pinned } @@ -1393,7 +1395,7 @@ fn pin_pkg( pinned_git.to_string() ) })?; - let manifest = ManifestFile::from_dir(&path)?; + let manifest = PackageManifestFile::from_dir(&path)?; entry.insert(manifest); } pinned @@ -1644,7 +1646,7 @@ fn dep_to_source(pkg_path: &Path, dep: &Dependency) -> Result { /// If a patch exists for the given dependency source within the given project manifest, this /// returns the patch. fn dep_source_patch<'manifest>( - manifest: &'manifest ManifestFile, + manifest: &'manifest PackageManifestFile, dep_name: &str, dep_source: &Source, ) -> Option<&'manifest Dependency> { @@ -1662,7 +1664,11 @@ fn dep_source_patch<'manifest>( /// `Source` with the patch applied. /// /// If no patch exists, this returns the original `Source`. -fn apply_patch(manifest: &ManifestFile, dep_name: &str, dep_source: &Source) -> Result { +fn apply_patch( + manifest: &PackageManifestFile, + dep_name: &str, + dep_source: &Source, +) -> Result { match dep_source_patch(manifest, dep_name, dep_source) { Some(patch) => dep_to_source(manifest.dir(), patch), None => Ok(dep_source.clone()), @@ -1672,7 +1678,7 @@ fn apply_patch(manifest: &ManifestFile, dep_name: &str, dep_source: &Source) -> /// Converts the `Dependency` to a `Source` with any relevant patches in the given manifest /// applied. fn dep_to_source_patched( - manifest: &ManifestFile, + manifest: &PackageManifestFile, dep_name: &str, dep: &Dependency, ) -> Result { @@ -1799,7 +1805,7 @@ fn find_core_dep(graph: &Graph, node: NodeIx) -> Option { /// Compiles the package to an AST. pub fn compile_ast( - manifest: &ManifestFile, + manifest: &PackageManifestFile, build_profile: &BuildProfile, namespace: namespace::Module, ) -> Result> { @@ -1830,7 +1836,7 @@ pub fn compile_ast( /// Scripts and Predicates will be compiled to bytecode and will not emit any JSON ABI. pub fn compile( pkg: &Pinned, - manifest: &ManifestFile, + manifest: &PackageManifestFile, build_profile: &BuildProfile, namespace: namespace::Module, source_map: &mut SourceMap, @@ -2047,7 +2053,7 @@ pub fn build_with_options(build_options: BuildOptions) -> Result { std::env::current_dir()? }; - let manifest = ManifestFile::from_dir(&this_dir)?; + let manifest = PackageManifestFile::from_dir(&this_dir)?; let plan = BuildPlan::from_lock_and_manifest(&manifest, locked, offline_mode)?; @@ -2391,7 +2397,7 @@ pub fn check( /// Returns a parsed AST from the supplied [ManifestFile] pub fn parse( - manifest: &ManifestFile, + manifest: &PackageManifestFile, terse_mode: bool, ) -> anyhow::Result> { let profile = BuildProfile { @@ -2413,7 +2419,7 @@ pub fn find_within(dir: &Path, pkg_name: &str) -> Option { .filter(|entry| entry.path().ends_with(constants::MANIFEST_FILE_NAME)) .find_map(|entry| { let path = entry.path(); - let manifest = Manifest::from_file(path).ok()?; + let manifest = PackageManifest::from_file(path).ok()?; if manifest.project.name == pkg_name { Some(path.to_path_buf()) } else { diff --git a/forc-plugins/forc-client/src/ops/deploy/op.rs b/forc-plugins/forc-client/src/ops/deploy/op.rs index b78559b647..03e844db7f 100644 --- a/forc-plugins/forc-client/src/ops/deploy/op.rs +++ b/forc-plugins/forc-client/src/ops/deploy/op.rs @@ -1,5 +1,5 @@ use anyhow::{bail, Result}; -use forc_pkg::{BuildOptions, Compiled, ManifestFile}; +use forc_pkg::{BuildOptions, Compiled, PackageManifestFile}; use fuel_crypto::Signature; use fuel_gql_client::client::FuelClient; use fuel_tx::{Output, Salt, StorageSlot, Transaction}; @@ -20,7 +20,7 @@ pub async fn deploy(command: DeployCommand) -> Result { } else { std::env::current_dir()? }; - let manifest = ManifestFile::from_dir(&curr_dir)?; + let manifest = PackageManifestFile::from_dir(&curr_dir)?; manifest.check_program_type(vec![TreeType::Contract])?; let DeployCommand { diff --git a/forc-plugins/forc-client/src/ops/run/op.rs b/forc-plugins/forc-client/src/ops/run/op.rs index 0dc5713da0..4e77632b6f 100644 --- a/forc-plugins/forc-client/src/ops/run/op.rs +++ b/forc-plugins/forc-client/src/ops/run/op.rs @@ -1,5 +1,5 @@ use anyhow::{anyhow, bail, Result}; -use forc_pkg::{fuel_core_not_running, BuildOptions, ManifestFile}; +use forc_pkg::{fuel_core_not_running, BuildOptions, PackageManifestFile}; use fuel_crypto::Signature; use fuel_gql_client::client::FuelClient; use fuel_tx::{AssetId, Output, Transaction, Witness}; @@ -21,7 +21,7 @@ pub async fn run(command: RunCommand) -> Result> { } else { std::env::current_dir().map_err(|e| anyhow!("{:?}", e))? }; - let manifest = ManifestFile::from_dir(&path_dir)?; + let manifest = PackageManifestFile::from_dir(&path_dir)?; manifest.check_program_type(vec![TreeType::Script])?; let input_data = &command.data.unwrap_or_else(|| "".into()); diff --git a/forc/src/ops/forc_check.rs b/forc/src/ops/forc_check.rs index adb79d348d..07a4fa8768 100644 --- a/forc/src/ops/forc_check.rs +++ b/forc/src/ops/forc_check.rs @@ -1,6 +1,6 @@ use crate::cli::CheckCommand; use anyhow::Result; -use forc_pkg::{self as pkg, ManifestFile}; +use forc_pkg::{self as pkg, PackageManifestFile}; use std::path::PathBuf; use sway_core::CompileResult; @@ -17,7 +17,7 @@ pub fn check(command: CheckCommand) -> Result Result<()> { })?, None => { let manifest_path = repo_path.join(constants::MANIFEST_FILE_NAME); - if Manifest::from_file(&manifest_path).is_err() { + if PackageManifest::from_file(&manifest_path).is_err() { anyhow::bail!("failed to find a template in {}", command.url); } repo_path diff --git a/forc/src/ops/forc_update.rs b/forc/src/ops/forc_update.rs index 95a84097fb..89c10cba2d 100644 --- a/forc/src/ops/forc_update.rs +++ b/forc/src/ops/forc_update.rs @@ -1,6 +1,6 @@ use crate::cli::UpdateCommand; use anyhow::{anyhow, Result}; -use forc_pkg::{self as pkg, lock, Lock, ManifestFile}; +use forc_pkg::{self as pkg, lock, Lock, PackageManifestFile}; use forc_util::lock_path; use std::{fs, path::PathBuf}; use tracing::info; @@ -32,7 +32,7 @@ pub async fn update(command: UpdateCommand) -> Result<()> { None => std::env::current_dir()?, }; - let manifest = ManifestFile::from_dir(&this_dir)?; + let manifest = PackageManifestFile::from_dir(&this_dir)?; let lock_path = lock_path(manifest.dir()); let old_lock = Lock::from_path(&lock_path).ok().unwrap_or_default(); let offline = false; diff --git a/forc/src/utils/defaults.rs b/forc/src/utils/defaults.rs index 04b44a1773..24ef6807f2 100644 --- a/forc/src/utils/defaults.rs +++ b/forc/src/utils/defaults.rs @@ -157,7 +157,8 @@ fn parse_default_manifest() { use sway_utils::constants::MAIN_ENTRY; tracing::info!( "{:#?}", - toml::from_str::(&default_manifest("test_proj", MAIN_ENTRY)).unwrap() + toml::from_str::(&default_manifest("test_proj", MAIN_ENTRY)) + .unwrap() ) } @@ -165,6 +166,6 @@ fn parse_default_manifest() { fn parse_default_tests_manifest() { tracing::info!( "{:#?}", - toml::from_str::(&default_tests_manifest("test_proj")).unwrap() + toml::from_str::(&default_tests_manifest("test_proj")).unwrap() ) } diff --git a/sway-lsp/src/core/session.rs b/sway-lsp/src/core/session.rs index 117e4ed0e8..50ae297810 100644 --- a/sway-lsp/src/core/session.rs +++ b/sway-lsp/src/core/session.rs @@ -147,7 +147,7 @@ impl Session { let offline = false; // TODO: match on any errors and report them back to the user in a future PR - if let Ok(manifest) = pkg::ManifestFile::from_dir(&manifest_dir) { + if let Ok(manifest) = pkg::PackageManifestFile::from_dir(&manifest_dir) { if let Ok(plan) = pkg::BuildPlan::from_lock_and_manifest(&manifest, locked, offline) { //we can then use them directly to convert them to a Vec if let Ok(CompileResult {