From 708a612bdeabc51bd84186fd498d2f54e0205609 Mon Sep 17 00:00:00 2001 From: woutdenolf Date: Thu, 8 Oct 2026 14:07:07 +0200 Subject: [PATCH 1/2] ewoks install: add bootstrap shell scripts --- CHANGELOG.md | 1 + doc/conf.py | 1 + doc/howtoguides/requirements.rst | 2 + doc/tutorials/install.rst | 47 ++- doc/tutorials/install/conda.rst | 25 ++ doc/tutorials/install/pip_venv.rst | 25 ++ doc/tutorials/install/pixi.rst | 25 ++ doc/tutorials/install/poetry.rst | 25 ++ doc/tutorials/install/uv.rst | 25 ++ pyproject.toml | 1 + src/ewoks/__main__.py | 4 +- src/ewoks/_bootstrap/ewoks-install.ps1 | 330 ++++++++++++++++++ src/ewoks/_bootstrap/ewoks-install.sh | 269 ++++++++++++++ src/ewoks/_requirements/utils/base_manager.py | 11 +- .../_requirements/utils/metadata/models.py | 4 + .../tests/requirements/test_bootstrap.py | 143 ++++++++ src/ewoks/tests/requirements/test_managers.py | 2 + 17 files changed, 937 insertions(+), 3 deletions(-) create mode 100644 src/ewoks/_bootstrap/ewoks-install.ps1 create mode 100644 src/ewoks/_bootstrap/ewoks-install.sh create mode 100644 src/ewoks/tests/requirements/test_bootstrap.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 09d77d1..1fd1071 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Add `ewoks-install` scripts to install the environment of a workflow with only a package manager. - `ewoks install`: add `--package-manager-name` and `--package-manager-command` arguments. - `ewoks convert`: add `--package-manager-name` and `--package-manager-command` arguments. - Add `ewoks lint` CLI command to check if a workflow is conform to the Ewoks specification. diff --git a/doc/conf.py b/doc/conf.py index fcef75b..26cdd38 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -48,6 +48,7 @@ html_title = docstitle html_logo = "_static/logo.png" html_static_path = ["_static"] +html_extra_path = ["../src/ewoks/_bootstrap"] html_template_path = ["_templates"] html_css_files = ["custom.css"] diff --git a/doc/howtoguides/requirements.rst b/doc/howtoguides/requirements.rst index 74867f6..38c61ab 100644 --- a/doc/howtoguides/requirements.rst +++ b/doc/howtoguides/requirements.rst @@ -13,6 +13,8 @@ What is stored ``ewoks convert`` and ``ewoks execute -o convert_destination=...`` store +* ``ewoks``: the version of :term:`ewoks` that generated the requirements. The + :ref:`ewoks-install script ` installs this version to run ``ewoks install``. * ``python`` and ``system``: the python interpreter and the operating system. * ``distributions``: every installed python package with its version and, when it was not installed from the python package index, the git commit or the archive it came from. Any diff --git a/doc/tutorials/install.rst b/doc/tutorials/install.rst index 96dd6f7..6c6476c 100644 --- a/doc/tutorials/install.rst +++ b/doc/tutorials/install.rst @@ -40,6 +40,7 @@ Store the requirements .. code-block:: json { + "ewoks": {"version": "7.0.0"}, "python": {"version": "3.12.11", "implementation": "CPython", "...": "..."}, "system": {"system": "Linux", "machine": "x86_64", "...": "..."}, "distributions": [ @@ -53,6 +54,7 @@ Store the requirements } } +* ``ewoks`` is the version of :term:`ewoks` that generated the requirements. * ``distributions`` are the installed python packages. Any :term:`package manager` can recreate the environment from this list. * ``manager`` is the :term:`package manager` that generated the requirements together with @@ -69,13 +71,56 @@ and the command to remove it again Installed requirements for demo.json Python : ewoks_envs/demo/bin/python - Execute: ewoks execute --env ewoks_envs/demo demo.json + Execute: ewoks_envs/demo/bin/python -m ewoks execute demo.json Remove : rm -rf ewoks_envs/demo Without ``--yes`` you are asked to confirm after the packages have been listed. The walk-throughs use ``--env-root`` to create the environment in the working directory. Without it the environment is created where the :term:`package manager` creates named environments. +.. _install_bootstrap: + +Create the environment without ewoks +++++++++++++++++++++++++++++++++++++ + +``ewoks install`` needs a python environment with :term:`ewoks`. The ``ewoks-install`` script +only needs a :term:`package manager`: it installs the version of :term:`ewoks` that generated +the requirements in a bootstrap environment and runs ``ewoks install`` from there. All +arguments are passed to ``ewoks install``, except for the options of the script itself + +.. tabs:: + + .. group-tab:: Linux + + .. code-block:: bash + + curl -LsSf https://ewoks.readthedocs.io/en/stable/ewoks-install.sh | sh -s -- demo.json --package-manager-name uv + + .. group-tab:: macOS + + .. code-block:: bash + + curl -LsSf https://ewoks.readthedocs.io/en/stable/ewoks-install.sh | sh -s -- demo.json --package-manager-name uv + + .. group-tab:: Windows + + .. code-block:: powershell + + & ([scriptblock]::Create((irm https://ewoks.readthedocs.io/en/stable/ewoks-install.ps1))) demo.json --package-manager-name uv + +* ``--package-manager-name`` and ``--package-manager-command`` select the + :term:`package manager` that creates the bootstrap environment. Without them it is the first + one that is available of uv, pixi, conda, poetry and pip-venv. +* ``--print-command`` prints the ``ewoks install`` command instead of running it. +* ``--ewoks-requirement`` installs another version of :term:`ewoks`, for example + ``--ewoks-requirement "ewoks>=7"``. The latest version is installed when the requirements do + not provide one. + +The script keeps the bootstrap environment in ``~/.ewoks/bootstrap`` (``EWOKS_BOOTSTRAP_DIR``) +and uses it again when it installs another :term:`workflow` whose requirements were generated +by the same version of :term:`ewoks`. The bootstrap environment only runs ``ewoks install``: +it is not the environment of the :term:`workflow`. + Execute the workflow -------------------- diff --git a/doc/tutorials/install/conda.rst b/doc/tutorials/install/conda.rst index ee665bf..bfa5283 100644 --- a/doc/tutorials/install/conda.rst +++ b/doc/tutorials/install/conda.rst @@ -124,6 +124,31 @@ The environment of the :term:`workflow` is a conda environment in ``ewoks_envs/d ``--env-root`` it is created in the first environment directory of conda, where ``conda activate demo`` finds it. +Alternatively, the :ref:`ewoks-install script ` recreates the +environment without an environment with :term:`ewoks` + +.. tabs:: + + .. group-tab:: Linux + + .. code-block:: bash + + curl -LsSf https://ewoks.readthedocs.io/en/stable/ewoks-install.sh | sh -s -- demo.json --package-manager-name conda --yes --env-root ewoks_envs + + .. group-tab:: macOS + + .. code-block:: bash + + curl -LsSf https://ewoks.readthedocs.io/en/stable/ewoks-install.sh | sh -s -- demo.json --package-manager-name conda --yes --env-root ewoks_envs + + .. group-tab:: Windows + + .. code-block:: powershell + + & ([scriptblock]::Create((irm https://ewoks.readthedocs.io/en/stable/ewoks-install.ps1))) demo.json --package-manager-name conda --yes --env-root ewoks_envs + +Execute the workflow with the python interpreter of the environment it prints. + Execute the workflow -------------------- diff --git a/doc/tutorials/install/pip_venv.rst b/doc/tutorials/install/pip_venv.rst index 2ee3857..b202f48 100644 --- a/doc/tutorials/install/pip_venv.rst +++ b/doc/tutorials/install/pip_venv.rst @@ -83,6 +83,31 @@ Only ``ewoks`` itself is needed to recreate the environment of ``demo.json`` The environment of the :term:`workflow` is a virtual environment in ``ewoks_envs/demo``. +Alternatively, the :ref:`ewoks-install script ` recreates the +environment without an environment with :term:`ewoks` + +.. tabs:: + + .. group-tab:: Linux + + .. code-block:: bash + + curl -LsSf https://ewoks.readthedocs.io/en/stable/ewoks-install.sh | sh -s -- demo.json --package-manager-name pip-venv --yes --env-root ewoks_envs + + .. group-tab:: macOS + + .. code-block:: bash + + curl -LsSf https://ewoks.readthedocs.io/en/stable/ewoks-install.sh | sh -s -- demo.json --package-manager-name pip-venv --yes --env-root ewoks_envs + + .. group-tab:: Windows + + .. code-block:: powershell + + & ([scriptblock]::Create((irm https://ewoks.readthedocs.io/en/stable/ewoks-install.ps1))) demo.json --package-manager-name pip-venv --yes --env-root ewoks_envs + +Execute the workflow with the python interpreter of the environment it prints. + Execute the workflow -------------------- diff --git a/doc/tutorials/install/pixi.rst b/doc/tutorials/install/pixi.rst index 3a420dd..a15bf9f 100644 --- a/doc/tutorials/install/pixi.rst +++ b/doc/tutorials/install/pixi.rst @@ -119,6 +119,31 @@ Only ``ewoks`` itself is needed to recreate the environment of ``demo.json`` The environment of the :term:`workflow` is a pixi workspace in ``ewoks_envs/demo`` with the environment in its ``.pixi/envs/default`` directory. +Alternatively, the :ref:`ewoks-install script ` recreates the +environment without an environment with :term:`ewoks` + +.. tabs:: + + .. group-tab:: Linux + + .. code-block:: bash + + curl -LsSf https://ewoks.readthedocs.io/en/stable/ewoks-install.sh | sh -s -- demo.json --package-manager-name pixi --yes --env-root ewoks_envs + + .. group-tab:: macOS + + .. code-block:: bash + + curl -LsSf https://ewoks.readthedocs.io/en/stable/ewoks-install.sh | sh -s -- demo.json --package-manager-name pixi --yes --env-root ewoks_envs + + .. group-tab:: Windows + + .. code-block:: powershell + + & ([scriptblock]::Create((irm https://ewoks.readthedocs.io/en/stable/ewoks-install.ps1))) demo.json --package-manager-name pixi --yes --env-root ewoks_envs + +Execute the workflow with the python interpreter of the environment it prints. + Execute the workflow -------------------- diff --git a/doc/tutorials/install/poetry.rst b/doc/tutorials/install/poetry.rst index f4360bd..c61fe63 100644 --- a/doc/tutorials/install/poetry.rst +++ b/doc/tutorials/install/poetry.rst @@ -106,6 +106,31 @@ Only ``ewoks`` itself is needed to recreate the environment of ``demo.json`` The environment of the :term:`workflow` is a poetry project in ``ewoks_envs/demo`` with the virtual environment in its ``.venv`` directory. +Alternatively, the :ref:`ewoks-install script ` recreates the +environment without an environment with :term:`ewoks` + +.. tabs:: + + .. group-tab:: Linux + + .. code-block:: bash + + curl -LsSf https://ewoks.readthedocs.io/en/stable/ewoks-install.sh | sh -s -- demo.json --package-manager-name poetry --yes --env-root ewoks_envs + + .. group-tab:: macOS + + .. code-block:: bash + + curl -LsSf https://ewoks.readthedocs.io/en/stable/ewoks-install.sh | sh -s -- demo.json --package-manager-name poetry --yes --env-root ewoks_envs + + .. group-tab:: Windows + + .. code-block:: powershell + + & ([scriptblock]::Create((irm https://ewoks.readthedocs.io/en/stable/ewoks-install.ps1))) demo.json --package-manager-name poetry --yes --env-root ewoks_envs + +Execute the workflow with the python interpreter of the environment it prints. + Execute the workflow -------------------- diff --git a/doc/tutorials/install/uv.rst b/doc/tutorials/install/uv.rst index 11db413..81691e8 100644 --- a/doc/tutorials/install/uv.rst +++ b/doc/tutorials/install/uv.rst @@ -113,6 +113,31 @@ Only ``ewoks`` itself is needed to recreate the environment of ``demo.json`` The environment of the :term:`workflow` is a uv project in ``ewoks_envs/demo`` with the virtual environment in its ``.venv`` directory. +Alternatively, the :ref:`ewoks-install script ` recreates the +environment without an environment with :term:`ewoks` + +.. tabs:: + + .. group-tab:: Linux + + .. code-block:: bash + + curl -LsSf https://ewoks.readthedocs.io/en/stable/ewoks-install.sh | sh -s -- demo.json --package-manager-name uv --yes --env-root ewoks_envs + + .. group-tab:: macOS + + .. code-block:: bash + + curl -LsSf https://ewoks.readthedocs.io/en/stable/ewoks-install.sh | sh -s -- demo.json --package-manager-name uv --yes --env-root ewoks_envs + + .. group-tab:: Windows + + .. code-block:: powershell + + & ([scriptblock]::Create((irm https://ewoks.readthedocs.io/en/stable/ewoks-install.ps1))) demo.json --package-manager-name uv --yes --env-root ewoks_envs + +Execute the workflow with the python interpreter of the environment it prints. + Execute the workflow -------------------- diff --git a/pyproject.toml b/pyproject.toml index cebf15e..6b60671 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -80,6 +80,7 @@ ewoks = "ewoks.__main__:main" [tool.setuptools.package-data] "ewoks.tests.notebooks" = ["*.ipynb"] +"ewoks._bootstrap" = ["*.sh", "*.ps1"] [tool.ruff.lint] select = [ diff --git a/src/ewoks/__main__.py b/src/ewoks/__main__.py index 1383a56..7d2e512 100644 --- a/src/ewoks/__main__.py +++ b/src/ewoks/__main__.py @@ -211,7 +211,9 @@ def command_install( location = environment.location print(f" Python : {environment.python}") if environment.distribution_version("ewoks"): - print(f" Execute: ewoks execute --env {location} {workflow}") + print( + f" Execute: {environment.python} -m ewoks execute {workflow}" + ) else: print( " Execute: the environment has no ewoks to execute the " diff --git a/src/ewoks/_bootstrap/ewoks-install.ps1 b/src/ewoks/_bootstrap/ewoks-install.ps1 new file mode 100644 index 0000000..5b51083 --- /dev/null +++ b/src/ewoks/_bootstrap/ewoks-install.ps1 @@ -0,0 +1,330 @@ +# Install the python environment of an ewoks workflow with nothing but a package +# manager. For example +# +# & ([scriptblock]::Create((irm https://ewoks.readthedocs.io/en/stable/ewoks-install.ps1))) demo.json --package-manager-name uv +# +# Errors are thrown instead of calling `exit`, which would close the PowerShell +# session that runs the script block. + +$ErrorActionPreference = "Stop" + +$usage = @' +Usage: ewoks-install.ps1 [OPTIONS] WORKFLOW [ARGS ...] + +Install the python environment of an ewoks workflow with nothing but a package +manager. The ewoks version that generated the workflow requirements is +installed in a bootstrap environment, which then runs + + ewoks install WORKFLOW ARGS + +See 'ewoks install --help' for ARGS. The package manager that creates the +bootstrap environment is selected with '--package-manager-name' and +'--package-manager-command', which are passed to 'ewoks install' as well. +Without them it is the first available of uv, pixi, conda, poetry and pip-venv. + +Options: + --print-command Print the 'ewoks install' command instead of + running it. + --ewoks-requirement REQ Install REQ instead of the ewoks version of the + workflow, for example 'ewoks>=7' or a wheel file. + -h, --help Show this help. + +Environment variables: + EWOKS_BOOTSTRAP_DIR Directory of the bootstrap environments. + Default: ~/.ewoks/bootstrap +'@ + +$onWindows = ($PSVersionTable.PSEdition -eq "Desktop") -or $IsWindows + +function Write-Info([string]$message) { + [Console]::Error.WriteLine("ewoks-install: $message") +} + +# Run a native command with its output on the console instead of in the pipeline +function Invoke-Native([string[]]$command) { + $arguments = @() + if ($command.Count -gt 1) { + $arguments = $command[1..($command.Count - 1)] + } + & $command[0] @arguments | Out-Host + if ($LASTEXITCODE -ne 0) { + throw "command failed: $($command -join ' ')" + } +} + +function Test-Command([string]$name) { + return [bool](Get-Command $name -CommandType Application -ErrorAction SilentlyContinue) +} + +# Python interpreter with this major.minor version, or the default one +function Find-Python([string]$minor) { + if ($onWindows) { + if (Test-Command "py") { + if ($minor) { + & py "-$minor" -c "pass" 2>$null | Out-Null + if ($LASTEXITCODE -eq 0) { + return @("py", "-$minor") + } + } + return @("py", "-3") + } + } elseif ($minor -and (Test-Command "python$minor")) { + return @("python$minor") + } elseif (Test-Command "python3") { + return @("python3") + } + if (Test-Command "python") { + return @("python") + } + throw "no python interpreter found" +} + +function Get-PythonMinor([string[]]$python) { + $arguments = @() + if ($python.Count -gt 1) { + $arguments = $python[1..($python.Count - 1)] + } + $minor = & $python[0] @arguments -c 'import sys; print(''%d.%d'' % sys.version_info[:2])' + if ($LASTEXITCODE -ne 0) { + throw "cannot run $($python -join ' ')" + } + return "$minor".Trim() +} + +# Argument quoted for PowerShell when needed +function Format-Argument([string]$argument) { + if ($argument -match '^[A-Za-z0-9_./=:@%+,\\-]+$') { + return $argument + } + return "'" + $argument.Replace("'", "''") + "'" +} + +function Invoke-EwoksInstall([object[]]$arguments) { + # --- Arguments: the bootstrap options are removed, the others are kept --- + + $printCommand = $false + $requirement = "" + $manager = "" + $managerCommand = "" + $workflow = "" + $forwarded = @() + $i = 0 + while ($i -lt $arguments.Count) { + $arg = [string]$arguments[$i] + $i++ + if ($arg -in @("-h", "--help")) { + Write-Output $usage + return + } + if ($arg -eq "--print-command") { + $printCommand = $true + continue + } + if ($arg -eq "--ewoks-requirement") { + if ($i -ge $arguments.Count) { throw "$arg requires a value" } + $requirement = [string]$arguments[$i] + $i++ + continue + } + if ($arg.StartsWith("--ewoks-requirement=")) { + $requirement = $arg.Substring($arg.IndexOf("=") + 1) + continue + } + $forwarded += $arg + if ($arg -in @("--package-manager-name", "--package-manager-command")) { + if ($i -ge $arguments.Count) { throw "$arg requires a value" } + $value = [string]$arguments[$i] + $i++ + $forwarded += $value + if ($arg -eq "--package-manager-name") { + $manager = $value + } else { + $managerCommand = $value + } + } elseif ($arg.StartsWith("--package-manager-name=")) { + $manager = $arg.Substring($arg.IndexOf("=") + 1) + } elseif ($arg.StartsWith("--package-manager-command=")) { + $managerCommand = $arg.Substring($arg.IndexOf("=") + 1) + } elseif (-not $arg.StartsWith("-") -and -not $workflow -and (Test-Path -LiteralPath $arg -PathType Leaf)) { + $workflow = $arg + } + } + + if (-not $workflow) { + Write-Info $usage + throw "provide the file of the workflow" + } + + # --- Package manager --- + + $manager = $manager.ToLower() + if (-not $manager) { + if ($managerCommand) { + throw "--package-manager-command requires --package-manager-name" + } + foreach ($name in @("uv", "pixi", "conda", "poetry")) { + if (Test-Command $name) { + $manager = $name + break + } + } + if (-not $manager) { + $manager = "pip-venv" + } + } + + $command = @() + if ($managerCommand) { + $command = @($managerCommand -split '\s+' | Where-Object { $_ }) + } + if ($manager -in @("uv", "pixi", "conda", "poetry")) { + if (-not $command) { $command = @($manager) } + } elseif ($manager -ne "pip-venv") { + throw "package manager '$manager' is not supported" + } + + # --- Ewoks and python versions of the workflow requirements --- + + $version = "" + $pythonVersion = "" + try { + $requirements = (Get-Content -LiteralPath $workflow -Raw | ConvertFrom-Json).graph.requirements + if ($requirements.ewoks.version) { $version = [string]$requirements.ewoks.version } + if ($requirements.python.version) { $pythonVersion = [string]$requirements.python.version } + } catch { + Write-Info "cannot read the requirements of '$workflow'" + } + $pythonMinor = "" + if ($pythonVersion -match '^(\d+\.\d+)') { + $pythonMinor = $Matches[1] + } + + if ($requirement) { + $sha = [System.Security.Cryptography.SHA256]::Create() + $hash = $sha.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($requirement)) + $tag = "custom-" + (($hash[0..4] | ForEach-Object { $_.ToString("x2") }) -join "") + } elseif ($version) { + $tag = $version + $requirement = "ewoks==$version" + } else { + Write-Info "the workflow does not provide the ewoks version: using the latest one" + $tag = "latest" + $requirement = "ewoks>=7" + } + + # pip-venv and poetry cannot provide a python version: the interpreter decides + $basePython = @() + if ($manager -eq "pip-venv") { + if (-not $command) { $command = @(Find-Python $pythonMinor) } + $pythonMinor = Get-PythonMinor $command + } elseif ($manager -eq "poetry") { + $basePython = @(Find-Python $pythonMinor) + $pythonMinor = Get-PythonMinor $basePython + } + + $root = $env:EWOKS_BOOTSTRAP_DIR + if (-not $root) { + $root = Join-Path (Join-Path $HOME ".ewoks") "bootstrap" + } + $name = "ewoks-$tag" + if ($pythonMinor) { + $name = "$name-python$pythonMinor" + } + $location = Join-Path (Join-Path $root $manager) $name + + # --- Bootstrap environment --- + + if ($onWindows) { + $venvPython = Join-Path "Scripts" "python.exe" + $prefixPython = "python.exe" + } else { + $venvPython = Join-Path "bin" "python" + $prefixPython = $venvPython + } + switch ($manager) { + "pixi" { $python = Join-Path $location (Join-Path ".pixi/envs/default" $prefixPython) } + "conda" { $python = Join-Path $location $prefixPython } + "poetry" { $python = Join-Path $location (Join-Path ".venv" $venvPython) } + default { $python = Join-Path $location $venvPython } + } + + $pythonSpec = "python" + if ($pythonMinor) { + $pythonSpec = "python=$pythonMinor" + } + if (-not (Test-Path -LiteralPath $python -PathType Leaf)) { + Write-Info "create the bootstrap environment $location" + # A previous attempt failed + if (Test-Path -LiteralPath $location) { + Remove-Item -LiteralPath $location -Recurse -Force + } + New-Item -ItemType Directory -Path $location -Force | Out-Null + switch ($manager) { + "uv" { + $create = $command + @("venv", "--quiet") + if ($pythonMinor) { $create += @("--python", $pythonMinor) } + Invoke-Native ($create + @($location)) + } + "pixi" { + Invoke-Native ($command + @("init", $location)) + Invoke-Native ($command + @("add", "--manifest-path", (Join-Path $location "pixi.toml"), $pythonSpec, "pip")) + } + "conda" { + Invoke-Native ($command + @("create", "--yes", "--quiet", "--prefix", $location, $pythonSpec, "pip")) + } + "poetry" { + $pyproject = "[tool.poetry]`npackage-mode = false`n`n[tool.poetry.dependencies]`npython = `"*`"`n" + [System.IO.File]::WriteAllText((Join-Path $location "pyproject.toml"), $pyproject) + # Poetry uses an active virtual or conda environment instead of the one of the project + $virtualEnv = $env:VIRTUAL_ENV + $condaPrefix = $env:CONDA_PREFIX + $inProject = $env:POETRY_VIRTUALENVS_IN_PROJECT + try { + Remove-Item Env:VIRTUAL_ENV, Env:CONDA_PREFIX -ErrorAction SilentlyContinue + $env:POETRY_VIRTUALENVS_IN_PROJECT = "true" + $interpreter = & $basePython[0] @($basePython | Select-Object -Skip 1) -c "import sys; print(sys.executable)" + Invoke-Native ($command + @("-C", $location, "env", "use", "$interpreter".Trim())) + } finally { + $env:VIRTUAL_ENV = $virtualEnv + $env:CONDA_PREFIX = $condaPrefix + $env:POETRY_VIRTUALENVS_IN_PROJECT = $inProject + } + } + "pip-venv" { + Invoke-Native ($command + @("-m", "venv", $location)) + } + } + } + + $installed = "" + try { + $installed = "$(& $python -c 'import importlib.metadata as m; print(m.version(''ewoks''))' 2>$null)".Trim() + } catch { + $installed = "" + } + + if (($tag -ne $version) -or ($installed -ne $version)) { + Write-Info "install $requirement in $location" + try { + if ($manager -eq "uv") { + Invoke-Native ($command + @("pip", "install", "--quiet", "--upgrade", "--python", $python, $requirement)) + } else { + Invoke-Native @($python, "-m", "pip", "install", "--quiet", "--disable-pip-version-check", "--upgrade", $requirement) + } + } catch { + throw "cannot install $requirement (see --ewoks-requirement)" + } + } + + # --- ewoks install --- + + $install = @("-m", "ewoks", "install") + $forwarded + if ($printCommand) { + $formatted = @($install | ForEach-Object { Format-Argument $_ }) + Write-Output ("& " + "'" + $python.Replace("'", "''") + "' " + ($formatted -join " ")) + } else { + & $python @install + } +} + +Invoke-EwoksInstall $args diff --git a/src/ewoks/_bootstrap/ewoks-install.sh b/src/ewoks/_bootstrap/ewoks-install.sh new file mode 100644 index 0000000..59f4eef --- /dev/null +++ b/src/ewoks/_bootstrap/ewoks-install.sh @@ -0,0 +1,269 @@ +#!/bin/sh +# Install the python environment of an ewoks workflow with nothing but a package +# manager. For example +# +# curl -LsSf https://ewoks.readthedocs.io/en/stable/ewoks-install.sh | sh -s -- demo.json --package-manager-name uv +# +set -eu + +usage() { + cat <<'EOF' +Usage: ewoks-install.sh [OPTIONS] WORKFLOW [ARGS ...] + +Install the python environment of an ewoks workflow with nothing but a package +manager. The ewoks version that generated the workflow requirements is +installed in a bootstrap environment, which then runs + + ewoks install WORKFLOW ARGS + +See 'ewoks install --help' for ARGS. The package manager that creates the +bootstrap environment is selected with '--package-manager-name' and +'--package-manager-command', which are passed to 'ewoks install' as well. +Without them it is the first available of uv, pixi, conda, poetry and pip-venv. + +Options: + --print-command Print the 'ewoks install' command instead of + running it. + --ewoks-requirement REQ Install REQ instead of the ewoks version of the + workflow, for example 'ewoks>=7' or a wheel file. + -h, --help Show this help. + +Environment variables: + EWOKS_BOOTSTRAP_DIR Directory of the bootstrap environments. + Default: ~/.ewoks/bootstrap +EOF +} + +say() { + printf 'ewoks-install: %s\n' "$*" >&2 +} + +die() { + say "error: $*" + exit 1 +} + +# Version of an object in a JSON file, for example `"ewoks": {"version": "7.0.0"}` +json_version() { + tr -d '\n\r' <"$1" | sed -n "s/.*\"$2\"[[:space:]]*:[[:space:]]*{[[:space:]]*\"version\"[[:space:]]*:[[:space:]]*\"\([^\"]*\)\".*/\1/p" +} + +# Argument quoted for the shell when needed +quote() { + case $1 in + '' | *[!A-Za-z0-9_./=:@%+,-]*) + printf "'%s'" "$(printf '%s' "$1" | sed "s/'/'\\\\''/g")" + ;; + *) + printf '%s' "$1" + ;; + esac +} + +# Python interpreter with this major.minor version, or the default one +find_python() { + if [ -n "$1" ] && command -v "python$1" >/dev/null 2>&1; then + printf 'python%s' "$1" + elif command -v python3 >/dev/null 2>&1; then + printf 'python3' + else + die "no python interpreter found" + fi +} + +# The whole script is parsed before it runs, which matters with `curl | sh` +main() { + # --- Arguments: the bootstrap options are removed, the others are kept --- + + print_command=0 + requirement="" + manager="" + manager_command="" + workflow="" + remaining=$# + while [ "$remaining" -gt 0 ]; do + arg=$1 + shift + remaining=$((remaining - 1)) + case $arg in + -h | --help) + usage + exit 0 + ;; + --print-command) + print_command=1 + continue + ;; + --ewoks-requirement) + [ "$remaining" -gt 0 ] || die "$arg requires a value" + requirement=$1 + shift + remaining=$((remaining - 1)) + continue + ;; + --ewoks-requirement=*) + requirement=${arg#*=} + continue + ;; + esac + set -- "$@" "$arg" + case $arg in + --package-manager-name | --package-manager-command) + [ "$remaining" -gt 0 ] || die "$arg requires a value" + value=$1 + shift + remaining=$((remaining - 1)) + set -- "$@" "$value" + if [ "$arg" = --package-manager-name ]; then + manager=$value + else + manager_command=$value + fi + ;; + --package-manager-name=*) + manager=${arg#*=} + ;; + --package-manager-command=*) + manager_command=${arg#*=} + ;; + -*) ;; + *) + if [ -z "$workflow" ] && [ -f "$arg" ]; then + workflow=$arg + fi + ;; + esac + done + + if [ -z "$workflow" ]; then + usage >&2 + die "provide the file of the workflow" + fi + + # --- Package manager --- + + manager=$(printf '%s' "$manager" | tr '[:upper:]' '[:lower:]') + if [ -z "$manager" ]; then + [ -z "$manager_command" ] || die "--package-manager-command requires --package-manager-name" + for name in uv pixi conda poetry; do + if command -v "$name" >/dev/null 2>&1; then + manager=$name + break + fi + done + manager=${manager:-pip-venv} + fi + + case $manager in + uv | pixi | conda | poetry) cmd=${manager_command:-$manager} ;; + pip-venv) cmd=${manager_command:-} ;; + *) die "package manager '$manager' is not supported" ;; + esac + + # --- Ewoks and python versions of the workflow requirements --- + + version=$(json_version "$workflow" ewoks) + python_version=$(json_version "$workflow" python) + python_minor=$(printf '%s' "$python_version" | sed -n 's/^\([0-9]*\.[0-9]*\).*/\1/p') + + if [ -n "$requirement" ]; then + tag=custom-$(printf '%s' "$requirement" | cksum | cut -d ' ' -f 1) + elif [ -n "$version" ]; then + tag=$version + requirement="ewoks==$version" + else + say "the workflow does not provide the ewoks version: using the latest one" + tag=latest + requirement="ewoks>=7" + fi + + # pip-venv and poetry cannot provide a python version: the interpreter decides + case $manager in + pip-venv) + if [ -z "$cmd" ]; then + cmd=$(find_python "$python_minor") + fi + # shellcheck disable=SC2086 + python_minor=$($cmd -c 'import sys; print("%d.%d" % sys.version_info[:2])') + ;; + poetry) + base_python=$(find_python "$python_minor") + python_minor=$("$base_python" -c 'import sys; print("%d.%d" % sys.version_info[:2])') + ;; + esac + + root=${EWOKS_BOOTSTRAP_DIR:-$HOME/.ewoks/bootstrap} + location=$root/$manager/ewoks-$tag${python_minor:+-python$python_minor} + + # --- Bootstrap environment --- + + case $manager in + pixi) python=$location/.pixi/envs/default/bin/python ;; + poetry) python=$location/.venv/bin/python ;; + *) python=$location/bin/python ;; + esac + + # shellcheck disable=SC2086 + if [ ! -x "$python" ]; then + say "create the bootstrap environment $location" + # A previous attempt failed + rm -rf "$location" + mkdir -p "$location" + case $manager in + uv) + $cmd venv --quiet ${python_minor:+--python "$python_minor"} "$location" >&2 + ;; + pixi) + $cmd init "$location" >&2 + $cmd add --manifest-path "$location/pixi.toml" "python${python_minor:+=$python_minor}" pip >&2 + ;; + conda) + $cmd create --yes --quiet --prefix "$location" "python${python_minor:+=$python_minor}" pip >&2 + ;; + poetry) + printf '[tool.poetry]\npackage-mode = false\n\n[tool.poetry.dependencies]\npython = "*"\n' >"$location/pyproject.toml" + # Poetry uses an active virtual or conda environment instead of the one of the project + ( + unset VIRTUAL_ENV CONDA_PREFIX + POETRY_VIRTUALENVS_IN_PROJECT=true $cmd -C "$location" env use "$base_python" >&2 + ) + ;; + pip-venv) + $cmd -m venv "$location" >&2 + ;; + esac + fi + + installed=$("$python" -c 'import importlib.metadata as m; print(m.version("ewoks"))' 2>/dev/null || true) + + # shellcheck disable=SC2086 + if [ "$tag" != "$version" ] || [ "$installed" != "$version" ]; then + say "install $requirement in $location" + if [ "$manager" = uv ]; then + $cmd pip install --quiet --upgrade --python "$python" "$requirement" >&2 || + die "cannot install $requirement (see --ewoks-requirement)" + else + "$python" -m pip install --quiet --disable-pip-version-check --upgrade "$requirement" >&2 || + die "cannot install $requirement (see --ewoks-requirement)" + fi + fi + + # --- ewoks install --- + + set -- "$python" -m ewoks install "$@" + + if [ "$print_command" = 1 ]; then + command="" + for arg in "$@"; do + command="$command $(quote "$arg")" + done + printf '%s\n' "${command# }" + elif [ ! -t 0 ] && (exec /dev/null; then + # Confirmation prompts do not read stdin, which is this script with `curl | sh` + exec "$@" BaseRequirements: ) manager = dict(name=self.NAME, version=manager_version, files=files) - return self.REQUIREMENTS_MODEL(manager=manager, **metadata) + ewoks = dict(version=importlib.metadata.version("ewoks")) + return self.REQUIREMENTS_MODEL(ewoks=ewoks, manager=manager, **metadata) def environment(self, location: Union[str, Path]) -> Environment: """Environment the manager creates at this location.""" diff --git a/src/ewoks/_requirements/utils/metadata/models.py b/src/ewoks/_requirements/utils/metadata/models.py index 72bfd66..4519139 100644 --- a/src/ewoks/_requirements/utils/metadata/models.py +++ b/src/ewoks/_requirements/utils/metadata/models.py @@ -5,6 +5,10 @@ from pydantic import Field +class EwoksInfo(BaseModel): + version: str = Field(description="Ewoks version.", examples=["7.0.0"]) + + class SystemInfo(BaseModel): system: str = Field(description="Operating system name.", examples=["Linux"]) release: str = Field( diff --git a/src/ewoks/tests/requirements/test_bootstrap.py b/src/ewoks/tests/requirements/test_bootstrap.py new file mode 100644 index 0000000..1d19899 --- /dev/null +++ b/src/ewoks/tests/requirements/test_bootstrap.py @@ -0,0 +1,143 @@ +"""Tests of the scripts that run `ewoks install` with nothing but a package manager. + +The bootstrap environment is prepared in advance so the scripts do not install +anything: its python interpreter runs the ewoks under test. +""" + +import importlib.metadata +import json +import os +import shutil +import subprocess +import sys +from pathlib import Path +from typing import Dict +from typing import List + +import pytest + +from .utils import PYTHON_MINOR_VERSION +from .utils import PYTHON_VERSION +from .utils import manager_requirements + +_SCRIPTS = Path(__file__).parents[2] / "_bootstrap" +_EWOKS_VERSION = importlib.metadata.version("ewoks") + + +@pytest.fixture(params=["sh", "powershell"]) +def bootstrap_script(request) -> List[str]: + """Command that runs a bootstrap script.""" + if sys.platform == "win32": + pytest.skip("the bootstrap environment of the tests needs a POSIX shell") + if request.param == "sh": + return ["sh", str(_SCRIPTS / "ewoks-install.sh")] + pwsh = shutil.which("pwsh") + if pwsh is None: + pytest.skip("PowerShell is not installed") + return [pwsh, "-NoProfile", "-File", str(_SCRIPTS / "ewoks-install.ps1")] + + +@pytest.fixture +def bootstrap_dir(tmp_path) -> Path: + """Directory of the bootstrap environments with the environment of the + ewoks under test for pip-venv.""" + root = tmp_path / "bootstrap" + location = ( + root / "pip-venv" / f"ewoks-{_EWOKS_VERSION}-python{PYTHON_MINOR_VERSION}" + ) + python = location / "bin" / "python" + python.parent.mkdir(parents=True) + with open(python, "w", encoding="utf-8") as fh: + fh.write(f'#!/bin/sh\nexec "{sys.executable}" "$@"\n') + python.chmod(0o755) + return root + + +def test_print_command(bootstrap_script, bootstrap_dir, tmp_path): + """The bootstrap environment of the ewoks version of the workflow runs + `ewoks install` with the arguments that are not for the script.""" + workflow = _workflow(tmp_path) + arguments = [str(workflow), *_pip_venv_arguments(), "--yes"] + + result = _run(bootstrap_script, ["--print-command", *arguments], bootstrap_dir) + + assert result.returncode == 0, result.stderr + command = result.stdout.strip() + python = ( + bootstrap_dir + / "pip-venv" + / f"ewoks-{_EWOKS_VERSION}-python{PYTHON_MINOR_VERSION}" + / "bin" + / "python" + ) + assert str(python) in command + assert command.endswith(f"-m ewoks install {' '.join(arguments)}") + + +def test_run_ewoks_install(bootstrap_script, bootstrap_dir, tmp_path): + """`ewoks install` runs in the bootstrap environment.""" + workflow = _workflow(tmp_path) + arguments = [str(workflow), *_pip_venv_arguments(), "--yes", "--in-place"] + + result = _run(bootstrap_script, arguments, bootstrap_dir) + + # The requirements of the workflow have nothing to install + assert "No distributions provided to install" in result.stderr + assert f"Install failed for {workflow}" in result.stdout + + +def test_no_workflow(bootstrap_script, bootstrap_dir): + result = _run(bootstrap_script, ["--test", "demo"], bootstrap_dir) + + assert result.returncode != 0 + assert "provide the file of the workflow" in result.stderr + + +def test_unsupported_manager(bootstrap_script, bootstrap_dir, tmp_path): + workflow = _workflow(tmp_path) + + result = _run( + bootstrap_script, + [str(workflow), "--package-manager-name", "unknown"], + bootstrap_dir, + ) + + assert result.returncode != 0 + assert "package manager 'unknown' is not supported" in result.stderr + + +def _workflow(tmp_path: Path) -> Path: + requirements = manager_requirements("pip-venv", python_version=PYTHON_VERSION) + requirements["ewoks"] = {"version": _EWOKS_VERSION} + graph = { + "graph": {"id": "bootstrap", "requirements": requirements}, + "nodes": [], + "links": [], + } + filename = tmp_path / "workflow.json" + with open(filename, "w", encoding="utf-8") as fh: + json.dump(graph, fh, indent=2) + return filename + + +def _pip_venv_arguments() -> List[str]: + return [ + "--package-manager-name", + "pip-venv", + "--package-manager-command", + sys.executable, + ] + + +def _run( + script: List[str], arguments: List[str], bootstrap_dir: Path +) -> "subprocess.CompletedProcess[str]": + env: Dict[str, str] = {**os.environ, "EWOKS_BOOTSTRAP_DIR": str(bootstrap_dir)} + return subprocess.run( # noqa: S603 - Script under test + [*script, *arguments], + capture_output=True, + text=True, + env=env, + stdin=subprocess.DEVNULL, + timeout=300, + ) diff --git a/src/ewoks/tests/requirements/test_managers.py b/src/ewoks/tests/requirements/test_managers.py index 402935c..07d55b5 100644 --- a/src/ewoks/tests/requirements/test_managers.py +++ b/src/ewoks/tests/requirements/test_managers.py @@ -1,5 +1,6 @@ """Tests that apply to all package managers.""" +import importlib.metadata from typing import Iterator import pytest @@ -83,6 +84,7 @@ def test_gather_requirements(manager, manager_case, monkeypatch): requirements = manager.gather_requirements() assert isinstance(requirements, manager_case.MANAGER_CLS.REQUIREMENTS_MODEL) + assert requirements.ewoks.version == importlib.metadata.version("ewoks") assert requirements.manager.name == manager_case.NAME assert requirements.manager.version assert requirements.python.version == PYTHON_VERSION From 8839a783fe6f4926736384a4cb4c6646b6adbc64 Mon Sep 17 00:00:00 2001 From: woutdenolf Date: Thu, 8 Oct 2026 14:22:08 +0200 Subject: [PATCH 2/2] docs: make doc URL dynamic --- doc/conf.py | 18 ++++++++++++++++++ doc/explanations/task_input_priority.rst | 2 +- doc/reference/cli.rst | 2 ++ doc/tutorials/install.rst | 6 +++--- doc/tutorials/install/conda.rst | 10 ++++++---- doc/tutorials/install/pip_venv.rst | 10 ++++++---- doc/tutorials/install/pixi.rst | 10 ++++++---- doc/tutorials/install/poetry.rst | 10 ++++++---- doc/tutorials/install/uv.rst | 10 ++++++---- 9 files changed, 54 insertions(+), 24 deletions(-) diff --git a/doc/conf.py b/doc/conf.py index 26cdd38..6748682 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -3,6 +3,7 @@ import importlib.metadata +import os release = importlib.metadata.version("ewoks") @@ -77,3 +78,20 @@ "footer_start": ["copyright"], "footer_end": ["footer_end"], } + +# Root of the documentation version that is built: on Read the Docs, for example +# .../en/stable, or on GitLab Pages +_DOCS_URL = ( + os.environ.get("READTHEDOCS_CANONICAL_URL") + or os.environ.get("CI_PAGES_URL") + or "https://ewoks.readthedocs.io/en/latest" +).rstrip("/") + + +def _substitute_docs_url(app, docname, source): + # Substitutions are not supported in code blocks + source[0] = source[0].replace("|docs_url|", _DOCS_URL) + + +def setup(app): + app.connect("source-read", _substitute_docs_url) diff --git a/doc/explanations/task_input_priority.rst b/doc/explanations/task_input_priority.rst index f003e44..d08274e 100644 --- a/doc/explanations/task_input_priority.rst +++ b/doc/explanations/task_input_priority.rst @@ -7,7 +7,7 @@ Details A :term:`node ` in a :term:`workflow` can get inputs from three different sources: 1. Via the ``data_mapping`` :term:`link ` attribute of an incoming :term:`link ` (see `Link attributes `_) -2. Via the ``parameters`` CLI argument (or ``inputs`` for Python) when executing/submitting the :term:`workflow` (see `ewoks execute reference `_) +2. Via the ``parameters`` CLI argument (or ``inputs`` for Python) when executing/submitting the :term:`workflow` (see :ref:`ewoks execute reference `) 3. Via the ``default_inputs`` :term:`node ` attribute of the :term:`node ` itself (see `Node attributes `_) If the same input is specified by these different sources, :term:`Ewoks` applies the following priorities: diff --git a/doc/reference/cli.rst b/doc/reference/cli.rst index 4add2cd..a68fe21 100644 --- a/doc/reference/cli.rst +++ b/doc/reference/cli.rst @@ -63,6 +63,8 @@ ewoks convert **ewoks convert** can also be used to store ``inputs`` inside the destination :term:`workflow`. +.. _cli_execute: + ewoks execute ------------- diff --git a/doc/tutorials/install.rst b/doc/tutorials/install.rst index 6c6476c..69a184b 100644 --- a/doc/tutorials/install.rst +++ b/doc/tutorials/install.rst @@ -94,19 +94,19 @@ arguments are passed to ``ewoks install``, except for the options of the script .. code-block:: bash - curl -LsSf https://ewoks.readthedocs.io/en/stable/ewoks-install.sh | sh -s -- demo.json --package-manager-name uv + curl -LsSf |docs_url|/ewoks-install.sh | sh -s -- demo.json --package-manager-name uv .. group-tab:: macOS .. code-block:: bash - curl -LsSf https://ewoks.readthedocs.io/en/stable/ewoks-install.sh | sh -s -- demo.json --package-manager-name uv + curl -LsSf |docs_url|/ewoks-install.sh | sh -s -- demo.json --package-manager-name uv .. group-tab:: Windows .. code-block:: powershell - & ([scriptblock]::Create((irm https://ewoks.readthedocs.io/en/stable/ewoks-install.ps1))) demo.json --package-manager-name uv + & ([scriptblock]::Create((irm |docs_url|/ewoks-install.ps1))) demo.json --package-manager-name uv * ``--package-manager-name`` and ``--package-manager-command`` select the :term:`package manager` that creates the bootstrap environment. Without them it is the first diff --git a/doc/tutorials/install/conda.rst b/doc/tutorials/install/conda.rst index bfa5283..381cd1c 100644 --- a/doc/tutorials/install/conda.rst +++ b/doc/tutorials/install/conda.rst @@ -133,21 +133,23 @@ environment without an environment with :term:`ewoks` .. code-block:: bash - curl -LsSf https://ewoks.readthedocs.io/en/stable/ewoks-install.sh | sh -s -- demo.json --package-manager-name conda --yes --env-root ewoks_envs + curl -LsSf |docs_url|/ewoks-install.sh | sh -s -- demo.json --package-manager-name conda --yes --env-root ewoks_envs .. group-tab:: macOS .. code-block:: bash - curl -LsSf https://ewoks.readthedocs.io/en/stable/ewoks-install.sh | sh -s -- demo.json --package-manager-name conda --yes --env-root ewoks_envs + curl -LsSf |docs_url|/ewoks-install.sh | sh -s -- demo.json --package-manager-name conda --yes --env-root ewoks_envs .. group-tab:: Windows .. code-block:: powershell - & ([scriptblock]::Create((irm https://ewoks.readthedocs.io/en/stable/ewoks-install.ps1))) demo.json --package-manager-name conda --yes --env-root ewoks_envs + & ([scriptblock]::Create((irm |docs_url|/ewoks-install.ps1))) demo.json --package-manager-name conda --yes --env-root ewoks_envs -Execute the workflow with the python interpreter of the environment it prints. +The script does not install ``ewoks`` in the current environment, so execute the +workflow with the python interpreter of the environment of the :term:`workflow`, for example +``ewoks_envs/demo/bin/python -m ewoks execute demo.json``. Execute the workflow -------------------- diff --git a/doc/tutorials/install/pip_venv.rst b/doc/tutorials/install/pip_venv.rst index b202f48..aca58cb 100644 --- a/doc/tutorials/install/pip_venv.rst +++ b/doc/tutorials/install/pip_venv.rst @@ -92,21 +92,23 @@ environment without an environment with :term:`ewoks` .. code-block:: bash - curl -LsSf https://ewoks.readthedocs.io/en/stable/ewoks-install.sh | sh -s -- demo.json --package-manager-name pip-venv --yes --env-root ewoks_envs + curl -LsSf |docs_url|/ewoks-install.sh | sh -s -- demo.json --package-manager-name pip-venv --yes --env-root ewoks_envs .. group-tab:: macOS .. code-block:: bash - curl -LsSf https://ewoks.readthedocs.io/en/stable/ewoks-install.sh | sh -s -- demo.json --package-manager-name pip-venv --yes --env-root ewoks_envs + curl -LsSf |docs_url|/ewoks-install.sh | sh -s -- demo.json --package-manager-name pip-venv --yes --env-root ewoks_envs .. group-tab:: Windows .. code-block:: powershell - & ([scriptblock]::Create((irm https://ewoks.readthedocs.io/en/stable/ewoks-install.ps1))) demo.json --package-manager-name pip-venv --yes --env-root ewoks_envs + & ([scriptblock]::Create((irm |docs_url|/ewoks-install.ps1))) demo.json --package-manager-name pip-venv --yes --env-root ewoks_envs -Execute the workflow with the python interpreter of the environment it prints. +The script does not install ``ewoks`` in the current environment, so execute the +workflow with the python interpreter of the environment of the :term:`workflow`, for example +``ewoks_envs/demo/bin/python -m ewoks execute demo.json``. Execute the workflow -------------------- diff --git a/doc/tutorials/install/pixi.rst b/doc/tutorials/install/pixi.rst index a15bf9f..e1deec3 100644 --- a/doc/tutorials/install/pixi.rst +++ b/doc/tutorials/install/pixi.rst @@ -128,21 +128,23 @@ environment without an environment with :term:`ewoks` .. code-block:: bash - curl -LsSf https://ewoks.readthedocs.io/en/stable/ewoks-install.sh | sh -s -- demo.json --package-manager-name pixi --yes --env-root ewoks_envs + curl -LsSf |docs_url|/ewoks-install.sh | sh -s -- demo.json --package-manager-name pixi --yes --env-root ewoks_envs .. group-tab:: macOS .. code-block:: bash - curl -LsSf https://ewoks.readthedocs.io/en/stable/ewoks-install.sh | sh -s -- demo.json --package-manager-name pixi --yes --env-root ewoks_envs + curl -LsSf |docs_url|/ewoks-install.sh | sh -s -- demo.json --package-manager-name pixi --yes --env-root ewoks_envs .. group-tab:: Windows .. code-block:: powershell - & ([scriptblock]::Create((irm https://ewoks.readthedocs.io/en/stable/ewoks-install.ps1))) demo.json --package-manager-name pixi --yes --env-root ewoks_envs + & ([scriptblock]::Create((irm |docs_url|/ewoks-install.ps1))) demo.json --package-manager-name pixi --yes --env-root ewoks_envs -Execute the workflow with the python interpreter of the environment it prints. +The script does not install ``ewoks`` in the current environment, so execute the +workflow with the python interpreter of the environment of the :term:`workflow`, for example +``ewoks_envs/demo/.pixi/envs/default/bin/python -m ewoks execute demo.json``. Execute the workflow -------------------- diff --git a/doc/tutorials/install/poetry.rst b/doc/tutorials/install/poetry.rst index c61fe63..c6175e0 100644 --- a/doc/tutorials/install/poetry.rst +++ b/doc/tutorials/install/poetry.rst @@ -115,21 +115,23 @@ environment without an environment with :term:`ewoks` .. code-block:: bash - curl -LsSf https://ewoks.readthedocs.io/en/stable/ewoks-install.sh | sh -s -- demo.json --package-manager-name poetry --yes --env-root ewoks_envs + curl -LsSf |docs_url|/ewoks-install.sh | sh -s -- demo.json --package-manager-name poetry --yes --env-root ewoks_envs .. group-tab:: macOS .. code-block:: bash - curl -LsSf https://ewoks.readthedocs.io/en/stable/ewoks-install.sh | sh -s -- demo.json --package-manager-name poetry --yes --env-root ewoks_envs + curl -LsSf |docs_url|/ewoks-install.sh | sh -s -- demo.json --package-manager-name poetry --yes --env-root ewoks_envs .. group-tab:: Windows .. code-block:: powershell - & ([scriptblock]::Create((irm https://ewoks.readthedocs.io/en/stable/ewoks-install.ps1))) demo.json --package-manager-name poetry --yes --env-root ewoks_envs + & ([scriptblock]::Create((irm |docs_url|/ewoks-install.ps1))) demo.json --package-manager-name poetry --yes --env-root ewoks_envs -Execute the workflow with the python interpreter of the environment it prints. +The script does not install ``ewoks`` in the current environment, so execute the +workflow with the python interpreter of the environment of the :term:`workflow`, for example +``ewoks_envs/demo/.venv/bin/python -m ewoks execute demo.json``. Execute the workflow -------------------- diff --git a/doc/tutorials/install/uv.rst b/doc/tutorials/install/uv.rst index 81691e8..e98a5d6 100644 --- a/doc/tutorials/install/uv.rst +++ b/doc/tutorials/install/uv.rst @@ -122,21 +122,23 @@ environment without an environment with :term:`ewoks` .. code-block:: bash - curl -LsSf https://ewoks.readthedocs.io/en/stable/ewoks-install.sh | sh -s -- demo.json --package-manager-name uv --yes --env-root ewoks_envs + curl -LsSf |docs_url|/ewoks-install.sh | sh -s -- demo.json --package-manager-name uv --yes --env-root ewoks_envs .. group-tab:: macOS .. code-block:: bash - curl -LsSf https://ewoks.readthedocs.io/en/stable/ewoks-install.sh | sh -s -- demo.json --package-manager-name uv --yes --env-root ewoks_envs + curl -LsSf |docs_url|/ewoks-install.sh | sh -s -- demo.json --package-manager-name uv --yes --env-root ewoks_envs .. group-tab:: Windows .. code-block:: powershell - & ([scriptblock]::Create((irm https://ewoks.readthedocs.io/en/stable/ewoks-install.ps1))) demo.json --package-manager-name uv --yes --env-root ewoks_envs + & ([scriptblock]::Create((irm |docs_url|/ewoks-install.ps1))) demo.json --package-manager-name uv --yes --env-root ewoks_envs -Execute the workflow with the python interpreter of the environment it prints. +The script does not install ``ewoks`` in the current environment, so execute the +workflow with the python interpreter of the environment of the :term:`workflow`, for example +``ewoks_envs/demo/.venv/bin/python -m ewoks execute demo.json``. Execute the workflow --------------------