🪧

【Python】Pixiの特異性「Task」の基本

に公開

タスクの全容を知る

https://pixi.sh/latest/workspace/advanced_tasks/

パッケージマネージャーは数多あれ、それがタスクを持つことは尠少、しかしその利便たるや計り知れないところがある。本記事はPixiの持つ特異性の一つ、タスクについて、その基本的な使い方を極力簡素に網羅するものである。

なお、一部の操作はコマンドpixi task ~で行うことができるが、本記事ではこれを扱わない。

0. ワークスペース新規作成

まずは新規にワークスペースを一つ作る。

tasksの名で作成
pixi init tasks

tasksフォルダーと、その中にpixi.tomlが作られただろう。本記事では、このpixi.tomlを編集することでタスクを扱っていく。

pixi.toml
[workspace]
channels = ["conda-forge"]
name = "tasks"
platforms = ["win-64"] # OSによる
version = "0.1.0"

[tasks]

[dependencies]

一応、ワークスペースにPythonを追加する。

pixi add python

pixi.tomlが更新され、Pythonの追加されたことが確認できる。

pixi.toml
[dependencies]
python = ">=3.13.7,<3.14"

最終的なpixi.toml

長いのでアコーディオン
pixi.toml
[workspace]
channels = ["conda-forge"]
name = "tasks"
platforms = ["win-64"]
version = "0.1.0"

[dependencies]
python = ">=3.13.7,<3.14"

[activation.env]
WORLD_ENVIRONMENT = "world"

[tasks]
make_dir = "mkdir -p src"
create_source_file = "echo 'import sys\n\nprint(sys.version)' > src/main.py"
run_script = "python src/main.py"
running_all_flow = [
    { task = "make_dir" },
    { task = "create_source_file" },
    { task = "run_script" }
]

mkdir_src = { cmd = "mkdir -p src" }
create_main_py = { cmd = "echo 'import sys\n\nprint(sys.version)' > src/main.py", depends-on = ["mkdir_src"] }
run_main_py = { cmd = "python src/main.py", depends-on = ["create_main_py"] }

delete_source_dir = "rm -rf src"

unique_task = "python --version"
show_all_unique_tasks = { depends-on = [
    { task = "unique_task", environment = "default"},
    { task = "unique_task", environment = "node-env"}
] }
print_hello = "python -c 'print(\"Hello from Python\")'"
hello_from_all_envs = { depends-on = [
    { task = "print_hello", environment = "default"},
    { task = "log_hello", environment = "node-env"}
] }

run_in_src_dir = { cmd = "python main.py", cwd = "src" }

greet_with_name = { cmd = "python -c 'print(\"Hello, {{ name }}\")'", args = ["name"] }
greet_with_optional = { cmd = "python -c 'print(\"Hello, {{ name }}\")'", args = [{ "arg" = "name", "default" = "World" }] }

addition = { cmd = "python -c 'print({{ a }} + {{ b }})'", args = [
    { "arg" = "a", "default" = "0" },
    { "arg" = "b", "default" = "0" }
] }

make_user_dir = { cmd = "mkdir -p users/{{ username }}", args = ["username"] }
create_user_file = { cmd = """echo 'print("Hello, {{ username }}")' > users/{{ username }}/{{ username }}.py""", depends-on = [
    {task = "make_user_dir", args = ["{{ username }}"]}
], args = ["username"] }

minus_addition = { depends-on = [
    { task = "addition", args = [
        { "a" = "-6" },
        { "b" = "-3" }
    ] }
] }

upper_case = { cmd = "echo {{ text | upper }}", args = ["text"] }
lower_case = { cmd = "echo {{ text | lower }}", args = ["text"] }

division = { cmd = """python -c '''
{% if not b == "0" %}
print({{ a }} / {{ b }})
{% else %}
print("Error: Division by zero")
{% endif %}
'''
""", args = [
    { "arg" = "a", "default" = "1" },
    { "arg" = "b", "default" = "1" }
] }

concatenate = { cmd = "echo {{ a + b }}", args = ["a", { arg = "b", default = "!" }] }

for_split = { cmd = "{% for name in names | split %} echo {{ name }};{% endfor %}", args = ["names"] }

_hidden = "echo hidden"

create_main_py_cache = { cmd = "echo 'import sys\n\nprint(sys.version)' > src/main.py", depends-on = ["mkdir_src"], outputs = ["src/main.py"] }
run_main_py_cache = { cmd = "python src/main.py", depends-on = ["create_main_py_cache"], inputs = ["src/main.py"] }

hello_world_env_var = { cmd = "echo $HELLO_ENVIRONMENT $WORLD_ENVIRONMENT", env = { HELLO_ENVIRONMENT = "hello" } }

ls = "ls"

[environments]
node-env = { features = ["f_node"], no-default-feature = true }

[feature.f_node]
dependencies = { nodejs = ">=24.9.0,<24.10" }
tasks = { unique_task = "npm --version", log_hello = "node -e 'console.log(\"Hello from Node.js\")'" }

1. コマンド入力の簡略化

手始めに、四つのタスクを登録する。

pixi.toml
[tasks]
# srcフォルダーが無ければ作る
make_dir = "mkdir -p src"
# srcフォルダーの中にmain.pyを作る
create_source_file = "echo 'import sys\n\nprint(sys.version)' > src/main.py"
# main.pyを実行する
run_script = "python src/main.py"
# srcフォルダーを内容ごと消す
delete_source_dir = "rm -rf src"

これらのタスクを実行する時は、pixi run タスク名と入力する。

各タスク実行の様子
make_dir
# 実行前の様子確認
PS C:\⋯\tasks> ls -Name
.pixi
.gitattributes
.gitignore
pixi.lock
pixi.toml
# タスク実行
PS C:\⋯\tasks> pixi run make_dir
Pixi task (make_dir): mkdir -p src
# 実行後の様子確認
PS C:\⋯\tasks> ls -Name
.pixi
src            # ☚ srcフォルダーが作られた
.gitattributes
.gitignore
pixi.lock
pixi.toml
create_source_file
# 実行前の様子確認(何もないため何も表示されない)
PS C:\⋯\tasks> ls .\src\ -Name
# タスク実行
PS C:\⋯\tasks> pixi run create_source_file
Pixi task (create_source_file): echo 'import sys

print(sys.version)' > src/main.py
# 実行後の様子確認
PS C:\⋯\tasks> ls .\src\ -Name
main.py
# ファイルの内容確認
PS C:\⋯\tasks> type .\src\main.py
import sys

print(sys.version)
run_script
# タスク実行
PS C:\⋯\tasks> pixi run run_script
Pixi task (run_script): python src/main.py
3.13.7 | packaged by conda-forge | (main, Sep  3 2025, 14:18:04) [MSC v.1944 64 bit (AMD64)]
delete_source_dir
# 実行前の様子確認
PS C:\⋯\tasks> ls -Name
.pixi
src
.gitattributes
.gitignore
pixi.lock
pixi.toml
# タスク実行
PS C:\⋯\tasks> pixi run delete_source_dir
Pixi task (delete_source_dir): rm -rf src
# 実行後の様子確認
PS C:\⋯\tasks> ls -Name
.pixi
.gitattributes
.gitignore
pixi.lock
pixi.toml

問題点:実行順序によるエラー

当然ながら、main.pyが存在しない状態でrun_scriptタスクを実行すれば、エラーになる。

No such file or directory
PS C:\⋯\tasks> pixi run run_script
Pixi task (run_script): python src/main.py
C:\⋯\tasks\.pixi\envs\default\python.exe: can't open file 'C:\\⋯\\tasks\\src\\main.py': [Errno 2] No such file or directory

run_scriptタスクが正しく実行されるには、create_source_fileタスクによってmain.pyが作られていなければならない。

更に、create_source_fileタスクが正しく実行されるには、make_dirタスクによってsrcフォルダーが作られていなければならない。

指定されたパスが見つかりません
PS C:\⋯\tasks> pixi run create_source_file
Pixi task (create_source_file): echo 'import sys

print(sys.version)' > src/main.py
error opening file for redirect (C:\⋯\tasks\src/main.py). 指定されたパスが見つかりません。 (os error 3)

このような状況を、「タスク同士に依存関係がある」と言う。

2. 依存関係

タスク同士に依存関係がある場合、その通り正しい順序で実行しなければならない。これを強制するためには、depends-onで依存関係を定めておく。

depends-on:依存関係定義の基本

ここで、pixi.tomlにタスクを追加する。演じることは先のものと全く同じだが、depends-onによる依存関係の定義を付した。

pixi.toml
[tasks]
mkdir_src = { cmd = "mkdir -p src" }
create_main_py = { cmd = "echo 'import sys\n\nprint(sys.version)' > src/main.py", depends-on = ["mkdir_src"] }
run_main_py = { cmd = "python src/main.py", depends-on = ["create_main_py"] }

このように依存関係を定めたことで、run_main_pyタスクを実行すると、依存関係を遡って全てのタスクが実行される

# srcフォルダーが無い状態
PS C:\⋯\tasks> ls -Name
.pixi
.gitattributes
.gitignore
pixi.lock
pixi.toml
# run_main_pyの実行命令
PS C:\⋯\tasks> pixi run run_main_py
# mkdir_srcの実行
Pixi task (mkdir_src): mkdir -p src

# create_main_pyの実行
Pixi task (create_main_py): echo 'import sys

print(sys.version)' > src/main.py

# run_main_pyの実行
Pixi task (run_main_py): python src/main.py
3.13.7 | packaged by conda-forge | (main, Sep  3 2025, 14:18:04) [MSC v.1944 64 bit (AMD64)]
# 実行後はsrcフォルダーが作られている
PS C:\⋯\tasks> ls -Name
.pixi
src
.gitattributes
.gitignore
pixi.lock
pixi.toml

alias:依存関係の後付け

depends-onを使わずとも、依存関係を定めることができる。先の、依存関係を定めていないタスクについて、次の若く後から依存関係を定める。

pixi.toml
[tasks]
make_dir = "mkdir -p src"
create_source_file = "echo 'import sys\n\nprint(sys.version)' > src/main.py"
run_script = "python src/main.py"
# 依存関係を定める配列
running_all_flow = [
    { task = "make_dir" },
    { task = "create_source_file" },
    { task = "run_script" }
]

running_all_flowそれ自体はタスクというよりも、別名(alias)である。しかし実行時はタスク同様にして扱うことができる。

aliasの実行
PS C:\⋯\tasks> pixi run running_all_flow
Pixi task (make_dir): mkdir -p src

Pixi task (create_source_file): echo 'import sys

print(sys.version)' > src/main.py

Pixi task (run_script): python src/main.py
3.13.7 | packaged by conda-forge | (main, Sep  3 2025, 14:18:04) [MSC v.1944 64 bit (AMD64)]

難:環境と依存タスク

Pixiでは、異なる複数の環境を司ることができる。従って、タスクを実行するに当たり、どの環境下で実行するかを指定することができる。pixi.tomlにて、新たに一つNode.jsの環境を定義する。

pixi.toml
[environments]
# Node.jsの環境
# `no-default-feature = true`は`unique_task`という名前の重複を許すために付している
node-env = { features = ["f_node"], no-default-feature = true }

[feature.f_node]
# pixi add -f f_node nodejs
dependencies = { nodejs = ">=24.9.0,<24.10" }
tasks = { unique_task = "npm --version" }

次に、新たな二つのタスクを追加する。

pixi.toml
[tasks]
unique_task = "python --version"
show_all_unique_tasks = { depends-on = [
    { task = "unique_task", environment = "default"},
    { task = "unique_task", environment = "node-env"}
] }

この内、depends-onのみからなるタスクはa dependent taskと表現されているため、本記事では依存タスクと呼んでいる。

現状、各環境について次のようになっている。

環境 パッケージ タスクunique_taskの定義
default python unique_task = "python --version"
node-env nodejs unique_task = "npm --version"
show_all_unique_tasks
PS C:\⋯\tasks> pixi run show_all_unique_tasks
Pixi task (unique_task in default): python --version
Python 3.13.7

Pixi task (unique_task in node-env): npm --version
11.6.0

unique_taskという同名のタスクを用いたが、必ずしも同名である必要はない。print_hellolog_helloという二つの全く異なるタスクを使っても、依存タスクは同様に働く。

pixi.toml
[tasks]
print_hello = "python -c 'print(\"Hello from Python\")'"
hello_from_all_envs = { depends-on = [
    { task = "print_hello", environment = "default"},
    { task = "log_hello", environment = "node-env"}
] }

[environments]
node-env = { features = ["f_node"], no-default-feature = true }

[feature.f_node]
dependencies = { nodejs = ">=24.9.0,<24.10" }
tasks = { unique_task = "npm --version", log_hello = "node -e 'console.log(\"Hello from Node.js\")'" }
hello_from_all_envs
PS C:\⋯\tasks> pixi run hello_from_all_envs
Pixi task (print_hello in default): python -c 'print("Hello from Python")'
Hello from Python

Pixi task (log_hello in node-env): node -e 'console.log("Hello from Node.js")'
Hello from Node.js

Pixiの環境に関する基本的な内容は、別に記事として紹介している。

https://zenn.dev/amenaruya/articles/e96b5f52e7f098

3. Current Working Directory

Current Working Directoryとは、コマンドを実行する時点での「現在の位置」である。通常はワークスペースの直下、pixi.tomlのある位置となる。

先述したrun_main_pyの定義を改めて見てみよう。

run_main_py = { cmd = "python src/main.py", depends-on = ["create_main_py"] }

main.pysrcフォルダーにあるため、src/main.pyとしている。

では試みに、現在の位置をsrcにしてみよう。なお、依存関係は省略している。

run_in_src_dir = { cmd = "python main.py", cwd = "src" }
run_in_src_dir
PS C:\⋯\tasks> pixi run run_in_src_dir
Pixi task (run_in_src_dir in default): python main.py
3.13.7 | packaged by conda-forge | (main, Sep  3 2025, 14:18:04) [MSC v.1944 64 bit (AMD64)]

Current Working Directorysrcフォルダーになるため、単にmain.pyとできる。

4. タスク引数

Pythonがコマンドライン引数を受け取るように、タスクも引数を受け取ることができる。本記事では、タスク引数と表現する。

必須のタスク引数

単純な例から始めよう。タスク引数に与えた文字列を使って挨拶する、単純なものである。

pixi.toml
[tasks]
greet_with_name = { cmd = "python -c 'print(\"Hello, {{ name }}\")'", args = ["name"] }

タスク引数に与えられた値にはnameという名がつく。タスク引数nameをコマンド上に展開するには、{{name}}とする。

greet_with_name
PS C:\⋯\tasks> pixi run greet_with_name takahashi
Pixi task (greet_with_name in default): python -c 'print("Hello, takahashi")'
Hello, takahashi

# 必須のため、タスク引数を与えないとエラーになる
PS C:\⋯\tasks> pixi run greet_with_name
Error:   × no value provided for argument 'name' for task 'greet_with_name'

タスク引数の初期値

初期値を定めることで、タスク引数を任意入力とすることができる。

pixi.toml
[tasks]
greet_with_optional = { cmd = "python -c 'print(\"Hello, {{ name }}\")'", args = [{ "arg" = "name", "default" = "World" }] }
greet_with_optional
# 省略した場合
PS C:\⋯\tasks> pixi run greet_with_optional
Pixi task (greet_with_optional in default): python -c 'print("Hello, World")'
Hello, World
# 省略しない場合
PS C:\⋯\tasks> pixi run greet_with_optional takachiho
Pixi task (greet_with_optional in default): python -c 'print("Hello, takachiho")'
Hello, takachiho

複数のタスク引数

タスク引数は複数定めることができる。

pixi.toml
[tasks]
addition = { cmd = "python -c 'print({{ a }} + {{ b }})'", args = [
    { "arg" = "a", "default" = "0" },
    { "arg" = "b", "default" = "0" }
] }
addition
PS C:\⋯\tasks> pixi run addition
Pixi task (addition in default): python -c 'print(0 + 0)'
0
PS C:\⋯\tasks> pixi run addition 1 2
Pixi task (addition in default): python -c 'print(1 + 2)'
3

依存と引数

依存先のタスクに対してもまた、タスク引数を与えることができる。

pixi.toml
# ユーザー名を基にフォルダーを作る
make_user_dir = { cmd = "mkdir -p users/{{ username }}", args = ["username"] }
# ユーザー名.py を作る
create_user_file = { cmd = """echo 'print("Hello, {{ username }}")' > users/{{ username }}/{{ username }}.py""", depends-on = [
    {task = "make_user_dir", args = ["{{ username }}"]}
], args = ["username"] }
create_user_file
# タスク実行
PS C:\⋯\tasks> pixi run create_user_file tabayama
Pixi task (make_user_dir in default): mkdir -p users/tabayama

Pixi task (create_user_file in default): echo 'print("Hello, tabayama")' > users/tabayama/tabayama.py

# tabayamaのフォルダーとファイルが作られている
PS C:\⋯\tasks> tree .\users\ /f
Folder PATH listing for volume OS
Volume serial number is ⋯
C:\⋯\TASKS\USERS
└───tabayama
        tabayama.py

PS C:\⋯\tasks> type .\users\tabayama\tabayama.py
print("Hello, tabayama ")

また、引数名を明示することもできる。

pixi.toml
[tasks]
addition = { cmd = "python -c 'print({{ a }} + {{ b }})'", args = [
    { "arg" = "a", "default" = "0" },
    { "arg" = "b", "default" = "0" }
] }
# 負の値を渡す
minus_addition = { depends-on = [
    { task = "addition", args = [
        { "a" = "-6" },
        { "b" = "-3" }
    ] }
] }
minus_addition
PS C:\⋯\tasks> pixi run minus_addition
Pixi task (addition in default): python -c 'print(-6 + -3)'
-9

MiniJinja

MiniJinja

コマンド上にタスク引数の値を展開する際、{{ 引数名 }}という表記を用いてきた。これはRustMiniJinjaによる機能である。そしてMiniJinjaPythonJinjaに基づいている。

Jinja

従って{{ ~ }}という表記自体は、屢〻Pythonでも用いられている。例えばFlaskで、Pythonから渡した値をHTML上に展開するなどといった使い方が知られている。

MiniJinjaはこの表記をRustでも使えるようにするものである。PixiRustによって作られているため、JinjaではなくMiniJinjaと記載されている

詳しくはMiniJinjaあるいはJinjaの仕様を参照するべきだが、本記事でも幾つか例を示す。

条件分岐

除算を例に取ると、0で割ろうとしていないか検べなければならない。

pixi.toml
[tasks]
division = { cmd = """python -c '''
{% if not b == "0" %}
print({{ a }} / {{ b }})
{% else %}
print("Error: Division by zero")
{% endif %}
'''
""", args = [
    { "arg" = "a", "default" = "1" },
    { "arg" = "b", "default" = "1" }
] }
division
PS C:\⋯\tasks> pixi run division 10 3
Pixi task (division in default): python -c '''

print(10 / 3)

'''
3.3333333333333335
PS C:\⋯\tasks> pixi run division 10 0
Pixi task (division in default): python -c '''

print("Error: Division by zero")

'''
Error: Division by zero

反復

pixi.toml
[tasks]
for_split = { cmd = "{% for name in names | split %} echo {{ name }};{% endfor %}", args = ["names"] }
loop
PS C:\⋯\tasks> pixi run for_split_list 'nagashima ezaki ogata'
Pixi task (for_split_list in default):  echo nagashima; echo ezaki; echo ogata;
nagashima
ezaki
ogata

5. 命名と秘匿

タスクの命名について次の規則がある。

  • タスクの名前を_から始めると、pixi task listなどに表示されなくなる。
  • 半角スペース を含めることができない。
  • 一意で重複しない。

特に、_から始まる命名によって、タスクを秘匿することができる。

タスク一覧の表示

タスクの一覧はpixi task listで表示できるが、pixi runの表示の方が読み好い。

PS C:\⋯\tasks> pixi task list -s
Tasks per environment:
----------------------
default: addition, concatenate, create_main_py, create_main_py_cache, create_source_file, create_user_file, delete_source_dir, division, for_split, greet_with_name, greet_with_optional, hello_from_all_envs, hello_world_env_var, lower_case, ls, make_dir, make_user_dir, minus_addition, mkdir_src, print_hello, run_in_src_dir, run_main_py, run_main_py_cache, run_script, running_all_flow, show_all_unique_tasks, unique_task, upper_case
node-env: log_hello, unique_task
PS C:\⋯\tasks> pixi run

Available tasks:
        addition
        concatenate
        create_main_py
        create_main_py_cache
        create_source_file
        create_user_file
        delete_source_dir
        division
        for_split
        greet_with_name
        greet_with_optional
        hello_from_all_envs
        hello_world_env_var
        log_hello
        lower_case
        ls
        make_dir
        make_user_dir
        minus_addition
        mkdir_src
        print_hello
        run_in_src_dir
        run_main_py
        run_main_py_cache
        run_script
        running_all_flow
        show_all_unique_tasks
        unique_task
        upper_case

_から始まる名前が一つも現れていないことが分かる。

タスク一覧のJSON表示

タスクの一覧はJSON型式で表示することもできるが、こちらには_から始まるタスクも秘匿されず、名を連ねている。

PowerShellによる検索
PS C:\⋯\tasks> pixi task list --json | Out-String -Stream | sls -Pattern "hidden" -Context 0

            "name": "_hidden",
            "cmd": "echo hidden",

6. キャッシュ

ここで、先述のタスクを振り返る。

pixi.toml
[tasks]
mkdir_src = { cmd = "mkdir -p src" }
create_main_py = { cmd = "echo 'import sys\n\nprint(sys.version)' > src/main.py", depends-on = ["mkdir_src"] }
run_main_py = { cmd = "python src/main.py", depends-on = ["create_main_py"] }

これらは依存関係を定義し、実行順序によるエラーが起こらないようになったものであった。しかし、main.pyが存在する場合でも必ず上書きしている。この問題はキャッシュを使うことで解消することができる。

キャッシュは入力と出力との二種あり、且つその対象はファイルであるらしい。つまり、フォルダーは対象とならない。

pixi.toml
[tasks]
# 出力:src/main.py が存在する場合、再実行されない
create_main_py_cache = { cmd = "echo 'import sys\n\nprint(sys.version)' > src/main.py", depends-on = ["mkdir_src"], outputs = ["src/main.py"] }
# 入力:src/main.py が変更されていない場合、再実行されない
run_main_py_cache = { cmd = "python src/main.py", depends-on = ["create_main_py_cache"], inputs = ["src/main.py"] }
run_main_py_cache
# 一度目は実行される
PS C:\⋯\tasks> pixi run run_main_py_cache
Pixi task (mkdir_src in default): mkdir -p src

Pixi task (create_main_py_cache in default): echo 'import sys

print(sys.version)' > src/main.py
Pixi task (run_main_py_cache in default): python src/main.py
3.13.7 | packaged by conda-forge | (main, Sep  3 2025, 14:18:04) [MSC v.1944 64 bit (AMD64)]
# 二度目は実行されない
PS C:\⋯\tasks> pixi run run_main_py_cache
Pixi task (mkdir_src in default): mkdir -p src

Pixi task (create_main_py_cache in default): echo 'import sys

print(sys.version)' > src/main.py
Task 'create_main_py_cache' can be skipped (cache hit) 🚀

Pixi task (run_main_py_cache in default): python src/main.py
Task 'run_main_py_cache' can be skipped (cache hit) 🚀

outputs:存在の確認

outputsには、タスクによって生成されるファイルを指定する。このファイルが存在しているならばそれ以上タスクを実行する必要はない

inputs:変更の確認

inputsには、タスクが使用するファイルを指定する。実行するファイルや、参照する設定ファイルなどが該当する。このファイルに変更がないならばそれ以上タスクを実行する必要はない

7. 環境変数

タスク内で環境変数を設定・使用することができる。なおターミナルでこれらを使えるわけではない。

pixi.toml
[activation.env]
WORLD_ENVIRONMENT = "world"

[tasks]
hello_world_env_var = { cmd = "echo $HELLO_ENVIRONMENT $WORLD_ENVIRONMENT", env = { HELLO_ENVIRONMENT = "hello" } }
hello_world_env_var
PS C:\⋯\tasks> pixi run hello_world_env_var
Pixi task (hello_world_env_var in default): echo $HELLO_ENVIRONMENT $WORLD_ENVIRONMENT
hello world

環境変数の定義には幾つか手段がある。同名の環境変数を異なる手段で定義した場合、既定の優先順位によって一方が採用される。

必要最小限の環境変数

プログラムで使用できる環境変数は、必ずしもプログラムに関係あるものではない。不要なものを除外し、プログラムに必要最小限の環境変数を備えた環境を使ってタスクを実行することができる。なお、一部の環境変数(USERHOMEなど)は除外されない。

確認用ワークスペースでの実験

Colaboratoryで実行した。

# pixiのインストール
/content# curl -fsSL https://pixi.sh/install.sh | sh
This script will automatically download and install Pixi (latest) for you.
Getting it from this url: https://github.com/prefix-dev/pixi/releases/latest/download/pixi-x86_64-unknown-linux-musl.tar.gz
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0
100 24.2M  100 24.2M    0     0  26.6M      0 --:--:-- --:--:-- --:--:-- 26.6M
The 'pixi' binary is installed into '/root/.pixi/bin'
Updating '/root/.bashrc'
Please restart or source your shell.
# ターミナルの再起動
/content# source /root/.bashrc
# ワークスペース`clean`作成
/content# pixi init clean
✔ Created /content/clean/pixi.toml
# 移動
/content# cd clean/
pixi.toml
[workspace]
channels = ["conda-forge"]
name = "clean"
platforms = ["linux-64"]
version = "0.1.0"

[tasks]
path = { cmd = "echo $PATH" }
home = { cmd = "echo $HOME" }
cuda_ver = { cmd = "echo $CUDA_VERSION" }

path_clean = { cmd = "echo $PATH", clean-env = true }
home_clean = { cmd = "echo $HOME", clean-env = true }
cuda_ver_clean = { cmd = "echo $CUDA_VERSION", clean-env = true }

[dependencies]

# cleanでない環境
/content/clean# pixi run path
✨ Pixi task (path): echo $PATH
/content/clean/.pixi/envs/default/bin:/root/.pixi/bin:/opt/bin:/usr/local/nvidia/bin:/usr/local/cuda/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/tools/node/bin:/tools/google-cloud-sdk/bin
/content/clean# pixi run home
✨ Pixi task (home): echo $HOME
/root
/content/clean# pixi run cuda_ver
✨ Pixi task (cuda_ver): echo $CUDA_VERSION
12.5.1
# cleanな環境
/content/clean# pixi run path_clean
✨ Pixi task (path_clean): echo $PATH
/content/clean/.pixi/envs/default/bin
/content/clean# pixi run home_clean
✨ Pixi task (home_clean): echo $HOME
/root
/content/clean# pixi run cuda_ver_clean
✨ Pixi task (cuda_ver_clean): echo $CUDA_VERSION

補遺1.deno_task_shell

Pixiは複数のOSで使うことができる。しかしOSによってコマンドは、表面的にも内部的にも異なる。こうした問題を解消するため、deno_task_shellが使われている。

これまでにもタスク内でmkdirrmechoといったコマンドを用いてきたが、これはあくまでもdeno_task_shellによって提供されているコマンドである。つまりPixiのタスクで使うことのできるコマンドはdeno_task_shellのものであり、OSのコマンドとは関係ないことがある。

その分かりやすい証左に、lsコマンドが使えない。

pixi.toml
[tasks]
ls = "ls"
PS C:\⋯\tasks> pixi run ls                 
Pixi task (ls in default): ls                                                          
ls: command not found
Windowsとls

コマンドプロンプトではlsが使えず、dirがそれに対応することが知られている。

コマンドプロンプト
C:\>ls
'ls' は、内部コマンドまたは外部コマンド、
操作可能なプログラムまたはバッチ ファイルとして認識されていません。

しかしPowerShellでは、Get-ChildItemの別名としてlsが存在する。このため、Windowsでもlsを使うことはできるのである。

https://learn.microsoft.com/ja-jp/powershell/module/microsoft.powershell.management/get-childitem?view=powershell-7.5#notes

コマンド一覧

  • cp
  • mv
  • rm
  • mkdir
  • pwd
  • sleep
  • echo
  • cat
  • exit
  • head
  • unset
  • xargs
参考:Windowsで実行した様子
pixi.toml
[workspace]
channels = ["conda-forge"]
name = "deno_sample"
platforms = ["win-64"]
version = "0.1.0"

[tasks.echo]
cmd = "echo {{ message }}"
args = [
    { "arg" = "message", "default" = "Hello, World!" }
]

[tasks.create_text]
cmd = "echo {{ content }} > {{ filename }}.txt"
args = [
    { "arg" = "content", "default" = "Hello, World!" },
    { "arg" = "filename", "default" = "output" }
]

[tasks.copy_file]
cmd = "cp {{ source }} {{ destination }}"
args = [
    { "arg" = "source" },
    { "arg" = "destination" }
]

[tasks.move_file]
cmd = "mv {{ source }} {{ destination }}"
args = [
    { "arg" = "source" },
    { "arg" = "destination" }
]

[tasks.remove_file]
cmd = "rm {{ filename }}"
args = [
    { "arg" = "filename" }
]

[tasks.make_directory]
cmd = "mkdir -p {{ directoryname }}"
args = [
    { "arg" = "directoryname" }
]

[tasks.print_working_directory]
cmd = "pwd"

[tasks.sleep]
cmd = "sleep {{ seconds }}"
args = [
    { "arg" = "seconds", "default" = "1" }
]

[tasks.concat_files]
cmd = "cat {{ filename }}"
args = [
    { "arg" = "filename" }
]

[tasks.exit]
cmd = "exit {{ code }}"
args = [
    { "arg" = "code", "default" = "0" }
]

[tasks.head]
cmd = "head -n {{ lines }} {{ filename }}"
args = [
    { "arg" = "filename" },
    { "arg" = "lines", "default" = "10" }
]

[tasks.unset]
cmd = "{{ variable }}={{ value }} ; echo ${{ variable }} ; unset {{ variable }} ; echo ${{ variable }}"
args = [
    { "arg" = "variable" },
    { "arg" = "value" }
]

[tasks.xargs]
cmd = "echo {{ items }} | xargs -n1 echo"
args = [
    { "arg" = "items", "default" = "item1 item2 item3" }
]

# command not found
[tasks.dir]
cmd = "dir"

[dependencies]

始めに、Windows特有のコマンドとして代表的なdirが使えないことを確認する。

dir
# タスクでなければ使える
PS C:\⋯> dir -name
.pixi
.gitattributes
.gitignore
pixi.lock
pixi.toml
# タスクでは「command not found」となる
PS C:\⋯> pixi run dir
Pixi task (dir): dir
dir: command not found

Available tasks:
        concat_files
        copy_file
        create_text
        dir
        echo
        exit
        head
        make_directory
        move_file
        print_working_directory
        remove_file
        sleep
        unset
        xargs

次に、deno_task_shellのコマンドが使えることを一つ一つ確認する。

echo
PS C:\⋯> pixi run echo 
Pixi task (echo): echo Hello, World!
Hello, World!
PS C:\⋯> pixi run echo メッセージ
Pixi task (echo): echo メッセージ
メッセージ
redirect
PS C:\⋯> pixi run create_text
Pixi task (create_text): echo Hello, World! > output.txt
PS C:\⋯> ls -Name
.pixi
.gitattributes
.gitignore
output.txt
pixi.lock
pixi.toml
PS C:\⋯> pixi run create_text "大凡學文、初要膽大、終要心小。由麤入細、由俗入雅󠄂、由繁入簡、由豪蕩入純粹。" 放膽文
Pixi task (create_text): echo 大凡學文、初要膽大、終要心小。由麤入細、由俗入雅󠄂、由繁 入簡、由豪蕩入純粹。 > 放膽文.txt
 ⠁
PS C:\⋯> ls -Name
.pixi
.gitattributes
.gitignore
output.txt
pixi.lock
pixi.toml
放膽文.txt
cp
PS C:\⋯> pixi run copy_file output.txt copied.txt
Pixi task (copy_file): cp output.txt copied.txt
PS C:\⋯> ls -Name
.pixi
.gitattributes
.gitignore
copied.txt
output.txt
pixi.lock
pixi.toml
放膽文.txt
mv
PS C:\⋯> pixi run move_file output.txt hello_world.txt
Pixi task (move_file): mv output.txt hello_world.txt
PS C:\⋯> ls -Name
.pixi
.gitattributes
.gitignore
copied.txt
hello_world.txt
pixi.lock
pixi.toml
放膽文.txt
rm
PS C:\⋯> pixi run remove_file copied.txt
Pixi task (remove_file): rm copied.txt
PS C:\⋯> ls -Name
.pixi
.gitattributes
.gitignore
hello_world.txt
pixi.lock
pixi.toml
放膽文.txt
mkdir
PS C:\⋯> pixi run make_directory new_dir
Pixi task (make_directory): mkdir -p new_dir
PS C:\⋯> ls -Name
.pixi
new_dir
.gitattributes
.gitignore
hello_world.txt
pixi.lock
pixi.toml
放膽文.txt
pwd
PS C:\⋯> pixi run print_working_directory
Pixi task (print_working_directory): pwd
C:\⋯
sleep
PS C:\⋯> pixi run sleep
Pixi task (sleep): sleep 1
PS C:\⋯> pixi run sleep 5
Pixi task (sleep): sleep 5
cat
PS C:\⋯> pixi run concat_files hello_world.txt
Pixi task (concat_files): cat hello_world.txt
Hello, World!
PS C:\⋯> pixi run concat_files 放膽文.txt
Pixi task (concat_files): cat 放膽文.txt
大凡學文、初要膽大、終要心小。由麤入細、由俗入雅󠄂、由繁入簡、由豪蕩入純粹。
exit
PS C:\⋯> pixi run exit
Pixi task (exit): exit 0
PS C:\⋯> pixi run exit 1
Pixi task (exit): exit 1
head
PS C:\⋯> pixi run head pixi.toml
Pixi task (head): head -n 10 pixi.toml
[workspace]
channels = ["conda-forge"]
name = "deno_sample"
platforms = ["win-64"]
version = "0.1.0"

[tasks.echo]
cmd = "echo {{ message }}"
args = [
    { "arg" = "message", "default" = "Hello, World!" }
PS C:\⋯> pixi run head pixi.toml 15
Pixi task (head): head -n 15 pixi.toml
[workspace]
channels = ["conda-forge"]
name = "deno_sample"
platforms = ["win-64"]
version = "0.1.0"

[tasks.echo]
cmd = "echo {{ message }}"
args = [
    { "arg" = "message", "default" = "Hello, World!" }
]

[tasks.create_text]
cmd = "echo {{ content }} > {{ filename }}.txt"
args = [
unset
PS C:\⋯> pixi run unset SOME_VALUE pixi_deno
Pixi task (unset): SOME_VALUE=pixi_deno ; echo $SOME_VALUE ; unset SOME_VALUE ; echo $SOME_VALUE
pixi_deno

xargs
PS C:\⋯> pixi run concat_files dirnames.txt
Pixi task (concat_files): cat dirnames.txt
dir1
dir2
dir3
PS C:\⋯> pixi run xargs dirnames.txt echo
Pixi task (xargs): cat dirnames.txt | xargs echo
dir1 dir2 dir3
PS C:\⋯> pixi run xargs dirnames.txt mkdir
Pixi task (xargs): cat dirnames.txt | xargs mkdir
PS C:\⋯> ls -Name
.pixi
dir1
dir2
dir3
new_dir
.gitattributes
.gitignore
dirnames.txt
hello_world.txt
pixi.lock
pixi.toml
放膽文.txt

互換性の無いその他のコマンド

以上のコマンドは環境に依存していないため、OSに依らず互換性が保たれている。しかし、deno_task_shellに含まれないコマンドが全く使えないわけではない。

コマンドpixi shell-hookから、Pixiの環境変数を一覧することができる。その中には、Pixiとは関係ないパスも含まれている。

PS C:\⋯> pixi shell-hook
$OutputEncoding = [System.Console]::OutputEncoding = [System.Console]::InputEncoding = [System.Text.Encoding]::UTF8
${Env:Path} = "C:\⋯\.pixi\envs\default;C:\⋯\.pixi\envs\default\Library/mingw-w64/bin;C:\⋯\.pixi\envs\default\Library/usr/bin;C:\⋯\.pixi\envs\default\Library/bin;C:\⋯\.pixi\envs\default\Scripts;C:\⋯\.pixi\envs\default\bin;C:\Program Files\PowerShell\7;C:\Program Files\Common Files\Oracle\Java\javapath;⋯以下略⋯
${Env:CONDA_SHLVL} = "1"
${Env:CONDA_PREFIX} = "C:\⋯\.pixi\envs\default"
${Env:PIXI_PROJECT_NAME} = "deno_sample"
${Env:PIXI_PROJECT_MANIFEST} = "C:\⋯\pixi.toml"
${Env:PIXI_PROJECT_ROOT} = "C:\⋯"
${Env:PIXI_EXE} = "C:\Users\⋯\.pixi\bin\pixi.exe"
${Env:PIXI_PROJECT_VERSION} = "0.1.0"
${Env:PIXI_IN_SHELL} = "1"
${Env:CONDA_DEFAULT_ENV} = "deno_sample"
${Env:PIXI_ENVIRONMENT_NAME} = "default"
${Env:PIXI_ENVIRONMENT_PLATFORMS} = "win-64"
${Env:PIXI_PROMPT} = "(deno_sample) "


$old_prompt = $function:prompt
function prompt {"(deno_sample) $($old_prompt.Invoke())"}

Env:Pathを見ると、ワークスペースの情報だけではなく、私のPCに存在するソフトウェアのパスも含まれている。従って、次のようなタスクも有効である。

pixi.toml
[tasks.java]
cmd = "java -version"

[tasks.gcc]
cmd = "gcc --version"

[tasks.qemu]
cmd = "qemu-system-x86_64 --version"

[tasks.find_text]
cmd = """ find '"world"' *.txt """

[tasks.ls]
cmd = "powershell -command ls -name"
PS C:\⋯> pixi run java
Pixi task (java): java -version
java version "20.0.1" 2023-04-18
Java(TM) SE Runtime Environment (build 20.0.1+9-29)
Java HotSpot(TM) 64-Bit Server VM (build 20.0.1+9-29, mixed mode, sharing)
PS C:\⋯> pixi run gcc
Pixi task (gcc): gcc --version
gcc.exe (x86_64-win32-seh-rev1, Built by MinGW-Builds project) 14.2.0
Copyright (C) 2024 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

PS C:\⋯> pixi run qemu
Pixi task (qemu): qemu-system-x86_64 --version
QEMU emulator version 9.0.0 (v9.0.0-12054-g923cf646f4)
Copyright (c) 2003-2024 Fabrice Bellard and the QEMU Project developers
PS C:\⋯> pixi run find_text
Pixi task (find_text):  find '"world"' *.txt
Access denied - \

---------- DIRNAMES.TXT

---------- HELLO_WORLD.TXT

---------- 放膽文.TXT
PS C:\⋯> pixi run ls
Pixi task (ls): powershell -command ls -name
.pixi
dir1
dir2
dir3
new_dir
.gitattributes
.gitignore
dirnames.txt
hello_world.txt
pixi.lock
pixi.toml
放膽文.txt

これらのタスクは完全に私のPCに依存しているため、同じWindowsであっても他の環境で実行できるとは限らない。

なおこの内、JavaGCCQEMUは、conda-forgeからパッケージとして入手することができる。環境に存在するものではなく、Pixiでインストールしたものを使うならば、互換性を保つことができる。

一方でfindpowershellは、WindowsのシステムフォルダーC:\Windows\System32に存在する実行ファイルである。Linuxにもfindlsコマンドは存在するが、実体が全く別物であるため、オプションの使い方が異なっており、結局互換性はない。

conda-forgeからはbashも手に入るため、Pixiでインストールしたbashを使うようにするならば、互換性は保たれるかもしれない。

補遺2.:タスクごとに分けた記述

本記事では一貫して、[tasks]の下にタスクを記述した。しかしタスクの定義が複雑になると、TOMLの性質から横に長く記述せざるを得ず、見悪くなる。

[tasks]
division = { cmd = """python -c '''
{% if not b == "0" %}
print({{ a }} / {{ b }})
{% else %}
print("Error: Division by zero")
{% endif %}
'''
""", args = [
    { "arg" = "a", "default" = "1" },
    { "arg" = "b", "default" = "1" }
] }
mkdir_src = { cmd = "mkdir -p src" }
create_main_py_cache = { cmd = "echo 'import sys\n\nprint(sys.version)' > src/main.py", depends-on = ["mkdir_src"], outputs = ["src/main.py"] }
run_main_py_cache = { cmd = "python src/main.py", depends-on = ["create_main_py_cache"], inputs = ["src/main.py"] }

このような場合、一つのタスクを次のようにして個々に記述することで、改行できるようになり、若干読みやすくなる。

[tasks]
mkdir_src = { cmd = "mkdir -p src" }

[tasks.division]
cmd = """python -c '''
{% if not b == "0" %}
print({{ a }} / {{ b }})
{% else %}
print("Error: Division by zero")
{% endif %}
'''"""
args = [
    { "arg" = "a", "default" = "1" },
    { "arg" = "b", "default" = "1" }
]

[tasks.create_main_py_cache]
cmd = "echo 'import sys\n\nprint(sys.version)' > src/main.py"
depends-on = ["mkdir_src"]
outputs = ["src/main.py"]

[tasks.run_main_py_cache]
cmd = "python src/main.py"
depends-on = ["create_main_py_cache"]
inputs = ["src/main.py"]

Discussion