Skip to content

Commit a687c90

Browse files
committed
reuse existing functions
1 parent f7257d2 commit a687c90

2 files changed

Lines changed: 136 additions & 101 deletions

File tree

xircuits/library/update_library.py

Lines changed: 135 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,11 @@
88
from typing import List, Optional, Set
99
from importlib_resources import files, as_file
1010

11-
from xircuits.utils.pathing import resolve_working_dir, components_base_dir
12-
from .core_libs import is_core_library
13-
1411
from xircuits.utils.pathing import (
1512
resolve_working_dir,
1613
resolve_library_dir,
1714
normalize_library_slug,
15+
components_base_dir,
1816
)
1917
from xircuits.utils.git_toml_manager import (
2018
read_component_metadata_entry,
@@ -30,6 +28,8 @@
3028
from xircuits.utils.venv_ops import install_specs
3129
from xircuits.handlers.request_remote import get_remote_config
3230

31+
from .core_libs import is_core_library
32+
3333

3434
@dataclass
3535
class SourceSpec:
@@ -70,7 +70,7 @@ def update_library(
7070
dry_run: If True, compute actions and print a unified diff (no files changed).
7171
prune: If True, also archive local-only files/dirs (rename to *.bak).
7272
install_deps: If True (default), update per-library extra and install its requirements.
73-
use_latest: If True, ignore metadata ref and pull latest from default branch.
73+
use_latest: If True, ignore metadata ref and pull latest from default branch.
7474
no_overwrite: If True, skip updating files with local modifications.
7575
7676
Returns:
@@ -105,7 +105,6 @@ def update_library(
105105
# Regular (non-core) library update from git
106106
return _update_from_git(
107107
lib_name=lib_name,
108-
working_dir=working_dir,
109108
repo=repo,
110109
ref=ref,
111110
dry_run=dry_run,
@@ -160,7 +159,7 @@ def _update_from_wheel(
160159

161160
# Sync files
162161
if is_base_file:
163-
report = _sync_single_file(src_path, dst_path, dry_run, timestamp, no_overwrite) # <-- ADD no_overwrite
162+
report = _sync_single_file(src_path, dst_path, dry_run, timestamp, no_overwrite)
164163
else:
165164
report = _sync_with_backups(
166165
source_root=src_path,
@@ -205,7 +204,6 @@ def _update_from_wheel(
205204

206205
def _update_from_git(
207206
lib_name: str,
208-
working_dir: Path,
209207
repo: Optional[str],
210208
ref: Optional[str],
211209
dry_run: bool,
@@ -218,6 +216,10 @@ def _update_from_git(
218216
Update a regular component library from its git repository.
219217
(Original update_library logic)
220218
"""
219+
working_dir = resolve_working_dir()
220+
if working_dir is None:
221+
raise RuntimeError("Xircuits working directory not found. Run 'xircuits init' first.")
222+
221223
dest_dir = resolve_library_dir(lib_name)
222224
if not dest_dir.exists() or not dest_dir.is_dir():
223225
raise FileNotFoundError(
@@ -233,25 +235,22 @@ def _update_from_git(
233235
"Ensure it was installed (so metadata exists) or present in your index.json."
234236
)
235237

236-
print(f"Updating '{lib_name}' from {source_spec.repo_url} @ {source_spec.desired_ref or 'default'}...")
238+
print(
239+
f"Updating {lib_name} from {source_spec.repo_url} "
240+
f"{'(ref='+source_spec.desired_ref+')' if source_spec.desired_ref else '(default branch)'}"
241+
)
237242

238243
temp_repo_dir = Path(tempfile.mkdtemp(prefix=f"update_{lib_name}_"))
239244
try:
240-
clone_from_github_url(
241-
library_url=source_spec.repo_url,
242-
target_dir=str(temp_repo_dir),
243-
ref=source_spec.desired_ref
244-
)
245+
git_clone_shallow(source_spec.repo_url, temp_repo_dir)
246+
if source_spec.desired_ref:
247+
git_checkout_ref(temp_repo_dir, source_spec.desired_ref)
245248

246-
temp_lib_dir = temp_repo_dir / lib_name
247-
if not temp_lib_dir.exists():
248-
raise FileNotFoundError(
249-
f"No '{lib_name}' subdirectory in cloned repository. "
250-
"Ensure the library name matches the folder in the repo."
251-
)
249+
repo_url_final, resolved_ref, is_tag = get_git_metadata(str(temp_repo_dir))
250+
src_dir = _select_library_source_dir(temp_repo_dir, lib_name)
252251

253-
sync_report = _sync_with_backups(
254-
source_root=temp_lib_dir,
252+
report = _sync_with_backups(
253+
source_root=src_dir,
255254
destination_root=dest_dir,
256255
dry_run=dry_run,
257256
prune=prune,
@@ -260,14 +259,12 @@ def _update_from_git(
260259
)
261260

262261
# On DRY-RUN: show and save a unified diff of planned changes.
263-
# This is an *in-memory* comparison; no filesystem writes occur, and we don't
264-
# ever touch '/dev/null'. For adds/deletes we diff against an empty side.
265262
if dry_run:
266263
diff_text = _build_combined_diff(
267-
temp_lib_dir, dest_dir,
268-
added=sync_report.added,
269-
updated=sync_report.updated,
270-
deleted=sync_report.deleted
264+
src_dir, dest_dir,
265+
added=report.added,
266+
updated=report.updated,
267+
deleted=report.deleted
271268
)
272269
if diff_text.strip():
273270
diff_path = dest_dir / f"{lib_name}.update.{timestamp}.dry-run.diff.txt"
@@ -278,21 +275,51 @@ def _update_from_git(
278275

279276
# Update pyproject metadata / deps (skipped during dry-run)
280277
if not dry_run:
281-
if repo:
282-
write_component_metadata_entry(lib_name, repo, source_spec.desired_ref)
278+
try:
279+
record_component_metadata(
280+
library_name=lib_name,
281+
member_path=str(dest_dir),
282+
repo_url=repo_url_final or source_spec.repo_url,
283+
ref=resolved_ref or source_spec.desired_ref or "latest",
284+
is_tag=is_tag,
285+
)
286+
except Exception as e:
287+
print(f"Warning: could not update pyproject metadata: {e}")
288+
289+
# Requirements / extras install
290+
try:
291+
reqs = read_requirements_for_library(dest_dir)
292+
except Exception as e:
293+
reqs = []
294+
print(f"Warning: could not read requirements for {lib_name}: {e}")
295+
296+
# Always refresh the per-library extra + meta extra on update
297+
try:
298+
set_library_extra(lib_name, reqs)
299+
rebuild_meta_extra("xai-components")
300+
except Exception as e:
301+
print(f"Warning: could not update optional-dependencies for {lib_name}: {e}")
283302

284303
if install_deps:
285-
reqs_list = read_requirements_for_library(dest_dir)
286-
if reqs_list:
287-
print(f"Installing requirements for {lib_name}...")
288-
install_per_library_extra(lib_name, reqs_list)
289-
else:
290-
print(f"No requirements.txt found for {lib_name}; skipping dependency install.")
304+
try:
305+
if reqs:
306+
print(f"Installing Python dependencies for {lib_name}...")
307+
install_specs(reqs)
308+
print(f"✓ Dependencies for {lib_name} installed.")
309+
else:
310+
print(f"No requirements.txt entries for {lib_name}; nothing to install.")
311+
except Exception as e:
312+
print(f"Warning: installing dependencies for {lib_name} failed:{e}".rstrip())
313+
314+
try:
315+
regenerate_lock_file()
316+
except Exception as e:
317+
print(f"Warning: could not regenerate lock file: {e}")
291318

292319
summary = (
293320
f"{lib_name} update "
294-
f"(added: {len(sync_report.added)}, updated: {len(sync_report.updated)}, "
295-
f"deleted: {len(sync_report.deleted)}, unchanged: {len(sync_report.unchanged)})"
321+
f"(added: {len(report.added)}, updated: {len(report.updated)}, "
322+
f"deleted: {len(report.deleted)}, unchanged: {len(report.unchanged)})"
296323
)
297324
return summary
298325
finally:
@@ -328,7 +355,13 @@ def _extract_base_py_from_wheel(dest_dir: Path) -> None:
328355
raise RuntimeError(f"Failed to extract base.py from wheel: {e}")
329356

330357

331-
def _sync_single_file(src_file: Path, dst_file: Path, dry_run: bool, timestamp: str, no_overwrite: bool = False) -> SyncReport:
358+
def _sync_single_file(
359+
src_file: Path,
360+
dst_file: Path,
361+
dry_run: bool,
362+
timestamp: str,
363+
no_overwrite: bool = False
364+
) -> SyncReport:
332365
"""
333366
Sync a single file with backup support.
334367
"""
@@ -494,83 +527,85 @@ def _sync_with_backups(
494527
no_overwrite: bool = False,
495528
) -> SyncReport:
496529
"""
497-
Synchronize source_root into destination_root.
498-
499-
- Added files/dirs are copied.
500-
- Modified files: backed up then overwritten (unless no_overwrite=True).
501-
- Local-only files: left alone unless prune=True.
502-
- If no_overwrite=True, skip updating files that differ locally.
503-
"""
504-
report = SyncReport(added=[], updated=[], deleted=[], unchanged=[])
505-
506-
src_files = _gather_files_recursive(source_root)
507-
dst_files = _gather_files_recursive(destination_root)
530+
Perform the filesystem sync (or simulate it on dry_run).
508531
509-
src_rel = {p.relative_to(source_root) for p in src_files}
510-
dst_rel = {p.relative_to(destination_root) for p in dst_files}
532+
Prints concise markers:
533+
+++ path
534+
--- path (backup: <name>) for updated/deleted
535+
--- path (would backup) for updated/deleted (dry-run mode)
536+
⊙ path (local changes preserved) when no_overwrite=True
537+
"""
538+
added: List[str] = []
539+
updated: List[str] = []
540+
deleted: List[str] = []
541+
unchanged: List[str] = []
511542

512-
added_rel = src_rel - dst_rel
513-
common_rel = src_rel & dst_rel
514-
local_only = dst_rel - src_rel
543+
source_files = _walk_files(source_root)
544+
destination_files = _walk_files(destination_root)
515545

516-
# 1) Added files
517-
for rel in sorted(added_rel):
546+
# Add / update
547+
for rel in sorted(source_files, key=str):
518548
src = source_root / rel
519549
dst = destination_root / rel
520-
report.added.append(str(rel))
521-
print(f"+++ {rel}")
522-
if not dry_run:
523-
dst.parent.mkdir(parents=True, exist_ok=True)
524-
shutil.copy2(src, dst)
550+
path_str = rel.as_posix()
525551

526-
# 2) Common files
527-
for rel in sorted(common_rel):
528-
src = source_root / rel
529-
dst = destination_root / rel
530552
if not dst.exists():
531-
report.added.append(str(rel))
532-
print(f"+++ {rel}")
533-
if not dry_run:
534-
dst.parent.mkdir(parents=True, exist_ok=True)
535-
shutil.copy2(src, dst)
536-
elif src.is_file() and dst.is_file():
537-
if _files_equal(src, dst):
538-
report.unchanged.append(str(rel))
539-
else:
540-
# File differs - local modification detected
541-
if no_overwrite:
542-
print(f"⊙ {rel} (local changes preserved)")
543-
report.unchanged.append(str(rel))
544-
elif dry_run:
545-
backup_name = _backup_in_place(dst, timestamp, dry_run)
546-
report.updated.append(str(rel))
547-
print(f"--- {rel} (would backup as: {backup_name})")
548-
print(f"+++ {rel}")
549-
else:
550-
backup_name = _backup_in_place(dst, timestamp, dry_run)
551-
report.updated.append(str(rel))
552-
print(f"--- {rel} (backup: {backup_name})")
553-
print(f"+++ {rel}")
554-
shutil.copy2(src, dst)
553+
print(f"+++ {path_str}")
554+
_copy_file(src, dst, dry_run)
555+
added.append(path_str)
556+
continue
557+
558+
if _files_equal(src, dst):
559+
unchanged.append(path_str)
560+
continue
561+
562+
# File differs - check no_overwrite flag
563+
if no_overwrite:
564+
print(f"⊙ {path_str} (local changes preserved)")
565+
unchanged.append(path_str)
566+
continue
567+
568+
if dry_run:
569+
backup_name = _backup_in_place(dst, timestamp, dry_run)
570+
print(f"--- {path_str} (would backup as: {backup_name})")
571+
print(f"+++ {path_str}")
555572
else:
556-
report.unchanged.append(str(rel))
573+
backup_name = _backup_in_place(dst, timestamp, dry_run)
574+
print(f"--- {path_str} (backup: {backup_name})")
575+
print(f"+++ {path_str}")
576+
_copy_file(src, dst, dry_run)
577+
updated.append(path_str)
557578

558-
# 3) Local-only files (prune if requested)
579+
# Deletions (dest-only) — only when prune=True
559580
if prune:
560-
for rel in sorted(local_only):
581+
source_dirs = _walk_dirs(source_root)
582+
destination_dirs = _walk_dirs(destination_root)
583+
584+
for rel in sorted(destination_files - source_files, key=str):
561585
dst = destination_root / rel
562-
if dst.exists():
586+
path_str = rel.as_posix()
587+
if dry_run:
588+
backup_name = _backup_in_place(dst, timestamp, dry_run)
589+
print(f"--- {path_str} (would backup as: {backup_name})")
590+
else:
563591
backup_name = _backup_in_place(dst, timestamp, dry_run)
564-
report.deleted.append(str(rel))
592+
print(f"--- {path_str} (backup: {backup_name})")
593+
deleted.append(path_str)
594+
595+
# Directories only in destination — deepest first
596+
for rel in sorted(destination_dirs - source_dirs, key=lambda p: len(p.as_posix()), reverse=True):
597+
dst_dir = destination_root / rel
598+
if dst_dir.exists():
599+
path_str = rel.as_posix() + "/"
565600
if dry_run:
566-
print(f"--- {rel} (would prune as: {backup_name})")
601+
backup_name = _backup_in_place(dst_dir, timestamp, dry_run)
602+
print(f"--- {path_str} (would backup as: {backup_name})")
567603
else:
568-
print(f"--- {rel} (pruned: {backup_name})")
569-
else:
570-
for rel in sorted(local_only):
571-
report.unchanged.append(str(rel))
604+
backup_name = _backup_in_place(dst_dir, timestamp, dry_run)
605+
print(f"--- {path_str} (backup: {backup_name})")
606+
deleted.append(path_str)
572607

573-
return report
608+
return SyncReport(added=added, updated=updated, deleted=deleted, unchanged=unchanged)
574609

575610
# ---------- Diff helpers (for dry-run) ----------
576611

xircuits/start_xircuits.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ def cmd_update_library(args, extra_args=[]):
186186
dry_run=args.dry_run,
187187
prune=args.prune,
188188
install_deps=args.install_deps,
189-
no_overwrite=args.no_overwrite, # <-- ADD THIS
189+
no_overwrite=args.no_overwrite,
190190
)
191191
print(message)
192192

0 commit comments

Comments
 (0)