casperbox 0.2.1__tar.gz → 0.2.2__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 (26) hide show
  1. {casperbox-0.2.1 → casperbox-0.2.2}/PKG-INFO +11 -26
  2. {casperbox-0.2.1 → casperbox-0.2.2}/README.md +10 -25
  3. {casperbox-0.2.1 → casperbox-0.2.2}/pyproject.toml +1 -1
  4. {casperbox-0.2.1 → casperbox-0.2.2}/src/casperbox/cli.py +12 -2
  5. {casperbox-0.2.1 → casperbox-0.2.2}/src/casperbox/podman.py +41 -4
  6. {casperbox-0.2.1 → casperbox-0.2.2}/tests/test_config.py +24 -0
  7. {casperbox-0.2.1 → casperbox-0.2.2}/uv.lock +1 -1
  8. {casperbox-0.2.1 → casperbox-0.2.2}/.gitignore +0 -0
  9. {casperbox-0.2.1 → casperbox-0.2.2}/src/casperbox/__init__.py +0 -0
  10. {casperbox-0.2.1 → casperbox-0.2.2}/src/casperbox/assets/common/packages/software.list +0 -0
  11. {casperbox-0.2.1 → casperbox-0.2.2}/src/casperbox/assets/common/rootfs/etc/profile.d/casper.sh +0 -0
  12. {casperbox-0.2.1 → casperbox-0.2.2}/src/casperbox/assets/common/rootfs/etc/ssh/sshd_config.d/casper.conf +0 -0
  13. {casperbox-0.2.1 → casperbox-0.2.2}/src/casperbox/assets/common/rootfs/usr/local/bin/entrypoint.sh +0 -0
  14. {casperbox-0.2.1 → casperbox-0.2.2}/src/casperbox/assets/common/scripts/config_mlib_devel.sh +0 -0
  15. {casperbox-0.2.1 → casperbox-0.2.2}/src/casperbox/assets/common/scripts/create_user.sh +0 -0
  16. {casperbox-0.2.1 → casperbox-0.2.2}/src/casperbox/assets/common/scripts/install_base.sh +0 -0
  17. {casperbox-0.2.1 → casperbox-0.2.2}/src/casperbox/assets/common/scripts/install_miniconda.sh +0 -0
  18. {casperbox-0.2.1 → casperbox-0.2.2}/src/casperbox/assets/common/scripts/install_scilab.sh +0 -0
  19. {casperbox-0.2.1 → casperbox-0.2.2}/src/casperbox/assets/common/scripts/setup_conda_env.sh +0 -0
  20. {casperbox-0.2.1 → casperbox-0.2.2}/src/casperbox/assets/common/vnc/xstartup +0 -0
  21. {casperbox-0.2.1 → casperbox-0.2.2}/src/casperbox/assets/versions/m2021a/Dockerfile +0 -0
  22. {casperbox-0.2.1 → casperbox-0.2.2}/src/casperbox/assets/versions/m2021a/toolflow.toml +0 -0
  23. {casperbox-0.2.1 → casperbox-0.2.2}/src/casperbox/config.py +0 -0
  24. {casperbox-0.2.1 → casperbox-0.2.2}/src/casperbox/errors.py +0 -0
  25. {casperbox-0.2.1 → casperbox-0.2.2}/src/casperbox/templates/casperbox.toml +0 -0
  26. {casperbox-0.2.1 → casperbox-0.2.2}/src/casperbox/versions.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: casperbox
3
- Version: 0.2.1
3
+ Version: 0.2.2
4
4
  Summary: Build and run CASPER toolflow development containers with rootless podman
5
5
  Requires-Python: >=3.11
6
6
  Requires-Dist: typer>=0.12
@@ -17,7 +17,7 @@ mlib_devel, device-tree-xlnx and Scilab. Vivado and the license are mounted from
17
17
 
18
18
  | Version | Base image | Vivado | Python | mlib_devel | device-tree-xlnx | Scilab |
19
19
  |---|---|---|---|---|---|---|
20
- | m2021a | Ubuntu 20.04 | 2021.1 | 3.14 | [liuweiseu/mlib_devel](https://github.com/liuweiseu/mlib_devel) `scilab-dev-ai` | xlnx_rel_v2021.1 | 2024.1.0 |
20
+ | m2021a | Ubuntu 20.04 | 2021.1 | 3.14 | scilab-dev-ai | xlnx_rel_v2021.1 | 2024.1.0 |
21
21
 
22
22
  ## Host requirements
23
23
 
@@ -27,30 +27,12 @@ mlib_devel, device-tree-xlnx and Scilab. Vivado and the license are mounted from
27
27
 
28
28
  ## Host setup
29
29
 
30
- Log in directly (SSH or desktop) as the user that runs podman (do not switch with `su`), then run:
30
+ Log in directly (SSH or desktop) as the user that runs podman, then run:
31
31
 
32
32
  ```bash
33
- loginctl enable-linger "$(id -un)" # keep the user's systemd running, also after logout
34
- grep "^$(id -un):" /etc/subuid /etc/subgid # should print one line each; otherwise ask an admin
35
- podman system migrate
36
- ls /run/user/$(id -u)/bus # the user's systemd bus must exist
37
- podman run --rm docker.io/library/ubuntu:20.04 true # must succeed with no WARN/ERROR
38
- ```
39
-
40
- If the last command fails with `sd-bus call: Access denied` (no user systemd session, e.g. after `su`),
41
- either point the shell at the user's bus:
42
-
43
- ```bash
44
- export XDG_RUNTIME_DIR=/run/user/$(id -u)
45
- export DBUS_SESSION_BUS_ADDRESS=unix:path=$XDG_RUNTIME_DIR/bus
46
- ```
47
-
48
- or stop podman from using systemd for cgroups:
49
-
50
- ```bash
51
- mkdir -p ~/.config/containers
52
- printf '[engine]\ncgroup_manager = "cgroupfs"\nevents_logger = "file"\n' >> ~/.config/containers/containers.conf
33
+ loginctl enable-linger "$(id -un)"
53
34
  podman system migrate
35
+ podman run --rm docker.io/library/ubuntu:20.04 true # check: must finish without errors
54
36
  ```
55
37
 
56
38
  ## Install
@@ -159,8 +141,11 @@ the interface exists only in the container's network namespace, so reusing the h
159
141
 
160
142
  | Change | Action |
161
143
  |---|---|
162
- | Directories, ports, passwords, MAC, mounts | `casperbox up` |
163
- | `container.user`, or a different host user (UID/GID changed) | `casperbox build`, then `casperbox up` |
144
+ | Anything | `casperbox up` |
145
+
146
+ `casperbox up` rebuilds the image automatically when it is out of date: when the build files
147
+ (Dockerfile, scripts, `software.list`), the toolflow version, `container.user` or the host UID/GID
148
+ changed since the last build. `casperbox status` shows whether the image is up to date.
164
149
 
165
150
  ## Development
166
151
 
@@ -182,7 +167,7 @@ uv run casperbox --help
182
167
  uv tool install -e . # installed casperbox uses this source tree
183
168
  ```
184
169
 
185
- Adding apt packages: add one per line to `assets/common/packages/software.list`, then `casperbox build`.
170
+ Adding apt packages: add one per line to `assets/common/packages/software.list`, then `casperbox up` (the image is rebuilt automatically).
186
171
 
187
172
  Adding a toolflow version: copy `assets/versions/m2021a` to `assets/versions/<new>`, edit
188
173
  `toolflow.toml` (`TOOLFLOW_VERSION` must match the directory name) and the Dockerfile as needed.
@@ -9,7 +9,7 @@ mlib_devel, device-tree-xlnx and Scilab. Vivado and the license are mounted from
9
9
 
10
10
  | Version | Base image | Vivado | Python | mlib_devel | device-tree-xlnx | Scilab |
11
11
  |---|---|---|---|---|---|---|
12
- | m2021a | Ubuntu 20.04 | 2021.1 | 3.14 | [liuweiseu/mlib_devel](https://github.com/liuweiseu/mlib_devel) `scilab-dev-ai` | xlnx_rel_v2021.1 | 2024.1.0 |
12
+ | m2021a | Ubuntu 20.04 | 2021.1 | 3.14 | scilab-dev-ai | xlnx_rel_v2021.1 | 2024.1.0 |
13
13
 
14
14
  ## Host requirements
15
15
 
@@ -19,30 +19,12 @@ mlib_devel, device-tree-xlnx and Scilab. Vivado and the license are mounted from
19
19
 
20
20
  ## Host setup
21
21
 
22
- Log in directly (SSH or desktop) as the user that runs podman (do not switch with `su`), then run:
22
+ Log in directly (SSH or desktop) as the user that runs podman, then run:
23
23
 
24
24
  ```bash
25
- loginctl enable-linger "$(id -un)" # keep the user's systemd running, also after logout
26
- grep "^$(id -un):" /etc/subuid /etc/subgid # should print one line each; otherwise ask an admin
27
- podman system migrate
28
- ls /run/user/$(id -u)/bus # the user's systemd bus must exist
29
- podman run --rm docker.io/library/ubuntu:20.04 true # must succeed with no WARN/ERROR
30
- ```
31
-
32
- If the last command fails with `sd-bus call: Access denied` (no user systemd session, e.g. after `su`),
33
- either point the shell at the user's bus:
34
-
35
- ```bash
36
- export XDG_RUNTIME_DIR=/run/user/$(id -u)
37
- export DBUS_SESSION_BUS_ADDRESS=unix:path=$XDG_RUNTIME_DIR/bus
38
- ```
39
-
40
- or stop podman from using systemd for cgroups:
41
-
42
- ```bash
43
- mkdir -p ~/.config/containers
44
- printf '[engine]\ncgroup_manager = "cgroupfs"\nevents_logger = "file"\n' >> ~/.config/containers/containers.conf
25
+ loginctl enable-linger "$(id -un)"
45
26
  podman system migrate
27
+ podman run --rm docker.io/library/ubuntu:20.04 true # check: must finish without errors
46
28
  ```
47
29
 
48
30
  ## Install
@@ -151,8 +133,11 @@ the interface exists only in the container's network namespace, so reusing the h
151
133
 
152
134
  | Change | Action |
153
135
  |---|---|
154
- | Directories, ports, passwords, MAC, mounts | `casperbox up` |
155
- | `container.user`, or a different host user (UID/GID changed) | `casperbox build`, then `casperbox up` |
136
+ | Anything | `casperbox up` |
137
+
138
+ `casperbox up` rebuilds the image automatically when it is out of date: when the build files
139
+ (Dockerfile, scripts, `software.list`), the toolflow version, `container.user` or the host UID/GID
140
+ changed since the last build. `casperbox status` shows whether the image is up to date.
156
141
 
157
142
  ## Development
158
143
 
@@ -174,7 +159,7 @@ uv run casperbox --help
174
159
  uv tool install -e . # installed casperbox uses this source tree
175
160
  ```
176
161
 
177
- Adding apt packages: add one per line to `assets/common/packages/software.list`, then `casperbox build`.
162
+ Adding apt packages: add one per line to `assets/common/packages/software.list`, then `casperbox up` (the image is rebuilt automatically).
178
163
 
179
164
  Adding a toolflow version: copy `assets/versions/m2021a` to `assets/versions/<new>`, edit
180
165
  `toolflow.toml` (`TOOLFLOW_VERSION` must match the directory name) and the Dockerfile as needed.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "casperbox"
3
- version = "0.2.1"
3
+ version = "0.2.2"
4
4
  description = "Build and run CASPER toolflow development containers with rootless podman"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -176,9 +176,13 @@ def up(ctx: typer.Context, config: ConfigOpt = None) -> None:
176
176
  podman.check_owner(cfg, own)
177
177
  podman.check_ports_free(cfg, own)
178
178
 
179
- if not podman.image_exists(cfg.toolflow.image):
179
+ state = podman.image_state(cfg)
180
+ if state == "missing":
180
181
  info(f"Image {cfg.toolflow.image} not found, building it")
181
182
  _build(cfg)
183
+ elif state == "outdated":
184
+ info(f"Image {cfg.toolflow.image} is out of date (build files, toolflow version or user changed), rebuilding it")
185
+ _build(cfg)
182
186
  podman.check_image_matches(cfg)
183
187
 
184
188
  if not cfg.work_dir.exists():
@@ -237,8 +241,14 @@ def shell(ctx: typer.Context, config: ConfigOpt = None) -> None:
237
241
 
238
242
  @app.command()
239
243
  def status(ctx: typer.Context, config: ConfigOpt = None) -> None:
240
- """Show the container status."""
244
+ """Show the image and container status."""
241
245
  cfg = _prepare(ctx, config)
246
+ image_note = {
247
+ "missing": "not built",
248
+ "outdated": "out of date, `casperbox up` or `casperbox build` will rebuild it",
249
+ "current": "up to date",
250
+ }[podman.image_state(cfg)]
251
+ typer.echo(f"{cfg.toolflow.image}: {image_note}")
242
252
  own = podman.get_container(cfg.name)
243
253
  if not own:
244
254
  typer.echo(f"{cfg.name}: not created")
@@ -2,6 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import hashlib
5
6
  import json
6
7
  import os
7
8
  import shutil
@@ -18,6 +19,8 @@ from .versions import assets_dir
18
19
  LABEL_MANAGED = "casperbox.managed"
19
20
  LABEL_CONFIG = "casperbox.config"
20
21
  LABEL_TOOLFLOW = "casperbox.toolflow"
22
+ # Label put on images: hash of the build inputs, used to detect outdated images
23
+ LABEL_BUILD_HASH = "casperbox.build-hash"
21
24
 
22
25
  # Environment passed to the container. Values are put in podman's own environment
23
26
  # and referenced by name (-e NAME), so passwords never appear on a command line.
@@ -73,22 +76,56 @@ def image_labels(image: str) -> dict[str, str]:
73
76
  return json.loads(out) or {}
74
77
 
75
78
 
76
- def build_args(cfg: Config, no_cache: bool = False) -> list[str]:
77
- args = {
79
+ def _build_arg_values(cfg: Config) -> dict[str, str]:
80
+ return {
78
81
  **cfg.toolflow.params,
79
82
  "CONTAINER_USER": cfg.user,
80
83
  "USER_UID": str(os.getuid()),
81
84
  "USER_GID": str(os.getgid()),
82
85
  }
83
- cmd = ["podman", "build", "-f", str(cfg.toolflow.dockerfile), "-t", cfg.toolflow.image]
86
+
87
+
88
+ def build_hash(cfg: Config) -> str:
89
+ """Hash of everything the image is built from: build args, the version's
90
+ Dockerfile/toolflow.toml and all shared build files under assets/common."""
91
+ h = hashlib.sha256()
92
+ for k, v in sorted(_build_arg_values(cfg).items()):
93
+ h.update(f"arg {k}={v}\n".encode())
94
+ root = assets_dir()
95
+ version_dir = cfg.toolflow.dockerfile.parent
96
+ files = sorted(
97
+ p for d in (root / "common", version_dir) for p in d.rglob("*")
98
+ if p.is_file() and "__pycache__" not in p.parts
99
+ )
100
+ for p in files:
101
+ h.update(f"file {p.relative_to(root).as_posix()}\n".encode())
102
+ h.update(p.read_bytes())
103
+ h.update(b"\n")
104
+ return h.hexdigest()
105
+
106
+
107
+ def build_args(cfg: Config, no_cache: bool = False) -> list[str]:
108
+ cmd = [
109
+ "podman", "build", "-f", str(cfg.toolflow.dockerfile), "-t", cfg.toolflow.image,
110
+ "--label", f"{LABEL_BUILD_HASH}={build_hash(cfg)}",
111
+ ]
84
112
  if no_cache:
85
113
  cmd.append("--no-cache")
86
- for k, v in args.items():
114
+ for k, v in _build_arg_values(cfg).items():
87
115
  cmd += ["--build-arg", f"{k}={v}"]
88
116
  cmd.append(str(assets_dir()))
89
117
  return cmd
90
118
 
91
119
 
120
+ def image_state(cfg: Config) -> str:
121
+ """'missing', 'outdated' or 'current'."""
122
+ if not image_exists(cfg.toolflow.image):
123
+ return "missing"
124
+ if image_labels(cfg.toolflow.image).get(LABEL_BUILD_HASH) != build_hash(cfg):
125
+ return "outdated"
126
+ return "current"
127
+
128
+
92
129
  def check_image_matches(cfg: Config) -> None:
93
130
  """The image user must match the config, otherwise mounted directories get wrong permissions."""
94
131
  labels = image_labels(cfg.toolflow.image)
@@ -170,3 +170,27 @@ def test_port_check_reports_all_conflicts(env, monkeypatch):
170
170
  assert f"{ssh}/tcp (ports.ssh): already in use" in msg
171
171
  assert "45999/tcp (ports.vnc): used by container other" in msg
172
172
  assert f"{extra}/tcp (extra_ports[0]): already in use" in msg
173
+
174
+
175
+ def test_build_hash_tracks_build_inputs(env, monkeypatch, tmp_path):
176
+ import shutil
177
+
178
+ from casperbox import podman, versions
179
+
180
+ # work on a copy of the assets so the test can modify build files
181
+ assets = tmp_path / "assets"
182
+ shutil.copytree(versions.assets_dir(), assets)
183
+ monkeypatch.setattr(versions, "assets_dir", lambda: assets)
184
+ monkeypatch.setattr(podman, "assets_dir", lambda: assets)
185
+
186
+ cfg = env()
187
+ h1 = podman.build_hash(cfg)
188
+ assert podman.build_hash(cfg) == h1 # deterministic
189
+ assert f"casperbox.build-hash={h1}" in podman.build_args(cfg)
190
+
191
+ with (assets / "common" / "packages" / "software.list").open("a") as f:
192
+ f.write("htop\n")
193
+ h2 = podman.build_hash(cfg)
194
+ assert h2 != h1 # build file changed
195
+
196
+ assert podman.build_hash(env(container__user="alice")) != h2 # build arg changed
@@ -13,7 +13,7 @@ wheels = [
13
13
 
14
14
  [[package]]
15
15
  name = "casperbox"
16
- version = "0.2.1"
16
+ version = "0.2.2"
17
17
  source = { editable = "." }
18
18
  dependencies = [
19
19
  { name = "typer" },
File without changes