Skip to content

Commit 3aa1ca9

Browse files
authored
Add forc feature for overriding packages in the package graph, akin to cargo's [patch] feature (#1836)
1 parent a43aec0 commit 3aa1ca9

File tree

8 files changed

+162
-7
lines changed

8 files changed

+162
-7
lines changed

docs/src/forc/manifest_reference.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ The `Forc.toml` (the _manifest_ file) is a compulsory file for each package and
1717

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

20+
* [`[patch]`](#the-patch-section) - Defines the patches.
21+
2022
## The `[project]` section
2123

2224
An example `Forc.toml` is shown below. Under `[project]` the following fields are optional:
@@ -99,3 +101,47 @@ Note that providing the corresponding cli options (like `--print-finalized-asm`)
99101
* print-intermediate-asm - false
100102
* print-ir - false
101103
* silent - false
104+
105+
## The `[patch]` section
106+
107+
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.
108+
109+
```toml
110+
[project]
111+
authors = ["user"]
112+
entry = "main.sw"
113+
organization = "Fuel_Labs"
114+
license = "Apache-2.0"
115+
name = "wallet_contract"
116+
117+
[dependencies]
118+
119+
[patch.'https://github.com/fuellabs/sway']
120+
std = { git = "https://github.com/fuellabs/sway", branch = "test" }
121+
```
122+
123+
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.
124+
125+
```toml
126+
[patch.'https://github.com/fuellabs/sway']
127+
std = { path = "/path/to/local_std_version" }
128+
```
129+
130+
Just like `std` or `core` you can also patch dependencies you declared with a git repo.
131+
132+
```toml
133+
[project]
134+
authors = ["user"]
135+
entry = "main.sw"
136+
organization = "Fuel_Labs"
137+
license = "Apache-2.0"
138+
name = "wallet_contract"
139+
140+
[dependencies]
141+
foo = { git = "https://github.com/foo/foo", branch = "master" }
142+
143+
[patch.'https://github.com/foo']
144+
foo = { git = "https://github.com/foo/foo", branch = "test" }
145+
```
146+
147+
Note that each key after the `[patch]` is a URL of the source that is being patched.

forc-pkg/src/manifest.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ use std::{
1111
use sway_core::{parse, TreeType};
1212
use sway_utils::constants;
1313

14+
type PatchMap = BTreeMap<String, Dependency>;
15+
1416
/// A [Manifest] that was deserialized from a file at a particular path.
1517
#[derive(Debug)]
1618
pub struct ManifestFile {
@@ -27,6 +29,7 @@ pub struct Manifest {
2729
pub project: Project,
2830
pub network: Option<Network>,
2931
pub dependencies: Option<BTreeMap<String, Dependency>>,
32+
pub patch: Option<BTreeMap<String, PatchMap>>,
3033
build_profile: Option<BTreeMap<String, BuildProfile>>,
3134
}
3235

@@ -271,6 +274,14 @@ impl Manifest {
271274
})
272275
}
273276

277+
/// Produce an iterator yielding all listed patches.
278+
pub fn patches(&self) -> impl Iterator<Item = (&String, &PatchMap)> {
279+
self.patch
280+
.as_ref()
281+
.into_iter()
282+
.flat_map(|patches| patches.iter())
283+
}
284+
274285
/// Check for the `core` and `std` packages under `[dependencies]`. If both are missing, add
275286
/// `std` implicitly.
276287
///

forc-pkg/src/pkg.rs

Lines changed: 61 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -388,7 +388,8 @@ impl BuildPlan {
388388
}
389389

390390
let name = dep.package().unwrap_or(dep_name).to_string();
391-
let source = dep_to_source(proj_path, dep)?;
391+
let source =
392+
apply_patch(&name, &dep_to_source(proj_path, dep)?, manifest, proj_path)?;
392393
let dep_pkg = Pkg { name, source };
393394
Ok((dep_name, dep_pkg))
394395
})
@@ -882,10 +883,24 @@ pub fn graph_to_path_map(
882883
bail!("missing path info for dependency: {}", &dep_name);
883884
}
884885
})?;
885-
let rel_dep_path = detailed
886-
.path
887-
.as_ref()
888-
.ok_or_else(|| anyhow!("missing path info for dependency: {}", dep.name))?;
886+
// Check if there is a patch for this dep
887+
let patch = parent_manifest
888+
.patches()
889+
.find_map(|patches| patches.1.get(&dep_name));
890+
// If there is one fetch the details.
891+
let patch_details = patch.and_then(|patch| match patch {
892+
Dependency::Simple(_) => None,
893+
Dependency::Detailed(detailed) => Some(detailed),
894+
});
895+
// If there is a detail we should have the path.
896+
// If not either we do not have a patch so we are checking dependencies of parent
897+
// 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.
898+
let rel_dep_path = if let Some(patch_details) = patch_details {
899+
patch_details.path.as_ref()
900+
} else {
901+
detailed.path.as_ref()
902+
}
903+
.ok_or_else(|| anyhow!("missing path info for dep: {}", &dep_name))?;
889904
let path = parent_path.join(rel_dep_path);
890905
if !path.exists() {
891906
bail!("pinned `path` dependency \"{}\" source missing", dep.name);
@@ -985,6 +1000,38 @@ pub(crate) fn fetch_deps(
9851000
Ok((graph, path_map))
9861001
}
9871002

1003+
fn apply_patch(
1004+
name: &str,
1005+
source: &Source,
1006+
manifest: &Manifest,
1007+
parent_path: &Path,
1008+
) -> Result<Source> {
1009+
match source {
1010+
// Check if the patch is for a git dependency.
1011+
Source::Git(git) => {
1012+
// Check if we got a patch for the git dependency.
1013+
if let Some(source_patches) = manifest
1014+
.patch
1015+
.as_ref()
1016+
.and_then(|patches| patches.get(git.repo.as_str()))
1017+
{
1018+
if let Some(patch) = source_patches.get(name) {
1019+
Ok(dep_to_source(parent_path, patch)?)
1020+
} else {
1021+
bail!(
1022+
"Cannot find the patch for the {} for package {}",
1023+
git.repo,
1024+
name
1025+
)
1026+
}
1027+
} else {
1028+
Ok(source.clone())
1029+
}
1030+
}
1031+
_ => Ok(source.clone()),
1032+
}
1033+
}
1034+
9881035
/// Produce a unique ID for a particular fetch pass.
9891036
///
9901037
/// This is used in the temporary git directory and allows for avoiding contention over the git repo directory.
@@ -1013,7 +1060,12 @@ fn fetch_children(
10131060
let parent_path = path_map[&parent_id].clone();
10141061
for (dep_name, dep) in manifest.deps() {
10151062
let name = dep.package().unwrap_or(dep_name).to_string();
1016-
let source = dep_to_source(&parent_path, dep)?;
1063+
let source = apply_patch(
1064+
&name,
1065+
&dep_to_source(&parent_path, dep)?,
1066+
manifest,
1067+
&parent_path,
1068+
)?;
10171069
if offline_mode && !matches!(source, Source::Path(_)) {
10181070
bail!("Unable to fetch pkg {:?} in offline mode", source);
10191071
}
@@ -1292,7 +1344,9 @@ fn dep_to_source(pkg_path: &Path, dep: &Dependency) -> Result<Source> {
12921344
Dependency::Detailed(ref det) => match (&det.path, &det.version, &det.git) {
12931345
(Some(relative_path), _, _) => {
12941346
let path = pkg_path.join(relative_path);
1295-
Source::Path(path.canonicalize()?)
1347+
Source::Path(path.canonicalize().map_err(|err| {
1348+
anyhow!("Cant apply patch from {}, cause: {}", relative_path, &err)
1349+
})?)
12961350
}
12971351
(_, _, Some(repo)) => {
12981352
let reference = match (&det.branch, &det.tag, &det.rev) {
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[[package]]
2+
name = 'core'
3+
source = 'path+from-root-B0CAE5E4D7DDF437'
4+
dependencies = []
5+
6+
[[package]]
7+
name = 'dependency_patching'
8+
source = 'root'
9+
dependencies = ['std']
10+
11+
[[package]]
12+
name = 'std'
13+
source = 'path+from-root-B0CAE5E4D7DDF437'
14+
dependencies = ['core']
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
[project]
2+
authors = ["Fuel Labs <contact@fuel.sh>"]
3+
entry = "main.sw"
4+
license = "Apache-2.0"
5+
name = "dependency_patching"
6+
7+
[dependencies]
8+
9+
[patch.'https://github.com/fuellabs/sway']
10+
std = { path = "../../../../../../../sway-lib-std" }
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[
2+
{
3+
"inputs": [],
4+
"name": "main",
5+
"outputs": [
6+
{
7+
"components": null,
8+
"name": "",
9+
"type": "u64"
10+
}
11+
],
12+
"type": "function"
13+
}
14+
]
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
script;
2+
3+
fn main() -> u64 {
4+
0
5+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
category = "compile"

0 commit comments

Comments
 (0)