Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
121 changes: 121 additions & 0 deletions .github/workflows/sync-dirs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,127 @@ jobs:

echo "Sync completed successfully!"

- name: Heal libs.versions.toml — pull missing build-logic aliases from upstream
shell: bash
run: |
set -uo pipefail
# Self-healing step: `build-logic/` is sync'd from upstream, but
# `gradle/libs.versions.toml` is intentionally consumer-local (it carries
# project-specific package names + version overrides). When upstream adds
# new `libs.<X>` accessors in build-logic with their matching catalog
# entries, only the build-logic gets sync'd — the consumer's catalog falls
# behind and the synced build-logic references aliases that don't exist
# locally. This step detects that gap and pulls the missing alias entries
# (and their referenced version keys) directly from the upstream catalog.

LIBS_TOML="gradle/libs.versions.toml"
TEMP_BRANCH="${{ env.TEMP_BRANCH }}"

if [[ ! -f "$LIBS_TOML" ]]; then
echo "ℹ️ Consumer has no $LIBS_TOML — nothing to heal."
exit 0
fi
if [[ ! -d "build-logic" ]]; then
echo "ℹ️ Consumer has no build-logic/ — nothing to heal."
exit 0
fi

# Extract every libs.<ref> usage in build-logic Kotlin + KTS source.
raw_refs=$(grep -rhoE '\blibs\.[a-zA-Z][a-zA-Z0-9._]*' \
--include='*.kt' --include='*.kts' \
build-logic 2>/dev/null \
| sed -E 's/^libs\.//' \
| sort -u)

# Method-call accessors are not alias references — they take string args.
method_re='^(findLibrary|findVersion|findBundle|findPlugin)([(.]|$)'

# Cache upstream's catalog content once (avoid re-shelling git show per ref).
upstream_toml=$(git show "${TEMP_BRANCH}:${LIBS_TOML}" 2>/dev/null || true)
if [[ -z "$upstream_toml" ]]; then
echo "⚠️ Upstream has no ${LIBS_TOML} — skipping heal."
exit 0
fi

# Helper: insert a line at the start of [section] in the consumer's catalog.
insert_into_section() {
local section_name="$1"
local line="$2"
local line_no
line_no=$(grep -n "^\[${section_name}\]" "$LIBS_TOML" | head -1 | cut -d: -f1)
if [[ -z "$line_no" ]]; then
echo " ⚠️ No [$section_name] section header in $LIBS_TOML — skipping '$line'"
return 1
fi
{ head -n "$line_no" "$LIBS_TOML"; echo "$line"; tail -n +$((line_no + 1)) "$LIBS_TOML"; } > "${LIBS_TOML}.tmp"
mv "${LIBS_TOML}.tmp" "$LIBS_TOML"
}

healed=0
warnings=0
while IFS= read -r ref; do
[[ -z "$ref" ]] && continue
[[ "$ref" =~ $method_re ]] && continue

# Resolve target section + alias key per Gradle accessor convention.
section="libraries"
lookup="$ref"
case "$ref" in
plugins.*)
section="plugins"
lookup="${ref#plugins.}"
;;
versions.*)
section="versions"
lookup="${ref#versions.}"
# Whitelist the catalog filename — `libs.versions.toml` is a string in source.
[[ "$lookup" == "toml" ]] && continue
;;
esac
key=$(echo "$lookup" | sed 's/\./-/g')

# Already declared in the consumer's catalog? Skip.
if grep -qE "^${key}[[:space:]]*=" "$LIBS_TOML"; then
continue
fi

# Pull the matching entry from upstream.
upstream_line=$(echo "$upstream_toml" | grep -E "^${key}[[:space:]]*=" | head -1)
if [[ -z "$upstream_line" ]]; then
echo " ⚠️ Missing alias '$key' (for libs.$ref) not present in upstream catalog either — manual fix needed."
warnings=$((warnings + 1))
continue
fi

# If the alias references a version.ref, ensure that version key is also present.
ref_version=$(echo "$upstream_line" | grep -oE 'version\.ref = "[^"]+"' | sed -E 's/version\.ref = "([^"]+)"/\1/' | head -1)
if [[ -n "$ref_version" ]] && ! grep -qE "^${ref_version}[[:space:]]*=" "$LIBS_TOML"; then
ver_line=$(echo "$upstream_toml" | grep -E "^${ref_version}[[:space:]]*=" | head -1)
Comment on lines +332 to +347

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Make the workflow's catalog matching section-aware.

This step also searches the entire TOML for ^${key}=. If the same key exists in another section, the workflow can incorrectly skip a missing alias or fetch the wrong upstream line. Match within the resolved [libraries], [plugins], or [versions] block instead.

🤖 Prompt for 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.

In @.github/workflows/sync-dirs.yaml around lines 332 - 347, The current grep
checks for "^${key}=" across the whole TOML which can match identical keys in
other sections; instead, determine which TOML section the key belongs to (e.g.,
libraries/plugins/versions) and extract only that section from both "$LIBS_TOML"
and "$upstream_toml" before running the greps. Concretely: compute a section
name for the current key (based on how key is built for libs.$ref), extract
section_local=$(sed -n '/^\[<section>\]/,/^\[/{p}' "$LIBS_TOML") and
section_upstream=$(echo "$upstream_toml" | sed -n '/^\[<section>\]/,/^\[/{p}'),
then replace the three greps that use "$LIBS_TOML" or "$upstream_toml" (the
exists check, upstream_line lookup, and ref_version presence check that sets
ver_line) to search only within section_local or section_upstream as appropriate
(and when ref_version refers to a version key, search the [versions] section
contents). Ensure variable names from the diff (key, LIBS_TOML, upstream_toml,
ref_version, ver_line) are used so you update the exact checks.

if [[ -n "$ver_line" ]]; then
if insert_into_section "versions" "$ver_line"; then
echo " ✅ Added version '$ref_version' (referenced by '$key') to [versions]"
healed=$((healed + 1))
fi
fi
fi

# Insert the alias entry itself.
if insert_into_section "$section" "$upstream_line"; then
echo " ✅ Added '$key' to [$section] from upstream"
healed=$((healed + 1))
fi
done <<< "$raw_refs"

echo
if [[ $healed -gt 0 ]]; then
echo "🩹 Healed $healed entries in $LIBS_TOML — they will be included in the sync PR."
elif [[ $warnings -gt 0 ]]; then
echo "⚠️ $warnings missing aliases could not be auto-healed (not in upstream either). Manual fix needed."
exit 1
else
echo "✅ libs.versions.toml already in sync with build-logic — no heal needed."
fi

- name: Clean up temporary branch
if: always()
run: git branch -D "${{ env.TEMP_BRANCH }}" 2>/dev/null || true
Expand Down
127 changes: 126 additions & 1 deletion sync-dirs.sh
Original file line number Diff line number Diff line change
Expand Up @@ -546,13 +546,133 @@ for file in "${SYNC_FILES[@]}"; do
sync_file "$file" "$TEMP_BRANCH"
done

# Self-heal libs.versions.toml — pull missing build-logic aliases from upstream.
# `build-logic/` is sync'd but `gradle/libs.versions.toml` is consumer-local (it
# carries project-specific package names + version overrides). When upstream adds
# new `libs.<X>` accessors in build-logic with their matching catalog entries,
# only the build-logic gets sync'd — the consumer's catalog falls behind and the
# synced build-logic references aliases that don't exist locally. This function
# detects that gap and pulls the missing alias entries (and their referenced
# version keys) directly from the upstream catalog (`$TEMP_BRANCH`).
heal_libs_versions_toml() {
local libs_toml="gradle/libs.versions.toml"

if [ ! -f "$libs_toml" ]; then
return 0
fi
if [ ! -d "build-logic" ]; then
return 0
fi

echo -e "\n${BLUE}${BOLD}Healing libs.versions.toml...${NC}"
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n"

local raw_refs
raw_refs=$(grep -rhoE '\blibs\.[a-zA-Z][a-zA-Z0-9._]*' \
--include='*.kt' --include='*.kts' \
build-logic 2>/dev/null \
| sed -E 's/^libs\.//' \
| sort -u)

# Method-call accessors are not alias references — they take string args.
local method_re='^(findLibrary|findVersion|findBundle|findPlugin)([(.]|$)'

# Cache upstream's catalog content once.
local upstream_toml
upstream_toml=$(git show "${TEMP_BRANCH}:${libs_toml}" 2>/dev/null || true)
if [ -z "$upstream_toml" ]; then
print_warning "Upstream has no ${libs_toml} — skipping heal."
return 0
fi

# Helper: insert a line right after [section] in the consumer's catalog.
insert_into_section() {
local section_name="$1"
local line="$2"
local line_no
line_no=$(grep -n "^\[${section_name}\]" "$libs_toml" | head -1 | cut -d: -f1)
if [ -z "$line_no" ]; then
print_warning "No [${section_name}] section header — skipping insert of '$line'"
return 1
fi
{ head -n "$line_no" "$libs_toml"; echo "$line"; tail -n +$((line_no + 1)) "$libs_toml"; } > "${libs_toml}.tmp"
mv "${libs_toml}.tmp" "$libs_toml"
}

local healed=0
local warnings=0
while IFS= read -r ref; do
[ -z "$ref" ] && continue
[[ "$ref" =~ $method_re ]] && continue

local section="libraries"
local lookup="$ref"
case "$ref" in
plugins.*)
section="plugins"
lookup="${ref#plugins.}"
;;
versions.*)
section="versions"
lookup="${ref#versions.}"
[ "$lookup" = "toml" ] && continue
;;
esac
local key
key=$(echo "$lookup" | sed 's/\./-/g')

if grep -qE "^${key}[[:space:]]*=" "$libs_toml"; then
continue
fi

local upstream_line
upstream_line=$(echo "$upstream_toml" | grep -E "^${key}[[:space:]]*=" | head -1)
if [ -z "$upstream_line" ]; then
print_warning "Missing alias '$key' (for libs.$ref) not present in upstream catalog either — manual fix needed."
warnings=$((warnings + 1))
continue
fi

# Pull the version key too, if the alias version.refs something missing.
local ref_version
ref_version=$(echo "$upstream_line" | grep -oE 'version\.ref = "[^"]+"' | sed -E 's/version\.ref = "([^"]+)"/\1/' | head -1)
if [ -n "$ref_version" ] && ! grep -qE "^${ref_version}[[:space:]]*=" "$libs_toml"; then
local ver_line
ver_line=$(echo "$upstream_toml" | grep -E "^${ref_version}[[:space:]]*=" | head -1)
Comment on lines +624 to +641

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Scope catalog lookups to the computed TOML section.

These grep checks scan the whole file, so a key that already exists in [versions] can make a missing [plugins] or [libraries] entry look resolved, and the upstream lookup can copy the wrong line for the same reason. The heal logic needs section-aware matching for both the alias lookup and the version.ref lookup.

🤖 Prompt for 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.

In `@sync-dirs.sh` around lines 624 - 641, The current grep checks on $libs_toml
and $upstream_toml are scanning entire files and can match keys from other TOML
sections; update the lookup logic so both the alias lookup (when computing
$upstream_line) and the version lookup (when computing $ver_line using
$ref_version) first extract the specific TOML section block (e.g. the computed
libraries/plugins section that contains the alias) and then run the grep on that
block only. Concretely: derive the section header for the alias, extract the
block from $libs_toml and $upstream_toml (using sed/awk to get lines between the
header and the next [...]) into temporary variables and use those variables in
the existing grep/grep -oE commands that produce $upstream_line, $ref_version
and $ver_line so matches are section-scoped.

if [ -n "$ver_line" ]; then
if insert_into_section "versions" "$ver_line"; then
echo -e " ${GREEN}${CHECKMARK}${NC} Added version '${ref_version}' (referenced by '${key}') to [versions]"
healed=$((healed + 1))
fi
fi
fi

if insert_into_section "$section" "$upstream_line"; then
echo -e " ${GREEN}${CHECKMARK}${NC} Added '${key}' to [${section}] from upstream"
healed=$((healed + 1))
fi
done <<< "$raw_refs"

if [ $healed -gt 0 ]; then
echo -e "\n${GREEN}${BOLD}🩹 Healed ${healed} entries in ${libs_toml} — they will be included in the sync.${NC}"
elif [ $warnings -gt 0 ]; then
print_warning "${warnings} missing aliases could not be auto-healed (not in upstream either). Manual fix needed."
else
echo -e " ${GREEN}${CHECKMARK}${NC} libs.versions.toml already in sync with build-logic — no heal needed."
fi
}

if [ "$DRY_RUN" = false ]; then
# Restore root-level excluded files
restore_root_files

cleanup_temp_dirs
rm -rf temp_files

# Self-heal the version catalog BEFORE we drop the temp branch (we need it
# to access upstream's libs.versions.toml).
heal_libs_versions_toml

# Cleanup temporary branch
print_step "Cleaning up temporary branch..."
git branch -D "$TEMP_BRANCH" || handle_error "Failed to delete temporary branch"
Expand All @@ -562,11 +682,16 @@ if [ "$DRY_RUN" = false ]; then
if ! git diff --quiet --exit-code --cached; then
print_step "Committing changes..."
git add "${SYNC_DIRS[@]}" "${SYNC_FILES[@]}"
# Include any catalog heal additions made by heal_libs_versions_toml.
if [ -f "gradle/libs.versions.toml" ]; then
git add "gradle/libs.versions.toml"
fi
git commit -m "sync: Update directories and files from upstream

This PR syncs the following items with upstream:
- Directories: ${SYNC_DIRS[*]}
- Files: ${SYNC_FILES[*]}" || handle_error "Failed to commit changes"
- Files: ${SYNC_FILES[*]}
- Auto-heal: gradle/libs.versions.toml (pulls missing build-logic aliases from upstream)" || handle_error "Failed to commit changes"
show_progress

if [ "$FORCE" = false ]; then
Expand Down
Loading