Compare commits

8 Commits
1.3.3 ... main

Author SHA1 Message Date
10cc4f968f build.py: uh slightly better depency stuff 2026-01-03 10:36:19 +01:00
727990282b s/2025/2026/
Signed-off-by: Gabriel Di Martino <gabrys_world13@proton.me>
2026-01-01 10:02:22 +01:00
3d6f16a358 s/2025/2026/
why the hell did i type 2 instead of s in some commits xd

Signed-off-by: Gabriel Di Martino <gabrys_world13@proton.me>
2026-01-01 10:01:23 +01:00
fa56d345bf s/2025/2026/
Signed-off-by: Gabriel Di Martino <gabrys_world13@proton.me>
2026-01-01 09:59:44 +01:00
35d4041b46 2/2025/2026
Signed-off-by: Gabriel Di Martino <gabrys_world13@proton.me>
2026-01-01 09:59:08 +01:00
ae046d9261 s/2025/2026/
Signed-off-by: Gabriel Di Martino <gabrys_world13@proton.me>
2026-01-01 09:58:47 +01:00
10f46ec6ce build.py: slightly fix dep resolving problems 2025-12-17 21:03:23 +01:00
e861f35573 forgot to auto delete asc and binpkgs 2025-12-17 13:43:50 +01:00
5 changed files with 33 additions and 33 deletions

View File

@@ -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.

View File

@@ -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
View File

@@ -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
View File

@@ -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

View File

@@ -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