build.py: slightly fix dep resolving problems

This commit is contained in:
2025-12-17 21:03:23 +01:00
parent e861f35573
commit 10f46ec6ce

View File

@@ -119,16 +119,26 @@ def extract_tar_zst_with_progress(tar_path, dest="/"):
# -------------------------- # --------------------------
# Main build function # Main build function
# -------------------------- # --------------------------
def build_package(recipe_file, repo_dir=None, force_rebuild=False): def build_package(recipe_file, repo_dir=None, force_rebuild=False, _visited=None):
_ensure_symlink("/tmp/fempkg", "/var/tmp/fempkg") _ensure_symlink("/tmp/fempkg", "/var/tmp/fempkg")
_ensure_symlink("/tmp/fempkgbuild", "/var/tmp/fempkgbuild") _ensure_symlink("/tmp/fempkgbuild", "/var/tmp/fempkgbuild")
if _visited is None:
_visited = set() # track packages in current build chain
db = load_db() db = load_db()
recipe = {} recipe = {}
with open(recipe_file, "r") as f: with open(recipe_file, "r") as f:
exec(f.read(), recipe) exec(f.read(), recipe)
name, version = recipe["pkgname"], recipe["pkgver"] name, version = recipe["pkgname"], recipe["pkgver"]
# Prevent infinite recursion from dependency loops
if name in _visited:
print(f"[fempkg] Detected circular dependency on {name}, skipping...")
return
_visited.add(name)
source_type = recipe.get("source_type") source_type = recipe.get("source_type")
deps = recipe.get("deps", []) deps = recipe.get("deps", [])
triggers = recipe.get("triggers", []) triggers = recipe.get("triggers", [])
@@ -136,7 +146,6 @@ def build_package(recipe_file, repo_dir=None, force_rebuild=False):
# --- Check for "source-only" mode --- # --- Check for "source-only" mode ---
source_only = os.environ.get("FEMPKG_SOURCE", "").lower() in ("1", "true", "yes") source_only = os.environ.get("FEMPKG_SOURCE", "").lower() in ("1", "true", "yes")
# nodelete for debug: if set don't delete old manifest files
nodelete_env = os.environ.get("FEMPKG_NODELETE", "").lower() in ("1", "true", "yes") nodelete_env = os.environ.get("FEMPKG_NODELETE", "").lower() in ("1", "true", "yes")
if not force_rebuild and is_installed(name, version, db=db): if not force_rebuild and is_installed(name, version, db=db):
@@ -161,7 +170,7 @@ def build_package(recipe_file, repo_dir=None, force_rebuild=False):
if installed_ver is None or not version_satisfies(installed_ver, dep_latest_ver): if installed_ver is None or not version_satisfies(installed_ver, dep_latest_ver):
print(f"Installing/updating dependency {dep_name} " print(f"Installing/updating dependency {dep_name} "
f"(installed: {installed_ver}, latest: {dep_latest_ver})") f"(installed: {installed_ver}, latest: {dep_latest_ver})")
build_package(dep_recipe, repo_dir) build_package(dep_recipe, repo_dir, _visited=_visited) # <-- pass _visited down
else: else:
print(f"Dependency {dep_name} is up-to-date ({installed_ver}). Skipping.") print(f"Dependency {dep_name} is up-to-date ({installed_ver}). Skipping.")
@@ -189,23 +198,20 @@ def build_package(recipe_file, repo_dir=None, force_rebuild=False):
total=total_size, unit='B', unit_scale=True, desc=f"Downloading {name}-{version}", total=total_size, unit='B', unit_scale=True, desc=f"Downloading {name}-{version}",
) as pbar: ) as pbar:
for chunk in r.iter_content(chunk_size=chunk_size): for chunk in r.iter_content(chunk_size=chunk_size):
if chunk: # filter out keep-alive chunks if chunk:
f.write(chunk) f.write(chunk)
pbar.update(len(chunk)) pbar.update(len(chunk))
# --------------------------- # ---------------------------
# Manifest / deletion workflow # Manifest / deletion workflow
# --------------------------- # ---------------------------
# resolved manifest (from manifests repo) for this package/version
try: try:
resolved_manifest = fetch_manifest(name, pkgver=version) resolved_manifest = fetch_manifest(name, pkgver=version)
except FileNotFoundError: except FileNotFoundError:
# if manifest not present in repo, fallback to existing manifest if present
resolved_manifest = os.path.join(MANIFEST_CACHE_DIR, f"{name}.txt") resolved_manifest = os.path.join(MANIFEST_CACHE_DIR, f"{name}.txt")
if not os.path.exists(resolved_manifest): if not os.path.exists(resolved_manifest):
resolved_manifest = None resolved_manifest = None
# If we have a resolved manifest, snapshot it to local-manifests/<pkg>-<newver>.txt
if resolved_manifest: if resolved_manifest:
try: try:
os.makedirs(LOCAL_MANIFESTS_DIR, exist_ok=True) os.makedirs(LOCAL_MANIFESTS_DIR, exist_ok=True)
@@ -216,7 +222,6 @@ def build_package(recipe_file, repo_dir=None, force_rebuild=False):
else: else:
print(f"[fempkg] No manifest available to snapshot for {name} {version}.") print(f"[fempkg] No manifest available to snapshot for {name} {version}.")
# If package already installed and we are NOT in atomic mode and not nodelete -> delete old manifest paths
old_installed_version = db["installed"].get(name) old_installed_version = db["installed"].get(name)
if old_installed_version and (not atomic_upgrade) and (not nodelete_env): if old_installed_version and (not atomic_upgrade) and (not nodelete_env):
old_versioned_path = os.path.join(VERSIONED_MANIFEST_DIR, f"{name}-{old_installed_version}.txt") old_versioned_path = os.path.join(VERSIONED_MANIFEST_DIR, f"{name}-{old_installed_version}.txt")
@@ -226,16 +231,12 @@ def build_package(recipe_file, repo_dir=None, force_rebuild=False):
for p in old_paths: for p in old_paths:
if not p: if not p:
continue continue
# Avoid accidental relative paths
if not os.path.isabs(p): if not os.path.isabs(p):
print(f"[fempkg] Skipping (not absolute) path from manifest: {p}") print(f"[fempkg] Skipping (not absolute) path from manifest: {p}")
continue continue
if os.path.exists(p): if os.path.exists(p):
print(f"[fempkg] Removing old file: {p}") print(f"[fempkg] Removing old file: {p}")
delete_file_and_prune_dirs(p) delete_file_and_prune_dirs(p)
else:
# not present, skip
pass
else: else:
print(f"[fempkg] No old versioned manifest found at {old_versioned_path}; nothing to remove.") print(f"[fempkg] No old versioned manifest found at {old_versioned_path}; nothing to remove.")
else: else:
@@ -246,10 +247,9 @@ def build_package(recipe_file, repo_dir=None, force_rebuild=False):
elif nodelete_env: elif nodelete_env:
print(f"[fempkg] FEMPKG_NODELETE set - skipping deletion of old files.") print(f"[fempkg] FEMPKG_NODELETE set - skipping deletion of old files.")
# Verify GPG signature first # Verify GPG signature
asc_path = local_path + ".asc" asc_path = local_path + ".asc"
if not os.path.exists(asc_path): if not os.path.exists(asc_path):
# download .asc if missing
binpkg_asc_url = f"https://rocketleaguechatp.duckdns.org/binpkg/{name}-{version}.tar.zst.asc" binpkg_asc_url = f"https://rocketleaguechatp.duckdns.org/binpkg/{name}-{version}.tar.zst.asc"
with requests.get(binpkg_asc_url) as r: with requests.get(binpkg_asc_url) as r:
r.raise_for_status() r.raise_for_status()
@@ -263,17 +263,13 @@ def build_package(recipe_file, repo_dir=None, force_rebuild=False):
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
raise RuntimeError(f"[fempkg] GPG verification failed for {name}-{version}: {e}") raise RuntimeError(f"[fempkg] GPG verification failed for {name}-{version}: {e}")
# Now extract the verified package
print(f"[fempkg] Extracting binary package to / : {local_path}") print(f"[fempkg] Extracting binary package to / : {local_path}")
extract_tar_zst_with_progress(local_path, dest="/") extract_tar_zst_with_progress(local_path, dest="/")
# After successful extraction, register package
register_package(name, version, db=db) register_package(name, version, db=db)
print(f"[fempkg] Installed {name}-{version} from binary package.") print(f"[fempkg] Installed {name}-{version} from binary package.")
os.system(f"rm -rf {local_path} {asc_path}") os.system(f"rm -rf {local_path} {asc_path}")
# Promote the local snapshot (if saved) to versioned manifests and current manifest
if resolved_manifest: if resolved_manifest:
promoted = promote_local_to_versioned(name, version) promoted = promote_local_to_versioned(name, version)
if promoted: if promoted:
@@ -281,7 +277,6 @@ def build_package(recipe_file, repo_dir=None, force_rebuild=False):
else: else:
print(f"[fempkg] Warning: promotion of manifest failed for {name}-{version}") print(f"[fempkg] Warning: promotion of manifest failed for {name}-{version}")
# Remove the old versioned manifest (we only keep the new one)
if old_installed_version: if old_installed_version:
remove_versioned_manifest(name, old_installed_version) remove_versioned_manifest(name, old_installed_version)
print(f"[fempkg] Removed old versioned manifest for {name}-{old_installed_version} (if present).") print(f"[fempkg] Removed old versioned manifest for {name}-{old_installed_version} (if present).")
@@ -293,7 +288,6 @@ def build_package(recipe_file, repo_dir=None, force_rebuild=False):
binpkg_success = False binpkg_success = False
if binpkg_success: if binpkg_success:
# run triggers and return
if triggers: if triggers:
print(f"[fempkg] Running post triggers for {name}...") print(f"[fempkg] Running post triggers for {name}...")
for trig in triggers: for trig in triggers:
@@ -306,7 +300,7 @@ def build_package(recipe_file, repo_dir=None, force_rebuild=False):
print(f"[fempkg] Unknown trigger type: {trig}") print(f"[fempkg] Unknown trigger type: {trig}")
return return
# --- If we get here: source build (or binpkg fallback) --- # --- Source build fallback ---
print(f"[fempkg] Building {name}-{version} from source...") print(f"[fempkg] Building {name}-{version} from source...")
manifest_path = fetch_manifest(name, pkgver=version) manifest_path = fetch_manifest(name, pkgver=version)
with open(manifest_path) as f: with open(manifest_path) as f:
@@ -318,26 +312,20 @@ def build_package(recipe_file, repo_dir=None, force_rebuild=False):
print(f"> {cmd}") print(f"> {cmd}")
subprocess.run(f". /etc/profile && mkdir -p /tmp/fempkg && {cmd}", shell=True, check=True) subprocess.run(f". /etc/profile && mkdir -p /tmp/fempkg && {cmd}", shell=True, check=True)
# After build/install steps (source path), register package & save manifest snapshot/versioned
register_package(name, version, db=db) register_package(name, version, db=db)
try: try:
# snapshot current manifest (from resolved manifest that we used earlier)
resolved_manifest = fetch_manifest(name, pkgver=version) resolved_manifest = fetch_manifest(name, pkgver=version)
if resolved_manifest: if resolved_manifest:
# save snapshot to versioned dir directly (source builds produce final files already)
os.makedirs(VERSIONED_MANIFEST_DIR, exist_ok=True) os.makedirs(VERSIONED_MANIFEST_DIR, exist_ok=True)
versioned_path = os.path.join(VERSIONED_MANIFEST_DIR, f"{name}-{version}.txt") versioned_path = os.path.join(VERSIONED_MANIFEST_DIR, f"{name}-{version}.txt")
shutil.copy(resolved_manifest, versioned_path) shutil.copy(resolved_manifest, versioned_path)
# also update the current manifest
shutil.copy(versioned_path, os.path.join(MANIFEST_CACHE_DIR, f"{name}.txt")) shutil.copy(versioned_path, os.path.join(MANIFEST_CACHE_DIR, f"{name}.txt"))
# remove old versioned manifest if existing
old_installed_version = db["installed"].get(name) old_installed_version = db["installed"].get(name)
if old_installed_version and old_installed_version != version: if old_installed_version and old_installed_version != version:
remove_versioned_manifest(name, old_installed_version) remove_versioned_manifest(name, old_installed_version)
except Exception as e: except Exception as e:
print(f"[fempkg] Warning: failed to save versioned manifest for {name}: {e}") print(f"[fempkg] Warning: failed to save versioned manifest for {name}: {e}")
# Cleanup temp dirs and downloaded tarballs from PKG_DIR if present
for cleanup_path in ["/tmp/fempkg", "/tmp/fempkgbuild"]: for cleanup_path in ["/tmp/fempkg", "/tmp/fempkgbuild"]:
target = os.path.realpath(cleanup_path) target = os.path.realpath(cleanup_path)
if os.path.exists(target): if os.path.exists(target):