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

provide crate fs functions and fix a remove_dir_all bug #129

Merged
merged 3 commits into from
Dec 26, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
8 changes: 3 additions & 5 deletions twoliter/src/cmd/debug.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use crate::common::fs;
use crate::tools::install_tools;
use anyhow::{Context, Result};
use anyhow::Result;
use clap::Parser;
use std::env;
use std::path::PathBuf;
use tokio::fs;
use uuid::Uuid;

#[derive(Debug, Clone, Parser)]
Expand Down Expand Up @@ -49,9 +49,7 @@ impl CheckToolArgs {
.install_dir
.clone()
.unwrap_or_else(|| env::temp_dir().join(unique_name()));
fs::create_dir_all(&dir)
.await
.context(format!("Unable to create directory '{}'", dir.display()))?;
fs::create_dir_all(&dir).await?;
install_tools(&dir).await?;
println!("{}", dir.display());
Ok(())
Expand Down
4 changes: 2 additions & 2 deletions twoliter/src/cmd/make.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use crate::cargo_make::CargoMake;
use crate::common::fs;
use crate::project::{self};
use crate::tools::install_tools;
use anyhow::Result;
use clap::Parser;
use std::path::PathBuf;
use tokio::fs;

/// Run a cargo make command in Twoliter's build environment. Known Makefile.toml environment
/// variables will be passed-through to the cargo make invocation.
Expand Down Expand Up @@ -35,7 +35,7 @@ impl Make {
pub(super) async fn run(&self) -> Result<()> {
let project = project::load_or_find_project(self.project_path.clone()).await?;
let toolsdir = project.project_dir().join("build/tools");
fs::remove_dir_all(&toolsdir).await?;
let _ = fs::remove_dir_all(&toolsdir).await;
install_tools(&toolsdir).await?;
let makefile_path = toolsdir.join("Makefile.toml");
CargoMake::new(&project, &self.arch)?
Expand Down
111 changes: 111 additions & 0 deletions twoliter/src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,114 @@ pub(crate) async fn exec(cmd: &mut Command, quiet: bool) -> Result<Option<String
None
})
}

/// These are thin wrappers for `tokio::fs` functions which provide more useful error messages. For
/// example, tokio will provide an unhelpful `std` error message such as `Error: No such file or
/// directory (os error 2)` and we want to augment this with the filepath that was not found.
///
/// We allow `dead_code` here because it is inconvenient to delete and replace these simple helper
/// functions as we change calling code. The compiler will strip dead code in release builds anyway,
/// so there is no real issue having these unused here.
#[allow(dead_code)]
pub(crate) mod fs {
use anyhow::{Context, Result};
use std::fs::Metadata;
use std::path::{Path, PathBuf};
use tokio::fs;

pub(crate) async fn canonicalize(path: impl AsRef<Path>) -> Result<PathBuf> {
fs::canonicalize(path.as_ref()).await.context(format!(
"Unable to canonicalize '{}'",
path.as_ref().display()
))
}

pub(crate) async fn copy<P1, P2>(from: P1, to: P2) -> Result<u64>
where
P1: AsRef<Path>,
P2: AsRef<Path>,
{
let from = from.as_ref();
let to = to.as_ref();
fs::copy(from, to).await.context(format!(
"Unable to copy '{}' to '{}'",
from.display(),
to.display()
))
}

pub(crate) async fn create_dir(path: impl AsRef<Path>) -> Result<()> {
fs::create_dir(path.as_ref()).await.context(format!(
"Unable to create directory '{}'",
path.as_ref().display()
))
}

pub(crate) async fn create_dir_all(path: impl AsRef<Path>) -> Result<()> {
fs::create_dir_all(path.as_ref()).await.context(format!(
"Unable to create directory '{}'",
path.as_ref().display()
))
}

pub(crate) async fn metadata(path: impl AsRef<Path>) -> Result<Metadata> {
fs::metadata(path.as_ref()).await.context(format!(
"Unable to read metadata for '{}'",
path.as_ref().display()
))
}

pub(crate) async fn read(path: impl AsRef<Path>) -> Result<Vec<u8>> {
fs::read(path.as_ref())
.await
.context(format!("Unable to read from '{}'", path.as_ref().display()))
}

pub(crate) async fn read_to_string(path: impl AsRef<Path>) -> Result<String> {
fs::read_to_string(path.as_ref()).await.context(format!(
"Unable to read the following file as a string '{}'",
path.as_ref().display()
))
}

pub(crate) async fn remove_dir(path: impl AsRef<Path>) -> Result<()> {
fs::remove_dir(path.as_ref()).await.context(format!(
"Unable to remove directory (remove_dir) '{}'",
path.as_ref().display()
))
}

pub(crate) async fn remove_dir_all(path: impl AsRef<Path>) -> Result<()> {
fs::remove_dir_all(path.as_ref()).await.context(format!(
"Unable to remove directory (remove_dir_all) '{}'",
path.as_ref().display()
))
}

pub(crate) async fn rename(from: impl AsRef<Path>, to: impl AsRef<Path>) -> Result<()> {
let from = from.as_ref();
let to = to.as_ref();
fs::rename(from, to).await.context(format!(
"Unable to rename '{}' to '{}'",
from.display(),
to.display()
))
}

pub(crate) async fn remove_file(path: impl AsRef<Path>) -> Result<()> {
fs::remove_file(path.as_ref()).await.context(format!(
"Unable to XXSOMETHINGXX '{}'",
path.as_ref().display()
))
}

pub(crate) async fn write<P, C>(path: P, contents: C) -> Result<()>
where
P: AsRef<Path>,
C: AsRef<[u8]>,
{
fs::write(path.as_ref(), contents)
.await
.context(format!("Unable to write to '{}'", path.as_ref().display()))
}
}
28 changes: 6 additions & 22 deletions twoliter/src/project.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::common::fs;
use crate::docker::ImageArchUri;
use crate::schema_version::SchemaVersion;
use anyhow::{ensure, Context, Result};
Expand All @@ -7,7 +8,6 @@ use non_empty_string::NonEmptyString;
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha512};
use std::path::{Path, PathBuf};
use tokio::fs;
use toml::Table;

/// Common functionality in commands, if the user gave a path to the `Twoliter.toml` file,
Expand Down Expand Up @@ -241,9 +241,9 @@ impl UnvalidatedProject {
#[cfg(test)]
mod test {
use super::*;
use crate::common::fs;
use crate::test::data_dir;
use tempfile::TempDir;
use tokio::fs;

/// Ensure that `Twoliter.toml` can be deserialized.
#[tokio::test]
Expand Down Expand Up @@ -341,18 +341,10 @@ mod test {
let release_toml_to = p.join("Release.toml");
fs::copy(&twoliter_toml_from, &twoliter_toml_to)
.await
.expect(&format!(
"Unable to copy {} to {}",
twoliter_toml_from.display(),
twoliter_toml_to.display()
));
.unwrap();
fs::copy(&release_toml_from, &release_toml_to)
.await
.expect(&format!(
"Unable to copy {} to {}",
release_toml_from.display(),
release_toml_to.display()
));
.unwrap();
let result = Project::find_and_load(&p).await;
assert!(
result.is_err(),
Expand All @@ -372,18 +364,10 @@ mod test {
let release_toml_to = p.join("Release.toml");
fs::copy(&twoliter_toml_from, &twoliter_toml_to)
.await
.expect(&format!(
"Unable to copy {} to {}",
twoliter_toml_from.display(),
twoliter_toml_to.display()
));
.unwrap();
fs::copy(&release_toml_from, &release_toml_to)
.await
.expect(&format!(
"Unable to copy {} to {}",
release_toml_from.display(),
release_toml_to.display()
));
.unwrap();

// The project should load because Release.toml and Twoliter.toml versions match.
Project::find_and_load(&p).await.unwrap();
Expand Down
18 changes: 13 additions & 5 deletions twoliter/src/tools.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
use crate::common::fs;
use anyhow::{Context, Result};
use filetime::{set_file_handle_times, set_file_mtime, FileTime};
use flate2::read::ZlibDecoder;
use log::debug;
use std::path::Path;
use tar::Archive;
use tokio::fs;
use tokio::fs::OpenOptions;
use tokio::io::AsyncWriteExt;
use tokio::runtime::Handle;

const TAR_GZ_DATA: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/tools.tar.gz"));
const BOTTLEROCKET_VARIANT: &[u8] =
Expand Down Expand Up @@ -49,7 +51,7 @@ pub(crate) async fn install_tools(tools_dir: impl AsRef<Path>) -> Result<()> {

async fn write_bin(name: &str, data: &[u8], dir: impl AsRef<Path>, mtime: FileTime) -> Result<()> {
let path = dir.as_ref().join(name);
let mut f = fs::OpenOptions::new()
let mut f = OpenOptions::new()
.create(true)
.read(false)
.write(true)
Expand All @@ -65,9 +67,15 @@ async fn write_bin(name: &str, data: &[u8], dir: impl AsRef<Path>, mtime: FileTi
.context(format!("Unable to finalize '{}'", path.display()))?;

let f = f.into_std().await;
set_file_handle_times(&f, None, Some(mtime))
.context(format!("Unable to set mtime for '{}'", path.display()))?;
Ok(())
let rt = Handle::current();
rt.spawn_blocking(move || {
set_file_handle_times(&f, None, Some(mtime))
.context(format!("Unable to set mtime for '{}'", path.display()))
})
.await
.context(format!(
"Unable to run and join async task for reading handle time"
))?
}

async fn unpack_tarball(tools_dir: impl AsRef<Path>) -> Result<()> {
Expand Down