From 306670b3404acb28040e0765651e6638fb833635 Mon Sep 17 00:00:00 2001 From: Julian Hofer Date: Wed, 5 Mar 2025 13:13:50 +0100 Subject: [PATCH] feat: default to "workspace" with `pixi init` --- .../pixi_build_frontend/tests/diagnostics.rs | 6 +-- crates/pixi_consts/src/consts.rs | 2 +- crates/pixi_manifest/src/discovery.rs | 2 +- .../pixi_manifest/src/manifests/document.rs | 32 +++++++++++-- .../pixi_manifest/src/manifests/provenance.rs | 4 +- src/cli/init.rs | 46 +++++++++---------- src/install_pypi/mod.rs | 2 +- src/lock_file/resolve/pypi.rs | 4 +- src/workspace/discovery.rs | 2 +- tests/integration_python/test_main_cli.py | 4 +- tests/integration_rust/common/mod.rs | 6 +-- 11 files changed, 68 insertions(+), 42 deletions(-) diff --git a/crates/pixi_build_frontend/tests/diagnostics.rs b/crates/pixi_build_frontend/tests/diagnostics.rs index 70992e7bd4..532193e746 100644 --- a/crates/pixi_build_frontend/tests/diagnostics.rs +++ b/crates/pixi_build_frontend/tests/diagnostics.rs @@ -59,7 +59,7 @@ async fn test_invalid_manifest() { let source_dir = tempfile::TempDir::new().unwrap(); let manifest = source_dir .path() - .join(pixi_consts::consts::PROJECT_MANIFEST); + .join(pixi_consts::consts::WORKSPACE_MANIFEST); tokio::fs::write(&manifest, "[workspace]").await.unwrap(); let err = BuildFrontend::default() .setup_protocol(SetupRequest { @@ -92,7 +92,7 @@ async fn test_not_a_package() { let source_dir = tempfile::TempDir::new().unwrap(); let manifest = source_dir .path() - .join(pixi_consts::consts::PROJECT_MANIFEST); + .join(pixi_consts::consts::WORKSPACE_MANIFEST); tokio::fs::write( &manifest, r#" @@ -126,7 +126,7 @@ async fn test_invalid_backend() { let source_dir = tempfile::TempDir::new().unwrap(); let manifest = source_dir .path() - .join(pixi_consts::consts::PROJECT_MANIFEST); + .join(pixi_consts::consts::WORKSPACE_MANIFEST); let toml = r#" [workspace] diff --git a/crates/pixi_consts/src/consts.rs b/crates/pixi_consts/src/consts.rs index 52bde05c60..9d923c285d 100644 --- a/crates/pixi_consts/src/consts.rs +++ b/crates/pixi_consts/src/consts.rs @@ -11,7 +11,7 @@ pub const DEFAULT_ENVIRONMENT_NAME: &str = "default"; pub const DEFAULT_FEATURE_NAME: &str = DEFAULT_ENVIRONMENT_NAME; pub const PYPROJECT_PIXI_PREFIX: &str = "tool.pixi"; -pub const PROJECT_MANIFEST: &str = "pixi.toml"; +pub const WORKSPACE_MANIFEST: &str = "pixi.toml"; pub const PYPROJECT_MANIFEST: &str = "pyproject.toml"; pub const CONFIG_FILE: &str = "config.toml"; pub const PIXI_VERSION: &str = match option_env!("PIXI_VERSION") { diff --git a/crates/pixi_manifest/src/discovery.rs b/crates/pixi_manifest/src/discovery.rs index a553d3721b..7c3199ecd6 100644 --- a/crates/pixi_manifest/src/discovery.rs +++ b/crates/pixi_manifest/src/discovery.rs @@ -491,7 +491,7 @@ impl WorkspaceDiscoverer { /// Discover the workspace manifest in a directory. fn provenance_from_dir(dir: &Path) -> Option { - let pixi_toml_path = dir.join(consts::PROJECT_MANIFEST); + let pixi_toml_path = dir.join(consts::WORKSPACE_MANIFEST); let pyproject_toml_path = dir.join(consts::PYPROJECT_MANIFEST); if pixi_toml_path.is_file() { Some(ManifestProvenance::new(pixi_toml_path, ManifestKind::Pixi)) diff --git a/crates/pixi_manifest/src/manifests/document.rs b/crates/pixi_manifest/src/manifests/document.rs index 436907cdbd..648dfd8acb 100644 --- a/crates/pixi_manifest/src/manifests/document.rs +++ b/crates/pixi_manifest/src/manifests/document.rs @@ -178,6 +178,17 @@ impl ManifestDocument { /// Detect the table name to use when querying elements of the manifest. fn detect_table_name(&self) -> &'static str { if self.manifest().as_table().contains_key("workspace") { + // pixi.toml + "workspace" + } else if self + .manifest() + .as_table() + .get("tool") + .and_then(|t| t.get("pixi")) + .and_then(|t| t.get("workspace")) + .is_some() + { + // pyproject.toml "workspace" } else { "project" @@ -677,16 +688,31 @@ impl ManifestDocument { /// Sets the name of the project pub fn set_name(&mut self, name: &str) { - self.as_table_mut()["project"]["name"] = value(name); + let table = self.as_table_mut(); + if table.contains_key("project") { + table["project"]["name"] = value(name); + } else { + table["workspace"]["name"] = value(name); + } } /// Sets the description of the project pub fn set_description(&mut self, description: &str) { - self.as_table_mut()["project"]["description"] = value(description); + let table = self.as_table_mut(); + if table.contains_key("project") { + table["project"]["description"] = value(description); + } else { + table["workspace"]["description"] = value(description); + } } /// Sets the version of the project pub fn set_version(&mut self, version: &str) { - self.as_table_mut()["project"]["version"] = value(version); + let table = self.as_table_mut(); + if table.contains_key("project") { + table["project"]["version"] = value(version); + } else { + table["workspace"]["version"] = value(version); + } } } diff --git a/crates/pixi_manifest/src/manifests/provenance.rs b/crates/pixi_manifest/src/manifests/provenance.rs index 1fb0e7c65f..3f3b5539d4 100644 --- a/crates/pixi_manifest/src/manifests/provenance.rs +++ b/crates/pixi_manifest/src/manifests/provenance.rs @@ -81,7 +81,7 @@ impl ManifestKind { /// Try to determine the type of manifest from a path pub fn try_from_path(path: &Path) -> Option { match path.file_name().and_then(OsStr::to_str)? { - consts::PROJECT_MANIFEST => Some(Self::Pixi), + consts::WORKSPACE_MANIFEST => Some(Self::Pixi), consts::PYPROJECT_MANIFEST => Some(Self::Pyproject), _ => None, } @@ -90,7 +90,7 @@ impl ManifestKind { /// Returns the default file name for a manifest of a certain kind. pub fn file_name(self) -> &'static str { match self { - ManifestKind::Pixi => consts::PROJECT_MANIFEST, + ManifestKind::Pixi => consts::WORKSPACE_MANIFEST, ManifestKind::Pyproject => consts::PYPROJECT_MANIFEST, } } diff --git a/src/cli/init.rs b/src/cli/init.rs index 11eba2d64a..1d07436b38 100644 --- a/src/cli/init.rs +++ b/src/cli/init.rs @@ -33,19 +33,19 @@ pub enum ManifestFormat { /// Creates a new workspace #[derive(Parser, Debug)] pub struct Args { - /// Where to place the project (defaults to current path) + /// Where to place the workspace (defaults to current path) #[arg(default_value = ".")] pub path: PathBuf, - /// Channels to use in the project. + /// Channels to use in the workspace. #[arg(short, long = "channel", id = "channel", conflicts_with = "env_file")] pub channels: Option>, - /// Platforms that the project supports. + /// Platforms that the workspace supports. #[arg(short, long = "platform", id = "platform")] pub platforms: Vec, - /// Environment.yml file to bootstrap the project. + /// Environment.yml file to bootstrap the workspace. #[arg(short = 'i', long = "import")] pub env_file: Option, @@ -58,7 +58,7 @@ pub struct Args { #[arg(long, conflicts_with_all = ["env_file", "format"], alias = "pyproject", hide = true)] pub pyproject_toml: bool, - /// Source Control Management used for this project + /// Source Control Management used for this workspace #[arg(short = 's', long = "scm", ignore_case = true)] pub scm: Option, } @@ -66,7 +66,7 @@ pub struct Args { /// The pixi.toml template /// /// This uses a template just to simplify the flexibility of emitting it. -const PROJECT_TEMPLATE: &str = r#"[project] +const WORKSPACE_TEMPLATE: &str = r#"[workspace] {%- if author %} authors = ["{{ author[0] }} <{{ author[1] }}>"] {%- endif %} @@ -85,7 +85,7 @@ version = "{{ version }}" {%- if s3 %} {%- for key in s3 %} -[project.s3-options.{{ key }}] +[workspace.s3-options.{{ key }}] {%- if s3[key]["endpoint-url"] %} endpoint-url = "{{ s3[key]["endpoint-url"] }}" {%- endif %} @@ -117,7 +117,7 @@ env = { {{ env_vars }} } /// /// This is injected into an existing pyproject.toml const PYROJECT_TEMPLATE_EXISTING: &str = r#" -[tool.pixi.project] +[tool.pixi.workspace] {%- if pixi_name %} name = "{{ name }}" {%- endif %} @@ -138,7 +138,7 @@ default = { solve-group = "default" } {%- if s3 %} {%- for key in s3 %} -[tool.pixi.project.s3-options.{{ key }}] +[tool.pixi.workspace.s3-options.{{ key }}] {%- if s3[key]["endpoint-url"] %} endpoint-url = "{{ s3[key]["endpoint-url"] }}" {%- endif %} @@ -174,7 +174,7 @@ version = "{{ version }}" build-backend = "hatchling.build" requires = ["hatchling"] -[tool.pixi.project] +[tool.pixi.workspace] channels = {{ channels }} platforms = {{ platforms }} @@ -189,7 +189,7 @@ platforms = {{ platforms }} {%- if s3 %} {%- for key in s3 %} -[tool.pixi.project.s3-options.{{ key }}] +[tool.pixi.workspace.s3-options.{{ key }}] {%- if s3[key]["endpoint-url"] %} endpoint-url = "{{ s3[key]["endpoint-url"] }}" {%- endif %} @@ -247,7 +247,7 @@ pub async fn execute(args: Args) -> miette::Result<()> { // Fail silently if the directory already exists or cannot be created. fs_err::create_dir_all(&args.path).ok(); let dir = args.path.canonicalize().into_diagnostic()?; - let pixi_manifest_path = dir.join(consts::PROJECT_MANIFEST); + let pixi_manifest_path = dir.join(consts::WORKSPACE_MANIFEST); let pyproject_manifest_path = dir.join(consts::PYPROJECT_MANIFEST); let gitignore_path = dir.join(".gitignore"); let gitattributes_path = dir.join(".gitattributes"); @@ -263,7 +263,7 @@ pub async fn execute(args: Args) -> miette::Result<()> { ); } - let default_name = get_name_from_dir(&dir).unwrap_or_else(|_| String::from("new_project")); + let default_name = get_name_from_dir(&dir).unwrap_or_else(|_| String::from("new_workspace")); let version = "0.1.0"; let author = get_default_author(); let platforms = if args.platforms.is_empty() { @@ -278,7 +278,7 @@ pub async fn execute(args: Args) -> miette::Result<()> { // Check if the 'pixi.toml' file doesn't already exist. We don't want to // overwrite it. if pixi_manifest_path.is_file() { - miette::bail!("{} already exists", consts::PROJECT_MANIFEST); + miette::bail!("{} already exists", consts::WORKSPACE_MANIFEST); } let env_file = CondaEnvFile::from_path(&env_file_path)?; @@ -291,7 +291,7 @@ pub async fn execute(args: Args) -> miette::Result<()> { // TODO: Improve this: // - Use .condarc as channel config let (conda_deps, pypi_deps, channels) = env_file.to_manifest(&config)?; - let rendered_workspace_template = render_project( + let rendered_workspace_template = render_workspace( &env, name, version, @@ -372,15 +372,15 @@ pub async fn execute(args: Args) -> miette::Result<()> { args.format == Some(ManifestFormat::Pyproject) || args.pyproject_toml }; - // Inject a tool.pixi.project section into an existing pyproject.toml file if - // there is one without '[tool.pixi.project]' + // Inject a tool.pixi.workspace section into an existing pyproject.toml file if + // there is one without '[tool.pixi.workspace]' if pyproject && pyproject_manifest_path.is_file() { let pyproject = PyProjectManifest::from_path(&pyproject_manifest_path)?; - // Early exit if 'pyproject.toml' already contains a '[tool.pixi.project]' table + // Early exit if 'pyproject.toml' already contains a '[tool.pixi.workspace]' table if pyproject.has_pixi_table() { eprintln!( - "{}Nothing to do here: 'pyproject.toml' already contains a '[tool.pixi.project]' section.", + "{}Nothing to do here: 'pyproject.toml' already contains a '[tool.pixi.workspace3]' section.", console::style(console::Emoji("🤔 ", "")).blue(), ); return Ok(()); @@ -492,9 +492,9 @@ pub async fn execute(args: Args) -> miette::Result<()> { // Check if the 'pixi.toml' file doesn't already exist. We don't want to // overwrite it. if pixi_manifest_path.is_file() { - miette::bail!("{} already exists", consts::PROJECT_MANIFEST); + miette::bail!("{} already exists", consts::WORKSPACE_MANIFEST); } - let rv = render_project( + let rv = render_workspace( &env, default_name, version, @@ -534,7 +534,7 @@ pub async fn execute(args: Args) -> miette::Result<()> { } #[allow(clippy::too_many_arguments)] -fn render_project( +fn render_workspace( env: &Environment<'_>, name: String, version: &str, @@ -560,7 +560,7 @@ fn render_project( } else {String::new()}}, }; - env.render_named_str(consts::PROJECT_MANIFEST, PROJECT_TEMPLATE, ctx) + env.render_named_str(consts::WORKSPACE_MANIFEST, WORKSPACE_TEMPLATE, ctx) .expect("should be able to render the template") } diff --git a/src/install_pypi/mod.rs b/src/install_pypi/mod.rs index 0cfa919226..d2adfe6cb9 100644 --- a/src/install_pypi/mod.rs +++ b/src/install_pypi/mod.rs @@ -64,7 +64,7 @@ pub async fn update_python_distributions( let python_record = pixi_records .iter() .find(|r| is_python_record(r)) - .ok_or_else(|| miette::miette!("could not resolve pypi dependencies because no python interpreter is added to the dependencies of the project.\nMake sure to add a python interpreter to the [dependencies] section of the {manifest}, or run:\n\n\tpixi add python", manifest=consts::PROJECT_MANIFEST))?; + .ok_or_else(|| miette::miette!("could not resolve pypi dependencies because no python interpreter is added to the dependencies of the project.\nMake sure to add a python interpreter to the [dependencies] section of the {manifest}, or run:\n\n\tpixi add python", manifest=consts::WORKSPACE_MANIFEST))?; let tags = get_pypi_tags( platform, system_requirements, diff --git a/src/lock_file/resolve/pypi.rs b/src/lock_file/resolve/pypi.rs index b1b687b51f..dc260373b6 100644 --- a/src/lock_file/resolve/pypi.rs +++ b/src/lock_file/resolve/pypi.rs @@ -237,7 +237,7 @@ pub async fn resolve_pypi( .collect::, _>>() .into_diagnostic()?; - use pixi_consts::consts::PROJECT_MANIFEST; + use pixi_consts::consts::WORKSPACE_MANIFEST; // Determine the python interpreter that is installed as part of the conda // packages. let python_record = locked_pixi_records @@ -246,7 +246,7 @@ pub async fn resolve_pypi( PixiRecord::Binary(r) => is_python_record(r), _ => false, }) - .ok_or_else(|| miette::miette!("could not resolve pypi dependencies because no python interpreter is added to the dependencies of the project.\nMake sure to add a python interpreter to the [dependencies] section of the {PROJECT_MANIFEST}, or run:\n\n\tpixi add python"))?; + .ok_or_else(|| miette::miette!("could not resolve pypi dependencies because no python interpreter is added to the dependencies of the project.\nMake sure to add a python interpreter to the [dependencies] section of the {WORKSPACE_MANIFEST}, or run:\n\n\tpixi add python"))?; // Construct the marker environment for the target platform let marker_environment = determine_marker_environment(platform, python_record.as_ref())?; diff --git a/src/workspace/discovery.rs b/src/workspace/discovery.rs index 589b0a2f65..e3bc4be519 100644 --- a/src/workspace/discovery.rs +++ b/src/workspace/discovery.rs @@ -60,7 +60,7 @@ pub enum WorkspaceLocatorError { /// The workspace could not be located. #[error( "could not find {project_manifest} or {pyproject_manifest} at directory {0}", - project_manifest = consts::PROJECT_MANIFEST, + project_manifest = consts::WORKSPACE_MANIFEST, pyproject_manifest = consts::PYPROJECT_MANIFEST )] WorkspaceNotFound(PathBuf), diff --git a/tests/integration_python/test_main_cli.py b/tests/integration_python/test_main_cli.py index 2594f83336..c4e84b381c 100644 --- a/tests/integration_python/test_main_cli.py +++ b/tests/integration_python/test_main_cli.py @@ -348,7 +348,7 @@ def test_pixi_init_cwd(pixi: Path, tmp_pixi_workspace: Path) -> None: # Verify that the manifest file contains expected content manifest_content = manifest_path.read_text() - assert "[project]" in manifest_content + assert "[workspace]" in manifest_content def test_pixi_init_non_existing_dir(pixi: Path, tmp_pixi_workspace: Path) -> None: @@ -364,7 +364,7 @@ def test_pixi_init_non_existing_dir(pixi: Path, tmp_pixi_workspace: Path) -> Non # Verify that the manifest file contains expected content manifest_content = manifest_path.read_text() - assert "[project]" in manifest_content + assert "[workspace]" in manifest_content @pytest.mark.slow diff --git a/tests/integration_rust/common/mod.rs b/tests/integration_rust/common/mod.rs index 938076007e..2624249859 100644 --- a/tests/integration_rust/common/mod.rs +++ b/tests/integration_rust/common/mod.rs @@ -274,10 +274,10 @@ impl PixiControl { // Either pixi.toml or pyproject.toml if self .workspace_path() - .join(consts::PROJECT_MANIFEST) + .join(consts::WORKSPACE_MANIFEST) .exists() { - self.workspace_path().join(consts::PROJECT_MANIFEST) + self.workspace_path().join(consts::WORKSPACE_MANIFEST) } else if self .workspace_path() .join(consts::PYPROJECT_MANIFEST) @@ -285,7 +285,7 @@ impl PixiControl { { self.workspace_path().join(consts::PYPROJECT_MANIFEST) } else { - self.workspace_path().join(consts::PROJECT_MANIFEST) + self.workspace_path().join(consts::WORKSPACE_MANIFEST) } }