Skip to content

Commit

Permalink
feat: workspace level patch table added (#4086)
Browse files Browse the repository at this point in the history
## Description
closes #3958.

This is a handy feature for tests with workspaces. We were already
supporting a `patch` table feature for packages. This PR adds the same
feature for workspaces. Basically we can patch every instance of a
package with a different version. One main motivation behind this can be
patching the standard library while working with an unreleased version
of `forc`. With this feature we can pin the `std` version for the whole
workspace so that all members in the workspace depending on `std` would
depend on master version of the `std`.
  • Loading branch information
kayagokalp authored Apr 19, 2023
1 parent 375d31f commit 35fe324
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 15 deletions.
20 changes: 20 additions & 0 deletions docs/book/src/forc/workspaces.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ The key points for workspaces are:
Workspace manifests are declared within `Forc.toml` files and support the following fields:

* [`members`](#the-members-field) - Packages to include in the workspace.
* [`[patch]`](#the-patch-section) - Defines the patches.

An empty workspace can be created with `forc new --workspace` or `forc init --workspace`.

Expand All @@ -25,6 +26,25 @@ members = ["member1", "path/to/member2"]
The `members` field accepts entries to be given in relative path with respect to the workspace root.
Packages that are located within a workspace directory but are *not* contained within the `members` set are ignored.

## The `[patch]` section

The `[patch]` section can be used to override any dependency in the workspace dependency graph. The usage is the same with package level `[patch]` section and details can be seen [here](./manifest_reference.md#the-patch-section).

It is not allowed to declare patch table in member of a workspace if the workspace manifest file contains a patch table.

Example:

```toml
[workspace]
members = ["member1", "path/to/member2"]


[patch.'https://github.com/fuellabs/sway']
std = { git = "https://github.com/fuellabs/sway", branch = "test" }
```

In the above example each occurance of `std` as a dependency in the workspace will be changed with `std` from `test` branch of sway repo.

## Some `forc` commands that support workspaces

* `forc build` - Builds an entire workspace.
Expand Down
55 changes: 48 additions & 7 deletions forc-pkg/src/manifest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,38 @@ impl PackageManifestFile {
Ok(manifest_file)
}

/// Returns an iterator over patches defined in underlying `PackageManifest` if this is a
/// standalone package.
///
/// If this package is a member of a workspace, patches are fetched from
/// the workspace manifest file.
pub fn resolve_patches(&self) -> Result<impl Iterator<Item = (String, PatchMap)>> {
let workspace_patches = self
.workspace()
.ok()
.flatten()
.and_then(|workspace| workspace.patch.clone());
let package_patches = self.patch.clone();
match (workspace_patches, package_patches) {
(Some(_), Some(_)) => bail!("Found [patch] table both in workspace and member package's manifest file. Consider removing [patch] table from package's manifest file."),
(Some(workspace_patches), None) => Ok(workspace_patches.into_iter()),
(None, Some(pkg_patches)) => Ok(pkg_patches.into_iter()),
(None, None) => Ok(BTreeMap::default().into_iter()),
}
}

/// Retrieve the listed patches for the given name from underlying `PackageManifest` if this is
/// a standalone package.
///
/// If this package is a member of a workspace, patch is fetched from
/// the workspace manifest file.
pub fn resolve_patch(&self, patch_name: &str) -> Result<Option<PatchMap>> {
Ok(self
.resolve_patches()?
.find(|(p_name, _)| patch_name == p_name.as_str())
.map(|(_, patch)| patch))
}

/// Read the manifest from the `Forc.toml` in the directory specified by the given `path` or
/// any of its parent directories.
///
Expand Down Expand Up @@ -509,6 +541,13 @@ impl PackageManifest {
.flat_map(|patches| patches.iter())
}

/// Retrieve the listed patches for the given name.
pub fn patch(&self, patch_name: &str) -> Option<&PatchMap> {
self.patch
.as_ref()
.and_then(|patches| patches.get(patch_name))
}

/// Check for the `core` and `std` packages under `[dependencies]`. If both are missing, add
/// `std` implicitly.
///
Expand Down Expand Up @@ -568,13 +607,6 @@ impl PackageManifest {
})
}

/// Retrieve the listed patches for the given name.
pub fn patch(&self, patch_name: &str) -> Option<&PatchMap> {
self.patch
.as_ref()
.and_then(|patches| patches.get(patch_name))
}

/// Retrieve a reference to the contract dependency with the given name.
pub fn contract_dep(&self, contract_dep_name: &str) -> Option<&ContractDependency> {
self.contract_dependencies
Expand Down Expand Up @@ -721,6 +753,7 @@ pub struct WorkspaceManifestFile {
#[serde(rename_all = "kebab-case")]
pub struct WorkspaceManifest {
workspace: Workspace,
patch: Option<BTreeMap<String, PatchMap>>,
}

#[derive(Serialize, Deserialize, Clone, Debug)]
Expand Down Expand Up @@ -826,6 +859,14 @@ impl WorkspaceManifestFile {
pub fn lock_path(&self) -> PathBuf {
self.dir().to_path_buf().join(constants::LOCK_FILE_NAME)
}

/// Produce an iterator yielding all listed patches.
pub fn patches(&self) -> impl Iterator<Item = (&String, &PatchMap)> {
self.patch
.as_ref()
.into_iter()
.flat_map(|patches| patches.iter())
}
}

impl WorkspaceManifest {
Expand Down
16 changes: 8 additions & 8 deletions forc-pkg/src/source/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,19 +179,19 @@ impl Source {

/// If a patch exists for this dependency source within the given project
/// manifest, this returns the patch.
fn dep_patch<'manifest>(
fn dep_patch(
&self,
dep_name: &str,
manifest: &'manifest PackageManifestFile,
) -> Option<&'manifest manifest::Dependency> {
manifest: &PackageManifestFile,
) -> Result<Option<manifest::Dependency>> {
if let Source::Git(git) = self {
if let Some(patches) = manifest.patch(&git.repo.to_string()) {
if let Some(patches) = manifest.resolve_patch(&git.repo.to_string())? {
if let Some(patch) = patches.get(dep_name) {
return Some(patch);
return Ok(Some(patch.clone()));
}
}
}
None
Ok(None)
}

/// If a patch exists for the dependency associated with this source within
Expand All @@ -204,8 +204,8 @@ impl Source {
manifest: &PackageManifestFile,
members: &MemberManifestFiles,
) -> Result<Self> {
match self.dep_patch(dep_name, manifest) {
Some(patch) => Self::from_manifest_dep(manifest.dir(), patch, members),
match self.dep_patch(dep_name, manifest)? {
Some(patch) => Self::from_manifest_dep(manifest.dir(), &patch, members),
None => Ok(self.clone()),
}
}
Expand Down

0 comments on commit 35fe324

Please sign in to comment.