Skip to content

Commit 7aa59f9

Browse files
authored
Add forc-migrate tool (#6790)
## Description This PR introduces `forc-migrate`, a Forc tool for migrating Sway projects to the next breaking change version of Sway. The tool addresses two points crucial for code updates caused by breaking changes: - it informs developers about the breaking changes and **assists in planing and executing** the migration. - it **automatically changes source code** where possible, reducing the manual effort needed for code changes. Besides adding the `forc-migrate` tool, the PR: - extends `Diagnostic` to support migration diagnostics aside with errors and warnings. - changes `swayfmt` to support generating source code from arbitrary lexed trees. The change is a minimal one done only in the parts of `swayfmt` that are executed by migration steps written in this PR. Adapting `swayfmt` to fully support arbitrary lexed trees will be done in #6779. The migration for the `references` feature, migrating `ref mut` to `&mut`, is developed only partially, to demonstrate the development and usage of automatic migrations that alter the original source code. The intended usage of the tool is documented in detail in the "forc migrate" chapter of The Sway Book: _Forc reference > Plugins > forc_migrate_. (The generated documentation has issues that are caused by the documentation generation bug explained in #6792. These issues will be fixed in a separate PR that will fix it for all the plugins.) We expect the `forc-migrate` to evolve based on the developer's feedback. Some of the possible extensions of the tool are: - adding additional CLI options, e.g., for executing only specific migration steps, or ignoring them. - passing parameters to migration steps from the CLI. - not allowing updates by default, if the repository contains modified or untracked files. - migrating workspaces. - migrating other artifacts, e.g., Forc.toml files or contract IDs. - migrating between arbitrary versions of Sway. - migrating SDK code. - etc. `forc-migrate` also showed a clear need for better infrastructure for writing static analyzers and transforming Sway code. The approach used in the implementation of this PR should be seen as a pragmatic beginning, based on the reuse of what we currently have. Some future options are discussed in #6836. ## Demo ### `forc migrate show` Shows the breaking change features and related migration steps. This command can be run anywhere and does not require a Sway project. ``` Breaking change features: - storage_domains (#6701) - references (#5063) Migration steps (1 manual and 1 semiautomatic): storage_domains [M] Review explicitly defined slot keys in storage declarations (`in` keywords) references [S] Replace `ref mut` function parameters with `&mut` Experimental feature flags: - for Forc.toml: experimental = { storage_domains = true, references = true } - for CLI: --experimental storage_domains,references ``` ### `forc migrate check` Performs a dry-run of the migration on a concrete Sway project. It outputs all the occurrences in code that need to be reviewed or changed, as well as the migration time effort: ``` info: [storage_domains] Review explicitly defined slot keys in storage declarations (`in` keywords) --> /home/kebradalaonda/Desktop/M Forc migrate tool/src/main.sw:19:10 | ... 19 | y in b256::zero(): u64 = 0, | ------------ 20 | z: u64 = 0, 21 | a in calculate_slot_address(): u64 = 0, | ------------------------ 22 | b in 0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20: u64 = 0, | ------------------------------------------------------------------ | = help: If the slot keys used in `in` keywords represent keys generated for `storage` fields = help: by the Sway compiler, those keys might need to be recalculated. = help: = help: The previous formula for calculating storage field keys was: `sha256("storage.<field name>")`. = help: The new formula is: `sha256((0u8, "storage.<field name>"))`. = help: = help: For a detailed migration guide see: #6701 ____ Migration effort: storage_domains [M] Review explicitly defined slot keys in storage declarations (`in` keywords) Occurrences: 3 Migration effort (hh::mm): ~00:06 references [S] Replace `ref mut` function parameters with `&mut` Occurrences: 0 Migration effort (hh::mm): ~00:00 Total migration effort (hh::mm): ~00:06 ``` ### `forc migrate run` Runs the migration steps and guides developers through the migration process. ## Checklist - [x] I have linked to any relevant issues. - [x] I have commented my code, particularly in hard-to-understand areas. - [x] I have updated the documentation where relevant (API docs, the reference, and the Sway book). - [x] If my change requires substantial documentation changes, I have [requested support from the DevRel team](https://github.com/FuelLabs/devrel-requests/issues/new/choose) - [ ] I have added tests that prove my fix is effective or that my feature works. - [ ] I have added (or requested a maintainer to add) the necessary `Breaking*` or `New Feature` labels where relevant. - [x] I have done my best to ensure that my PR adheres to [the Fuel Labs Code Review Standards](https://github.com/FuelLabs/rfcs/blob/master/text/code-standards/external-contributors.md). - [x] I have requested a review from the relevant team or maintainers.
1 parent 3f7b5f1 commit 7aa59f9

File tree

43 files changed

+3006
-85
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+3006
-85
lines changed

.github/workflows/ci.yml

+1
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,7 @@ jobs:
239239
cargo install --locked --debug --path ./forc-plugins/forc-doc
240240
cargo install --locked --debug --path ./forc-plugins/forc-tx
241241
cargo install --locked --debug --path ./forc-plugins/forc-crypto
242+
cargo install --locked --debug --path ./forc-plugins/forc-migrate
242243
cargo install --locked --debug forc-explore
243244
- name: Install mdbook-forc-documenter
244245
run: cargo install --locked --debug --path ./scripts/mdbook-forc-documenter

Cargo.lock

+20
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ members = [
99
"forc-plugins/forc-doc",
1010
"forc-plugins/forc-fmt",
1111
"forc-plugins/forc-lsp",
12+
"forc-plugins/forc-migrate",
1213
"forc-plugins/forc-tx",
1314
"forc-test",
1415
"forc-tracing",

docs/book/spell-check-custom-words.txt

+6-1
Original file line numberDiff line numberDiff line change
@@ -231,4 +231,9 @@ fmt
231231
deallocated
232232
deallocate
233233
destructors
234-
destructor
234+
destructor
235+
semiautomatically
236+
FuelLabs
237+
github
238+
toml
239+
hardcoded

docs/book/src/SUMMARY.md

+1
Original file line numberDiff line numberDiff line change
@@ -105,3 +105,4 @@
105105
- [forc explore](./forc/plugins/forc_explore.md)
106106
- [forc fmt](./forc/plugins/forc_fmt.md)
107107
- [forc lsp](./forc/plugins/forc_lsp.md)
108+
- [forc migrate](./forc/plugins/forc_migrate.md)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# forc migrate

forc-plugins/forc-doc/src/cli.rs

+5-7
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ forc_util::cli_examples! {
88
[ Build the docs for a project in the current path and open it in the browser => "forc doc --open" ]
99
[ Build the docs for a project located in another path => "forc doc --path {path}" ]
1010
[ Build the docs for the current project exporting private types => "forc doc --document-private-items" ]
11-
[ Build the docs offline without downloading any dependency from the network => "forc doc --offline" ]
11+
[ Build the docs offline without downloading any dependencies => "forc doc --offline" ]
1212
}
1313
}
1414

@@ -35,11 +35,8 @@ pub struct Command {
3535
/// Meaning it will only try to use previously downloaded dependencies.
3636
#[clap(long = "offline")]
3737
pub offline: bool,
38-
/// Silent mode. Don't output any warnings or errors to the command line.
39-
#[clap(long = "silent", short = 's')]
40-
pub silent: bool,
4138
/// Requires that the Forc.lock file is up-to-date. If the lock file is missing, or it
42-
/// needs to be updated, Forc will exit with an error
39+
/// needs to be updated, Forc will exit with an error.
4340
#[clap(long)]
4441
pub locked: bool,
4542
/// Do not build documentation for dependencies.
@@ -50,10 +47,11 @@ pub struct Command {
5047
/// Possible values: PUBLIC, LOCAL, <GATEWAY_URL>
5148
#[clap(long)]
5249
pub ipfs_node: Option<IPFSNode>,
53-
5450
#[cfg(test)]
5551
pub(crate) doc_path: Option<String>,
56-
5752
#[clap(flatten)]
5853
pub experimental: sway_features::CliFields,
54+
/// Silent mode. Don't output any warnings or errors to the command line.
55+
#[clap(long = "silent", short = 's')]
56+
pub silent: bool,
5957
}

forc-plugins/forc-fmt/src/main.rs

+1-10
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use forc_pkg::{
77
WorkspaceManifestFile,
88
};
99
use forc_tracing::{init_tracing_subscriber, println_error, println_green, println_red};
10-
use forc_util::fs_locking::PidFileLocking;
10+
use forc_util::fs_locking::is_file_dirty;
1111
use prettydiff::{basic::DiffOp, diff_lines};
1212
use std::{
1313
default::Default,
@@ -101,15 +101,6 @@ fn run() -> Result<()> {
101101
Ok(())
102102
}
103103

104-
/// Checks if the specified file is marked as "dirty".
105-
/// This is used to prevent formatting files that are currently open in an editor
106-
/// with unsaved changes.
107-
///
108-
/// Returns `true` if a corresponding "dirty" flag file exists, `false` otherwise.
109-
fn is_file_dirty<X: AsRef<Path>>(path: X) -> bool {
110-
PidFileLocking::lsp(path.as_ref()).is_locked()
111-
}
112-
113104
/// Recursively get a Vec<PathBuf> of subdirectories that contains a Forc.toml.
114105
fn get_sway_dirs(workspace_dir: PathBuf) -> Vec<PathBuf> {
115106
let mut dirs_to_format = vec![];

forc-plugins/forc-migrate/Cargo.toml

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
[package]
2+
name = "forc-migrate"
3+
version.workspace = true
4+
description = "Migrate Sway projects to the next breaking change version of Sway."
5+
authors.workspace = true
6+
edition.workspace = true
7+
homepage.workspace = true
8+
license.workspace = true
9+
repository.workspace = true
10+
11+
[dependencies]
12+
anyhow.workspace = true
13+
clap = { workspace = true, features = ["derive"] }
14+
forc-pkg.workspace = true
15+
forc-tracing.workspace = true
16+
forc-util.workspace = true
17+
itertools.workspace = true
18+
num-bigint.workspace = true
19+
sha2.workspace = true
20+
sway-ast.workspace = true
21+
sway-core.workspace = true
22+
sway-error.workspace = true
23+
sway-features.workspace = true
24+
sway-types.workspace = true
25+
swayfmt.workspace = true
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
use clap::Parser;
2+
3+
use crate::{
4+
cli::{
5+
self,
6+
shared::{
7+
compile_package, create_migration_diagnostic, print_features_and_migration_steps,
8+
},
9+
},
10+
get_migration_steps_or_return,
11+
migrations::{DryRun, MigrationStepKind},
12+
};
13+
use anyhow::{Ok, Result};
14+
use forc_util::format_diagnostic;
15+
use itertools::Itertools;
16+
use sway_core::Engines;
17+
18+
forc_util::cli_examples! {
19+
crate::cli::Opt {
20+
[ Check the project in the current path => "forc migrate check"]
21+
[ Check the project located in another path => "forc migrate check --path {path}" ]
22+
}
23+
}
24+
25+
/// Check the project for code that needs to be migrated.
26+
///
27+
/// Dry-runs the migration steps and prints places in code that need to be reviewed or changed.
28+
#[derive(Debug, Parser)]
29+
pub(crate) struct Command {
30+
#[clap(flatten)]
31+
pub check: cli::shared::Compile,
32+
}
33+
34+
pub(crate) fn exec(command: Command) -> Result<()> {
35+
let migration_steps = get_migration_steps_or_return!();
36+
let engines = Engines::default();
37+
let build_instructions = command.check;
38+
39+
let mut program_info = compile_package(&engines, &build_instructions)?;
40+
41+
// Dry-run all the migration steps.
42+
let mut check_result = vec![];
43+
for (feature, migration_steps) in migration_steps.iter() {
44+
for migration_step in migration_steps.iter() {
45+
let migration_point_spans = match migration_step.kind {
46+
MigrationStepKind::Instruction(instruction) => instruction(&program_info)?,
47+
MigrationStepKind::CodeModification(modification, _) => {
48+
modification(&mut program_info.as_mut(), DryRun::Yes)?
49+
}
50+
MigrationStepKind::Interaction(instruction, _, _) => instruction(&program_info)?,
51+
};
52+
53+
check_result.push((feature, migration_step, migration_point_spans));
54+
}
55+
}
56+
57+
// For every migration step, display the found occurrences in code that require migration effort, if any.
58+
for (feature, migration_step, occurrences_spans) in check_result.iter() {
59+
if let Some(diagnostic) =
60+
create_migration_diagnostic(engines.se(), feature, migration_step, occurrences_spans)
61+
{
62+
format_diagnostic(&diagnostic);
63+
}
64+
}
65+
66+
// Display the summary of the migration effort.
67+
let features_and_migration_steps = check_result
68+
.iter()
69+
.chunk_by(|(feature, _, _)| feature)
70+
.into_iter()
71+
.map(|(key, chunk)| {
72+
(
73+
**key,
74+
chunk
75+
.map(|(_, migration_step, migration_point_spans)| {
76+
(*migration_step, Some(migration_point_spans.len()))
77+
})
78+
.collect::<Vec<_>>(),
79+
)
80+
})
81+
.collect::<Vec<_>>();
82+
83+
println!("Migration effort:");
84+
println!();
85+
print_features_and_migration_steps(&features_and_migration_steps);
86+
87+
Ok(())
88+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
pub(crate) mod check;
2+
pub(crate) mod run;
3+
pub(crate) mod show;

0 commit comments

Comments
 (0)