#!/usr/bin/env python3
# fempkg - a simple package manager
# Copyright (C) 2025 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.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
import sys, os
from db import register_package, load_db, save_db
from build import build_package, fetch_recipe, MANIFEST_CACHE_DIR
from utils import fetch_all
RECIPE_CACHE_DIR = "/var/lib/fempkg/repo"
def list_installed():
db = load_db()
for pkg, ver in db["installed"].items():
print(f"{pkg}-{ver}")
def update_all():
fetch_all()
print("Recipe, manifest and the binpkg index cache updated.")
def upgrade_packages(pkg=None):
db = load_db()
if pkg:
recipe_file = os.path.join(RECIPE_CACHE_DIR, f"{pkg}.recipe.py")
if not os.path.exists(recipe_file):
print(f"Recipe for {pkg} not found. Run `fempkg update` first.")
return
build_package(recipe_file)
else:
for pkgname in db["installed"]:
recipe_file = os.path.join(RECIPE_CACHE_DIR, f"{pkgname}.recipe.py")
if not os.path.exists(recipe_file):
print(f"Recipe for {pkgname} missing, skipping.")
continue
try: build_package(recipe_file)
except Exception as e:
print(f"Failed to upgrade {pkgname}: {e}")
def uninstall_package(pkgname):
manifest_path = os.path.join(MANIFEST_CACHE_DIR, f"{pkgname}.txt")
if not os.path.exists(manifest_path):
print(f"No manifest for {pkgname}, cannot uninstall safely. Run `fempkg update` first.")
return
with open(manifest_path) as f:
files = [line.strip() for line in f if line.strip()]
# Remove files/directories in reverse order (leaves first)
for file in reversed(sorted(files)):
try:
if os.path.islink(file) or os.path.isfile(file):
os.remove(file)
print(f"Removed {file}")
elif os.path.isdir(file):
try:
shutil.rmtree(file)
print(f"Removed directory {file}")
except Exception:
print(f"Directory {file} not empty or protected, skipping")
except Exception as e:
print(f"Failed to remove {file}: {e}")
# Remove manifest
try:
os.remove(manifest_path)
except Exception as e:
print(f"Failed to remove manifest {manifest_path}: {e}")
# Remove from DB
db = load_db()
if pkgname in db["installed"]:
db["installed"].pop(pkgname)
save_db(db)
print(f"{pkgname} uninstalled.")
def help_menu():
print("Usage: fempkg [list|register|install|update|upgrade|uninstall]")
print(" list - list installed packages")
print(" register - manually register package")
print(" install - install from recipe")
print(" update - update recipe+manifest+binpkg cache")
print(" upgrade - update all packages, optionally one")
print(" uninstall - uninstall a package")
def main():
if len(sys.argv) < 2:
help_menu()
return
cmd = sys.argv[1]
if cmd == "list": list_installed()
elif cmd == "register" and len(sys.argv) == 4: register_package(sys.argv[2], sys.argv[3])
elif cmd == "install" and len(sys.argv) >= 3:
for pkgname in sys.argv[2:]:
print(f"Installing {pkgname} …")
recipe_file = fetch_recipe(pkgname)
build_package(recipe_file)
print(f"{pkgname} installed")
elif cmd == "update" and len(sys.argv) == 2: update_all()
elif cmd == "upgrade":
if len(sys.argv) == 3: upgrade_packages(sys.argv[2])
elif len(sys.argv) == 2: upgrade_packages()
else: help_menu()
elif cmd == "uninstall" and len(sys.argv) == 3: uninstall_package(sys.argv[2])
else: help_menu()
if __name__ == "__main__":
main()