Skip to content

Commit

Permalink
Clean implementation (#2882)
Browse files Browse the repository at this point in the history
Resolves #2723
adding clean.rs and modifying lib.rs

---------

Co-authored-by: ksew1 <[email protected]>
Co-authored-by: Franciszek Job <[email protected]>
Co-authored-by: kkawula <[email protected]>
Co-authored-by: Artur Michałek <[email protected]>
Co-authored-by: ddoktorski <[email protected]>
Co-authored-by: CodeBestia <[email protected]>
Co-authored-by: Thibrac <[email protected]>
  • Loading branch information
8 people authored Feb 4, 2025
1 parent d078611 commit 21afb8b
Show file tree
Hide file tree
Showing 9 changed files with 383 additions and 0 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

#### Added

- `snforge clean` command - used to manage and remove files generated by snforge. It supports cleaning the following components: coverage, profile, cache, trace, all
- `snforge new` now adds the `snfoundry_trace`, `coverage`, and `profile` directories to `.gitignore`.

#### Deprecated

- `snforge clean-cache` command

## [0.37.0] - 2025-02-03

### Forge
Expand Down
64 changes: 64 additions & 0 deletions crates/forge/src/clean.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
use crate::{CleanArgs, CleanComponent};
use anyhow::{ensure, Context, Result};
use camino::Utf8PathBuf;
use scarb_api::{metadata::MetadataCommandExt, ScarbCommand};
use std::fs;

const COVERAGE_DIR: &str = "coverage";
const PROFILE_DIR: &str = "profile";
const CACHE_DIR: &str = ".snfoundry_cache";
const TRACE_DIR: &str = "snfoundry_trace";

pub fn clean(args: CleanArgs) -> Result<()> {
let components = if args.clean_components.contains(&CleanComponent::All) {
ensure!(
args.clean_components.len() == 1,
"The 'all' component cannot be combined with other components"
);
vec![
CleanComponent::Trace,
CleanComponent::Profile,
CleanComponent::Cache,
CleanComponent::Coverage,
]
} else {
args.clean_components
};

let scarb_metadata = ScarbCommand::metadata().inherit_stderr().no_deps().run()?;
let workspace_root = scarb_metadata.workspace.root;

let packages_root: Vec<Utf8PathBuf> = scarb_metadata
.packages
.into_iter()
.map(|package_metadata| package_metadata.root)
.collect();

for component in &components {
match component {
CleanComponent::Coverage => clean_dirs(&packages_root, COVERAGE_DIR)?,
CleanComponent::Profile => clean_dirs(&packages_root, PROFILE_DIR)?,
CleanComponent::Cache => clean_dir(&workspace_root, CACHE_DIR)?,
CleanComponent::Trace => clean_dir(&workspace_root, TRACE_DIR)?,
CleanComponent::All => unreachable!("All component should have been handled earlier"),
}
}

Ok(())
}

fn clean_dirs(root_dirs: &[Utf8PathBuf], dir_name: &str) -> Result<()> {
for root_dir in root_dirs {
clean_dir(root_dir, dir_name)?;
}
Ok(())
}
fn clean_dir(dir: &Utf8PathBuf, dir_name: &str) -> Result<()> {
let dir = dir.join(dir_name);
if dir.exists() {
fs::remove_dir_all(&dir).with_context(|| format!("Failed to remove directory: {dir}"))?;
println!("Removed directory: {dir}");
}

Ok(())
}
35 changes: 35 additions & 0 deletions crates/forge/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::compatibility_check::{create_version_parser, Requirement, RequirementsChecker};
use anyhow::anyhow;
use anyhow::Result;
use camino::Utf8PathBuf;
use clap::{Parser, Subcommand, ValueEnum};
Expand All @@ -7,6 +8,7 @@ use run_tests::workspace::run_for_workspace;
use scarb_api::{metadata::MetadataCommandExt, ScarbCommand};
use scarb_ui::args::{FeaturesSpec, PackagesFilter};
use semver::Version;
use shared::print::print_as_warning;
use std::cell::RefCell;
use std::ffi::OsString;
use std::process::Command;
Expand All @@ -15,6 +17,7 @@ use tokio::runtime::Builder;
use universal_sierra_compiler_api::UniversalSierraCompilerCommand;

pub mod block_number_map;
mod clean;
mod combine_configs;
mod compatibility_check;
mod init;
Expand Down Expand Up @@ -86,12 +89,37 @@ enum ForgeSubcommand {
#[command(flatten)]
args: NewArgs,
},
/// Clean `snforge` generated directories
Clean {
#[command(flatten)]
args: CleanArgs,
},
/// Clean Forge cache directory
CleanCache {},
/// Check if all `snforge` requirements are installed
CheckRequirements,
}

#[derive(Parser, Debug)]
pub struct CleanArgs {
#[arg(num_args = 1.., required = true)]
pub clean_components: Vec<CleanComponent>,
}

#[derive(ValueEnum, Debug, Clone, PartialEq, Eq)]
pub enum CleanComponent {
/// Clean the `coverage` directory
Coverage,
/// Clean the `profile` directory
Profile,
/// Clean the `.snfoundry_cache` directory
Cache,
/// Clean the `snfoundry_trace` directory
Trace,
/// Clean all generated directories
All,
}

#[derive(ValueEnum, Debug, Clone)]
enum ColorOption {
Auto,
Expand Down Expand Up @@ -204,7 +232,14 @@ pub fn main_execution() -> Result<ExitStatus> {
new::new(args)?;
Ok(ExitStatus::Success)
}
ForgeSubcommand::Clean { args } => {
clean::clean(args)?;
Ok(ExitStatus::Success)
}
ForgeSubcommand::CleanCache {} => {
print_as_warning(&anyhow!(
"`snforge clean-cache` is deprecated and will be removed in the future. Use `snforge clean cache` instead"
));
let scarb_metadata = ScarbCommand::metadata().inherit_stderr().run()?;
let cache_dir = scarb_metadata.workspace.root.join(CACHE_DIR);

Expand Down
245 changes: 245 additions & 0 deletions crates/forge/tests/e2e/clean.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
use super::common::runner::{runner, setup_package, test_runner};
use assert_fs::TempDir;
use camino::Utf8PathBuf;
use scarb_api::metadata::MetadataCommandExt;
use scarb_api::ScarbCommand;
use shared::test_utils::output_assert::assert_stdout_contains;
use std::path::Path;

const COVERAGE_DIR: &str = "coverage";
const PROFILE_DIR: &str = "profile";
const CACHE_DIR: &str = ".snfoundry_cache";
const TRACE_DIR: &str = "snfoundry_trace";

#[allow(clippy::struct_excessive_bools)]
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
struct CleanComponentsState {
coverage: bool,
profile: bool,
cache: bool,
trace: bool,
}

#[test]
#[cfg_attr(not(feature = "scarb_2_8_3"), ignore)]
fn test_clean_coverage() {
let temp_dir = setup_package("coverage_project");

let clean_components_state = CleanComponentsState {
coverage: true,
profile: false,
cache: true,
trace: true,
};

generate_clean_components(clean_components_state, &temp_dir);

runner(&temp_dir)
.arg("clean")
.arg("coverage")
.arg("trace")
.assert()
.success();

let expected_state = CleanComponentsState {
coverage: false,
profile: false,
cache: true,
trace: false,
};

assert_eq!(
check_clean_components_state(temp_dir.path()),
expected_state
);
}

#[test]
#[cfg(not(target_os = "windows"))]
fn test_clean_profile() {
let temp_dir = setup_package("coverage_project");

let clean_components_state = CleanComponentsState {
coverage: false,
profile: true,
cache: true,
trace: true,
};

generate_clean_components(clean_components_state, &temp_dir);

runner(&temp_dir)
.arg("clean")
.arg("profile")
.assert()
.success();

let expected_state = CleanComponentsState {
coverage: false,
profile: false,
cache: true,
trace: true,
};

assert_eq!(
check_clean_components_state(temp_dir.path()),
expected_state
);
}

#[test]
fn test_clean_cache() {
let temp_dir = setup_package("coverage_project");

let clean_components_state = CleanComponentsState {
coverage: false,
profile: false,
cache: true,
trace: false,
};

generate_clean_components(clean_components_state, &temp_dir);

runner(&temp_dir)
.arg("clean")
.arg("cache")
.assert()
.success();

let expected_state = CleanComponentsState {
coverage: false,
profile: false,
cache: false,
trace: false,
};

assert_eq!(
check_clean_components_state(temp_dir.path()),
expected_state
);
}

#[test]
#[cfg_attr(not(feature = "scarb_2_8_3"), ignore)]
fn test_clean_all() {
let temp_dir = setup_package("coverage_project");

let clean_components_state = CleanComponentsState {
coverage: true,
cache: true,
trace: true,
profile: true,
};

generate_clean_components(clean_components_state, &temp_dir);

runner(&temp_dir).arg("clean").arg("all").assert().success();

assert_eq!(
check_clean_components_state(temp_dir.path()),
clean_components_state
);
}

#[test]
fn test_clean_all_and_component() {
let temp_dir = setup_package("coverage_project");

let clean_components_state = CleanComponentsState {
coverage: false,
cache: true,
trace: true,
profile: false,
};
generate_clean_components(clean_components_state, &temp_dir);

// This command should fail because 'all' cannot be combined with other components
let output = runner(&temp_dir)
.arg("clean")
.arg("all")
.arg("cache")
.assert()
.failure();

assert_stdout_contains(
output,
"[ERROR] The 'all' component cannot be combined with other components",
);
}

fn generate_clean_components(state: CleanComponentsState, temp_dir: &TempDir) {
let args = match state {
CleanComponentsState {
coverage: true,
trace: true,
cache: true,
profile: false,
} => {
vec!["--coverage"]
}
CleanComponentsState {
profile: true,
trace: true,
cache: true,
coverage: false,
} => {
vec!["--build-profile"]
}
CleanComponentsState {
trace: true,
cache: true,
profile: false,
coverage: false,
} => {
vec!["--save-trace-data"]
}
CleanComponentsState {
coverage: false,
profile: false,
trace: false,
cache: true,
} => {
vec![]
}
state => {
panic!("Invalid state: {state:?}");
}
};

test_runner(temp_dir).args(&args).assert().success();

assert_eq!(check_clean_components_state(temp_dir.path()), state);
}

fn check_clean_components_state(path: &Path) -> CleanComponentsState {
let scarb_metadata = ScarbCommand::metadata()
.inherit_stderr()
.current_dir(path)
.no_deps()
.run()
.unwrap();

let workspace_root = scarb_metadata.workspace.root;

let packages_root: Vec<_> = scarb_metadata
.packages
.into_iter()
.map(|package_metadata| package_metadata.root)
.collect();

CleanComponentsState {
coverage: dirs_exist(&packages_root, COVERAGE_DIR),
profile: dirs_exist(&packages_root, PROFILE_DIR),
cache: dir_exists(&workspace_root, CACHE_DIR),
trace: dir_exists(&workspace_root, TRACE_DIR),
}
}

fn dirs_exist(root_dirs: &[Utf8PathBuf], dir_name: &str) -> bool {
root_dirs
.iter()
.all(|root_dir| dir_exists(root_dir, dir_name))
}
fn dir_exists(dir: &Utf8PathBuf, dir_name: &str) -> bool {
dir.join(dir_name).exists()
}
Loading

0 comments on commit 21afb8b

Please sign in to comment.