diff --git a/docs/features/advanced_tasks.md b/docs/features/advanced_tasks.md index af4c6fff4..e35dd9f68 100644 --- a/docs/features/advanced_tasks.md +++ b/docs/features/advanced_tasks.md @@ -212,6 +212,8 @@ This setting can also be set from the command line with `pixi run --clean-env TA On Windows it's hard to create a "clean environment" as `conda-forge` doesn't ship Windows compilers and Windows needs a lot of base variables. Making this feature not worthy of implementing as the amount of edge cases will make it unusable. + + ## Our task runner: deno_task_shell To support the different OS's (Windows, OSX and Linux), pixi integrates a shell that can run on all of them. diff --git a/docs/features/global_tools.md b/docs/features/global_tools.md index c6d368ed8..d05a53e7c 100644 --- a/docs/features/global_tools.md +++ b/docs/features/global_tools.md @@ -93,22 +93,22 @@ The manifest can be found at the following locations depending on your operating | **Priority** | **Location** | **Comments** | |--------------|------------------------------------------------------------------------|------------------------------------------------------------------------------------| - | 1 | `$HOME/.pixi/manifests/pixi-global.toml` | User-specific manifest | | 2 | `$PIXI_HOME/manifests/pixi-global.toml` | Global manifest in the user home directory. `PIXI_HOME` defaults to `~/.pixi` | + | 1 | `$HOME/.pixi/manifests/pixi-global.toml` | User-specific manifest | === "macOS" | **Priority** | **Location** | **Comments** | |--------------|------------------------------------------------------------------------|------------------------------------------------------------------------------------| - | 1 | `$HOME/.pixi/manifests/pixi-global.toml` | User-specific manifest | | 2 | `$PIXI_HOME/manifests/pixi-global.toml` | Global manifest in the user home directory. `PIXI_HOME` defaults to `~/.pixi` | + | 1 | `$HOME/.pixi/manifests/pixi-global.toml` | User-specific manifest | === "Windows" | **Priority** | **Location** | **Comments** | |--------------|------------------------------------------------------------------------|------------------------------------------------------------------------------------------------| - | 1 | `%USERPROFILE%\.pixi\manifests\pixi-global.toml` | User-specific manifest | | 2 | `$PIXI_HOME\manifests/pixi-global.toml` | Global manifest in the user home directory. `PIXI_HOME` defaults to `%USERPROFILE%/.pixi` | + | 1 | `%USERPROFILE%\.pixi\manifests\pixi-global.toml` | User-specific manifest | !!! note If multiple locations exist, the manifest with the highest priority will be used. diff --git a/docs/reference/pixi_configuration.md b/docs/reference/pixi_configuration.md index 7c73a536a..a56f74754 100644 --- a/docs/reference/pixi_configuration.md +++ b/docs/reference/pixi_configuration.md @@ -8,33 +8,33 @@ The configuration is loaded in the following order: | **Priority** | **Location** | **Comments** | |--------------|------------------------------------------------------------------------|------------------------------------------------------------------------------------| - | 1 | `/etc/pixi/config.toml` | System-wide configuration | - | 2 | `$XDG_CONFIG_HOME/pixi/config.toml` | XDG compliant user-specific configuration | - | 3 | `$HOME/.config/pixi/config.toml` | User-specific configuration | - | 4 | `$PIXI_HOME/config.toml` | Global configuration in the user home directory. `PIXI_HOME` defaults to `~/.pixi` | - | 5 | `your_project/.pixi/config.toml` | Project-specific configuration | | 6 | Command line arguments (`--tls-no-verify`, `--change-ps1=false`, etc.) | Configuration via command line arguments | + | 5 | `your_project/.pixi/config.toml` | Project-specific configuration | + | 4 | `$PIXI_HOME/config.toml` | Global configuration in the user home directory. `PIXI_HOME` defaults to `~/.pixi` | + | 3 | `$HOME/.config/pixi/config.toml` | User-specific configuration | + | 2 | `$XDG_CONFIG_HOME/pixi/config.toml` | XDG compliant user-specific configuration | + | 1 | `/etc/pixi/config.toml` | System-wide configuration | === "macOS" | **Priority** | **Location** | **Comments** | |--------------|------------------------------------------------------------------------|------------------------------------------------------------------------------------| - | 1 | `/etc/pixi/config.toml` | System-wide configuration | - | 2 | `$XDG_CONFIG_HOME/pixi/config.toml` | XDG compliant user-specific configuration | - | 3 | `$HOME/Library/Application Support/pixi/config.toml` | User-specific configuration | - | 4 | `$PIXI_HOME/config.toml` | Global configuration in the user home directory. `PIXI_HOME` defaults to `~/.pixi` | - | 5 | `your_project/.pixi/config.toml` | Project-specific configuration | | 6 | Command line arguments (`--tls-no-verify`, `--change-ps1=false`, etc.) | Configuration via command line arguments | + | 5 | `your_project/.pixi/config.toml` | Project-specific configuration | + | 4 | `$PIXI_HOME/config.toml` | Global configuration in the user home directory. `PIXI_HOME` defaults to `~/.pixi` | + | 3 | `$HOME/Library/Application Support/pixi/config.toml` | User-specific configuration | + | 2 | `$XDG_CONFIG_HOME/pixi/config.toml` | XDG compliant user-specific configuration | + | 1 | `/etc/pixi/config.toml` | System-wide configuration | === "Windows" | **Priority** | **Location** | **Comments** | |--------------|------------------------------------------------------------------------|------------------------------------------------------------------------------------------------| - | 1 | `C:\ProgramData\pixi\config.toml` | System-wide configuration | - | 2 | `%APPDATA%\pixi\config.toml` | User-specific configuration | - | 3 | `$PIXI_HOME\config.toml` | Global configuration in the user home directory. `PIXI_HOME` defaults to `%USERPROFILE%/.pixi` | - | 4 | `your_project\.pixi\config.toml` | Project-specific configuration | | 5 | Command line arguments (`--tls-no-verify`, `--change-ps1=false`, etc.) | Configuration via command line arguments | + | 4 | `your_project\.pixi\config.toml` | Project-specific configuration | + | 3 | `$PIXI_HOME\config.toml` | Global configuration in the user home directory. `PIXI_HOME` defaults to `%USERPROFILE%/.pixi` | + | 2 | `%APPDATA%\pixi\config.toml` | User-specific configuration | + | 1 | `C:\ProgramData\pixi\config.toml` | System-wide configuration | !!! note The highest priority wins. If a configuration file is found in a higher priority location, the values from the configuration read from lower priority locations are overwritten. diff --git a/docs/reference/project_configuration.md b/docs/reference/project_configuration.md index 4c6aa17a3..ff98878e3 100644 --- a/docs/reference/project_configuration.md +++ b/docs/reference/project_configuration.md @@ -1,20 +1,38 @@ --- part: pixi -title: Configuration -description: Learn what you can do in the pixi.toml configuration. +title: Manifest +description: Learn what you can do in the pixi manifest. --- -The `pixi.toml` is the pixi project configuration file, also known as the project manifest. +The `pixi.toml` is the project manifest, also known as the pixi project configuration file. A `toml` file is structured in different tables. This document will explain the usage of the different tables. -For more technical documentation check pixi on [crates.io](https://docs.rs/pixi/latest/pixi/project/manifest/struct.ProjectManifest.html). +For more technical documentation check pixi on [docs.rs](https://docs.rs/pixi/latest/pixi/project/manifest/struct.ProjectManifest.html). !!! tip We also support the `pyproject.toml` file. It has the same structure as the `pixi.toml` file. except that you need to prepend the tables with `tool.pixi` instead of just the table name. For example, the `[project]` table becomes `[tool.pixi.project]`. There are also some small extras that are available in the `pyproject.toml` file, checkout the [pyproject.toml](../advanced/pyproject_toml.md) documentation for more information. +## Manifest discovery + +The manifest can be found at the following locations depending on your operating system. + + +| **Priority** | **Location** | **Comments** | +|--------------|------------------------------------------------------------------------|------------------------------------------------------------------------------------| +| 6 | `--manifest-path` | Command-line argument | +| 5 | `pixi.toml` | In your current working directory. | +| 4 | `pyproject.toml` | In your current working directory. | +| 3 | `pixi.toml` or `pyproject.toml` | Iterate through all parent directories. The first discovered manifest is used. | +| 1 | `$PIXI_PROJECT_MANIFEST` | If `$PIXI_IN_SHELL` is set. This happens with `pixi shell` or `pixi run`. | + + +!!! note + If multiple locations exist, the manifest with the highest priority will be used. + + ## The `project` table The minimally required information in the `project` table is: diff --git a/src/project/mod.rs b/src/project/mod.rs index 25522051a..d129104d2 100644 --- a/src/project/mod.rs +++ b/src/project/mod.rs @@ -199,31 +199,30 @@ impl Project { pub(crate) fn discover() -> miette::Result { let project_toml = find_project_manifest(std::env::current_dir().into_diagnostic()?); - if std::env::var("PIXI_IN_SHELL").is_ok() { - if let Ok(env_manifest_path) = std::env::var("PIXI_PROJECT_MANIFEST") { - if let Some(project_toml) = project_toml { + if let Some(project_toml) = project_toml { + if std::env::var("PIXI_IN_SHELL").is_ok() { + if let Ok(env_manifest_path) = std::env::var("PIXI_PROJECT_MANIFEST") { if env_manifest_path != project_toml.to_string_lossy() { tracing::warn!( - "Using manifest {} from `PIXI_PROJECT_MANIFEST` rather than local {}", + "Using local manifest {} rather than {} from environment variable `PIXI_PROJECT_MANIFEST`", + project_toml.to_string_lossy(), env_manifest_path, - project_toml.to_string_lossy() ); } } - return Self::from_path(Path::new(env_manifest_path.as_str())); } + return Self::from_path(&project_toml); } - let project_toml = match project_toml { - Some(file) => file, - None => miette::bail!( - "could not find {} or {} which is configured to use pixi", - consts::PROJECT_MANIFEST, - consts::PYPROJECT_MANIFEST - ), - }; + if let Ok(env_manifest_path) = std::env::var("PIXI_PROJECT_MANIFEST") { + return Self::from_path(Path::new(env_manifest_path.as_str())); + } - Self::from_path(&project_toml) + miette::bail!( + "could not find {} or {} which is configured to use pixi", + consts::PROJECT_MANIFEST, + consts::PYPROJECT_MANIFEST + ); } /// Loads a project from manifest file. @@ -253,9 +252,9 @@ impl Project { if let (Some(discover_path), Ok(env_path)) = (discover_path, env_path) { if env_path.as_str() != discover_path.to_str().unwrap() { tracing::warn!( - "Used manifest {} from `PIXI_PROJECT_MANIFEST` rather than local {}", + "Used local manifest {} rather than {} from environment variable `PIXI_PROJECT_MANIFEST`", + discover_path.to_string_lossy(), env_path, - discover_path.to_string_lossy() ); } } diff --git a/tests/integration_python/common.py b/tests/integration_python/common.py index 35e130356..7751085fa 100644 --- a/tests/integration_python/common.py +++ b/tests/integration_python/common.py @@ -37,17 +37,15 @@ def verify_cli_command( stderr_contains: str | list[str] | None = None, stderr_excludes: str | list[str] | None = None, env: dict[str, str] | None = None, + cwd: str | Path | None = None, + reset_env: bool = False, ) -> Output: - # Setup the environment type safe. - base_env = dict(os.environ) - if env is not None: - complete_env = base_env | env - else: - complete_env = base_env - # Avoid to have miette splitting up lines - complete_env = complete_env | {"NO_GRAPHICS": "1"} + base_env = {} if reset_env else dict(os.environ) + complete_env = base_env if env is None else base_env | env + # Set `NO_GRAPHICS` to avoid to have miette splitting up lines + complete_env |= {"NO_GRAPHICS": "1"} - process = subprocess.run(command, capture_output=True, text=True, env=complete_env) + process = subprocess.run(command, capture_output=True, text=True, env=complete_env, cwd=cwd) stdout, stderr, returncode = process.stdout, process.stderr, process.returncode output = Output(command, stdout, stderr, returncode) print(f"command: {command}, stdout: {stdout}, stderr: {stderr}, code: {returncode}") diff --git a/tests/integration_python/test_run_cli.py b/tests/integration_python/test_run_cli.py index 82f611c49..619bf7f55 100644 --- a/tests/integration_python/test_run_cli.py +++ b/tests/integration_python/test_run_cli.py @@ -1,6 +1,8 @@ import json from pathlib import Path from .common import verify_cli_command, ExitCode, default_env_path +import tempfile +import os ALL_PLATFORMS = '["linux-64", "osx-64", "win-64", "linux-ppc64le", "linux-aarch64"]' @@ -12,7 +14,7 @@ """ -def test_run_in_shell(pixi: Path, tmp_path: Path) -> None: +def test_run_in_shell_environment(pixi: Path, tmp_path: Path) -> None: manifest = tmp_path.joinpath("pixi.toml") toml = f""" {EMPTY_BOILERPLATE_PROJECT} @@ -31,7 +33,6 @@ def test_run_in_shell(pixi: Path, tmp_path: Path) -> None: # Run the default task verify_cli_command( [pixi, "run", "--manifest-path", manifest, "--environment", "default", "task"], - ExitCode.SUCCESS, stdout_contains="default", stderr_excludes="default1", ) @@ -39,7 +40,6 @@ def test_run_in_shell(pixi: Path, tmp_path: Path) -> None: # Run the a task verify_cli_command( [pixi, "run", "--manifest-path", manifest, "--environment", "a", "task"], - ExitCode.SUCCESS, stdout_contains=["a", "a1"], ) @@ -54,12 +54,75 @@ def test_run_in_shell(pixi: Path, tmp_path: Path) -> None: env = {"PIXI_IN_SHELL": "true", "PIXI_ENVIRONMENT_NAME": "a"} verify_cli_command( [pixi, "run", "--manifest-path", manifest, "task"], - ExitCode.SUCCESS, stdout_contains=["a", "a1"], env=env, ) +def test_run_in_shell_project(pixi: Path) -> None: + # We don't want a `pixi.toml` in our parent directory + # so let's use tempfile here + with tempfile.TemporaryDirectory() as tmp_str: + tmp_path = Path(tmp_str) + manifest_1_dir = tmp_path.joinpath("manifest_1") + manifest_1_dir.mkdir() + manifest_1 = manifest_1_dir.joinpath("pixi.toml") + toml = f""" + {EMPTY_BOILERPLATE_PROJECT} + [tasks] + task = "echo manifest_1" + """ + manifest_1.write_text(toml) + + manifest_2_dir = tmp_path.joinpath("manifest_2") + manifest_2_dir.mkdir() + manifest_2 = manifest_2_dir.joinpath("pixi.toml") + toml = f""" + {EMPTY_BOILERPLATE_PROJECT} + [tasks] + task = "echo manifest_2" + """ + manifest_2.write_text(toml) + + base_env = dict(os.environ) + base_env.pop("PIXI_IN_SHELL", None) + base_env.pop("PIXI_PROJECT_MANIFEST", None) + extended_env = base_env | { + "PIXI_IN_SHELL": "true", + "PIXI_PROJECT_MANIFEST": str(manifest_2), + } + + # Run task with PIXI_PROJECT_MANIFEST set to manifest_2 + verify_cli_command( + [pixi, "run", "task"], + stdout_contains="manifest_2", + env=extended_env, + cwd=tmp_path, + reset_env=True, + ) + + # Run with working directory at manifest_1_dir + verify_cli_command( + [pixi, "run", "task"], + stdout_contains="manifest_1", + env=base_env, + cwd=manifest_1_dir, + reset_env=True, + ) + + # Run task with PIXI_PROJECT_MANIFEST set to manifest_2 and working directory at manifest_1_dir + # working directory should win + # pixi should warn that it uses the local manifest rather than PIXI_PROJECT_MANIFEST + verify_cli_command( + [pixi, "run", "task"], + stdout_contains="manifest_1", + stderr_contains="manifest_2", + env=extended_env, + cwd=manifest_1_dir, + reset_env=True, + ) + + def test_using_prefix_validation(pixi: Path, tmp_path: Path, dummy_channel_1: str) -> None: manifest = tmp_path.joinpath("pixi.toml") toml = f""" @@ -128,7 +191,6 @@ def test_prefix_revalidation(pixi: Path, tmp_path: Path, dummy_channel_1: str) - # Run the installation verify_cli_command( [pixi, "install", "--manifest-path", manifest], - ExitCode.SUCCESS, ) # Validate creation of the pixi file with the hash @@ -147,7 +209,6 @@ def test_prefix_revalidation(pixi: Path, tmp_path: Path, dummy_channel_1: str) - # Run with revalidation to force reinstallation verify_cli_command( [pixi, "run", "--manifest-path", manifest, "--revalidate", "echo", "hello"], - ExitCode.SUCCESS, stdout_contains="hello", ) @@ -170,7 +231,6 @@ def test_run_with_activation(pixi: Path, tmp_path: Path) -> None: # Run the default task verify_cli_command( [pixi, "run", "--manifest-path", manifest, "task"], - ExitCode.SUCCESS, stdout_contains="test123", ) @@ -189,13 +249,11 @@ def test_run_with_activation(pixi: Path, tmp_path: Path) -> None: "experimental.use-environment-activation-cache", "true", ], - ExitCode.SUCCESS, ) # Run the default task and create cache verify_cli_command( [pixi, "run", "--manifest-path", manifest, "task"], - ExitCode.SUCCESS, stdout_contains="test123", ) @@ -207,7 +265,6 @@ def test_run_with_activation(pixi: Path, tmp_path: Path) -> None: verify_cli_command( [pixi, "run", "--manifest-path", manifest, "task"], - ExitCode.SUCCESS, # Contain overwritten value stdout_contains="test456", stdout_excludes="test123", @@ -216,6 +273,5 @@ def test_run_with_activation(pixi: Path, tmp_path: Path) -> None: # Ignore activation cache verify_cli_command( [pixi, "run", "--manifest-path", manifest, "--force-activate", "task", "-vvv"], - ExitCode.SUCCESS, stdout_contains="test123", )