Skip to content

Commit

Permalink
Add forc feature for overriding packages in the package graph, akin t…
Browse files Browse the repository at this point in the history
…o cargo's [patch] feature (FuelLabs#1836)
  • Loading branch information
kayagokalp authored Jun 24, 2022
1 parent a43aec0 commit 3aa1ca9
Show file tree
Hide file tree
Showing 8 changed files with 162 additions and 7 deletions.
46 changes: 46 additions & 0 deletions docs/src/forc/manifest_reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ The `Forc.toml` (the _manifest_ file) is a compulsory file for each package and

* [`[build-profiles]`](#the-build-profiles--section) - Defines the build profiles.

* [`[patch]`](#the-patch-section) - Defines the patches.

## The `[project]` section

An example `Forc.toml` is shown below. Under `[project]` the following fields are optional:
Expand Down Expand Up @@ -99,3 +101,47 @@ Note that providing the corresponding cli options (like `--print-finalized-asm`)
* print-intermediate-asm - false
* print-ir - false
* silent - false

## The `[patch]` section

The [patch] section of `Forc.toml` can be used to override dependencies with other copies. The example provided below patches <https://github.com/fuellabs/sway> source with master branch of the same repo.

```toml
[project]
authors = ["user"]
entry = "main.sw"
organization = "Fuel_Labs"
license = "Apache-2.0"
name = "wallet_contract"

[dependencies]

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

In the example above, `std` is patched with the `test` branch from `std` repo. You can also patch git dependencies with dependencies defined with a path.

```toml
[patch.'https://github.com/fuellabs/sway']
std = { path = "/path/to/local_std_version" }
```

Just like `std` or `core` you can also patch dependencies you declared with a git repo.

```toml
[project]
authors = ["user"]
entry = "main.sw"
organization = "Fuel_Labs"
license = "Apache-2.0"
name = "wallet_contract"

[dependencies]
foo = { git = "https://github.com/foo/foo", branch = "master" }

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

Note that each key after the `[patch]` is a URL of the source that is being patched.
11 changes: 11 additions & 0 deletions forc-pkg/src/manifest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ use std::{
use sway_core::{parse, TreeType};
use sway_utils::constants;

type PatchMap = BTreeMap<String, Dependency>;

/// A [Manifest] that was deserialized from a file at a particular path.
#[derive(Debug)]
pub struct ManifestFile {
Expand All @@ -27,6 +29,7 @@ pub struct Manifest {
pub project: Project,
pub network: Option<Network>,
pub dependencies: Option<BTreeMap<String, Dependency>>,
pub patch: Option<BTreeMap<String, PatchMap>>,
build_profile: Option<BTreeMap<String, BuildProfile>>,
}

Expand Down Expand Up @@ -271,6 +274,14 @@ impl Manifest {
})
}

/// 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())
}

/// Check for the `core` and `std` packages under `[dependencies]`. If both are missing, add
/// `std` implicitly.
///
Expand Down
68 changes: 61 additions & 7 deletions forc-pkg/src/pkg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -388,7 +388,8 @@ impl BuildPlan {
}

let name = dep.package().unwrap_or(dep_name).to_string();
let source = dep_to_source(proj_path, dep)?;
let source =
apply_patch(&name, &dep_to_source(proj_path, dep)?, manifest, proj_path)?;
let dep_pkg = Pkg { name, source };
Ok((dep_name, dep_pkg))
})
Expand Down Expand Up @@ -882,10 +883,24 @@ pub fn graph_to_path_map(
bail!("missing path info for dependency: {}", &dep_name);
}
})?;
let rel_dep_path = detailed
.path
.as_ref()
.ok_or_else(|| anyhow!("missing path info for dependency: {}", dep.name))?;
// Check if there is a patch for this dep
let patch = parent_manifest
.patches()
.find_map(|patches| patches.1.get(&dep_name));
// If there is one fetch the details.
let patch_details = patch.and_then(|patch| match patch {
Dependency::Simple(_) => None,
Dependency::Detailed(detailed) => Some(detailed),
});
// If there is a detail we should have the path.
// If not either we do not have a patch so we are checking dependencies of parent
// If we can't find the path there, either patch or dep is provided as a basic dependency, so we are missing the path info.
let rel_dep_path = if let Some(patch_details) = patch_details {
patch_details.path.as_ref()
} else {
detailed.path.as_ref()
}
.ok_or_else(|| anyhow!("missing path info for dep: {}", &dep_name))?;
let path = parent_path.join(rel_dep_path);
if !path.exists() {
bail!("pinned `path` dependency \"{}\" source missing", dep.name);
Expand Down Expand Up @@ -985,6 +1000,38 @@ pub(crate) fn fetch_deps(
Ok((graph, path_map))
}

fn apply_patch(
name: &str,
source: &Source,
manifest: &Manifest,
parent_path: &Path,
) -> Result<Source> {
match source {
// Check if the patch is for a git dependency.
Source::Git(git) => {
// Check if we got a patch for the git dependency.
if let Some(source_patches) = manifest
.patch
.as_ref()
.and_then(|patches| patches.get(git.repo.as_str()))
{
if let Some(patch) = source_patches.get(name) {
Ok(dep_to_source(parent_path, patch)?)
} else {
bail!(
"Cannot find the patch for the {} for package {}",
git.repo,
name
)
}
} else {
Ok(source.clone())
}
}
_ => Ok(source.clone()),
}
}

/// Produce a unique ID for a particular fetch pass.
///
/// This is used in the temporary git directory and allows for avoiding contention over the git repo directory.
Expand Down Expand Up @@ -1013,7 +1060,12 @@ fn fetch_children(
let parent_path = path_map[&parent_id].clone();
for (dep_name, dep) in manifest.deps() {
let name = dep.package().unwrap_or(dep_name).to_string();
let source = dep_to_source(&parent_path, dep)?;
let source = apply_patch(
&name,
&dep_to_source(&parent_path, dep)?,
manifest,
&parent_path,
)?;
if offline_mode && !matches!(source, Source::Path(_)) {
bail!("Unable to fetch pkg {:?} in offline mode", source);
}
Expand Down Expand Up @@ -1292,7 +1344,9 @@ fn dep_to_source(pkg_path: &Path, dep: &Dependency) -> Result<Source> {
Dependency::Detailed(ref det) => match (&det.path, &det.version, &det.git) {
(Some(relative_path), _, _) => {
let path = pkg_path.join(relative_path);
Source::Path(path.canonicalize()?)
Source::Path(path.canonicalize().map_err(|err| {
anyhow!("Cant apply patch from {}, cause: {}", relative_path, &err)
})?)
}
(_, _, Some(repo)) => {
let reference = match (&det.branch, &det.tag, &det.rev) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[[package]]
name = 'core'
source = 'path+from-root-B0CAE5E4D7DDF437'
dependencies = []

[[package]]
name = 'dependency_patching'
source = 'root'
dependencies = ['std']

[[package]]
name = 'std'
source = 'path+from-root-B0CAE5E4D7DDF437'
dependencies = ['core']
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[project]
authors = ["Fuel Labs <[email protected]>"]
entry = "main.sw"
license = "Apache-2.0"
name = "dependency_patching"

[dependencies]

[patch.'https://github.com/fuellabs/sway']
std = { path = "../../../../../../../sway-lib-std" }
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[
{
"inputs": [],
"name": "main",
"outputs": [
{
"components": null,
"name": "",
"type": "u64"
}
],
"type": "function"
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
script;

fn main() -> u64 {
0
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
category = "compile"

0 comments on commit 3aa1ca9

Please sign in to comment.