Skip to content

Commit

Permalink
Merge pull request #8 from ink0rr/dev
Browse files Browse the repository at this point in the history
Add `--cached` flag to run and watch command
  • Loading branch information
ink0rr authored Nov 5, 2023
2 parents 610bf61 + ed1d2de commit 758e332
Show file tree
Hide file tree
Showing 7 changed files with 249 additions and 38 deletions.
68 changes: 67 additions & 1 deletion Cargo.lock

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

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name = "rgl"

[package]
name = "rgl"
version = "0.1.6"
version = "0.1.7"
edition = "2021"

[dependencies]
Expand All @@ -16,6 +16,7 @@ md5 = "0.7.0"
notify = "6.0.1"
notify-debouncer-mini = "0.3.0"
paris = { version = "1.5.15", features = ["macros", "no_logger"] }
rayon = "1.8.0"
semver = "1.0.18"
serde = { version = "1.0.183", features = ["derive"] }
serde_json = "1.0.105"
Expand Down
32 changes: 23 additions & 9 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ fn run_command() -> Result<()> {
.arg(
Arg::new("debug")
.long("debug")
.alias("Print debug messages")
.help("Print debug messages")
.global(true)
.action(ArgAction::SetTrue),
)
Expand All @@ -35,7 +35,7 @@ fn run_command() -> Result<()> {
.subcommand(
Command::new("install")
.alias("i")
.about("Downloads and installs Regolith filters from the internet, and adds them to the \"filterDefinitions\" list of the project's \"config.json\" file.")
.about("Downloads and installs Regolith filters from the internet, and adds them to the \"filterDefinitions\" list of the project's \"config.json\" file")
.arg(Arg::new("filters").num_args(0..).action(ArgAction::Set))
.arg(
Arg::new("force")
Expand All @@ -47,12 +47,26 @@ fn run_command() -> Result<()> {
.subcommand(
Command::new("run")
.about("Runs Regolith with specified profile")
.arg(Arg::new("profile").action(ArgAction::Set)),
.arg(Arg::new("profile").action(ArgAction::Set))
.arg(
Arg::new("cached")
.short('c')
.long("cached")
.help("Use previous run output as cache")
.action(ArgAction::SetTrue),
),
)
.subcommand(
Command::new("watch")
.about("Watches project files and automatically runs Regolith when they change")
.arg(Arg::new("profile").action(ArgAction::Set)),
.arg(Arg::new("profile").action(ArgAction::Set))
.arg(
Arg::new("cached")
.short('c')
.long("cached")
.help("Use previous run output as cache")
.action(ArgAction::SetTrue),
),
)
.get_matches();

Expand Down Expand Up @@ -84,17 +98,17 @@ fn run_command() -> Result<()> {
Some(profile) => profile,
None => "default",
};
measure_time!("Run", {
rgl::run_or_watch(profile, false)
.context(format!("Error running <b>{profile}</> profile"))?;
});
let cached = matches.get_flag("cached");
rgl::run_or_watch(profile, false, cached)
.context(format!("Error running <b>{profile}</> profile"))?;
}
Some(("watch", matches)) => {
let profile = match matches.get_one::<String>("profile") {
Some(profile) => profile,
None => "default",
};
rgl::run_or_watch(profile, true)
let cached = matches.get_flag("cached");
rgl::run_or_watch(profile, true, cached)
.context(format!("Error running <b>{profile}</> profile"))?;
}
_ => unreachable!(),
Expand Down
116 changes: 116 additions & 0 deletions src/rgl/cache.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
use super::{copy_dir, move_dir};
use anyhow::{Context, Result};
use rayon::prelude::*;
use std::{
fs,
io::{BufRead, BufReader},
path::Path,
};

pub fn copy_dir_cached(
from: impl AsRef<Path>,
to: impl AsRef<Path>,
cache: impl AsRef<Path>,
) -> Result<()> {
let from = from.as_ref();
let to = to.as_ref();
let cache = cache.as_ref();
if cache.is_dir() {
move_dir(cache, to)?;
copy_cached(from, to).context(format!(
"Failed to copy directory\n\
<yellow> >></> From: {}\n\
<yellow> >></> To: {}",
from.display(),
to.display(),
))?;
cleanup(from, to)
} else {
copy_dir(from, to)
}
}

/// Copy files from the source directory to the target directory, but only if they are different.
fn copy_cached(from: &Path, to: &Path) -> Result<()> {
fs::create_dir_all(to)?;
fs::read_dir(from)?
.par_bridge()
.map(|entry| -> Result<()> {
let entry = entry?;
let from = entry.path();
let to = to.join(entry.file_name());
if diff(&from, &to)? {
return Ok(());
}
let to_dir = to.is_dir();
if from.is_dir() {
if !to_dir {
fs::remove_file(&to)?;
}
return copy_cached(&from, &to);
}
if to_dir {
fs::remove_dir_all(&to)?;
}
fs::copy(&from, &to)?;
Ok(())
})
.collect()
}

/// Remove files that are not present in the source directory.
fn cleanup(from: &Path, to: &Path) -> Result<()> {
fs::read_dir(to)?
.par_bridge()
.map(|entry| -> Result<()> {
let entry = entry?;
let from = from.join(entry.file_name());
let to = entry.path();
let is_dir = to.is_dir();
if !from.exists() {
let remove: std::io::Result<()> = match is_dir {
true => fs::remove_dir_all(&to),
false => fs::remove_file(&to),
};
return remove.context(format!(
"Failed to remove file/directory\n\
<yellow> >></> Path: {}",
to.display(),
));
}
if is_dir {
cleanup(&from, &to)?;
}
Ok(())
})
.collect()
}

/// Compare two file contents. Return true if they are identical.
fn diff(a: impl AsRef<Path>, b: impl AsRef<Path>) -> Result<bool> {
let a = fs::File::open(a);
let b = fs::File::open(b);
if a.is_err() || b.is_err() {
return Ok(false);
}
let mut a_reader = BufReader::new(a.unwrap());
let mut b_reader = BufReader::new(b.unwrap());
if a_reader.capacity() != b_reader.capacity() {
return Ok(false);
}
loop {
let len = {
let a_buf = a_reader.fill_buf()?;
let b_buf = b_reader.fill_buf()?;
if a_buf.is_empty() && b_buf.is_empty() {
return Ok(true);
}
if a_buf != b_buf {
return Ok(false);
}
a_buf.len()
};
a_reader.consume(len);
b_reader.consume(len);
}
}
30 changes: 18 additions & 12 deletions src/rgl/core.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use super::{copy_dir, empty_dir, move_dir, rimraf, symlink, Config, RunContext};
use crate::{info, measure_time, warn};
use super::{copy_dir, copy_dir_cached, empty_dir, move_dir, rimraf, symlink, Config, RunContext};
use crate::{debug, info, measure_time, warn};
use anyhow::{Context, Result};
use std::{fs, io};
use std::{fs, io, time};

pub fn run_or_watch(profile_name: &str, watch: bool) -> Result<()> {
pub fn run_or_watch(profile_name: &str, watch: bool, cached: bool) -> Result<()> {
let start_time = time::Instant::now();
let config = Config::load()?;

let context = RunContext::new(config, profile_name);
Expand All @@ -13,10 +14,17 @@ pub fn run_or_watch(profile_name: &str, watch: bool) -> Result<()> {
let temp_rp = temp.join("RP");
let (bp, rp) = profile.get_export_paths(&context.name)?;

measure_time!("Setup", {
measure_time!("Setup temp dir", {
empty_dir(&temp)?;
copy_dir(&context.behavior_pack, &temp_bp)?;
copy_dir(&context.resource_pack, &temp_rp)?;
if cached {
copy_dir_cached(&context.behavior_pack, &temp_bp, &bp)?;
copy_dir_cached(&context.resource_pack, &temp_rp, &rp)?;
} else {
rimraf(&bp)?;
rimraf(&rp)?;
copy_dir(&context.behavior_pack, &temp_bp)?;
copy_dir(&context.resource_pack, &temp_rp)?;
}
if let Err(e) = symlink(&context.data_path, temp.join("data")) {
match e.downcast_ref::<io::Error>().map(|e| e.kind()) {
Some(io::ErrorKind::NotFound) => {
Expand All @@ -42,22 +50,20 @@ pub fn run_or_watch(profile_name: &str, watch: bool) -> Result<()> {
rp.display()
);
let export: Result<()> = {
rimraf(&bp)?;
rimraf(&rp)?;
move_dir(temp_bp, bp)?;
move_dir(temp_rp, rp)?;
Ok(())
move_dir(temp_rp, rp)
};
export.context("Failed to export project")?;
});

info!("Successfully ran the <b>{profile_name}</> profile");
debug!("Total time: {}ms", start_time.elapsed().as_millis());
if watch {
info!("Watching for changes...");
info!("Press Ctrl+C to stop watching");
context.watch_project_files()?;
warn!("Changes detected, restarting...");
return run_or_watch(profile_name, watch);
return run_or_watch(profile_name, watch, cached);
}
Ok(())
}
Loading

0 comments on commit 758e332

Please sign in to comment.