Skip to content

Commit 03dce44

Browse files
therajanmauryaRajan Maurya
andauthored
fix(sync-dirs): self-heal libs.versions.toml when upstream adds build-logic aliases (#163)
Co-authored-by: Rajan Maurya <therajanmaurya@Rajans-MacBook-Pro.local>
1 parent e80e14c commit 03dce44

2 files changed

Lines changed: 247 additions & 1 deletion

File tree

.github/workflows/sync-dirs.yaml

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,127 @@ jobs:
249249
250250
echo "Sync completed successfully!"
251251
252+
- name: Heal libs.versions.toml — pull missing build-logic aliases from upstream
253+
shell: bash
254+
run: |
255+
set -uo pipefail
256+
# Self-healing step: `build-logic/` is sync'd from upstream, but
257+
# `gradle/libs.versions.toml` is intentionally consumer-local (it carries
258+
# project-specific package names + version overrides). When upstream adds
259+
# new `libs.<X>` accessors in build-logic with their matching catalog
260+
# entries, only the build-logic gets sync'd — the consumer's catalog falls
261+
# behind and the synced build-logic references aliases that don't exist
262+
# locally. This step detects that gap and pulls the missing alias entries
263+
# (and their referenced version keys) directly from the upstream catalog.
264+
265+
LIBS_TOML="gradle/libs.versions.toml"
266+
TEMP_BRANCH="${{ env.TEMP_BRANCH }}"
267+
268+
if [[ ! -f "$LIBS_TOML" ]]; then
269+
echo "ℹ️ Consumer has no $LIBS_TOML — nothing to heal."
270+
exit 0
271+
fi
272+
if [[ ! -d "build-logic" ]]; then
273+
echo "ℹ️ Consumer has no build-logic/ — nothing to heal."
274+
exit 0
275+
fi
276+
277+
# Extract every libs.<ref> usage in build-logic Kotlin + KTS source.
278+
raw_refs=$(grep -rhoE '\blibs\.[a-zA-Z][a-zA-Z0-9._]*' \
279+
--include='*.kt' --include='*.kts' \
280+
build-logic 2>/dev/null \
281+
| sed -E 's/^libs\.//' \
282+
| sort -u)
283+
284+
# Method-call accessors are not alias references — they take string args.
285+
method_re='^(findLibrary|findVersion|findBundle|findPlugin)([(.]|$)'
286+
287+
# Cache upstream's catalog content once (avoid re-shelling git show per ref).
288+
upstream_toml=$(git show "${TEMP_BRANCH}:${LIBS_TOML}" 2>/dev/null || true)
289+
if [[ -z "$upstream_toml" ]]; then
290+
echo "⚠️ Upstream has no ${LIBS_TOML} — skipping heal."
291+
exit 0
292+
fi
293+
294+
# Helper: insert a line at the start of [section] in the consumer's catalog.
295+
insert_into_section() {
296+
local section_name="$1"
297+
local line="$2"
298+
local line_no
299+
line_no=$(grep -n "^\[${section_name}\]" "$LIBS_TOML" | head -1 | cut -d: -f1)
300+
if [[ -z "$line_no" ]]; then
301+
echo " ⚠️ No [$section_name] section header in $LIBS_TOML — skipping '$line'"
302+
return 1
303+
fi
304+
{ head -n "$line_no" "$LIBS_TOML"; echo "$line"; tail -n +$((line_no + 1)) "$LIBS_TOML"; } > "${LIBS_TOML}.tmp"
305+
mv "${LIBS_TOML}.tmp" "$LIBS_TOML"
306+
}
307+
308+
healed=0
309+
warnings=0
310+
while IFS= read -r ref; do
311+
[[ -z "$ref" ]] && continue
312+
[[ "$ref" =~ $method_re ]] && continue
313+
314+
# Resolve target section + alias key per Gradle accessor convention.
315+
section="libraries"
316+
lookup="$ref"
317+
case "$ref" in
318+
plugins.*)
319+
section="plugins"
320+
lookup="${ref#plugins.}"
321+
;;
322+
versions.*)
323+
section="versions"
324+
lookup="${ref#versions.}"
325+
# Whitelist the catalog filename — `libs.versions.toml` is a string in source.
326+
[[ "$lookup" == "toml" ]] && continue
327+
;;
328+
esac
329+
key=$(echo "$lookup" | sed 's/\./-/g')
330+
331+
# Already declared in the consumer's catalog? Skip.
332+
if grep -qE "^${key}[[:space:]]*=" "$LIBS_TOML"; then
333+
continue
334+
fi
335+
336+
# Pull the matching entry from upstream.
337+
upstream_line=$(echo "$upstream_toml" | grep -E "^${key}[[:space:]]*=" | head -1)
338+
if [[ -z "$upstream_line" ]]; then
339+
echo " ⚠️ Missing alias '$key' (for libs.$ref) not present in upstream catalog either — manual fix needed."
340+
warnings=$((warnings + 1))
341+
continue
342+
fi
343+
344+
# If the alias references a version.ref, ensure that version key is also present.
345+
ref_version=$(echo "$upstream_line" | grep -oE 'version\.ref = "[^"]+"' | sed -E 's/version\.ref = "([^"]+)"/\1/' | head -1)
346+
if [[ -n "$ref_version" ]] && ! grep -qE "^${ref_version}[[:space:]]*=" "$LIBS_TOML"; then
347+
ver_line=$(echo "$upstream_toml" | grep -E "^${ref_version}[[:space:]]*=" | head -1)
348+
if [[ -n "$ver_line" ]]; then
349+
if insert_into_section "versions" "$ver_line"; then
350+
echo " ✅ Added version '$ref_version' (referenced by '$key') to [versions]"
351+
healed=$((healed + 1))
352+
fi
353+
fi
354+
fi
355+
356+
# Insert the alias entry itself.
357+
if insert_into_section "$section" "$upstream_line"; then
358+
echo " ✅ Added '$key' to [$section] from upstream"
359+
healed=$((healed + 1))
360+
fi
361+
done <<< "$raw_refs"
362+
363+
echo
364+
if [[ $healed -gt 0 ]]; then
365+
echo "🩹 Healed $healed entries in $LIBS_TOML — they will be included in the sync PR."
366+
elif [[ $warnings -gt 0 ]]; then
367+
echo "⚠️ $warnings missing aliases could not be auto-healed (not in upstream either). Manual fix needed."
368+
exit 1
369+
else
370+
echo "✅ libs.versions.toml already in sync with build-logic — no heal needed."
371+
fi
372+
252373
- name: Clean up temporary branch
253374
if: always()
254375
run: git branch -D "${{ env.TEMP_BRANCH }}" 2>/dev/null || true

sync-dirs.sh

Lines changed: 126 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -546,13 +546,133 @@ for file in "${SYNC_FILES[@]}"; do
546546
sync_file "$file" "$TEMP_BRANCH"
547547
done
548548

549+
# Self-heal libs.versions.toml — pull missing build-logic aliases from upstream.
550+
# `build-logic/` is sync'd but `gradle/libs.versions.toml` is consumer-local (it
551+
# carries project-specific package names + version overrides). When upstream adds
552+
# new `libs.<X>` accessors in build-logic with their matching catalog entries,
553+
# only the build-logic gets sync'd — the consumer's catalog falls behind and the
554+
# synced build-logic references aliases that don't exist locally. This function
555+
# detects that gap and pulls the missing alias entries (and their referenced
556+
# version keys) directly from the upstream catalog (`$TEMP_BRANCH`).
557+
heal_libs_versions_toml() {
558+
local libs_toml="gradle/libs.versions.toml"
559+
560+
if [ ! -f "$libs_toml" ]; then
561+
return 0
562+
fi
563+
if [ ! -d "build-logic" ]; then
564+
return 0
565+
fi
566+
567+
echo -e "\n${BLUE}${BOLD}Healing libs.versions.toml...${NC}"
568+
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n"
569+
570+
local raw_refs
571+
raw_refs=$(grep -rhoE '\blibs\.[a-zA-Z][a-zA-Z0-9._]*' \
572+
--include='*.kt' --include='*.kts' \
573+
build-logic 2>/dev/null \
574+
| sed -E 's/^libs\.//' \
575+
| sort -u)
576+
577+
# Method-call accessors are not alias references — they take string args.
578+
local method_re='^(findLibrary|findVersion|findBundle|findPlugin)([(.]|$)'
579+
580+
# Cache upstream's catalog content once.
581+
local upstream_toml
582+
upstream_toml=$(git show "${TEMP_BRANCH}:${libs_toml}" 2>/dev/null || true)
583+
if [ -z "$upstream_toml" ]; then
584+
print_warning "Upstream has no ${libs_toml} — skipping heal."
585+
return 0
586+
fi
587+
588+
# Helper: insert a line right after [section] in the consumer's catalog.
589+
insert_into_section() {
590+
local section_name="$1"
591+
local line="$2"
592+
local line_no
593+
line_no=$(grep -n "^\[${section_name}\]" "$libs_toml" | head -1 | cut -d: -f1)
594+
if [ -z "$line_no" ]; then
595+
print_warning "No [${section_name}] section header — skipping insert of '$line'"
596+
return 1
597+
fi
598+
{ head -n "$line_no" "$libs_toml"; echo "$line"; tail -n +$((line_no + 1)) "$libs_toml"; } > "${libs_toml}.tmp"
599+
mv "${libs_toml}.tmp" "$libs_toml"
600+
}
601+
602+
local healed=0
603+
local warnings=0
604+
while IFS= read -r ref; do
605+
[ -z "$ref" ] && continue
606+
[[ "$ref" =~ $method_re ]] && continue
607+
608+
local section="libraries"
609+
local lookup="$ref"
610+
case "$ref" in
611+
plugins.*)
612+
section="plugins"
613+
lookup="${ref#plugins.}"
614+
;;
615+
versions.*)
616+
section="versions"
617+
lookup="${ref#versions.}"
618+
[ "$lookup" = "toml" ] && continue
619+
;;
620+
esac
621+
local key
622+
key=$(echo "$lookup" | sed 's/\./-/g')
623+
624+
if grep -qE "^${key}[[:space:]]*=" "$libs_toml"; then
625+
continue
626+
fi
627+
628+
local upstream_line
629+
upstream_line=$(echo "$upstream_toml" | grep -E "^${key}[[:space:]]*=" | head -1)
630+
if [ -z "$upstream_line" ]; then
631+
print_warning "Missing alias '$key' (for libs.$ref) not present in upstream catalog either — manual fix needed."
632+
warnings=$((warnings + 1))
633+
continue
634+
fi
635+
636+
# Pull the version key too, if the alias version.refs something missing.
637+
local ref_version
638+
ref_version=$(echo "$upstream_line" | grep -oE 'version\.ref = "[^"]+"' | sed -E 's/version\.ref = "([^"]+)"/\1/' | head -1)
639+
if [ -n "$ref_version" ] && ! grep -qE "^${ref_version}[[:space:]]*=" "$libs_toml"; then
640+
local ver_line
641+
ver_line=$(echo "$upstream_toml" | grep -E "^${ref_version}[[:space:]]*=" | head -1)
642+
if [ -n "$ver_line" ]; then
643+
if insert_into_section "versions" "$ver_line"; then
644+
echo -e " ${GREEN}${CHECKMARK}${NC} Added version '${ref_version}' (referenced by '${key}') to [versions]"
645+
healed=$((healed + 1))
646+
fi
647+
fi
648+
fi
649+
650+
if insert_into_section "$section" "$upstream_line"; then
651+
echo -e " ${GREEN}${CHECKMARK}${NC} Added '${key}' to [${section}] from upstream"
652+
healed=$((healed + 1))
653+
fi
654+
done <<< "$raw_refs"
655+
656+
if [ $healed -gt 0 ]; then
657+
echo -e "\n${GREEN}${BOLD}🩹 Healed ${healed} entries in ${libs_toml} — they will be included in the sync.${NC}"
658+
elif [ $warnings -gt 0 ]; then
659+
print_warning "${warnings} missing aliases could not be auto-healed (not in upstream either). Manual fix needed."
660+
else
661+
echo -e " ${GREEN}${CHECKMARK}${NC} libs.versions.toml already in sync with build-logic — no heal needed."
662+
fi
663+
}
664+
549665
if [ "$DRY_RUN" = false ]; then
550666
# Restore root-level excluded files
551667
restore_root_files
552668

553669
cleanup_temp_dirs
554670
rm -rf temp_files
555671

672+
# Self-heal the version catalog BEFORE we drop the temp branch (we need it
673+
# to access upstream's libs.versions.toml).
674+
heal_libs_versions_toml
675+
556676
# Cleanup temporary branch
557677
print_step "Cleaning up temporary branch..."
558678
git branch -D "$TEMP_BRANCH" || handle_error "Failed to delete temporary branch"
@@ -562,11 +682,16 @@ if [ "$DRY_RUN" = false ]; then
562682
if ! git diff --quiet --exit-code --cached; then
563683
print_step "Committing changes..."
564684
git add "${SYNC_DIRS[@]}" "${SYNC_FILES[@]}"
685+
# Include any catalog heal additions made by heal_libs_versions_toml.
686+
if [ -f "gradle/libs.versions.toml" ]; then
687+
git add "gradle/libs.versions.toml"
688+
fi
565689
git commit -m "sync: Update directories and files from upstream
566690
567691
This PR syncs the following items with upstream:
568692
- Directories: ${SYNC_DIRS[*]}
569-
- Files: ${SYNC_FILES[*]}" || handle_error "Failed to commit changes"
693+
- Files: ${SYNC_FILES[*]}
694+
- Auto-heal: gradle/libs.versions.toml (pulls missing build-logic aliases from upstream)" || handle_error "Failed to commit changes"
570695
show_progress
571696

572697
if [ "$FORCE" = false ]; then

0 commit comments

Comments
 (0)