Skip to content
Closed
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
4 changes: 2 additions & 2 deletions crates/pixi_cli/src/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,8 +192,8 @@ pub async fn execute(args: Args) -> miette::Result<()> {
let cache_dir = AbsPathBuf::new(pixi_config::get_cache_dir()?)
.expect("cache dir is not absolute")
.into_assume_dir();
let workspace_dir = AbsPathBuf::new(workspace.pixi_dir())
.expect("pixi dir is not absolute")
let workspace_dir = AbsPathBuf::new(workspace.workspace_cache_root_dir())
.expect("workspace cache root dir is not absolute")
.into_assume_dir();
let mut cache_dirs = CacheDirs::new(cache_dir).with_workspace(workspace_dir);
if let Some(build_dir) = args.build_dir {
Expand Down
26 changes: 16 additions & 10 deletions crates/pixi_cli/src/clean.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,22 +151,28 @@ pub async fn execute(args: Args) -> miette::Result<()> {
remove_folder_with_progress(workspace.solve_group_environments_dir(), false).await?;
remove_folder_with_progress(workspace.task_cache_folder(), false).await?;
remove_folder_with_progress(workspace.activation_env_cache_folder(), false).await?;
remove_folder_with_progress(
workspace.pixi_dir().join(consts::WORKSPACE_CACHE_DIR),
false,
)
.await?;
if !workspace
.workspace_cache_dir()
.starts_with(workspace.pixi_dir())
&& workspace.default_workspace_cache_dir().exists()
{
remove_folder_with_progress(workspace.default_workspace_cache_dir(), false).await?;
}
remove_folder_with_progress(workspace.workspace_cache_dir(), false).await?;
prune_workspace_registry().await?;
} else {
if args.activation_cache {
remove_folder_with_progress(workspace.activation_env_cache_folder(), true).await?;
}
if args.build {
remove_folder_with_progress(
workspace.pixi_dir().join(consts::WORKSPACE_CACHE_DIR),
true,
)
.await?;
if !workspace
.workspace_cache_dir()
.starts_with(workspace.pixi_dir())
&& workspace.default_workspace_cache_dir().exists()
{
remove_folder_with_progress(workspace.default_workspace_cache_dir(), false).await?;
}
remove_folder_with_progress(workspace.workspace_cache_dir(), true).await?;
eprintln!(
"{}When issues persist, you can remove all build related global cache with: {}",
console::style("Hint: ").blue(),
Expand Down
198 changes: 188 additions & 10 deletions crates/pixi_core/src/workspace/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ pub use workspace_mut::WorkspaceMut;
use xxhash_rust::xxh3::xxh3_64;

static CUSTOM_TARGET_DIR_WARN: OnceCell<()> = OnceCell::new();
static CUSTOM_WORKSPACE_CACHE_DIR_WARN: OnceCell<()> = OnceCell::new();

/// The dependency types we support
#[derive(Debug, Copy, Clone)]
Expand Down Expand Up @@ -381,7 +382,11 @@ impl Workspace {
}

#[cfg(windows)]
write_warning_file(&default_envs_dir, &detached_environments_path);
write_warning_file(
&default_envs_dir,
&detached_environments_path,
consts::ENVIRONMENTS_DIR,
);
});

return detached_environments_path;
Expand Down Expand Up @@ -411,6 +416,72 @@ impl Workspace {
self.default_solve_group_environments_dir()
}

/// Returns the default workspace build cache directory, without
/// interacting with config.
pub fn default_workspace_cache_dir(&self) -> PathBuf {
self.pixi_dir().join(consts::WORKSPACE_CACHE_DIR)
}

/// Returns the workspace build cache directory.
///
/// When `detached-environments` is enabled this path is moved outside the
/// workspace and `.pixi/build` is symlinked to the detached location when
/// possible.
pub fn workspace_cache_dir(&self) -> PathBuf {
let default_workspace_cache_dir = self.default_workspace_cache_dir();

// Early out if detached-environments is not set.
if self.config().detached_environments().is_false() {
return default_workspace_cache_dir;
}

if let Some(detached_environments_path) = self.detached_environments_path() {
let detached_workspace_cache_dir =
detached_environments_path.join(consts::WORKSPACE_CACHE_DIR);

let _ = CUSTOM_WORKSPACE_CACHE_DIR_WARN.get_or_init(|| {
if !default_workspace_cache_dir.is_symlink() && default_workspace_cache_dir.exists()
{
tracing::warn!(
"Build cache found in '{}', this will be ignored and pixi-build artifacts will be stored in the 'detached-environments' directory: '{}'. It's advised to remove the {} folder from the default directory to avoid confusion{}.",
default_workspace_cache_dir.display(),
detached_workspace_cache_dir.display(),
format!("{}/{}", consts::PIXI_DIR, consts::WORKSPACE_CACHE_DIR),
if cfg!(windows) { "" } else { " as a symlink can be made, please re-run after removal." }
);
} else {
#[cfg(not(windows))]
create_symlink(&detached_workspace_cache_dir, &default_workspace_cache_dir);
}

#[cfg(windows)]
write_warning_file(
&default_workspace_cache_dir,
&detached_workspace_cache_dir,
consts::WORKSPACE_CACHE_DIR,
);
});

return detached_workspace_cache_dir;
}

tracing::debug!(
"Using default root directory: `{}` as workspace cache directory.",
default_workspace_cache_dir.display()
);

default_workspace_cache_dir
}

/// Returns the workspace cache root directory that should be passed to
/// `CacheDirs::with_workspace`.
pub fn workspace_cache_root_dir(&self) -> PathBuf {
self.workspace_cache_dir()
.parent()
.expect("workspace cache dir should have parent")
.to_path_buf()
}

/// Returns the path to the lock file of the project
/// [consts::PROJECT_LOCK_FILE]
pub fn lock_file_path(&self) -> PathBuf {
Expand Down Expand Up @@ -567,8 +638,8 @@ impl Workspace {
let cache_dir = AbsPathBuf::new(pixi_config::get_cache_dir()?)
.expect("cache dir is not absolute")
.into_assume_dir();
let workspace_dir = AbsPathBuf::new(self.pixi_dir())
.expect("pixi dir is not absolute")
let workspace_dir = AbsPathBuf::new(self.workspace_cache_root_dir())
.expect("workspace cache root dir is not absolute")
.into_assume_dir();
let cache_dirs = CacheDirs::new(cache_dir).with_workspace(workspace_dir);

Expand Down Expand Up @@ -891,8 +962,8 @@ fn create_symlink(target_dir: &Path, symlink_dir: &Path) {
/// Write a warning file to the default pixi directory to inform the user that
/// symlinks are not supported on this platform (Windows).
#[cfg(windows)]
fn write_warning_file(default_envs_dir: &PathBuf, envs_dir_name: &Path) {
let warning_file = default_envs_dir.join("README.txt");
fn write_warning_file(default_dir: &Path, detached_dir: &Path, default_relative_dir: &str) {
let warning_file = default_dir.join("README.txt");
if warning_file.exists() {
tracing::debug!(
"Symlink warning file already exists at '{}', skipping writing warning file.",
Expand All @@ -901,16 +972,17 @@ fn write_warning_file(default_envs_dir: &PathBuf, envs_dir_name: &Path) {
return;
}
let warning_message = format!(
"Environments are installed in a custom detached-environments directory: {}.\n\
Symlinks are not supported on this platform so environments will not be reachable from the default ('.pixi/envs') directory.",
envs_dir_name.display()
"Data is stored in a custom detached-environments directory: {}.\n\
Symlinks are not supported on this platform so data will not be reachable from the default ('.pixi/{}') directory.",
detached_dir.display(),
default_relative_dir
);

// Create directory if it doesn't exist
if let Err(e) = fs_err::create_dir_all(default_envs_dir) {
if let Err(e) = fs_err::create_dir_all(default_dir) {
tracing::error!(
"Failed to create directory '{}': {}",
default_envs_dir.display(),
default_dir.display(),
e
);
return;
Expand Down Expand Up @@ -1043,6 +1115,112 @@ mod tests {
assert_eq!(workspace.display_name(), "workspace");
}

#[test]
fn test_workspace_cache_dir_default_location() {
let temp_dir = tempfile::tempdir().unwrap();
let workspace = Workspace::from_str(
&temp_dir.path().join(consts::WORKSPACE_MANIFEST),
PROJECT_BOILERPLATE,
)
.unwrap();

assert_eq!(
workspace.workspace_cache_dir(),
workspace.pixi_dir().join(consts::WORKSPACE_CACHE_DIR)
);
assert_eq!(workspace.workspace_cache_root_dir(), workspace.pixi_dir());
}

#[test]
fn test_workspace_cache_dir_detached_location() {
let temp_dir = tempfile::tempdir().unwrap();
let detached_root = temp_dir.path().join("detached");

let workspace = Workspace::from_str(
&temp_dir.path().join(consts::WORKSPACE_MANIFEST),
PROJECT_BOILERPLATE,
)
.unwrap()
.with_cli_config(Config {
detached_environments: Some(pixi_config::DetachedEnvironments::Path(
detached_root.clone(),
)),
..Default::default()
});

let expected = workspace
.detached_environments_path()
.expect("detached environments path should be set")
.join(consts::WORKSPACE_CACHE_DIR);

assert_eq!(workspace.workspace_cache_dir(), expected);
assert_eq!(
workspace.workspace_cache_root_dir(),
expected
.parent()
.expect("workspace cache dir should have parent")
.to_path_buf()
);
}

#[test]
fn test_workspace_cache_dir_detached_existing_default_dir() {
let temp_dir = tempfile::tempdir().unwrap();
let detached_root = temp_dir.path().join("detached");

let workspace = Workspace::from_str(
&temp_dir.path().join(consts::WORKSPACE_MANIFEST),
PROJECT_BOILERPLATE,
)
.unwrap()
.with_cli_config(Config {
detached_environments: Some(pixi_config::DetachedEnvironments::Path(detached_root)),
..Default::default()
});

let default_workspace_cache_dir = workspace.default_workspace_cache_dir();
fs_err::create_dir_all(&default_workspace_cache_dir).unwrap();
let marker_file = default_workspace_cache_dir.join("marker.txt");
fs_err::write(&marker_file, "keep").unwrap();

let expected_detached_cache_dir = workspace
.detached_environments_path()
.expect("detached environments path should be set")
.join(consts::WORKSPACE_CACHE_DIR);

assert_eq!(workspace.workspace_cache_dir(), expected_detached_cache_dir);
assert_eq!(
workspace.workspace_cache_root_dir(),
expected_detached_cache_dir.parent().unwrap()
);
assert!(default_workspace_cache_dir.exists());
assert!(!default_workspace_cache_dir.is_symlink());
assert!(marker_file.exists());
}

#[test]
fn test_workspace_cache_root_dir_detached_boolean_true() {
let temp_dir = tempfile::tempdir().unwrap();
let workspace = Workspace::from_str(
&temp_dir.path().join(consts::WORKSPACE_MANIFEST),
PROJECT_BOILERPLATE,
)
.unwrap()
.with_cli_config(Config {
detached_environments: Some(pixi_config::DetachedEnvironments::Boolean(true)),
..Default::default()
});

let detached_workspace_root = workspace
.detached_environments_path()
.expect("detached environments path should be set");

assert_eq!(
workspace.workspace_cache_root_dir(),
detached_workspace_root
);
}

fn format_dependencies(deps: pixi_manifest::CondaDependencies) -> String {
deps.iter_specs()
.map(|(name, spec)| format!("{} = {}", name.as_source(), spec.to_toml_value()))
Expand Down
Loading