Skip to content

feat: add dependency conflict resolver#75

Open
jienigoto wants to merge 2 commits into
cxlinux-ai:mainfrom
jienigoto:codex/dependency-conflict-resolver-50
Open

feat: add dependency conflict resolver#75
jienigoto wants to merge 2 commits into
cxlinux-ai:mainfrom
jienigoto:codex/dependency-conflict-resolver-50

Conversation

@jienigoto

@jienigoto jienigoto commented Jun 27, 2026

Copy link
Copy Markdown

Fixes #50.

Summary

  • Add a read-only CX dependency resolver script for Debian/Ubuntu systems.
  • Use APT simulation only; the script does not install packages.
  • Report dependency trees, simulated install/removal/conflict plans, package alternatives, and auto-installed packages to review before orphan cleanup.
  • Add a make test-dep-resolver target and a mocked shell test harness.

Follow-up hardening

  • Validate missing or invalid --tree-depth operands before shifting.
  • Tolerate unresolved or virtual packages while printing dependency trees.
  • Keep failure-path test logs under the unique test TMP_DIR instead of predictable /tmp paths.

Validation

  • bash -n scripts/cx-dep-resolver.sh tests/dependency-resolver-tests.sh
  • bash tests/dependency-resolver-tests.sh
  • make test-dep-resolver

@jienigoto
jienigoto requested a review from a team as a code owner June 27, 2026 10:42
@coderabbitai

coderabbitai Bot commented Jun 27, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a Bash dependency-resolver script that checks package candidates, simulates APT installs, prints dependency and orphan-review output, and exits on broken or removal states. Also adds a mocked test script and a Makefile target to run it.

Changes

Dependency resolver CLI and test wiring

Layer / File(s) Summary
Script foundation and tree checks
scripts/cx-dep-resolver.sh
Strict Bash setup, package-name normalization, install-candidate checks, and recursive dependency-tree printing are added.
Simulation and reporting
scripts/cx-dep-resolver.sh
CLI parsing, APT simulation parsing, plain-English status output, alternative-package hints, orphan-review output, and exit-status handling are added.
Test harness and Makefile entrypoint
tests/dependency-resolver-tests.sh, Makefile
Mock APT/Dpkg tools exercise successful, removal, and missing-candidate runs, and test-dep-resolver is exposed through the Makefile.

Sequence Diagram(s)

sequenceDiagram
  participant Makefile
  participant "tests/dependency-resolver-tests.sh" as Tests
  participant "scripts/cx-dep-resolver.sh" as Resolver
  participant apt-cache
  participant apt-get
  participant apt-mark
  participant dpkg-query

  Makefile->>Tests: test-dep-resolver
  Tests->>Resolver: --no-color cx-demo
  Resolver->>apt-cache: policy / depends / show / search
  Resolver->>apt-get: -s install
  Resolver->>apt-mark: showauto
  Resolver->>dpkg-query: -W
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐇 Hop hop, the packages align,
The resolver sniffs each leafy sign.
Tests thump softly, makefile bright,
APT dreams dance through the night.
Hooray for tidy code!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ 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%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly matches the main change: adding a dependency conflict resolver script and related test support.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

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

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a new dependency conflict resolver script, scripts/cx-dep-resolver.sh, along with a test suite and a new Makefile target to run the tests. The review feedback highlights several robustness and security improvements for the shell scripts, including preventing potential crashes when parsing options, avoiding subshell overhead in recursive functions, handling regex characters in package names safely, improving error detection from APT simulation output, and using secure temporary directories for test logs.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +187 to +190
--tree-depth)
tree_depth="${2:-}"
shift 2
;;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

If --tree-depth is passed as the last argument without a value, shift 2 will attempt to shift more arguments than are left. In Bash, this returns a non-zero exit status, which under set -e will cause the script to crash immediately. Adding a check to ensure the argument is present prevents this crash.

Suggested change
--tree-depth)
tree_depth="${2:-}"
shift 2
;;
--tree-depth)
if [[ $# -lt 2 ]]; then
bad "Option --tree-depth requires an argument"
exit 2
fi
tree_depth="$2"
shift 2
;;

Comment on lines +71 to +74
candidate_exists() {
local package="$1"
"$APT_CACHE_BIN" policy "$package" 2>/dev/null | awk '/Candidate:/ {print $2; found=1} END {exit !found}' | grep -vq "(none)"
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The current implementation of candidate_exists uses a pipeline with grep -vq. With set -o pipefail enabled, if grep exits early or if the package does not exist, the pipeline can return a non-zero exit status or trigger SIGPIPE issues. We can simplify this and make it more robust by performing the entire check within awk.

Suggested change
candidate_exists() {
local package="$1"
"$APT_CACHE_BIN" policy "$package" 2>/dev/null | awk '/Candidate:/ {print $2; found=1} END {exit !found}' | grep -vq "(none)"
}
candidate_exists() {
local package="$1"
"$APT_CACHE_BIN" policy "$package" 2>/dev/null | awk '/Candidate:/ { found = ($2 && $2 != "(none)"); exit } END { exit !found }'
}

Comment thread scripts/cx-dep-resolver.sh Outdated
Comment on lines +93 to +100
"$APT_CACHE_BIN" depends "$package" 2>/dev/null |
awk '/^[[:space:]]*(PreDepends|Depends):/ {print $2}' |
sed 's/[<>|]//g' |
awk 'NF && !seen[$0]++' |
while read -r dependency; do
print_dependency_tree "$dependency" "$((depth - 1))" "$indent " "${seen},${package}"
done
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Running recursive function calls inside a pipeline causes each level of recursion to execute in a nested subshell. This is highly inefficient and can lead to process exhaustion or unexpected behavior with variables. Additionally, with pipefail and set -e active, any failure in the pipeline can prematurely terminate the script. Reading the dependencies into an array using process substitution avoids these issues.

Suggested change
"$APT_CACHE_BIN" depends "$package" 2>/dev/null |
awk '/^[[:space:]]*(PreDepends|Depends):/ {print $2}' |
sed 's/[<>|]//g' |
awk 'NF && !seen[$0]++' |
while read -r dependency; do
print_dependency_tree "$dependency" "$((depth - 1))" "$indent " "${seen},${package}"
done
}
local dependencies=()
while read -r dependency; do
dependencies+=("$dependency")
done < <("$APT_CACHE_BIN" depends "$package" 2>/dev/null |
awk '/^[[:space:]]*(PreDepends|Depends):/ {print $2}' |
sed 's/[<>|]//g' |
awk 'NF && !seen[$0]++' || true)
for dependency in "${dependencies[@]}"; do
print_dependency_tree "$dependency" "$((depth - 1))" "$indent " "${seen},${package}"
done

Comment thread scripts/cx-dep-resolver.sh Outdated
if [[ "$prefix" != "$package" && -n "$prefix" ]]; then
alternatives=$("$APT_CACHE_BIN" search "^${prefix}" 2>/dev/null |
awk '{print $1}' |
grep -v "^${package}$" |

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Using grep -v "^${package}$" can fail or behave unexpectedly if the package name contains regex special characters (such as + in g++ or . in node.js). Using grep -Fvx performs a safe, literal, exact-line match instead.

Suggested change
grep -v "^${package}$" |
grep -Fvx "${package}" |

Comment thread scripts/cx-dep-resolver.sh Outdated
simulation="$("$APT_GET_BIN" -s install "${packages[@]}" 2>&1 || true)"
removals="$(grep -E '^(Remv|The following packages will be REMOVED:)' <<<"$simulation" || true)"
held="$(grep -Ei 'held|kept back|changed held' <<<"$simulation" || true)"
broken="$(grep -Ei 'broken packages|unmet dependencies|conflicts with|but it is not going to be installed' <<<"$simulation" || true)"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

If apt-get fails with a fatal error (such as E: ...), the script might exit with a success status (0) if the error message does not match the current broken regex. Including ^E: in the regex ensures that any standard APT error is correctly caught and treated as a failure.

Suggested change
broken="$(grep -Ei 'broken packages|unmet dependencies|conflicts with|but it is not going to be installed' <<<"$simulation" || true)"
broken="$(grep -Ei 'broken packages|unmet dependencies|conflicts with|but it is not going to be installed|^E:' <<<"$simulation" || true)"

Comment thread tests/dependency-resolver-tests.sh Outdated
Comment on lines +107 to +121
"$ROOT_DIR/scripts/cx-dep-resolver.sh" --no-color danger-package >/tmp/cx-dep-danger.log 2>&1; then
echo "Expected resolver to fail when apt simulation removes packages" >&2
cat /tmp/cx-dep-danger.log >&2
exit 1
fi

if PATH="$TMP_DIR:$PATH" \
APT_GET_BIN="$TMP_DIR/apt-get" \
APT_CACHE_BIN="$TMP_DIR/apt-cache" \
APT_MARK_BIN="$TMP_DIR/apt-mark" \
DPKG_QUERY_BIN="$TMP_DIR/dpkg-query" \
"$ROOT_DIR/scripts/cx-dep-resolver.sh" --no-color missing-package >/tmp/cx-dep-missing.log 2>&1; then
echo "Expected resolver to fail when no candidate exists" >&2
cat /tmp/cx-dep-missing.log >&2
exit 1

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Writing temporary log files directly to /tmp is insecure and can cause permission conflicts or failures on shared multi-user systems or concurrent CI runners. Since a secure temporary directory TMP_DIR is already created and cleaned up on exit, we should write the test logs there instead.

if PATH="$TMP_DIR:$PATH" \
    APT_GET_BIN="$TMP_DIR/apt-get" \
    APT_CACHE_BIN="$TMP_DIR/apt-cache" \
    APT_MARK_BIN="$TMP_DIR/apt-mark" \
    DPKG_QUERY_BIN="$TMP_DIR/dpkg-query" \
    "$ROOT_DIR/scripts/cx-dep-resolver.sh" --no-color danger-package >"$TMP_DIR/cx-dep-danger.log" 2>&1; then
    echo "Expected resolver to fail when apt simulation removes packages" >&2
    cat "$TMP_DIR/cx-dep-danger.log" >&2
    exit 1
fi

if PATH="$TMP_DIR:$PATH" \
    APT_GET_BIN="$TMP_DIR/apt-get" \
    APT_CACHE_BIN="$TMP_DIR/apt-cache" \
    APT_MARK_BIN="$TMP_DIR/apt-mark" \
    DPKG_QUERY_BIN="$TMP_DIR/dpkg-query" \
    "$ROOT_DIR/scripts/cx-dep-resolver.sh" --no-color missing-package >"$TMP_DIR/cx-dep-missing.log" 2>&1; then
    echo "Expected resolver to fail when no candidate exists" >&2
    cat "$TMP_DIR/cx-dep-missing.log" >&2
    exit 1
fi

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/cx-dep-resolver.sh`:
- Around line 187-190: The `--tree-depth` option handling in
`cx-dep-resolver.sh` is shifting before confirming an operand exists, so the
script dies with a shell error when the flag is last. Update the `--tree-depth)`
case to validate that `$2` is present before `shift 2`, and route missing or
invalid values through the existing non-negative-integer validation/error path.
Keep the fix localized to the `--tree-depth` branch so the script emits the
intended message instead of aborting from `shift`.
- Around line 93-99: The dependency traversal in print_dependency_tree should
tolerate unresolved packages instead of exiting when apt-cache depends fails
with an unknown or virtual package. Update the pipeline around "$APT_CACHE_BIN"
depends "$package" so that non-zero status from the producer is handled or
ignored for missing-package cases, while preserving the recursive call over
discovered dependencies. Keep the fix localized to the print_dependency_tree
flow and its dependency discovery loop.

In `@tests/dependency-resolver-tests.sh`:
- Around line 107-122: The failure-path log redirections in the dependency
resolver test use predictable /tmp paths, which can be hijacked on shared
runners. Update the shell test around the cx-dep-resolver.sh invocations to
create the danger-package and missing-package log files under the existing
TMP_DIR or with mktemp, and redirect stdout/stderr to those unique paths instead
of hardcoded /tmp/cx-dep-danger.log and /tmp/cx-dep-missing.log.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2f230b50-9bef-4d08-a33c-ecc7708b5d22

📥 Commits

Reviewing files that changed from the base of the PR and between cfd0627 and 9a6ac3e.

📒 Files selected for processing (3)
  • Makefile
  • scripts/cx-dep-resolver.sh
  • tests/dependency-resolver-tests.sh

Comment thread scripts/cx-dep-resolver.sh Outdated
Comment thread scripts/cx-dep-resolver.sh
Comment thread tests/dependency-resolver-tests.sh Outdated
@jienigoto

jienigoto commented Jun 29, 2026

Copy link
Copy Markdown
Author

Follow-up after the automated review: I pushed commit 32e1064 to address the three actionable items. The resolver now validates a missing/invalid --tree-depth operand before shifting, dependency-tree traversal tolerates unresolved/virtual packages, and the failure-path test logs are written under the existing TMP_DIR instead of predictable /tmp paths.

Validation: bash tests/dependency-resolver-tests.sh passes locally.

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.

Dependency Conflict Resolver with Visual Tree

1 participant