xcrunner 0.1.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. xcrunner-0.1.3/.github/release-notes/v0.1.2.md +36 -0
  2. xcrunner-0.1.3/.github/release-notes/v0.1.3.md +34 -0
  3. xcrunner-0.1.3/.github/workflows/ci.yml +33 -0
  4. xcrunner-0.1.3/.github/workflows/release.yml +113 -0
  5. xcrunner-0.1.3/.gitignore +8 -0
  6. xcrunner-0.1.3/LICENSE +21 -0
  7. xcrunner-0.1.3/PKG-INFO +293 -0
  8. xcrunner-0.1.3/README.md +278 -0
  9. xcrunner-0.1.3/pyproject.toml +45 -0
  10. xcrunner-0.1.3/scripts/fetch_proot.py +47 -0
  11. xcrunner-0.1.3/scripts/mac_smoke.sh +120 -0
  12. xcrunner-0.1.3/src/xcodon_runtime/__init__.py +3 -0
  13. xcrunner-0.1.3/src/xcodon_runtime/_bin/LICENSE-proot +339 -0
  14. xcrunner-0.1.3/src/xcodon_runtime/_bin/MANIFEST +7 -0
  15. xcrunner-0.1.3/src/xcodon_runtime/_bin/proot-x86_64 +0 -0
  16. xcrunner-0.1.3/src/xcodon_runtime/api.py +471 -0
  17. xcrunner-0.1.3/src/xcodon_runtime/build.py +699 -0
  18. xcrunner-0.1.3/src/xcodon_runtime/buildpaths.py +246 -0
  19. xcrunner-0.1.3/src/xcodon_runtime/cli.py +803 -0
  20. xcrunner-0.1.3/src/xcodon_runtime/coala_adapter.py +160 -0
  21. xcrunner-0.1.3/src/xcodon_runtime/condaroot.py +123 -0
  22. xcrunner-0.1.3/src/xcodon_runtime/condarun.py +185 -0
  23. xcrunner-0.1.3/src/xcodon_runtime/condashim.py +374 -0
  24. xcrunner-0.1.3/src/xcodon_runtime/containers.py +137 -0
  25. xcrunner-0.1.3/src/xcodon_runtime/daemon.py +204 -0
  26. xcrunner-0.1.3/src/xcodon_runtime/engine.py +94 -0
  27. xcrunner-0.1.3/src/xcodon_runtime/engine_ns.py +275 -0
  28. xcrunner-0.1.3/src/xcodon_runtime/engine_proot.py +219 -0
  29. xcrunner-0.1.3/src/xcodon_runtime/envdir.py +134 -0
  30. xcrunner-0.1.3/src/xcodon_runtime/envrecord.py +525 -0
  31. xcrunner-0.1.3/src/xcodon_runtime/errors.py +37 -0
  32. xcrunner-0.1.3/src/xcodon_runtime/flatten.py +75 -0
  33. xcrunner-0.1.3/src/xcodon_runtime/forwarded.py +125 -0
  34. xcrunner-0.1.3/src/xcodon_runtime/home.py +149 -0
  35. xcrunner-0.1.3/src/xcodon_runtime/imagestore.py +452 -0
  36. xcrunner-0.1.3/src/xcodon_runtime/keeper.py +253 -0
  37. xcrunner-0.1.3/src/xcodon_runtime/layerdiff.py +310 -0
  38. xcrunner-0.1.3/src/xcodon_runtime/macvm.py +669 -0
  39. xcrunner-0.1.3/src/xcodon_runtime/micromamba.py +161 -0
  40. xcrunner-0.1.3/src/xcodon_runtime/nsexec.py +111 -0
  41. xcrunner-0.1.3/src/xcodon_runtime/pkgscan.py +326 -0
  42. xcrunner-0.1.3/src/xcodon_runtime/probe.py +117 -0
  43. xcrunner-0.1.3/src/xcodon_runtime/reference.py +148 -0
  44. xcrunner-0.1.3/src/xcodon_runtime/registry.py +239 -0
  45. xcrunner-0.1.3/src/xcodon_runtime/sandbox.py +77 -0
  46. xcrunner-0.1.3/src/xcodon_runtime/shim.py +109 -0
  47. xcrunner-0.1.3/src/xcodon_runtime/spec.py +124 -0
  48. xcrunner-0.1.3/src/xcodon_runtime/syscalls.py +175 -0
  49. xcrunner-0.1.3/src/xcodon_runtime/tarlayer.py +121 -0
  50. xcrunner-0.1.3/tests/__init__.py +0 -0
  51. xcrunner-0.1.3/tests/conftest.py +202 -0
  52. xcrunner-0.1.3/tests/fake_lima.py +123 -0
  53. xcrunner-0.1.3/tests/fake_micromamba.py +110 -0
  54. xcrunner-0.1.3/tests/fake_registry.py +164 -0
  55. xcrunner-0.1.3/tests/test_build.py +922 -0
  56. xcrunner-0.1.3/tests/test_build_e2e.py +125 -0
  57. xcrunner-0.1.3/tests/test_buildpaths.py +166 -0
  58. xcrunner-0.1.3/tests/test_cli.py +254 -0
  59. xcrunner-0.1.3/tests/test_cli_docker.py +288 -0
  60. xcrunner-0.1.3/tests/test_coala_adapter.py +173 -0
  61. xcrunner-0.1.3/tests/test_commit.py +300 -0
  62. xcrunner-0.1.3/tests/test_conda_e2e.py +56 -0
  63. xcrunner-0.1.3/tests/test_condarun.py +248 -0
  64. xcrunner-0.1.3/tests/test_condashim.py +651 -0
  65. xcrunner-0.1.3/tests/test_container_dir.py +162 -0
  66. xcrunner-0.1.3/tests/test_containers_api.py +258 -0
  67. xcrunner-0.1.3/tests/test_cwltool.py +54 -0
  68. xcrunner-0.1.3/tests/test_daemon.py +242 -0
  69. xcrunner-0.1.3/tests/test_daemon_e2e.py +38 -0
  70. xcrunner-0.1.3/tests/test_engine_ns.py +334 -0
  71. xcrunner-0.1.3/tests/test_engine_proot.py +199 -0
  72. xcrunner-0.1.3/tests/test_engine_select.py +47 -0
  73. xcrunner-0.1.3/tests/test_env_dir.py +351 -0
  74. xcrunner-0.1.3/tests/test_env_layer_dir.py +95 -0
  75. xcrunner-0.1.3/tests/test_envrecord.py +486 -0
  76. xcrunner-0.1.3/tests/test_envrecord_wiring.py +164 -0
  77. xcrunner-0.1.3/tests/test_flatten.py +83 -0
  78. xcrunner-0.1.3/tests/test_forwarded.py +104 -0
  79. xcrunner-0.1.3/tests/test_home.py +154 -0
  80. xcrunner-0.1.3/tests/test_imagestore.py +234 -0
  81. xcrunner-0.1.3/tests/test_keeper_plan.py +42 -0
  82. xcrunner-0.1.3/tests/test_layerdiff.py +198 -0
  83. xcrunner-0.1.3/tests/test_macvm.py +212 -0
  84. xcrunner-0.1.3/tests/test_macvm_forward.py +217 -0
  85. xcrunner-0.1.3/tests/test_micromamba.py +132 -0
  86. xcrunner-0.1.3/tests/test_micromamba_platforms.py +99 -0
  87. xcrunner-0.1.3/tests/test_network.py +25 -0
  88. xcrunner-0.1.3/tests/test_pkgscan.py +406 -0
  89. xcrunner-0.1.3/tests/test_platform_fallback.py +125 -0
  90. xcrunner-0.1.3/tests/test_reference.py +69 -0
  91. xcrunner-0.1.3/tests/test_registry.py +170 -0
  92. xcrunner-0.1.3/tests/test_sandbox.py +125 -0
  93. xcrunner-0.1.3/tests/test_shim_conda.py +103 -0
  94. xcrunner-0.1.3/tests/test_spec.py +111 -0
  95. xcrunner-0.1.3/tests/test_stale_tag.py +148 -0
  96. xcrunner-0.1.3/tests/test_store_metadata.py +131 -0
  97. xcrunner-0.1.3/tests/test_syscalls.py +73 -0
  98. xcrunner-0.1.3/tests/test_tarlayer.py +199 -0
  99. xcrunner-0.1.3/tests/test_vendored.py +29 -0
  100. xcrunner-0.1.3/tests/test_version.py +10 -0
@@ -0,0 +1,36 @@
1
+ First release on PyPI. The package is `xc-xrunner`, and the command is `xrunner`.
2
+
3
+ xrunner is a rootless container runtime for Docker images. It runs the images that coala and coala-runtime use on hosts with no Docker and no root.
4
+
5
+ ## Install
6
+
7
+ pip install xc-xrunner
8
+ xrunner info
9
+
10
+ Add the `zstd` extra for zstd-compressed image layers: `pip install 'xc-xrunner[zstd]'`.
11
+
12
+ ## Changes since v0.1.1
13
+
14
+ - The package is renamed from `xcodon-runtime` to `xc-xrunner`, its name on PyPI. The Python module is still `xcodon_runtime`. If an older copy is installed, uninstall `xcodon-runtime` before installing `xc-xrunner`.
15
+ - v0.1.1 was published on GitHub only, under the old name.
16
+
17
+ ## What it does
18
+
19
+ - **Two engines, picked at startup.** The ns engine uses user namespaces and overlayfs at native speed. The proot engine uses a bundled PRoot and works on any Linux.
20
+ - **Images.** Pulls anonymously from Docker Hub, quay.io and ghcr.io. Reuses images from a local Docker daemon.
21
+ - **Docker-style commands.** `run`, `create`, `start`, `exec`, `stop`, `rm`, `pull`, `rmi`, `tag`, `logs` and `prune`.
22
+ - **Build and commit without a daemon.** `xrunner build` runs a Dockerfile subset. `xrunner commit` snapshots a container.
23
+ - **Docker shim.** A `docker` command that sends supported calls to xrunner, for tools such as cwltool.
24
+ - **Conda shim.** `conda`, `mamba` and `micromamba` commands over a pinned micromamba, kept apart from the user's own conda.
25
+ - **Env folders.** `--env-dir` and `XRUNNER_ENV_DIR` keep installed tools across containers.
26
+ - **Environment record.** Each project gets a record of the images and packages it used.
27
+ - **Sandbox activation.** `xrunner sandbox activate` sets up a project's shims and prints the settings a shell needs.
28
+ - **Clusters.** `XRUNNER_CONTAINER_DIR` keeps container folders on a node-local disk while images stay on shared storage.
29
+ - **Integrations.** Works with coala through cwltool, and with coala-runtime as a container engine.
30
+
31
+ ## Known limits
32
+
33
+ - One user id inside a container.
34
+ - Host network only.
35
+ - No cgroups and no GPU passthrough.
36
+ - The conda shim and the bundled PRoot are for x86_64 only.
@@ -0,0 +1,34 @@
1
+ xrunner is now **xcrunner**: the command, the PyPI package, the settings and the folders. This release also adds experimental macOS support and three features that help on every platform.
2
+
3
+ pip install xcrunner
4
+
5
+ ## The rename
6
+
7
+ This is a clean break. Nothing reads the old names.
8
+
9
+ - The command is `xcrunner`. The PyPI package is `xcrunner`; `xc-xrunner` gets no more releases. Uninstall it first: `pip uninstall xc-xrunner`.
10
+ - Every `XRUNNER_*` setting is now `XCRUNNER_*`, for example `XCRUNNER_ENV_DIR`.
11
+ - A project's env folder is now `.xcrunner-env`. An existing `.xrunner-env` is not used; rename it, or let xcrunner start a new one.
12
+ - The engine value in coala and coala-runtime is `xcrunner`, and coala-runtime's extra is `[xcrunner]`.
13
+ - The repository is `xcodon-ai/xcrunner`. The Python module is still `xcodon_runtime`, and the `XCODON_*` settings keep their names.
14
+
15
+ ## macOS (experimental)
16
+
17
+ xcrunner now runs on Apple silicon Macs with macOS 26 or newer. It runs the Linux xcrunner inside one Lima VM.
18
+
19
+ brew install lima
20
+ pip install xcrunner
21
+ xcrunner machine start
22
+ xcrunner run --rm alpine echo hi
23
+
24
+ - The first `xcrunner machine start` creates an Ubuntu 24.04 VM with Rosetta. Your home folder, `/private/var/folders` and `/private/tmp` are shared into it at the same paths.
25
+ - `conda`, `shim`, `sandbox` and `machine` run on the Mac. Every other command runs in the VM, with the same exit code and output.
26
+ - `xcrunner machine start|stop|status|shell|rm` manage the VM. The `XCRUNNER_MACHINE_*` settings set its size and extra shared folders.
27
+ - This release was tested on Linux against a fake Lima only. It has not run on a real Mac yet. `scripts/mac_smoke.sh` runs the end-to-end checks on a Mac; please report what fails.
28
+
29
+ ## All platforms
30
+
31
+ - **`XCRUNNER_ENV_LAYER_DIR`** keeps env folder layers on another disk, while the environment record stays in the project. On a cluster, point it at node-local disk.
32
+ - **x86_64 images on ARM hosts.** With a Rosetta or qemu-x86_64 handler registered, an image list without an ARM image falls back to x86_64.
33
+ - **Pull-time architecture check.** An image the host cannot run now fails at pull time with a clear message, instead of failing later with "exec format error". An explicit `--platform` skips the check.
34
+ - **Conda shim on macOS ARM.** The shim pins micromamba 2.9.0 for `osx-arm64` as well as `linux-64`. A failed install on a Mac suggests `--platform osx-64`.
@@ -0,0 +1,33 @@
1
+ name: ci
2
+ on: [push, pull_request]
3
+ jobs:
4
+ test:
5
+ runs-on: ubuntu-latest
6
+ strategy:
7
+ matrix:
8
+ python: ["3.10", "3.12"]
9
+ steps:
10
+ - uses: actions/checkout@v4
11
+ - uses: actions/setup-python@v5
12
+ with:
13
+ python-version: ${{ matrix.python }}
14
+ - run: sudo apt-get update && sudo apt-get install -y busybox-static
15
+ - run: pip install -e ".[dev,zstd]"
16
+ - run: ruff check src tests
17
+ - run: xcrunner info
18
+ # cwltool is not installed in this job, so the cwltool-marked tests skip by design.
19
+ - run: pytest -q -rs
20
+ - run: XCODON_TEST_NETWORK=1 pytest -q -rs -m network
21
+ # The Mac side only: no VM on GitHub's macOS runners. Catches Linux-only imports
22
+ # and runs the tests that do not need Linux (spec 16.10).
23
+ macos:
24
+ runs-on: macos-15
25
+ steps:
26
+ - uses: actions/checkout@v4
27
+ - uses: actions/setup-python@v5
28
+ with:
29
+ python-version: "3.12"
30
+ - run: pip install -e ".[dev,zstd]"
31
+ - run: python -c "import xcodon_runtime.cli, xcodon_runtime.macvm, xcodon_runtime.forwarded"
32
+ - run: xcrunner --version
33
+ - run: pytest -q -rs tests/test_macvm.py tests/test_macvm_forward.py tests/test_forwarded.py tests/test_micromamba_platforms.py
@@ -0,0 +1,113 @@
1
+ name: Release
2
+
3
+ # A pushed v* tag builds the package, creates the GitHub release with the wheel
4
+ # and sdist attached, and publishes to PyPI. For a tag pushed before this
5
+ # workflow existed, run it by hand: gh workflow run release.yml -f tag=v0.1.1
6
+
7
+ on:
8
+ push:
9
+ tags: ["v*"]
10
+ workflow_dispatch:
11
+ inputs:
12
+ tag:
13
+ description: "Existing tag to build and release, for example v0.1.1"
14
+ required: true
15
+
16
+ permissions:
17
+ contents: read
18
+
19
+ concurrency:
20
+ group: release-${{ inputs.tag || github.ref_name }}
21
+
22
+ jobs:
23
+ build:
24
+ name: Build
25
+ runs-on: ubuntu-latest
26
+ outputs:
27
+ tag: ${{ steps.tag.outputs.tag }}
28
+ steps:
29
+ - name: Pick the tag
30
+ id: tag
31
+ env:
32
+ TAG: ${{ inputs.tag || github.ref_name }}
33
+ run: echo "tag=$TAG" >> "$GITHUB_OUTPUT"
34
+
35
+ - name: Checkout
36
+ uses: actions/checkout@v4
37
+ with:
38
+ ref: ${{ steps.tag.outputs.tag }}
39
+
40
+ - name: Install uv
41
+ uses: astral-sh/setup-uv@v8.1.0
42
+ with:
43
+ python-version: "3.11"
44
+
45
+ - name: Check the tag matches the package version
46
+ env:
47
+ TAG: ${{ steps.tag.outputs.tag }}
48
+ run: |
49
+ version=$(python3 -c "import tomllib; print(tomllib.load(open('pyproject.toml', 'rb'))['project']['version'])")
50
+ test "${TAG#v}" = "$version" || { echo "tag $TAG does not match version $version"; exit 1; }
51
+
52
+ - name: Build package
53
+ run: uv build
54
+
55
+ - name: Check the wheel carries the PRoot binary
56
+ run: unzip -l dist/*.whl | grep -q 'xcodon_runtime/_bin/proot-x86_64'
57
+
58
+ - name: Keep the files
59
+ uses: actions/upload-artifact@v4
60
+ with:
61
+ name: dist
62
+ path: dist/
63
+
64
+ github-release:
65
+ name: GitHub release
66
+ needs: build
67
+ runs-on: ubuntu-latest
68
+ permissions:
69
+ contents: write
70
+ steps:
71
+ - uses: actions/download-artifact@v4
72
+ with:
73
+ name: dist
74
+ path: dist/
75
+
76
+ - name: Checkout the tag, for its release notes
77
+ uses: actions/checkout@v4
78
+ with:
79
+ ref: ${{ needs.build.outputs.tag }}
80
+ path: src
81
+
82
+ # .github/release-notes/<tag>.md in the tagged commit becomes the release
83
+ # body; without one, GitHub generates the notes.
84
+ - name: Create the release, or add the files to it
85
+ env:
86
+ GH_TOKEN: ${{ github.token }}
87
+ TAG: ${{ needs.build.outputs.tag }}
88
+ run: |
89
+ notes=(--generate-notes)
90
+ if [ -f "src/.github/release-notes/$TAG.md" ]; then notes=(--notes-file "src/.github/release-notes/$TAG.md"); fi
91
+ if gh release view "$TAG" -R "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
92
+ gh release upload "$TAG" dist/* --clobber -R "$GITHUB_REPOSITORY"
93
+ else
94
+ gh release create "$TAG" dist/* --title "$TAG" "${notes[@]}" --verify-tag -R "$GITHUB_REPOSITORY"
95
+ fi
96
+
97
+ pypi:
98
+ name: Publish to PyPI
99
+ needs: build
100
+ runs-on: ubuntu-latest
101
+ environment: pypi
102
+ permissions:
103
+ id-token: write
104
+ steps:
105
+ - uses: actions/download-artifact@v4
106
+ with:
107
+ name: dist
108
+ path: dist/
109
+
110
+ - name: Publish to PyPI
111
+ uses: pypa/gh-action-pypi-publish@release/v1
112
+ with:
113
+ skip-existing: true
@@ -0,0 +1,8 @@
1
+ .superpowers/
2
+ __pycache__/
3
+ *.pyc
4
+ .venv/
5
+ dist/
6
+ .pytest_cache/
7
+ .ruff_cache/
8
+ docs/superpowers/
xcrunner-0.1.3/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Qiang Hu
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,293 @@
1
+ Metadata-Version: 2.5
2
+ Name: xcrunner
3
+ Version: 0.1.3
4
+ Summary: Rootless container runtime for Docker images, built for coala
5
+ License: MIT
6
+ License-File: LICENSE
7
+ Requires-Python: >=3.10
8
+ Provides-Extra: dev
9
+ Requires-Dist: pytest>=8; extra == 'dev'
10
+ Requires-Dist: pyyaml>=6; extra == 'dev'
11
+ Requires-Dist: ruff>=0.4; extra == 'dev'
12
+ Provides-Extra: zstd
13
+ Requires-Dist: zstandard>=0.21; extra == 'zstd'
14
+ Description-Content-Type: text/markdown
15
+
16
+ # xcrunner
17
+
18
+ A rootless container runtime for Docker images. It runs the images that
19
+ coala and coala-runtime use on hosts without Docker and without root.
20
+
21
+ Two engines, chosen automatically:
22
+
23
+ - **ns**: kernel user namespaces plus overlayfs, no helper binary. Native
24
+ speed. Needs a Linux kernel 5.11 or newer with unprivileged user namespaces
25
+ enabled. Most workstations, cloud VMs, and current HPC nodes qualify.
26
+ - **proot**: a vendored static PRoot runs the container under ptrace. Works
27
+ on any Linux, slower, and creation copies the image rootfs.
28
+
29
+ ## Install
30
+
31
+ pip install xcrunner # or: uv pip install xcrunner
32
+ xcrunner info # shows the engine and probe results
33
+
34
+ Optional: `pip install 'xcrunner[zstd]'` for zstd-compressed layers.
35
+ On aarch64 hosts without user namespaces, provide a PRoot binary with
36
+ `XCODON_PROOT=/path/to/proot`. The command is `xcrunner`. The package on PyPI is `xcrunner`, and the Python module is `xcodon_runtime`.
37
+ ## Use
38
+
39
+ xcrunner pull python:3.12-slim
40
+ xcrunner run --rm -v $PWD:/work -w /work python:3.12-slim python -c 'print("hi")'
41
+ xcrunner create --name dev python:3.12-slim
42
+ xcrunner start dev
43
+ xcrunner exec dev pip install numpy # persists in the container's writable layer
44
+ xcrunner exec dev python -c 'import numpy'
45
+ xcrunner stop dev && xcrunner rm dev
46
+ xcrunner logs dev # the ns engine's keeper log
47
+ xcrunner rmi python:3.12-slim # drop a tag, and the image with its last tag
48
+ xcrunner prune # leftovers: half-built dirs and orphan blobs
49
+ xcrunner prune --all # also unused layers and day-old exited containers
50
+
51
+ xcrunner run --rm --env-dir $PWD/.xcrunner-env python:3.12-slim pip install numpy
52
+ xcrunner run --rm --env-dir $PWD/.xcrunner-env python:3.12-slim python -c 'import numpy'
53
+
54
+ `--env-dir` keeps the container's writable layer in a host folder, keyed by
55
+ image id, so tools installed in one container are there for the next one.
56
+ Delete `<env-dir>/<image-id>` to reset. coala-runtime uses this through the
57
+ `XCRUNNER_ENV_DIR` variable.
58
+
59
+ Two global flags come before the subcommand: `--engine ns|proot` forces an
60
+ engine, and `--home DIR` picks the state directory for this one command.
61
+
62
+ Images already in a local Docker daemon are reused through `docker save`, so
63
+ locally built images work without a registry. xcrunner records the daemon's
64
+ image id at import. When the daemon's tag later points at another image
65
+ (for example after a `docker build` there), the next `run`, `create`, or
66
+ `build` that uses the tag imports it again. `--pull never` and image ids
67
+ skip this check.
68
+
69
+ ## Build and commit
70
+
71
+ xcrunner build -t myapp:1 . # runs a Dockerfile subset
72
+ xcrunner commit -m "installed numpy" dev myapp:2 # snapshot a stopped container
73
+ xcrunner tag myapp:2 myapp:latest
74
+
75
+ `xcrunner build` runs each Dockerfile instruction in its own container and
76
+ commits one image per step, so unchanged steps are cached. The cache lives
77
+ in `<home>/build-cache.json`. The step images are untagged, and
78
+ `xcrunner prune --all` removes untagged images, which empties the cache in
79
+ effect. `xcrunner commit` also works on an `--env-dir` folder instead of a
80
+ container: `xcrunner commit --env-dir $PWD/.xcrunner-env --image myapp:1 myapp:2`.
81
+
82
+ Docker-style names work too:
83
+
84
+ xcrunner image inspect myapp:1 # also: image ls, image rm
85
+ xcrunner docker build -t myapp:1 . # any docker verb xcrunner supports
86
+ xcrunner docker image inspect --format '{{.Id}}' myapp:1
87
+
88
+ `xcrunner docker VERB ...` takes docker's own verbs and flags for `build`,
89
+ `image inspect|ls|rm`, `images`, `rmi`, `tag`, `pull`, `run`, `create`,
90
+ `start`, `exec`, `stop`, `rm`, `ps`, `logs`, `commit`, `inspect`, `version`, and
91
+ `info`. Other verbs exit with code 125 and a message.
92
+
93
+ For tools that shell out to a real `docker` binary directly — running
94
+ `docker build` or `docker image inspect` as a subprocess, rather than going
95
+ through cwltool's `--user-space-docker-cmd` — `xcrunner shim install` writes
96
+ a `docker` script that forwards every call to `xcrunner docker`:
97
+
98
+ xcrunner shim install --dir ~/.local/bin
99
+ export PATH="$HOME/.local/bin:$PATH"
100
+ docker build -t myapp:1 .
101
+ docker image inspect myapp:1
102
+
103
+ The shim refuses to install while any real `docker` is anywhere on PATH,
104
+ even outside DIR, unless you pass `--force`. It also refuses to replace a
105
+ `DIR/docker` that is not an xcrunner shim unless you pass `--force`, and it
106
+ never replaces a directory. xcrunner also never mistakes its own shim for a
107
+ real Docker daemon: image pulls that would reuse a local `docker save` skip
108
+ a shim.
109
+
110
+ Limits on the Dockerfile subset: no multi-stage builds (a second `FROM`, or
111
+ `--from=`), no `.dockerignore`, and no remote `ADD`/`COPY` from a URL.
112
+
113
+ ## Tools without conda
114
+
115
+ Agents often install command-line tools with `conda create`, `conda install` and
116
+ `conda run`. On a host with no conda, xcrunner can answer those calls itself:
117
+
118
+ xcrunner shim install conda --dir ~/.xcodon/shim
119
+ export PATH="$HOME/.xcodon/shim:$PATH"
120
+
121
+ This downloads a pinned micromamba (2.9.0, from conda-forge, checksum-verified) to
122
+ `<xcrunner home>/bin/micromamba-2.9.0/micromamba` and writes `conda`, `mamba` and
123
+ `micromamba` scripts that forward to `xcrunner conda`.
124
+ Offline, pass `--micromamba PATH` to use a binary you already have.
125
+
126
+ - Environments live in the project's `.xcrunner-env/conda` (found from the working
127
+ directory, or from `XCRUNNER_ENV_DIR`), else under the xcrunner home. Downloads are
128
+ cached once under the xcrunner home. A `.xcrunner-env` found by searching upward is
129
+ used only when you own it and no one else can write to it.
130
+ - Launchers should export `XCRUNNER_ENV_DIR=<project>/.xcrunner-env`, or create
131
+ `<project>/.xcrunner-env`, before the agent starts. Otherwise an early conda call
132
+ falls back to the xcrunner home: its envs land outside the project, `conda env
133
+ list` in the project does not show them, and the agent's `run_shell` guardrail
134
+ refuses their paths.
135
+ - A relative `-p PATH` is always a folder under the working directory, even
136
+ without a `/`.
137
+ - Your own `~/.conda` and `~/.condarc` are never read or written.
138
+ - `conda run -n NAME CMD` runs CMD with the env on PATH; `conda activate` is not
139
+ supported, because it changes the calling shell.
140
+ - Each env gets `conda-explicit.txt`, listing every package URL and checksum, so it
141
+ can be rebuilt with `conda create -p PATH --file conda-explicit.txt`.
142
+ - The shim refuses to install while a real conda, mamba or micromamba is on PATH,
143
+ unless you pass `--force`.
144
+ - The shims use the default xcrunner home, or `XCODON_RUNTIME_HOME` when it is set;
145
+ they do not remember an `xcrunner --home H` given at install time. After
146
+ `xcrunner --home H shim install conda ...`, also export `XCODON_RUNTIME_HOME=H`
147
+ wherever the shims run, or they will look for micromamba in the default home
148
+ instead.
149
+
150
+ ## Sandbox activation
151
+
152
+ `xcrunner sandbox activate /abs/project/.xcrunner-env` writes `docker`, `conda`,
153
+ `mamba` and `micromamba` shims into the project's `.xcrunner-env/bin` and prints
154
+ the two `export` lines (PATH first, then `XCRUNNER_ENV_DIR`) that send a shell's
155
+ docker and conda calls to xcrunner. The Python API is
156
+ `xcodon_runtime.sandbox.activate(env_dir)`. opencodon calls it by itself when
157
+ its container engine is `xcrunner`, so `--container-engine xcrunner` is enough.
158
+
159
+ ## Environment record
160
+
161
+ xcrunner keeps `.xcrunner-env/environment.json`, a per-project record of the images a
162
+ project used (id, source, registry digests, and any build Dockerfile) and the
163
+ packages each install added, changed or removed.
164
+
165
+ For each image it also keeps the platform, package counts, and for built images
166
+ the packages the build added. `.xcrunner-env/packages/<image id>.json` holds the full
167
+ package list of each image the project used, not only the changes.
168
+
169
+ The record updates automatically after conda changes and when containers stop.
170
+ `xcrunner env show` prints it, `xcrunner env show --json` prints the file, and
171
+ `xcrunner env record` rebuilds it from what is on disk. Images keep their entries as
172
+ history; `env record` never removes one.
173
+
174
+ It is a record, not a restore mechanism: it does not recreate images or packages,
175
+ only describes what happened. Images built before this feature have no Dockerfile
176
+ on record. On the proot engine each container stop rescans the rootfs copy, which
177
+ takes about 3-4 s on large images.
178
+
179
+ ## Hosts without docker
180
+
181
+ On a host with no docker daemon, a `FROM` name that only ever existed in a
182
+ local daemon, such as `coala-runtime-python:latest`, is looked up on Docker
183
+ Hub and not found there. Seed such base images once: pull the published
184
+ image and give it the local name.
185
+
186
+ xcrunner pull hubentu/coala-runtime-python:latest
187
+ xcrunner tag hubentu/coala-runtime-python:latest coala-runtime-python:latest
188
+ xcrunner pull hubentu/coala-runtime-r:latest
189
+ xcrunner tag hubentu/coala-runtime-r:latest coala-runtime-r:latest
190
+
191
+ After that, `FROM coala-runtime-python:latest` uses the stored image and
192
+ never goes to the network. xcrunner does not map image names itself.
193
+
194
+ ## Clusters with shared storage
195
+
196
+ Overlayfs cannot write its upper layer to NFS, Lustre or GPFS. When the home is on
197
+ shared storage, the overlay probe fails and xcrunner falls back to the slower proot
198
+ engine. Keep the image store on shared storage and put container folders on a
199
+ node-local disk:
200
+
201
+ export XCODON_RUNTIME_HOME=/shared/$USER/xcrunner
202
+ export XCRUNNER_CONTAINER_DIR=${TMPDIR:-/tmp}/xcrunner-containers
203
+
204
+ Images are pulled once into the shared home and read from there. Each container's
205
+ writable layer, its keeper log and its locks live under `XCRUNNER_CONTAINER_DIR`.
206
+ Containers are then local to one node: `xcrunner ps` on another node does not list
207
+ them. `xcrunner info` shows both folders and the engine the probes chose.
208
+
209
+ The setting does not move an env folder's layers. On the ns engine they must also be
210
+ on a local disk. Set `XCRUNNER_ENV_LAYER_DIR` to node-local storage: the record stays
211
+ in the project's env folder, and each node keeps its own layers for as long as that
212
+ disk lasts. Or set `XCODON_ENGINE=proot` to keep the layers on shared storage.
213
+
214
+ ## macOS
215
+
216
+ On an Apple silicon Mac with macOS 26 or newer, xcrunner runs the Linux xcrunner
217
+ inside one Lima VM. Install Lima first:
218
+
219
+ brew install lima
220
+ pip install xcrunner
221
+ xcrunner machine start # the first start creates the VM and takes a few minutes
222
+ xcrunner run --rm alpine echo hi
223
+
224
+ - The VM is a Lima `vz` VM named `xcrunner` with Ubuntu 24.04 and Rosetta. Images,
225
+ layers and containers live on its own disk.
226
+ - Your home folder, `/private/var/folders` and `/private/tmp` are shared into the VM
227
+ at the same paths, so bind mounts and cwltool's temp folders work unchanged. Work
228
+ in one of those folders, or add more with `XCRUNNER_MACHINE_MOUNTS` before the VM is
229
+ created.
230
+ - `conda`, `shim`, `sandbox` and `machine` run on the Mac. Every other command runs
231
+ in the VM. The conda shim installs macOS programs with a macOS micromamba.
232
+ - Env folder layers live on the VM disk; the record in `.xcrunner-env` stays with the
233
+ project. `xcrunner info` shows where the layers are.
234
+ - x86_64-only images, such as most biocontainers, run through Rosetta.
235
+ - `xcrunner machine status|stop|shell|rm` manage the VM. `rm` deletes its disk,
236
+ with all images and env layers.
237
+
238
+ `scripts/mac_smoke.sh` runs the end-to-end checks on a Mac and writes a report to
239
+ `~/xcrunner-mac-smoke.txt`.
240
+
241
+ ## Environment variables
242
+
243
+ | Variable | Meaning |
244
+ |---|---|
245
+ | `XCODON_RUNTIME_HOME` | State directory. Default `~/.xcodon/runtime`. |
246
+ | `XCRUNNER_CONTAINER_DIR` | Container folders. Default `<home>/containers`. |
247
+ | `XCRUNNER_ENV_LAYER_DIR` | Env folder layers on another disk. Default: in the env folder. On a cluster, a node-local disk gives each node its own layers. |
248
+ | `XCRUNNER_MACHINE_NAME` | macOS: the Lima VM's name. Default `xcrunner`. |
249
+ | `XCRUNNER_MACHINE_CPUS`, `XCRUNNER_MACHINE_MEMORY`, `XCRUNNER_MACHINE_DISK` | macOS: VM size when it is created. Defaults `4`, `4GiB`, `100GiB`. |
250
+ | `XCRUNNER_MACHINE_MOUNTS` | macOS: extra Mac folders to share, separated by `:`. |
251
+ | `XCODON_ENGINE` | `ns` or `proot`. Skips probing. |
252
+ | `XCODON_PROOT` | Path to a PRoot binary. |
253
+ | `XCODON_PROOT_ARGS` | Extra PRoot flags, for example `-k 5.15.0`. |
254
+ | `XCODON_LOG` | `info` or `debug`. |
255
+
256
+ ## Limits
257
+
258
+ - One uid inside the container. Every image file is owned by that uid.
259
+ `chown` to another user fails. Setuid binaries do not elevate.
260
+ - Host network only. No `--net=none`, no port mapping.
261
+ - No cgroups. `--memory` and `--cpus` are accepted and ignored with a warning.
262
+ - No GPU passthrough.
263
+ - macOS: Apple silicon and macOS 26 or newer only, through a Lima VM.
264
+ - Read-only binds apply to the top mount only; submounts under a bound host
265
+ path stay writable.
266
+
267
+ ## coala
268
+
269
+ In coala, add one branch to `configure_container_runner`:
270
+
271
+ ```python
272
+ if container_runner == "xcrunner":
273
+ runtime_context.user_space_docker_cmd = shutil.which("xcrunner") or "xcrunner"
274
+ ```
275
+
276
+ cwltool then calls `xcrunner inspect`, `xcrunner pull`, and `xcrunner run` with
277
+ docker-style flags.
278
+
279
+ ## coala-runtime
280
+
281
+ xcodon-runtime ships `xcodon_runtime.coala_adapter.XcodonContainerManager`,
282
+ which implements coala-runtime's `ContainerManager` interface. In
283
+ coala-runtime, add `XCRUNNER = "xcrunner"` to `ContainerEngine`, return the
284
+ adapter from `make_container_manager` for that value, and try it in
285
+ autodetection after Docker and Podman and before Apptainer.
286
+
287
+ ## Development
288
+
289
+ uv venv .venv && . .venv/bin/activate
290
+ uv pip install -e ".[dev,zstd]"
291
+ python scripts/fetch_proot.py # only if _bin/ is missing
292
+ pytest -q # unit + ns + proot on a capable host
293
+ XCODON_TEST_NETWORK=1 pytest -m network