Skip to content
Merged
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
342 changes: 342 additions & 0 deletions .github/workflows/verify-deps.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,342 @@
name: πŸ§ͺ Verify Package Dependencies

on:
push:
branches: ['*']

env:
DEBIAN_FRONTEND: noninteractive

jobs:
verify-dependencies:
name: πŸ› οΈ ${{ matrix.os }}-${{ matrix.arch }}
runs-on: ${{ matrix.runs_on }}
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-22.04
arch: x86_64
runs_on: ubuntu-22.04
codename: jammy
- os: ubuntu-22.04
arch: aarch64
runs_on: ubuntu-22.04
codename: jammy
- os: ubuntu-24.04
arch: x86_64
runs_on: ubuntu-24.04
codename: noble
- os: ubuntu-24.04
arch: aarch64
runs_on: ubuntu-24.04
codename: noble

steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 1

- name: Set up environment
run: |
echo "CONTROL_FILE=packages/Debian/${{ matrix.arch }}/DEBIAN/control" >> $GITHUB_ENV
echo "REPORT=dep_report_${{ matrix.os }}_${{ matrix.arch }}.md" >> $GITHUB_ENV

- name: Validate control file exists
run: |
if [ ! -f "$CONTROL_FILE" ]; then
echo "❌ Control file missing: $CONTROL_FILE"
echo "Available files in packages/Debian/:"
find packages/Debian/ -name "control" 2>/dev/null || echo "No control files found"
exit 1
fi
echo "βœ… Control file found: $CONTROL_FILE"

- name: Verify dependencies & generate report
run: |
set -euo pipefail

# Initialize report
cat > "$REPORT" << EOF
## πŸ“¦ Dependency Verification Report

**Platform:** ${{ matrix.arch }} on ${{ matrix.os }} (${{ matrix.codename }})
**Generated:** $(date -u '+%Y-%m-%d %H:%M:%S UTC')
**Workflow:** ${GITHUB_WORKFLOW} - Run #${GITHUB_RUN_NUMBER}

EOF

# Enhanced dependency extraction function
extract_deps() {
local field="$1"
awk -v field="$field" '
BEGIN { found=0; deps="" }
{
if ($0 ~ "^" field ":") {
found=1
gsub("^" field ":[[:space:]]*", "")
deps = $0
} else if (found && $0 ~ "^[[:space:]]") {
# Continuation line (starts with whitespace)
gsub("^[[:space:]]*", "")
if ($0 != "") {
deps = deps " " $0
}
} else if (found && $0 !~ "^[[:space:]]" && $0 != "") {
# New field found, stop processing
print deps
exit
}
}
END { if (found && deps != "") print deps }
' "$CONTROL_FILE" | \
tr ',' '\n' | \
sed -E 's/\([^)]*\)//g' | \
sed -E 's/\[[^]]*\]//g' | \
awk '{gsub(/^[[:space:]]+|[[:space:]]+$/, ""); if ($1 != "") print $1}' | \
sort -u
}

# Function to detect and resolve version placeholders
resolve_version_placeholders() {
local control_file="$1"
local temp_file=""

echo "πŸ” Checking for version placeholders in $(basename "$control_file")..." >&2

# Check for BOOST_VER placeholder
if grep -q "{{BOOST_VER}}" "$control_file"; then
echo "πŸ“¦ Found {{BOOST_VER}} placeholder, resolving Boost version..." >&2

local boost_version=""
local boost_suffix=""

# Method 1: Check installed libboost-dev package
if [ -z "$boost_version" ]; then
boost_version=$(dpkg -l 2>/dev/null | grep "libboost-dev" | awk '{print $3}' | grep -oP '^\d+\.\d+\.\d+' | head -1 || true)
[ -n "$boost_version" ] && echo "βœ… Found installed Boost version: $boost_version" >&2
fi

# Method 2: Check available package version
if [ -z "$boost_version" ]; then
boost_version=$(apt-cache policy libboost-dev 2>/dev/null | grep "Candidate:" | awk '{print $2}' | grep -oP '^\d+\.\d+\.\d+' | head -1 || true)
[ -n "$boost_version" ] && echo "βœ… Found available Boost version: $boost_version" >&2
fi

# Determine the correct Boost package suffix format
echo "πŸ” Determining correct Boost package naming format..." >&2

local boost_major_minor=$(echo "$boost_version" | cut -d'.' -f1-2)
local boost_major_minor_no_dot=$(echo "$boost_major_minor" | tr -d '.')

# Test different naming conventions
if apt-cache show "libboost-system${boost_version}" >/dev/null 2>&1; then
boost_suffix="$boost_version"
echo "βœ… Found format: libboost-system${boost_suffix}" >&2
elif apt-cache show "libboost-system${boost_major_minor}" >/dev/null 2>&1; then
boost_suffix="$boost_major_minor"
echo "βœ… Found format: libboost-system${boost_suffix}" >&2
elif apt-cache show "libboost-system${boost_major_minor_no_dot}" >/dev/null 2>&1; then
boost_suffix="$boost_major_minor_no_dot"
echo "βœ… Found format: libboost-system${boost_suffix}" >&2
else
# Fallback to the most common format
boost_suffix="$boost_major_minor"
echo "⚠️ Could not detect format, using default: libboost-system${boost_suffix}" >&2
fi

echo "πŸ”’ Final Boost package suffix: $boost_suffix" >&2

# Create temporary file if not already created
if [ -z "$temp_file" ]; then
temp_file=$(mktemp)
cp "$control_file" "$temp_file"
fi

# Replace BOOST_VER placeholder
sed -i "s/{{BOOST_VER}}/${boost_suffix}/g" "$temp_file"
echo "βœ… Resolved {{BOOST_VER}} to ${boost_suffix}" >&2

# Show resolved dependencies for verification
echo "πŸ” Resolved Boost dependencies:" >&2
grep -E "libboost.*${boost_suffix}" "$temp_file" >&2 || true
fi

# TODO: Add more placeholder handlers here in the future
# Example for future use:
# if grep -q "{{PYTHON_VER}}" "$control_file"; then
# echo "πŸ“¦ Found {{PYTHON_VER}} placeholder, resolving Python version..."
# # Add Python version detection logic here
# fi

# Return the path to the resolved file (or original if no changes)
if [ -n "$temp_file" ]; then
echo "πŸ“ Using temporary control file with resolved placeholders: $temp_file" >&2
echo "$temp_file"
else
echo "ℹ️ No version placeholders found, using original file" >&2
echo "$control_file"
fi
}

# Resolve version placeholders in control file
RESOLVED_CONTROL_FILE=$(resolve_version_placeholders "$CONTROL_FILE")

# Store the temporary file path for cleanup later
if [ "$RESOLVED_CONTROL_FILE" != "$CONTROL_FILE" ]; then
TEMP_CONTROL_FILE="$RESOLVED_CONTROL_FILE"
echo "πŸ”„ Using resolved control file: $TEMP_CONTROL_FILE"
else
echo "πŸ“„ Using original control file: $CONTROL_FILE"
fi

# Update CONTROL_FILE to point to the resolved file
CONTROL_FILE="$RESOLVED_CONTROL_FILE"

# Extract dependencies
echo "πŸ” Extracting dependencies from control file..."
echo "πŸ“„ Using control file: $CONTROL_FILE"

BUILD_DEPS=$(extract_deps "Build-Depends")
RUNTIME_DEPS=$(extract_deps "Depends")
RECOMMENDS=$(extract_deps "Recommends")
SUGGESTS=$(extract_deps "Suggests")

# Validate extraction results
BUILD_COUNT=$(echo "$BUILD_DEPS" | wc -w)
RUNTIME_COUNT=$(echo "$RUNTIME_DEPS" | wc -w)

echo "πŸ“Š Extraction results:"
echo " - Build dependencies: $BUILD_COUNT packages"
echo " - Runtime dependencies: $RUNTIME_COUNT packages"
echo " - Recommended packages: $(echo "$RECOMMENDS" | wc -w) packages"
echo " - Suggested packages: $(echo "$SUGGESTS" | wc -w) packages"

if [ $BUILD_COUNT -eq 0 ] && [ $RUNTIME_COUNT -eq 0 ]; then
echo "❌ No dependencies extracted! This might indicate a parsing error."
echo "Control file content:"
head -20 "$CONTROL_FILE"
exit 1
fi

# Update package cache with retry
echo "πŸ“₯ Updating package cache..."
for i in {1..3}; do
if sudo apt-get update -qq; then
break
elif [ $i -eq 3 ]; then
echo "❌ Failed to update package cache after 3 attempts"
exit 1
else
echo "⚠️ Package cache update failed, retrying in 10s..."
sleep 10
fi
done

# Enhanced dependency checking function
check_dependencies() {
local label="$1"
local pkgs="$2"
local is_critical="$3"
local fail=0
local total=0
local available=0

echo "" >> "$REPORT"
echo "## $label" >> "$REPORT"

if [ -z "$pkgs" ]; then
echo "- ℹ️ No $label specified" >> "$REPORT"
return 0
fi

for pkg in $pkgs; do
total=$((total + 1))

# Skip empty package names
[ -z "$pkg" ] && continue

# Check if package is available
if apt-cache show "$pkg" > /dev/null 2>&1; then
# Get package version info
version=$(apt-cache policy "$pkg" 2>/dev/null | grep "Candidate:" | awk '{print $2}' || echo "unknown")
echo "- βœ… **$pkg** (version: $version)" >> "$REPORT"
available=$((available + 1))
else
echo "- ❌ **$pkg** - Package not found in repositories" >> "$REPORT"

# Try to find similar packages
similar=$(apt-cache search "^$pkg" 2>/dev/null | head -3 | cut -d' ' -f1 | tr '\n' ', ' | sed 's/,$//')
if [ -n "$similar" ]; then
echo " - πŸ’‘ Similar packages: $similar" >> "$REPORT"
fi

if [ "$is_critical" = "true" ]; then
fail=1
fi
fi
done

# Add summary
echo "" >> "$REPORT"
echo "**Summary:** $available/$total packages available" >> "$REPORT"

if [ $fail -eq 1 ]; then
echo "⚠️ **Critical dependencies missing!**" >> "$REPORT"
fi

return $fail
}

# Check all dependency types
check_dependencies "πŸ”¨ Build Dependencies" "$BUILD_DEPS" "true" || exit 1
check_dependencies "πŸƒ Runtime Dependencies" "$RUNTIME_DEPS" "true" || exit 1
check_dependencies "πŸ’‘ Recommended Packages" "$RECOMMENDS" "false" || true
check_dependencies "πŸ”§ Suggested Packages" "$SUGGESTS" "false" || true

# Add final summary
cat >> "$REPORT" << EOF

---

## βœ… Verification Complete

All dependencies are accessible for ${{ matrix.arch }} on ${{ matrix.os }} (${{ matrix.codename }}).

EOF

# Cleanup temporary file if created
if [ -n "${TEMP_CONTROL_FILE:-}" ] && [ -f "$TEMP_CONTROL_FILE" ]; then
rm -f "$TEMP_CONTROL_FILE"
echo "🧹 Cleaned up temporary control file"
fi

- name: Validate report was generated
run: |
if [ ! -f "$REPORT" ] || [ ! -s "$REPORT" ]; then
echo "❌ Report file is missing or empty"
exit 1
fi
echo "βœ… Report generated successfully ($(wc -l < "$REPORT") lines)"

- name: Add summary to job
if: always()
run: |
echo "# πŸ“¦ Summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
if [ -f "$REPORT" ]; then
cat "$REPORT" >> $GITHUB_STEP_SUMMARY
else
echo "❌ Report generation failed" >> $GITHUB_STEP_SUMMARY
fi

- name: Check for failures
if: failure()
run: |
echo "❌ Dependency verification failed for ${{ matrix.os }} (${{ matrix.arch }})"
echo "This indicates build or runtime dependencies are missing."
echo "Please review the generated report and update the control file or repository configuration."
exit 1