Commit 7ad8c31
0
plików
+0
dodanych
-0
usuniętych
@@ -1015,7 +1015,8 @@ def save_json(path, data):
1015
1015
1016
1016
class PackageInfo:
1017
1017
__slots__ = ("name","version","description","dependencies",
1018
- "size_bytes","sha256","gpg_fp","repo_url","filename","provides","license")
1018
+ "size_bytes","sha256","gpg_fp","repo_url","filename","provides","license",
1019
+ "provides_so","requires_so")
1019
1020
def __init__(self, d, repo=""):
1020
1021
self.name = d.get("name","?")
1021
1022
self.version = d.get("version","0")
@@ -1028,6 +1029,8 @@ class PackageInfo:
1028
1029
self.filename = d.get("filename", f"{self.name}-{self.version}{PKG_EXT}")
1029
1030
self.provides = d.get("provides", []) or []
1030
1031
self.license = d.get("license", []) or []
1032
+ self.provides_so = d.get("provides_so", []) or []
1033
+ self.requires_so = d.get("requires_so", []) or []
1031
1034
1032
1035
# =============================================================================
1033
1036
# REPOZYTORIA (cache, ETag, GPG)
@@ -2099,6 +2102,8 @@ def cmd_install(package_names, as_dep=False, upgrade=False):
2099
2102
"sha256": pkg.sha256, "installed_at": datetime.now().isoformat(),
2100
2103
"repo": "local",
2101
2104
"provides": getattr(pkg, "provides", None) or [],
2105
+ "provides_so": getattr(pkg, "provides_so", None) or [],
2106
+ "requires_so": getattr(pkg, "requires_so", None) or [],
2102
2107
}
2103
2108
world.add(pkg.name)
2104
2109
failed_local, _fl = _install_local_pkg_files(local_files, _ok)
@@ -2169,6 +2174,11 @@ def cmd_install(package_names, as_dep=False, upgrade=False):
2169
2174
print(f" Zainstaluj brakujące pakiety lub dodaj repozytoria.")
2170
2175
return 1
2171
2176
2177
+ so_missing = _verify_so_deps(to_install, repo_pkgs, installed_db)
2178
+ if so_missing > 0:
2179
+ print(" Zainstaluj dostawcę biblioteki lub zaktualizuj repozytorium.")
2180
+ return 1
2181
+
2172
2182
if not to_install:
2173
2183
print(f"✅ {_('all_installed')}"); return 0
2174
2184
@@ -2297,6 +2307,8 @@ def cmd_install(package_names, as_dep=False, upgrade=False):
2297
2307
"sha256": pkg.sha256, "installed_at": datetime.now().isoformat(),
2298
2308
"repo": pkg.repo_url,
2299
2309
"provides": getattr(pkg, "provides", None) or [],
2310
+ "provides_so": getattr(pkg, "provides_so", None) or [],
2311
+ "requires_so": getattr(pkg, "requires_so", None) or [],
2300
2312
}
2301
2313
if not as_dep and name in package_names:
2302
2314
world.add(name)
@@ -2355,6 +2367,9 @@ def cmd_install(package_names, as_dep=False, upgrade=False):
2355
2367
print(f" 💡 Restart wymagany do przeładowania systemu.")
2356
2368
else:
2357
2369
_refresh_dynamic_linker_cache()
2370
+ # Hooki zbiorcze – raz na transakcję (fc-cache itp.), tylko gdy pliki
2371
+ # trafiły do realnego systemu (nie do deploymentu).
2372
+ _process_triggers(all_installed_files)
2358
2373
2359
2374
print(f"\n✅ {_('installed', len(to_install))}")
2360
2375
return 0
@@ -2429,6 +2444,7 @@ def cmd_remove(package_names):
2429
2444
world = load_world()
2430
2445
snapshot = json.loads(json.dumps(installed_db))
2431
2446
removed = []
2447
+ removed_files = []
2432
2448
2433
2449
total = len(package_names)
2434
2450
for i, name in enumerate(package_names, 1):
@@ -2445,10 +2461,11 @@ def cmd_remove(package_names):
2445
2461
# Pre-remove hook (jeśli dostępny w staging)
2446
2462
_run_hook_for_installed(name, "pre-remove")
2447
2463
2448
- count, _ = _safe_remove_files(name, installed_db)
2464
+ count, rm_files = _safe_remove_files(name, installed_db)
2449
2465
del installed_db[name]
2450
2466
world.discard(name)
2451
2467
removed.append(name)
2468
+ removed_files.extend(rm_files)
2452
2469
print(f"✅ ({count} files)")
2453
2470
2454
2471
# Post-remove hook + sprzątanie zapisanych hooków
@@ -2463,6 +2480,7 @@ def cmd_remove(package_names):
2463
2480
2464
2481
if not removed: return 0
2465
2482
print(f"\n✅ Removed {len(removed)}.")
2483
+ _process_triggers(removed_files)
2466
2484
2467
2485
orphans = _find_orphans(installed_db, world)
2468
2486
if orphans:
@@ -2477,6 +2495,76 @@ def _run_hook_for_installed(pkg_name, hook_name):
2477
2495
ver = load_json(INSTALLED_DB).get(pkg_name, {}).get("version", "")
2478
2496
_run_hook(hook_dir, hook_name, PackageInfo({"name": pkg_name, "version": ver}))
2479
2497
2498
+
2499
+# =============================================================================
2500
+# TRIGGERS – hooki zbiorcze (raz na transakcję, nie per pakiet)
2501
+# =============================================================================
2502
+# Wzorem pacman/dpkg: pakiet/administrator deklaruje zainteresowanie ścieżkami,
2503
+# a pasujący trigger uruchamia się DOKŁADNIE RAZ na końcu transakcji
2504
+# (np. fc-cache, glib-compile-schemas, update-desktop-database) zamiast po
2505
+# każdym pakiecie z osobna.
2506
+
2507
+TRIGGERS_DIR = PAG_CONF + "/triggers"
2508
+
2509
+DEFAULT_TRIGGERS = [
2510
+ {"name": "font-cache", "paths": ["/usr/share/fonts/", "/usr/local/share/fonts/"],
2511
+ "run": "fc-cache -fs"},
2512
+ {"name": "glib-schemas", "paths": ["/usr/share/glib-2.0/schemas/"],
2513
+ "run": "glib-compile-schemas /usr/share/glib-2.0/schemas"},
2514
+ {"name": "desktop-database", "paths": ["/usr/share/applications/"],
2515
+ "run": "update-desktop-database -q /usr/share/applications"},
2516
+ {"name": "mime-database", "paths": ["/usr/share/mime/"],
2517
+ "run": "update-mime-database /usr/share/mime"},
2518
+]
2519
+
2520
+def _load_triggers() -> List[dict]:
2521
+ """Ładuje triggery: domyślne (tylko gdy binarka istnieje) + /etc/pag/triggers/*.json."""
2522
+ out = []
2523
+ for t in DEFAULT_TRIGGERS:
2524
+ bin_name = t["run"].split()[0]
2525
+ if shutil.which(bin_name):
2526
+ out.append(dict(t))
2527
+ if os.path.isdir(TRIGGERS_DIR):
2528
+ for fn in sorted(os.listdir(TRIGGERS_DIR)):
2529
+ if not fn.endswith(".json"):
2530
+ continue
2531
+ try:
2532
+ with open(os.path.join(TRIGGERS_DIR, fn)) as f:
2533
+ data = json.load(f)
2534
+ except (OSError, json.JSONDecodeError):
2535
+ continue
2536
+ if isinstance(data, dict):
2537
+ data = [data]
2538
+ for t in data:
2539
+ if isinstance(t, dict) and t.get("name") and t.get("paths") and t.get("run"):
2540
+ out.append(t)
2541
+ return out
2542
+
2543
+def _process_triggers(touched_paths: List[str]):
2544
+ """Uruchamia pasujące triggery RAZ na końcu transakcji (best-effort)."""
2545
+ if not touched_paths:
2546
+ return
2547
+ if os.environ.get("PAG_NO_HOOKS", "") == "1":
2548
+ return
2549
+ import shlex as _shlex
2550
+ matched = []
2551
+ for trig in _load_triggers():
2552
+ if any(path.startswith(p) for p in trig["paths"] for path in touched_paths):
2553
+ matched.append(trig)
2554
+ for trig in matched:
2555
+ run = trig["run"]
2556
+ print(f" ⚡ Trigger: {trig['name']} ({run})")
2557
+ try:
2558
+ r = subprocess.run(_shlex.split(run), capture_output=True, text=True, timeout=120)
2559
+ _audit(f"TRIGGER {trig['name']}: {run} rc={r.returncode}")
2560
+ if r.returncode != 0:
2561
+ print(f" ⚠ rc={r.returncode}: {(r.stderr or r.stdout or '').strip()[:160]}")
2562
+ except subprocess.TimeoutExpired:
2563
+ print(f" ⚠ trigger {trig['name']} przekroczył limit czasu (120 s)")
2564
+ _audit(f"TRIGGER {trig['name']} TIMEOUT")
2565
+ except Exception as e:
2566
+ print(f" ⚠ trigger {trig['name']}: {e}")
2567
+
2480
2568
# =============================================================================
2481
2569
# UPDATE / UPGRADE / LIST / SEARCH / INFO / VERIFY
2482
2570
# =============================================================================
@@ -4169,6 +4257,45 @@ def _verify_dependencies(to_install: list, repo: dict, installed: dict) -> int:
4169
4257
4170
4258
return len(all_missing)
4171
4259
4260
+# Biblioteki bazowe (glibc/gcc runtime) – zawsze dostępne, nie wymagają pakietu
4261
+BASE_SO = {
4262
+ "libc.so.6", "libm.so.6", "libpthread.so.0", "libdl.so.2", "librt.so.1",
4263
+ "libutil.so.1", "libresolv.so.2", "libnsl.so.1", "libcrypt.so.1",
4264
+ "ld-linux.so.2", "ld-linux-x86-64.so.2", "ld-linux-aarch64.so.1",
4265
+ "libgcc_s.so.1", "linux-vdso.so.1",
4266
+}
4267
+
4268
+def _verify_so_deps(to_install: list, repo: dict, installed: dict) -> int:
4269
+ """Sprawdza wymagania ABI (provides_so / requires_so z metadata.json).
4270
+
4271
+ Fail-closed TYLKO gdy metadata jawnie deklaruje requires_so, a żaden pakiet
4272
+ (bazowy, zainstalowany lub instalowany w tej transakcji) nie dostarcza
4273
+ wymaganej wersji biblioteki. Stare pakiety bez tych pól są pomijane.
4274
+ """
4275
+ provided = set(BASE_SO)
4276
+ for n in to_install:
4277
+ p = repo.get(n)
4278
+ if p:
4279
+ provided.update(p.provides_so or [])
4280
+ for n, info in installed.items():
4281
+ provided.update(info.get("provides_so", []) or [])
4282
+
4283
+ missing = []
4284
+ for n in sorted(to_install):
4285
+ p = repo.get(n)
4286
+ if not p:
4287
+ continue
4288
+ for so in (p.requires_so or []):
4289
+ if so not in provided:
4290
+ missing.append((n, so))
4291
+
4292
+ if missing:
4293
+ print(f"\n❌ {_c('red', 'BRAK WYMAGANYCH BIBLIOTEK (ABI so-name)')}:")
4294
+ for n, so in missing:
4295
+ print(f" {n} → wymaga {_c('red', so)} – żaden pakiet nie dostarcza tej wersji")
4296
+ print()
4297
+ return len(missing)
4298
+
4172
4299
def _download_pkg(pkg):
4173
4300
url = f"{pkg.repo_url}/{pkg.filename}"
4174
4301
dest = os.path.join(PAG_CACHE, pkg.filename)