Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add forc check command #2026

Merged
merged 21 commits into from
Jun 22, 2022
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions docs/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
- [Commands](./forc/commands/index.md)
- [forc addr2line](./forc/commands/forc_addr2line.md)
- [forc build](./forc/commands/forc_build.md)
- [forc check](./forc/commands/forc_check.md)
- [forc clean](./forc/commands/forc_clean.md)
- [forc completions](./forc/commands/forc_completions.md)
- [forc deploy](./forc/commands/forc_deploy.md)
Expand Down
1 change: 1 addition & 0 deletions docs/src/forc/commands/forc_check.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# forc check
128 changes: 125 additions & 3 deletions forc-pkg/src/pkg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use petgraph::{
use serde::{Deserialize, Serialize};
use std::{
collections::{hash_map, BTreeSet, HashMap, HashSet},
fmt,
fmt, fs,
hash::{Hash, Hasher},
path::{Path, PathBuf},
str::FromStr,
Expand Down Expand Up @@ -235,6 +235,61 @@ impl BuildPlan {
})
}

/// Create a new build plan and syncronize manifest and lock file
JoshuaBatty marked this conversation as resolved.
Show resolved Hide resolved
pub fn load_from_manifest(
manifest: &ManifestFile,
locked: bool,
offline: bool,
sway_git_tag: &str,
) -> Result<Self> {
let lock_path = forc_util::lock_path(manifest.dir());
let plan_result = BuildPlan::from_lock_file(&lock_path, sway_git_tag);

// Retrieve the old lock file state so we can produce a diff.
let old_lock = plan_result
.as_ref()
.ok()
.map(|plan| Lock::from_graph(plan.graph()))
.unwrap_or_default();

// Check if there are any errors coming from the BuildPlan generation from the lock file
// If there are errors we will need to create the BuildPlan from scratch, i.e fetch & pin everything
let mut new_lock_cause = None;
let mut plan = plan_result.or_else(|e| -> Result<BuildPlan> {
if locked {
bail!(
"The lock file {} needs to be updated but --locked was passed to prevent this.",
lock_path.to_string_lossy()
);
}
new_lock_cause = if e.to_string().contains("No such file or directory") {
Some(anyhow!("lock file did not exist"))
} else {
Some(e)
};
let plan = BuildPlan::new(manifest, sway_git_tag, offline)?;
Ok(plan)
})?;

// If there are no issues with the BuildPlan generated from the lock file
// Check and apply the diff.
if new_lock_cause.is_none() {
let diff = plan.validate(manifest, sway_git_tag)?;
if !diff.added.is_empty() || !diff.removed.is_empty() {
new_lock_cause = Some(anyhow!("lock file did not match manifest `diff`"));
plan = plan.apply_pkg_diff(diff, sway_git_tag, offline)?;
}
}

if let Some(cause) = new_lock_cause {
info!(" Creating a new `Forc.lock` file. (Cause: {})", cause);
create_new_lock(&plan, &old_lock, manifest, &lock_path)?;
info!(" Created new lock file at {}", lock_path.display());
}

Ok(plan)
}

/// Create a new build plan from an existing one. Needs the difference with the existing plan with the lock.
pub fn apply_pkg_diff(
&self,
Expand Down Expand Up @@ -1316,6 +1371,22 @@ pub fn dependency_namespace(
namespace
}

/// Compiles the package to an AST.
pub fn compile_ast(
manifest: &ManifestFile,
build_config: &BuildConfig,
namespace: namespace::Module,
) -> Result<CompileAstResult> {
let source = manifest.entry_string()?;
let sway_build_config =
sway_build_config(manifest.dir(), &manifest.entry_path(), build_config)?;
Ok(sway_core::compile_to_ast(
source,
namespace,
Some(&sway_build_config),
))
}

/// Compiles the given package.
///
/// ## Program Types
Expand All @@ -1342,12 +1413,11 @@ pub fn compile(
source_map: &mut SourceMap,
) -> Result<(Compiled, Option<namespace::Root>)> {
let entry_path = manifest.entry_path();
let source = manifest.entry_string()?;
let sway_build_config = sway_build_config(manifest.dir(), &entry_path, build_config)?;
let silent_mode = build_config.silent;

// First, compile to an AST. We'll update the namespace and check for JSON ABI output.
let ast_res = sway_core::compile_to_ast(source, namespace, Some(&sway_build_config));
let ast_res = compile_ast(manifest, build_config, namespace)?;
match &ast_res {
CompileAstResult::Failure { warnings, errors } => {
print_on_failure(silent_mode, warnings, errors);
Expand Down Expand Up @@ -1444,6 +1514,43 @@ pub fn build(
Ok((compiled, source_map))
}

/// Compile the entire forc package and return a CompileAstResult.
pub fn check(
plan: &BuildPlan,
silent_mode: bool,
sway_git_tag: &str,
) -> anyhow::Result<CompileAstResult> {
let conf = &BuildConfig {
print_ir: false,
print_finalized_asm: false,
print_intermediate_asm: false,
silent: silent_mode,
};

let mut namespace_map = Default::default();
let mut source_map = SourceMap::new();
for (i, &node) in plan.compilation_order.iter().enumerate() {
let dep_namespace =
dependency_namespace(&namespace_map, &plan.graph, &plan.compilation_order, node);
let pkg = &plan.graph[node];
let path = &plan.path_map[&pkg.id()];
let manifest = ManifestFile::from_dir(path, sway_git_tag)?;
let (_, maybe_namespace) =
compile(pkg, &manifest, conf, dep_namespace.clone(), &mut source_map)?;
JoshuaBatty marked this conversation as resolved.
Show resolved Hide resolved
if let Some(namespace) = maybe_namespace {
namespace_map.insert(node, namespace.into());
}
source_map.insert_dependency(path.clone());

// We only need to return the final CompileAstResult
let ast_res = compile_ast(&manifest, conf, dep_namespace)?;
if i == plan.compilation_order.len() - 1 {
return Ok(ast_res);
}
}
bail!("unable to check sway program from build plan")
JoshuaBatty marked this conversation as resolved.
Show resolved Hide resolved
}

/// Attempt to find a `Forc.toml` with the given project name within the given directory.
///
/// Returns the path to the package on success, or `None` in the case it could not be found.
Expand Down Expand Up @@ -1565,3 +1672,18 @@ pub fn fuel_core_not_running(node_url: &str) -> anyhow::Error {
let message = format!("could not get a response from node at the URL {}. Start a node with `fuel-core`. See https://github.com/FuelLabs/fuel-core#running for more information", node_url);
Error::msg(message)
}

fn create_new_lock(
plan: &BuildPlan,
old_lock: &Lock,
manifest: &ManifestFile,
lock_path: &Path,
) -> Result<()> {
let lock = Lock::from_graph(plan.graph());
let diff = lock.diff(old_lock);
super::lock::print_diff(&manifest.project.name, &diff);
let string = toml::ser::to_string_pretty(&lock)
.map_err(|e| anyhow!("failed to serialize lock file: {}", e))?;
fs::write(&lock_path, &string).map_err(|e| anyhow!("failed to write lock file: {}", e))?;
Ok(())
}
30 changes: 30 additions & 0 deletions forc/src/cli/commands/check.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
use crate::ops::forc_check;
use anyhow::Result;
use clap::Parser;

/// Check the current or target project and all of its dependencies for errors.
///
/// This will essentially compile the packages without performing the final step of code generation,
/// which is faster than running forc build.
#[derive(Debug, Default, Parser)]
pub struct Command {
/// Path to the project, if not specified, current working directory will be used.
#[clap(short, long)]
pub path: Option<String>,
/// Offline mode, prevents Forc from using the network when managing dependencies.
/// Meaning it will only try to use previously downloaded dependencies.
#[clap(long = "offline")]
pub offline_mode: bool,
/// Silent mode. Don't output any warnings or errors to the command line.
#[clap(long = "silent", short = 's')]
pub silent_mode: bool,
/// Requires that the Forc.lock file is up-to-date. If the lock file is missing, or it
/// needs to be updated, Forc will exit with an error
#[clap(long)]
pub locked: bool,
}

pub(crate) fn exec(command: Command) -> Result<()> {
forc_check::check(command)?;
Ok(())
}
1 change: 1 addition & 0 deletions forc/src/cli/commands/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub mod addr2line;
pub mod build;
pub mod check;
pub mod clean;
pub mod completions;
pub mod deploy;
Expand Down
7 changes: 5 additions & 2 deletions forc/src/cli/mod.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
use self::commands::{
addr2line, build, clean, completions, deploy, init, json_abi, parse_bytecode, plugins, run,
template, test, update,
addr2line, build, check, clean, completions, deploy, init, json_abi, parse_bytecode, plugins,
run, template, test, update,
};
use addr2line::Command as Addr2LineCommand;
use anyhow::{anyhow, Result};
pub use build::Command as BuildCommand;
pub use check::Command as CheckCommand;
use clap::Parser;
pub use clean::Command as CleanCommand;
pub use completions::Command as CompletionsCommand;
Expand Down Expand Up @@ -35,6 +36,7 @@ enum Forc {
Addr2Line(Addr2LineCommand),
#[clap(visible_alias = "b")]
Build(BuildCommand),
Check(CheckCommand),
Clean(CleanCommand),
Completions(CompletionsCommand),
Deploy(DeployCommand),
Expand Down Expand Up @@ -64,6 +66,7 @@ pub async fn run_cli() -> Result<()> {
match opt.command {
Forc::Addr2Line(command) => addr2line::exec(command),
Forc::Build(command) => build::exec(command),
Forc::Check(command) => check::exec(command),
Forc::Clean(command) => clean::exec(command),
Forc::Completions(command) => completions::exec(command),
Forc::Deploy(command) => deploy::exec(command).await,
Expand Down
2 changes: 1 addition & 1 deletion forc/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
pub mod cli;
mod ops;
mod utils;
pub mod utils;

#[cfg(feature = "test")]
pub mod test {
Expand Down
71 changes: 6 additions & 65 deletions forc/src/ops/forc_build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ use crate::{
cli::BuildCommand,
utils::{SWAY_BIN_HASH_SUFFIX, SWAY_BIN_ROOT_SUFFIX, SWAY_GIT_TAG},
};
use anyhow::{anyhow, bail, Result};
use forc_pkg::{self as pkg, lock, Lock, ManifestFile};
use forc_util::{default_output_directory, lock_path};
use anyhow::Result;
use forc_pkg::{self as pkg, ManifestFile};
use forc_util::default_output_directory;
use fuel_tx::Contract;
use std::{
fs::{self, File},
path::{Path, PathBuf},
path::PathBuf,
};
use sway_core::TreeType;
use tracing::{info, warn};
Expand Down Expand Up @@ -56,6 +56,8 @@ pub fn build(command: BuildCommand) -> Result<pkg::Compiled> {

let manifest = ManifestFile::from_dir(&this_dir, SWAY_GIT_TAG)?;

let plan = pkg::BuildPlan::load_from_manifest(&manifest, locked, offline, SWAY_GIT_TAG)?;

// If any cli parameter is passed by the user it overrides the selected build profile.
let mut config = &pkg::BuildConfig {
print_ir,
Expand All @@ -80,52 +82,6 @@ pub fn build(command: BuildCommand) -> Result<pkg::Compiled> {
});
}

let lock_path = lock_path(manifest.dir());

let plan_result = pkg::BuildPlan::from_lock_file(&lock_path, SWAY_GIT_TAG);

// Retrieve the old lock file state so we can produce a diff.
let old_lock = plan_result
.as_ref()
.ok()
.map(|plan| Lock::from_graph(plan.graph()))
.unwrap_or_default();

// Check if there are any errors coming from the BuildPlan generation from the lock file
// If there are errors we will need to create the BuildPlan from scratch, i.e fetch & pin everything
let mut new_lock_cause = None;
let mut plan = plan_result.or_else(|e| -> Result<pkg::BuildPlan> {
if locked {
bail!(
"The lock file {} needs to be updated but --locked was passed to prevent this.",
lock_path.to_string_lossy()
);
}
new_lock_cause = if e.to_string().contains("No such file or directory") {
Some(anyhow!("lock file did not exist"))
} else {
Some(e)
};
let plan = pkg::BuildPlan::new(&manifest, SWAY_GIT_TAG, offline)?;
Ok(plan)
})?;

// If there are no issues with the BuildPlan generated from the lock file
// Check and apply the diff.
if new_lock_cause.is_none() {
let diff = plan.validate(&manifest, SWAY_GIT_TAG)?;
if !diff.added.is_empty() || !diff.removed.is_empty() {
new_lock_cause = Some(anyhow!("lock file did not match manifest `diff`"));
plan = plan.apply_pkg_diff(diff, SWAY_GIT_TAG, offline)?;
}
}

if let Some(cause) = new_lock_cause {
info!(" Creating a new `Forc.lock` file. (Cause: {})", cause);
create_new_lock(&plan, &old_lock, &manifest, &lock_path)?;
info!(" Created new lock file at {}", lock_path.display());
}

// Build it!
let (compiled, source_map) = pkg::build(&plan, config, SWAY_GIT_TAG)?;

Expand Down Expand Up @@ -187,18 +143,3 @@ pub fn build(command: BuildCommand) -> Result<pkg::Compiled> {

Ok(compiled)
}

fn create_new_lock(
plan: &pkg::BuildPlan,
old_lock: &Lock,
manifest: &ManifestFile,
lock_path: &Path,
) -> Result<()> {
let lock = Lock::from_graph(plan.graph());
let diff = lock.diff(old_lock);
lock::print_diff(&manifest.project.name, &diff);
let string = toml::ser::to_string_pretty(&lock)
.map_err(|e| anyhow!("failed to serialize lock file: {}", e))?;
fs::write(&lock_path, &string).map_err(|e| anyhow!("failed to write lock file: {}", e))?;
Ok(())
}
23 changes: 23 additions & 0 deletions forc/src/ops/forc_check.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
use crate::{cli::CheckCommand, utils::SWAY_GIT_TAG};
use anyhow::Result;
use forc_pkg::{self as pkg, ManifestFile};
use std::path::PathBuf;

pub fn check(command: CheckCommand) -> Result<sway_core::CompileAstResult> {
let CheckCommand {
path,
offline_mode: offline,
silent_mode,
locked,
} = command;

let this_dir = if let Some(ref path) = path {
PathBuf::from(path)
} else {
std::env::current_dir()?
};
let manifest = ManifestFile::from_dir(&this_dir, SWAY_GIT_TAG)?;
let plan = pkg::BuildPlan::load_from_manifest(&manifest, locked, offline, SWAY_GIT_TAG)?;

pkg::check(&plan, silent_mode, SWAY_GIT_TAG)
}
1 change: 1 addition & 0 deletions forc/src/ops/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub mod forc_abi_json;
pub mod forc_build;
pub mod forc_check;
pub mod forc_clean;
pub mod forc_deploy;
pub mod forc_init;
Expand Down
Loading