Skip to content

Commit

Permalink
fix: create manifest missing directory with pixi global edit
Browse files Browse the repository at this point in the history
  • Loading branch information
zbowling committed Jan 7, 2025
1 parent c464ed7 commit cb2926a
Show file tree
Hide file tree
Showing 7 changed files with 44 additions and 21 deletions.
4 changes: 4 additions & 0 deletions crates/pixi_consts/src/consts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ pub const PIXI_DIR: &str = match option_env!("PIXI_DIR") {
Some(dir) => dir,
None => ".pixi",
};
pub const MANIFEST_DEFAULT_NAME: &str = match option_env!("PIXI_MANIFEST_DEFAULT_NAME") {
Some(name) => name,
None => "pixi-global.toml",
};

pub static DEFAULT_CHANNELS: LazyLock<Vec<NamedChannelOrUrl>> =
LazyLock::new(|| match option_env!("PIXI_DEFAULT_CHANNELS") {
Expand Down
4 changes: 1 addition & 3 deletions crates/pixi_spec/src/toml.rs
Original file line number Diff line number Diff line change
Expand Up @@ -575,9 +575,7 @@ mod test {
let result = match spec {
Ok(spec) => serde_json::to_value(&spec).unwrap(),
Err(e) => {
json!({
"error": format!("ERROR: {e}")
})
json!({ "error": format!("ERROR: {e}") })
}
};

Expand Down
8 changes: 8 additions & 0 deletions src/cli/global/edit.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::global::Project;
use clap::Parser;
use fs_err as fs;
use miette::IntoDiagnostic;

/// Edit the global manifest file
Expand All @@ -16,6 +17,13 @@ pub struct Args {
pub async fn execute(args: Args) -> miette::Result<()> {
let manifest_path = Project::default_manifest_path()?;

// Make sure directory exists to avoid errors when opening the file
let dir = manifest_path.parent().ok_or(miette::miette!(
"Failed to get parent directory of manifest file"
))?;
fs::create_dir_all(dir)
.map_err(|e| miette::miette!("Failed to create directory for manifest file: {e}"))?;

let editor = args.editor.unwrap_or_else(|| {
if cfg!(windows) {
"notepad".to_string()
Expand Down
2 changes: 1 addition & 1 deletion src/cli/info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -438,7 +438,7 @@ pub async fn execute(args: Args) -> miette::Result<()> {
let global_info = Some(GlobalInfo {
bin_dir: BinDir::from_env().await?.path().to_path_buf(),
env_dir: EnvRoot::from_env().await?.path().to_path_buf(),
manifest: global::Project::manifest_dir()?.join(global::project::MANIFEST_DEFAULT_NAME),
manifest: global::Project::manifest_dir()?.join(consts::MANIFEST_DEFAULT_NAME),
});

let virtual_packages = VirtualPackage::detect(&VirtualPackageOverrides::from_env())
Expand Down
5 changes: 3 additions & 2 deletions src/global/project/manifest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use fs_err::tokio as tokio_fs;
use indexmap::IndexSet;
use miette::IntoDiagnostic;
use pixi_config::Config;
use pixi_consts::consts;
use pixi_manifest::{toml::TomlDocument, PrioritizedChannel};
use pixi_spec::PixiSpec;
use pixi_toml::TomlIndexMap;
Expand All @@ -19,7 +20,7 @@ use toml_span::{DeserError, Value};

use super::{
parsed_manifest::{ManifestParsingError, ManifestVersion, ParsedManifest},
EnvironmentName, ExposedName, MANIFEST_DEFAULT_NAME,
EnvironmentName, ExposedName,
};
use crate::global::project::ParsedEnvironment;

Expand Down Expand Up @@ -60,7 +61,7 @@ impl Manifest {
.map_err(ManifestParsingError::from)
}) {
Ok(result) => result,
Err(e) => e.to_fancy(MANIFEST_DEFAULT_NAME, &contents, manifest_path)?,
Err(e) => e.to_fancy(consts::MANIFEST_DEFAULT_NAME, &contents, manifest_path)?,
};

let manifest = Self {
Expand Down
22 changes: 12 additions & 10 deletions src/global/project/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ mod environment;
mod manifest;
mod parsed_manifest;

pub(crate) const MANIFEST_DEFAULT_NAME: &str = "pixi-global.toml";
pub(crate) const MANIFESTS_DIR: &str = "manifests";

/// The pixi global project, this main struct to interact with the pixi global
Expand Down Expand Up @@ -386,15 +385,18 @@ impl Project {
/// Get default dir for the pixi global manifest
pub(crate) fn manifest_dir() -> miette::Result<PathBuf> {
// Potential directories, with the highest priority coming first
let potential_dirs = [pixi_home(), dirs::config_dir().map(|dir| dir.join("pixi"))]
.into_iter()
.flatten()
.map(|dir| dir.join(MANIFESTS_DIR))
.collect_vec();
let potential_dirs = [
pixi_home(),
dirs::config_dir().map(|dir| dir.join(consts::CONFIG_DIR)),
]
.into_iter()
.flatten()
.map(|dir| dir.join(MANIFESTS_DIR))
.collect_vec();

// First, check if a `pixi-global.toml` already exists
for dir in &potential_dirs {
if dir.join(MANIFEST_DEFAULT_NAME).is_file() {
if dir.join(consts::MANIFEST_DEFAULT_NAME).is_file() {
return Ok(dir.clone());
}
}
Expand All @@ -408,7 +410,7 @@ impl Project {

/// Get the default path to the global manifest file
pub(crate) fn default_manifest_path() -> miette::Result<PathBuf> {
Self::manifest_dir().map(|dir| dir.join(MANIFEST_DEFAULT_NAME))
Self::manifest_dir().map(|dir| dir.join(consts::MANIFEST_DEFAULT_NAME))
}

/// Loads a project from manifest file.
Expand Down Expand Up @@ -1097,7 +1099,7 @@ mod tests {
#[tokio::test]
async fn test_project_from_path() {
let tempdir = tempfile::tempdir().unwrap();
let manifest_path = tempdir.path().join(MANIFEST_DEFAULT_NAME);
let manifest_path = tempdir.path().join(consts::MANIFEST_DEFAULT_NAME);

let env_root = EnvRoot::from_env().await.unwrap();
let bin_dir = BinDir::from_env().await.unwrap();
Expand Down Expand Up @@ -1251,7 +1253,7 @@ mod tests {

// Create project with env1 and env3
let manifest = Manifest::from_str(
&env_root.path().join(MANIFEST_DEFAULT_NAME),
&env_root.path().join(consts::MANIFEST_DEFAULT_NAME),
r#"
[envs.env1]
channels = ["conda-forge"]
Expand Down
20 changes: 15 additions & 5 deletions src/global/project/parsed_manifest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -329,15 +329,18 @@ impl FancyDisplay for ExposedName {

#[derive(Error, Diagnostic, Debug, PartialEq)]
pub(crate) enum ExposedNameError {
#[error("'pixi' is not allowed as exposed name in the map")]
#[error(
"'{0}' is not allowed as exposed name in the map",
pixi_utils::executable_name()
)]
PixiBinParseError,
}

impl FromStr for ExposedName {
type Err = ExposedNameError;

fn from_str(value: &str) -> Result<Self, Self::Err> {
if value == "pixi" {
if value == pixi_utils::executable_name() {
Err(ExposedNameError::PixiBinParseError)
} else {
Ok(ExposedName(value.to_string()))
Expand Down Expand Up @@ -442,12 +445,19 @@ mod tests {
[envs.test.dependencies]
python = "*"
[envs.test.exposed]
pixi = "python"
pixi-bin-name = "python"
"#;
let manifest = ParsedManifest::from_toml_str(contents);

// Replace the pixi-bin-name with the actual executable name that can be variable at runtime in tests
let contents = contents.replace("pixi-bin-name", pixi_utils::executable_name());
let manifest = ParsedManifest::from_toml_str(contents.as_str());

assert!(manifest.is_err());
assert_snapshot!(manifest.unwrap_err());
// Replace back the executable name with "pixi" to satisfy the snapshot
assert_snapshot!(manifest
.unwrap_err()
.to_string()
.replace(pixi_utils::executable_name(), "pixi"));
}

#[test]
Expand Down

0 comments on commit cb2926a

Please sign in to comment.