pmbootstrap-meow/pmb/config/pmaports.py
Caleb Connolly 3f11fa2500
tests: basic pkgrepo tests, clone pmaports (MR 2252)
Signed-off-by: Caleb Connolly <caleb@postmarketos.org>
2024-06-23 12:38:41 +02:00

228 lines
7.8 KiB
Python

# Copyright 2023 Oliver Smith
# SPDX-License-Identifier: GPL-3.0-or-later
import configparser
from pathlib import Path
from typing import List, Optional
from pmb.core.pkgrepo import pkgrepo_default_path, pkgrepo_paths, pkgrepo_relative_path
from pmb.helpers import logging
import os
import sys
import pmb.config
from pmb.meta import Cache
from pmb.types import PmbArgs
import pmb.helpers.git
import pmb.helpers.pmaports
import pmb.parse.version
def clone():
logging.info("Setting up the native chroot and cloning the package build"
" recipes (pmaports)...")
# Set up the native chroot and clone pmaports
pmb.helpers.git.clone("pmaports")
def check_version_pmaports(real):
# Compare versions
min = pmb.config.pmaports_min_version
if pmb.parse.version.compare(real, min) >= 0:
return
# Outated error
logging.info("NOTE: your pmaports folder has version " + real + ", but" +
" version " + min + " is required.")
raise RuntimeError("Run 'pmbootstrap pull' to update your pmaports.")
def check_version_pmbootstrap(min_ver):
# Compare versions
real = pmb.__version__
if pmb.parse.version.compare(real, min_ver) >= 0:
return
# Show versions
logging.info(f"NOTE: you are using pmbootstrap version {real}, but"
f" version {min_ver} is required.")
# Error for git clone
pmb_src = pmb.config.pmb_src
if os.path.exists(pmb_src / ".git"):
raise RuntimeError("Please update your local pmbootstrap repository."
f" Usually with: 'git -C \"{pmb_src}\" pull'")
# Error for package manager installation
raise RuntimeError("Please update your pmbootstrap version (with your"
" distribution's package manager, or with pip, "
" depending on how you have installed it). If that is"
" not possible, consider cloning the latest version"
" of pmbootstrap from git.")
@Cache()
def read_config_repos():
""" Read the sections starting with "repo:" from pmaports.cfg. """
cfg = configparser.ConfigParser()
cfg.read(f"{pkgrepo_default_path()}/pmaports.cfg")
ret = {}
for section in cfg.keys():
if not section.startswith("repo:"):
continue
repo = section.split("repo:", 1)[1]
ret[repo] = cfg[section]
return ret
@Cache("aports")
def read_config(aports: Optional[Path] = None):
"""Read and verify pmaports.cfg. If aports is not
specified and systemd is enabled, the returned channel
will be the systemd one (e.g. systemd-edge instead of edge)
since we'll use the first pkgrepo which is systemd."""
if aports is None:
aports = pkgrepo_paths()[0]
systemd = aports.name == "systemd"
# extra-repos don't have a pmaports.cfg
# so jump up the main aports dir
if "extra-repos" in aports.parts:
aports = pkgrepo_relative_path(aports)[0]
# Migration message
if not os.path.exists(aports):
logging.error(f"ERROR: pmaports dir not found: {aports}")
logging.error("Did you run 'pmbootstrap init'?")
sys.exit(1)
# Require the config
path_cfg = aports / "pmaports.cfg"
if not os.path.exists(path_cfg):
raise RuntimeError("Invalid pmaports repository, could not find the"
f" config: {path_cfg}")
# Load the config
cfg = configparser.ConfigParser()
cfg.read(path_cfg)
ret = cfg["pmaports"]
# Version checks
check_version_pmaports(ret["version"])
check_version_pmbootstrap(ret["pmbootstrap_min_version"])
# Translate legacy channel names
ret["channel"] = pmb.helpers.pmaports.get_channel_new(ret["channel"])
if systemd:
ret["channel"] = "systemd-" + ret["channel"]
return ret
def all_channels() -> List[str]:
"""Get a list of all channels for all pkgrepos."""
ret = set()
for repo in pkgrepo_paths():
ret.add(read_config(repo)["channel"])
logging.verbose(f"all_chanels: {ret}")
return list(ret)
def read_config_channel():
"""Get the properties of the currently active channel in pmaports.git.
As specified in channels.cfg (https://postmarketos.org/channels.cfg).
:returns: {"description: ...,
"branch_pmaports": ...,
"branch_aports": ...,
"mirrordir_alpine": ...}
"""
aports = pkgrepo_default_path()
channel = read_config(aports)["channel"]
channels_cfg = pmb.helpers.git.parse_channels_cfg(aports)
if channel in channels_cfg["channels"]:
return channels_cfg["channels"][channel]
# Channel not in channels.cfg, try to be helpful
branch = pmb.helpers.git.rev_parse(aports,
extra_args=["--abbrev-ref"])
branches_official = pmb.helpers.git.get_branches_official(aports)
branches_official = ", ".join(branches_official)
remote = pmb.helpers.git.get_upstream_remote(aports)
logging.info("NOTE: fix the error by rebasing or cherry picking relevant"
" commits from this branch onto a branch that is on a"
f" supported channel: {branches_official}")
logging.info("NOTE: as workaround, you may pass --config-channels with a"
" custom channels.cfg. Reference:"
" https://postmarketos.org/channels.cfg")
raise RuntimeError(f"Current branch '{branch}' of pmaports.git is on"
f" channel '{channel}', but this channel was not"
f" found in channels.cfg (of {remote}/master"
" branch). Looks like a very old branch.")
def init():
if not os.path.exists(pkgrepo_default_path()):
clone()
read_config()
def switch_to_channel_branch(channel_new):
"""Checkout the channel's branch in pmaports.git.
:channel_new: channel name (e.g. "edge", "v21.03")
:returns: True if another branch was checked out, False otherwise
"""
# Check current pmaports branch channel
channel_current = read_config()["channel"]
if channel_current == channel_new:
return False
aports = pkgrepo_default_path()
# List current and new branches/channels
channels_cfg = pmb.helpers.git.parse_channels_cfg(aports)
branch_new = channels_cfg["channels"][channel_new]["branch_pmaports"]
branch_current = pmb.helpers.git.rev_parse(aports,
extra_args=["--abbrev-ref"])
logging.info(f"Currently checked out branch '{branch_current}' of"
f" pmaports.git is on channel '{channel_current}'.")
logging.info(f"Switching to branch '{branch_new}' on channel"
f" '{channel_new}'...")
# Make sure we don't have mounts related to the old channel
pmb.chroot.shutdown()
# Attempt to switch branch (git gives a nice error message, mentioning
# which files need to be committed/stashed, so just pass it through)
if pmb.helpers.run.user(["git", "checkout", branch_new],
aports, "interactive", check=False):
raise RuntimeError("Failed to switch branch. Go to your pmaports and"
" fix what git complained about, then try again: "
f"{aports}")
# Verify pmaports.cfg on new branch
read_config()
return True
def install_githooks():
aports = pkgrepo_default_path()
hooks_dir = aports / ".githooks"
if not hooks_dir.exists():
logging.info("No .githooks dir found")
return
for h in os.listdir(hooks_dir):
src = os.path.join(hooks_dir, h)
# Use git default hooks dir so users can ignore our hooks
# if they dislike them by setting "core.hooksPath" git config
dst = aports / ".git/hooks" / h
if pmb.helpers.run.user(["cp", src, dst], check=False):
logging.warning(f"WARNING: Copying git hook failed: {dst}")