Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 10cc4f968f | |||
| 727990282b | |||
| 3d6f16a358 | |||
| fa56d345bf | |||
| 35d4041b46 | |||
| ae046d9261 | |||
| 10f46ec6ce | |||
| e861f35573 |
4
LICENSE
4
LICENSE
@@ -209,7 +209,7 @@ If you develop a new program, and you want it to be of the greatest possible use
|
||||
To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the “copyright” line and a pointer to where the full notice is found.
|
||||
|
||||
fempkg
|
||||
Copyright (C) 2025 Gabriel Di Martino
|
||||
Copyright (C) 2026 Gabriel Di Martino
|
||||
|
||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
|
||||
@@ -221,7 +221,7 @@ Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode:
|
||||
|
||||
fempkg Copyright (C) 2025 FemboyOS
|
||||
fempkg Copyright (C) 2026 Gabriel Di Martino
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details.
|
||||
|
||||
|
||||
56
build.py
56
build.py
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
# fempkg - a simple package manager
|
||||
# Copyright (C) 2025 Gabriel Di Martino
|
||||
# Copyright (C) 2026 Gabriel Di Martino
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
@@ -69,18 +69,29 @@ def fetch_manifest(pkgname, pkgver=None):
|
||||
def rebuild_package(packages, repo_dir=None):
|
||||
if isinstance(packages, str):
|
||||
packages = [packages]
|
||||
|
||||
db = load_db() # <-- load db once
|
||||
|
||||
for pkg in packages:
|
||||
if pkg not in db["installed"]:
|
||||
print(f"[fempkg] Skipping rebuild of {pkg}: not installed.")
|
||||
continue
|
||||
|
||||
print(f"[fempkg] Rebuilding dependency: {pkg}")
|
||||
|
||||
dep_recipe = None
|
||||
if repo_dir:
|
||||
dep_recipe = os.path.join(repo_dir, f"{pkg}.recipe.py")
|
||||
if not os.path.exists(dep_recipe):
|
||||
print(f"Warning: recipe for {pkg} not found in {repo_dir}.")
|
||||
dep_recipe = None
|
||||
|
||||
if not dep_recipe:
|
||||
dep_recipe = fetch_recipe(pkg)
|
||||
|
||||
build_package(dep_recipe, repo_dir, force_rebuild=True)
|
||||
|
||||
|
||||
def extract_tar_zst_with_progress(tar_path, dest="/"):
|
||||
"""
|
||||
Extract a .tar.zst archive with a progress bar.
|
||||
@@ -119,16 +130,26 @@ def extract_tar_zst_with_progress(tar_path, dest="/"):
|
||||
# --------------------------
|
||||
# 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/fempkgbuild", "/var/tmp/fempkgbuild")
|
||||
|
||||
if _visited is None:
|
||||
_visited = set() # track packages in current build chain
|
||||
|
||||
db = load_db()
|
||||
recipe = {}
|
||||
with open(recipe_file, "r") as f:
|
||||
exec(f.read(), recipe)
|
||||
|
||||
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")
|
||||
deps = recipe.get("deps", [])
|
||||
triggers = recipe.get("triggers", [])
|
||||
@@ -136,7 +157,6 @@ def build_package(recipe_file, repo_dir=None, force_rebuild=False):
|
||||
|
||||
# --- Check for "source-only" mode ---
|
||||
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")
|
||||
|
||||
if not force_rebuild and is_installed(name, version, db=db):
|
||||
@@ -161,7 +181,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):
|
||||
print(f"Installing/updating dependency {dep_name} "
|
||||
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:
|
||||
print(f"Dependency {dep_name} is up-to-date ({installed_ver}). Skipping.")
|
||||
|
||||
@@ -189,23 +209,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}",
|
||||
) as pbar:
|
||||
for chunk in r.iter_content(chunk_size=chunk_size):
|
||||
if chunk: # filter out keep-alive chunks
|
||||
if chunk:
|
||||
f.write(chunk)
|
||||
pbar.update(len(chunk))
|
||||
|
||||
# ---------------------------
|
||||
# Manifest / deletion workflow
|
||||
# ---------------------------
|
||||
# resolved manifest (from manifests repo) for this package/version
|
||||
try:
|
||||
resolved_manifest = fetch_manifest(name, pkgver=version)
|
||||
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")
|
||||
if not os.path.exists(resolved_manifest):
|
||||
resolved_manifest = None
|
||||
|
||||
# If we have a resolved manifest, snapshot it to local-manifests/<pkg>-<newver>.txt
|
||||
if resolved_manifest:
|
||||
try:
|
||||
os.makedirs(LOCAL_MANIFESTS_DIR, exist_ok=True)
|
||||
@@ -216,7 +233,6 @@ def build_package(recipe_file, repo_dir=None, force_rebuild=False):
|
||||
else:
|
||||
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)
|
||||
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")
|
||||
@@ -226,16 +242,12 @@ def build_package(recipe_file, repo_dir=None, force_rebuild=False):
|
||||
for p in old_paths:
|
||||
if not p:
|
||||
continue
|
||||
# Avoid accidental relative paths
|
||||
if not os.path.isabs(p):
|
||||
print(f"[fempkg] Skipping (not absolute) path from manifest: {p}")
|
||||
continue
|
||||
if os.path.exists(p):
|
||||
print(f"[fempkg] Removing old file: {p}")
|
||||
delete_file_and_prune_dirs(p)
|
||||
else:
|
||||
# not present, skip
|
||||
pass
|
||||
else:
|
||||
print(f"[fempkg] No old versioned manifest found at {old_versioned_path}; nothing to remove.")
|
||||
else:
|
||||
@@ -246,10 +258,9 @@ def build_package(recipe_file, repo_dir=None, force_rebuild=False):
|
||||
elif nodelete_env:
|
||||
print(f"[fempkg] FEMPKG_NODELETE set - skipping deletion of old files.")
|
||||
|
||||
# Verify GPG signature first
|
||||
# Verify GPG signature
|
||||
asc_path = local_path + ".asc"
|
||||
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"
|
||||
with requests.get(binpkg_asc_url) as r:
|
||||
r.raise_for_status()
|
||||
@@ -263,16 +274,13 @@ def build_package(recipe_file, repo_dir=None, force_rebuild=False):
|
||||
except subprocess.CalledProcessError as 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}")
|
||||
extract_tar_zst_with_progress(local_path, dest="/")
|
||||
|
||||
|
||||
# After successful extraction, register package
|
||||
register_package(name, version, db=db)
|
||||
print(f"[fempkg] Installed {name}-{version} from binary package.")
|
||||
os.system(f"rm -rf {local_path} {asc_path}")
|
||||
|
||||
# Promote the local snapshot (if saved) to versioned manifests and current manifest
|
||||
if resolved_manifest:
|
||||
promoted = promote_local_to_versioned(name, version)
|
||||
if promoted:
|
||||
@@ -280,7 +288,6 @@ def build_package(recipe_file, repo_dir=None, force_rebuild=False):
|
||||
else:
|
||||
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:
|
||||
remove_versioned_manifest(name, old_installed_version)
|
||||
print(f"[fempkg] Removed old versioned manifest for {name}-{old_installed_version} (if present).")
|
||||
@@ -292,7 +299,6 @@ def build_package(recipe_file, repo_dir=None, force_rebuild=False):
|
||||
binpkg_success = False
|
||||
|
||||
if binpkg_success:
|
||||
# run triggers and return
|
||||
if triggers:
|
||||
print(f"[fempkg] Running post triggers for {name}...")
|
||||
for trig in triggers:
|
||||
@@ -305,7 +311,7 @@ def build_package(recipe_file, repo_dir=None, force_rebuild=False):
|
||||
print(f"[fempkg] Unknown trigger type: {trig}")
|
||||
return
|
||||
|
||||
# --- If we get here: source build (or binpkg fallback) ---
|
||||
# --- Source build fallback ---
|
||||
print(f"[fempkg] Building {name}-{version} from source...")
|
||||
manifest_path = fetch_manifest(name, pkgver=version)
|
||||
with open(manifest_path) as f:
|
||||
@@ -317,26 +323,20 @@ def build_package(recipe_file, repo_dir=None, force_rebuild=False):
|
||||
print(f"> {cmd}")
|
||||
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)
|
||||
try:
|
||||
# snapshot current manifest (from resolved manifest that we used earlier)
|
||||
resolved_manifest = fetch_manifest(name, pkgver=version)
|
||||
if resolved_manifest:
|
||||
# save snapshot to versioned dir directly (source builds produce final files already)
|
||||
os.makedirs(VERSIONED_MANIFEST_DIR, exist_ok=True)
|
||||
versioned_path = os.path.join(VERSIONED_MANIFEST_DIR, f"{name}-{version}.txt")
|
||||
shutil.copy(resolved_manifest, versioned_path)
|
||||
# also update the current manifest
|
||||
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)
|
||||
if old_installed_version and old_installed_version != version:
|
||||
remove_versioned_manifest(name, old_installed_version)
|
||||
except Exception as 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"]:
|
||||
target = os.path.realpath(cleanup_path)
|
||||
if os.path.exists(target):
|
||||
|
||||
2
db.py
2
db.py
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
# fempkg - a simple package manager
|
||||
# Copyright (C) 2025 Gabriel Di Martino
|
||||
# Copyright (C) 2026 Gabriel Di Martino
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
|
||||
2
fempkg
2
fempkg
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
# fempkg - a simple package manager
|
||||
# Copyright (C) 2025 Gabriel Di Martino
|
||||
# Copyright (C) 2026 Gabriel Di Martino
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
|
||||
2
utils.py
2
utils.py
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
# fempkg - a simple package manager
|
||||
# Copyright (C) 2025 Gabriel Di Martino
|
||||
# Copyright (C) 2026 Gabriel Di Martino
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
|
||||
Reference in New Issue
Block a user