-
Notifications
You must be signed in to change notification settings - Fork 7
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 a --check
subcommand
#157
Comments
Thank you for the report @juntyr! |
Thanks for pointing me in the direction of |
That sounds like the right behavior actually (not doing any changes), but I don't have capacity to implement that any time soon. I suppose the way it would work for now would be failing fast if any of the paths specified in Would you be interested in contributing this change? |
I unfortunately also don't have the time at the moment, but I can share the code that I'm using (ignore my custom error types), which runs use std::{
fmt,
fs::{self, File},
hash::{BuildHasher, DefaultHasher, Hasher, RandomState},
io::{self, BufReader},
path::Path,
};
use core_error::LocationError;
use tempdir::TempDir;
use walkdir::{DirEntry, WalkDir};
pub type Result<T, E = LocationError<AnyError>> = std::result::Result<T, E>;
#[allow(clippy::too_many_lines)]
#[allow(clippy::missing_errors_doc)]
pub fn check_is_locked(wit: impl AsRef<Path>) -> Result<()> {
let wit = wit.as_ref();
let deps_file = fs::read_to_string(wit.join("deps.toml")).map_err(AnyError::new)?;
let deps_lock = fs::read_to_string(wit.join("deps.lock")).map_err(AnyError::new)?;
let deps = TempDir::new("deps").map_err(AnyError::new)?;
let lock = wit_deps::lock(
Some(&wit),
deps_file,
Some(deps_lock),
deps.path().join("deps"),
);
let lock = tokio::runtime::Builder::new_current_thread()
.enable_io()
.enable_time()
.build()
.map_err(AnyError::new)?
.block_on(lock)
.map_err(AnyError::from)?;
if lock.is_some() {
return Err(AnyError::msg("lock file has changed").into());
}
let old_wit = wit.join("deps");
let new_wit = deps.path().join("deps");
let mut old_deps = WalkDir::new(&old_wit)
.min_depth(1)
.follow_links(true)
.sort_by_file_name()
.into_iter();
let mut new_deps = WalkDir::new(&new_wit)
.min_depth(1)
.follow_links(true)
.sort_by_file_name()
.into_iter();
let (mut old_dep, mut new_dep) = (
old_deps.next().transpose().map_err(AnyError::new)?,
new_deps.next().transpose().map_err(AnyError::new)?,
);
loop {
// skip indirect dependency deps files, lock files, and directories,
// since indirect dependencies are flattened into the main dendencies
let skip = |dep: &Option<DirEntry>| match dep {
Some(dep) if dep.path().ends_with("deps") && dep.file_type().is_dir() => {
Some(Skip::Directory)
},
Some(dep)
if (dep.path().ends_with("deps.toml") && dep.file_type().is_file())
|| (dep.path().ends_with("deps.lock") && dep.file_type().is_file()) =>
{
Some(Skip::File)
},
_ => None,
};
if let Some(old_skip) = skip(&old_dep) {
if matches!(old_skip, Skip::Directory) {
old_deps.skip_current_dir();
}
old_dep = old_deps.next().transpose().map_err(AnyError::new)?;
continue;
}
if let Some(new_skip) = skip(&new_dep) {
if matches!(new_skip, Skip::Directory) {
new_deps.skip_current_dir();
}
new_dep = new_deps.next().transpose().map_err(AnyError::new)?;
continue;
}
// check that both have the same number of files
let (some_old_dep, some_new_dep) = match (old_dep, new_dep) {
(Some(old_dep), Some(new_dep)) => (old_dep, new_dep),
(None, None) => break,
(Some(extra), None) => {
return Err(AnyError::msg(format!(
"{} is extraneous in deps",
extra.path().display()
))
.into())
},
(None, Some(missing)) => {
return Err(AnyError::msg(format!(
"{} is missing from deps",
missing.path().display()
))
.into())
},
};
// strip the file path prefixes to make them comparable
let old_dep_path = some_old_dep
.path()
.strip_prefix(&old_wit)
.map_err(AnyError::new)?;
let new_dep_path = some_new_dep
.path()
.strip_prefix(&new_wit)
.map_err(AnyError::new)?;
// check that the next file path and type match
if old_dep_path != new_dep_path {
return Err(AnyError::msg(format!(
"file name mismatch between {} and {} in deps",
old_dep_path.display(),
new_dep_path.display(),
))
.into());
}
if some_old_dep.file_type() != some_new_dep.file_type() {
return Err(AnyError::msg(format!(
"file type mismatch for {}",
old_dep_path.display()
))
.into());
}
// we can only compare the binary contents of files
if !some_old_dep.file_type().is_file() {
old_dep = old_deps.next().transpose().map_err(AnyError::new)?;
new_dep = new_deps.next().transpose().map_err(AnyError::new)?;
continue;
}
let mut old_file = BufReader::new(File::open(some_old_dep.path()).map_err(AnyError::new)?);
let mut new_file = BufReader::new(File::open(some_new_dep.path()).map_err(AnyError::new)?);
let rng = RandomState::new();
let mut old_hasher = HashWriter {
hasher: rng.build_hasher(),
};
let mut new_hasher = HashWriter {
hasher: rng.build_hasher(),
};
// hash the file contents
io::copy(&mut old_file, &mut old_hasher).map_err(AnyError::new)?;
io::copy(&mut new_file, &mut new_hasher).map_err(AnyError::new)?;
let (old_hash, new_hash) = (old_hasher.hasher.finish(), new_hasher.hasher.finish());
// check that the file content hashes match
if old_hash != new_hash {
return Err(AnyError::msg(format!(
"file hash mismatch for {}",
old_dep_path.display()
))
.into());
}
old_dep = old_deps.next().transpose().map_err(AnyError::new)?;
new_dep = new_deps.next().transpose().map_err(AnyError::new)?;
}
deps.close().map_err(AnyError::new)?;
Ok(())
}
#[derive(Debug, thiserror::Error)]
#[error(transparent)]
pub struct AnyError(#[from] anyhow::Error);
impl AnyError {
pub fn new<E: 'static + std::error::Error + Send + Sync>(error: E) -> Self {
Self(anyhow::Error::new(error))
}
pub fn msg<M: 'static + fmt::Display + fmt::Debug + Send + Sync>(message: M) -> Self {
Self(anyhow::Error::msg(message))
}
}
enum Skip {
File,
Directory,
}
struct HashWriter {
hasher: DefaultHasher,
}
impl io::Write for HashWriter {
fn write(&mut self, bytes: &[u8]) -> Result<usize, io::Error> {
self.hasher.write(bytes);
Ok(bytes.len())
}
fn flush(&mut self) -> Result<(), io::Error> {
Ok(())
}
} |
First of all, thank you for creating this useful tool!
I've been experimenting with wit-deps for a while. At the moment, using it as a tightly integrated dependency manager for my wit deps from within my Rust deps doesn't quite work yet. Therefore, I'm trying to switch to just using wit-deps in CI for now to assert that my deps.lock file is accurate and my deps folder is up-to-date. At the moment, it seems that checking the deps folder requires running
wit-deps update
, which fully recreates the folder. In my usecase, this wont't work, unfortunately.Would it be possible to add a
wit-deps check
subcommand? It should work almost identically towit-deps update
, but instead of overriding folders and files, it would check that their contents are the same. Crucially, it should follow symlinks.Thanks for your help and feedback :)
The text was updated successfully, but these errors were encountered: