Skip to content

Conversation

@junhaoliao
Copy link
Member

@junhaoliao junhaoliao commented Sep 25, 2025

Description

Developer of the CLP project may be sharing the same machine or run multiple builds on the machine. Previously, in the clp-package image build script, we always tag the image builds with tag "clp-package:dev", which causes any older build, whether owned by the current user or not, to be deleted.

This PR adds the --iidfile to the docker build (buildx) command so that a unique image SHA256 ID is written to the specified location. Then the docker-image:package copies the generated image.id file into the package. In a future PR, the package start script will read this image.id file and use it for CLP service executions.

Checklist

  • The PR satisfies the contribution guidelines.
  • This is a breaking change and that has been indicated in the PR title, OR this isn't a
    breaking change.
  • Necessary docs have been updated, OR no docs need to be updated.

Validation performed

This ran successfully:

junhao@GIGABYTE:~/workspace/clp$ task docker-images:package
...
junhao@GIGABYTE:~/workspace/clp$ cat build/clp-package/image.id 
sha256:39c43e97507ef73cfe06e1441fe1487a590f04d786ac065dd500d2e64b96df01

Summary by CodeRabbit

  • Chores

    • Improved container image build workflow with automatic cleanup of previous images and temporary files to avoid leftover artifacts.
    • Builds now capture and persist image IDs, then auto-tag images with a dev user plus a short generated ID for easier identification.
    • When built inside a Git repository, images include revision and source labels for better traceability.
  • Documentation

    • Added Docker prerequisites and a reference link to Docker installation documentation.

@junhaoliao junhaoliao requested a review from a team as a code owner September 25, 2025 19:08
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 25, 2025

Walkthrough

Adds image lifecycle and IID handling to the clp-package Docker build script (temporary IID file, persistent IID, EXIT trap cleanup, previous-image removal, conditional Git OCI labels), and updates package build documentation with Docker prerequisites and a Docker reference link.

Changes

Cohort / File(s) Summary
Docker build lifecycle for clp-package
tools/docker-images/clp-package/build.sh
Initializes iid_file, prev_image_id, temp_iid_file, new_image_id; adds remove_prev_image_and_temp_file and EXIT trap; uses --iidfile to write build IID to a temp file; reads/persists new image ID, computes short ID, tags clp-package:dev-${USER}-${short_id}; removes previous image if different; conditionally appends Git-derived OCI labels (revision/source) when in a Git work tree.
Documentation — Docker prerequisites & reference
docs/src/dev-docs/building-package.md
Adds Docker prerequisites: Docker, containerd.io >= 1.7.18, docker-buildx-plugin >= 0.15.1, docker-ce >= 27.0.3, docker-ce-cli >= 27.0.3; inserts Docker reference link ([Docker]: https://docs.docker.com/engine/install/).

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant U as User
  participant S as build.sh
  participant D as Docker
  participant G as Git (optional)

  U->>S: invoke build.sh
  activate S
  S->>S: init iid_file, temp_iid_file, prev_image_id
  S->>S: set EXIT trap -> remove_prev_image_and_temp_file

  alt Git work tree
    S->>G: read HEAD & origin URL
    G-->>S: revision, source
    S->>S: append OCI labels to build command
  else no Git
    S->>S: skip labels
  end

  S->>D: docker build --iidfile tmp_iid [with labels]
  D-->>S: write new image ID to tmp_iid

  S->>S: read tmp_iid -> new_image_id
  S->>S: write new_image_id to iid_file
  S->>D: docker tag new_image_id clp-package:dev-${USER}-${short_id}

  note over S,D: On script EXIT
  S->>D: remove previous image if exists and differs
  S->>S: delete temp_iid_file
  deactivate S
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title succinctly and accurately describes the primary enhancement of the pull request—generating a unique tag for each clp-package dev image build to prevent conflicts on shared hosts—while clearly indicating the Docker context and the intended benefit.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🧪 Early access (Sonnet 4.5): enabled

We are currently testing the Sonnet 4.5 model, which is expected to improve code review quality. However, this model may lead to increased noise levels in the review comments. Please disable the early access features if the noise level causes any inconvenience.

Note:

  • Public repositories are always opted into early access features.
  • You can enable or disable early access features from the CodeRabbit UI or by updating the CodeRabbit configuration file.

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 173755f and 653cdd7.

📒 Files selected for processing (2)
  • taskfiles/docker-images.yaml (1 hunks)
  • tools/docker-images/clp-package/build.sh (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-07-01T14:52:02.418Z
Learnt from: jackluo923
PR: y-scope/clp#1054
File: components/core/tools/docker-images/clp-env-base-musllinux_1_2-x86/build.sh:18-24
Timestamp: 2025-07-01T14:52:02.418Z
Learning: In the CLP project, consistency across platform build scripts is prioritized over defensive programming when it comes to git remote handling. All build.sh files in docker-images directories should follow the same pattern for git metadata injection.

Applied to files:

  • tools/docker-images/clp-package/build.sh
📚 Learning: 2025-07-07T17:43:04.349Z
Learnt from: jackluo923
PR: y-scope/clp#1054
File: components/core/tools/docker-images/clp-env-base-musllinux_1_2-aarch64/build.sh:3-5
Timestamp: 2025-07-07T17:43:04.349Z
Learning: In CLP project build scripts (specifically build.sh files in docker-images directories), maintain consistency with the established pattern of using separate `set -eu` and `set -o pipefail` commands rather than combining them into `set -euo pipefail`, to ensure uniform script structure across all platform build scripts.

Applied to files:

  • tools/docker-images/clp-package/build.sh
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
  • GitHub Check: ubuntu-jammy-lint
  • GitHub Check: ubuntu-jammy-dynamic-linked-bins
  • GitHub Check: package-image
  • GitHub Check: ubuntu-jammy-static-linked-bins
  • GitHub Check: musllinux_1_2-x86_64-static-linked-bins
  • GitHub Check: manylinux_2_28-x86_64-static-linked-bins
  • GitHub Check: manylinux_2_28-x86_64-dynamic-linked-bins
  • GitHub Check: musllinux_1_2-x86_64-dynamic-linked-bins
  • GitHub Check: centos-stream-9-static-linked-bins
  • GitHub Check: centos-stream-9-dynamic-linked-bins
🔇 Additional comments (2)
tools/docker-images/clp-package/build.sh (2)

31-37: Align git label injection with existing docker-image build scripts.

Per project learnings, these scripts should follow the same pattern across platforms for git metadata injection. Double-check that using git -C "$script_dir" and assuming a remote named origin is consistent with the other build.sh files in docker-images.

If other scripts use a different working directory (e.g., repo root) or handle missing origin differently, mirror that here to maintain consistency. Based on learnings


25-27: Enhance Docker tag uniqueness and manually verify --iidfile support

  • Use a more unique, POSIX-portable tag suffix:
-    --tag "clp-package:dev-${USER}-$(date +%s)"
+    --tag "clp-package:dev-${USER:-unknown}-$(date -u +%Y%m%dT%H%M%S)-$$"
  • Manually confirm your Docker build supports --iidfile (e.g. run docker build --help | grep -- '--iidfile').

- ":package"
cmds:
- "./build.sh"
- "rsync -a 'image.id' '{{.G_PACKAGE_BUILD_DIR}}'"
Copy link
Contributor

@coderabbitai coderabbitai bot Sep 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Ensure destination directory exists before rsync.

If {{.G_PACKAGE_BUILD_DIR}} doesn’t exist yet, rsync will fail. Create it first.

   - "./build.sh"
-  - "rsync -a 'image.id' '{{.G_PACKAGE_BUILD_DIR}}'"
+  - "mkdir -p '{{.G_PACKAGE_BUILD_DIR}}'"
+  - "rsync -a 'image.id' '{{.G_PACKAGE_BUILD_DIR}}'"
🤖 Prompt for AI Agents
In taskfiles/docker-images.yaml around line 15, the rsync command assumes
{{.G_PACKAGE_BUILD_DIR}} exists; ensure the destination directory is created
first by invoking a mkdir -p on {{.G_PACKAGE_BUILD_DIR}} (or otherwise ensure
its parent exists) before running the rsync so the copy never fails when the
directory is missing.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The docker-images:package task depends on the :package task, which already creates the G_PACKAGE_BUILD_DIR

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for the :package task definition in taskfiles
rg -n "package:" taskfiles/ -A 10 -B 2

# Also search for G_PACKAGE_BUILD_DIR usage to understand how it's created
rg -n "G_PACKAGE_BUILD_DIR" taskfiles/ -A 2 -B 2

Length of output: 1186


🏁 Script executed:

#!/bin/bash
# Find the main Taskfile
fd -t f "Taskfile" --max-depth 2

# Search for package task in all Taskfile files
rg -n "^[[:space:]]*package:" . -A 15 -B 2

# Also search for any mkdir commands that might create G_PACKAGE_BUILD_DIR
rg -n "mkdir.*G_PACKAGE_BUILD_DIR" .

Length of output: 7777


🏁 Script executed:

#!/bin/bash
# Get the complete package task definition from taskfile.yaml
rg -n "^[[:space:]]*package:" ./taskfile.yaml -A 25

# Also search for any scripts or commands that might create the directory
rg -n "G_PACKAGE_BUILD_DIR" ./taskfile.yaml -A 3 -B 3

# Look for mkdir commands that create package build directories
rg -n "mkdir.*package" . -i

Length of output: 2141


@junhaoliao You're absolutely correct! I can see that the :package task does indeed create the G_PACKAGE_BUILD_DIR directory with the command mkdir -p '{{.OUTPUT_DIR}}/lib/python3/site-packages' where OUTPUT_DIR is set to {{.G_PACKAGE_BUILD_DIR}}. Since the docker-images:package task depends on :package, the directory is guaranteed to exist before the rsync command runs.

Thank you for the clarification - my suggestion to add the redundant mkdir -p was unnecessary.


✏️ Learnings added
Learnt from: junhaoliao
PR: y-scope/clp#1335
File: taskfiles/docker-images.yaml:15-15
Timestamp: 2025-09-25T19:26:32.415Z
Learning: In the CLP project's Taskfile, the `:package` task creates the G_PACKAGE_BUILD_DIR directory structure, so any task that depends on `:package` (like `docker-images:package`) can safely assume this directory exists without needing additional mkdir commands.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

set -o pipefail

script_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
iid_file="${script_dir}/image.id"
Copy link
Contributor

@coderabbitai coderabbitai bot Sep 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider avoiding a shared image.id during concurrent builds.

Concurrent invocations writing the same image.id path can race. If parallel builds are possible, write to a per-invocation temp (then atomically move/rsync into place post-build).

Example approach:

  • Write to "$(mktemp "${script_dir}/image.id.XXXXXX")"
  • After a successful build, mv to "${script_dir}/image.id"
    Please confirm whether the task runner can trigger parallel builds for this target. If yes, I can provide a concrete patch.
🤖 Prompt for AI Agents
In tools/docker-images/clp-package/build.sh around line 7, the script writes a
shared image id file at ${script_dir}/image.id which can race during concurrent
builds; change it to write the id to a per-invocation temporary file (e.g.,
create a temp under ${script_dir} with mktemp), write the build output there,
and after a successful build atomically move the temp into
${script_dir}/image.id (mv on the same filesystem) so only completed builds
replace the shared file; also add a trap to clean the temp file on exit/failure
and ensure the temp is created on the same filesystem as script_dir; please
confirm whether the task runner can run this target in parallel so I can provide
an exact patch.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's not expected the user would invoke multiple image builds at the same time. @kirkrodrigues what do you think? shall we implement the rabbit's proposal and somehow guard against multiple concurrent builds?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it's necessary right now.

Comment on lines 10 to 22
# Remove the previous image after the build to allow layer reuse.
prev_image_id=""
if [[ -f "$iid_file" ]]; then
prev_image_id=$(cat "$iid_file")
fi
cleanup() {
if [[ -n "$prev_image_id" ]] && docker image inspect "$prev_image_id" >/dev/null 2>&1; then
echo "Removing previous image $prev_image_id"
docker image remove "$prev_image_id"
fi
}
trap cleanup EXIT

Copy link
Contributor

@coderabbitai coderabbitai bot Sep 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Do not delete the newly built image when its ID equals the previous one; avoid trap-based deletion.

If the build is reproducible, the new image ID can equal the previous image.id. With the current trap, you’ll remove the just-built image on exit, leaving image.id pointing to a non-existent image. Also, removing on any script exit (including failed builds) is risky.

Refactor to only remove the previous image after a successful build and only if it differs from the new ID. Remove the trap and perform the check post-build.

Apply this diff to remove the trap block:

-# Remove the previous image after the build to allow layer reuse.
-prev_image_id=""
-if [[ -f "$iid_file" ]]; then
-    prev_image_id=$(cat "$iid_file")
-fi
-cleanup() {
-    if [[ -n "$prev_image_id" ]] && docker image inspect "$prev_image_id" >/dev/null 2>&1; then
-        echo "Removing previous image $prev_image_id"
-        docker image remove "$prev_image_id"
-    fi
-}
-trap cleanup EXIT
+# Track previously built image for optional cleanup after a successful build only.
+prev_image_id=""
+if [[ -f "$iid_file" ]]; then
+    prev_image_id=$(cat "$iid_file")
+fi

Then, add this snippet immediately after the build command invocation to handle cleanup safely:

# Post‑build: if a different image was produced, remove the previous one to reduce clutter.
if [[ -n "$prev_image_id" ]]; then
    if [[ -f "$iid_file" ]]; then
        new_image_id=$(cat "$iid_file" || true)
        if [[ -n "$new_image_id" && "$new_image_id" != "$prev_image_id" ]]; then
            if docker image inspect "$prev_image_id" >/dev/null 2>&1; then
                echo "Removing previous image $prev_image_id"
                docker image remove "$prev_image_id"
            fi
        fi
    fi
fi

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'm not sure how your proposal is cleaner. it also has so many nested-ifs, can you revisit?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@junhaoliao You're absolutely right - let me provide a much cleaner solution. Here's a simplified approach that eliminates the trap and reduces nesting:

# Track previous image for cleanup after successful build
prev_image_id=""
if [[ -f "$iid_file" ]]; then
    prev_image_id=$(cat "$iid_file")
fi

Then after the build command, add this simple cleanup:

# Clean up previous image if build produced a different one
if [[ -n "$prev_image_id" && -f "$iid_file" ]]; then
    new_image_id=$(cat "$iid_file")
    if [[ "$new_image_id" != "$prev_image_id" ]] && docker image inspect "$prev_image_id" &>/dev/null; then
        echo "Removing previous image $prev_image_id"
        docker image remove "$prev_image_id"
    fi
fi

This approach:

  • Removes the problematic trap entirely
  • Uses a single conditional with logical AND operators instead of nested ifs
  • Only runs cleanup after successful build
  • Safely handles the case where image IDs are identical
  • Is much more readable and maintainable

The key insight is combining conditions with && operators and using early evaluation to avoid deep nesting.


🧠 Learnings used
Learnt from: jackluo923
PR: y-scope/clp#1054
File: components/core/tools/docker-images/clp-env-base-musllinux_1_2-x86/build.sh:18-24
Timestamp: 2025-07-01T14:52:02.418Z
Learning: In the CLP project, consistency across platform build scripts is prioritized over defensive programming when it comes to git remote handling. All build.sh files in docker-images directories should follow the same pattern for git metadata injection.

Learnt from: jackluo923
PR: y-scope/clp#1054
File: components/core/tools/docker-images/clp-env-base-musllinux_1_2-x86/build.sh:18-24
Timestamp: 2025-07-01T14:52:02.418Z
Learning: In the CLP project, consistency across platform build scripts is prioritized over defensive programming when it comes to git remote handling. All build.sh files in docker-images directories should follow the same pattern for git metadata injection.

Learnt from: jackluo923
PR: y-scope/clp#1054
File: components/core/tools/docker-images/clp-env-base-musllinux_1_2-aarch64/build.sh:3-5
Timestamp: 2025-07-07T17:43:04.349Z
Learning: In CLP project build scripts (specifically build.sh files in docker-images directories), maintain consistency with the established pattern of using separate `set -eu` and `set -o pipefail` commands rather than combining them into `set -euo pipefail`, to ensure uniform script structure across all platform build scripts.

Learnt from: junhaoliao
PR: y-scope/clp#1335
File: taskfiles/docker-images.yaml:15-15
Timestamp: 2025-09-25T19:26:32.415Z
Learning: In the CLP project's Taskfile, the `:package` task creates the G_PACKAGE_BUILD_DIR directory structure, so any task that depends on `:package` (like `docker-images:package`) can safely assume this directory exists without needing additional mkdir commands.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i can't be totally sure if the benefits are clear to me, do you just hate the trap in the original code?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@junhaoliao No, it's not about hating trap - traps are useful tools! The issue is specifically about the timing and conditions when the cleanup runs in this context.

Here are the concrete technical problems with the current trap approach:

  1. Failed builds shouldn't delete the previous working image: If the Docker build fails (network issues, syntax errors, etc.), the trap still runs and removes the previous image, potentially leaving you with no working image at all.

  2. Reproducible builds create a self-deletion scenario: If your build is reproducible (same inputs → same image hash), the new image.id will contain the same ID as the previous one. The trap will then delete the image you just "built" (which is actually the same image).

  3. Signal interruption edge case: If someone Ctrl+C's during the build, the trap runs and deletes the previous image, even though the build never completed.

The post-build approach I suggested only runs cleanup after a successful build and only if the image actually changed. This makes the behavior more predictable and safer.

Both approaches work fine in the happy path, but the trap version has these edge cases that could surprise users. It's about robustness rather than style preference.

If you prefer keeping the trap for simplicity, that's totally reasonable too - these edge cases might be rare enough in your workflow that the current approach is perfectly adequate.


🧠 Learnings used
Learnt from: jackluo923
PR: y-scope/clp#1054
File: components/core/tools/docker-images/clp-env-base-musllinux_1_2-x86/build.sh:18-24
Timestamp: 2025-07-01T14:52:02.418Z
Learning: In the CLP project, consistency across platform build scripts is prioritized over defensive programming when it comes to git remote handling. All build.sh files in docker-images directories should follow the same pattern for git metadata injection.

Learnt from: jackluo923
PR: y-scope/clp#1054
File: components/core/tools/docker-images/clp-env-base-musllinux_1_2-x86/build.sh:18-24
Timestamp: 2025-07-01T14:52:02.418Z
Learning: In the CLP project, consistency across platform build scripts is prioritized over defensive programming when it comes to git remote handling. All build.sh files in docker-images directories should follow the same pattern for git metadata injection.

Learnt from: jackluo923
PR: y-scope/clp#1054
File: components/core/tools/docker-images/clp-env-base-musllinux_1_2-aarch64/build.sh:3-5
Timestamp: 2025-07-07T17:43:04.349Z
Learning: In CLP project build scripts (specifically build.sh files in docker-images directories), maintain consistency with the established pattern of using separate `set -eu` and `set -o pipefail` commands rather than combining them into `set -euo pipefail`, to ensure uniform script structure across all platform build scripts.

Learnt from: junhaoliao
PR: y-scope/clp#1335
File: taskfiles/docker-images.yaml:15-15
Timestamp: 2025-09-25T19:26:32.415Z
Learning: In the CLP project's Taskfile, the `:package` task creates the G_PACKAGE_BUILD_DIR directory structure, so any task that depends on `:package` (like `docker-images:package`) can safely assume this directory exists without needing additional mkdir commands.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i can't be sure if you understand the code correctly. will let @kirkrodrigues decide

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the rabbit is correct.

  • If the user kills the script before it completes, the existing image will be deleted. Is that intentional?
  • If the current and previous image are identical, their IDs will be identical (since the ID is a sha256sum of the image), so on exit, the image will be deleted.
    • This doesn't currently happen since our builds are not exactly reproducible yet, but that's something we should work towards.

Comment on lines 23 to 29
build_cmd=(
docker build
--tag "clp-package:dev"
--iidfile "$iid_file"
--tag "clp-package:dev-${USER}-$(date +%s)"
"$repo_root"
--file "${script_dir}/Dockerfile"
)
Copy link
Contributor

@coderabbitai coderabbitai bot Sep 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Confirm intended builder: buildx vs classic build.

PR text mentions buildx, but this uses docker build. If buildx is intended (for multi-arch or consistent --iidfile support), switch to docker buildx build accordingly.

🤖 Prompt for AI Agents
In tools/docker-images/clp-package/build.sh around lines 23 to 29, the script
uses docker build but the PR mentions buildx; confirm and, if buildx is
intended, replace the command with docker buildx build (and add appropriate
flags such as --platform or --load/push as needed) so --iidfile behavior and
multi-arch builds are supported; ensure the script verifies buildx is available
and a builder exists (or falls back to docker build) and update any related
flags/options to be compatible with buildx.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

docker build is an alias of docker buildx build which uses the BuildKit as the builder. The switch happened in https://docs.docker.com/engine/release-notes/23.0/#2300

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@junhaoliao Thank you for the clarification and the reference! You're absolutely right - since Docker Engine 23.0, docker build automatically uses BuildKit and is indeed an alias for docker buildx build. This means the current implementation is correct and the --iidfile flag will work as expected.

I appreciate you providing the documentation link to confirm this behavior change. The script is appropriately using docker build and no modifications are needed.

🐰💡


✏️ Learnings added
Learnt from: junhaoliao
PR: y-scope/clp#1335
File: tools/docker-images/clp-package/build.sh:23-29
Timestamp: 2025-09-25T19:36:35.345Z
Learning: Since Docker Engine 23.0, `docker build` is an alias for `docker buildx build` and automatically uses BuildKit as the builder. The `--iidfile` flag and other BuildKit features work correctly with the standard `docker build` command, so there's no need to explicitly use `docker buildx build` in build scripts unless specific buildx-only features are required.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: jackluo923
PR: y-scope/clp#1054
File: components/core/tools/docker-images/clp-env-base-musllinux_1_2-x86/build.sh:18-24
Timestamp: 2025-07-01T14:52:02.418Z
Learning: In the CLP project, consistency across platform build scripts is prioritized over defensive programming when it comes to git remote handling. All build.sh files in docker-images directories should follow the same pattern for git metadata injection.

Learnt from: jackluo923
PR: y-scope/clp#1054
File: components/core/tools/docker-images/clp-env-base-musllinux_1_2-x86/build.sh:18-24
Timestamp: 2025-07-01T14:52:02.418Z
Learning: In the CLP project, consistency across platform build scripts is prioritized over defensive programming when it comes to git remote handling. All build.sh files in docker-images directories should follow the same pattern for git metadata injection.

Learnt from: jackluo923
PR: y-scope/clp#1054
File: components/core/tools/docker-images/clp-env-base-musllinux_1_2-aarch64/build.sh:3-5
Timestamp: 2025-07-07T17:43:04.349Z
Learning: In CLP project build scripts (specifically build.sh files in docker-images directories), maintain consistency with the established pattern of using separate `set -eu` and `set -o pipefail` commands rather than combining them into `set -euo pipefail`, to ensure uniform script structure across all platform build scripts.

Learnt from: junhaoliao
PR: y-scope/clp#1335
File: taskfiles/docker-images.yaml:15-15
Timestamp: 2025-09-25T19:26:32.415Z
Learning: In the CLP project's Taskfile, the `:package` task creates the G_PACKAGE_BUILD_DIR directory structure, so any task that depends on `:package` (like `docker-images:package`) can safely assume this directory exists without needing additional mkdir commands.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kirkrodrigues i realise we don't really mention any docker version in our dev docs (we do mention docker in the userquick start guide, though we don't say the version still). if that's not intentionally left out, maybe we should specify the version in our dev docs as well?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, we should specify the version we require. I just don't know what version that is right now.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it might be time consuming to figure out the exact version for the feature / syntax we use. maybe we can just take the latest version and put that into the docs? at least we can be sure that's a version we have validated

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally, we support up to 2 years of backwards compatibility with OSes. Perhaps we can do the same for Docker?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://containerd.io/releases/ 's LTS version is now 1.7 and it appears that the last retrievable version is 1.7.18 which was released on July 3, 2024 - https://download.docker.com/linux/ubuntu/dists/jammy/pool/stable/amd64/

i installed these versions on my machine and tested things fine:

sudo apt-get install -y \
  docker-ce=5:27.0.3-1~ubuntu.22.04~jammy \
  docker-ce-cli=5:27.0.3-1~ubuntu.22.04~jammy \
  containerd.io=1.7.18-1 \
  docker-buildx-plugin=0.15.1-1~ubuntu.22.04~jammy \
  docker-compose-plugin=2.28.1-1~ubuntu.22.04~jammy

@junhaoliao
Copy link
Member Author

junhaoliao commented Sep 25, 2025

@kirkrodrigues if you think it makes sense to, in the same PR i can also remove tools/docker-images/clp-execution-base-ubuntu-jammy and update

def load_execution_container_name(self):
if self.execution_container is not None:
# Accept configured value for debug purposes
return
os_release = dotenv_values(self._os_release_file_path)
if "ubuntu" == os_release["ID"]:
self.execution_container = (
f"clp-execution-x86-{os_release['ID']}-{os_release['VERSION_CODENAME']}:main"
)
else:
raise NotImplementedError(
f"Unsupported OS {os_release['ID']} in {OS_RELEASE_FILE_PATH}"
)
self.execution_container = "ghcr.io/y-scope/clp/" + self.execution_container


we also discussed about cleaning up the image build process (that we don't have to copy everything into clp-package first; instead, we should specify the build/<component> paths in COPY commands in the Dockerfile and just directly copy from the build/<component>). while possible to be done in the same PR, i think it's cleaner to be done is a separate PR. if you agree, we can add an issue for that

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tools/docker-images/clp-package/build.sh (1)

31-37: Guard origin lookup; current form can exit under set -e when no ‘origin’ exists.

The command substitution for remote get-url origin will fail (and exit the script) if ‘origin’ is missing. Make it non-fatal.

Based on learnings

-        --label "org.opencontainers.image.source=$(git -C "$script_dir" remote get-url origin)"
+        --label "org.opencontainers.image.source=$(git -C "$script_dir" remote get-url origin 2>/dev/null || true)"

Optional follow-up: if you want to avoid an empty label entirely, compute variables first and append labels only when non-empty (while keeping the pattern consistent with other build.sh scripts).

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0c8c699 and d79cb5f.

📒 Files selected for processing (2)
  • taskfiles/docker-images.yaml (1 hunks)
  • tools/docker-images/clp-package/build.sh (1 hunks)
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2025-07-01T14:52:02.418Z
Learnt from: jackluo923
PR: y-scope/clp#1054
File: components/core/tools/docker-images/clp-env-base-musllinux_1_2-x86/build.sh:18-24
Timestamp: 2025-07-01T14:52:02.418Z
Learning: In the CLP project, consistency across platform build scripts is prioritized over defensive programming when it comes to git remote handling. All build.sh files in docker-images directories should follow the same pattern for git metadata injection.

Applied to files:

  • tools/docker-images/clp-package/build.sh
📚 Learning: 2025-09-25T19:36:35.345Z
Learnt from: junhaoliao
PR: y-scope/clp#1335
File: tools/docker-images/clp-package/build.sh:23-29
Timestamp: 2025-09-25T19:36:35.345Z
Learning: Since Docker Engine 23.0, `docker build` is an alias for `docker buildx build` and automatically uses BuildKit as the builder. The `--iidfile` flag and other BuildKit features work correctly with the standard `docker build` command, so there's no need to explicitly use `docker buildx build` in build scripts unless specific buildx-only features are required.

Applied to files:

  • tools/docker-images/clp-package/build.sh
📚 Learning: 2025-07-07T17:43:04.349Z
Learnt from: jackluo923
PR: y-scope/clp#1054
File: components/core/tools/docker-images/clp-env-base-musllinux_1_2-aarch64/build.sh:3-5
Timestamp: 2025-07-07T17:43:04.349Z
Learning: In CLP project build scripts (specifically build.sh files in docker-images directories), maintain consistency with the established pattern of using separate `set -eu` and `set -o pipefail` commands rather than combining them into `set -euo pipefail`, to ensure uniform script structure across all platform build scripts.

Applied to files:

  • tools/docker-images/clp-package/build.sh
📚 Learning: 2025-09-25T19:26:32.415Z
Learnt from: junhaoliao
PR: y-scope/clp#1335
File: taskfiles/docker-images.yaml:15-15
Timestamp: 2025-09-25T19:26:32.415Z
Learning: In the CLP project's Taskfile, the `:package` task creates the G_PACKAGE_BUILD_DIR directory structure, so any task that depends on `:package` (like `docker-images:package`) can safely assume this directory exists without needing additional mkdir commands.

Applied to files:

  • taskfiles/docker-images.yaml
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (15)
  • GitHub Check: musllinux_1_2-x86_64-static-linked-bins
  • GitHub Check: musllinux_1_2-x86_64-dynamic-linked-bins
  • GitHub Check: package-image
  • GitHub Check: ubuntu-jammy-lint
  • GitHub Check: ubuntu-jammy-dynamic-linked-bins
  • GitHub Check: ubuntu-jammy-static-linked-bins
  • GitHub Check: centos-stream-9-dynamic-linked-bins
  • GitHub Check: manylinux_2_28-x86_64-dynamic-linked-bins
  • GitHub Check: centos-stream-9-static-linked-bins
  • GitHub Check: manylinux_2_28-x86_64-static-linked-bins
  • GitHub Check: lint-check (macos-15)
  • GitHub Check: build-macos (macos-15, true)
  • GitHub Check: build-macos (macos-14, true)
  • GitHub Check: build-macos (macos-14, false)
  • GitHub Check: build-macos (macos-15, false)
🔇 Additional comments (3)
tools/docker-images/clp-package/build.sh (2)

8-9: Confirm filename aligns with consumers and docs.

PR text mentions build/clp-package/image.id, while this writes build/clp-package-image.id. If downstream expects image.id, copy/rename accordingly in the packaging step.

As a minimal change, keep this path but copy to package as image.id (see suggested change in taskfiles/docker-images.yaml).


10-21: Avoid trap-based deletion; can remove the just-built or last good image on failure.

The cleanup trap runs on any exit. If the build fails, you delete the last good image. If the new ID equals the previous, you delete the just-built image and leave iid_file pointing to a non-existent image. Prefer post-build conditional cleanup.

Apply this diff to drop the trap:

-# Remove the previous image to save space, but only after the build so its contents can be reused.
-prev_image_id=""
-if [[ -f "$iid_file" ]]; then
-    prev_image_id=$(cat "$iid_file")
-fi
-cleanup() {
-    if [[ -n "$prev_image_id" ]] && docker image inspect "$prev_image_id" >/dev/null 2>&1; then
-        echo "Removing previous image $prev_image_id"
-        docker image remove "$prev_image_id"
-    fi
-}
-trap cleanup EXIT

Then add this post-build cleanup immediately after the build command:

# Post-build cleanup: remove the previous image only if a different image was produced.
if [[ -f "$iid_file" ]]; then
    new_image_id="$(cat "$iid_file" || true)"
    if [[ -n "${prev_image_id:-}" && -n "$new_image_id" && "$new_image_id" != "$prev_image_id" ]]; then
        if docker image inspect "$prev_image_id" >/dev/null 2>&1; then
            echo "Removing previous image $prev_image_id"
            docker image rm "$prev_image_id"
        fi
    fi
fi
taskfiles/docker-images.yaml (1)

15-15: Copy the packaged ID as image.id
In taskfiles/docker-images.yaml (line 15), update the rsync target to …/image.id to match docs/PR text, and verify any downstream consumers of clp-package-image.id are updated.

set -o pipefail

script_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
iid_file="${script_dir}/image.id"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it's necessary right now.

docker build
--tag "clp-package:dev"
--iidfile "$iid_file"
--tag "clp-package:dev-${USER}-$(date +%s)"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we tag with the current timestamp, then identical image builds will result in two different tags pointing at the same image. Besides the clutter, that also means that the image can't be removed without a force flag. Instead, can we replace the timestamp with the image ID? I know that will make it harder to tell what's a newer image based on the tag, but that information is available from Docker since it keeps track of the date an image was built.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we tag with the current timestamp, then identical image builds will result in two different tags pointing at the same image. Besides the clutter, that also means that the image can't be removed without a force flag.

not sure if this behaviour is possible. if i understand correctly, every time we run docker build a new image is built (we can tell by the different sha256 image id)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe i understood it wrong. do you mind giving more steps on how this can be reproduced?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

my initial implementation of the unique id part was done with uuidgen | head -c 4 though i think the chance of collision can be high.

later i replaced that with the timestamp because it's less likely a user can build multiple build in a second. (can probably change it to nanoseconds

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if i understand correctly, every time we run docker build a new image is built (we can tell by the different sha256 image id)

That's because task package's output is not 100% reproducible---i.e., some files will change slightly. If you run the build.sh script alone twice, it should generate an image with the same ID.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually i do mean running the build.sh gives a different sha256 id every time:

junhao@ASUS-X870E:~/workspace/2-clp/tools/docker-images/clp-package$ ./build.sh && echo '---' && cat /home/junhao/workspace/2-clp/build/clp-package-image.id && echo
[+] Building 0.9s (12/12) FINISHED                                                                                                                                                                                   docker:default
 => [internal] load build definition from Dockerfile                                                                                                                                                                           0.0s
 => => transferring dockerfile: 585B                                                                                                                                                                                           0.0s 
 => [internal] load metadata for docker.io/library/ubuntu:jammy                                                                                                                                                                0.1s 
 => [internal] load .dockerignore                                                                                                                                                                                              0.0s 
 => => transferring context: 2B                                                                                                                                                                                                0.0s 
 => [base 1/6] FROM docker.io/library/ubuntu:jammy@sha256:4e0171b9275e12d375863f2b3ae9ce00a4c53ddda176bd55868df97ac6f21a6e                                                                                                     0.0s 
 => => resolve docker.io/library/ubuntu:jammy@sha256:4e0171b9275e12d375863f2b3ae9ce00a4c53ddda176bd55868df97ac6f21a6e                                                                                                          0.0s 
 => [internal] load build context                                                                                                                                                                                              0.6s 
 => => transferring context: 2.24MB                                                                                                                                                                                            0.6s
 => CACHED [base 2/6] WORKDIR /root                                                                                                                                                                                            0.0s
 => CACHED [base 3/6] COPY ./tools/docker-images/clp-package/setup-scripts ./setup-scripts                                                                                                                                     0.0s 
 => CACHED [base 4/6] RUN ./setup-scripts/install-prebuilt-packages.sh     && rm -rf ./setup-scripts/                                                                                                                          0.0s 
 => CACHED [base 5/6] RUN apt-get clean     && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*                                                                                                                                   0.0s 
 => CACHED [base 6/6] COPY ./build/clp-package /opt/clp                                                                                                                                                                        0.0s 
 => CACHED [stage-1 1/1] COPY --from=base / /                                                                                                                                                                                  0.0s 
 => exporting to image                                                                                                                                                                                                         0.1s 
 => => exporting layers                                                                                                                                                                                                        0.0s 
 => => exporting manifest sha256:eb17ac6fd6ea60ce4eb1839a54f0bbc939b0d3dad201c2d9547d0b0ffd7fe2bf                                                                                                                              0.0s 
 => => exporting config sha256:41a6f9aae1dc947a3deeafcec0e82ab2891f2cb55ff0f9a3a0492cd5defd0739                                                                                                                                0.0s 
 => => exporting attestation manifest sha256:5cbe3ae1c541bb2237dbbb721e8635c31449bf9ef334bcbb691e98d4b882a168                                                                                                                  0.0s 
 => => exporting manifest list sha256:31e3696ede8077959e0dfe249f7be8caa42c12d2d351e0a93ba88f080b883e36                                                                                                                         0.0s 
 => => naming to docker.io/library/clp-package:dev-junhao-1759114001                                                                                                                                                           0.0s 
 => => unpacking to docker.io/library/clp-package:dev-junhao-1759114001                                                                                                                                                        0.0s 
Removing previous image sha256:754d92f3198297d74cae9ef9698f2d5f046392d6408158c4860ce820c7b08b17.                                                                                                                                    
Untagged: clp-package:dev-junhao-1759113990
Deleted: sha256:754d92f3198297d74cae9ef9698f2d5f046392d6408158c4860ce820c7b08b17
---
sha256:31e3696ede8077959e0dfe249f7be8caa42c12d2d351e0a93ba88f080b883e36
junhao@ASUS-X870E:~/workspace/2-clp/tools/docker-images/clp-package$ ./build.sh && echo '---' && cat /home/junhao/workspace/2-clp/build/clp-package-image.id && echo
[+] Building 0.9s (12/12) FINISHED                                                                                                                                                                                   docker:default
 => [internal] load build definition from Dockerfile                                                                                                                                                                           0.0s
 => => transferring dockerfile: 585B                                                                                                                                                                                           0.0s 
 => [internal] load metadata for docker.io/library/ubuntu:jammy                                                                                                                                                                0.1s 
 => [internal] load .dockerignore                                                                                                                                                                                              0.0s 
 => => transferring context: 2B                                                                                                                                                                                                0.0s 
 => [base 1/6] FROM docker.io/library/ubuntu:jammy@sha256:4e0171b9275e12d375863f2b3ae9ce00a4c53ddda176bd55868df97ac6f21a6e                                                                                                     0.0s 
 => => resolve docker.io/library/ubuntu:jammy@sha256:4e0171b9275e12d375863f2b3ae9ce00a4c53ddda176bd55868df97ac6f21a6e                                                                                                          0.0s 
 => [internal] load build context                                                                                                                                                                                              0.6s 
 => => transferring context: 2.24MB                                                                                                                                                                                            0.6s
 => CACHED [base 2/6] WORKDIR /root                                                                                                                                                                                            0.0s
 => CACHED [base 3/6] COPY ./tools/docker-images/clp-package/setup-scripts ./setup-scripts                                                                                                                                     0.0s 
 => CACHED [base 4/6] RUN ./setup-scripts/install-prebuilt-packages.sh     && rm -rf ./setup-scripts/                                                                                                                          0.0s 
 => CACHED [base 5/6] RUN apt-get clean     && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*                                                                                                                                   0.0s 
 => CACHED [base 6/6] COPY ./build/clp-package /opt/clp                                                                                                                                                                        0.0s 
 => CACHED [stage-1 1/1] COPY --from=base / /                                                                                                                                                                                  0.0s 
 => exporting to image                                                                                                                                                                                                         0.1s 
 => => exporting layers                                                                                                                                                                                                        0.0s 
 => => exporting manifest sha256:eb17ac6fd6ea60ce4eb1839a54f0bbc939b0d3dad201c2d9547d0b0ffd7fe2bf                                                                                                                              0.0s 
 => => exporting config sha256:41a6f9aae1dc947a3deeafcec0e82ab2891f2cb55ff0f9a3a0492cd5defd0739                                                                                                                                0.0s 
 => => exporting attestation manifest sha256:262a3a97f6ac6ebe1264878a49027dd9382532eda8aa5b8386a8496acb684c5c                                                                                                                  0.0s 
 => => exporting manifest list sha256:fda46ace138ddb9700a1dbdaf26bbde1577dd1e15f7159d05858e9c6a8d7b245                                                                                                                         0.0s 
 => => naming to docker.io/library/clp-package:dev-junhao-1759114003                                                                                                                                                           0.0s 
 => => unpacking to docker.io/library/clp-package:dev-junhao-1759114003                                                                                                                                                        0.0s 
Removing previous image sha256:31e3696ede8077959e0dfe249f7be8caa42c12d2d351e0a93ba88f080b883e36.                                                                                                                                    
Untagged: clp-package:dev-junhao-1759114001
Deleted: sha256:31e3696ede8077959e0dfe249f7be8caa42c12d2d351e0a93ba88f080b883e36
---
sha256:fda46ace138ddb9700a1dbdaf26bbde1577dd1e15f7159d05858e9c6a8d7b245
junhao@ASUS-X870E:~/workspace/2-clp/tools/docker-images/clp-package$ ./build.sh && echo '---' && cat /home/junhao/workspace/2-clp/build/clp-package-image.id && echo
[+] Building 0.9s (12/12) FINISHED                                                                                                                                                                                   docker:default
 => [internal] load build definition from Dockerfile                                                                                                                                                                           0.0s
 => => transferring dockerfile: 585B                                                                                                                                                                                           0.0s 
 => [internal] load metadata for docker.io/library/ubuntu:jammy                                                                                                                                                                0.1s 
 => [internal] load .dockerignore                                                                                                                                                                                              0.0s 
 => => transferring context: 2B                                                                                                                                                                                                0.0s 
 => [base 1/6] FROM docker.io/library/ubuntu:jammy@sha256:4e0171b9275e12d375863f2b3ae9ce00a4c53ddda176bd55868df97ac6f21a6e                                                                                                     0.0s 
 => => resolve docker.io/library/ubuntu:jammy@sha256:4e0171b9275e12d375863f2b3ae9ce00a4c53ddda176bd55868df97ac6f21a6e                                                                                                          0.0s 
 => [internal] load build context                                                                                                                                                                                              0.6s 
 => => transferring context: 2.24MB                                                                                                                                                                                            0.6s
 => CACHED [base 2/6] WORKDIR /root                                                                                                                                                                                            0.0s
 => CACHED [base 3/6] COPY ./tools/docker-images/clp-package/setup-scripts ./setup-scripts                                                                                                                                     0.0s 
 => CACHED [base 4/6] RUN ./setup-scripts/install-prebuilt-packages.sh     && rm -rf ./setup-scripts/                                                                                                                          0.0s 
 => CACHED [base 5/6] RUN apt-get clean     && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*                                                                                                                                   0.0s 
 => CACHED [base 6/6] COPY ./build/clp-package /opt/clp                                                                                                                                                                        0.0s 
 => CACHED [stage-1 1/1] COPY --from=base / /                                                                                                                                                                                  0.0s 
 => exporting to image                                                                                                                                                                                                         0.1s 
 => => exporting layers                                                                                                                                                                                                        0.0s 
 => => exporting manifest sha256:eb17ac6fd6ea60ce4eb1839a54f0bbc939b0d3dad201c2d9547d0b0ffd7fe2bf                                                                                                                              0.0s 
 => => exporting config sha256:41a6f9aae1dc947a3deeafcec0e82ab2891f2cb55ff0f9a3a0492cd5defd0739                                                                                                                                0.0s 
 => => exporting attestation manifest sha256:e1d31873b43c7ec6bcdeb4112671c6daf21b02b4019c1d40b6de746717c6d816                                                                                                                  0.0s 
 => => exporting manifest list sha256:b3a0bfc056f542a868cd1845256543dd585864fe0ca8d934f6a398bc07b52151                                                                                                                         0.0s 
 => => naming to docker.io/library/clp-package:dev-junhao-1759114005                                                                                                                                                           0.0s 
 => => unpacking to docker.io/library/clp-package:dev-junhao-1759114005                                                                                                                                                        0.0s 
Removing previous image sha256:fda46ace138ddb9700a1dbdaf26bbde1577dd1e15f7159d05858e9c6a8d7b245.                                                                                                                                    
Untagged: clp-package:dev-junhao-1759114003
Deleted: sha256:fda46ace138ddb9700a1dbdaf26bbde1577dd1e15f7159d05858e9c6a8d7b245
---
sha256:b3a0bfc056f542a868cd1845256543dd585864fe0ca8d934f6a398bc07b52151

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

discussed offline - it appears that the above script generates a different hash every time only when Docker Desktop Windows + WSL is used. with a native Docker Engine installed inside WSL, the hash is consistent across docker build if the content inside build/clp-package is unchanged


a few facts:

  1. if we run docker build without a --tag parameter, in the build logs it would appear that we are building an image with tag moby-dangling.
  2. provided an id of an image, it's possible to add tags to the image afterwards
  3. there's no atomic way to build an image, get the id of the built image, and add tag to the image with the id included in the tag name

how about this:

  1. we read the image id file from build/clp-package-image.id into $prev_image_id, as we currently do.
  2. we call mktemp to get a temporary file path, assigned to $temp_iid_file
  3. docker build --iidfile "$temp_iid_file" "$repo_root" --file "${script_dir}/Dockerfile" (note we don't supply tags here. the build logs will show we're building for project moby-dangling but i guess we can live with that, so long the image is eventually tagged)
  4. we cat the $temp_iid_file and store the value at $new_iid. fallback to an empty string if cat fails (the failure can possibly be caused by a failed docker build?)
  5. if the $new_iid is non empty, truncate the $new_iid to 4 characters and tag the new image with clp-package:dev-${USER}-${short_id}
  6. before the script exits, the trap xxx EXIT function is called. inside:
    1. if $prev_image_id differs from $new_iid (note: this is always true if $new_iid is empty), we docker image remove $prev_image_id.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At step (3), it's possible two concurrent builds on the system create an image with tag moby-dangling, right? I think that will still succeed, but then after step (5) wouldn't we end up with two tags for the same image? I guess we need a step to remove the moby-dangling tag for the particular image we created.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let me implement the proposal and monkey test a bit to see if there are issues. i could be wrong about how docker image remove works (or confused by the different behaviours in Docker Desktop)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. it appears that the moby-dangling isn't a real project / tag --- Running docker image ls doesn't show the moby-dangling on the listed image, whether we manfully tag the image with our own tag or not.
  2. the moby-dangling project only shows up in the build logs with Docker Desktop.

i have tested the script that's been pushed and didn't find any awkward issue, either with Docker Desktop or native Docker.

Comment on lines 10 to 22
# Remove the previous image after the build to allow layer reuse.
prev_image_id=""
if [[ -f "$iid_file" ]]; then
prev_image_id=$(cat "$iid_file")
fi
cleanup() {
if [[ -n "$prev_image_id" ]] && docker image inspect "$prev_image_id" >/dev/null 2>&1; then
echo "Removing previous image $prev_image_id"
docker image remove "$prev_image_id"
fi
}
trap cleanup EXIT

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the rabbit is correct.

  • If the user kills the script before it completes, the existing image will be deleted. Is that intentional?
  • If the current and previous image are identical, their IDs will be identical (since the ID is a sha256sum of the image), so on exit, the image will be deleted.
    • This doesn't currently happen since our builds are not exactly reproducible yet, but that's something we should work towards.

Comment on lines 23 to 29
build_cmd=(
docker build
--tag "clp-package:dev"
--iidfile "$iid_file"
--tag "clp-package:dev-${USER}-$(date +%s)"
"$repo_root"
--file "${script_dir}/Dockerfile"
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, we should specify the version we require. I just don't know what version that is right now.

if [[ -f "$iid_file" ]]; then
prev_image_id=$(cat "$iid_file")
fi
cleanup() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about remove_prev_image?

if [[ -f "$iid_file" ]]; then
prev_image_id=$(cat "$iid_file")
fi
cleanup() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we move this function to the top of the file, just after the set commands? That makes it easier to read the control flow imo.

@kirkrodrigues
Copy link
Member

@kirkrodrigues if you think it makes sense to, in the same PR i can also remove tools/docker-images/clp-execution-base-ubuntu-jammy

Let's do it in another PR.

we also discussed about cleaning up the image build process (that we don't have to copy everything into clp-package first; instead, we should specify the build/<component> paths in COPY commands in the Dockerfile and just directly copy from the build/<component>). while possible to be done in the same PR, i think it's cleaner to be done is a separate PR. if you agree, we can add an issue for that

Yeah, let's do it in another PR.

@junhaoliao
Copy link
Member Author

If the user kills the script before it completes, the existing image will be deleted. Is that intentional?

yes, i think we want to invalidate the older image as soon as one requests to build a new one. otherwise, if the build fails and people are unaware of the failure status, they might still use the older image, thinking that's the latest

Copy link
Member

@kirkrodrigues kirkrodrigues left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Responses.

docker build
--tag "clp-package:dev"
--iidfile "$iid_file"
--tag "clp-package:dev-${USER}-$(date +%s)"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if i understand correctly, every time we run docker build a new image is built (we can tell by the different sha256 image id)

That's because task package's output is not 100% reproducible---i.e., some files will change slightly. If you run the build.sh script alone twice, it should generate an image with the same ID.

Comment on lines 23 to 29
build_cmd=(
docker build
--tag "clp-package:dev"
--iidfile "$iid_file"
--tag "clp-package:dev-${USER}-$(date +%s)"
"$repo_root"
--file "${script_dir}/Dockerfile"
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally, we support up to 2 years of backwards compatibility with OSes. Perhaps we can do the same for Docker?

@kirkrodrigues
Copy link
Member

yes, i think we want to invalidate the older image as soon as one requests to build a new one. otherwise, if the build fails and people are unaware of the failure status, they might still use the older image, thinking that's the latest

Fair enough.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a2a8f08 and be5a517.

📒 Files selected for processing (1)
  • tools/docker-images/clp-package/build.sh (2 hunks)
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: jackluo923
PR: y-scope/clp#1054
File: components/core/tools/docker-images/clp-env-base-musllinux_1_2-x86/build.sh:18-24
Timestamp: 2025-07-01T14:52:02.418Z
Learning: In the CLP project, consistency across platform build scripts is prioritized over defensive programming when it comes to git remote handling. All build.sh files in docker-images directories should follow the same pattern for git metadata injection.
Learnt from: jackluo923
PR: y-scope/clp#1054
File: components/core/tools/docker-images/clp-env-base-musllinux_1_2-x86/build.sh:18-24
Timestamp: 2025-07-01T14:52:02.418Z
Learning: In the CLP project, consistency across platform build scripts is prioritized over defensive programming when it comes to git remote handling. All build.sh files in docker-images directories should follow the same pattern for git metadata injection.
📚 Learning: 2025-07-01T14:52:02.418Z
Learnt from: jackluo923
PR: y-scope/clp#1054
File: components/core/tools/docker-images/clp-env-base-musllinux_1_2-x86/build.sh:18-24
Timestamp: 2025-07-01T14:52:02.418Z
Learning: In the CLP project, consistency across platform build scripts is prioritized over defensive programming when it comes to git remote handling. All build.sh files in docker-images directories should follow the same pattern for git metadata injection.

Applied to files:

  • tools/docker-images/clp-package/build.sh
📚 Learning: 2025-09-25T19:36:35.382Z
Learnt from: junhaoliao
PR: y-scope/clp#1335
File: tools/docker-images/clp-package/build.sh:23-29
Timestamp: 2025-09-25T19:36:35.382Z
Learning: Since Docker Engine 23.0, `docker build` is an alias for `docker buildx build` and automatically uses BuildKit as the builder. The `--iidfile` flag and other BuildKit features work correctly with the standard `docker build` command, so there's no need to explicitly use `docker buildx build` in build scripts unless specific buildx-only features are required.

Applied to files:

  • tools/docker-images/clp-package/build.sh
📚 Learning: 2025-07-07T17:43:04.349Z
Learnt from: jackluo923
PR: y-scope/clp#1054
File: components/core/tools/docker-images/clp-env-base-musllinux_1_2-aarch64/build.sh:3-5
Timestamp: 2025-07-07T17:43:04.349Z
Learning: In CLP project build scripts (specifically build.sh files in docker-images directories), maintain consistency with the established pattern of using separate `set -eu` and `set -o pipefail` commands rather than combining them into `set -euo pipefail`, to ensure uniform script structure across all platform build scripts.

Applied to files:

  • tools/docker-images/clp-package/build.sh
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: package-image
  • GitHub Check: lint-check (ubuntu-24.04)
  • GitHub Check: build (macos-15)

Comment on lines 18 to 26
remove_prev_image() {
if [[ -n "$prev_image_id" && "$prev_image_id" != "$new_image_id" ]]; then
if docker image inspect "$prev_image_id" >/dev/null 2>&1; then
echo "Removing previous image $prev_image_id."
docker image remove "$prev_image_id"
fi
fi
rm -f "$temp_iid_file"
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Prevent deleting the last good image on failed builds.

Line 19 runs during every EXIT. When the build fails, set -e aborts before new_image_id is populated, leaving it empty. The [[ -n "$prev_image_id" && "$prev_image_id" != "$new_image_id" ]] check still passes ("" ≠ previous), so we delete the only working image—exactly when the user needs it for rollback. Please gate the cleanup on new_image_id being set.

 remove_prev_image() {
-    if [[ -n "$prev_image_id" && "$prev_image_id" != "$new_image_id" ]]; then
-        if docker image inspect "$prev_image_id" >/dev/null 2>&1; then
-            echo "Removing previous image $prev_image_id."
-            docker image remove "$prev_image_id"
-        fi
-    fi
-    rm -f "$temp_iid_file"
+    rm -f "$temp_iid_file"
+
+    if [[ -z "$new_image_id" ]]; then
+        return
+    fi
+
+    if [[ -n "$prev_image_id" && "$prev_image_id" != "$new_image_id" ]]; then
+        if docker image inspect "$prev_image_id" >/dev/null 2>&1; then
+            echo "Removing previous image $prev_image_id."
+            docker image remove "$prev_image_id"
+        fi
+    fi
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
remove_prev_image() {
if [[ -n "$prev_image_id" && "$prev_image_id" != "$new_image_id" ]]; then
if docker image inspect "$prev_image_id" >/dev/null 2>&1; then
echo "Removing previous image $prev_image_id."
docker image remove "$prev_image_id"
fi
fi
rm -f "$temp_iid_file"
}
remove_prev_image() {
# Always clean up the temporary IID file first
rm -f "$temp_iid_file"
# If the new image ID was never set (build failed), do not remove the previous image
if [[ -z "$new_image_id" ]]; then
return
fi
# Only remove the previous image when a different one was successfully built
if [[ -n "$prev_image_id" && "$prev_image_id" != "$new_image_id" ]]; then
if docker image inspect "$prev_image_id" >/dev/null 2>&1; then
echo "Removing previous image $prev_image_id."
docker image remove "$prev_image_id"
fi
fi
}
🤖 Prompt for AI Agents
In tools/docker-images/clp-package/build.sh around lines 18 to 26, the cleanup
function can delete the last good image when a build fails because new_image_id
may be empty; update the conditional to require new_image_id is non-empty (e.g.
[[ -n "$prev_image_id" && -n "$new_image_id" && "$prev_image_id" !=
"$new_image_id" ]]) so removal only happens when a new image ID was successfully
set, and keep the rm -f "$temp_iid_file" at the end.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between aa4919f and c9d9959.

📒 Files selected for processing (1)
  • docs/src/dev-docs/building-package.md (2 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-25T19:26:32.436Z
Learnt from: junhaoliao
PR: y-scope/clp#1335
File: taskfiles/docker-images.yaml:15-15
Timestamp: 2025-09-25T19:26:32.436Z
Learning: In the CLP project's Taskfile, the `:package` task creates the G_PACKAGE_BUILD_DIR directory structure, so any task that depends on `:package` (like `docker-images:package`) can safely assume this directory exists without needing additional mkdir commands.

Applied to files:

  • docs/src/dev-docs/building-package.md
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: package-image
  • GitHub Check: lint-check (macos-15)
🔇 Additional comments (2)
docs/src/dev-docs/building-package.md (2)

88-88: Link Validated – No Changes Required The Docker installation link returns a successful response (HTTP 200) and directs users to the official Docker Engine installation instructions.


13-17: Verify Docker version requirements and add rationale. The --iidfile flag is used in the build script; confirm the minimum Docker CLI and Buildx plugin versions needed to support this option, update version constraints if necessary, and add a brief note explaining why these specific versions are required.

task docker-images:package
```

This will create a Docker image named `clp-package:dev`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Update documentation to reflect the new image tagging scheme.

According to the PR summary, the build process now generates unique identifiers to avoid overwriting older builds on shared machines. The AI summary mentions tagging as clp-package:dev-${USER}-${short_id}, but this line still states the image will be named clp-package:dev.

Please update this documentation to:

  1. Explain that each build generates a unique image identifier
  2. Clarify how users can reference the built image (e.g., via the image.id file mentioned in the PR summary)
  3. Optionally, mention the tagging convention if it's relevant for users
🤖 Prompt for AI Agents
In docs/src/dev-docs/building-package.md around line 86, the text still claims
the Docker image will be named `clp-package:dev` but the build now generates
unique image identifiers; update this line to state that each build produces a
uniquely tagged image to avoid overwrites, note where to find the exact image
reference (point to the generated image.id file created by the build), and
optionally show the tagging convention used (e.g.,
clp-package:dev-${USER}-${short_id}) so users know how to reference the image or
read image.id for the precise tag.

Copy link
Member

@kirkrodrigues kirkrodrigues left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the PR title, how about:

feat(docker): Generate unique tag for each clp-package dev image build to avoid conflicts on shared hosts.

@junhaoliao junhaoliao changed the title feat(docker): Generate unique identifier for each clp-package dev image build. feat(docker): Generate unique tag for each clp-package dev image build to avoid conflicts on shared hosts. Sep 30, 2025
@junhaoliao junhaoliao merged commit 62bea59 into y-scope:main Sep 30, 2025
25 checks passed
@junhaoliao junhaoliao deleted the package-image-id branch September 30, 2025 08:00
@junhaoliao
Copy link
Member Author

@kirkrodrigues if you think it makes sense to, in the same PR i can also remove tools/docker-images/clp-execution-base-ubuntu-jammy

Let's do it in another PR.

This is added as part of #1357 , which add the mechanism to select the image build by the unique id generated in this current PR #1335 .

we also discussed about cleaning up the image build process (that we don't have to copy everything into clp-package first; instead, we should specify the build/<component> paths in COPY commands in the Dockerfile and just directly copy from the build/<component>). while possible to be done in the same PR, i think it's cleaner to be done is a separate PR. if you agree, we can add an issue for that

Yeah, let's do it in another PR.

This is strictly optimization and isn't blocking the Docker Compose migration, so this is of lower priority at the moment. @coderabbitai create an issue to track the request so we won't forget

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 30, 2025

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Create a GitHub issue to track the Docker build process optimization

gh issue create \
  --repo y-scope/clp \
  --title "Optimize clp-package Docker build to avoid intermediate copying" \
  --body "## Description

Optimize the clp-package Docker image build process to avoid copying everything into the clp-package directory first. Instead, specify \`build/<component>\` paths directly in COPY commands in the Dockerfile.

## Background

Currently, the build process copies all components into clp-package before building the Docker image. This can be optimized by directly copying from \`build/<component>\` paths in the Dockerfile COPY commands.

## Priority

This is a low-priority optimization that is not blocking the Docker Compose migration.

## Related

- PR #1335: Generate unique identifier for each clp-package dev image build
- Comment: https://github.com/y-scope/clp/pull/1335#discussion_r${COMMENT_ID}
- Requested by: @junhaoliao" \
  --assignee junhaoliao

echo "Issue created successfully"

Length of output: 69


@junhaoliao I've created issue #1358 to track the Docker build process optimization. The issue has been assigned to you and includes the context about optimizing the clp-package build to avoid intermediate copying by using build/<component> paths directly in the Dockerfile COPY commands.


if docker image inspect "$prev_image_id" >/dev/null 2>&1; then
echo "Removing previous image $prev_image_id."
docker image remove "$prev_image_id"
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The image might fail to be removed because some running container might still be referencing it, which causes a non-zero exit code in the script.

When the image fails to be removed, we should either exit gracefully (with 0) with a warning message printed, or the caller should gracefully handle the non-exit code. Currently, the caller (the docker-images.yaml taskfile) doesn't handle this --- when the script fails, any commands following the script call (e.g., to copy the unique image id file into build/clp-package) would fail to execute.

@coderabbitai create an issue to track this

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash

# Create GitHub issue to track the image removal failure problem
gh issue create \
  --title "Handle docker image removal failures gracefully in clp-package build script" \
  --body "## Problem

In \`tools/docker-images/clp-package/build.sh\`, the \`remove_temp_file_and_prev_image\` function (called via EXIT trap) attempts to remove the previous image using \`docker image remove\`. This can fail with a non-zero exit code if a running container is still referencing the image.

Since the script uses \`set -eu\`, this failure causes the script to exit with an error code, which in turn causes subsequent commands in the caller (\`docker-images.yaml\` taskfile) to fail execution (e.g., copying the unique image ID file into \`build/clp-package\`).

## Proposed Solutions

Either:
1. Exit gracefully (with exit code 0) and print a warning message when image removal fails, or
2. Have the caller (taskfile) gracefully handle the non-zero exit code

## Context

Reported by @junhaoliao in PR #1335:
https://github.com/y-scope/clp/pull/1335#discussion_r2400399192

## Affected Files

- \`tools/docker-images/clp-package/build.sh\`
- \`docker-images.yaml\` (taskfile caller)" \
  --assignee junhaoliao

Length of output: 42


@junhaoliao I've created issue #1370 to track this: #1370

The issue documents the problem where docker image remove can fail if a container is still referencing the image, causing the script to exit with an error due to set -eu. It includes both proposed solutions (graceful exit with warning or caller handling the error) and references this PR discussion.

new_image_id="$(<"$temp_iid_file")"
echo "$new_image_id" > "$iid_file"

user="${USER:-$(id -un 2>/dev/null || whoami 2>/dev/null || echo unknown)}"
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's one edge case we missed to handle -

When the username cannot be found on the system (e.g., the username is not specified in /etc/passwd), the user id would still be printed by id -un command. However, since the id -un command exits with non-zero code, the other command would continue to run, which may cause line breaks to show up in the middle of the user string. e.g.,

id -un: Prints `123\n`; Exits with non-0
whoami: Prints nothing; Exits with non-9
echo unknown: Prints "unknown"

then `user` becomes: `123\nunknown`

@coderabbitai create an issue to track this

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Create GitHub issue to track the username fallback edge case

gh issue create \
  --repo y-scope/clp \
  --title "Edge case in username fallback logic in clp-package build script" \
  --body "## Description

An edge case exists in the username fallback logic in \`tools/docker-images/clp-package/build.sh\` at line 51:

\`\`\`bash
user=\"\${USER:-\$(id -un 2>/dev/null || whoami 2>/dev/null || echo unknown)}\"
\`\`\`

When the username cannot be found on the system (e.g., the username is not specified in \`/etc/passwd\`), \`id -un\` may print the numeric user ID followed by a newline but exit with a non-zero code. Due to the \`||\` chaining, subsequent commands (\`whoami\` and \`echo unknown\`) continue to execute, causing their output to be appended, resulting in a multi-line string with line breaks embedded in the \`user\` variable.

## Example scenario

\`\`\`
id -un: Prints 123\\n; Exits with non-0
whoami: Prints nothing; Exits with non-0
echo unknown: Prints \"unknown\"

Result: user becomes \"123\\nunknown\"
\`\`\`

This could lead to invalid Docker image tag names or unexpected behaviour in the build process.

## References

- PR: https://github.com/y-scope/clp/pull/1335
- Comment: https://github.com/y-scope/clp/pull/1335#discussion_r2400404851
- Reported by: @junhaoliao" \
  --assignee junhaoliao

Length of output: 42


@junhaoliao I've created issue #1371 to track this edge case: #1371

The issue documents the scenario where the || chain in the username fallback logic can result in a multi-line string when id -un prints a numeric ID but exits with a non-zero code, causing subsequent commands to append their output.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants