mirror of
https://gitlab.postmarketos.org/postmarketOS/pmbootstrap.git
synced 2025-07-12 19:09:56 +03:00
Auto-format codebase with ruff (MR 2325)
See: https://gitlab.com/postmarketOS/pmbootstrap/-/issues/2324 Closes: https://gitlab.com/postmarketOS/pmbootstrap/-/merge_requests/2278
This commit is contained in:
parent
5a8e2c6cad
commit
e421bb2d41
109 changed files with 4044 additions and 2984 deletions
46
docs/conf.py
46
docs/conf.py
|
@ -9,54 +9,58 @@ import datetime
|
|||
from typing import Any, Dict
|
||||
|
||||
|
||||
sys.path.insert(0, os.path.abspath('..')) # Allow modules to be found
|
||||
sys.path.insert(0, os.path.abspath("..")) # Allow modules to be found
|
||||
from pmb import __version__
|
||||
|
||||
on_rtd = os.environ.get('READTHEDOCS', None) == 'True'
|
||||
on_rtd = os.environ.get("READTHEDOCS", None) == "True"
|
||||
|
||||
if not on_rtd: # only import and set the theme if we're building docs locally
|
||||
import sphinx_rtd_theme
|
||||
|
||||
html_theme = 'sphinx_rtd_theme'
|
||||
html_theme = "sphinx_rtd_theme"
|
||||
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
|
||||
|
||||
# -- Project information -----------------------------------------------------
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
|
||||
|
||||
project = 'pmbootstrap'
|
||||
copyright = str(datetime.date.today().year) + ', postmarketOS developers'
|
||||
author = 'postmarketOS developers'
|
||||
project = "pmbootstrap"
|
||||
copyright = str(datetime.date.today().year) + ", postmarketOS developers"
|
||||
author = "postmarketOS developers"
|
||||
|
||||
release = __version__
|
||||
version = '.'.join(release.split('.')[:3])
|
||||
version = ".".join(release.split(".")[:3])
|
||||
|
||||
# -- General configuration ---------------------------------------------------
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
|
||||
|
||||
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.autosummary', 'sphinx.ext.doctest', 'sphinxcontrib.autoprogram']
|
||||
extensions = [
|
||||
"sphinx.ext.autodoc",
|
||||
"sphinx.ext.autosummary",
|
||||
"sphinx.ext.doctest",
|
||||
"sphinxcontrib.autoprogram",
|
||||
]
|
||||
|
||||
templates_path = ['_templates']
|
||||
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
|
||||
templates_path = ["_templates"]
|
||||
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
|
||||
|
||||
# -- Options for HTML output -------------------------------------------------
|
||||
html_theme = 'sphinx_rtd_theme'
|
||||
html_favicon = 'https://wiki.postmarketos.org/favicon.ico'
|
||||
html_theme = "sphinx_rtd_theme"
|
||||
html_favicon = "https://wiki.postmarketos.org/favicon.ico"
|
||||
|
||||
html_theme_options: Dict[str, Any] = {'style_nav_header_background': '008b69',}
|
||||
html_theme_options: Dict[str, Any] = {
|
||||
"style_nav_header_background": "008b69",
|
||||
}
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'pmboostrapdoc'
|
||||
htmlhelp_basename = "pmboostrapdoc"
|
||||
|
||||
html_theme_options = {
|
||||
'display_version': True,
|
||||
'style_external_links': True,
|
||||
}
|
||||
"display_version": True,
|
||||
"style_external_links": True,
|
||||
}
|
||||
|
||||
# -- Options for manual page output ---------------------------------------
|
||||
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
('index', 'pmbootstrap', 'pmbootstrap Documentation',
|
||||
['postmarketOS Developers'], 1)
|
||||
]
|
||||
man_pages = [("index", "pmbootstrap", "pmbootstrap Documentation", ["postmarketOS Developers"], 1)]
|
||||
|
|
|
@ -27,8 +27,7 @@ __version__ = "3.0.0_alpha"
|
|||
version = sys.version_info
|
||||
if version < (3, 9):
|
||||
print("You need at least Python 3.9 to run pmbootstrap")
|
||||
print("(You are running it with Python " + str(version.major) +
|
||||
"." + str(version.minor) + ")")
|
||||
print("(You are running it with Python " + str(version.major) + "." + str(version.minor) + ")")
|
||||
sys.exit()
|
||||
|
||||
|
||||
|
@ -38,8 +37,10 @@ def print_log_hint() -> None:
|
|||
# Hints about the log file (print to stdout only)
|
||||
log_hint = "Run 'pmbootstrap log' for details."
|
||||
if not os.path.exists(log):
|
||||
log_hint += (" Alternatively you can use '--details-to-stdout' to get more"
|
||||
" output, e.g. 'pmbootstrap --details-to-stdout init'.")
|
||||
log_hint += (
|
||||
" Alternatively you can use '--details-to-stdout' to get more"
|
||||
" output, e.g. 'pmbootstrap --details-to-stdout init'."
|
||||
)
|
||||
print()
|
||||
print(log_hint)
|
||||
|
||||
|
@ -67,11 +68,13 @@ def main() -> int:
|
|||
if args.action == "init":
|
||||
return config_init.frontend(args)
|
||||
elif not os.path.exists(args.config):
|
||||
raise RuntimeError("Please specify a config file, or run"
|
||||
" 'pmbootstrap init' to generate one.")
|
||||
raise RuntimeError(
|
||||
"Please specify a config file, or run" " 'pmbootstrap init' to generate one."
|
||||
)
|
||||
elif not os.path.exists(context.config.work):
|
||||
raise RuntimeError("Work path not found, please run 'pmbootstrap"
|
||||
" init' to create it.")
|
||||
raise RuntimeError(
|
||||
"Work path not found, please run 'pmbootstrap" " init' to create it."
|
||||
)
|
||||
|
||||
# Migrate work folder if necessary
|
||||
if args.action not in ["shutdown", "zap", "log"]:
|
||||
|
@ -85,8 +88,9 @@ def main() -> int:
|
|||
|
||||
# Still active notice
|
||||
if mount.ismount(Chroot.native() / "dev"):
|
||||
logging.info("NOTE: chroot is still active (use 'pmbootstrap"
|
||||
" shutdown' as necessary)")
|
||||
logging.info(
|
||||
"NOTE: chroot is still active (use 'pmbootstrap" " shutdown' as necessary)"
|
||||
)
|
||||
logging.info("DONE!")
|
||||
|
||||
except KeyboardInterrupt:
|
||||
|
@ -106,6 +110,7 @@ def main() -> int:
|
|||
# Dump log to stdout when args (and therefore logging) init failed
|
||||
if "args" not in locals():
|
||||
import logging as pylogging
|
||||
|
||||
pylogging.getLogger().setLevel(logging.DEBUG)
|
||||
|
||||
logging.info("ERROR: " + str(e))
|
||||
|
@ -114,13 +119,11 @@ def main() -> int:
|
|||
|
||||
print_log_hint()
|
||||
print()
|
||||
print("Before you report this error, ensure that pmbootstrap is "
|
||||
"up to date.")
|
||||
print("Find the latest version here:"
|
||||
" https://gitlab.com/postmarketOS/pmbootstrap/-/tags")
|
||||
print("Before you report this error, ensure that pmbootstrap is " "up to date.")
|
||||
print("Find the latest version here:" " https://gitlab.com/postmarketOS/pmbootstrap/-/tags")
|
||||
print(f"Your version: {__version__}")
|
||||
return 1
|
||||
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
|
|
|
@ -19,11 +19,11 @@ import pmb.helpers.cli
|
|||
def get_cross_package_arches(pkgname):
|
||||
"""
|
||||
Get the arches for which we want to build cross packages.
|
||||
|
||||
|
||||
:param pkgname: package name, e.g. "gcc-aarch64", "gcc-x86_64"
|
||||
|
||||
|
||||
:returns: string of architecture(s) (space separated)
|
||||
|
||||
|
||||
"""
|
||||
if pkgname.endswith("-x86_64"):
|
||||
return "aarch64"
|
||||
|
@ -39,26 +39,29 @@ def properties(pkgname):
|
|||
Example: "musl-armhf" => ("musl", "cross", {"confirm_overwrite": False})
|
||||
|
||||
:param pkgname: package name
|
||||
|
||||
|
||||
:returns: (prefix, folder, options)
|
||||
|
||||
|
||||
"""
|
||||
for folder, options in pmb.config.aportgen.items():
|
||||
for prefix in options["prefixes"]:
|
||||
if pkgname.startswith(prefix):
|
||||
return (prefix, folder, options)
|
||||
logging.info("NOTE: aportgen is for generating postmarketOS specific"
|
||||
" aports, such as the cross-compiler related packages"
|
||||
" or the linux kernel fork packages.")
|
||||
logging.info("NOTE: If you wanted to package new software in general, try"
|
||||
" 'pmbootstrap newapkbuild' to generate a template.")
|
||||
logging.info(
|
||||
"NOTE: aportgen is for generating postmarketOS specific"
|
||||
" aports, such as the cross-compiler related packages"
|
||||
" or the linux kernel fork packages."
|
||||
)
|
||||
logging.info(
|
||||
"NOTE: If you wanted to package new software in general, try"
|
||||
" 'pmbootstrap newapkbuild' to generate a template."
|
||||
)
|
||||
raise ValueError("No generator available for " + pkgname + "!")
|
||||
|
||||
|
||||
def generate(pkgname: str, fork_alpine: bool):
|
||||
if fork_alpine:
|
||||
prefix, folder, options = (pkgname, "temp",
|
||||
{"confirm_overwrite": True})
|
||||
prefix, folder, options = (pkgname, "temp", {"confirm_overwrite": True})
|
||||
else:
|
||||
prefix, folder, options = properties(pkgname)
|
||||
config = get_context().config
|
||||
|
@ -66,8 +69,7 @@ def generate(pkgname: str, fork_alpine: bool):
|
|||
|
||||
# Confirm overwrite
|
||||
if options["confirm_overwrite"] and os.path.exists(path_target):
|
||||
logging.warning("WARNING: Target folder already exists: "
|
||||
f"{path_target}")
|
||||
logging.warning("WARNING: Target folder already exists: " f"{path_target}")
|
||||
if not pmb.helpers.cli.confirm("Continue and overwrite?"):
|
||||
raise RuntimeError("Aborted.")
|
||||
|
||||
|
@ -77,10 +79,10 @@ def generate(pkgname: str, fork_alpine: bool):
|
|||
pmb.helpers.run.user(["rm", "-r", aportgen])
|
||||
if fork_alpine:
|
||||
upstream = pmb.aportgen.core.get_upstream_aport(pkgname)
|
||||
pmb.helpers.run.user(["cp", "-r", upstream,
|
||||
aportgen])
|
||||
pmb.aportgen.core.rewrite(pkgname, replace_simple={
|
||||
"# Contributor:*": None, "# Maintainer:*": None})
|
||||
pmb.helpers.run.user(["cp", "-r", upstream, aportgen])
|
||||
pmb.aportgen.core.rewrite(
|
||||
pkgname, replace_simple={"# Contributor:*": None, "# Maintainer:*": None}
|
||||
)
|
||||
else:
|
||||
# Run pmb.aportgen.PREFIX.generate()
|
||||
# FIXME: this is really bad and hacky let's not do this please
|
||||
|
@ -89,7 +91,6 @@ def generate(pkgname: str, fork_alpine: bool):
|
|||
# Move to the aports folder
|
||||
if os.path.exists(path_target):
|
||||
pmb.helpers.run.user(["rm", "-r", path_target])
|
||||
pmb.helpers.run.user(
|
||||
["mv", aportgen, path_target])
|
||||
pmb.helpers.run.user(["mv", aportgen, path_target])
|
||||
|
||||
logging.info(f"*** pmaport generated: {path_target}")
|
||||
|
|
|
@ -25,8 +25,7 @@ def generate(pkgname: str):
|
|||
tempdir = Path("/tmp/aportgen")
|
||||
aportgen = context.config.work / "aportgen"
|
||||
pmb.chroot.root(["rm", "-rf", tempdir])
|
||||
pmb.helpers.run.user(["mkdir", "-p", aportgen,
|
||||
Chroot.native() / tempdir])
|
||||
pmb.helpers.run.user(["mkdir", "-p", aportgen, Chroot.native() / tempdir])
|
||||
|
||||
# Write the APKBUILD
|
||||
channel_cfg = pmb.config.pmaports.read_config_channel()
|
||||
|
|
|
@ -49,9 +49,16 @@ def format_function(name, body, remove_indent=4):
|
|||
return name + "() {\n" + ret + "}\n"
|
||||
|
||||
|
||||
def rewrite(pkgname, path_original="", fields={}, replace_pkgname=None,
|
||||
replace_functions={}, replace_simple={}, below_header="",
|
||||
remove_indent=4):
|
||||
def rewrite(
|
||||
pkgname,
|
||||
path_original="",
|
||||
fields={},
|
||||
replace_pkgname=None,
|
||||
replace_functions={},
|
||||
replace_simple={},
|
||||
below_header="",
|
||||
remove_indent=4,
|
||||
):
|
||||
"""
|
||||
Append a header to $WORK/aportgen/APKBUILD, delete maintainer/contributor
|
||||
lines (so they won't be bugged with issues regarding our generated aports),
|
||||
|
@ -76,7 +83,7 @@ def rewrite(pkgname, path_original="", fields={}, replace_pkgname=None,
|
|||
# Header
|
||||
if path_original:
|
||||
lines_new = [
|
||||
"# Automatically generated aport, do not edit!\n",
|
||||
"# Automatically generated aport, do not edit!\n",
|
||||
f"# Generator: pmbootstrap aportgen {pkgname}\n",
|
||||
f"# Based on: {path_original}\n",
|
||||
"\n",
|
||||
|
@ -99,8 +106,7 @@ def rewrite(pkgname, path_original="", fields={}, replace_pkgname=None,
|
|||
skip_in_func = False
|
||||
for line in handle.readlines():
|
||||
# Skip maintainer/contributor
|
||||
if line.startswith("# Maintainer") or line.startswith(
|
||||
"# Contributor"):
|
||||
if line.startswith("# Maintainer") or line.startswith("# Contributor"):
|
||||
continue
|
||||
|
||||
# Replace functions
|
||||
|
@ -113,8 +119,7 @@ def rewrite(pkgname, path_original="", fields={}, replace_pkgname=None,
|
|||
if line.startswith(func + "() {"):
|
||||
skip_in_func = True
|
||||
if body:
|
||||
lines_new += format_function(
|
||||
func, body, remove_indent=remove_indent)
|
||||
lines_new += format_function(func, body, remove_indent=remove_indent)
|
||||
break
|
||||
if skip_in_func:
|
||||
continue
|
||||
|
@ -171,30 +176,28 @@ def get_upstream_aport(pkgname: str, arch=None):
|
|||
args = pmb.helpers.args.please_i_really_need_args()
|
||||
|
||||
if getattr(args, "fork_alpine_retain_branch", False):
|
||||
logging.info("Not changing aports branch as --fork-alpine-retain-branch was "
|
||||
"used.")
|
||||
logging.info("Not changing aports branch as --fork-alpine-retain-branch was " "used.")
|
||||
else:
|
||||
# Checkout branch
|
||||
channel_cfg = pmb.config.pmaports.read_config_channel()
|
||||
branch = channel_cfg["branch_aports"]
|
||||
logging.info(f"Checkout aports.git branch: {branch}")
|
||||
if pmb.helpers.run.user(["git", "checkout", branch],
|
||||
aports_upstream_path, check=False):
|
||||
if pmb.helpers.run.user(["git", "checkout", branch], aports_upstream_path, check=False):
|
||||
logging.info("NOTE: run 'pmbootstrap pull' and try again")
|
||||
logging.info("NOTE: if it still fails, your aports.git was cloned with"
|
||||
" an older version of pmbootstrap, as shallow clone."
|
||||
" Unshallow it, or remove it and let pmbootstrap clone it"
|
||||
f" again: {aports_upstream_path}")
|
||||
logging.info(
|
||||
"NOTE: if it still fails, your aports.git was cloned with"
|
||||
" an older version of pmbootstrap, as shallow clone."
|
||||
" Unshallow it, or remove it and let pmbootstrap clone it"
|
||||
f" again: {aports_upstream_path}"
|
||||
)
|
||||
raise RuntimeError("Branch checkout failed.")
|
||||
|
||||
# Search package
|
||||
paths = list(aports_upstream_path.glob(f"*/{pkgname}"))
|
||||
if len(paths) > 1:
|
||||
raise RuntimeError("Package " + pkgname + " found in multiple"
|
||||
" aports subfolders.")
|
||||
raise RuntimeError("Package " + pkgname + " found in multiple" " aports subfolders.")
|
||||
elif len(paths) == 0:
|
||||
raise RuntimeError("Package " + pkgname + " not found in alpine"
|
||||
" aports repository.")
|
||||
raise RuntimeError("Package " + pkgname + " not found in alpine" " aports repository.")
|
||||
aport_path = paths[0]
|
||||
|
||||
# Parse APKBUILD
|
||||
|
@ -213,17 +216,23 @@ def get_upstream_aport(pkgname: str, arch=None):
|
|||
|
||||
# APKBUILD > binary: this is fine
|
||||
if compare == 1:
|
||||
logging.info(f"NOTE: {pkgname} {arch} binary package has a lower"
|
||||
f" version {package['version']} than the APKBUILD"
|
||||
f" {apkbuild_version}")
|
||||
logging.info(
|
||||
f"NOTE: {pkgname} {arch} binary package has a lower"
|
||||
f" version {package['version']} than the APKBUILD"
|
||||
f" {apkbuild_version}"
|
||||
)
|
||||
return aport_path
|
||||
|
||||
# APKBUILD < binary: aports.git is outdated
|
||||
if compare == -1:
|
||||
logging.warning("WARNING: Package '" + pkgname + "' has a lower version in"
|
||||
" local checkout of Alpine's aports (" + apkbuild_version +
|
||||
") compared to Alpine's binary package (" +
|
||||
package["version"] + ")!")
|
||||
logging.warning(
|
||||
"WARNING: Package '" + pkgname + "' has a lower version in"
|
||||
" local checkout of Alpine's aports ("
|
||||
+ apkbuild_version
|
||||
+ ") compared to Alpine's binary package ("
|
||||
+ package["version"]
|
||||
+ ")!"
|
||||
)
|
||||
logging.info("NOTE: You can update your local checkout with: 'pmbootstrap pull'")
|
||||
|
||||
return aport_path
|
||||
|
|
|
@ -17,14 +17,17 @@ def ask_for_architecture():
|
|||
if "armhf" in architectures:
|
||||
architectures.remove("armhf")
|
||||
while True:
|
||||
ret = pmb.helpers.cli.ask("Device architecture", architectures,
|
||||
"aarch64", complete=architectures)
|
||||
ret = pmb.helpers.cli.ask(
|
||||
"Device architecture", architectures, "aarch64", complete=architectures
|
||||
)
|
||||
if ret in architectures:
|
||||
return Arch.from_str(ret)
|
||||
logging.fatal("ERROR: Invalid architecture specified. If you want to"
|
||||
" add a new architecture, edit"
|
||||
" build_device_architectures in"
|
||||
" pmb/config/__init__.py.")
|
||||
logging.fatal(
|
||||
"ERROR: Invalid architecture specified. If you want to"
|
||||
" add a new architecture, edit"
|
||||
" build_device_architectures in"
|
||||
" pmb/config/__init__.py."
|
||||
)
|
||||
|
||||
|
||||
def ask_for_manufacturer():
|
||||
|
@ -37,8 +40,7 @@ def ask_for_name(manufacturer):
|
|||
ret = pmb.helpers.cli.ask("Name", None, None, False)
|
||||
|
||||
# Always add the manufacturer
|
||||
if not ret.startswith(manufacturer) and \
|
||||
not ret.startswith("Google"):
|
||||
if not ret.startswith(manufacturer) and not ret.startswith("Google"):
|
||||
ret = manufacturer + " " + ret
|
||||
return ret
|
||||
|
||||
|
@ -46,8 +48,7 @@ def ask_for_name(manufacturer):
|
|||
def ask_for_year():
|
||||
# Regex from https://stackoverflow.com/a/12240826
|
||||
logging.info("In what year was the device released (e.g. 2012)?")
|
||||
return pmb.helpers.cli.ask("Year", None, None, False,
|
||||
validation_regex=r'^[1-9]\d{3,}$')
|
||||
return pmb.helpers.cli.ask("Year", None, None, False, validation_regex=r"^[1-9]\d{3,}$")
|
||||
|
||||
|
||||
def ask_for_chassis():
|
||||
|
@ -55,58 +56,60 @@ def ask_for_chassis():
|
|||
|
||||
logging.info("What type of device is it?")
|
||||
logging.info("Valid types are: " + ", ".join(types))
|
||||
return pmb.helpers.cli.ask("Chassis", None, None, True,
|
||||
validation_regex='|'.join(types),
|
||||
complete=types)
|
||||
return pmb.helpers.cli.ask(
|
||||
"Chassis", None, None, True, validation_regex="|".join(types), complete=types
|
||||
)
|
||||
|
||||
|
||||
def ask_for_keyboard() -> bool:
|
||||
return pmb.helpers.cli.confirm("Does the device have a hardware"
|
||||
" keyboard?")
|
||||
return pmb.helpers.cli.confirm("Does the device have a hardware" " keyboard?")
|
||||
|
||||
|
||||
def ask_for_external_storage() -> bool:
|
||||
return pmb.helpers.cli.confirm("Does the device have a sdcard or"
|
||||
" other external storage medium?")
|
||||
return pmb.helpers.cli.confirm(
|
||||
"Does the device have a sdcard or" " other external storage medium?"
|
||||
)
|
||||
|
||||
|
||||
def ask_for_flash_method():
|
||||
while True:
|
||||
logging.info("Which flash method does the device support?")
|
||||
method = pmb.helpers.cli.ask("Flash method",
|
||||
pmb.config.flash_methods,
|
||||
"none",
|
||||
complete=pmb.config.flash_methods)
|
||||
method = pmb.helpers.cli.ask(
|
||||
"Flash method", pmb.config.flash_methods, "none", complete=pmb.config.flash_methods
|
||||
)
|
||||
|
||||
if method in pmb.config.flash_methods:
|
||||
if method == "heimdall":
|
||||
heimdall_types = ["isorec", "bootimg"]
|
||||
while True:
|
||||
logging.info("Does the device use the \"isolated"
|
||||
" recovery\" or boot.img?")
|
||||
logging.info("<https://wiki.postmarketos.org/wiki"
|
||||
"/Deviceinfo_flash_methods#Isorec_or_bootimg"
|
||||
".3F>")
|
||||
heimdall_type = pmb.helpers.cli.ask("Type",
|
||||
heimdall_types,
|
||||
heimdall_types[0])
|
||||
logging.info('Does the device use the "isolated' ' recovery" or boot.img?')
|
||||
logging.info(
|
||||
"<https://wiki.postmarketos.org/wiki"
|
||||
"/Deviceinfo_flash_methods#Isorec_or_bootimg"
|
||||
".3F>"
|
||||
)
|
||||
heimdall_type = pmb.helpers.cli.ask("Type", heimdall_types, heimdall_types[0])
|
||||
if heimdall_type in heimdall_types:
|
||||
method += "-" + heimdall_type
|
||||
break
|
||||
logging.fatal("ERROR: Invalid type specified.")
|
||||
return method
|
||||
|
||||
logging.fatal("ERROR: Invalid flash method specified. If you want to"
|
||||
" add a new flash method, edit flash_methods in"
|
||||
" pmb/config/__init__.py.")
|
||||
logging.fatal(
|
||||
"ERROR: Invalid flash method specified. If you want to"
|
||||
" add a new flash method, edit flash_methods in"
|
||||
" pmb/config/__init__.py."
|
||||
)
|
||||
|
||||
|
||||
def ask_for_bootimg():
|
||||
logging.info("You can analyze a known working boot.img file to"
|
||||
" automatically fill out the flasher information for your"
|
||||
" deviceinfo file. Either specify the path to an image or"
|
||||
" press return to skip this step (you can do it later with"
|
||||
" 'pmbootstrap bootimg_analyze').")
|
||||
logging.info(
|
||||
"You can analyze a known working boot.img file to"
|
||||
" automatically fill out the flasher information for your"
|
||||
" deviceinfo file. Either specify the path to an image or"
|
||||
" press return to skip this step (you can do it later with"
|
||||
" 'pmbootstrap bootimg_analyze')."
|
||||
)
|
||||
|
||||
while True:
|
||||
response = pmb.helpers.cli.ask("Path", None, "", False)
|
||||
|
@ -121,17 +124,19 @@ def ask_for_bootimg():
|
|||
|
||||
def generate_deviceinfo_fastboot_content(bootimg=None):
|
||||
if bootimg is None:
|
||||
bootimg = {"cmdline": "",
|
||||
"qcdt": "false",
|
||||
"dtb_second": "false",
|
||||
"base": "",
|
||||
"kernel_offset": "",
|
||||
"ramdisk_offset": "",
|
||||
"second_offset": "",
|
||||
"tags_offset": "",
|
||||
"pagesize": "2048",
|
||||
"mtk_label_kernel": "",
|
||||
"mtk_label_ramdisk": ""}
|
||||
bootimg = {
|
||||
"cmdline": "",
|
||||
"qcdt": "false",
|
||||
"dtb_second": "false",
|
||||
"base": "",
|
||||
"kernel_offset": "",
|
||||
"ramdisk_offset": "",
|
||||
"second_offset": "",
|
||||
"tags_offset": "",
|
||||
"pagesize": "2048",
|
||||
"mtk_label_kernel": "",
|
||||
"mtk_label_ramdisk": "",
|
||||
}
|
||||
|
||||
content = f"""\
|
||||
deviceinfo_kernel_cmdline="{bootimg["cmdline"]}"
|
||||
|
@ -178,9 +183,18 @@ def generate_deviceinfo_fastboot_content(bootimg=None):
|
|||
return content
|
||||
|
||||
|
||||
def generate_deviceinfo(pkgname: str, name: str, manufacturer: str, year: str, arch: Arch,
|
||||
chassis: str, has_keyboard: bool, has_external_storage: bool,
|
||||
flash_method: str, bootimg=None):
|
||||
def generate_deviceinfo(
|
||||
pkgname: str,
|
||||
name: str,
|
||||
manufacturer: str,
|
||||
year: str,
|
||||
arch: Arch,
|
||||
chassis: str,
|
||||
has_keyboard: bool,
|
||||
has_external_storage: bool,
|
||||
flash_method: str,
|
||||
bootimg=None,
|
||||
):
|
||||
codename = "-".join(pkgname.split("-")[1:])
|
||||
external_storage = "true" if has_external_storage else "false"
|
||||
# Note: New variables must be added to pmb/config/__init__.py as well
|
||||
|
@ -270,8 +284,7 @@ def generate_modules_initfs() -> None:
|
|||
|
||||
def generate_apkbuild(pkgname: str, name: str, arch: Arch, flash_method: str):
|
||||
# Dependencies
|
||||
depends = ["postmarketos-base",
|
||||
"linux-" + "-".join(pkgname.split("-")[1:])]
|
||||
depends = ["postmarketos-base", "linux-" + "-".join(pkgname.split("-")[1:])]
|
||||
if flash_method in ["fastboot", "heimdall-bootimg"]:
|
||||
depends.append("mkbootimg")
|
||||
if flash_method == "0xffff":
|
||||
|
@ -332,8 +345,17 @@ def generate(pkgname: str):
|
|||
if flash_method in ["fastboot", "heimdall-bootimg"]:
|
||||
bootimg = ask_for_bootimg()
|
||||
|
||||
generate_deviceinfo(pkgname, name, manufacturer, year, arch,
|
||||
chassis, has_keyboard, has_external_storage,
|
||||
flash_method, bootimg)
|
||||
generate_deviceinfo(
|
||||
pkgname,
|
||||
name,
|
||||
manufacturer,
|
||||
year,
|
||||
arch,
|
||||
chassis,
|
||||
has_keyboard,
|
||||
has_external_storage,
|
||||
flash_method,
|
||||
bootimg,
|
||||
)
|
||||
generate_modules_initfs()
|
||||
generate_apkbuild(pkgname, name, arch, flash_method)
|
||||
|
|
|
@ -22,8 +22,7 @@ def generate(pkgname: str):
|
|||
upstream = pkgrepo_default_path() / "main/gcc6"
|
||||
based_on = "main/gcc6 (from postmarketOS)"
|
||||
else:
|
||||
raise ValueError(f"Invalid prefix '{prefix}', expected gcc, gcc4 or"
|
||||
" gcc6.")
|
||||
raise ValueError(f"Invalid prefix '{prefix}', expected gcc, gcc4 or" " gcc6.")
|
||||
pmb.helpers.run.user(["cp", "-r", upstream, context.config.work / "aportgen"])
|
||||
|
||||
# Rewrite APKBUILD
|
||||
|
@ -33,15 +32,13 @@ def generate(pkgname: str):
|
|||
"arch": pmb.aportgen.get_cross_package_arches(pkgname),
|
||||
"depends": f"binutils-{arch} mpc1",
|
||||
"makedepends_build": "gcc g++ bison flex texinfo gawk zip"
|
||||
" gmp-dev mpfr-dev mpc1-dev zlib-dev",
|
||||
" gmp-dev mpfr-dev mpc1-dev zlib-dev",
|
||||
"makedepends_host": "linux-headers gmp-dev mpfr-dev mpc1-dev isl-dev"
|
||||
f" zlib-dev musl-dev-{arch} binutils-{arch}",
|
||||
f" zlib-dev musl-dev-{arch} binutils-{arch}",
|
||||
"subpackages": "",
|
||||
|
||||
# gcc6: options is already there, so we need to replace it and not only
|
||||
# set it below the header like done below.
|
||||
"options": "!strip",
|
||||
|
||||
"LIBGOMP": "false",
|
||||
"LIBGCC": "false",
|
||||
"LIBATOMIC": "false",
|
||||
|
@ -50,10 +47,12 @@ def generate(pkgname: str):
|
|||
|
||||
# Latest gcc only, not gcc4 and gcc6
|
||||
if prefix == "gcc":
|
||||
fields["subpackages"] = f"g++-{arch}:gpp" \
|
||||
f" libstdc++-dev-{arch}:libcxx_dev"
|
||||
fields["subpackages"] = f"g++-{arch}:gpp" f" libstdc++-dev-{arch}:libcxx_dev"
|
||||
|
||||
below_header = "CTARGET_ARCH=" + arch + """
|
||||
below_header = (
|
||||
"CTARGET_ARCH="
|
||||
+ arch
|
||||
+ """
|
||||
CTARGET="$(arch_to_hostspec ${CTARGET_ARCH})"
|
||||
LANG_D=false
|
||||
LANG_OBJC=false
|
||||
|
@ -74,22 +73,21 @@ def generate(pkgname: str):
|
|||
|
||||
_cross_configure="--disable-bootstrap --with-sysroot=/usr/$CTARGET"
|
||||
"""
|
||||
)
|
||||
|
||||
replace_simple = {
|
||||
# Do not package libstdc++, do not add "g++-$ARCH" here (already
|
||||
# did that explicitly in the subpackages variable above, so
|
||||
# pmbootstrap picks it up properly).
|
||||
'*subpackages="$subpackages libstdc++:libcxx:*': None,
|
||||
|
||||
# We set the cross_configure variable at the beginning, so it does not
|
||||
# use CBUILDROOT as sysroot. In the original APKBUILD this is a local
|
||||
# variable, but we make it a global one.
|
||||
'*_cross_configure=*': None,
|
||||
|
||||
"*_cross_configure=*": None,
|
||||
# Do not build foreign arch libgcc, we use the one from Alpine (#2168)
|
||||
'_libgcc=true*': '_libgcc=false',
|
||||
"_libgcc=true*": "_libgcc=false",
|
||||
}
|
||||
|
||||
pmb.aportgen.core.rewrite(pkgname, based_on, fields,
|
||||
replace_simple=replace_simple,
|
||||
below_header=below_header)
|
||||
pmb.aportgen.core.rewrite(
|
||||
pkgname, based_on, fields, replace_simple=replace_simple, below_header=below_header
|
||||
)
|
||||
|
|
|
@ -26,8 +26,7 @@ def generate(pkgname):
|
|||
tempdir = Path("/tmp/aportgen")
|
||||
aportgen = context.config.work / "aportgen"
|
||||
pmb.chroot.root(["rm", "-rf", tempdir])
|
||||
pmb.helpers.run.user(["mkdir", "-p", aportgen,
|
||||
Chroot.native() / tempdir])
|
||||
pmb.helpers.run.user(["mkdir", "-p", aportgen, Chroot.native() / tempdir])
|
||||
|
||||
# Write the APKBUILD
|
||||
channel_cfg = pmb.config.pmaports.read_config_channel()
|
||||
|
|
|
@ -12,8 +12,16 @@ def generate_apkbuild(pkgname: str, deviceinfo: Deviceinfo, patches: List[str]):
|
|||
device = "-".join(pkgname.split("-")[1:])
|
||||
carch = deviceinfo.arch.kernel()
|
||||
|
||||
makedepends = ["bash", "bc", "bison", "devicepkg-dev", "findutils", "flex",
|
||||
"openssl-dev", "perl"]
|
||||
makedepends = [
|
||||
"bash",
|
||||
"bc",
|
||||
"bison",
|
||||
"devicepkg-dev",
|
||||
"findutils",
|
||||
"flex",
|
||||
"openssl-dev",
|
||||
"perl",
|
||||
]
|
||||
|
||||
build = """
|
||||
unset LDFLAGS
|
||||
|
@ -34,8 +42,7 @@ def generate_apkbuild(pkgname: str, deviceinfo: Deviceinfo, patches: List[str]):
|
|||
build += """\n
|
||||
# Master DTB (deviceinfo_bootimg_qcdt)"""
|
||||
vendors = ["spreadtrum", "exynos", "other"]
|
||||
soc_vendor = pmb.helpers.cli.ask("SoC vendor", vendors,
|
||||
vendors[-1], complete=vendors)
|
||||
soc_vendor = pmb.helpers.cli.ask("SoC vendor", vendors, vendors[-1], complete=vendors)
|
||||
if soc_vendor == "spreadtrum":
|
||||
makedepends.append("dtbtool-sprd")
|
||||
build += """
|
||||
|
@ -125,8 +132,8 @@ def generate(pkgname: str):
|
|||
"kernel-use-the-gnu89-standard-explicitly.patch",
|
||||
]
|
||||
for patch in patches:
|
||||
pmb.helpers.run.user(["ln", "-s",
|
||||
"../../.shared-patches/linux/" + patch,
|
||||
(work / "aportgen" / patch)])
|
||||
pmb.helpers.run.user(
|
||||
["ln", "-s", "../../.shared-patches/linux/" + patch, (work / "aportgen" / patch)]
|
||||
)
|
||||
|
||||
generate_apkbuild(pkgname, deviceinfo, patches)
|
||||
|
|
|
@ -25,8 +25,7 @@ def generate(pkgname):
|
|||
tempdir = Path("/tmp/aportgen")
|
||||
aportgen = work / "aportgen"
|
||||
pmb.chroot.root(["rm", "-rf", tempdir])
|
||||
pmb.helpers.run.user(["mkdir", "-p", aportgen,
|
||||
Chroot.native() / tempdir])
|
||||
pmb.helpers.run.user(["mkdir", "-p", aportgen, Chroot.native() / tempdir])
|
||||
|
||||
# Write the APKBUILD
|
||||
channel_cfg = pmb.config.pmaports.read_config_channel()
|
||||
|
|
|
@ -4,8 +4,13 @@ from pmb.build.init import init, init_abuild_minimal, init_compiler
|
|||
from pmb.build.envkernel import package_kernel
|
||||
from pmb.build.kconfig import menuconfig
|
||||
from pmb.build.newapkbuild import newapkbuild
|
||||
from pmb.build.other import copy_to_buildpath, get_status, \
|
||||
index_repo
|
||||
from pmb.build.other import copy_to_buildpath, get_status, index_repo
|
||||
from .backend import mount_pmaports
|
||||
from pmb.build._package import BootstrapStage, packages, \
|
||||
output_path, BuildQueueItem, get_apkbuild, get_depends
|
||||
from pmb.build._package import (
|
||||
BootstrapStage,
|
||||
packages,
|
||||
output_path,
|
||||
BuildQueueItem,
|
||||
get_apkbuild,
|
||||
get_depends,
|
||||
)
|
||||
|
|
|
@ -47,18 +47,21 @@ def check_build_for_arch(pkgname: str, arch: Arch):
|
|||
if binary:
|
||||
pmaport = pmb.helpers.pmaports.get(pkgname)
|
||||
pmaport_version = pmaport["pkgver"] + "-r" + pmaport["pkgrel"]
|
||||
logging.debug(pkgname + ": found pmaport (" + pmaport_version + ") and"
|
||||
" binary package (" + binary["version"] + ", from"
|
||||
" postmarketOS or Alpine), but pmaport can't be built"
|
||||
f" for {arch} -> using binary package")
|
||||
logging.debug(
|
||||
pkgname + ": found pmaport (" + pmaport_version + ") and"
|
||||
" binary package (" + binary["version"] + ", from"
|
||||
" postmarketOS or Alpine), but pmaport can't be built"
|
||||
f" for {arch} -> using binary package"
|
||||
)
|
||||
return False
|
||||
|
||||
# No binary package exists and can't build it
|
||||
logging.info("NOTE: You can edit the 'arch=' line inside the APKBUILD")
|
||||
if context.command == "build":
|
||||
logging.info("NOTE: Alternatively, use --arch to build for another"
|
||||
" architecture ('pmbootstrap build --arch=armhf " +
|
||||
pkgname + "')")
|
||||
logging.info(
|
||||
"NOTE: Alternatively, use --arch to build for another"
|
||||
" architecture ('pmbootstrap build --arch=armhf " + pkgname + "')"
|
||||
)
|
||||
raise RuntimeError(f"Can't build '{pkgname}' for architecture {arch}")
|
||||
|
||||
|
||||
|
@ -80,11 +83,9 @@ def get_depends(context: Context, apkbuild):
|
|||
ret = sorted(set(ret))
|
||||
|
||||
# Don't recurse forever when a package depends on itself (#948)
|
||||
for pkgname in ([apkbuild["pkgname"]] +
|
||||
list(apkbuild["subpackages"].keys())):
|
||||
for pkgname in [apkbuild["pkgname"]] + list(apkbuild["subpackages"].keys()):
|
||||
if pkgname in ret:
|
||||
logging.verbose(apkbuild["pkgname"] + ": ignoring dependency on"
|
||||
" itself: " + pkgname)
|
||||
logging.verbose(apkbuild["pkgname"] + ": ignoring dependency on" " itself: " + pkgname)
|
||||
ret.remove(pkgname)
|
||||
|
||||
# FIXME: is this needed? is this sensible?
|
||||
|
@ -123,7 +124,7 @@ def output_path(arch: Arch, pkgname: str, pkgver: str, pkgrel: str) -> Path:
|
|||
def finish(apkbuild, channel, arch, output: Path, chroot: Chroot, strict=False):
|
||||
"""Various finishing tasks that need to be done after a build."""
|
||||
# Verify output file
|
||||
out_dir = (get_context().config.work / "packages" / channel)
|
||||
out_dir = get_context().config.work / "packages" / channel
|
||||
if not (out_dir / output).exists():
|
||||
raise RuntimeError(f"Package not found after build: {(out_dir / output)}")
|
||||
|
||||
|
@ -135,16 +136,22 @@ def finish(apkbuild, channel, arch, output: Path, chroot: Chroot, strict=False):
|
|||
# Uninstall build dependencies (strict mode)
|
||||
if strict or "pmb:strict" in apkbuild["options"]:
|
||||
logging.info(f"({chroot}) uninstall build dependencies")
|
||||
pmb.chroot.user(["abuild", "undeps"], chroot, Path("/home/pmos/build"),
|
||||
env={"SUDO_APK": "abuild-apk --no-progress"})
|
||||
pmb.chroot.user(
|
||||
["abuild", "undeps"],
|
||||
chroot,
|
||||
Path("/home/pmos/build"),
|
||||
env={"SUDO_APK": "abuild-apk --no-progress"},
|
||||
)
|
||||
# If the build depends contain postmarketos-keys or postmarketos-base,
|
||||
# abuild will have removed the postmarketOS repository key (pma#1230)
|
||||
pmb.chroot.init_keys()
|
||||
|
||||
logging.info(f"@YELLOW@=>@END@ @BLUE@{channel}/{apkbuild['pkgname']}@END@: Done!")
|
||||
|
||||
|
||||
_package_cache: Dict[str, List[str]] = {}
|
||||
|
||||
|
||||
def is_cached_or_cache(arch: Arch, pkgname: str) -> bool:
|
||||
"""Check if a package is in the built packages cache, if not
|
||||
then mark it as built. We must mark as built before building
|
||||
|
@ -182,27 +189,34 @@ def get_apkbuild(pkgname):
|
|||
|
||||
|
||||
class BuildQueueItem(TypedDict):
|
||||
name: str
|
||||
arch: Arch # Arch to build for
|
||||
aports: str
|
||||
apkbuild: Dict[str, Any]
|
||||
output_path: Path
|
||||
channel: str
|
||||
depends: List[str]
|
||||
cross: str
|
||||
chroot: Chroot
|
||||
name: str
|
||||
arch: Arch # Arch to build for
|
||||
aports: str
|
||||
apkbuild: Dict[str, Any]
|
||||
output_path: Path
|
||||
channel: str
|
||||
depends: List[str]
|
||||
cross: str
|
||||
chroot: Chroot
|
||||
|
||||
|
||||
# arch is set if we should build for a specific arch
|
||||
def process_package(context: Context, queue_build: Callable, pkgname: str,
|
||||
arch: Optional[Arch], fallback_arch: Arch, force: bool) -> List[str]:
|
||||
def process_package(
|
||||
context: Context,
|
||||
queue_build: Callable,
|
||||
pkgname: str,
|
||||
arch: Optional[Arch],
|
||||
fallback_arch: Arch,
|
||||
force: bool,
|
||||
) -> List[str]:
|
||||
# Only build when APKBUILD exists
|
||||
base_aports, base_apkbuild = get_apkbuild(pkgname)
|
||||
if not base_apkbuild:
|
||||
if pmb.parse.apkindex.providers(pkgname, fallback_arch, False):
|
||||
return []
|
||||
raise RuntimeError(f"{pkgname}: Could not find aport, and"
|
||||
" could not find this package in any APKINDEX!")
|
||||
raise RuntimeError(
|
||||
f"{pkgname}: Could not find aport, and" " could not find this package in any APKINDEX!"
|
||||
)
|
||||
|
||||
if arch is None:
|
||||
arch = pmb.build.autodetect.arch(base_apkbuild)
|
||||
|
@ -252,29 +266,36 @@ def process_package(context: Context, queue_build: Callable, pkgname: str,
|
|||
cross = pmb.build.autodetect.crosscompile(apkbuild, arch)
|
||||
_dep_arch = Arch.native() if cross == "native" else arch
|
||||
if not pmb.parse.apkindex.package(dep, _dep_arch, False):
|
||||
raise RuntimeError("Missing binary package for dependency '" +
|
||||
dep + "' of '" + parent + "', but"
|
||||
" pmbootstrap won't build any depends since"
|
||||
" it was started with --no-depends.")
|
||||
raise RuntimeError(
|
||||
"Missing binary package for dependency '" + dep + "' of '" + parent + "', but"
|
||||
" pmbootstrap won't build any depends since"
|
||||
" it was started with --no-depends."
|
||||
)
|
||||
|
||||
bstatus = pmb.build.get_status(arch, apkbuild)
|
||||
if bstatus.necessary():
|
||||
if context.no_depends:
|
||||
raise RuntimeError(f"Binary package for dependency '{dep}'"
|
||||
f" of '{parent}' is outdated, but"
|
||||
f" pmbootstrap won't build any depends"
|
||||
f" since it was started with --no-depends.")
|
||||
raise RuntimeError(
|
||||
f"Binary package for dependency '{dep}'"
|
||||
f" of '{parent}' is outdated, but"
|
||||
f" pmbootstrap won't build any depends"
|
||||
f" since it was started with --no-depends."
|
||||
)
|
||||
|
||||
deps = get_depends(context, apkbuild)
|
||||
# Preserve the old behaviour where we don't build second order dependencies by default
|
||||
# unless they are NEW packages, in which case we
|
||||
# unless they are NEW packages, in which case we
|
||||
if base_build_status.necessary() or bstatus == BuildStatus.NEW:
|
||||
logging.debug(f"BUILDQUEUE: queue {dep} (dependency of {parent}) for build, reason: {bstatus}")
|
||||
logging.debug(
|
||||
f"BUILDQUEUE: queue {dep} (dependency of {parent}) for build, reason: {bstatus}"
|
||||
)
|
||||
queue_build(aports, apkbuild, deps, cross)
|
||||
else:
|
||||
logging.info(f"@YELLOW@SKIP:@END@ NOT building {arch}/{dep}: it is a"
|
||||
f" dependency of {pkgname} which isn't marked for build."
|
||||
f" Call with --force or consider building {dep} manually")
|
||||
logging.info(
|
||||
f"@YELLOW@SKIP:@END@ NOT building {arch}/{dep}: it is a"
|
||||
f" dependency of {pkgname} which isn't marked for build."
|
||||
f" Call with --force or consider building {dep} manually"
|
||||
)
|
||||
|
||||
logging.verbose(f"{arch}/{dep}: Inserting {len(deps)} dependencies")
|
||||
depends = deps + depends
|
||||
|
@ -283,8 +304,16 @@ def process_package(context: Context, queue_build: Callable, pkgname: str,
|
|||
return depends
|
||||
|
||||
|
||||
def packages(context: Context, pkgnames: List[str], arch: Optional[Arch]=None, force=False, strict=False,
|
||||
src=None, bootstrap_stage=BootstrapStage.NONE, log_callback: Optional[Callable]=None) -> List[str]:
|
||||
def packages(
|
||||
context: Context,
|
||||
pkgnames: List[str],
|
||||
arch: Optional[Arch] = None,
|
||||
force=False,
|
||||
strict=False,
|
||||
src=None,
|
||||
bootstrap_stage=BootstrapStage.NONE,
|
||||
log_callback: Optional[Callable] = None,
|
||||
) -> List[str]:
|
||||
"""
|
||||
Build a package and its dependencies with Alpine Linux' abuild.
|
||||
|
||||
|
@ -307,7 +336,9 @@ def packages(context: Context, pkgnames: List[str], arch: Optional[Arch]=None, f
|
|||
|
||||
# Add a package to the build queue, fetch it's dependency, and
|
||||
# add record build helpers to installed (e.g. sccache)
|
||||
def queue_build(aports: Path, apkbuild: Dict[str, Any], depends: List[str], cross: Optional[str] = None) -> List[str]:
|
||||
def queue_build(
|
||||
aports: Path, apkbuild: Dict[str, Any], depends: List[str], cross: Optional[str] = None
|
||||
) -> List[str]:
|
||||
# Skip if already queued
|
||||
name = apkbuild["pkgname"]
|
||||
if any(item["name"] == name for item in build_queue):
|
||||
|
@ -316,18 +347,21 @@ def packages(context: Context, pkgnames: List[str], arch: Optional[Arch]=None, f
|
|||
pkg_arch = pmb.build.autodetect.arch(apkbuild) if arch is None else arch
|
||||
chroot = pmb.build.autodetect.chroot(apkbuild, pkg_arch)
|
||||
cross = cross or pmb.build.autodetect.crosscompile(apkbuild, pkg_arch)
|
||||
build_queue.append({
|
||||
"name": name,
|
||||
"arch": pkg_arch,
|
||||
"aports": aports.name, # the pmaports source repo (e.g. "systemd")
|
||||
"apkbuild": apkbuild,
|
||||
"output_path": output_path(pkg_arch, apkbuild["pkgname"],
|
||||
apkbuild["pkgver"], apkbuild["pkgrel"]),
|
||||
"channel": pmb.config.pmaports.read_config(aports)["channel"],
|
||||
"depends": depends,
|
||||
"chroot": chroot,
|
||||
"cross": cross
|
||||
})
|
||||
build_queue.append(
|
||||
{
|
||||
"name": name,
|
||||
"arch": pkg_arch,
|
||||
"aports": aports.name, # the pmaports source repo (e.g. "systemd")
|
||||
"apkbuild": apkbuild,
|
||||
"output_path": output_path(
|
||||
pkg_arch, apkbuild["pkgname"], apkbuild["pkgver"], apkbuild["pkgrel"]
|
||||
),
|
||||
"channel": pmb.config.pmaports.read_config(aports)["channel"],
|
||||
"depends": depends,
|
||||
"chroot": chroot,
|
||||
"cross": cross,
|
||||
}
|
||||
)
|
||||
|
||||
# If we just queued a package that was request to be built explicitly then
|
||||
# record it, since we return which packages we actually built
|
||||
|
@ -357,7 +391,9 @@ def packages(context: Context, pkgnames: List[str], arch: Optional[Arch]=None, f
|
|||
# dependencies that need building as well as the package itself
|
||||
all_dependencies: List[str] = []
|
||||
for pkgname in pkgnames:
|
||||
all_dependencies += process_package(context, queue_build, pkgname, arch, fallback_arch, force)
|
||||
all_dependencies += process_package(
|
||||
context, queue_build, pkgname, arch, fallback_arch, force
|
||||
)
|
||||
|
||||
if not len(build_queue):
|
||||
return []
|
||||
|
@ -380,7 +416,9 @@ def packages(context: Context, pkgnames: List[str], arch: Optional[Arch]=None, f
|
|||
channel = pkg["channel"]
|
||||
output = pkg["output_path"]
|
||||
if not log_callback:
|
||||
logging.info(f"@YELLOW@=>@END@ @BLUE@{channel}/{pkg['name']}@END@: Installing dependencies")
|
||||
logging.info(
|
||||
f"@YELLOW@=>@END@ @BLUE@{channel}/{pkg['name']}@END@: Installing dependencies"
|
||||
)
|
||||
else:
|
||||
log_callback(pkg)
|
||||
|
||||
|
@ -408,8 +446,18 @@ def packages(context: Context, pkgnames: List[str], arch: Optional[Arch]=None, f
|
|||
# Build and finish up
|
||||
logging.info(f"@YELLOW@=>@END@ @BLUE@{channel}/{pkg['name']}@END@: Building package")
|
||||
try:
|
||||
run_abuild(context, pkg["apkbuild"], channel, pkg_arch, strict, force, cross,
|
||||
chroot, src, bootstrap_stage)
|
||||
run_abuild(
|
||||
context,
|
||||
pkg["apkbuild"],
|
||||
channel,
|
||||
pkg_arch,
|
||||
strict,
|
||||
force,
|
||||
cross,
|
||||
chroot,
|
||||
src,
|
||||
bootstrap_stage,
|
||||
)
|
||||
except RuntimeError:
|
||||
raise BuildFailedError(f"Couldn't build {output}!")
|
||||
finish(pkg["apkbuild"], channel, pkg_arch, output, chroot, strict)
|
||||
|
|
|
@ -42,7 +42,7 @@ def arch(package: Union[str, Dict[str, Any]]):
|
|||
"""
|
||||
Find a good default in case the user did not specify for which architecture
|
||||
a package should be built.
|
||||
|
||||
|
||||
:param package: The name of the package or parsed APKBUILD
|
||||
|
||||
:returns: arch string like "x86_64" or "armhf". Preferred order, depending
|
||||
|
@ -95,7 +95,7 @@ def chroot(apkbuild: Dict[str, str], arch: Arch) -> Chroot:
|
|||
|
||||
def crosscompile(apkbuild, arch: Arch):
|
||||
"""
|
||||
:returns: None, "native", "crossdirect"
|
||||
:returns: None, "native", "crossdirect"
|
||||
"""
|
||||
if not get_context().cross:
|
||||
return None
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
import enum
|
||||
from pathlib import Path
|
||||
from typing import Dict
|
||||
|
@ -17,11 +16,12 @@ class BootstrapStage(enum.IntEnum):
|
|||
Pass a BOOTSTRAP= environment variable with the given value to abuild. See
|
||||
bootstrap_1 etc. at https://postmarketos.org/pmaports.cfg for details.
|
||||
"""
|
||||
|
||||
NONE = 0
|
||||
# We don't need explicit representations of the other numbers.
|
||||
|
||||
|
||||
def override_source(apkbuild, pkgver, src, chroot: Chroot=Chroot.native()):
|
||||
def override_source(apkbuild, pkgver, src, chroot: Chroot = Chroot.native()):
|
||||
"""Mount local source inside chroot and append new functions (prepare() etc.)
|
||||
to the APKBUILD to make it use the local source.
|
||||
"""
|
||||
|
@ -45,11 +45,16 @@ def override_source(apkbuild, pkgver, src, chroot: Chroot=Chroot.native()):
|
|||
pkgname = apkbuild["pkgname"]
|
||||
|
||||
# Appended content
|
||||
append = """
|
||||
append = (
|
||||
"""
|
||||
# ** Overrides below appended by pmbootstrap for --src **
|
||||
|
||||
pkgver=\"""" + pkgver + """\"
|
||||
pkgdesc=\"""" + pkgdesc + """\"
|
||||
pkgver=\""""
|
||||
+ pkgver
|
||||
+ """\"
|
||||
pkgdesc=\""""
|
||||
+ pkgdesc
|
||||
+ """\"
|
||||
_pmb_src_copy="/tmp/pmbootstrap-local-source-copy"
|
||||
|
||||
# Empty $source avoids patching in prepare()
|
||||
|
@ -59,17 +64,25 @@ def override_source(apkbuild, pkgver, src, chroot: Chroot=Chroot.native()):
|
|||
|
||||
fetch() {
|
||||
# Update source copy
|
||||
msg "Copying source from host system: """ + src + """\"
|
||||
local exclude_from=\"""" + mount_path + """/.gitignore\"
|
||||
msg "Copying source from host system: """
|
||||
+ src
|
||||
+ """\"
|
||||
local exclude_from=\""""
|
||||
+ mount_path
|
||||
+ """/.gitignore\"
|
||||
local rsync_args=""
|
||||
if [ -f "$exclude_from" ]; then
|
||||
rsync_args="--exclude-from=\"$exclude_from\""
|
||||
fi
|
||||
if ! [ \"""" + pkgname + """\" = "$(cat /tmp/src-pkgname)" ]; then
|
||||
if ! [ \""""
|
||||
+ pkgname
|
||||
+ """\" = "$(cat /tmp/src-pkgname)" ]; then
|
||||
rsync_args="--delete $rsync_args"
|
||||
fi
|
||||
rsync -a --exclude=".git/" $rsync_args --ignore-errors --force \\
|
||||
\"""" + mount_path + """\" "$_pmb_src_copy" || true
|
||||
\""""
|
||||
+ mount_path
|
||||
+ """\" "$_pmb_src_copy" || true
|
||||
|
||||
# Link local source files (e.g. kernel config)
|
||||
mkdir "$srcdir"
|
||||
|
@ -78,13 +91,16 @@ def override_source(apkbuild, pkgver, src, chroot: Chroot=Chroot.native()):
|
|||
is_remote "$s" || ln -sf "$startdir/$s" "$srcdir/"
|
||||
done
|
||||
|
||||
echo \"""" + pkgname + """\" > /tmp/src-pkgname
|
||||
echo \""""
|
||||
+ pkgname
|
||||
+ """\" > /tmp/src-pkgname
|
||||
}
|
||||
|
||||
unpack() {
|
||||
ln -sv "$_pmb_src_copy" "$builddir"
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
# Write and log append file
|
||||
with open(append_path_outside, "w", encoding="utf-8") as handle:
|
||||
|
@ -94,15 +110,12 @@ def override_source(apkbuild, pkgver, src, chroot: Chroot=Chroot.native()):
|
|||
|
||||
# Append it to the APKBUILD
|
||||
apkbuild_path = "/home/pmos/build/APKBUILD"
|
||||
shell_cmd = ("cat " + apkbuild_path + " " + append_path + " > " +
|
||||
append_path + "_")
|
||||
shell_cmd = "cat " + apkbuild_path + " " + append_path + " > " + append_path + "_"
|
||||
pmb.chroot.user(["sh", "-c", shell_cmd], chroot)
|
||||
pmb.chroot.user(["mv", append_path + "_", apkbuild_path], chroot)
|
||||
|
||||
|
||||
|
||||
|
||||
def mount_pmaports(chroot: Chroot=Chroot.native()) -> Dict[str, Path]:
|
||||
def mount_pmaports(chroot: Chroot = Chroot.native()) -> Dict[str, Path]:
|
||||
"""
|
||||
Mount pmaports.git in chroot.
|
||||
|
||||
|
@ -115,13 +128,12 @@ def mount_pmaports(chroot: Chroot=Chroot.native()) -> Dict[str, Path]:
|
|||
outside_destination = chroot / destination
|
||||
pmb.helpers.mount.bind(repo, outside_destination, umount=True)
|
||||
dest_paths[repo.name] = destination
|
||||
|
||||
|
||||
return dest_paths
|
||||
|
||||
|
||||
|
||||
def link_to_git_dir(chroot: Chroot):
|
||||
""" Make ``/home/pmos/build/.git`` point to the .git dir from pmaports.git, with a
|
||||
"""Make ``/home/pmos/build/.git`` point to the .git dir from pmaports.git, with a
|
||||
symlink so abuild does not fail (#1841).
|
||||
|
||||
abuild expects the current working directory to be a subdirectory of a
|
||||
|
@ -143,9 +155,7 @@ def link_to_git_dir(chroot: Chroot):
|
|||
|
||||
# Create .git symlink
|
||||
pmb.chroot.user(["mkdir", "-p", "/home/pmos/build"], chroot)
|
||||
pmb.chroot.user(["ln", "-sf", dest_paths["pmaports"] / ".git",
|
||||
"/home/pmos/build/.git"], chroot)
|
||||
|
||||
pmb.chroot.user(["ln", "-sf", dest_paths["pmaports"] / ".git", "/home/pmos/build/.git"], chroot)
|
||||
|
||||
|
||||
def handle_csum_failure(apkbuild, chroot: Chroot):
|
||||
|
@ -155,18 +165,32 @@ def handle_csum_failure(apkbuild, chroot: Chroot):
|
|||
|
||||
reason = csum_fail_path.open().read().strip()
|
||||
if reason == "local":
|
||||
logging.info("WARNING: Some checksums didn't match, run"
|
||||
f" 'pmbootstrap checksum {apkbuild['pkgname']}' to fix them.")
|
||||
logging.info(
|
||||
"WARNING: Some checksums didn't match, run"
|
||||
f" 'pmbootstrap checksum {apkbuild['pkgname']}' to fix them."
|
||||
)
|
||||
else:
|
||||
logging.error(f"ERROR: Remote checksum mismatch for {apkbuild['pkgname']}")
|
||||
logging.error("NOTE: If you just modified this package:")
|
||||
logging.error(f" * run 'pmbootstrap checksum {apkbuild['pkgname']}' to update the checksums.")
|
||||
logging.error("If you didn't modify it, try building again to re-download the sources.")
|
||||
logging.error(
|
||||
f" * run 'pmbootstrap checksum {apkbuild['pkgname']}' to update the checksums."
|
||||
)
|
||||
logging.error("If you didn't modify it, try building again to re-download the sources.")
|
||||
raise RuntimeError(f"Remote checksum mismatch for {apkbuild['pkgname']}")
|
||||
|
||||
|
||||
def run_abuild(context: Context, apkbuild, channel, arch: Arch, strict=False, force=False, cross=None,
|
||||
suffix: Chroot=Chroot.native(), src=None, bootstrap_stage=BootstrapStage.NONE):
|
||||
def run_abuild(
|
||||
context: Context,
|
||||
apkbuild,
|
||||
channel,
|
||||
arch: Arch,
|
||||
strict=False,
|
||||
force=False,
|
||||
cross=None,
|
||||
suffix: Chroot = Chroot.native(),
|
||||
src=None,
|
||||
bootstrap_stage=BootstrapStage.NONE,
|
||||
):
|
||||
"""
|
||||
Set up all environment variables and construct the abuild command (all
|
||||
depending on the cross-compiler method and target architecture), copy
|
||||
|
@ -182,30 +206,40 @@ def run_abuild(context: Context, apkbuild, channel, arch: Arch, strict=False, fo
|
|||
"""
|
||||
# Sanity check
|
||||
if cross == "native" and "!tracedeps" not in apkbuild["options"]:
|
||||
logging.info("WARNING: Option !tracedeps is not set, but we're"
|
||||
" cross-compiling in the native chroot. This will"
|
||||
" probably fail!")
|
||||
logging.info(
|
||||
"WARNING: Option !tracedeps is not set, but we're"
|
||||
" cross-compiling in the native chroot. This will"
|
||||
" probably fail!"
|
||||
)
|
||||
pkgdir = context.config.work / "packages" / channel
|
||||
if not pkgdir.exists():
|
||||
pmb.helpers.run.root(["mkdir", "-p", pkgdir])
|
||||
pmb.helpers.run.root(["chown", "-R", f"{pmb.config.chroot_uid_user}:{pmb.config.chroot_uid_user}",
|
||||
pkgdir.parent])
|
||||
pmb.helpers.run.root(
|
||||
[
|
||||
"chown",
|
||||
"-R",
|
||||
f"{pmb.config.chroot_uid_user}:{pmb.config.chroot_uid_user}",
|
||||
pkgdir.parent,
|
||||
]
|
||||
)
|
||||
|
||||
pmb.chroot.rootm([["mkdir", "-p", "/home/pmos/packages"],
|
||||
["rm", "-f", "/home/pmos/packages/pmos"],
|
||||
["ln", "-sf", f"/mnt/pmbootstrap/packages/{channel}",
|
||||
"/home/pmos/packages/pmos"]], suffix)
|
||||
pmb.chroot.rootm(
|
||||
[
|
||||
["mkdir", "-p", "/home/pmos/packages"],
|
||||
["rm", "-f", "/home/pmos/packages/pmos"],
|
||||
["ln", "-sf", f"/mnt/pmbootstrap/packages/{channel}", "/home/pmos/packages/pmos"],
|
||||
],
|
||||
suffix,
|
||||
)
|
||||
|
||||
# Environment variables
|
||||
env = {"CARCH": arch,
|
||||
"SUDO_APK": "abuild-apk --no-progress"}
|
||||
env = {"CARCH": arch, "SUDO_APK": "abuild-apk --no-progress"}
|
||||
if cross == "native":
|
||||
hostspec = arch.alpine_triple()
|
||||
env["CROSS_COMPILE"] = hostspec + "-"
|
||||
env["CC"] = hostspec + "-gcc"
|
||||
if cross == "crossdirect":
|
||||
env["PATH"] = ":".join([f"/native/usr/lib/crossdirect/{arch}",
|
||||
pmb.config.chroot_path])
|
||||
env["PATH"] = ":".join([f"/native/usr/lib/crossdirect/{arch}", pmb.config.chroot_path])
|
||||
if not context.ccache:
|
||||
env["CCACHE_DISABLE"] = "1"
|
||||
|
||||
|
@ -232,8 +266,9 @@ def run_abuild(context: Context, apkbuild, channel, arch: Arch, strict=False, fo
|
|||
cmd = ["abuild", "-D", "postmarketOS"]
|
||||
if strict or "pmb:strict" in apkbuild["options"]:
|
||||
if not strict:
|
||||
logging.debug(apkbuild["pkgname"] + ": 'pmb:strict' found in"
|
||||
" options, building in strict mode")
|
||||
logging.debug(
|
||||
apkbuild["pkgname"] + ": 'pmb:strict' found in" " options, building in strict mode"
|
||||
)
|
||||
cmd += ["-r"] # install depends with abuild
|
||||
else:
|
||||
cmd += ["-d"] # do not install depends with abuild
|
||||
|
@ -256,4 +291,3 @@ def run_abuild(context: Context, apkbuild, channel, arch: Arch, strict=False, fo
|
|||
pmb.chroot.user(cmd, suffix, Path("/home/pmos/build"), env=env)
|
||||
finally:
|
||||
handle_csum_failure(apkbuild, suffix)
|
||||
|
||||
|
|
|
@ -16,8 +16,7 @@ def update(pkgname):
|
|||
pmb.build.init_abuild_minimal()
|
||||
pmb.build.copy_to_buildpath(pkgname, no_override=True)
|
||||
logging.info("(native) generate checksums for " + pkgname)
|
||||
pmb.chroot.user(["abuild", "checksum"],
|
||||
working_dir=Path("/home/pmos/build"))
|
||||
pmb.chroot.user(["abuild", "checksum"], working_dir=Path("/home/pmos/build"))
|
||||
|
||||
# Copy modified APKBUILD back
|
||||
source = Chroot.native() / "home/pmos/build/APKBUILD"
|
||||
|
@ -33,5 +32,4 @@ def verify(pkgname):
|
|||
|
||||
# Fetch and verify sources, "fetch" alone does not verify them:
|
||||
# https://github.com/alpinelinux/abuild/pull/86
|
||||
pmb.chroot.user(["abuild", "fetch", "verify"],
|
||||
working_dir=Path("/home/pmos/build"))
|
||||
pmb.chroot.user(["abuild", "fetch", "verify"], working_dir=Path("/home/pmos/build"))
|
||||
|
|
|
@ -33,14 +33,14 @@ def match_kbuild_out(word):
|
|||
empty string when a separate build output directory isn't used.
|
||||
None, when no output directory is found.
|
||||
"""
|
||||
prefix = "^\\\"?\\$({?builddir}?|{?srcdir}?)\\\"?/"
|
||||
prefix = '^\\"?\\$({?builddir}?|{?srcdir}?)\\"?/'
|
||||
kbuild_out = "(.*\\/)*"
|
||||
|
||||
postfix = "(arch\\/.*\\/boot.*)\\\"?$"
|
||||
postfix = '(arch\\/.*\\/boot.*)\\"?$'
|
||||
match = re.match(prefix + kbuild_out + postfix, word)
|
||||
|
||||
if match is None:
|
||||
postfix = "(include\\/config\\/kernel\\.release)\\\"?$"
|
||||
postfix = '(include\\/config\\/kernel\\.release)\\"?$'
|
||||
match = re.match(prefix + kbuild_out + postfix, word)
|
||||
|
||||
if match is None:
|
||||
|
@ -83,15 +83,19 @@ def find_kbuild_output_dir(function_body):
|
|||
it = iter(guesses)
|
||||
first = next(it, None)
|
||||
if first is None:
|
||||
raise RuntimeError("Couldn't find a kbuild out directory. Is your "
|
||||
"APKBUILD messed up? If not, then consider "
|
||||
"adjusting the patterns in pmb/build/envkernel.py "
|
||||
"to work with your APKBUILD, or submit an issue.")
|
||||
raise RuntimeError(
|
||||
"Couldn't find a kbuild out directory. Is your "
|
||||
"APKBUILD messed up? If not, then consider "
|
||||
"adjusting the patterns in pmb/build/envkernel.py "
|
||||
"to work with your APKBUILD, or submit an issue."
|
||||
)
|
||||
if all(first == rest for rest in it):
|
||||
return first
|
||||
raise RuntimeError("Multiple kbuild out directories found. Can you modify "
|
||||
"your APKBUILD so it only has one output path? If you "
|
||||
"can't resolve it, please open an issue.")
|
||||
raise RuntimeError(
|
||||
"Multiple kbuild out directories found. Can you modify "
|
||||
"your APKBUILD so it only has one output path? If you "
|
||||
"can't resolve it, please open an issue."
|
||||
)
|
||||
|
||||
|
||||
def modify_apkbuild(pkgname: str, aport: Path):
|
||||
|
@ -103,15 +107,15 @@ def modify_apkbuild(pkgname: str, aport: Path):
|
|||
pmb.helpers.run.user(["rm", "-r", work / "aportgen"])
|
||||
|
||||
pmb.helpers.run.user(["mkdir", work / "aportgen"])
|
||||
pmb.helpers.run.user(["cp", "-r", apkbuild_path,
|
||||
work / "aportgen"])
|
||||
pmb.helpers.run.user(["cp", "-r", apkbuild_path, work / "aportgen"])
|
||||
|
||||
pkgver = pmb.build._package.get_pkgver(apkbuild["pkgver"],
|
||||
original_source=False)
|
||||
fields = {"pkgver": pkgver,
|
||||
"pkgrel": "0",
|
||||
"subpackages": "",
|
||||
"builddir": "/home/pmos/build/src"}
|
||||
pkgver = pmb.build._package.get_pkgver(apkbuild["pkgver"], original_source=False)
|
||||
fields = {
|
||||
"pkgver": pkgver,
|
||||
"pkgrel": "0",
|
||||
"subpackages": "",
|
||||
"builddir": "/home/pmos/build/src",
|
||||
}
|
||||
|
||||
pmb.aportgen.core.rewrite(pkgname, apkbuild_path, fields=fields)
|
||||
|
||||
|
@ -137,12 +141,14 @@ def run_abuild(context: Context, pkgname: str, arch: Arch, apkbuild_path: Path,
|
|||
pmb.helpers.mount.bind(Path("."), chroot / "mnt/linux")
|
||||
|
||||
if not os.path.exists(chroot / kbuild_out_source):
|
||||
raise RuntimeError("No '.output' dir found in your kernel source dir. "
|
||||
"Compile the " + context.config.device + " kernel first and "
|
||||
"then try again. See https://postmarketos.org/envkernel"
|
||||
"for details. If building on your host and only using "
|
||||
"--envkernel for packaging, make sure you have O=.output "
|
||||
"as an argument to make.")
|
||||
raise RuntimeError(
|
||||
"No '.output' dir found in your kernel source dir. "
|
||||
"Compile the " + context.config.device + " kernel first and "
|
||||
"then try again. See https://postmarketos.org/envkernel"
|
||||
"for details. If building on your host and only using "
|
||||
"--envkernel for packaging, make sure you have O=.output "
|
||||
"as an argument to make."
|
||||
)
|
||||
|
||||
# Create working directory for abuild
|
||||
pmb.build.copy_to_buildpath(pkgname)
|
||||
|
@ -154,32 +160,43 @@ def run_abuild(context: Context, pkgname: str, arch: Arch, apkbuild_path: Path,
|
|||
pkgdir = context.config.work / "packages" / channel
|
||||
if not pkgdir.exists():
|
||||
pmb.helpers.run.root(["mkdir", "-p", pkgdir])
|
||||
pmb.helpers.run.root(["chown", "-R", f"{pmb.config.chroot_uid_user}:{pmb.config.chroot_uid_user}",
|
||||
pkgdir.parent])
|
||||
pmb.helpers.run.root(
|
||||
[
|
||||
"chown",
|
||||
"-R",
|
||||
f"{pmb.config.chroot_uid_user}:{pmb.config.chroot_uid_user}",
|
||||
pkgdir.parent,
|
||||
]
|
||||
)
|
||||
|
||||
pmb.chroot.rootm([["mkdir", "-p", "/home/pmos/packages"],
|
||||
["rm", "-f", "/home/pmos/packages/pmos"],
|
||||
["ln", "-sf", f"/mnt/pmbootstrap/packages/{channel}",
|
||||
"/home/pmos/packages/pmos"]], chroot)
|
||||
pmb.chroot.rootm(
|
||||
[
|
||||
["mkdir", "-p", "/home/pmos/packages"],
|
||||
["rm", "-f", "/home/pmos/packages/pmos"],
|
||||
["ln", "-sf", f"/mnt/pmbootstrap/packages/{channel}", "/home/pmos/packages/pmos"],
|
||||
],
|
||||
chroot,
|
||||
)
|
||||
|
||||
# Create symlink from abuild working directory to envkernel build directory
|
||||
if kbuild_out != "":
|
||||
if os.path.islink(chroot / "mnt/linux" / kbuild_out) and \
|
||||
os.path.lexists(chroot / "mnt/linux" / kbuild_out):
|
||||
if os.path.islink(chroot / "mnt/linux" / kbuild_out) and os.path.lexists(
|
||||
chroot / "mnt/linux" / kbuild_out
|
||||
):
|
||||
pmb.chroot.root(["rm", "/mnt/linux" / kbuild_out])
|
||||
pmb.chroot.root(["ln", "-s", "/mnt/linux",
|
||||
build_path / "src"])
|
||||
pmb.chroot.root(["ln", "-s", kbuild_out_source,
|
||||
build_path / "src" / kbuild_out])
|
||||
pmb.chroot.root(["ln", "-s", "/mnt/linux", build_path / "src"])
|
||||
pmb.chroot.root(["ln", "-s", kbuild_out_source, build_path / "src" / kbuild_out])
|
||||
|
||||
cmd: List[PathString] = ["cp", apkbuild_path, chroot / build_path / "APKBUILD"]
|
||||
pmb.helpers.run.root(cmd)
|
||||
|
||||
# Create the apk package
|
||||
env = {"CARCH": str(arch),
|
||||
"CHOST": str(arch),
|
||||
"CBUILD": Arch.native(),
|
||||
"SUDO_APK": "abuild-apk --no-progress"}
|
||||
env = {
|
||||
"CARCH": str(arch),
|
||||
"CHOST": str(arch),
|
||||
"CBUILD": Arch.native(),
|
||||
"SUDO_APK": "abuild-apk --no-progress",
|
||||
}
|
||||
cmd = ["abuild", "rootpkg"]
|
||||
pmb.chroot.user(cmd, working_dir=build_path, env=env)
|
||||
|
||||
|
@ -188,8 +205,9 @@ def run_abuild(context: Context, pkgname: str, arch: Arch, apkbuild_path: Path,
|
|||
|
||||
# Clean up symlinks
|
||||
if kbuild_out != "":
|
||||
if os.path.islink(chroot / "mnt/linux" / kbuild_out) and \
|
||||
os.path.lexists(chroot / "mnt/linux" / kbuild_out):
|
||||
if os.path.islink(chroot / "mnt/linux" / kbuild_out) and os.path.lexists(
|
||||
chroot / "mnt/linux" / kbuild_out
|
||||
):
|
||||
pmb.chroot.root(["rm", "/mnt/linux" / kbuild_out])
|
||||
pmb.chroot.root(["rm", build_path / "src"])
|
||||
|
||||
|
@ -198,8 +216,7 @@ def package_kernel(args: PmbArgs):
|
|||
"""Frontend for 'pmbootstrap build --envkernel': creates a package from envkernel output."""
|
||||
pkgname = args.packages[0]
|
||||
if len(args.packages) > 1 or not pkgname.startswith("linux-"):
|
||||
raise RuntimeError("--envkernel needs exactly one linux-* package as "
|
||||
"argument.")
|
||||
raise RuntimeError("--envkernel needs exactly one linux-* package as " "argument.")
|
||||
|
||||
aport = pmb.helpers.pmaports.find(pkgname)
|
||||
context = get_context()
|
||||
|
@ -223,8 +240,9 @@ def package_kernel(args: PmbArgs):
|
|||
depends.append(f"binutils-{arch}")
|
||||
pmb.chroot.apk.install(depends, chroot)
|
||||
|
||||
output = pmb.build.output_path(arch, apkbuild["pkgname"], apkbuild["pkgver"],
|
||||
apkbuild["pkgrel"])
|
||||
output = pmb.build.output_path(
|
||||
arch, apkbuild["pkgname"], apkbuild["pkgver"], apkbuild["pkgrel"]
|
||||
)
|
||||
message = f"({chroot}) build {output}"
|
||||
logging.info(message)
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ from pmb.core import Chroot
|
|||
from pmb.core.context import get_context
|
||||
|
||||
|
||||
def init_abuild_minimal(chroot: Chroot=Chroot.native(), additional_pkgs: List[str]=[]):
|
||||
def init_abuild_minimal(chroot: Chroot = Chroot.native(), additional_pkgs: List[str] = []):
|
||||
"""Initialize a minimal chroot with abuild where one can do 'abuild checksum'."""
|
||||
marker = chroot / "tmp/pmb_chroot_abuild_init_done"
|
||||
if os.path.exists(marker):
|
||||
|
@ -28,10 +28,8 @@ def init_abuild_minimal(chroot: Chroot=Chroot.native(), additional_pkgs: List[st
|
|||
pmb.chroot.apk.install(["abuild"] + additional_pkgs, chroot, build=False)
|
||||
|
||||
# Fix permissions
|
||||
pmb.chroot.root(["chown", "root:abuild",
|
||||
"/var/cache/distfiles"], chroot)
|
||||
pmb.chroot.root(["chmod", "g+w",
|
||||
"/var/cache/distfiles"], chroot)
|
||||
pmb.chroot.root(["chown", "root:abuild", "/var/cache/distfiles"], chroot)
|
||||
pmb.chroot.root(["chmod", "g+w", "/var/cache/distfiles"], chroot)
|
||||
|
||||
# Add user to group abuild
|
||||
pmb.chroot.root(["adduser", "pmos", "abuild"], chroot)
|
||||
|
@ -39,7 +37,7 @@ def init_abuild_minimal(chroot: Chroot=Chroot.native(), additional_pkgs: List[st
|
|||
pathlib.Path(marker).touch()
|
||||
|
||||
|
||||
def init(chroot: Chroot=Chroot.native()) -> bool:
|
||||
def init(chroot: Chroot = Chroot.native()) -> bool:
|
||||
"""Initialize a chroot for building packages with abuild."""
|
||||
marker = chroot / "tmp/pmb_chroot_build_init_done"
|
||||
if marker.exists():
|
||||
|
@ -55,8 +53,9 @@ def init(chroot: Chroot=Chroot.native()) -> bool:
|
|||
# Generate package signing keys
|
||||
if not os.path.exists(get_context().config.work / "config_abuild/abuild.conf"):
|
||||
logging.info(f"({chroot}) generate abuild keys")
|
||||
pmb.chroot.user(["abuild-keygen", "-n", "-q", "-a"],
|
||||
chroot, env={"PACKAGER": "pmos <pmos@local>"})
|
||||
pmb.chroot.user(
|
||||
["abuild-keygen", "-n", "-q", "-a"], chroot, env={"PACKAGER": "pmos <pmos@local>"}
|
||||
)
|
||||
|
||||
# Copy package signing key to /etc/apk/keys
|
||||
for key in (chroot / "mnt/pmbootstrap/abuild-config").glob("*.pub"):
|
||||
|
@ -66,8 +65,7 @@ def init(chroot: Chroot=Chroot.native()) -> bool:
|
|||
apk_arch = chroot.arch
|
||||
|
||||
# Add apk wrapper that runs native apk and lies about arch
|
||||
if apk_arch.cpu_emulation_required() and \
|
||||
not (chroot / "usr/local/bin/abuild-apk").exists():
|
||||
if apk_arch.cpu_emulation_required() and not (chroot / "usr/local/bin/abuild-apk").exists():
|
||||
with (chroot / "tmp/apk_wrapper.sh").open("w") as handle:
|
||||
content = f"""
|
||||
#!/bin/sh
|
||||
|
@ -94,20 +92,18 @@ def init(chroot: Chroot=Chroot.native()) -> bool:
|
|||
for i in range(len(lines)):
|
||||
lines[i] = lines[i][16:]
|
||||
handle.write("\n".join(lines))
|
||||
pmb.chroot.root(["cp", "/tmp/apk_wrapper.sh",
|
||||
"/usr/local/bin/abuild-apk"], chroot)
|
||||
pmb.chroot.root(["cp", "/tmp/apk_wrapper.sh", "/usr/local/bin/abuild-apk"], chroot)
|
||||
pmb.chroot.root(["chmod", "+x", "/usr/local/bin/abuild-apk"], chroot)
|
||||
|
||||
# abuild.conf: Don't clean the build folder after building, so we can
|
||||
# inspect it afterwards for debugging
|
||||
pmb.chroot.root(["sed", "-i", "-e", "s/^CLEANUP=.*/CLEANUP=''/",
|
||||
"/etc/abuild.conf"], chroot)
|
||||
pmb.chroot.root(["sed", "-i", "-e", "s/^CLEANUP=.*/CLEANUP=''/", "/etc/abuild.conf"], chroot)
|
||||
|
||||
# abuild.conf: Don't clean up installed packages in strict mode, so
|
||||
# abuild exits directly when pressing ^C in pmbootstrap.
|
||||
pmb.chroot.root(["sed", "-i", "-e",
|
||||
"s/^ERROR_CLEANUP=.*/ERROR_CLEANUP=''/",
|
||||
"/etc/abuild.conf"], chroot)
|
||||
pmb.chroot.root(
|
||||
["sed", "-i", "-e", "s/^ERROR_CLEANUP=.*/ERROR_CLEANUP=''/", "/etc/abuild.conf"], chroot
|
||||
)
|
||||
|
||||
pathlib.Path(marker).touch()
|
||||
return True
|
||||
|
|
|
@ -34,14 +34,18 @@ def get_arch(apkbuild):
|
|||
|
||||
# Disabled package (arch="")
|
||||
if not apkbuild["arch"]:
|
||||
raise RuntimeError(f"'{pkgname}' is disabled (arch=\"\"). Please use"
|
||||
" '--arch' to specify the desired architecture.")
|
||||
raise RuntimeError(
|
||||
f"'{pkgname}' is disabled (arch=\"\"). Please use"
|
||||
" '--arch' to specify the desired architecture."
|
||||
)
|
||||
|
||||
# Multiple architectures
|
||||
if len(apkbuild["arch"]) > 1:
|
||||
raise RuntimeError(f"'{pkgname}' supports multiple architectures"
|
||||
f" ({', '.join(apkbuild['arch'])}). Please use"
|
||||
" '--arch' to specify the desired architecture.")
|
||||
raise RuntimeError(
|
||||
f"'{pkgname}' supports multiple architectures"
|
||||
f" ({', '.join(apkbuild['arch'])}). Please use"
|
||||
" '--arch' to specify the desired architecture."
|
||||
)
|
||||
|
||||
return apkbuild["arch"][0]
|
||||
|
||||
|
@ -57,18 +61,22 @@ def get_outputdir(pkgname: str, apkbuild: Dict[str, Any]) -> Path:
|
|||
chroot = Chroot.native()
|
||||
if os.path.exists(chroot / ret / ".config"):
|
||||
logging.warning("*****")
|
||||
logging.warning("NOTE: The code in this linux APKBUILD is pretty old."
|
||||
" Consider making a backup and migrating to a modern"
|
||||
" version with: pmbootstrap aportgen " + pkgname)
|
||||
logging.warning(
|
||||
"NOTE: The code in this linux APKBUILD is pretty old."
|
||||
" Consider making a backup and migrating to a modern"
|
||||
" version with: pmbootstrap aportgen " + pkgname
|
||||
)
|
||||
logging.warning("*****")
|
||||
|
||||
return ret
|
||||
|
||||
# New style ($builddir)
|
||||
cmd = "srcdir=/home/pmos/build/src source APKBUILD; echo $builddir"
|
||||
ret = Path(pmb.chroot.user(["sh", "-c", cmd],
|
||||
chroot, Path("/home/pmos/build"),
|
||||
output_return=True).rstrip())
|
||||
ret = Path(
|
||||
pmb.chroot.user(
|
||||
["sh", "-c", cmd], chroot, Path("/home/pmos/build"), output_return=True
|
||||
).rstrip()
|
||||
)
|
||||
if (chroot / ret / ".config").exists():
|
||||
return ret
|
||||
# Some Mediatek kernels use a 'kernel' subdirectory
|
||||
|
@ -80,9 +88,11 @@ def get_outputdir(pkgname: str, apkbuild: Dict[str, Any]) -> Path:
|
|||
return ret / apkbuild["_outdir"]
|
||||
|
||||
# Not found
|
||||
raise RuntimeError("Could not find the kernel config. Consider making a"
|
||||
" backup of your APKBUILD and recreating it from the"
|
||||
" template with: pmbootstrap aportgen " + pkgname)
|
||||
raise RuntimeError(
|
||||
"Could not find the kernel config. Consider making a"
|
||||
" backup of your APKBUILD and recreating it from the"
|
||||
" template with: pmbootstrap aportgen " + pkgname
|
||||
)
|
||||
|
||||
|
||||
def extract_and_patch_sources(pkgname: str, arch):
|
||||
|
@ -90,8 +100,12 @@ def extract_and_patch_sources(pkgname: str, arch):
|
|||
logging.info("(native) extract kernel source")
|
||||
pmb.chroot.user(["abuild", "unpack"], working_dir=Path("/home/pmos/build"))
|
||||
logging.info("(native) apply patches")
|
||||
pmb.chroot.user(["abuild", "prepare"], working_dir=Path("/home/pmos/build"),
|
||||
output="interactive", env={"CARCH": arch})
|
||||
pmb.chroot.user(
|
||||
["abuild", "prepare"],
|
||||
working_dir=Path("/home/pmos/build"),
|
||||
output="interactive",
|
||||
env={"CARCH": arch},
|
||||
)
|
||||
|
||||
|
||||
def menuconfig(args: PmbArgs, pkgname: str, use_oldconfig):
|
||||
|
@ -143,16 +157,17 @@ def menuconfig(args: PmbArgs, pkgname: str, use_oldconfig):
|
|||
# Run make menuconfig
|
||||
outputdir = get_outputdir(pkgname, apkbuild)
|
||||
logging.info("(native) make " + kopt)
|
||||
env = {"ARCH": arch.kernel(),
|
||||
"DISPLAY": os.environ.get("DISPLAY"),
|
||||
"XAUTHORITY": "/home/pmos/.Xauthority"}
|
||||
env = {
|
||||
"ARCH": arch.kernel(),
|
||||
"DISPLAY": os.environ.get("DISPLAY"),
|
||||
"XAUTHORITY": "/home/pmos/.Xauthority",
|
||||
}
|
||||
if cross:
|
||||
env["CROSS_COMPILE"] = f"{hostspec}-"
|
||||
env["CC"] = f"{hostspec}-gcc"
|
||||
if color:
|
||||
env["MENUCONFIG_COLOR"] = color
|
||||
pmb.chroot.user(["make", kopt], Chroot.native(),
|
||||
outputdir, output="tui", env=env)
|
||||
pmb.chroot.user(["make", kopt], Chroot.native(), outputdir, output="tui", env=env)
|
||||
|
||||
# Find the updated config
|
||||
source = Chroot.native() / outputdir / ".config"
|
||||
|
|
|
@ -21,7 +21,7 @@ from pmb.core import Chroot
|
|||
from pmb.core.context import get_context
|
||||
|
||||
|
||||
def copy_to_buildpath(package, chroot: Chroot=Chroot.native(), no_override: bool=False):
|
||||
def copy_to_buildpath(package, chroot: Chroot = Chroot.native(), no_override: bool = False):
|
||||
# Sanity check
|
||||
aport = pmb.helpers.pmaports.find(package)
|
||||
if not os.path.exists(aport / "APKBUILD"):
|
||||
|
@ -47,8 +47,7 @@ def copy_to_buildpath(package, chroot: Chroot=Chroot.native(), no_override: bool
|
|||
if not no_override:
|
||||
abuild_overrides(build / "APKBUILD")
|
||||
|
||||
pmb.chroot.root(["chown", "-R", "pmos:pmos",
|
||||
"/home/pmos/build"], chroot)
|
||||
pmb.chroot.root(["chown", "-R", "pmos:pmos", "/home/pmos/build"], chroot)
|
||||
|
||||
|
||||
def abuild_overrides(apkbuild: Path):
|
||||
|
@ -78,6 +77,7 @@ class BuildStatus(enum.Enum):
|
|||
def necessary(self):
|
||||
return self in [BuildStatus.OUTDATED, BuildStatus.NEW]
|
||||
|
||||
|
||||
def get_status(arch, apkbuild, indexes=None) -> BuildStatus:
|
||||
"""Check if the package has already been built.
|
||||
|
||||
|
@ -93,31 +93,36 @@ def get_status(arch, apkbuild, indexes=None) -> BuildStatus:
|
|||
msg = "Build is necessary for package '" + package + "': "
|
||||
|
||||
# Get version from APKINDEX
|
||||
index_data = pmb.parse.apkindex.package(package, arch, False,
|
||||
indexes)
|
||||
index_data = pmb.parse.apkindex.package(package, arch, False, indexes)
|
||||
if not index_data:
|
||||
logging.debug(msg + "No binary package available")
|
||||
return BuildStatus.NEW
|
||||
|
||||
# Can't build pmaport for arch: use Alpine's package (#1897)
|
||||
if arch and not pmb.helpers.pmaports.check_arches(apkbuild["arch"], arch):
|
||||
logging.verbose(f"{package}: build is not necessary, because pmaport"
|
||||
" can't be built for {arch}. Using Alpine's binary"
|
||||
" package.")
|
||||
logging.verbose(
|
||||
f"{package}: build is not necessary, because pmaport"
|
||||
" can't be built for {arch}. Using Alpine's binary"
|
||||
" package."
|
||||
)
|
||||
return BuildStatus.CANT_BUILD
|
||||
|
||||
# a) Binary repo has a newer version
|
||||
version_binary = index_data["version"]
|
||||
if pmb.parse.version.compare(version_binary, version_pmaports) == 1:
|
||||
logging.warning(f"WARNING: about to install {package} {version_binary}"
|
||||
f" (local pmaports: {version_pmaports}, consider"
|
||||
" 'pmbootstrap pull')")
|
||||
logging.warning(
|
||||
f"WARNING: about to install {package} {version_binary}"
|
||||
f" (local pmaports: {version_pmaports}, consider"
|
||||
" 'pmbootstrap pull')"
|
||||
)
|
||||
return BuildStatus.UNNECESSARY
|
||||
|
||||
# b) Local pmaports has a newer version
|
||||
if version_pmaports != version_binary:
|
||||
logging.debug(f"{msg}binary package out of date (binary: "
|
||||
f"{version_binary}, local pmaports: {version_pmaports})")
|
||||
logging.debug(
|
||||
f"{msg}binary package out of date (binary: "
|
||||
f"{version_binary}, local pmaports: {version_pmaports})"
|
||||
)
|
||||
return BuildStatus.OUTDATED
|
||||
|
||||
# Local pmaports and binary repo have the same version
|
||||
|
@ -132,11 +137,11 @@ def index_repo(arch=None):
|
|||
:param arch: when not defined, re-index all repos
|
||||
"""
|
||||
pmb.build.init()
|
||||
|
||||
|
||||
paths: List[Path] = []
|
||||
|
||||
for channel in pmb.config.pmaports.all_channels():
|
||||
pkgdir: Path = (get_context().config.work / "packages" / channel)
|
||||
pkgdir: Path = get_context().config.work / "packages" / channel
|
||||
if arch:
|
||||
paths.append(pkgdir / arch)
|
||||
else:
|
||||
|
@ -150,11 +155,15 @@ def index_repo(arch=None):
|
|||
description = str(datetime.datetime.now())
|
||||
commands = [
|
||||
# Wrap the index command with sh so we can use '*.apk'
|
||||
["sh", "-c", "apk -q index --output APKINDEX.tar.gz_"
|
||||
" --description " + shlex.quote(description) + ""
|
||||
" --rewrite-arch " + shlex.quote(path_arch) + " *.apk"],
|
||||
[
|
||||
"sh",
|
||||
"-c",
|
||||
"apk -q index --output APKINDEX.tar.gz_"
|
||||
" --description " + shlex.quote(description) + ""
|
||||
" --rewrite-arch " + shlex.quote(path_arch) + " *.apk",
|
||||
],
|
||||
["abuild-sign", "APKINDEX.tar.gz_"],
|
||||
["mv", "APKINDEX.tar.gz_", "APKINDEX.tar.gz"]
|
||||
["mv", "APKINDEX.tar.gz_", "APKINDEX.tar.gz"],
|
||||
]
|
||||
pmb.chroot.userm(commands, working_dir=path_repo_chroot)
|
||||
else:
|
||||
|
@ -176,19 +185,21 @@ def configure_abuild(chroot: Chroot, verify=False):
|
|||
continue
|
||||
if line != (prefix + jobs + "\n"):
|
||||
if verify:
|
||||
raise RuntimeError(f"Failed to configure abuild: {path}"
|
||||
"\nTry to delete the file"
|
||||
"(or zap the chroot).")
|
||||
pmb.chroot.root(["sed", "-i", "-e",
|
||||
f"s/^{prefix}.*/{prefix}{jobs}/",
|
||||
"/etc/abuild.conf"],
|
||||
chroot)
|
||||
raise RuntimeError(
|
||||
f"Failed to configure abuild: {path}"
|
||||
"\nTry to delete the file"
|
||||
"(or zap the chroot)."
|
||||
)
|
||||
pmb.chroot.root(
|
||||
["sed", "-i", "-e", f"s/^{prefix}.*/{prefix}{jobs}/", "/etc/abuild.conf"],
|
||||
chroot,
|
||||
)
|
||||
configure_abuild(chroot, True)
|
||||
return
|
||||
pmb.chroot.root(["sed", "-i", f"$ a\\{prefix}{jobs}", "/etc/abuild.conf"], chroot)
|
||||
|
||||
|
||||
def configure_ccache(chroot: Chroot=Chroot.native(), verify=False):
|
||||
def configure_ccache(chroot: Chroot = Chroot.native(), verify=False):
|
||||
"""Set the maximum ccache size.
|
||||
|
||||
:param verify: internally used to test if changing the config has worked.
|
||||
|
@ -203,10 +214,10 @@ def configure_ccache(chroot: Chroot=Chroot.native(), verify=False):
|
|||
if line == ("max_size = " + config.ccache_size + "\n"):
|
||||
return
|
||||
if verify:
|
||||
raise RuntimeError(f"Failed to configure ccache: {path}\nTry to"
|
||||
" delete the file (or zap the chroot).")
|
||||
raise RuntimeError(
|
||||
f"Failed to configure ccache: {path}\nTry to" " delete the file (or zap the chroot)."
|
||||
)
|
||||
|
||||
# Set the size and verify
|
||||
pmb.chroot.user(["ccache", "--max-size", config.ccache_size],
|
||||
chroot)
|
||||
pmb.chroot.user(["ccache", "--max-size", config.ccache_size], chroot)
|
||||
configure_ccache(chroot, True)
|
||||
|
|
|
@ -27,8 +27,9 @@ from pmb.types import PathString
|
|||
|
||||
|
||||
@Cache("chroot", "user_repository", mirrors_exclude=[])
|
||||
def update_repository_list(chroot: Chroot, user_repository=False, mirrors_exclude: List[str]=[],
|
||||
check=False):
|
||||
def update_repository_list(
|
||||
chroot: Chroot, user_repository=False, mirrors_exclude: List[str] = [], check=False
|
||||
):
|
||||
"""
|
||||
Update /etc/apk/repositories, if it is outdated (when the user changed the
|
||||
--mirror-alpine or --mirror-pmOS parameters).
|
||||
|
@ -52,7 +53,9 @@ def update_repository_list(chroot: Chroot, user_repository=False, mirrors_exclud
|
|||
pmb.helpers.run.root(["mkdir", "-p", path.parent])
|
||||
|
||||
# Up to date: Save cache, return
|
||||
lines_new = pmb.helpers.repo.urls(user_repository=user_repository, mirrors_exclude=mirrors_exclude)
|
||||
lines_new = pmb.helpers.repo.urls(
|
||||
user_repository=user_repository, mirrors_exclude=mirrors_exclude
|
||||
)
|
||||
if lines_old == lines_new:
|
||||
return
|
||||
|
||||
|
@ -65,14 +68,14 @@ def update_repository_list(chroot: Chroot, user_repository=False, mirrors_exclud
|
|||
if path.exists():
|
||||
pmb.helpers.run.root(["rm", path])
|
||||
for line in lines_new:
|
||||
pmb.helpers.run.root(["sh", "-c", "echo "
|
||||
f"{shlex.quote(line)} >> {path}"])
|
||||
update_repository_list(chroot, user_repository=user_repository,
|
||||
mirrors_exclude=mirrors_exclude, check=True)
|
||||
pmb.helpers.run.root(["sh", "-c", "echo " f"{shlex.quote(line)} >> {path}"])
|
||||
update_repository_list(
|
||||
chroot, user_repository=user_repository, mirrors_exclude=mirrors_exclude, check=True
|
||||
)
|
||||
|
||||
|
||||
@Cache("chroot")
|
||||
def check_min_version(chroot: Chroot=Chroot.native()):
|
||||
def check_min_version(chroot: Chroot = Chroot.native()):
|
||||
"""
|
||||
Check the minimum apk version, before running it the first time in the
|
||||
current session (lifetime of one pmbootstrap call).
|
||||
|
@ -80,16 +83,18 @@ def check_min_version(chroot: Chroot=Chroot.native()):
|
|||
|
||||
# Skip if apk is not installed yet
|
||||
if not (chroot / "sbin/apk").exists():
|
||||
logging.debug(f"NOTE: Skipped apk version check for chroot '{chroot}'"
|
||||
", because it is not installed yet!")
|
||||
logging.debug(
|
||||
f"NOTE: Skipped apk version check for chroot '{chroot}'"
|
||||
", because it is not installed yet!"
|
||||
)
|
||||
return
|
||||
|
||||
# Compare
|
||||
version_installed = installed(chroot)["apk-tools"]["version"]
|
||||
pmb.helpers.apk.check_outdated(
|
||||
version_installed,
|
||||
"Delete your http cache and zap all chroots, then try again:"
|
||||
" 'pmbootstrap zap -hc'")
|
||||
"Delete your http cache and zap all chroots, then try again:" " 'pmbootstrap zap -hc'",
|
||||
)
|
||||
|
||||
|
||||
def packages_split_to_add_del(packages):
|
||||
|
@ -148,11 +153,12 @@ def packages_get_locally_built_apks(packages, arch: Arch) -> List[Path]:
|
|||
break
|
||||
|
||||
# Record all the packages we have visited so far
|
||||
walked |= set([data_repo['pkgname'], package])
|
||||
walked |= set([data_repo["pkgname"], package])
|
||||
# Add all dependencies to the list of packages to check, excluding
|
||||
# meta-deps like cmd:* and so:* as well as conflicts (!).
|
||||
packages |= set(filter(lambda x: ":" not in x and "!" not in x,
|
||||
data_repo["depends"])) - walked
|
||||
packages |= (
|
||||
set(filter(lambda x: ":" not in x and "!" not in x, data_repo["depends"])) - walked
|
||||
)
|
||||
|
||||
return local
|
||||
|
||||
|
@ -185,8 +191,10 @@ def install_run_apk(to_add: List[str], to_add_local: List[Path], to_del: List[st
|
|||
# Use a virtual package to mark only the explicitly requested packages as
|
||||
# explicitly installed, not the ones in to_add_local
|
||||
if to_add_local:
|
||||
commands += [["add", "-u", "--virtual", ".pmbootstrap"] + local_add,
|
||||
["del", ".pmbootstrap"]]
|
||||
commands += [
|
||||
["add", "-u", "--virtual", ".pmbootstrap"] + local_add,
|
||||
["del", ".pmbootstrap"],
|
||||
]
|
||||
|
||||
if to_del:
|
||||
commands += [["del"] + to_del]
|
||||
|
@ -214,12 +222,20 @@ def install_run_apk(to_add: List[str], to_add_local: List[Path], to_del: List[st
|
|||
traceback.print_stack(file=logging.logfd)
|
||||
pmb.chroot.init(chroot)
|
||||
|
||||
for (i, command) in enumerate(commands):
|
||||
for i, command in enumerate(commands):
|
||||
# --no-interactive is a parameter to `add`, so it must be appended or apk
|
||||
# gets confused
|
||||
command += ["--no-interactive"]
|
||||
command = ["--root", chroot.path, "--arch", arch, "--cache-dir", apk_cache,
|
||||
"--repository", user_repo] + command
|
||||
command = [
|
||||
"--root",
|
||||
chroot.path,
|
||||
"--arch",
|
||||
arch,
|
||||
"--cache-dir",
|
||||
apk_cache,
|
||||
"--repository",
|
||||
user_repo,
|
||||
] + command
|
||||
|
||||
# Ignore missing repos before initial build (bpo#137)
|
||||
if os.getenv("PMB_APK_FORCE_MISSING_REPOSITORIES") == "1":
|
||||
|
@ -253,8 +269,7 @@ def install(packages, chroot: Chroot, build=True):
|
|||
context = get_context()
|
||||
|
||||
if not packages:
|
||||
logging.verbose("pmb.chroot.apk.install called with empty packages list,"
|
||||
" ignoring")
|
||||
logging.verbose("pmb.chroot.apk.install called with empty packages list," " ignoring")
|
||||
return
|
||||
|
||||
# Initialize chroot
|
||||
|
@ -275,7 +290,7 @@ def install(packages, chroot: Chroot, build=True):
|
|||
install_run_apk(to_add, to_add_local, to_del, chroot)
|
||||
|
||||
|
||||
def installed(suffix: Chroot=Chroot.native()):
|
||||
def installed(suffix: Chroot = Chroot.native()):
|
||||
"""
|
||||
Read the list of installed packages (which has almost the same format, as
|
||||
an APKINDEX, but with more keys).
|
||||
|
|
|
@ -35,11 +35,13 @@ def read_signature_info(tar):
|
|||
sigfilename = filename
|
||||
break
|
||||
if not sigfilename:
|
||||
raise RuntimeError("Could not find signature filename in apk."
|
||||
" This means that your apk file is damaged."
|
||||
" Delete it and try again."
|
||||
" If the problem persists, fill out a bug report.")
|
||||
sigkey = sigfilename[len(prefix):]
|
||||
raise RuntimeError(
|
||||
"Could not find signature filename in apk."
|
||||
" This means that your apk file is damaged."
|
||||
" Delete it and try again."
|
||||
" If the problem persists, fill out a bug report."
|
||||
)
|
||||
sigkey = sigfilename[len(prefix) :]
|
||||
logging.debug(f"sigfilename: {sigfilename}")
|
||||
logging.debug(f"sigkey: {sigkey}")
|
||||
|
||||
|
@ -57,14 +59,8 @@ def extract_temp(tar, sigfilename):
|
|||
Extract apk.static and signature as temporary files.
|
||||
"""
|
||||
ret = {
|
||||
"apk": {
|
||||
"filename": "sbin/apk.static",
|
||||
"temp_path": None
|
||||
},
|
||||
"sig": {
|
||||
"filename": sigfilename,
|
||||
"temp_path": None
|
||||
}
|
||||
"apk": {"filename": "sbin/apk.static", "temp_path": None},
|
||||
"sig": {"filename": sigfilename, "temp_path": None},
|
||||
}
|
||||
for ftype in ret.keys():
|
||||
member = tar.getmember(ret[ftype]["filename"])
|
||||
|
@ -88,17 +84,27 @@ def verify_signature(files, sigkey_path):
|
|||
"""
|
||||
logging.debug(f"Verify apk.static signature with {sigkey_path}")
|
||||
try:
|
||||
pmb.helpers.run.user(["openssl", "dgst", "-sha1", "-verify",
|
||||
sigkey_path, "-signature", files[
|
||||
"sig"]["temp_path"],
|
||||
files["apk"]["temp_path"]])
|
||||
pmb.helpers.run.user(
|
||||
[
|
||||
"openssl",
|
||||
"dgst",
|
||||
"-sha1",
|
||||
"-verify",
|
||||
sigkey_path,
|
||||
"-signature",
|
||||
files["sig"]["temp_path"],
|
||||
files["apk"]["temp_path"],
|
||||
]
|
||||
)
|
||||
except BaseException:
|
||||
os.unlink(files["sig"]["temp_path"])
|
||||
os.unlink(files["apk"]["temp_path"])
|
||||
raise RuntimeError("Failed to validate signature of apk.static."
|
||||
" Either openssl is not installed, or the"
|
||||
" download failed. Run 'pmbootstrap zap -hc' to"
|
||||
" delete the download and try again.")
|
||||
raise RuntimeError(
|
||||
"Failed to validate signature of apk.static."
|
||||
" Either openssl is not installed, or the"
|
||||
" download failed. Run 'pmbootstrap zap -hc' to"
|
||||
" delete the download and try again."
|
||||
)
|
||||
|
||||
|
||||
def extract(version, apk_path):
|
||||
|
@ -118,19 +124,23 @@ def extract(version, apk_path):
|
|||
temp_path = files["apk"]["temp_path"]
|
||||
|
||||
# Verify the version that the extracted binary reports
|
||||
logging.debug("Verify the version reported by the apk.static binary"
|
||||
f" (must match the package version {version})")
|
||||
logging.debug(
|
||||
"Verify the version reported by the apk.static binary"
|
||||
f" (must match the package version {version})"
|
||||
)
|
||||
os.chmod(temp_path, os.stat(temp_path).st_mode | stat.S_IEXEC)
|
||||
version_bin = pmb.helpers.run.user_output([temp_path, "--version"])
|
||||
version_bin = version_bin.split(" ")[1].split(",")[0]
|
||||
if not version.startswith(f"{version_bin}-r"):
|
||||
os.unlink(temp_path)
|
||||
raise RuntimeError(f"Downloaded apk-tools-static-{version}.apk,"
|
||||
" but the apk binary inside that package reports"
|
||||
f" to be version: {version_bin}!"
|
||||
" Looks like a downgrade attack"
|
||||
" from a malicious server! Switch the server (-m)"
|
||||
" and try again.")
|
||||
raise RuntimeError(
|
||||
f"Downloaded apk-tools-static-{version}.apk,"
|
||||
" but the apk binary inside that package reports"
|
||||
f" to be version: {version_bin}!"
|
||||
" Looks like a downgrade attack"
|
||||
" from a malicious server! Switch the server (-m)"
|
||||
" and try again."
|
||||
)
|
||||
|
||||
# Move it to the right path
|
||||
target_path = get_context().config.work / "apk.static"
|
||||
|
@ -153,13 +163,11 @@ def init():
|
|||
"""
|
||||
# Get and parse the APKINDEX
|
||||
apkindex = pmb.helpers.repo.alpine_apkindex_path("main")
|
||||
index_data = pmb.parse.apkindex.package("apk-tools-static",
|
||||
indexes=[apkindex])
|
||||
index_data = pmb.parse.apkindex.package("apk-tools-static", indexes=[apkindex])
|
||||
version = index_data["version"]
|
||||
|
||||
# Verify the apk-tools-static version
|
||||
pmb.helpers.apk.check_outdated(
|
||||
version, "Run 'pmbootstrap update', then try again.")
|
||||
pmb.helpers.apk.check_outdated(version, "Run 'pmbootstrap update', then try again.")
|
||||
|
||||
# Download, extract, verify apk-tools-static
|
||||
apk_name = f"apk-tools-static-{version}.apk"
|
||||
|
|
|
@ -48,8 +48,7 @@ def register(arch: Arch):
|
|||
mask = info["mask"]
|
||||
interpreter = "/usr/bin/qemu-" + arch_qemu + "-static"
|
||||
flags = "C"
|
||||
code = ":".join(["", name, type, offset, magic, mask, interpreter,
|
||||
flags])
|
||||
code = ":".join(["", name, type, offset, magic, mask, interpreter, flags])
|
||||
|
||||
# Register in binfmt_misc
|
||||
logging.info("Register qemu binfmt (" + arch_qemu + ")")
|
||||
|
|
|
@ -21,11 +21,13 @@ from pmb.core.context import get_context
|
|||
|
||||
cache_chroot_is_outdated: List[str] = []
|
||||
|
||||
|
||||
class UsrMerge(enum.Enum):
|
||||
"""
|
||||
Merge /usr while initializing chroot.
|
||||
https://systemd.io/THE_CASE_FOR_THE_USR_MERGE/
|
||||
"""
|
||||
|
||||
AUTO = 0
|
||||
ON = 1
|
||||
OFF = 2
|
||||
|
@ -47,7 +49,7 @@ def copy_resolv_conf(chroot: Chroot):
|
|||
pmb.helpers.run.root(["touch", resolv_path])
|
||||
|
||||
|
||||
def mark_in_chroot(chroot: Chroot=Chroot.native()):
|
||||
def mark_in_chroot(chroot: Chroot = Chroot.native()):
|
||||
"""
|
||||
Touch a flag so we can know when we're running in chroot (and
|
||||
don't accidentally flash partitions on our host). This marker
|
||||
|
@ -78,8 +80,7 @@ def init_keys():
|
|||
def init_usr_merge(chroot: Chroot):
|
||||
logging.info(f"({chroot}) merge /usr")
|
||||
script = f"{pmb.config.pmb_src}/pmb/data/merge-usr.sh"
|
||||
pmb.helpers.run.root(["sh", "-e", script, "CALLED_FROM_PMB",
|
||||
chroot.path])
|
||||
pmb.helpers.run.root(["sh", "-e", script, "CALLED_FROM_PMB", chroot.path])
|
||||
|
||||
|
||||
def warn_if_chroot_is_outdated(chroot: Chroot):
|
||||
|
@ -91,9 +92,11 @@ def warn_if_chroot_is_outdated(chroot: Chroot):
|
|||
|
||||
if pmb.config.workdir.chroots_outdated(chroot):
|
||||
days_warn = int(pmb.config.chroot_outdated / 3600 / 24)
|
||||
logging.warning(f"WARNING: Your {chroot} chroot is older than"
|
||||
f" {days_warn} days. Consider running"
|
||||
" 'pmbootstrap zap'.")
|
||||
logging.warning(
|
||||
f"WARNING: Your {chroot} chroot is older than"
|
||||
f" {days_warn} days. Consider running"
|
||||
" 'pmbootstrap zap'."
|
||||
)
|
||||
|
||||
cache_chroot_is_outdated += [str(chroot)]
|
||||
|
||||
|
@ -137,8 +140,7 @@ def init(chroot: Chroot, usr_merge=UsrMerge.AUTO):
|
|||
|
||||
# Initialize cache
|
||||
apk_cache = config.work / f"cache_apk_{arch}"
|
||||
pmb.helpers.run.root(["ln", "-s", "-f", "/var/cache/apk",
|
||||
chroot / "etc/apk/cache"])
|
||||
pmb.helpers.run.root(["ln", "-s", "-f", "/var/cache/apk", chroot / "etc/apk/cache"])
|
||||
|
||||
# Initialize /etc/apk/keys/, resolv.conf, repositories
|
||||
init_keys()
|
||||
|
@ -155,10 +157,9 @@ def init(chroot: Chroot, usr_merge=UsrMerge.AUTO):
|
|||
# way to install/run it.
|
||||
if chroot.type == ChrootType.NATIVE:
|
||||
pkgs += ["apk-tools-static"]
|
||||
pmb.chroot.apk_static.run(["--root", chroot.path,
|
||||
"--cache-dir", apk_cache,
|
||||
"--initdb", "--arch", arch,
|
||||
"add"] + pkgs)
|
||||
pmb.chroot.apk_static.run(
|
||||
["--root", chroot.path, "--cache-dir", apk_cache, "--initdb", "--arch", arch, "add"] + pkgs
|
||||
)
|
||||
|
||||
# Merge /usr
|
||||
if usr_merge is UsrMerge.AUTO and pmb.config.is_systemd_selected(config):
|
||||
|
@ -168,9 +169,7 @@ def init(chroot: Chroot, usr_merge=UsrMerge.AUTO):
|
|||
|
||||
# Building chroots: create "pmos" user, add symlinks to /home/pmos
|
||||
if not chroot.type == ChrootType.ROOTFS:
|
||||
pmb.chroot.root(["adduser", "-D", "pmos", "-u",
|
||||
pmb.config.chroot_uid_user],
|
||||
chroot)
|
||||
pmb.chroot.root(["adduser", "-D", "pmos", "-u", pmb.config.chroot_uid_user], chroot)
|
||||
|
||||
# Create the links (with subfolders if necessary)
|
||||
for target, link_name in pmb.config.chroot_home_symlinks.items():
|
||||
|
|
|
@ -23,12 +23,10 @@ def build(flavor, chroot: Chroot):
|
|||
if pmaports_cfg.get("supported_mkinitfs_without_flavors", False):
|
||||
pmb.chroot.root(["mkinitfs"], chroot)
|
||||
else:
|
||||
release_file = (chroot / "usr/share/kernel" / flavor / "kernel.release")
|
||||
release_file = chroot / "usr/share/kernel" / flavor / "kernel.release"
|
||||
with release_file.open() as handle:
|
||||
release = handle.read().rstrip()
|
||||
pmb.chroot.root(["mkinitfs", "-o",
|
||||
f"/boot/initramfs-{flavor}", release],
|
||||
chroot)
|
||||
pmb.chroot.root(["mkinitfs", "-o", f"/boot/initramfs-{flavor}", release], chroot)
|
||||
|
||||
|
||||
def extract(flavor, chroot: Chroot, extra=False):
|
||||
|
@ -50,27 +48,26 @@ def extract(flavor, chroot: Chroot, extra=False):
|
|||
|
||||
outside = chroot / inside
|
||||
if outside.exists():
|
||||
if not pmb.helpers.cli.confirm(f"Extraction folder {outside}"
|
||||
" already exists."
|
||||
" Do you want to overwrite it?"):
|
||||
if not pmb.helpers.cli.confirm(
|
||||
f"Extraction folder {outside}" " already exists." " Do you want to overwrite it?"
|
||||
):
|
||||
raise RuntimeError("Aborted!")
|
||||
pmb.chroot.root(["rm", "-r", inside], chroot)
|
||||
|
||||
# Extraction script (because passing a file to stdin is not allowed
|
||||
# in pmbootstrap's chroot/shell functions for security reasons)
|
||||
with (chroot / "tmp/_extract.sh").open("w") as handle:
|
||||
handle.write(
|
||||
"#!/bin/sh\n"
|
||||
f"cd {inside} && cpio -i < _initfs\n")
|
||||
handle.write("#!/bin/sh\n" f"cd {inside} && cpio -i < _initfs\n")
|
||||
|
||||
# Extract
|
||||
commands = [["mkdir", "-p", inside],
|
||||
["cp", initfs_file, f"{inside}/_initfs.gz"],
|
||||
["gzip", "-d", f"{inside}/_initfs.gz"],
|
||||
["cat", "/tmp/_extract.sh"], # for the log
|
||||
["sh", "/tmp/_extract.sh"],
|
||||
["rm", "/tmp/_extract.sh", f"{inside}/_initfs"]
|
||||
]
|
||||
commands = [
|
||||
["mkdir", "-p", inside],
|
||||
["cp", initfs_file, f"{inside}/_initfs.gz"],
|
||||
["gzip", "-d", f"{inside}/_initfs.gz"],
|
||||
["cat", "/tmp/_extract.sh"], # for the log
|
||||
["sh", "/tmp/_extract.sh"],
|
||||
["rm", "/tmp/_extract.sh", f"{inside}/_initfs"],
|
||||
]
|
||||
for command in commands:
|
||||
pmb.chroot.root(command, chroot)
|
||||
|
||||
|
@ -78,7 +75,7 @@ def extract(flavor, chroot: Chroot, extra=False):
|
|||
return outside
|
||||
|
||||
|
||||
def ls( flavor, suffix, extra=False):
|
||||
def ls(flavor, suffix, extra=False):
|
||||
tmp = "/tmp/initfs-extracted"
|
||||
if extra:
|
||||
tmp = "/tmp/initfs-extra-extracted"
|
||||
|
|
|
@ -15,7 +15,7 @@ def list_chroot(suffix: Chroot, remove_prefix=True):
|
|||
for pkgname in pmb.chroot.apk.installed(suffix).keys():
|
||||
if pkgname.startswith(prefix):
|
||||
if remove_prefix:
|
||||
ret.append(pkgname[len(prefix):])
|
||||
ret.append(pkgname[len(prefix) :])
|
||||
else:
|
||||
ret.append(pkgname)
|
||||
return ret
|
||||
|
@ -25,7 +25,7 @@ def list_aports():
|
|||
ret = []
|
||||
prefix = pmb.config.initfs_hook_prefix
|
||||
for path in pkgrepo_iglob(f"*/{prefix}*"):
|
||||
ret.append(os.path.basename(path)[len(prefix):])
|
||||
ret.append(os.path.basename(path)[len(prefix) :])
|
||||
return ret
|
||||
|
||||
|
||||
|
@ -40,9 +40,9 @@ def ls(suffix: Chroot):
|
|||
|
||||
def add(hook, suffix: Chroot):
|
||||
if hook not in list_aports():
|
||||
raise RuntimeError("Invalid hook name!"
|
||||
" Run 'pmbootstrap initfs hook_ls'"
|
||||
" to get a list of all hooks.")
|
||||
raise RuntimeError(
|
||||
"Invalid hook name!" " Run 'pmbootstrap initfs hook_ls'" " to get a list of all hooks."
|
||||
)
|
||||
prefix = pmb.config.initfs_hook_prefix
|
||||
pmb.chroot.apk.install([f"{prefix}{hook}"], suffix)
|
||||
|
||||
|
|
|
@ -27,7 +27,9 @@ def mount_chroot_image(chroot: Chroot):
|
|||
chroot_native = Chroot.native()
|
||||
pmb.chroot.init(chroot_native)
|
||||
|
||||
loopdev = pmb.install.losetup.mount(Path("/") / Path(chroot.name).relative_to(chroot_native.path))
|
||||
loopdev = pmb.install.losetup.mount(
|
||||
Path("/") / Path(chroot.name).relative_to(chroot_native.path)
|
||||
)
|
||||
pmb.helpers.mount.bind_file(loopdev, chroot_native / "dev/install")
|
||||
# Set up device mapper bits
|
||||
pmb.chroot.root(["kpartx", "-u", "/dev/install"], chroot_native)
|
||||
|
@ -40,6 +42,7 @@ def mount_chroot_image(chroot: Chroot):
|
|||
|
||||
logging.info(f"({chroot}) mounted {chroot.name}")
|
||||
|
||||
|
||||
def create_device_nodes(chroot: Chroot):
|
||||
"""
|
||||
Create device nodes for null, zero, full, random, urandom in the chroot.
|
||||
|
@ -49,13 +52,17 @@ def create_device_nodes(chroot: Chroot):
|
|||
for dev in pmb.config.chroot_device_nodes:
|
||||
path = chroot / "dev" / str(dev[4])
|
||||
if not path.exists():
|
||||
pmb.helpers.run.root(["mknod",
|
||||
"-m", str(dev[0]), # permissions
|
||||
path, # name
|
||||
str(dev[1]), # type
|
||||
str(dev[2]), # major
|
||||
str(dev[3]), # minor
|
||||
])
|
||||
pmb.helpers.run.root(
|
||||
[
|
||||
"mknod",
|
||||
"-m",
|
||||
str(dev[0]), # permissions
|
||||
path, # name
|
||||
str(dev[1]), # type
|
||||
str(dev[2]), # major
|
||||
str(dev[3]), # minor
|
||||
]
|
||||
)
|
||||
|
||||
# Verify major and minor numbers of created nodes
|
||||
for dev in pmb.config.chroot_device_nodes:
|
||||
|
@ -68,7 +75,7 @@ def create_device_nodes(chroot: Chroot):
|
|||
# Verify /dev/zero reading and writing
|
||||
path = chroot / "dev/zero"
|
||||
with open(path, "r+b", 0) as handle:
|
||||
assert handle.write(bytes([0xff])), f"Write failed for {path}"
|
||||
assert handle.write(bytes([0xFF])), f"Write failed for {path}"
|
||||
assert handle.read(1) == bytes([0x00]), f"Read failed for {path}"
|
||||
|
||||
# On failure: Show filesystem-related error
|
||||
|
@ -77,7 +84,7 @@ def create_device_nodes(chroot: Chroot):
|
|||
raise RuntimeError(f"Failed to create device nodes in the '{chroot}' chroot.")
|
||||
|
||||
|
||||
def mount_dev_tmpfs(chroot: Chroot=Chroot.native()):
|
||||
def mount_dev_tmpfs(chroot: Chroot = Chroot.native()):
|
||||
"""
|
||||
Mount tmpfs inside the chroot's dev folder to make sure we can create
|
||||
device nodes, even if the filesystem of the work folder does not support
|
||||
|
@ -90,15 +97,13 @@ def mount_dev_tmpfs(chroot: Chroot=Chroot.native()):
|
|||
|
||||
# Create the $chroot/dev folder and mount tmpfs there
|
||||
pmb.helpers.run.root(["mkdir", "-p", dev])
|
||||
pmb.helpers.run.root(["mount", "-t", "tmpfs",
|
||||
"-o", "size=1M,noexec,dev",
|
||||
"tmpfs", dev])
|
||||
pmb.helpers.run.root(["mount", "-t", "tmpfs", "-o", "size=1M,noexec,dev", "tmpfs", dev])
|
||||
|
||||
# Create pts, shm folders and device nodes
|
||||
pmb.helpers.run.root(["mkdir", "-p", dev / "pts", dev / "shm"])
|
||||
pmb.helpers.run.root(["mount", "-t", "tmpfs",
|
||||
"-o", "nodev,nosuid,noexec",
|
||||
"tmpfs", dev / "shm"])
|
||||
pmb.helpers.run.root(
|
||||
["mount", "-t", "tmpfs", "-o", "nodev,nosuid,noexec", "tmpfs", dev / "shm"]
|
||||
)
|
||||
create_device_nodes(chroot)
|
||||
|
||||
# Setup /dev/fd as a symlink
|
||||
|
@ -128,7 +133,6 @@ def mount(chroot: Chroot):
|
|||
if not pmb.helpers.mount.ismount(target_outer):
|
||||
pmb.helpers.mount.bind(source, target_outer)
|
||||
|
||||
|
||||
# Set up binfmt
|
||||
if not arch.cpu_emulation_required():
|
||||
return
|
||||
|
@ -137,9 +141,11 @@ def mount(chroot: Chroot):
|
|||
|
||||
# mount --bind the qemu-user binary
|
||||
pmb.chroot.binfmt.register(arch)
|
||||
pmb.helpers.mount.bind_file(Chroot.native() / f"usr/bin/qemu-{arch_qemu}",
|
||||
chroot / f"usr/bin/qemu-{arch_qemu}-static",
|
||||
create_folders=True)
|
||||
pmb.helpers.mount.bind_file(
|
||||
Chroot.native() / f"usr/bin/qemu-{arch_qemu}",
|
||||
chroot / f"usr/bin/qemu-{arch_qemu}-static",
|
||||
create_folders=True,
|
||||
)
|
||||
|
||||
|
||||
def mount_native_into_foreign(chroot: Chroot):
|
||||
|
@ -148,19 +154,19 @@ def mount_native_into_foreign(chroot: Chroot):
|
|||
pmb.helpers.mount.bind(source, target)
|
||||
|
||||
musl = next(source.glob("lib/ld-musl-*.so.1")).name
|
||||
musl_link = (chroot / "lib" / musl)
|
||||
musl_link = chroot / "lib" / musl
|
||||
if not musl_link.is_symlink():
|
||||
pmb.helpers.run.root(["ln", "-s", "/native/lib/" + musl,
|
||||
musl_link])
|
||||
pmb.helpers.run.root(["ln", "-s", "/native/lib/" + musl, musl_link])
|
||||
# pmb.helpers.run.root(["ln", "-sf", "/native/usr/bin/pigz", "/usr/local/bin/pigz"])
|
||||
|
||||
|
||||
def remove_mnt_pmbootstrap(chroot: Chroot):
|
||||
""" Safely remove /mnt/pmbootstrap directories from the chroot, without
|
||||
running rm -r as root and potentially removing data inside the
|
||||
mountpoint in case it was still mounted (bug in pmbootstrap, or user
|
||||
ran pmbootstrap 2x in parallel). This is similar to running 'rm -r -d',
|
||||
but we don't assume that the host's rm has the -d flag (busybox does
|
||||
not). """
|
||||
"""Safely remove /mnt/pmbootstrap directories from the chroot, without
|
||||
running rm -r as root and potentially removing data inside the
|
||||
mountpoint in case it was still mounted (bug in pmbootstrap, or user
|
||||
ran pmbootstrap 2x in parallel). This is similar to running 'rm -r -d',
|
||||
but we don't assume that the host's rm has the -d flag (busybox does
|
||||
not)."""
|
||||
mnt_dir = chroot / "mnt/pmbootstrap"
|
||||
|
||||
if not mnt_dir.exists():
|
||||
|
|
|
@ -26,8 +26,7 @@ def kernel_flavor_installed(chroot: Chroot, autoinstall=True):
|
|||
if not chroot.is_mounted():
|
||||
pmb.chroot.init(chroot)
|
||||
config = get_context().config
|
||||
packages = ([f"device-{config.device}"] +
|
||||
pmb.install.get_kernel_package(config))
|
||||
packages = [f"device-{config.device}"] + pmb.install.get_kernel_package(config)
|
||||
pmb.chroot.apk.install(packages, chroot)
|
||||
|
||||
glob_result = list((chroot / "usr/share/kernel").glob("*"))
|
||||
|
@ -37,7 +36,7 @@ def kernel_flavor_installed(chroot: Chroot, autoinstall=True):
|
|||
|
||||
|
||||
# FIXME: this function has ONE user, does it need to exist?
|
||||
def tempfolder(path: Path, chroot: Chroot=Chroot.native()):
|
||||
def tempfolder(path: Path, chroot: Chroot = Chroot.native()):
|
||||
"""
|
||||
Create a temporary folder inside the chroot that belongs to "user".
|
||||
The folder gets deleted, if it already exists.
|
||||
|
@ -59,19 +58,22 @@ def copy_xauthority(args: PmbArgs):
|
|||
# Check $DISPLAY
|
||||
logging.info("(native) copy host Xauthority")
|
||||
if not os.environ.get("DISPLAY"):
|
||||
raise RuntimeError("Your $DISPLAY variable is not set. If you have an"
|
||||
" X11 server running as your current user, try"
|
||||
" 'export DISPLAY=:0' and run your last"
|
||||
" pmbootstrap command again.")
|
||||
raise RuntimeError(
|
||||
"Your $DISPLAY variable is not set. If you have an"
|
||||
" X11 server running as your current user, try"
|
||||
" 'export DISPLAY=:0' and run your last"
|
||||
" pmbootstrap command again."
|
||||
)
|
||||
|
||||
# Check $XAUTHORITY
|
||||
original = os.environ.get("XAUTHORITY")
|
||||
if not original:
|
||||
original = os.path.join(os.environ['HOME'], '.Xauthority')
|
||||
original = os.path.join(os.environ["HOME"], ".Xauthority")
|
||||
if not os.path.exists(original):
|
||||
raise RuntimeError("Could not find your Xauthority file, try to export"
|
||||
" your $XAUTHORITY correctly. Looked here: " +
|
||||
original)
|
||||
raise RuntimeError(
|
||||
"Could not find your Xauthority file, try to export"
|
||||
" your $XAUTHORITY correctly. Looked here: " + original
|
||||
)
|
||||
|
||||
# Copy to chroot and chown
|
||||
copy = Chroot.native() / "home/pmos/.Xauthority"
|
||||
|
|
|
@ -22,16 +22,26 @@ def executables_absolute_path():
|
|||
for binary in ["sh", "chroot"]:
|
||||
path = shutil.which(binary, path=pmb.config.chroot_host_path)
|
||||
if not path:
|
||||
raise RuntimeError(f"Could not find the '{binary}'"
|
||||
" executable. Make sure that it is in"
|
||||
" your current user's PATH.")
|
||||
raise RuntimeError(
|
||||
f"Could not find the '{binary}'"
|
||||
" executable. Make sure that it is in"
|
||||
" your current user's PATH."
|
||||
)
|
||||
ret[binary] = path
|
||||
return ret
|
||||
|
||||
|
||||
def rootm(cmds: Sequence[Sequence[PathString]], chroot: Chroot=Chroot.native(), working_dir: PurePath=PurePath("/"), output="log",
|
||||
output_return=False, check=None, env={},
|
||||
disable_timeout=False, add_proxy_env_vars=True):
|
||||
def rootm(
|
||||
cmds: Sequence[Sequence[PathString]],
|
||||
chroot: Chroot = Chroot.native(),
|
||||
working_dir: PurePath = PurePath("/"),
|
||||
output="log",
|
||||
output_return=False,
|
||||
check=None,
|
||||
env={},
|
||||
disable_timeout=False,
|
||||
add_proxy_env_vars=True,
|
||||
):
|
||||
"""
|
||||
Run a list of commands inside a chroot as root.
|
||||
|
||||
|
@ -59,14 +69,16 @@ def rootm(cmds: Sequence[Sequence[PathString]], chroot: Chroot=Chroot.native(),
|
|||
msg += "; ".join([" ".join(cmd_str) for cmd_str in cmd_strs])
|
||||
|
||||
# Merge env with defaults into env_all
|
||||
env_all: Env = {"CHARSET": "UTF-8",
|
||||
"HISTFILE": "~/.ash_history",
|
||||
"HOME": "/root",
|
||||
"LANG": "UTF-8",
|
||||
"PATH": pmb.config.chroot_path,
|
||||
"PYTHONUNBUFFERED": "1",
|
||||
"SHELL": "/bin/ash",
|
||||
"TERM": "xterm"}
|
||||
env_all: Env = {
|
||||
"CHARSET": "UTF-8",
|
||||
"HISTFILE": "~/.ash_history",
|
||||
"HOME": "/root",
|
||||
"LANG": "UTF-8",
|
||||
"PATH": pmb.config.chroot_path,
|
||||
"PYTHONUNBUFFERED": "1",
|
||||
"SHELL": "/bin/ash",
|
||||
"TERM": "xterm",
|
||||
}
|
||||
for key, value in env.items():
|
||||
env_all[key] = value
|
||||
if add_proxy_env_vars:
|
||||
|
@ -77,26 +89,60 @@ def rootm(cmds: Sequence[Sequence[PathString]], chroot: Chroot=Chroot.native(),
|
|||
# cmd_chroot: ["/sbin/chroot", "/..._native", "/bin/sh", "-c", "echo test"]
|
||||
# cmd_sudo: ["sudo", "env", "-i", "sh", "-c", "PATH=... /sbin/chroot ..."]
|
||||
executables = executables_absolute_path()
|
||||
cmd_chroot = [executables["chroot"], chroot.path, "/bin/sh", "-c",
|
||||
pmb.helpers.run_core.flat_cmd(cmd_strs, Path(working_dir))]
|
||||
cmd_sudo = pmb.config.sudo([
|
||||
"env", "-i", executables["sh"], "-c",
|
||||
pmb.helpers.run_core.flat_cmd([cmd_chroot], env=env_all)]
|
||||
cmd_chroot = [
|
||||
executables["chroot"],
|
||||
chroot.path,
|
||||
"/bin/sh",
|
||||
"-c",
|
||||
pmb.helpers.run_core.flat_cmd(cmd_strs, Path(working_dir)),
|
||||
]
|
||||
cmd_sudo = pmb.config.sudo(
|
||||
[
|
||||
"env",
|
||||
"-i",
|
||||
executables["sh"],
|
||||
"-c",
|
||||
pmb.helpers.run_core.flat_cmd([cmd_chroot], env=env_all),
|
||||
]
|
||||
)
|
||||
return pmb.helpers.run_core.core(
|
||||
msg, cmd_sudo, None, output, output_return, check, True, disable_timeout
|
||||
)
|
||||
return pmb.helpers.run_core.core(msg, cmd_sudo, None, output,
|
||||
output_return, check, True,
|
||||
disable_timeout)
|
||||
|
||||
|
||||
def root(cmds: Sequence[PathString], chroot: Chroot=Chroot.native(), working_dir: PurePath=PurePath("/"), output="log",
|
||||
output_return=False, check=None, env={},
|
||||
disable_timeout=False, add_proxy_env_vars=True):
|
||||
return rootm([cmds], chroot, working_dir, output, output_return, check, env,
|
||||
disable_timeout, add_proxy_env_vars)
|
||||
def root(
|
||||
cmds: Sequence[PathString],
|
||||
chroot: Chroot = Chroot.native(),
|
||||
working_dir: PurePath = PurePath("/"),
|
||||
output="log",
|
||||
output_return=False,
|
||||
check=None,
|
||||
env={},
|
||||
disable_timeout=False,
|
||||
add_proxy_env_vars=True,
|
||||
):
|
||||
return rootm(
|
||||
[cmds],
|
||||
chroot,
|
||||
working_dir,
|
||||
output,
|
||||
output_return,
|
||||
check,
|
||||
env,
|
||||
disable_timeout,
|
||||
add_proxy_env_vars,
|
||||
)
|
||||
|
||||
|
||||
def userm(cmds: Sequence[Sequence[PathString]], chroot: Chroot=Chroot.native(), working_dir: Path = Path("/"), output="log",
|
||||
output_return=False, check=None, env={}):
|
||||
def userm(
|
||||
cmds: Sequence[Sequence[PathString]],
|
||||
chroot: Chroot = Chroot.native(),
|
||||
working_dir: Path = Path("/"),
|
||||
output="log",
|
||||
output_return=False,
|
||||
check=None,
|
||||
env={},
|
||||
):
|
||||
"""
|
||||
Run a command inside a chroot as "user". We always use the BusyBox
|
||||
implementation of 'su', because other implementations may override the PATH
|
||||
|
@ -116,24 +162,31 @@ def userm(cmds: Sequence[Sequence[PathString]], chroot: Chroot=Chroot.native(),
|
|||
|
||||
flat_cmd = pmb.helpers.run_core.flat_cmd(cmds, env=env)
|
||||
cmd = ["busybox", "su", "pmos", "-c", flat_cmd]
|
||||
return pmb.chroot.root(cmd, chroot, working_dir, output,
|
||||
output_return, check, {},
|
||||
add_proxy_env_vars=False)
|
||||
return pmb.chroot.root(
|
||||
cmd, chroot, working_dir, output, output_return, check, {}, add_proxy_env_vars=False
|
||||
)
|
||||
|
||||
|
||||
def user(cmd: Sequence[PathString], chroot: Chroot=Chroot.native(), working_dir: Path = Path("/"), output="log",
|
||||
output_return=False, check=None, env={}):
|
||||
def user(
|
||||
cmd: Sequence[PathString],
|
||||
chroot: Chroot = Chroot.native(),
|
||||
working_dir: Path = Path("/"),
|
||||
output="log",
|
||||
output_return=False,
|
||||
check=None,
|
||||
env={},
|
||||
):
|
||||
userm([cmd], chroot, working_dir, output, output_return, check, env)
|
||||
|
||||
|
||||
def exists(username, chroot: Chroot=Chroot.native()):
|
||||
def exists(username, chroot: Chroot = Chroot.native()):
|
||||
"""
|
||||
Checks if username exists in the system
|
||||
|
||||
:param username: User name
|
||||
:returns: bool
|
||||
"""
|
||||
output = pmb.chroot.root(["getent", "passwd", username],
|
||||
chroot, output_return=True, check=False)
|
||||
output = pmb.chroot.root(
|
||||
["getent", "passwd", username], chroot, output_return=True, check=False
|
||||
)
|
||||
return len(output) > 0
|
||||
|
||||
|
|
|
@ -40,12 +40,13 @@ def shutdown_cryptsetup_device(name: str):
|
|||
if not (Chroot.native() / "dev/mapper" / name).exists():
|
||||
return
|
||||
pmb.chroot.apk.install(["cryptsetup"], Chroot.native())
|
||||
status = pmb.chroot.root(["cryptsetup", "status", name],
|
||||
output_return=True, check=False)
|
||||
status = pmb.chroot.root(["cryptsetup", "status", name], output_return=True, check=False)
|
||||
if not status:
|
||||
logging.warning("WARNING: Failed to run cryptsetup to get the status"
|
||||
" for " + name + ", assuming it is not mounted"
|
||||
" (shutdown fails later if it is)!")
|
||||
logging.warning(
|
||||
"WARNING: Failed to run cryptsetup to get the status"
|
||||
" for " + name + ", assuming it is not mounted"
|
||||
" (shutdown fails later if it is)!"
|
||||
)
|
||||
return
|
||||
|
||||
if status.startswith("/dev/mapper/" + name + " is active."):
|
||||
|
|
|
@ -6,15 +6,16 @@ from pmb.core.context import get_context
|
|||
from .apk import packages_get_locally_built_apks
|
||||
import pmb.config.pmaports
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def apk_mocks(monkeypatch):
|
||||
def _pmaports_config(_aports=None):
|
||||
return {
|
||||
"channel": "edge",
|
||||
}
|
||||
}
|
||||
|
||||
monkeypatch.setattr(pmb.config.pmaports, "read_config", _pmaports_config)
|
||||
|
||||
|
||||
def _apkindex_package(_package, _arch, _must_exist=False, indexes=None):
|
||||
if _package == "package1":
|
||||
return {
|
||||
|
@ -61,7 +62,7 @@ def test_get_local_apks(pmb_args, apk_mocks):
|
|||
|
||||
pkgname = "package1"
|
||||
arch = Arch.x86_64
|
||||
|
||||
|
||||
apk_file = create_apk(pkgname, arch)
|
||||
|
||||
local = packages_get_locally_built_apks([pkgname, "fake-package"], arch)
|
||||
|
@ -69,7 +70,6 @@ def test_get_local_apks(pmb_args, apk_mocks):
|
|||
assert len(local) == 1
|
||||
assert local[0].parts[-2:] == apk_file.parts[-2:]
|
||||
|
||||
|
||||
create_apk("package2", arch)
|
||||
create_apk("package3", arch)
|
||||
create_apk("package4", arch)
|
||||
|
@ -78,4 +78,3 @@ def test_get_local_apks(pmb_args, apk_mocks):
|
|||
local = packages_get_locally_built_apks(["package3"], arch)
|
||||
print(local)
|
||||
assert len(local) == 4
|
||||
|
||||
|
|
|
@ -36,9 +36,17 @@ def del_chroot(path: Path, confirm=True, dry=False):
|
|||
pmb.helpers.run.root(["rm", "-rf", path])
|
||||
|
||||
|
||||
def zap(confirm=True, dry=False, pkgs_local=False, http=False,
|
||||
pkgs_local_mismatch=False, pkgs_online_mismatch=False, distfiles=False,
|
||||
rust=False, netboot=False):
|
||||
def zap(
|
||||
confirm=True,
|
||||
dry=False,
|
||||
pkgs_local=False,
|
||||
http=False,
|
||||
pkgs_local_mismatch=False,
|
||||
pkgs_online_mismatch=False,
|
||||
distfiles=False,
|
||||
rust=False,
|
||||
netboot=False,
|
||||
):
|
||||
"""
|
||||
Shutdown everything inside the chroots (e.g. adb), umount
|
||||
everything and then safely remove folders from the work-directory.
|
||||
|
@ -90,8 +98,7 @@ def zap(confirm=True, dry=False, pkgs_local=False, http=False,
|
|||
pattern = os.path.realpath(f"{get_context().config.work}/{pattern}")
|
||||
matches = glob.glob(pattern)
|
||||
for match in matches:
|
||||
if (not confirm or
|
||||
pmb.helpers.cli.confirm(f"Remove {match}?")):
|
||||
if not confirm or pmb.helpers.cli.confirm(f"Remove {match}?"):
|
||||
logging.info(f"% rm -rf {match}")
|
||||
if not dry:
|
||||
pmb.helpers.run.root(["rm", "-rf", match])
|
||||
|
@ -114,13 +121,17 @@ def zap_pkgs_local_mismatch(confirm=True, dry=False):
|
|||
if not os.path.exists(f"{get_context().config.work}/packages/{channel}"):
|
||||
return
|
||||
|
||||
question = "Remove binary packages that are newer than the corresponding" \
|
||||
f" pmaports (channel '{channel}')?"
|
||||
question = (
|
||||
"Remove binary packages that are newer than the corresponding"
|
||||
f" pmaports (channel '{channel}')?"
|
||||
)
|
||||
if confirm and not pmb.helpers.cli.confirm(question):
|
||||
return
|
||||
|
||||
reindex = False
|
||||
for apkindex_path in (get_context().config.work / "packages" / channel).glob("*/APKINDEX.tar.gz"):
|
||||
for apkindex_path in (get_context().config.work / "packages" / channel).glob(
|
||||
"*/APKINDEX.tar.gz"
|
||||
):
|
||||
# Delete packages without same version in aports
|
||||
blocks = pmb.parse.apkindex.parse_blocks(apkindex_path)
|
||||
for block in blocks:
|
||||
|
@ -133,15 +144,13 @@ def zap_pkgs_local_mismatch(confirm=True, dry=False):
|
|||
apk_path_short = f"{arch}/{pkgname}-{version}.apk"
|
||||
apk_path = f"{get_context().config.work}/packages/{channel}/{apk_path_short}"
|
||||
if not os.path.exists(apk_path):
|
||||
logging.info("WARNING: Package mentioned in index not"
|
||||
f" found: {apk_path_short}")
|
||||
logging.info("WARNING: Package mentioned in index not" f" found: {apk_path_short}")
|
||||
continue
|
||||
|
||||
# Aport path
|
||||
aport_path = pmb.helpers.pmaports.find_optional(origin)
|
||||
if not aport_path:
|
||||
logging.info(f"% rm {apk_path_short}"
|
||||
f" ({origin} aport not found)")
|
||||
logging.info(f"% rm {apk_path_short}" f" ({origin} aport not found)")
|
||||
if not dry:
|
||||
pmb.helpers.run.root(["rm", apk_path])
|
||||
reindex = True
|
||||
|
@ -151,8 +160,7 @@ def zap_pkgs_local_mismatch(confirm=True, dry=False):
|
|||
apkbuild = pmb.parse.apkbuild(aport_path)
|
||||
version_aport = f"{apkbuild['pkgver']}-r{apkbuild['pkgrel']}"
|
||||
if version != version_aport:
|
||||
logging.info(f"% rm {apk_path_short}"
|
||||
f" ({origin} aport: {version_aport})")
|
||||
logging.info(f"% rm {apk_path_short}" f" ({origin} aport: {version_aport})")
|
||||
if not dry:
|
||||
pmb.helpers.run.root(["rm", apk_path])
|
||||
reindex = True
|
||||
|
@ -166,8 +174,7 @@ def zap_pkgs_online_mismatch(confirm=True, dry=False):
|
|||
paths = list(get_context().config.work.glob("cache_apk_*"))
|
||||
if not len(paths):
|
||||
return
|
||||
if (confirm and not pmb.helpers.cli.confirm("Remove outdated"
|
||||
" binary packages?")):
|
||||
if confirm and not pmb.helpers.cli.confirm("Remove outdated" " binary packages?"):
|
||||
return
|
||||
|
||||
# Iterate over existing apk caches
|
||||
|
|
|
@ -12,15 +12,15 @@ from pmb.core import Chroot
|
|||
|
||||
|
||||
def get_ci_scripts(topdir):
|
||||
""" Find 'pmbootstrap ci'-compatible scripts inside a git repository, and
|
||||
parse their metadata (description, options). The reference is at:
|
||||
https://postmarketos.org/pmb-ci
|
||||
"""Find 'pmbootstrap ci'-compatible scripts inside a git repository, and
|
||||
parse their metadata (description, options). The reference is at:
|
||||
https://postmarketos.org/pmb-ci
|
||||
|
||||
:param topdir: top directory of the git repository, get it with: pmb.helpers.git.get_topdir()
|
||||
:param topdir: top directory of the git repository, get it with: pmb.helpers.git.get_topdir()
|
||||
|
||||
:returns: a dict of CI scripts found in the git repository, e.g.
|
||||
{"ruff": {"description": "lint all python scripts", "options": []}, ...}
|
||||
"""
|
||||
:returns: a dict of CI scripts found in the git repository, e.g.
|
||||
{"ruff": {"description": "lint all python scripts", "options": []}, ...}
|
||||
"""
|
||||
ret = {}
|
||||
for script in glob.glob(f"{topdir}/.ci/*.sh"):
|
||||
is_pmb_ci_script = False
|
||||
|
@ -48,26 +48,27 @@ def get_ci_scripts(topdir):
|
|||
|
||||
for option in options:
|
||||
if option not in pmb.config.ci_valid_options:
|
||||
raise RuntimeError(f"{script}: unsupported option '{option}'."
|
||||
" Typo in script or pmbootstrap too old?")
|
||||
raise RuntimeError(
|
||||
f"{script}: unsupported option '{option}'."
|
||||
" Typo in script or pmbootstrap too old?"
|
||||
)
|
||||
|
||||
short_name = os.path.basename(script).split(".", -1)[0]
|
||||
ret[short_name] = {"description": description,
|
||||
"options": options}
|
||||
ret[short_name] = {"description": description, "options": options}
|
||||
return ret
|
||||
|
||||
|
||||
def sort_scripts_by_speed(scripts):
|
||||
""" Order the scripts, so fast scripts run before slow scripts. Whether a
|
||||
script is fast or not is determined by the '# Options: slow' comment in
|
||||
the file.
|
||||
|
||||
:param scripts: return of get_ci_scripts()
|
||||
|
||||
:returns: same format as get_ci_scripts(), but as ordered dict with
|
||||
fast scripts before slow scripts
|
||||
|
||||
"""
|
||||
"""Order the scripts, so fast scripts run before slow scripts. Whether a
|
||||
script is fast or not is determined by the '# Options: slow' comment in
|
||||
the file.
|
||||
|
||||
:param scripts: return of get_ci_scripts()
|
||||
|
||||
:returns: same format as get_ci_scripts(), but as ordered dict with
|
||||
fast scripts before slow scripts
|
||||
|
||||
"""
|
||||
ret = collections.OrderedDict()
|
||||
|
||||
# Fast scripts first
|
||||
|
@ -85,14 +86,14 @@ def sort_scripts_by_speed(scripts):
|
|||
|
||||
|
||||
def ask_which_scripts_to_run(scripts_available):
|
||||
""" Display an interactive prompt about which of the scripts the user
|
||||
wishes to run, or all of them.
|
||||
|
||||
:param scripts_available: same format as get_ci_scripts()
|
||||
|
||||
:returns: either full scripts_available (all selected), or a subset
|
||||
|
||||
"""
|
||||
"""Display an interactive prompt about which of the scripts the user
|
||||
wishes to run, or all of them.
|
||||
|
||||
:param scripts_available: same format as get_ci_scripts()
|
||||
|
||||
:returns: either full scripts_available (all selected), or a subset
|
||||
|
||||
"""
|
||||
count = len(scripts_available.items())
|
||||
choices = ["all"]
|
||||
|
||||
|
@ -104,8 +105,7 @@ def ask_which_scripts_to_run(scripts_available):
|
|||
logging.info(f"* {script_name}: {script['description']}{extra}")
|
||||
choices += [script_name]
|
||||
|
||||
selection = pmb.helpers.cli.ask("Which script?", None, "all",
|
||||
complete=choices)
|
||||
selection = pmb.helpers.cli.ask("Which script?", None, "all", complete=choices)
|
||||
if selection == "all":
|
||||
return scripts_available
|
||||
|
||||
|
@ -115,11 +115,11 @@ def ask_which_scripts_to_run(scripts_available):
|
|||
|
||||
|
||||
def copy_git_repo_to_chroot(topdir):
|
||||
""" Create a tarball of the git repo (including unstaged changes and new
|
||||
files) and extract it in chroot_native.
|
||||
|
||||
:param topdir: top directory of the git repository, get it with:
|
||||
pmb.helpers.git.get_topdir()
|
||||
"""Create a tarball of the git repo (including unstaged changes and new
|
||||
files) and extract it in chroot_native.
|
||||
|
||||
:param topdir: top directory of the git repository, get it with:
|
||||
pmb.helpers.git.get_topdir()
|
||||
"""
|
||||
chroot = Chroot.native()
|
||||
pmb.chroot.init(chroot)
|
||||
|
@ -131,27 +131,25 @@ def copy_git_repo_to_chroot(topdir):
|
|||
handle.write(file)
|
||||
handle.write("\n")
|
||||
|
||||
pmb.helpers.run.user(["tar", "-cf", tarball_path, "-T",
|
||||
f"{tarball_path}.files"], topdir)
|
||||
pmb.helpers.run.user(["tar", "-cf", tarball_path, "-T", f"{tarball_path}.files"], topdir)
|
||||
|
||||
ci_dir = Path("/home/pmos/ci")
|
||||
pmb.chroot.user(["rm", "-rf", ci_dir])
|
||||
pmb.chroot.user(["mkdir", ci_dir])
|
||||
pmb.chroot.user(["tar", "-xf", "/tmp/git.tar.gz"],
|
||||
working_dir=ci_dir)
|
||||
pmb.chroot.user(["tar", "-xf", "/tmp/git.tar.gz"], working_dir=ci_dir)
|
||||
|
||||
|
||||
def run_scripts(topdir, scripts):
|
||||
""" Run one of the given scripts after another, either natively or in a
|
||||
chroot. Display a progress message and stop on error (without printing
|
||||
a python stack trace).
|
||||
|
||||
:param topdir: top directory of the git repository, get it with:
|
||||
pmb.helpers.git.get_topdir()
|
||||
|
||||
:param scripts: return of get_ci_scripts()
|
||||
"""Run one of the given scripts after another, either natively or in a
|
||||
chroot. Display a progress message and stop on error (without printing
|
||||
a python stack trace).
|
||||
|
||||
"""
|
||||
:param topdir: top directory of the git repository, get it with:
|
||||
pmb.helpers.git.get_topdir()
|
||||
|
||||
:param scripts: return of get_ci_scripts()
|
||||
|
||||
"""
|
||||
steps = len(scripts)
|
||||
step = 0
|
||||
repo_copied = False
|
||||
|
@ -164,12 +162,10 @@ def run_scripts(topdir, scripts):
|
|||
where = "native"
|
||||
|
||||
script_path = f".ci/{script_name}.sh"
|
||||
logging.info(f"*** ({step}/{steps}) RUNNING CI SCRIPT: {script_path}"
|
||||
f" [{where}] ***")
|
||||
logging.info(f"*** ({step}/{steps}) RUNNING CI SCRIPT: {script_path}" f" [{where}] ***")
|
||||
|
||||
if "native" in script["options"]:
|
||||
rc = pmb.helpers.run.user([script_path], topdir,
|
||||
output="tui")
|
||||
rc = pmb.helpers.run.user([script_path], topdir, output="tui")
|
||||
continue
|
||||
else:
|
||||
# Run inside pmbootstrap chroot
|
||||
|
@ -178,9 +174,9 @@ def run_scripts(topdir, scripts):
|
|||
repo_copied = True
|
||||
|
||||
env = {"TESTUSER": "pmos"}
|
||||
rc = pmb.chroot.root([script_path], check=False, env=env,
|
||||
working_dir=Path("/home/pmos/ci"),
|
||||
output="tui")
|
||||
rc = pmb.chroot.root(
|
||||
[script_path], check=False, env=env, working_dir=Path("/home/pmos/ci"), output="tui"
|
||||
)
|
||||
if rc:
|
||||
logging.error(f"ERROR: CI script failed: {script_name}")
|
||||
exit(1)
|
||||
|
|
|
@ -52,6 +52,7 @@ unmigrated_commands = [
|
|||
"bootimg_analyze",
|
||||
]
|
||||
|
||||
|
||||
def run_command(args: PmbArgs):
|
||||
# Handle deprecated command format
|
||||
if args.action in unmigrated_commands:
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
# Copyright 2024 Caleb Connolly
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
class Command():
|
||||
|
||||
class Command:
|
||||
"""Base class for pmbootstrap commands."""
|
||||
|
||||
def run(self):
|
||||
|
|
|
@ -5,10 +5,10 @@ from __future__ import annotations
|
|||
from pmb import commands
|
||||
import pmb.build.other
|
||||
|
||||
|
||||
class Index(commands.Command):
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def run(self):
|
||||
pmb.build.other.index_repo()
|
||||
|
||||
|
|
|
@ -9,10 +9,11 @@ from pmb.helpers import run
|
|||
from pmb.core.context import get_context
|
||||
import pmb.config
|
||||
|
||||
|
||||
class Log(commands.Command):
|
||||
clear_log: bool
|
||||
lines: int
|
||||
|
||||
|
||||
def __init__(self, clear_log: bool, lines: int):
|
||||
self.clear_log = clear_log
|
||||
self.lines = lines
|
||||
|
@ -38,4 +39,3 @@ class Log(commands.Command):
|
|||
cmd += [context.log]
|
||||
|
||||
run.user(cmd, output="tui")
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ from pmb.core.context import get_context
|
|||
|
||||
from pmb import commands
|
||||
|
||||
|
||||
class RepoBootstrap(commands.Command):
|
||||
arch: Arch
|
||||
repo: str
|
||||
|
@ -33,13 +34,14 @@ class RepoBootstrap(commands.Command):
|
|||
return
|
||||
|
||||
if not cfg:
|
||||
raise ValueError("pmaports.cfg of current branch does not have any"
|
||||
" sections starting with 'repo:'")
|
||||
raise ValueError(
|
||||
"pmaports.cfg of current branch does not have any" " sections starting with 'repo:'"
|
||||
)
|
||||
|
||||
logging.info(f"Valid repositories: {', '.join(cfg.keys())}")
|
||||
raise ValueError(f"Couldn't find section 'repo:{self.repo}' in pmaports.cfg of"
|
||||
" current branch")
|
||||
|
||||
raise ValueError(
|
||||
f"Couldn't find section 'repo:{self.repo}' in pmaports.cfg of" " current branch"
|
||||
)
|
||||
|
||||
def __init__(self, arch: Optional[Arch], repository: str):
|
||||
context = get_context()
|
||||
|
@ -56,7 +58,6 @@ class RepoBootstrap(commands.Command):
|
|||
|
||||
self.check_repo_arg()
|
||||
|
||||
|
||||
def get_packages(self, bootstrap_line):
|
||||
ret = []
|
||||
for word in bootstrap_line.split(" "):
|
||||
|
@ -65,7 +66,6 @@ class RepoBootstrap(commands.Command):
|
|||
ret += [word]
|
||||
return ret
|
||||
|
||||
|
||||
def set_progress_total(self, steps):
|
||||
self.progress_total = 0
|
||||
|
||||
|
@ -80,14 +80,12 @@ class RepoBootstrap(commands.Command):
|
|||
if self.arch.cpu_emulation_required():
|
||||
self.progress_total += len(steps)
|
||||
|
||||
|
||||
def log_progress(self, msg):
|
||||
percent = int(100 * self.progress_done / self.progress_total)
|
||||
logging.info(f"*** {percent}% [{self.progress_step}] {msg} ***")
|
||||
|
||||
self.progress_done += 1
|
||||
|
||||
|
||||
def run_steps(self, steps):
|
||||
chroot: Chroot
|
||||
if self.arch.cpu_emulation_required():
|
||||
|
@ -116,17 +114,23 @@ class RepoBootstrap(commands.Command):
|
|||
pmb.chroot.init(chroot, usr_merge)
|
||||
|
||||
bootstrap_stage = int(step.split("bootstrap_", 1)[1])
|
||||
|
||||
def log_wrapper(pkg: BuildQueueItem):
|
||||
self.log_progress(f"building {pkg['name']}")
|
||||
|
||||
packages = self.get_packages(bootstrap_line)
|
||||
pmb.build.packages(self.context, packages, self.arch, force=True,
|
||||
strict=True, bootstrap_stage=bootstrap_stage,
|
||||
log_callback=log_wrapper)
|
||||
pmb.build.packages(
|
||||
self.context,
|
||||
packages,
|
||||
self.arch,
|
||||
force=True,
|
||||
strict=True,
|
||||
bootstrap_stage=bootstrap_stage,
|
||||
log_callback=log_wrapper,
|
||||
)
|
||||
|
||||
self.log_progress("bootstrap complete!")
|
||||
|
||||
|
||||
def check_existing_pkgs(self):
|
||||
channel = pmb.config.pmaports.read_config()["channel"]
|
||||
path = self.context.config.work / "packages" / channel / self.arch
|
||||
|
@ -134,15 +138,18 @@ class RepoBootstrap(commands.Command):
|
|||
if glob.glob(f"{path}/*"):
|
||||
logging.info(f"Packages path: {path}")
|
||||
|
||||
msg = f"Found previously built packages for {channel}/{self.arch}, run" \
|
||||
msg = (
|
||||
f"Found previously built packages for {channel}/{self.arch}, run"
|
||||
" 'pmbootstrap zap -p' first"
|
||||
)
|
||||
if self.arch.cpu_emulation_required():
|
||||
msg += " or remove the path manually (to keep cross compilers if" \
|
||||
msg += (
|
||||
" or remove the path manually (to keep cross compilers if"
|
||||
" you just built them)"
|
||||
)
|
||||
|
||||
raise RuntimeError(f"{msg}!")
|
||||
|
||||
|
||||
def get_steps(self):
|
||||
cfg = pmb.config.pmaports.read_config_repos()
|
||||
prev_step = 0
|
||||
|
@ -153,15 +160,16 @@ class RepoBootstrap(commands.Command):
|
|||
continue
|
||||
|
||||
step = int(key.split("bootstrap_", 1)[1])
|
||||
assert step == prev_step + 1, (f"{key}: wrong order of steps, expected"
|
||||
f" bootstrap_{prev_step + 1} (previous: bootstrap_{prev_step})")
|
||||
assert step == prev_step + 1, (
|
||||
f"{key}: wrong order of steps, expected"
|
||||
f" bootstrap_{prev_step + 1} (previous: bootstrap_{prev_step})"
|
||||
)
|
||||
prev_step = step
|
||||
|
||||
ret[key] = packages
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def run(self): # noqa: F821
|
||||
self.check_existing_pkgs()
|
||||
|
||||
|
|
|
@ -5,10 +5,10 @@ from __future__ import annotations
|
|||
from pmb import commands
|
||||
import pmb.chroot
|
||||
|
||||
|
||||
class Shutdown(commands.Command):
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def run(self):
|
||||
pmb.chroot.shutdown()
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ import time
|
|||
|
||||
"""Various internal test commands for performance testing and debugging."""
|
||||
|
||||
|
||||
def apkindex_parse_all():
|
||||
indexes = pmb.helpers.repo.apkindex_files(Arch.native())
|
||||
|
||||
|
@ -31,4 +32,3 @@ class Test(commands.Command):
|
|||
def run(self):
|
||||
if self.action == "apkindex_parse_all":
|
||||
apkindex_parse_all()
|
||||
|
||||
|
|
|
@ -20,23 +20,25 @@ from pmb.config.other import is_systemd_selected
|
|||
# Exported variables (internal configuration)
|
||||
#
|
||||
pmb_src: Path = Path(Path(__file__) / "../../..").resolve()
|
||||
apk_keys_path: Path = (pmb_src / "pmb/data/keys")
|
||||
apk_keys_path: Path = pmb_src / "pmb/data/keys"
|
||||
|
||||
# apk-tools minimum version
|
||||
# https://pkgs.alpinelinux.org/packages?name=apk-tools&branch=edge
|
||||
# Update this frequently to prevent a MITM attack with an outdated version
|
||||
# (which may contain a vulnerable apk/openssl, and allows an attacker to
|
||||
# exploit the system!)
|
||||
apk_tools_min_version = {"edge": "2.14.4-r0",
|
||||
"v3.20": "2.14.4-r0",
|
||||
"v3.19": "2.14.4-r0",
|
||||
"v3.18": "2.14.4-r0",
|
||||
"v3.17": "2.12.14-r0",
|
||||
"v3.16": "2.12.9-r3",
|
||||
"v3.15": "2.12.7-r3",
|
||||
"v3.14": "2.12.7-r0",
|
||||
"v3.13": "2.12.7-r0",
|
||||
"v3.12": "2.10.8-r1"}
|
||||
apk_tools_min_version = {
|
||||
"edge": "2.14.4-r0",
|
||||
"v3.20": "2.14.4-r0",
|
||||
"v3.19": "2.14.4-r0",
|
||||
"v3.18": "2.14.4-r0",
|
||||
"v3.17": "2.12.14-r0",
|
||||
"v3.16": "2.12.9-r3",
|
||||
"v3.15": "2.12.7-r3",
|
||||
"v3.14": "2.12.7-r0",
|
||||
"v3.13": "2.12.7-r0",
|
||||
"v3.12": "2.10.8-r1",
|
||||
}
|
||||
|
||||
# postmarketOS aports compatibility (checked against "version" in pmaports.cfg)
|
||||
pmaports_min_version = "7"
|
||||
|
@ -74,8 +76,9 @@ def sudo(cmd: Sequence[PathString]) -> Sequence[PathString]:
|
|||
|
||||
defaults: Dict[str, PathString] = {
|
||||
"cipher": "aes-xts-plain64",
|
||||
"config": Path((os.environ.get('XDG_CONFIG_HOME') or
|
||||
os.path.expanduser("~/.config")) + "/pmbootstrap.cfg"),
|
||||
"config": Path(
|
||||
(os.environ.get("XDG_CONFIG_HOME") or os.path.expanduser("~/.config")) + "/pmbootstrap.cfg"
|
||||
),
|
||||
# A higher value is typically desired, but this can lead to VERY long open
|
||||
# times on slower devices due to host systems being MUCH faster than the
|
||||
# target device (see issue #429).
|
||||
|
@ -84,19 +87,17 @@ defaults: Dict[str, PathString] = {
|
|||
|
||||
# Whether we're connected to a TTY (which allows things like e.g. printing
|
||||
# progress bars)
|
||||
is_interactive = sys.stdout.isatty() and \
|
||||
sys.stderr.isatty() and \
|
||||
sys.stdin.isatty()
|
||||
is_interactive = sys.stdout.isatty() and sys.stderr.isatty() and sys.stdin.isatty()
|
||||
|
||||
|
||||
# ANSI escape codes to highlight stdout
|
||||
styles = {
|
||||
"BLUE": '\033[94m',
|
||||
"BOLD": '\033[1m',
|
||||
"GREEN": '\033[92m',
|
||||
"RED": '\033[91m',
|
||||
"YELLOW": '\033[93m',
|
||||
"END": '\033[0m'
|
||||
"BLUE": "\033[94m",
|
||||
"BOLD": "\033[1m",
|
||||
"GREEN": "\033[92m",
|
||||
"RED": "\033[91m",
|
||||
"YELLOW": "\033[93m",
|
||||
"END": "\033[0m",
|
||||
}
|
||||
|
||||
if "NO_COLOR" in os.environ:
|
||||
|
@ -104,16 +105,17 @@ if "NO_COLOR" in os.environ:
|
|||
styles[style] = ""
|
||||
|
||||
# Supported filesystems and their fstools packages
|
||||
filesystems = {"btrfs": "btrfs-progs",
|
||||
"ext2": "e2fsprogs",
|
||||
"ext4": "e2fsprogs",
|
||||
"f2fs": "f2fs-tools",
|
||||
"fat16": "dosfstools",
|
||||
"fat32": "dosfstools"}
|
||||
filesystems = {
|
||||
"btrfs": "btrfs-progs",
|
||||
"ext2": "e2fsprogs",
|
||||
"ext4": "e2fsprogs",
|
||||
"f2fs": "f2fs-tools",
|
||||
"fat16": "dosfstools",
|
||||
"fat32": "dosfstools",
|
||||
}
|
||||
|
||||
# Legacy channels and their new names (pmb#2015)
|
||||
pmaports_channels_legacy = {"stable": "v20.05",
|
||||
"stable-next": "v21.03"}
|
||||
pmaports_channels_legacy = {"stable": "v20.05", "stable-next": "v21.03"}
|
||||
#
|
||||
# CHROOT
|
||||
#
|
||||
|
@ -124,14 +126,16 @@ pmaports_channels_legacy = {"stable": "v20.05",
|
|||
chroot_uid_user = "12345"
|
||||
|
||||
# The PATH variable used inside all chroots
|
||||
chroot_path = ":".join([
|
||||
"/usr/lib/ccache/bin",
|
||||
"/usr/local/sbin",
|
||||
"/usr/local/bin",
|
||||
"/usr/sbin:/usr/bin",
|
||||
"/sbin",
|
||||
"/bin"
|
||||
])
|
||||
chroot_path = ":".join(
|
||||
[
|
||||
"/usr/lib/ccache/bin",
|
||||
"/usr/local/sbin",
|
||||
"/usr/local/bin",
|
||||
"/usr/sbin:/usr/bin",
|
||||
"/sbin",
|
||||
"/bin",
|
||||
]
|
||||
)
|
||||
|
||||
# The PATH variable used on the host, to find the "chroot" and "sh"
|
||||
# executables. As pmbootstrap runs as user, not as root, the location
|
||||
|
@ -176,7 +180,7 @@ chroot_home_symlinks = {
|
|||
"/mnt/pmbootstrap/ccache": "/home/pmos/.ccache",
|
||||
"/mnt/pmbootstrap/go/gocache": "/home/pmos/.cache/go-build",
|
||||
"/mnt/pmbootstrap/go/gomodcache": "/home/pmos/go/pkg/mod",
|
||||
#"/mnt/pmbootstrap/packages": "/home/pmos/packages/pmos",
|
||||
# "/mnt/pmbootstrap/packages": "/home/pmos/packages/pmos",
|
||||
"/mnt/pmbootstrap/rust/git/db": "/home/pmos/.cargo/git/db",
|
||||
"/mnt/pmbootstrap/rust/registry/cache": "/home/pmos/.cargo/registry/cache",
|
||||
"/mnt/pmbootstrap/rust/registry/index": "/home/pmos/.cargo/registry/index",
|
||||
|
@ -269,11 +273,7 @@ kconfig_options = {
|
|||
"TZDEV": False,
|
||||
}
|
||||
},
|
||||
"<5.2.0": {
|
||||
"armhf armv7 x86": {
|
||||
"LBDAF": True
|
||||
}
|
||||
}
|
||||
"<5.2.0": {"armhf armv7 x86": {"LBDAF": True}},
|
||||
}
|
||||
|
||||
# Necessary waydroid kernel config options (android app support)
|
||||
|
@ -319,7 +319,7 @@ kconfig_options_waydroid = {
|
|||
"all": {
|
||||
"ASHMEM": True,
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
# Necessary iwd kernel config options (inet wireless daemon)
|
||||
|
@ -453,7 +453,7 @@ kconfig_options_containers = {
|
|||
"x86 x86_64": { # only for x86, x86_64 (and sparc64, ia64)
|
||||
"HUGETLB_PAGE": True,
|
||||
"CGROUP_HUGETLB": True, # Optional section
|
||||
}
|
||||
},
|
||||
},
|
||||
">=3.6 <6.1_rc1": { # option has been dropped
|
||||
"all": {
|
||||
|
@ -595,7 +595,7 @@ kconfig_options_community = {
|
|||
"UCLAMP_TASK_GROUP": True, # Scheduler hints
|
||||
"UHID": True, # e.g. Bluetooth input devices
|
||||
"USB_STORAGE": True, # USB mass storage devices
|
||||
"RT_GROUP_SCHED": False, # https://gitlab.com/postmarketOS/pmaports/-/issues/2652
|
||||
"RT_GROUP_SCHED": False, # https://gitlab.com/postmarketOS/pmaports/-/issues/2652
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -641,18 +641,15 @@ apkbuild_package_attributes = {
|
|||
"provider_priority": {"int": True},
|
||||
"install": {"array": True},
|
||||
"triggers": {"array": True},
|
||||
|
||||
# Packages can specify soft dependencies in "_pmb_recommends" to be
|
||||
# explicitly installed by default, and not implicitly as a hard dependency
|
||||
# of the package ("depends"). This makes these apps uninstallable, without
|
||||
# removing the meta-package. (#1933). To disable this feature, use:
|
||||
# "pmbootstrap install --no-recommends".
|
||||
"_pmb_recommends": {"array": True},
|
||||
|
||||
# UI meta-packages can specify groups to which the user must be added
|
||||
# to access specific hardware such as LED indicators.
|
||||
"_pmb_groups": {"array": True},
|
||||
|
||||
# postmarketos-base, UI and device packages can use _pmb_select to provide
|
||||
# additional configuration options in "pmbootstrap init" that allow
|
||||
# selecting alternative providers for a virtual APK package.
|
||||
|
@ -662,7 +659,6 @@ apkbuild_package_attributes = {
|
|||
# Variables in APKBUILD files that get parsed
|
||||
apkbuild_attributes = {
|
||||
**apkbuild_package_attributes,
|
||||
|
||||
"arch": {"array": True},
|
||||
"depends_dev": {"array": True},
|
||||
"makedepends": {"array": True},
|
||||
|
@ -675,35 +671,28 @@ apkbuild_attributes = {
|
|||
"sha512sums": {},
|
||||
"subpackages": {},
|
||||
"url": {},
|
||||
|
||||
# cross-compilers
|
||||
"makedepends_build": {"array": True},
|
||||
"makedepends_host": {"array": True},
|
||||
|
||||
# kernels
|
||||
"_flavor": {},
|
||||
"_device": {},
|
||||
"_kernver": {},
|
||||
"_outdir": {},
|
||||
"_config": {},
|
||||
|
||||
# linux-edge
|
||||
"_depends_dev": {"array": True},
|
||||
|
||||
# mesa
|
||||
"_llvmver": {},
|
||||
|
||||
# Overridden packages
|
||||
"_pkgver": {},
|
||||
"_pkgname": {},
|
||||
|
||||
# git commit
|
||||
"_commit": {},
|
||||
"source": {"array": True},
|
||||
|
||||
# gcc
|
||||
"_pkgbase": {},
|
||||
"_pkgsnap": {}
|
||||
"_pkgsnap": {},
|
||||
}
|
||||
|
||||
# Reference: https://postmarketos.org/apkbuild-options
|
||||
|
@ -736,7 +725,7 @@ deviceinfo_chassis_types = [
|
|||
"handset",
|
||||
"watch",
|
||||
"embedded",
|
||||
"vm"
|
||||
"vm",
|
||||
]
|
||||
|
||||
#
|
||||
|
@ -800,24 +789,26 @@ flashers: Dict[str, Dict[str, bool | List[str] | Dict[str, List[List[str]]]]] =
|
|||
"depends": [], # pmaports.cfg: supported_fastboot_depends
|
||||
"actions": {
|
||||
"list_devices": [["fastboot", "devices", "-l"]],
|
||||
"flash_rootfs": [["fastboot", "flash", "$PARTITION_ROOTFS",
|
||||
"$IMAGE"]],
|
||||
"flash_kernel": [["fastboot", "flash", "$PARTITION_KERNEL",
|
||||
"$BOOT/boot.img$FLAVOR"]],
|
||||
"flash_rootfs": [["fastboot", "flash", "$PARTITION_ROOTFS", "$IMAGE"]],
|
||||
"flash_kernel": [["fastboot", "flash", "$PARTITION_KERNEL", "$BOOT/boot.img$FLAVOR"]],
|
||||
"flash_vbmeta": [
|
||||
# Generate vbmeta image with "disable verification" flag
|
||||
["avbtool", "make_vbmeta_image", "--flags", "2",
|
||||
"--padding_size", "$FLASH_PAGESIZE",
|
||||
"--output", "/vbmeta.img"],
|
||||
[
|
||||
"avbtool",
|
||||
"make_vbmeta_image",
|
||||
"--flags",
|
||||
"2",
|
||||
"--padding_size",
|
||||
"$FLASH_PAGESIZE",
|
||||
"--output",
|
||||
"/vbmeta.img",
|
||||
],
|
||||
["fastboot", "flash", "$PARTITION_VBMETA", "/vbmeta.img"],
|
||||
["rm", "-f", "/vbmeta.img"]
|
||||
["rm", "-f", "/vbmeta.img"],
|
||||
],
|
||||
"flash_dtbo": [["fastboot", "flash", "$PARTITION_DTBO",
|
||||
"$BOOT/dtbo.img"]],
|
||||
"boot": [["fastboot", "--cmdline", "$KERNEL_CMDLINE",
|
||||
"boot", "$BOOT/boot.img$FLAVOR"]],
|
||||
"flash_lk2nd": [["fastboot", "flash", "$PARTITION_KERNEL",
|
||||
"$BOOT/lk2nd.img"]]
|
||||
"flash_dtbo": [["fastboot", "flash", "$PARTITION_DTBO", "$BOOT/dtbo.img"]],
|
||||
"boot": [["fastboot", "--cmdline", "$KERNEL_CMDLINE", "boot", "$BOOT/boot.img$FLAVOR"]],
|
||||
"flash_lk2nd": [["fastboot", "flash", "$PARTITION_KERNEL", "$BOOT/lk2nd.img"]],
|
||||
},
|
||||
},
|
||||
# Some devices provide Fastboot but using Android boot images is not
|
||||
|
@ -831,10 +822,8 @@ flashers: Dict[str, Dict[str, bool | List[str] | Dict[str, List[List[str]]]]] =
|
|||
"depends": ["android-tools"],
|
||||
"actions": {
|
||||
"list_devices": [["fastboot", "devices", "-l"]],
|
||||
"flash_rootfs": [["fastboot", "flash", "$PARTITION_ROOTFS",
|
||||
"$IMAGE_SPLIT_ROOT"]],
|
||||
"flash_kernel": [["fastboot", "flash", "$PARTITION_KERNEL",
|
||||
"$IMAGE_SPLIT_BOOT"]],
|
||||
"flash_rootfs": [["fastboot", "flash", "$PARTITION_ROOTFS", "$IMAGE_SPLIT_ROOT"]],
|
||||
"flash_kernel": [["fastboot", "flash", "$PARTITION_KERNEL", "$IMAGE_SPLIT_BOOT"]],
|
||||
# TODO: Add support for boot
|
||||
},
|
||||
},
|
||||
|
@ -849,11 +838,17 @@ flashers: Dict[str, Dict[str, bool | List[str] | Dict[str, List[List[str]]]]] =
|
|||
"list_devices": [["heimdall", "detect"]],
|
||||
"flash_rootfs": [
|
||||
["heimdall_wait_for_device.sh"],
|
||||
["heimdall", "flash", "--$PARTITION_ROOTFS", "$IMAGE"]],
|
||||
"flash_kernel": [["heimdall_flash_kernel.sh",
|
||||
"$BOOT/initramfs$FLAVOR", "$PARTITION_INITFS",
|
||||
"$BOOT/vmlinuz$FLAVOR$DTB",
|
||||
"$PARTITION_KERNEL"]]
|
||||
["heimdall", "flash", "--$PARTITION_ROOTFS", "$IMAGE"],
|
||||
],
|
||||
"flash_kernel": [
|
||||
[
|
||||
"heimdall_flash_kernel.sh",
|
||||
"$BOOT/initramfs$FLAVOR",
|
||||
"$PARTITION_INITFS",
|
||||
"$BOOT/vmlinuz$FLAVOR$DTB",
|
||||
"$PARTITION_KERNEL",
|
||||
]
|
||||
],
|
||||
},
|
||||
},
|
||||
# Some Samsung devices need a 'boot.img' file, just like the one generated
|
||||
|
@ -864,34 +859,63 @@ flashers: Dict[str, Dict[str, bool | List[str] | Dict[str, List[List[str]]]]] =
|
|||
"list_devices": [["heimdall", "detect"]],
|
||||
"flash_rootfs": [
|
||||
["heimdall_wait_for_device.sh"],
|
||||
["heimdall", "flash", "--$PARTITION_ROOTFS", "$IMAGE",
|
||||
"$NO_REBOOT", "$RESUME"]],
|
||||
["heimdall", "flash", "--$PARTITION_ROOTFS", "$IMAGE", "$NO_REBOOT", "$RESUME"],
|
||||
],
|
||||
"flash_kernel": [
|
||||
["heimdall_wait_for_device.sh"],
|
||||
["heimdall", "flash", "--$PARTITION_KERNEL",
|
||||
"$BOOT/boot.img$FLAVOR", "$NO_REBOOT", "$RESUME"]],
|
||||
[
|
||||
"heimdall",
|
||||
"flash",
|
||||
"--$PARTITION_KERNEL",
|
||||
"$BOOT/boot.img$FLAVOR",
|
||||
"$NO_REBOOT",
|
||||
"$RESUME",
|
||||
],
|
||||
],
|
||||
"flash_vbmeta": [
|
||||
["avbtool", "make_vbmeta_image", "--flags", "2",
|
||||
"--padding_size", "$FLASH_PAGESIZE",
|
||||
"--output", "/vbmeta.img"],
|
||||
["heimdall", "flash", "--$PARTITION_VBMETA", "/vbmeta.img",
|
||||
"$NO_REBOOT", "$RESUME"],
|
||||
["rm", "-f", "/vbmeta.img"]],
|
||||
[
|
||||
"avbtool",
|
||||
"make_vbmeta_image",
|
||||
"--flags",
|
||||
"2",
|
||||
"--padding_size",
|
||||
"$FLASH_PAGESIZE",
|
||||
"--output",
|
||||
"/vbmeta.img",
|
||||
],
|
||||
[
|
||||
"heimdall",
|
||||
"flash",
|
||||
"--$PARTITION_VBMETA",
|
||||
"/vbmeta.img",
|
||||
"$NO_REBOOT",
|
||||
"$RESUME",
|
||||
],
|
||||
["rm", "-f", "/vbmeta.img"],
|
||||
],
|
||||
"flash_lk2nd": [
|
||||
["heimdall_wait_for_device.sh"],
|
||||
["heimdall", "flash", "--$PARTITION_KERNEL", "$BOOT/lk2nd.img",
|
||||
"$NO_REBOOT", "$RESUME"]]
|
||||
[
|
||||
"heimdall",
|
||||
"flash",
|
||||
"--$PARTITION_KERNEL",
|
||||
"$BOOT/lk2nd.img",
|
||||
"$NO_REBOOT",
|
||||
"$RESUME",
|
||||
],
|
||||
],
|
||||
},
|
||||
},
|
||||
"adb": {
|
||||
"depends": ["android-tools"],
|
||||
"actions": {
|
||||
"list_devices": [["adb", "-P", "5038", "devices"]],
|
||||
"sideload": [["echo", "< wait for any device >"],
|
||||
["adb", "-P", "5038", "wait-for-usb-sideload"],
|
||||
["adb", "-P", "5038", "sideload",
|
||||
"$RECOVERY_ZIP"]],
|
||||
}
|
||||
"sideload": [
|
||||
["echo", "< wait for any device >"],
|
||||
["adb", "-P", "5038", "wait-for-usb-sideload"],
|
||||
["adb", "-P", "5038", "sideload", "$RECOVERY_ZIP"],
|
||||
],
|
||||
},
|
||||
},
|
||||
"uuu": {
|
||||
"depends": ["nxp-mfgtools-uuu"],
|
||||
|
@ -910,46 +934,51 @@ flashers: Dict[str, Dict[str, bool | List[str] | Dict[str, List[List[str]]]]] =
|
|||
"actions": {
|
||||
"list_devices": [["rkdeveloptool", "list"]],
|
||||
"flash_rootfs": [
|
||||
["rkdeveloptool", "write-partition", "$PARTITION_ROOTFS",
|
||||
"$IMAGE_SPLIT_ROOT"]
|
||||
["rkdeveloptool", "write-partition", "$PARTITION_ROOTFS", "$IMAGE_SPLIT_ROOT"]
|
||||
],
|
||||
"flash_kernel": [
|
||||
["rkdeveloptool", "write-partition", "$PARTITION_KERNEL",
|
||||
"$IMAGE_SPLIT_BOOT"]
|
||||
["rkdeveloptool", "write-partition", "$PARTITION_KERNEL", "$IMAGE_SPLIT_BOOT"]
|
||||
],
|
||||
},
|
||||
},
|
||||
"mtkclient": {
|
||||
"depends": ["mtkclient"],
|
||||
"actions": {
|
||||
"flash_rootfs": [["mtk", "w", "$PARTITION_ROOTFS",
|
||||
"$IMAGE"]],
|
||||
"flash_kernel": [["mtk", "w", "$PARTITION_KERNEL",
|
||||
"$BOOT/boot.img$FLAVOR"]],
|
||||
"flash_rootfs": [["mtk", "w", "$PARTITION_ROOTFS", "$IMAGE"]],
|
||||
"flash_kernel": [["mtk", "w", "$PARTITION_KERNEL", "$BOOT/boot.img$FLAVOR"]],
|
||||
"flash_vbmeta": [
|
||||
# Generate vbmeta image with "disable verification" flag
|
||||
["avbtool", "make_vbmeta_image", "--flags", "2",
|
||||
"--padding_size", "$FLASH_PAGESIZE",
|
||||
"--output", "/vbmeta.img"],
|
||||
[
|
||||
"avbtool",
|
||||
"make_vbmeta_image",
|
||||
"--flags",
|
||||
"2",
|
||||
"--padding_size",
|
||||
"$FLASH_PAGESIZE",
|
||||
"--output",
|
||||
"/vbmeta.img",
|
||||
],
|
||||
["mtk", "w", "$PARTITION_VBMETA", "/vbmeta.img"],
|
||||
["rm", "-f", "/vbmeta.img"]
|
||||
["rm", "-f", "/vbmeta.img"],
|
||||
],
|
||||
"flash_dtbo": [["mtk", "w", "$PARTITION_DTBO",
|
||||
"$BOOT/dtbo.img"]],
|
||||
"flash_lk2nd": [["mtk", "w", "$PARTITION_KERNEL",
|
||||
"$BOOT/lk2nd.img"]]
|
||||
}
|
||||
}
|
||||
"flash_dtbo": [["mtk", "w", "$PARTITION_DTBO", "$BOOT/dtbo.img"]],
|
||||
"flash_lk2nd": [["mtk", "w", "$PARTITION_KERNEL", "$BOOT/lk2nd.img"]],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
#
|
||||
# GIT
|
||||
#
|
||||
git_repos = {
|
||||
"aports_upstream": ["https://gitlab.alpinelinux.org/alpine/aports.git",
|
||||
"git@gitlab.alpinelinux.org:alpine/aports.git"],
|
||||
"pmaports": ["https://gitlab.com/postmarketOS/pmaports.git",
|
||||
"git@gitlab.com:postmarketos/pmaports.git"],
|
||||
"aports_upstream": [
|
||||
"https://gitlab.alpinelinux.org/alpine/aports.git",
|
||||
"git@gitlab.alpinelinux.org:alpine/aports.git",
|
||||
],
|
||||
"pmaports": [
|
||||
"https://gitlab.com/postmarketOS/pmaports.git",
|
||||
"git@gitlab.com:postmarketos/pmaports.git",
|
||||
],
|
||||
}
|
||||
|
||||
#
|
||||
|
@ -963,7 +992,7 @@ aportgen: Dict[str, AportGenEntry] = {
|
|||
"device/testing": {
|
||||
"prefixes": ["device", "linux"],
|
||||
"confirm_overwrite": True,
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
# Use a deterministic mirror URL instead of CDN for aportgen. Otherwise we may
|
||||
|
@ -982,8 +1011,7 @@ aportgen_mirror_alpine = "http://dl-4.alpinelinux.org/alpine/"
|
|||
newapkbuild_arguments_strings = [
|
||||
["-n", "pkgname", "set package name (only use with SRCURL)"],
|
||||
["-d", "pkgdesc", "set package description"],
|
||||
["-l", "license", "set package license identifier from"
|
||||
" <https://spdx.org/licenses/>"],
|
||||
["-l", "license", "set package license identifier from" " <https://spdx.org/licenses/>"],
|
||||
["-u", "url", "set package URL"],
|
||||
]
|
||||
newapkbuild_arguments_switches_pkgtypes = [
|
||||
|
@ -1005,8 +1033,16 @@ newapkbuild_arguments_switches_other = [
|
|||
#
|
||||
# Patterns of package names to ignore for automatic pmaport upgrading
|
||||
# ("pmbootstrap aportupgrade --all")
|
||||
upgrade_ignore = ["device-*", "firmware-*", "linux-*", "postmarketos-*",
|
||||
"*-aarch64", "*-armhf", "*-armv7", "*-riscv64"]
|
||||
upgrade_ignore = [
|
||||
"device-*",
|
||||
"firmware-*",
|
||||
"linux-*",
|
||||
"postmarketos-*",
|
||||
"*-aarch64",
|
||||
"*-armhf",
|
||||
"*-armv7",
|
||||
"*-riscv64",
|
||||
]
|
||||
|
||||
#
|
||||
# SIDELOAD
|
||||
|
|
|
@ -45,7 +45,9 @@ def load(path: Path) -> Config:
|
|||
setattr(config, key, Path(cfg["pmbootstrap"][key]))
|
||||
# Yeah this really sucks and there isn't a better way to do it without external
|
||||
# libraries
|
||||
elif isinstance(getattr(Config, key), List) and isinstance(getattr(Config, key)[0], PosixPath):
|
||||
elif isinstance(getattr(Config, key), List) and isinstance(
|
||||
getattr(Config, key)[0], PosixPath
|
||||
):
|
||||
value = cfg["pmbootstrap"][key]
|
||||
if not value:
|
||||
setattr(config, key, value)
|
||||
|
@ -67,7 +69,7 @@ def load(path: Path) -> Config:
|
|||
def serialize(config: Config, skip_defaults=True) -> configparser.ConfigParser:
|
||||
"""Serialize the config object into a ConfigParser to write it out
|
||||
in the pmbootstrap.cfg INI format.
|
||||
|
||||
|
||||
:param config: The config object to serialize
|
||||
:param skip_defaults: Skip writing out default values
|
||||
"""
|
||||
|
@ -95,7 +97,9 @@ def serialize(config: Config, skip_defaults=True) -> configparser.ConfigParser:
|
|||
# Convert strings to paths
|
||||
elif type(getattr(Config, key)) == PosixPath:
|
||||
cfg["pmbootstrap"][key] = str(getattr(config, key))
|
||||
elif isinstance(getattr(Config, key), List) and isinstance(getattr(Config, key)[0], PosixPath):
|
||||
elif isinstance(getattr(Config, key), List) and isinstance(
|
||||
getattr(Config, key)[0], PosixPath
|
||||
):
|
||||
cfg["pmbootstrap"][key] = ",".join(os.fspath(p) for p in getattr(config, key))
|
||||
elif isinstance(getattr(Config, key), bool):
|
||||
cfg["pmbootstrap"][key] = str(getattr(config, key))
|
||||
|
@ -104,17 +108,18 @@ def serialize(config: Config, skip_defaults=True) -> configparser.ConfigParser:
|
|||
|
||||
return cfg
|
||||
|
||||
|
||||
# FIXME: we should have distinct Config and ConfigFile types
|
||||
def save(output: Path, config: Config):
|
||||
"""Save the config object to the specified path.
|
||||
|
||||
|
||||
IMPORTANT: The global config (available via get_context().config)
|
||||
has invocation arguments merged into it. Do NOT call save() with
|
||||
the global config object."""
|
||||
logging.debug(f"Save config: {output}")
|
||||
output.parent.mkdir(parents=True, exist_ok=True)
|
||||
output.touch(0o700, exist_ok=True)
|
||||
|
||||
|
||||
cfg = serialize(config)
|
||||
|
||||
with output.open("w") as handle:
|
||||
|
|
|
@ -38,9 +38,11 @@ def require_programs():
|
|||
if not shutil.which(program):
|
||||
missing.append(program)
|
||||
if missing:
|
||||
raise RuntimeError("Can't find all programs required to run"
|
||||
" pmbootstrap. Please install first:"
|
||||
f" {', '.join(missing)}")
|
||||
raise RuntimeError(
|
||||
"Can't find all programs required to run"
|
||||
" pmbootstrap. Please install first:"
|
||||
f" {', '.join(missing)}"
|
||||
)
|
||||
|
||||
|
||||
def ask_for_username(args: PmbArgs, default_user: str):
|
||||
|
@ -49,12 +51,13 @@ def ask_for_username(args: PmbArgs, default_user: str):
|
|||
:returns: the username
|
||||
"""
|
||||
while True:
|
||||
ret = pmb.helpers.cli.ask("Username", None, default_user, False,
|
||||
"[a-z_][a-z0-9_-]*")
|
||||
ret = pmb.helpers.cli.ask("Username", None, default_user, False, "[a-z_][a-z0-9_-]*")
|
||||
if ret == "root":
|
||||
logging.fatal("ERROR: don't put \"root\" here. This is about"
|
||||
" creating an additional non-root user. Don't worry,"
|
||||
" the root user will also be created ;)")
|
||||
logging.fatal(
|
||||
'ERROR: don\'t put "root" here. This is about'
|
||||
" creating an additional non-root user. Don't worry,"
|
||||
" the root user will also be created ;)"
|
||||
)
|
||||
continue
|
||||
return ret
|
||||
|
||||
|
@ -67,22 +70,26 @@ def ask_for_work_path(args: PmbArgs):
|
|||
* exists: is False when the folder did not exist before we tested whether we can create it
|
||||
|
||||
"""
|
||||
logging.info("Location of the 'work' path. Multiple chroots"
|
||||
" (native, device arch, device rootfs) will be created"
|
||||
" in there.")
|
||||
logging.info(
|
||||
"Location of the 'work' path. Multiple chroots"
|
||||
" (native, device arch, device rootfs) will be created"
|
||||
" in there."
|
||||
)
|
||||
while True:
|
||||
try:
|
||||
work = os.path.expanduser(pmb.helpers.cli.ask(
|
||||
"Work path", None, get_context().config.work, False))
|
||||
work = os.path.expanduser(
|
||||
pmb.helpers.cli.ask("Work path", None, get_context().config.work, False)
|
||||
)
|
||||
work = os.path.realpath(work)
|
||||
exists = os.path.exists(work)
|
||||
|
||||
# Work must not be inside the pmbootstrap path
|
||||
if (work == pmb.config.pmb_src or
|
||||
work.startswith(f"{pmb.config.pmb_src}/")):
|
||||
logging.fatal("ERROR: The work path must not be inside the"
|
||||
" pmbootstrap path. Please specify another"
|
||||
" location.")
|
||||
if work == pmb.config.pmb_src or work.startswith(f"{pmb.config.pmb_src}/"):
|
||||
logging.fatal(
|
||||
"ERROR: The work path must not be inside the"
|
||||
" pmbootstrap path. Please specify another"
|
||||
" location."
|
||||
)
|
||||
continue
|
||||
|
||||
# Create the folder with a version file
|
||||
|
@ -102,8 +109,9 @@ def ask_for_work_path(args: PmbArgs):
|
|||
os.makedirs(f"{work}/cache_git", 0o700, True)
|
||||
return (work, exists)
|
||||
except OSError:
|
||||
logging.fatal("ERROR: Could not create this folder, or write"
|
||||
" inside it! Please try again.")
|
||||
logging.fatal(
|
||||
"ERROR: Could not create this folder, or write" " inside it! Please try again."
|
||||
)
|
||||
|
||||
|
||||
def ask_for_channel(config: Config):
|
||||
|
@ -136,12 +144,12 @@ def ask_for_channel(config: Config):
|
|||
|
||||
# Ask until user gives valid channel
|
||||
while True:
|
||||
ret = pmb.helpers.cli.ask("Channel", None, default,
|
||||
complete=choices)
|
||||
ret = pmb.helpers.cli.ask("Channel", None, default, complete=choices)
|
||||
if ret in choices:
|
||||
return ret
|
||||
logging.fatal("ERROR: Invalid channel specified, please type in one"
|
||||
" from the list above.")
|
||||
logging.fatal(
|
||||
"ERROR: Invalid channel specified, please type in one" " from the list above."
|
||||
)
|
||||
|
||||
|
||||
def ask_for_ui(deviceinfo):
|
||||
|
@ -151,9 +159,7 @@ def ask_for_ui(deviceinfo):
|
|||
if not device_is_accelerated:
|
||||
for i in reversed(range(len(ui_list))):
|
||||
pkgname = f"postmarketos-ui-{ui_list[i][0]}"
|
||||
apkbuild = pmb.helpers.pmaports.get(pkgname,
|
||||
subpackages=False,
|
||||
must_exist=False)
|
||||
apkbuild = pmb.helpers.pmaports.get(pkgname, subpackages=False, must_exist=False)
|
||||
if apkbuild and "pmb:gpu-accel" in apkbuild["options"]:
|
||||
ui_list.pop(i)
|
||||
hidden_ui_count += 1
|
||||
|
@ -169,21 +175,26 @@ def ask_for_ui(deviceinfo):
|
|||
logging.info(f"* {ui[0]}: {ui[1]}")
|
||||
ui_completion_list.append(ui[0])
|
||||
if hidden_ui_count > 0:
|
||||
logging.info(f"NOTE: {hidden_ui_count} UIs are hidden because"
|
||||
" \"deviceinfo_gpu_accelerated\" is not set (see"
|
||||
" https://postmarketos.org/deviceinfo).")
|
||||
logging.info(
|
||||
f"NOTE: {hidden_ui_count} UIs are hidden because"
|
||||
' "deviceinfo_gpu_accelerated" is not set (see'
|
||||
" https://postmarketos.org/deviceinfo)."
|
||||
)
|
||||
while True:
|
||||
ret = pmb.helpers.cli.ask("User interface", None, default, True,
|
||||
complete=ui_completion_list)
|
||||
ret = pmb.helpers.cli.ask(
|
||||
"User interface", None, default, True, complete=ui_completion_list
|
||||
)
|
||||
if ret in dict(ui_list).keys():
|
||||
return ret
|
||||
logging.fatal("ERROR: Invalid user interface specified, please type in"
|
||||
" one from the list above.")
|
||||
logging.fatal(
|
||||
"ERROR: Invalid user interface specified, please type in" " one from the list above."
|
||||
)
|
||||
|
||||
|
||||
def ask_for_ui_extras(config: Config, ui):
|
||||
apkbuild = pmb.helpers.pmaports.get(f"postmarketos-ui-{ui}",
|
||||
subpackages=False, must_exist=False)
|
||||
apkbuild = pmb.helpers.pmaports.get(
|
||||
f"postmarketos-ui-{ui}", subpackages=False, must_exist=False
|
||||
)
|
||||
if not apkbuild:
|
||||
return False
|
||||
|
||||
|
@ -191,11 +202,9 @@ def ask_for_ui_extras(config: Config, ui):
|
|||
if extra is None:
|
||||
return False
|
||||
|
||||
logging.info("This user interface has an extra package:"
|
||||
f" {extra['pkgdesc']}")
|
||||
logging.info("This user interface has an extra package:" f" {extra['pkgdesc']}")
|
||||
|
||||
return pmb.helpers.cli.confirm("Enable this package?",
|
||||
default=config.ui_extras)
|
||||
return pmb.helpers.cli.confirm("Enable this package?", default=config.ui_extras)
|
||||
|
||||
|
||||
def ask_for_systemd(config: Config, ui):
|
||||
|
@ -203,40 +212,42 @@ def ask_for_systemd(config: Config, ui):
|
|||
return config.systemd
|
||||
|
||||
if pmb.helpers.ui.check_option(ui, "pmb:systemd-never"):
|
||||
logging.info("Based on your UI selection, OpenRC will be used as init"
|
||||
" system. This UI does not support systemd.")
|
||||
logging.info(
|
||||
"Based on your UI selection, OpenRC will be used as init"
|
||||
" system. This UI does not support systemd."
|
||||
)
|
||||
return config.systemd
|
||||
|
||||
default_is_systemd = pmb.helpers.ui.check_option(ui, "pmb:systemd")
|
||||
not_str = " " if default_is_systemd else " not "
|
||||
logging.info("Based on your UI selection, 'default' will result"
|
||||
f" in{not_str}installing systemd.")
|
||||
logging.info(
|
||||
"Based on your UI selection, 'default' will result" f" in{not_str}installing systemd."
|
||||
)
|
||||
|
||||
choices = SystemdConfig.choices()
|
||||
answer = pmb.helpers.cli.ask("Install systemd?",
|
||||
choices,
|
||||
config.systemd,
|
||||
validation_regex=f"^({'|'.join(choices)})$",
|
||||
complete=choices)
|
||||
answer = pmb.helpers.cli.ask(
|
||||
"Install systemd?",
|
||||
choices,
|
||||
config.systemd,
|
||||
validation_regex=f"^({'|'.join(choices)})$",
|
||||
complete=choices,
|
||||
)
|
||||
return answer
|
||||
|
||||
|
||||
def ask_for_keymaps(args: PmbArgs, deviceinfo: Deviceinfo):
|
||||
if not deviceinfo.keymaps or deviceinfo.keymaps.strip() == "":
|
||||
return ""
|
||||
options = deviceinfo.keymaps.split(' ')
|
||||
logging.info(f"Available keymaps for device ({len(options)}): "
|
||||
f"{', '.join(options)}")
|
||||
options = deviceinfo.keymaps.split(" ")
|
||||
logging.info(f"Available keymaps for device ({len(options)}): " f"{', '.join(options)}")
|
||||
if args.keymap == "":
|
||||
args.keymap = options[0]
|
||||
|
||||
while True:
|
||||
ret = pmb.helpers.cli.ask("Keymap", None, args.keymap,
|
||||
True, complete=options)
|
||||
ret = pmb.helpers.cli.ask("Keymap", None, args.keymap, True, complete=options)
|
||||
if ret in options:
|
||||
return ret
|
||||
logging.fatal("ERROR: Invalid keymap specified, please type in"
|
||||
" one from the list above.")
|
||||
logging.fatal("ERROR: Invalid keymap specified, please type in" " one from the list above.")
|
||||
|
||||
|
||||
def ask_for_timezone():
|
||||
|
@ -256,11 +267,9 @@ def ask_for_timezone():
|
|||
pass
|
||||
if tz:
|
||||
logging.info(f"Your host timezone: {tz}")
|
||||
if pmb.helpers.cli.confirm("Use this timezone instead of GMT?",
|
||||
default="y"):
|
||||
if pmb.helpers.cli.confirm("Use this timezone instead of GMT?", default="y"):
|
||||
return tz
|
||||
logging.info("WARNING: Unable to determine timezone configuration on host,"
|
||||
" using GMT.")
|
||||
logging.info("WARNING: Unable to determine timezone configuration on host," " using GMT.")
|
||||
return "GMT"
|
||||
|
||||
|
||||
|
@ -277,13 +286,13 @@ def ask_for_provider_select(apkbuild, providers_cfg):
|
|||
|
||||
has_default = False
|
||||
providers_short = {}
|
||||
last_selected = providers_cfg.get(select, 'default')
|
||||
last_selected = providers_cfg.get(select, "default")
|
||||
|
||||
for pkgname, pkg in providers:
|
||||
# Strip provider prefix if possible
|
||||
short = pkgname
|
||||
if short.startswith(f'{select}-'):
|
||||
short = short[len(f"{select}-"):]
|
||||
if short.startswith(f"{select}-"):
|
||||
short = short[len(f"{select}-") :]
|
||||
|
||||
# Allow selecting the package using both short and long name
|
||||
providers_short[pkgname] = pkgname
|
||||
|
@ -292,20 +301,22 @@ def ask_for_provider_select(apkbuild, providers_cfg):
|
|||
if pkgname == last_selected:
|
||||
last_selected = short
|
||||
|
||||
if not has_default and pkg.get('provider_priority', 0) != 0:
|
||||
if not has_default and pkg.get("provider_priority", 0) != 0:
|
||||
# Display as default provider
|
||||
styles = pmb.config.styles
|
||||
logging.info(f"* {short}: {pkg['pkgdesc']} "
|
||||
f"{styles['BOLD']}(default){styles['END']}")
|
||||
logging.info(
|
||||
f"* {short}: {pkg['pkgdesc']} " f"{styles['BOLD']}(default){styles['END']}"
|
||||
)
|
||||
has_default = True
|
||||
else:
|
||||
logging.info(f"* {short}: {pkg['pkgdesc']}")
|
||||
|
||||
while True:
|
||||
ret = pmb.helpers.cli.ask("Provider", None, last_selected, True,
|
||||
complete=providers_short.keys())
|
||||
ret = pmb.helpers.cli.ask(
|
||||
"Provider", None, last_selected, True, complete=providers_short.keys()
|
||||
)
|
||||
|
||||
if has_default and ret == 'default':
|
||||
if has_default and ret == "default":
|
||||
# Selecting default means to not select any provider explicitly
|
||||
# In other words, apk chooses it automatically based on
|
||||
# "provider_priority"
|
||||
|
@ -315,8 +326,9 @@ def ask_for_provider_select(apkbuild, providers_cfg):
|
|||
if ret in providers_short:
|
||||
providers_cfg[select] = providers_short[ret]
|
||||
break
|
||||
logging.fatal("ERROR: Invalid provider specified, please type in"
|
||||
" one from the list above.")
|
||||
logging.fatal(
|
||||
"ERROR: Invalid provider specified, please type in" " one from the list above."
|
||||
)
|
||||
|
||||
|
||||
def ask_for_provider_select_pkg(pkgname, providers_cfg):
|
||||
|
@ -327,8 +339,7 @@ def ask_for_provider_select_pkg(pkgname, providers_cfg):
|
|||
:param providers_cfg: the configuration section with previously selected
|
||||
providers. Updated with new providers after selection
|
||||
"""
|
||||
apkbuild = pmb.helpers.pmaports.get(pkgname,
|
||||
subpackages=False, must_exist=False)
|
||||
apkbuild = pmb.helpers.pmaports.get(pkgname, subpackages=False, must_exist=False)
|
||||
if not apkbuild:
|
||||
return
|
||||
|
||||
|
@ -358,24 +369,23 @@ def ask_for_device_kernel(config: Config, device: str):
|
|||
# Ask for kernel (extra message when downstream and upstream are available)
|
||||
logging.info("Which kernel do you want to use with your device?")
|
||||
if "downstream" in kernels:
|
||||
logging.info("Downstream kernels are typically the outdated Android"
|
||||
" kernel forks.")
|
||||
logging.info("Downstream kernels are typically the outdated Android" " kernel forks.")
|
||||
if "downstream" in kernels and len(kernels) > 1:
|
||||
logging.info("Upstream kernels (mainline, stable, ...) get security"
|
||||
" updates, but may have less working features than"
|
||||
" downstream kernels.")
|
||||
logging.info(
|
||||
"Upstream kernels (mainline, stable, ...) get security"
|
||||
" updates, but may have less working features than"
|
||||
" downstream kernels."
|
||||
)
|
||||
|
||||
# List kernels
|
||||
logging.info(f"Available kernels ({len(kernels)}):")
|
||||
for type in sorted(kernels.keys()):
|
||||
logging.info(f"* {type}: {kernels[type]}")
|
||||
while True:
|
||||
ret = pmb.helpers.cli.ask("Kernel", None, default, True,
|
||||
complete=kernels)
|
||||
ret = pmb.helpers.cli.ask("Kernel", None, default, True, complete=kernels)
|
||||
if ret in kernels.keys():
|
||||
return ret
|
||||
logging.fatal("ERROR: Invalid kernel specified, please type in one"
|
||||
" from the list above.")
|
||||
logging.fatal("ERROR: Invalid kernel specified, please type in one" " from the list above.")
|
||||
return ret
|
||||
|
||||
|
||||
|
@ -389,8 +399,9 @@ def ask_for_device(context: Context):
|
|||
* kernel: type of kernel (downstream, etc)
|
||||
"""
|
||||
vendors = sorted(pmb.helpers.devices.list_vendors())
|
||||
logging.info("Choose your target device vendor (either an "
|
||||
"existing one, or a new one for porting).")
|
||||
logging.info(
|
||||
"Choose your target device vendor (either an " "existing one, or a new one for porting)."
|
||||
)
|
||||
logging.info(f"Available vendors ({len(vendors)}): {', '.join(vendors)}")
|
||||
|
||||
current_vendor = None
|
||||
|
@ -400,42 +411,41 @@ def ask_for_device(context: Context):
|
|||
current_codename = context.config.device.split("-", 1)[1]
|
||||
|
||||
while True:
|
||||
vendor = pmb.helpers.cli.ask("Vendor", None, current_vendor,
|
||||
False, r"[a-z0-9]+", vendors)
|
||||
vendor = pmb.helpers.cli.ask("Vendor", None, current_vendor, False, r"[a-z0-9]+", vendors)
|
||||
|
||||
new_vendor = vendor not in vendors
|
||||
codenames = []
|
||||
if new_vendor:
|
||||
logging.info("The specified vendor ({}) could not be found in"
|
||||
" existing ports, do you want to start a new"
|
||||
" port?".format(vendor))
|
||||
logging.info(
|
||||
"The specified vendor ({}) could not be found in"
|
||||
" existing ports, do you want to start a new"
|
||||
" port?".format(vendor)
|
||||
)
|
||||
if not pmb.helpers.cli.confirm(default=True):
|
||||
continue
|
||||
else:
|
||||
# Archived devices can be selected, but are not displayed
|
||||
devices = sorted(pmb.helpers.devices.list_codenames(
|
||||
vendor, archived=False))
|
||||
devices = sorted(pmb.helpers.devices.list_codenames(vendor, archived=False))
|
||||
# Remove "vendor-" prefixes from device list
|
||||
codenames = [x.split('-', 1)[1] for x in devices]
|
||||
logging.info(f"Available codenames ({len(codenames)}): " +
|
||||
", ".join(codenames))
|
||||
codenames = [x.split("-", 1)[1] for x in devices]
|
||||
logging.info(f"Available codenames ({len(codenames)}): " + ", ".join(codenames))
|
||||
|
||||
if current_vendor != vendor:
|
||||
current_codename = ''
|
||||
codename = pmb.helpers.cli.ask("Device codename", None,
|
||||
current_codename, False, r"[a-z0-9]+",
|
||||
codenames)
|
||||
current_codename = ""
|
||||
codename = pmb.helpers.cli.ask(
|
||||
"Device codename", None, current_codename, False, r"[a-z0-9]+", codenames
|
||||
)
|
||||
|
||||
device = f"{vendor}-{codename}"
|
||||
device_path = pmb.helpers.devices.find_path(device, 'deviceinfo')
|
||||
device_path = pmb.helpers.devices.find_path(device, "deviceinfo")
|
||||
if device_path is None:
|
||||
if device == context.device:
|
||||
raise RuntimeError(
|
||||
"This device does not exist anymore, check"
|
||||
" <https://postmarketos.org/renamed>"
|
||||
" to see if it was renamed")
|
||||
logging.info("You are about to do"
|
||||
f" a new device port for '{device}'.")
|
||||
" to see if it was renamed"
|
||||
)
|
||||
logging.info("You are about to do" f" a new device port for '{device}'.")
|
||||
if not pmb.helpers.cli.confirm(default=True):
|
||||
current_vendor = vendor
|
||||
continue
|
||||
|
@ -459,70 +469,77 @@ def ask_for_device(context: Context):
|
|||
def ask_for_additional_options(config):
|
||||
context = pmb.core.context.get_context()
|
||||
# Allow to skip additional options
|
||||
logging.info("Additional options:"
|
||||
f" extra free space: {config.extra_space} MB,"
|
||||
f" boot partition size: {config.boot_size} MB,"
|
||||
f" parallel jobs: {config.jobs},"
|
||||
f" ccache per arch: {config.ccache_size},"
|
||||
f" sudo timer: {context.sudo_timer},"
|
||||
f" mirror: {config.mirrors["pmaports"]}")
|
||||
logging.info(
|
||||
"Additional options:"
|
||||
f" extra free space: {config.extra_space} MB,"
|
||||
f" boot partition size: {config.boot_size} MB,"
|
||||
f" parallel jobs: {config.jobs},"
|
||||
f" ccache per arch: {config.ccache_size},"
|
||||
f" sudo timer: {context.sudo_timer},"
|
||||
f" mirror: {config.mirrors["pmaports"]}"
|
||||
)
|
||||
|
||||
if not pmb.helpers.cli.confirm("Change them?",
|
||||
default=False):
|
||||
if not pmb.helpers.cli.confirm("Change them?", default=False):
|
||||
return
|
||||
|
||||
# Extra space
|
||||
logging.info("Set extra free space to 0, unless you ran into a 'No space"
|
||||
" left on device' error. In that case, the size of the"
|
||||
" rootfs could not be calculated properly on your machine,"
|
||||
" and we need to add extra free space to make the image big"
|
||||
" enough to fit the rootfs (pmbootstrap#1904)."
|
||||
" How much extra free space do you want to add to the image"
|
||||
" (in MB)?")
|
||||
answer = pmb.helpers.cli.ask("Extra space size", None,
|
||||
config.extra_space, validation_regex="^[0-9]+$")
|
||||
logging.info(
|
||||
"Set extra free space to 0, unless you ran into a 'No space"
|
||||
" left on device' error. In that case, the size of the"
|
||||
" rootfs could not be calculated properly on your machine,"
|
||||
" and we need to add extra free space to make the image big"
|
||||
" enough to fit the rootfs (pmbootstrap#1904)."
|
||||
" How much extra free space do you want to add to the image"
|
||||
" (in MB)?"
|
||||
)
|
||||
answer = pmb.helpers.cli.ask(
|
||||
"Extra space size", None, config.extra_space, validation_regex="^[0-9]+$"
|
||||
)
|
||||
config.extra_space = answer
|
||||
|
||||
# Boot size
|
||||
logging.info("What should be the boot partition size (in MB)?")
|
||||
answer = pmb.helpers.cli.ask("Boot size", None, config.boot_size,
|
||||
validation_regex="^[1-9][0-9]*$")
|
||||
answer = pmb.helpers.cli.ask(
|
||||
"Boot size", None, config.boot_size, validation_regex="^[1-9][0-9]*$"
|
||||
)
|
||||
config.boot_size = int(answer)
|
||||
|
||||
# Parallel job count
|
||||
logging.info("How many jobs should run parallel on this machine, when"
|
||||
" compiling?")
|
||||
answer = pmb.helpers.cli.ask("Jobs", None, config.jobs,
|
||||
validation_regex="^[1-9][0-9]*$")
|
||||
logging.info("How many jobs should run parallel on this machine, when" " compiling?")
|
||||
answer = pmb.helpers.cli.ask("Jobs", None, config.jobs, validation_regex="^[1-9][0-9]*$")
|
||||
config.jobs = int(answer)
|
||||
|
||||
# Ccache size
|
||||
logging.info("We use ccache to speed up building the same code multiple"
|
||||
" times. How much space should the ccache folder take up per"
|
||||
" architecture? After init is through, you can check the"
|
||||
" current usage with 'pmbootstrap stats'. Answer with 0 for"
|
||||
" infinite.")
|
||||
logging.info(
|
||||
"We use ccache to speed up building the same code multiple"
|
||||
" times. How much space should the ccache folder take up per"
|
||||
" architecture? After init is through, you can check the"
|
||||
" current usage with 'pmbootstrap stats'. Answer with 0 for"
|
||||
" infinite."
|
||||
)
|
||||
regex = "0|[0-9]+(k|M|G|T|Ki|Mi|Gi|Ti)"
|
||||
answer = pmb.helpers.cli.ask("Ccache size", None, config.ccache_size,
|
||||
lowercase_answer=False,
|
||||
validation_regex=regex)
|
||||
answer = pmb.helpers.cli.ask(
|
||||
"Ccache size", None, config.ccache_size, lowercase_answer=False, validation_regex=regex
|
||||
)
|
||||
config.ccache_size = answer
|
||||
|
||||
# Sudo timer
|
||||
logging.info("pmbootstrap does everything in Alpine Linux chroots, so"
|
||||
" your host system does not get modified. In order to"
|
||||
" work with these chroots, pmbootstrap calls 'sudo'"
|
||||
" internally. For long running operations, it is possible"
|
||||
" that you'll have to authorize sudo more than once.")
|
||||
answer = pmb.helpers.cli.confirm("Enable background timer to prevent"
|
||||
" repeated sudo authorization?",
|
||||
default=context.sudo_timer)
|
||||
logging.info(
|
||||
"pmbootstrap does everything in Alpine Linux chroots, so"
|
||||
" your host system does not get modified. In order to"
|
||||
" work with these chroots, pmbootstrap calls 'sudo'"
|
||||
" internally. For long running operations, it is possible"
|
||||
" that you'll have to authorize sudo more than once."
|
||||
)
|
||||
answer = pmb.helpers.cli.confirm(
|
||||
"Enable background timer to prevent" " repeated sudo authorization?",
|
||||
default=context.sudo_timer,
|
||||
)
|
||||
config.sudo_timer = str(answer)
|
||||
|
||||
# Mirrors
|
||||
# prompt for mirror change
|
||||
logging.info("Selected mirror:"
|
||||
f" {','.join(context.config.mirrors_postmarketos)}")
|
||||
logging.info("Selected mirror:" f" {','.join(context.config.mirrors_postmarketos)}")
|
||||
if pmb.helpers.cli.confirm("Change mirror?", default=False):
|
||||
mirror = ask_for_mirror()
|
||||
config.mirrors["pmaports"] = mirror
|
||||
|
@ -532,8 +549,8 @@ def ask_for_mirror():
|
|||
regex = "^[1-9][0-9]*$" # single non-zero number only
|
||||
|
||||
json_path = pmb.helpers.http.download(
|
||||
"https://postmarketos.org/mirrors.json", "pmos_mirrors",
|
||||
cache=False)
|
||||
"https://postmarketos.org/mirrors.json", "pmos_mirrors", cache=False
|
||||
)
|
||||
with open(json_path, "rt") as handle:
|
||||
s = handle.read()
|
||||
|
||||
|
@ -572,9 +589,9 @@ def ask_for_mirror():
|
|||
mirror = ""
|
||||
# require one valid mirror index selected by user
|
||||
while len(mirror) == 0:
|
||||
answer = pmb.helpers.cli.ask("Select a mirror", None,
|
||||
",".join(mirror_indexes),
|
||||
validation_regex=regex)
|
||||
answer = pmb.helpers.cli.ask(
|
||||
"Select a mirror", None, ",".join(mirror_indexes), validation_regex=regex
|
||||
)
|
||||
i = int(answer)
|
||||
if i < 1 or i > len(urls):
|
||||
logging.info("You must select one valid mirror!")
|
||||
|
@ -585,8 +602,9 @@ def ask_for_mirror():
|
|||
|
||||
def ask_for_hostname(default: Optional[str], device):
|
||||
while True:
|
||||
ret = pmb.helpers.cli.ask("Device hostname (short form, e.g. 'foo')",
|
||||
None, (default or device), True)
|
||||
ret = pmb.helpers.cli.ask(
|
||||
"Device hostname (short form, e.g. 'foo')", None, (default or device), True
|
||||
)
|
||||
if not pmb.helpers.other.validate_hostname(ret):
|
||||
continue
|
||||
# Don't store device name in user's config (gets replaced in install)
|
||||
|
@ -598,18 +616,20 @@ def ask_for_hostname(default: Optional[str], device):
|
|||
def ask_for_ssh_keys(default: bool) -> bool:
|
||||
if not len(glob.glob(os.path.expanduser("~/.ssh/id_*.pub"))):
|
||||
return False
|
||||
return pmb.helpers.cli.confirm("Would you like to copy your SSH public"
|
||||
" keys to the device?",
|
||||
default=default)
|
||||
return pmb.helpers.cli.confirm(
|
||||
"Would you like to copy your SSH public" " keys to the device?", default=default
|
||||
)
|
||||
|
||||
|
||||
def ask_build_pkgs_on_install(default: bool) -> bool:
|
||||
logging.info("After pmaports are changed, the binary packages may be"
|
||||
" outdated. If you want to install postmarketOS without"
|
||||
" changes, reply 'n' for a faster installation.")
|
||||
return pmb.helpers.cli.confirm("Build outdated packages during"
|
||||
" 'pmbootstrap install'?",
|
||||
default=default)
|
||||
logging.info(
|
||||
"After pmaports are changed, the binary packages may be"
|
||||
" outdated. If you want to install postmarketOS without"
|
||||
" changes, reply 'n' for a faster installation."
|
||||
)
|
||||
return pmb.helpers.cli.confirm(
|
||||
"Build outdated packages during" " 'pmbootstrap install'?", default=default
|
||||
)
|
||||
|
||||
|
||||
def get_locales():
|
||||
|
@ -623,20 +643,23 @@ def get_locales():
|
|||
|
||||
def ask_for_locale(current_locale: str):
|
||||
locales = get_locales()
|
||||
logging.info("Choose your preferred locale, like e.g. en_US. Only UTF-8"
|
||||
" is supported, it gets appended automatically. Use"
|
||||
" tab-completion if needed.")
|
||||
logging.info(
|
||||
"Choose your preferred locale, like e.g. en_US. Only UTF-8"
|
||||
" is supported, it gets appended automatically. Use"
|
||||
" tab-completion if needed."
|
||||
)
|
||||
|
||||
while True:
|
||||
ret = pmb.helpers.cli.ask("Locale",
|
||||
choices=None,
|
||||
default=current_locale.replace(".UTF-8", ""),
|
||||
lowercase_answer=False,
|
||||
complete=locales)
|
||||
ret = pmb.helpers.cli.ask(
|
||||
"Locale",
|
||||
choices=None,
|
||||
default=current_locale.replace(".UTF-8", ""),
|
||||
lowercase_answer=False,
|
||||
complete=locales,
|
||||
)
|
||||
ret = ret.replace(".UTF-8", "")
|
||||
if ret not in locales:
|
||||
logging.info("WARNING: this locale is not in the list of known"
|
||||
" valid locales.")
|
||||
logging.info("WARNING: this locale is not in the list of known" " valid locales.")
|
||||
if pmb.helpers.cli.ask() != "y":
|
||||
# Ask again
|
||||
continue
|
||||
|
@ -652,7 +675,7 @@ def frontend(args: PmbArgs):
|
|||
config.work, work_exists = ask_for_work_path(args)
|
||||
|
||||
# Update args and save config (so chroots and 'pmbootstrap log' work)
|
||||
#pmb.helpers.args.update_work(args, config.work)
|
||||
# pmb.helpers.args.update_work(args, config.work)
|
||||
pmb.config.save(args.config, config)
|
||||
|
||||
# Migrate work dir if necessary
|
||||
|
@ -670,8 +693,7 @@ def frontend(args: PmbArgs):
|
|||
# Copy the git hooks if master was checked out. (Don't symlink them and
|
||||
# only do it on master, so the git hooks don't change unexpectedly when
|
||||
# having a random branch checked out.)
|
||||
branch_current = pmb.helpers.git.rev_parse(pkgrepo_default_path(),
|
||||
extra_args=["--abbrev-ref"])
|
||||
branch_current = pmb.helpers.git.rev_parse(pkgrepo_default_path(), extra_args=["--abbrev-ref"])
|
||||
if branch_current == "master":
|
||||
logging.info("NOTE: pmaports is on master branch, copying git hooks.")
|
||||
pmb.config.pmaports.install_githooks()
|
||||
|
@ -682,7 +704,7 @@ def frontend(args: PmbArgs):
|
|||
config.kernel = kernel
|
||||
|
||||
deviceinfo = pmb.parse.deviceinfo(device)
|
||||
apkbuild_path = pmb.helpers.devices.find_path(device, 'APKBUILD')
|
||||
apkbuild_path = pmb.helpers.devices.find_path(device, "APKBUILD")
|
||||
if apkbuild_path:
|
||||
apkbuild = pmb.parse.apkbuild(apkbuild_path)
|
||||
ask_for_provider_select(apkbuild, config.providers)
|
||||
|
@ -703,17 +725,18 @@ def frontend(args: PmbArgs):
|
|||
# systemd
|
||||
config.systemd = ask_for_systemd(config, ui)
|
||||
|
||||
ask_for_provider_select_pkg(f"postmarketos-ui-{ui}",
|
||||
config.providers)
|
||||
ask_for_provider_select_pkg(f"postmarketos-ui-{ui}", config.providers)
|
||||
ask_for_additional_options(config)
|
||||
|
||||
# Extra packages to be installed to rootfs
|
||||
logging.info("Additional packages that will be installed to rootfs."
|
||||
" Specify them in a comma separated list (e.g.: vim,file)"
|
||||
" or \"none\"")
|
||||
extra = pmb.helpers.cli.ask("Extra packages", None,
|
||||
config.extra_packages,
|
||||
validation_regex=r"^([-.+\w]+)(,[-.+\w]+)*$")
|
||||
logging.info(
|
||||
"Additional packages that will be installed to rootfs."
|
||||
" Specify them in a comma separated list (e.g.: vim,file)"
|
||||
' or "none"'
|
||||
)
|
||||
extra = pmb.helpers.cli.ask(
|
||||
"Extra packages", None, config.extra_packages, validation_regex=r"^([-.+\w]+)(,[-.+\w]+)*$"
|
||||
)
|
||||
config.extra_packages = extra
|
||||
|
||||
# Configure timezone info
|
||||
|
@ -738,11 +761,12 @@ def frontend(args: PmbArgs):
|
|||
pmb.config.save(args.config, config)
|
||||
|
||||
# Zap existing chroots
|
||||
if (work_exists and device_exists and
|
||||
len(list(Chroot.iter_patterns())) and
|
||||
pmb.helpers.cli.confirm(
|
||||
"Zap existing chroots to apply configuration?",
|
||||
default=True)):
|
||||
if (
|
||||
work_exists
|
||||
and device_exists
|
||||
and len(list(Chroot.iter_patterns()))
|
||||
and pmb.helpers.cli.confirm("Zap existing chroots to apply configuration?", default=True)
|
||||
):
|
||||
setattr(args, "deviceinfo", deviceinfo)
|
||||
|
||||
# Do not zap any existing packages or cache_http directories
|
||||
|
|
|
@ -16,8 +16,9 @@ import pmb.parse.version
|
|||
|
||||
|
||||
def clone():
|
||||
logging.info("Setting up the native chroot and cloning the package build"
|
||||
" recipes (pmaports)...")
|
||||
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")
|
||||
|
@ -30,8 +31,14 @@ def check_version_pmaports(real):
|
|||
return
|
||||
|
||||
# Outated error
|
||||
logging.info("NOTE: your pmaports folder has version " + real + ", but" +
|
||||
" version " + min + " is required.")
|
||||
logging.info(
|
||||
"NOTE: your pmaports folder has version "
|
||||
+ real
|
||||
+ ", but"
|
||||
+ " version "
|
||||
+ min
|
||||
+ " is required."
|
||||
)
|
||||
raise RuntimeError("Run 'pmbootstrap pull' to update your pmaports.")
|
||||
|
||||
|
||||
|
@ -42,26 +49,31 @@ def check_version_pmbootstrap(min_ver):
|
|||
return
|
||||
|
||||
# Show versions
|
||||
logging.info(f"NOTE: you are using pmbootstrap version {real}, but"
|
||||
f" version {min_ver} is required.")
|
||||
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'")
|
||||
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.")
|
||||
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. """
|
||||
"""Read the sections starting with "repo:" from pmaports.cfg."""
|
||||
|
||||
cfg = configparser.ConfigParser()
|
||||
cfg.read(f"{pkgrepo_default_path()}/pmaports.cfg")
|
||||
|
@ -100,8 +112,7 @@ def read_config(aports: Optional[Path] = None):
|
|||
# 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}")
|
||||
raise RuntimeError("Invalid pmaports repository, could not find the" f" config: {path_cfg}")
|
||||
|
||||
# Load the config
|
||||
cfg = configparser.ConfigParser()
|
||||
|
@ -150,19 +161,24 @@ def read_config_channel():
|
|||
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"])
|
||||
branch = pmb.helpers.git.rev_parse(aports, extra_args=["--abbrev-ref"])
|
||||
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"
|
||||
" supported channel: master, v24.06, …")
|
||||
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.")
|
||||
logging.info(
|
||||
"NOTE: fix the error by rebasing or cherry picking relevant"
|
||||
" commits from this branch onto a branch that is on a"
|
||||
" supported channel: master, v24.06, …"
|
||||
)
|
||||
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():
|
||||
|
@ -187,23 +203,24 @@ def switch_to_channel_branch(channel_new):
|
|||
# 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}'...")
|
||||
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}")
|
||||
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()
|
||||
|
|
|
@ -16,20 +16,24 @@ def which_sudo() -> Optional[str]:
|
|||
if os.getuid() == 0:
|
||||
return None
|
||||
|
||||
supported_sudos = ['doas', 'sudo']
|
||||
supported_sudos = ["doas", "sudo"]
|
||||
|
||||
user_set_sudo = os.getenv("PMB_SUDO")
|
||||
if user_set_sudo is not None:
|
||||
if shutil.which(user_set_sudo) is None:
|
||||
raise RuntimeError("PMB_SUDO environmental variable is set to"
|
||||
f" {user_set_sudo} but pmbootstrap cannot find"
|
||||
" this command on your system.")
|
||||
raise RuntimeError(
|
||||
"PMB_SUDO environmental variable is set to"
|
||||
f" {user_set_sudo} but pmbootstrap cannot find"
|
||||
" this command on your system."
|
||||
)
|
||||
return user_set_sudo
|
||||
|
||||
for sudo in supported_sudos:
|
||||
if shutil.which(sudo) is not None:
|
||||
return sudo
|
||||
|
||||
raise RuntimeError("Can't find sudo or doas required to run pmbootstrap."
|
||||
" Please install sudo, doas, or specify your own sudo"
|
||||
" with the PMB_SUDO environmental variable.")
|
||||
raise RuntimeError(
|
||||
"Can't find sudo or doas required to run pmbootstrap."
|
||||
" Please install sudo, doas, or specify your own sudo"
|
||||
" with the PMB_SUDO environmental variable."
|
||||
)
|
||||
|
|
|
@ -6,6 +6,7 @@ from pmb.core.config import SystemdConfig
|
|||
|
||||
"""Test the config file serialization and deserialization."""
|
||||
|
||||
|
||||
def test_load(config_file):
|
||||
config = pmb.config.load(config_file)
|
||||
assert config.build_default_device_arch
|
||||
|
@ -75,6 +76,7 @@ def test_migrate_2_to_3(config_file_2_3_x, tmp_path, monkeypatch):
|
|||
tmp_path = tmp_path / "pmbootstrap-new.cfg"
|
||||
|
||||
did_migrate = False
|
||||
|
||||
def mock_save(path, config):
|
||||
nonlocal did_migrate
|
||||
did_migrate = True
|
||||
|
@ -92,4 +94,5 @@ def test_migrate_2_to_3(config_file_2_3_x, tmp_path, monkeypatch):
|
|||
# Check that save was called (which happens on a config migration)
|
||||
assert did_migrate
|
||||
|
||||
|
||||
# FIXME: add save tests and better type checks
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
# Copyright 2023 Oliver Smith
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
""" Save, read, verify workdir state related information in $WORK/workdir.cfg,
|
||||
for example the init dates of the chroots. This is not saved in
|
||||
pmbootstrap.cfg, because pmbootstrap.cfg is not tied to a specific work
|
||||
dir. """
|
||||
"""Save, read, verify workdir state related information in $WORK/workdir.cfg,
|
||||
for example the init dates of the chroots. This is not saved in
|
||||
pmbootstrap.cfg, because pmbootstrap.cfg is not tied to a specific work
|
||||
dir."""
|
||||
|
||||
import configparser
|
||||
import os
|
||||
import time
|
||||
|
@ -39,7 +40,7 @@ def chroot_save_init(suffix: Chroot):
|
|||
cfg.write(handle)
|
||||
|
||||
|
||||
def chroots_outdated(chroot: Optional[Chroot]=None):
|
||||
def chroots_outdated(chroot: Optional[Chroot] = None):
|
||||
"""Check if init dates from workdir.cfg indicate that any chroot is
|
||||
outdated.
|
||||
|
||||
|
@ -75,11 +76,12 @@ def chroot_check_channel(chroot: Chroot) -> bool:
|
|||
the user has auto_zap_misconfigured_chroots enabled), False otherwise."""
|
||||
config = get_context().config
|
||||
path = config.work / "workdir.cfg"
|
||||
msg_again = "Run 'pmbootstrap zap' to delete your chroots and try again." \
|
||||
" To do this automatically, run 'pmbootstrap config" \
|
||||
msg_again = (
|
||||
"Run 'pmbootstrap zap' to delete your chroots and try again."
|
||||
" To do this automatically, run 'pmbootstrap config"
|
||||
" auto_zap_misconfigured_chroots yes'."
|
||||
msg_unknown = ("Could not figure out on which release channel the"
|
||||
f" '{chroot}' chroot is.")
|
||||
)
|
||||
msg_unknown = "Could not figure out on which release channel the" f" '{chroot}' chroot is."
|
||||
if not os.path.exists(path):
|
||||
raise RuntimeError(f"{msg_unknown} {msg_again}")
|
||||
|
||||
|
@ -91,17 +93,22 @@ def chroot_check_channel(chroot: Chroot) -> bool:
|
|||
|
||||
channel = pmb.config.pmaports.read_config()["channel"]
|
||||
channel_cfg = cfg[key][str(chroot)]
|
||||
msg = f"Chroot '{chroot}' is for the '{channel_cfg}' channel," \
|
||||
f" but you are on the '{channel}' channel."
|
||||
msg = (
|
||||
f"Chroot '{chroot}' is for the '{channel_cfg}' channel,"
|
||||
f" but you are on the '{channel}' channel."
|
||||
)
|
||||
|
||||
if channel != channel_cfg:
|
||||
if config.auto_zap_misconfigured_chroots.enabled():
|
||||
if config.auto_zap_misconfigured_chroots.noisy():
|
||||
logging.info(msg)
|
||||
logging.info("Automatically zapping since"
|
||||
" auto_zap_misconfigured_chroots is enabled.")
|
||||
logging.info("NOTE: You can silence this message with 'pmbootstrap"
|
||||
" config auto_zap_misconfigured_chroots silently'")
|
||||
logging.info(
|
||||
"Automatically zapping since" " auto_zap_misconfigured_chroots is enabled."
|
||||
)
|
||||
logging.info(
|
||||
"NOTE: You can silence this message with 'pmbootstrap"
|
||||
" config auto_zap_misconfigured_chroots silently'"
|
||||
)
|
||||
else:
|
||||
logging.debug(f"{msg} Zapping chroot.")
|
||||
return True
|
||||
|
|
|
@ -10,6 +10,7 @@ from pmb.helpers.args import init as init_args
|
|||
|
||||
_testdir = Path(__file__).parent / "data/tests"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def config_file(tmp_path_factory, request):
|
||||
"""Fixture to create a temporary pmbootstrap.cfg file."""
|
||||
|
@ -22,9 +23,8 @@ def config_file(tmp_path_factory, request):
|
|||
out_file = tmp_path / "pmbootstrap.cfg"
|
||||
workdir = tmp_path / "work"
|
||||
workdir.mkdir()
|
||||
|
||||
configs = {"default": f"aports = {workdir / 'cache_git' / 'pmaports'}",
|
||||
"no-repos": "aports = "}
|
||||
|
||||
configs = {"default": f"aports = {workdir / 'cache_git' / 'pmaports'}", "no-repos": "aports = "}
|
||||
|
||||
file = _testdir / "pmbootstrap.cfg"
|
||||
print(f"CONFIG: {out_file}")
|
||||
|
@ -43,8 +43,7 @@ def device_package(config_file):
|
|||
pkgdir.mkdir()
|
||||
|
||||
for file in ["APKBUILD", "deviceinfo"]:
|
||||
shutil.copy(_testdir / f"{file}.{MOCK_DEVICE}",
|
||||
pkgdir / file)
|
||||
shutil.copy(_testdir / f"{file}.{MOCK_DEVICE}", pkgdir / file)
|
||||
|
||||
return pkgdir
|
||||
|
||||
|
@ -52,7 +51,8 @@ def device_package(config_file):
|
|||
@pytest.fixture
|
||||
def mock_devices_find_path(device_package, monkeypatch):
|
||||
"""Fixture to mock pmb.helpers.devices.find_path()"""
|
||||
def mock_find_path(device, file=''):
|
||||
|
||||
def mock_find_path(device, file=""):
|
||||
print(f"mock_find_path({device}, {file})")
|
||||
out = device_package / file
|
||||
if not out.exists():
|
||||
|
@ -67,10 +67,11 @@ def mock_devices_find_path(device_package, monkeypatch):
|
|||
def logfile(tmp_path_factory):
|
||||
"""Setup logging for all tests."""
|
||||
from pmb.helpers import logging
|
||||
|
||||
tmp_path = tmp_path_factory.getbasetemp()
|
||||
logfile = tmp_path / "log_testsuite.txt"
|
||||
logging.init(logfile, verbose=True)
|
||||
|
||||
|
||||
return logfile
|
||||
|
||||
|
||||
|
@ -79,8 +80,14 @@ def setup_mock_ask(monkeypatch):
|
|||
"""Common setup to mock cli.ask() to avoid reading from stdin"""
|
||||
import pmb.helpers.cli
|
||||
|
||||
def mock_ask(question="Continue?", choices=["y", "n"], default="n",
|
||||
lowercase_answer=True, validation_regex=None, complete=None):
|
||||
def mock_ask(
|
||||
question="Continue?",
|
||||
choices=["y", "n"],
|
||||
default="n",
|
||||
lowercase_answer=True,
|
||||
validation_regex=None,
|
||||
complete=None,
|
||||
):
|
||||
return default
|
||||
|
||||
monkeypatch.setattr(pmb.helpers.cli, "ask", mock_ask)
|
||||
|
@ -123,16 +130,18 @@ def pmb_args(config_file, mock_context, logfile):
|
|||
args.log = logfile
|
||||
|
||||
init_args(args)
|
||||
|
||||
|
||||
print(f"WORK: {get_context().config.work}")
|
||||
|
||||
# Sanity check
|
||||
assert ".pytest_tmp" in get_context().config.work.parts
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def foreign_arch():
|
||||
"""Fixture to return the foreign arch."""
|
||||
from pmb.core.arch import Arch
|
||||
|
||||
if os.uname().machine == "x86_64":
|
||||
return Arch.aarch64
|
||||
|
||||
|
@ -142,7 +151,7 @@ def foreign_arch():
|
|||
@pytest.fixture
|
||||
def pmaports(pmb_args, monkeypatch):
|
||||
"""Fixture to clone pmaports."""
|
||||
|
||||
|
||||
from pmb.core import Config
|
||||
from pmb.core.context import get_context
|
||||
|
||||
|
@ -155,5 +164,4 @@ def pmaports(pmb_args, monkeypatch):
|
|||
|
||||
pmb.helpers.git.clone("pmaports")
|
||||
|
||||
assert pmb.helpers.run.user(["git", "checkout", "master"],
|
||||
working_dir=config.aports[0]) == 0
|
||||
assert pmb.helpers.run.user(["git", "checkout", "master"], working_dir=config.aports[0]) == 0
|
||||
|
|
|
@ -12,6 +12,7 @@ _cached_native_arch: "Arch"
|
|||
class Arch(enum.Enum):
|
||||
"""Supported architectures according to the Alpine
|
||||
APKBUILD format."""
|
||||
|
||||
x86 = "x86"
|
||||
x86_64 = "x86_64"
|
||||
armhf = "armhf"
|
||||
|
@ -33,20 +34,19 @@ class Arch(enum.Enum):
|
|||
ppc64 = "ppc64"
|
||||
riscv32 = "riscv32"
|
||||
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.value
|
||||
|
||||
|
||||
@staticmethod
|
||||
def from_str(arch: str) -> "Arch":
|
||||
try:
|
||||
return Arch(arch)
|
||||
except ValueError:
|
||||
raise ValueError(f"Invalid architecture: '{arch}',"
|
||||
" expected something like:"
|
||||
f" {', '.join([str(a) for a in Arch.supported()])}")
|
||||
|
||||
raise ValueError(
|
||||
f"Invalid architecture: '{arch}',"
|
||||
" expected something like:"
|
||||
f" {', '.join([str(a) for a in Arch.supported()])}"
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def from_machine_type(machine_type: str) -> "Arch":
|
||||
|
@ -60,17 +60,14 @@ class Arch(enum.Enum):
|
|||
}
|
||||
return mapping[machine_type]
|
||||
|
||||
|
||||
@staticmethod
|
||||
def native() -> "Arch":
|
||||
global _cached_native_arch
|
||||
return _cached_native_arch
|
||||
|
||||
|
||||
def is_native(self):
|
||||
return self == Arch.native()
|
||||
|
||||
|
||||
@staticmethod
|
||||
def supported() -> Set["Arch"]:
|
||||
"""Officially supported host/target architectures for postmarketOS. Only
|
||||
|
@ -78,16 +75,17 @@ class Arch(enum.Enum):
|
|||
we need to generate the "musl-$ARCH" and "gcc-$ARCH" packages (use
|
||||
"pmbootstrap aportgen musl-armhf" etc.)."""
|
||||
# FIXME: cache?
|
||||
return set([
|
||||
Arch.armhf,
|
||||
Arch.armv7,
|
||||
Arch.aarch64,
|
||||
Arch.x86_64,
|
||||
Arch.x86,
|
||||
Arch.riscv64,
|
||||
Arch.native(),
|
||||
])
|
||||
|
||||
return set(
|
||||
[
|
||||
Arch.armhf,
|
||||
Arch.armv7,
|
||||
Arch.aarch64,
|
||||
Arch.x86_64,
|
||||
Arch.x86,
|
||||
Arch.riscv64,
|
||||
Arch.native(),
|
||||
]
|
||||
)
|
||||
|
||||
def kernel(self):
|
||||
mapping = {
|
||||
|
@ -112,7 +110,6 @@ class Arch(enum.Enum):
|
|||
}
|
||||
return mapping.get(self, self.value)
|
||||
|
||||
|
||||
def alpine_triple(self):
|
||||
"""Get the cross compiler triple for this architecture on Alpine."""
|
||||
mapping = {
|
||||
|
@ -140,9 +137,7 @@ class Arch(enum.Enum):
|
|||
if self in mapping:
|
||||
return mapping[self]
|
||||
|
||||
raise ValueError(f"Can not map Alpine architecture '{self}'"
|
||||
" to the right hostspec value")
|
||||
|
||||
raise ValueError(f"Can not map Alpine architecture '{self}'" " to the right hostspec value")
|
||||
|
||||
def cpu_emulation_required(self):
|
||||
# Obvious case: host arch is target arch
|
||||
|
@ -162,7 +157,6 @@ class Arch(enum.Enum):
|
|||
# No match: then it's required
|
||||
return True
|
||||
|
||||
|
||||
# Magic to let us use an arch as a Path element
|
||||
def __truediv__(self, other: object) -> Path:
|
||||
if isinstance(other, PosixPath) or isinstance(other, PurePosixPath):
|
||||
|
@ -181,7 +175,6 @@ class Arch(enum.Enum):
|
|||
|
||||
return NotImplemented
|
||||
|
||||
|
||||
def __rtruediv__(self, other: object) -> Path:
|
||||
if isinstance(other, PosixPath) or isinstance(other, PurePosixPath):
|
||||
# Important to produce a new Path object here, otherwise we
|
||||
|
@ -192,4 +185,5 @@ class Arch(enum.Enum):
|
|||
|
||||
return NotImplemented
|
||||
|
||||
|
||||
_cached_native_arch = Arch.from_machine_type(platform.machine())
|
||||
|
|
|
@ -9,6 +9,7 @@ import pmb.config
|
|||
from pmb.core.arch import Arch
|
||||
from .context import get_context
|
||||
|
||||
|
||||
class ChrootType(enum.Enum):
|
||||
ROOTFS = "rootfs"
|
||||
BUILDROOT = "buildroot"
|
||||
|
@ -19,6 +20,7 @@ class ChrootType(enum.Enum):
|
|||
def __str__(self) -> str:
|
||||
return self.name
|
||||
|
||||
|
||||
class Chroot:
|
||||
__type: ChrootType
|
||||
__name: str
|
||||
|
@ -43,11 +45,11 @@ class Chroot:
|
|||
"x86",
|
||||
"x86_64",
|
||||
"aarch64",
|
||||
"armhf", # XXX: remove this?
|
||||
"armhf", # XXX: remove this?
|
||||
"armv7",
|
||||
"riscv64",
|
||||
]
|
||||
|
||||
|
||||
if self.__type not in ChrootType:
|
||||
raise ValueError(f"Invalid chroot type: '{self.__type}'")
|
||||
|
||||
|
@ -63,38 +65,31 @@ class Chroot:
|
|||
|
||||
# A native suffix must not have a name.
|
||||
if self.__type == ChrootType.NATIVE and self.__name != "":
|
||||
raise ValueError(f"The native suffix can't have a name but got: "
|
||||
f"'{self.__name}'")
|
||||
raise ValueError(f"The native suffix can't have a name but got: " f"'{self.__name}'")
|
||||
|
||||
if self.__type == ChrootType.IMAGE and not Path(self.__name).exists():
|
||||
raise ValueError(f"Image file '{self.__name}' does not exist")
|
||||
|
||||
|
||||
def __str__(self) -> str:
|
||||
if len(self.__name) > 0 and self.type != ChrootType.IMAGE:
|
||||
return f"{self.__type.value}_{self.__name}"
|
||||
else:
|
||||
return self.__type.value
|
||||
|
||||
|
||||
@property
|
||||
def dirname(self) -> str:
|
||||
return f"chroot_{self}"
|
||||
|
||||
|
||||
@property
|
||||
def path(self) -> Path:
|
||||
return Path(get_context().config.work, self.dirname)
|
||||
|
||||
|
||||
def exists(self) -> bool:
|
||||
return (self / "bin/sh").is_symlink()
|
||||
|
||||
|
||||
def is_mounted(self) -> bool:
|
||||
return self.exists() and pmb.helpers.mount.ismount(self.path / "etc/apk/keys")
|
||||
|
||||
|
||||
@property
|
||||
def arch(self) -> Arch:
|
||||
if self.type == ChrootType.NATIVE:
|
||||
|
@ -108,8 +103,7 @@ class Chroot:
|
|||
if arch is not None:
|
||||
return arch
|
||||
|
||||
raise ValueError(f"Invalid chroot suffix: {self}"
|
||||
" (wrong device chosen in 'init' step?)")
|
||||
raise ValueError(f"Invalid chroot suffix: {self}" " (wrong device chosen in 'init' step?)")
|
||||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
if isinstance(other, str):
|
||||
|
@ -123,7 +117,6 @@ class Chroot:
|
|||
|
||||
return self.type == other.type and self.name == other.name
|
||||
|
||||
|
||||
def __truediv__(self, other: object) -> Path:
|
||||
if isinstance(other, PosixPath) or isinstance(other, PurePosixPath):
|
||||
# Convert the other path to a relative path
|
||||
|
@ -136,7 +129,6 @@ class Chroot:
|
|||
|
||||
return NotImplemented
|
||||
|
||||
|
||||
def __rtruediv__(self, other: object) -> Path:
|
||||
if isinstance(other, PosixPath) or isinstance(other, PurePosixPath):
|
||||
# Important to produce a new Path object here, otherwise we
|
||||
|
@ -149,32 +141,26 @@ class Chroot:
|
|||
|
||||
return NotImplemented
|
||||
|
||||
|
||||
@property
|
||||
def type(self) -> ChrootType:
|
||||
return self.__type
|
||||
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return self.__name
|
||||
|
||||
|
||||
@staticmethod
|
||||
def native() -> Chroot:
|
||||
return Chroot(ChrootType.NATIVE)
|
||||
|
||||
|
||||
@staticmethod
|
||||
def buildroot(arch: Arch) -> Chroot:
|
||||
return Chroot(ChrootType.BUILDROOT, arch)
|
||||
|
||||
|
||||
@staticmethod
|
||||
def rootfs(device: str) -> Chroot:
|
||||
return Chroot(ChrootType.ROOTFS, device)
|
||||
|
||||
|
||||
@staticmethod
|
||||
def from_str(s: str) -> Chroot:
|
||||
"""
|
||||
|
@ -191,7 +177,7 @@ class Chroot:
|
|||
# "native" is the only valid suffix type, the constructor(s)
|
||||
# will validate that stype is "native"
|
||||
return Chroot(ChrootType(stype))
|
||||
|
||||
|
||||
@staticmethod
|
||||
def iter_patterns() -> Generator[str, None, None]:
|
||||
"""
|
||||
|
@ -203,7 +189,6 @@ class Chroot:
|
|||
else:
|
||||
yield f"chroot_{stype.value}_*"
|
||||
|
||||
|
||||
@staticmethod
|
||||
def glob() -> Generator[Path, None, None]:
|
||||
"""
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
from copy import deepcopy
|
||||
import enum
|
||||
import multiprocessing
|
||||
|
@ -6,6 +5,7 @@ from typing import Any, List, Dict, TypedDict
|
|||
from pathlib import Path
|
||||
import os
|
||||
|
||||
|
||||
class Mirrors(TypedDict):
|
||||
alpine: str
|
||||
pmaports: str
|
||||
|
@ -17,7 +17,6 @@ class SystemdConfig(enum.Enum):
|
|||
ALWAYS = "always"
|
||||
NEVER = "never"
|
||||
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.value
|
||||
|
||||
|
@ -41,13 +40,14 @@ class AutoZapConfig(enum.Enum):
|
|||
return self == AutoZapConfig.YES
|
||||
|
||||
|
||||
class Config():
|
||||
aports: List[Path] = [Path(os.path.expanduser("~") +
|
||||
"/.local/var/pmbootstrap/cache_git/pmaports")]
|
||||
class Config:
|
||||
aports: List[Path] = [
|
||||
Path(os.path.expanduser("~") + "/.local/var/pmbootstrap/cache_git/pmaports")
|
||||
]
|
||||
boot_size: int = 256
|
||||
build_default_device_arch: bool = False
|
||||
build_pkgs_on_install: bool = True
|
||||
ccache_size: str = "5G" # yeahhhh this one has a suffix
|
||||
ccache_size: str = "5G" # yeahhhh this one has a suffix
|
||||
device: str = "qemu-amd64"
|
||||
extra_packages: str = "none"
|
||||
extra_space: int = 0
|
||||
|
@ -60,7 +60,7 @@ class Config():
|
|||
mirrors: Mirrors = {
|
||||
"alpine": "http://dl-cdn.alpinelinux.org/alpine/",
|
||||
"pmaports": "http://mirror.postmarketos.org/postmarketos/",
|
||||
"systemd": "http://mirror.postmarketos.org/postmarketos/staging/systemd/"
|
||||
"systemd": "http://mirror.postmarketos.org/postmarketos/staging/systemd/",
|
||||
}
|
||||
qemu_redir_stdio: bool = False
|
||||
ssh_key_glob: str = "~/.ssh/id_*.pub"
|
||||
|
@ -75,15 +75,13 @@ class Config():
|
|||
# automatically zap chroots that are for the wrong channel
|
||||
auto_zap_misconfigured_chroots: AutoZapConfig = AutoZapConfig.NO
|
||||
|
||||
providers: Dict[str, str] = { }
|
||||
|
||||
providers: Dict[str, str] = {}
|
||||
|
||||
def __init__(self):
|
||||
# Make sure we aren't modifying the class defaults
|
||||
for key in Config.__annotations__.keys():
|
||||
setattr(self, key, deepcopy(Config.get_default(key)))
|
||||
|
||||
|
||||
@staticmethod
|
||||
def keys() -> List[str]:
|
||||
keys = list(Config.__annotations__.keys())
|
||||
|
@ -91,7 +89,6 @@ class Config():
|
|||
keys += [f"mirrors.{k}" for k in Mirrors.__annotations__.keys()]
|
||||
return sorted(keys)
|
||||
|
||||
|
||||
@staticmethod
|
||||
def get_default(dotted_key: str) -> Any:
|
||||
"""Get the default value for a config option, supporting
|
||||
|
@ -104,7 +101,6 @@ class Config():
|
|||
else:
|
||||
raise ValueError(f"Invalid dotted key: {dotted_key}")
|
||||
|
||||
|
||||
def __setattr__(self, key: str, value: Any):
|
||||
"""Allow for setattr() to be used with a dotted key
|
||||
to set nested dictionaries (e.g. "mirrors.alpine")."""
|
||||
|
@ -126,7 +122,6 @@ class Config():
|
|||
else:
|
||||
raise ValueError(f"Invalid dotted key: {key}")
|
||||
|
||||
|
||||
def __getattribute__(self, key: str) -> Any:
|
||||
"""Allow for getattr() to be used with a dotted key
|
||||
to get nested dictionaries (e.g. "mirrors.alpine")."""
|
||||
|
|
|
@ -7,7 +7,7 @@ from pathlib import Path
|
|||
from .config import Config
|
||||
|
||||
|
||||
class Context():
|
||||
class Context:
|
||||
details_to_stdout: bool = False
|
||||
quiet: bool = False
|
||||
command_timeout: float = 900
|
||||
|
@ -46,8 +46,9 @@ class Context():
|
|||
|
||||
__context: Context
|
||||
|
||||
|
||||
# mypy: disable-error-code="return-value"
|
||||
def get_context(allow_failure: bool=False) -> Context:
|
||||
def get_context(allow_failure: bool = False) -> Context:
|
||||
"""Get immutable global runtime context."""
|
||||
global __context
|
||||
|
||||
|
@ -59,6 +60,7 @@ def get_context(allow_failure: bool=False) -> Context:
|
|||
raise RuntimeError("Context not loaded yet")
|
||||
return __context
|
||||
|
||||
|
||||
def set_context(context: Context):
|
||||
"""Set global runtime context."""
|
||||
global __context
|
||||
|
@ -67,5 +69,3 @@ def set_context(context: Context):
|
|||
raise RuntimeError("Context already loaded")
|
||||
|
||||
__context = context
|
||||
|
||||
|
||||
|
|
|
@ -8,11 +8,13 @@ from typing import List
|
|||
from pmb.core.chroot import ChrootType
|
||||
from pmb.types import PathString
|
||||
|
||||
|
||||
class CrossToolTarget(enum.Enum):
|
||||
BUILDROOT = 0
|
||||
ROOTFS = 1
|
||||
|
||||
class CrossTool():
|
||||
|
||||
class CrossTool:
|
||||
__target: CrossToolTarget
|
||||
__package: str
|
||||
__paths: List[Path]
|
||||
|
@ -36,7 +38,11 @@ class CrossTool():
|
|||
def should_install(self, target: ChrootType) -> bool:
|
||||
if target == ChrootType.BUILDROOT and self.__target == CrossToolTarget.BUILDROOT:
|
||||
return True
|
||||
if target == ChrootType.ROOTFS or target == ChrootType.INSTALLER and self.__target == CrossToolTarget.ROOTFS:
|
||||
if (
|
||||
target == ChrootType.ROOTFS
|
||||
or target == ChrootType.INSTALLER
|
||||
and self.__target == CrossToolTarget.ROOTFS
|
||||
):
|
||||
return True
|
||||
|
||||
|
||||
return False
|
||||
|
|
|
@ -9,10 +9,9 @@ from pmb.meta import Cache
|
|||
|
||||
|
||||
@Cache(skip_extras=False)
|
||||
def pkgrepo_paths(skip_extras = False) -> List[Path]:
|
||||
def pkgrepo_paths(skip_extras=False) -> List[Path]:
|
||||
config = get_context().config
|
||||
paths = list(map(lambda x: Path(x),
|
||||
config.aports))
|
||||
paths = list(map(lambda x: Path(x), config.aports))
|
||||
if not paths:
|
||||
raise RuntimeError("No package repositories specified?")
|
||||
|
||||
|
@ -28,15 +27,18 @@ def pkgrepo_paths(skip_extras = False) -> List[Path]:
|
|||
|
||||
return out_paths
|
||||
|
||||
|
||||
def pkgrepo_default_path() -> Path:
|
||||
return pkgrepo_paths(skip_extras=True)[0]
|
||||
|
||||
def pkgrepo_names(skip_exras = False) -> List[str]:
|
||||
|
||||
def pkgrepo_names(skip_exras=False) -> List[str]:
|
||||
"""
|
||||
Return a list of all the package repository names.
|
||||
"""
|
||||
return [aports.name for aports in pkgrepo_paths(skip_exras)]
|
||||
|
||||
|
||||
def pkgrepo_path(name: str) -> Path:
|
||||
"""
|
||||
Return the absolute path to the package repository with the given name.
|
||||
|
@ -46,6 +48,7 @@ def pkgrepo_path(name: str) -> Path:
|
|||
return aports
|
||||
raise RuntimeError(f"aports '{name}' not found")
|
||||
|
||||
|
||||
def pkgrepo_name_from_subdir(subdir: Path) -> str:
|
||||
"""
|
||||
Return the name of the package repository for the given directory.
|
||||
|
@ -56,6 +59,7 @@ def pkgrepo_name_from_subdir(subdir: Path) -> str:
|
|||
return aports.name
|
||||
raise RuntimeError(f"aports subdir '{subdir}' not found")
|
||||
|
||||
|
||||
def pkgrepo_glob_one(path: str) -> Optional[Path]:
|
||||
"""
|
||||
Search for the file denoted by path in all aports repositories.
|
||||
|
@ -102,8 +106,10 @@ def pkgrepo_iter_package_dirs(skip_extra_repos=False) -> Generator[Path, None, N
|
|||
continue
|
||||
pkg = os.path.basename(pdir)
|
||||
if pkg in seen[repo.name]:
|
||||
raise RuntimeError(f"Package {pkg} found in multiple aports "
|
||||
"subfolders. Please put it only in one folder.")
|
||||
raise RuntimeError(
|
||||
f"Package {pkg} found in multiple aports "
|
||||
"subfolders. Please put it only in one folder."
|
||||
)
|
||||
if pkg in [x for li in seen.values() for x in li]:
|
||||
continue
|
||||
seen[repo.name].append(pkg)
|
||||
|
|
|
@ -5,6 +5,7 @@ import pytest
|
|||
|
||||
from .arch import Arch
|
||||
|
||||
|
||||
def test_valid_arches():
|
||||
# Silly test
|
||||
assert Arch.native().is_native()
|
||||
|
@ -14,7 +15,7 @@ def test_valid_arches():
|
|||
assert Arch.from_str("x86_64") == Arch.x86_64
|
||||
assert Arch.from_str("aarch64") == Arch.aarch64
|
||||
assert Arch.from_str("armhf") == Arch.armhf
|
||||
|
||||
|
||||
# Test from_machine_type
|
||||
assert Arch.from_machine_type("i686") == Arch.x86
|
||||
assert Arch.from_machine_type("x86_64") == Arch.x86_64
|
||||
|
@ -30,7 +31,7 @@ def test_valid_arches():
|
|||
# kernel arch
|
||||
assert Arch.x86.kernel() == "x86"
|
||||
assert Arch.x86_64.kernel() == "x86_64"
|
||||
assert Arch.aarch64.kernel() == "arm64" # The fun one
|
||||
assert Arch.aarch64.kernel() == "arm64" # The fun one
|
||||
assert Arch.armhf.kernel() == "arm"
|
||||
assert Arch.armv7.kernel() == "arm"
|
||||
|
||||
|
@ -58,6 +59,7 @@ def test_valid_arches():
|
|||
assert (Arch.aarch64 / "beep").name == "beep"
|
||||
assert Path("boop") / Arch.aarch64 == Path("boop/aarch64")
|
||||
|
||||
|
||||
def test_invalid_arches():
|
||||
excinfo: Any
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
|
|
|
@ -4,6 +4,7 @@ from .arch import Arch
|
|||
from .context import get_context
|
||||
from .chroot import Chroot, ChrootType
|
||||
|
||||
|
||||
def test_valid_chroots(pmb_args, mock_devices_find_path):
|
||||
"""Test that Chroot objects work as expected"""
|
||||
|
||||
|
@ -16,12 +17,12 @@ def test_valid_chroots(pmb_args, mock_devices_find_path):
|
|||
assert not chroot.exists() # Shouldn't be created
|
||||
assert chroot.path == work / "chroot_native"
|
||||
assert str(chroot) == "native"
|
||||
|
||||
|
||||
chroot = Chroot.buildroot(Arch.aarch64)
|
||||
assert chroot.type == ChrootType.BUILDROOT
|
||||
assert chroot.name == "aarch64"
|
||||
assert chroot.arch == Arch.aarch64
|
||||
assert not chroot.exists() # Shouldn't be created
|
||||
assert not chroot.exists() # Shouldn't be created
|
||||
assert chroot.path == work / "chroot_buildroot_aarch64"
|
||||
assert str(chroot) == "buildroot_aarch64"
|
||||
|
||||
|
@ -30,7 +31,7 @@ def test_valid_chroots(pmb_args, mock_devices_find_path):
|
|||
assert chroot.type == ChrootType.ROOTFS
|
||||
assert chroot.name == "qemu-amd64"
|
||||
assert chroot.arch == Arch.x86_64
|
||||
assert not chroot.exists() # Shouldn't be created
|
||||
assert not chroot.exists() # Shouldn't be created
|
||||
assert chroot.path == work / "chroot_rootfs_qemu-amd64"
|
||||
assert str(chroot) == "rootfs_qemu-amd64"
|
||||
|
||||
|
@ -59,8 +60,12 @@ def test_invalid_chroots(pmb_args):
|
|||
@pytest.mark.xfail
|
||||
def test_untested_chroots():
|
||||
# IMAGE type is untested, name should be a valid path in this case
|
||||
tested_chroot_types = [ChrootType.ROOTFS, ChrootType.BUILDROOT, ChrootType.NATIVE,
|
||||
ChrootType.INSTALLER]
|
||||
tested_chroot_types = [
|
||||
ChrootType.ROOTFS,
|
||||
ChrootType.BUILDROOT,
|
||||
ChrootType.NATIVE,
|
||||
ChrootType.INSTALLER,
|
||||
]
|
||||
for ct in ChrootType:
|
||||
if ct not in tested_chroot_types:
|
||||
raise ValueError(f"ChrootType {ct} is untested!")
|
||||
|
|
|
@ -13,6 +13,7 @@ def test_pkgrepo_paths_no_repos(pmb_args):
|
|||
paths = pkgrepo_paths()
|
||||
print(paths)
|
||||
|
||||
|
||||
def test_pkgrepo_pmaports(pmaports, monkeypatch):
|
||||
"""Test pkgrepo_paths() with pmaports repository and systemd extra repo"""
|
||||
|
||||
|
@ -23,14 +24,18 @@ def test_pkgrepo_pmaports(pmaports, monkeypatch):
|
|||
print(f"[master] pkgrepo_paths: {paths}")
|
||||
assert len(paths) == 1
|
||||
assert "pmaports" in paths[0].name
|
||||
|
||||
|
||||
default_path = pkgrepo_default_path()
|
||||
|
||||
assert default_path.name == "pmaports"
|
||||
|
||||
# Test extra-repos
|
||||
assert pmb.helpers.run.user(["git", "checkout", "master_staging_systemd"],
|
||||
working_dir=default_path) == 0
|
||||
assert (
|
||||
pmb.helpers.run.user(
|
||||
["git", "checkout", "master_staging_systemd"], working_dir=default_path
|
||||
)
|
||||
== 0
|
||||
)
|
||||
|
||||
paths = pkgrepo_paths()
|
||||
assert len(paths) == 2
|
||||
|
|
|
@ -10,7 +10,7 @@ import pmb.export
|
|||
from pmb.core import Chroot, ChrootType
|
||||
|
||||
|
||||
def frontend(args: PmbArgs): # FIXME: ARGS_REFACTOR
|
||||
def frontend(args: PmbArgs): # FIXME: ARGS_REFACTOR
|
||||
context = get_context()
|
||||
# Create the export folder
|
||||
target = args.export_folder
|
||||
|
@ -21,8 +21,10 @@ def frontend(args: PmbArgs): # FIXME: ARGS_REFACTOR
|
|||
chroot = Chroot.native()
|
||||
rootfs_dir = chroot / "home/pmos/rootfs" / context.device
|
||||
if not rootfs_dir.glob("*.img"):
|
||||
logging.info("NOTE: To export the rootfs image, run 'pmbootstrap"
|
||||
" install' first (without the 'disk' parameter).")
|
||||
logging.info(
|
||||
"NOTE: To export the rootfs image, run 'pmbootstrap"
|
||||
" install' first (without the 'disk' parameter)."
|
||||
)
|
||||
|
||||
# Rebuild the initramfs, just to make sure (see #69)
|
||||
flavor = pmb.helpers.frontend._parse_flavor(context.device, args.autoinstall)
|
||||
|
|
|
@ -31,10 +31,12 @@ def odin(context: Context, flavor, folder: Path):
|
|||
# Validate method
|
||||
method = deviceinfo.flash_method or ""
|
||||
if not method.startswith("heimdall-"):
|
||||
raise RuntimeError("An odin flashable tar is not supported"
|
||||
f" for the flash method '{method}' specified"
|
||||
" in the current configuration."
|
||||
" Only 'heimdall' methods are supported.")
|
||||
raise RuntimeError(
|
||||
"An odin flashable tar is not supported"
|
||||
f" for the flash method '{method}' specified"
|
||||
" in the current configuration."
|
||||
" Only 'heimdall' methods are supported."
|
||||
)
|
||||
|
||||
# Partitions
|
||||
partition_kernel = deviceinfo.flash_heimdall_partition_kernel or "KERNEL"
|
||||
|
@ -55,9 +57,7 @@ def odin(context: Context, flavor, folder: Path):
|
|||
odin_device_tar = f"{context.device}.tar"
|
||||
odin_device_tar_md5 = f"{context.device}.tar.md5"
|
||||
|
||||
handle.write(
|
||||
"#!/bin/sh\n"
|
||||
f"cd {temp_folder}\n")
|
||||
handle.write("#!/bin/sh\n" f"cd {temp_folder}\n")
|
||||
if method == "heimdall-isorec":
|
||||
handle.write(
|
||||
# Kernel: copy and append md5
|
||||
|
@ -66,33 +66,43 @@ def odin(context: Context, flavor, folder: Path):
|
|||
# Initramfs: recompress with lzop, append md5
|
||||
f"gunzip -c /boot/initramfs{suffix_flavor}"
|
||||
f" | lzop > {odin_initfs_md5}\n"
|
||||
f"md5sum -t {odin_initfs_md5} >> {odin_initfs_md5}\n")
|
||||
f"md5sum -t {odin_initfs_md5} >> {odin_initfs_md5}\n"
|
||||
)
|
||||
elif method == "heimdall-bootimg":
|
||||
handle.write(
|
||||
# boot.img: copy and append md5
|
||||
f"cp /boot/boot.img{suffix_flavor} {odin_kernel_md5}\n"
|
||||
f"md5sum -t {odin_kernel_md5} >> {odin_kernel_md5}\n")
|
||||
f"md5sum -t {odin_kernel_md5} >> {odin_kernel_md5}\n"
|
||||
)
|
||||
handle.write(
|
||||
# Create tar, remove included files and append md5
|
||||
f"tar -c -f {odin_device_tar} *.bin.md5\n"
|
||||
"rm *.bin.md5\n"
|
||||
f"md5sum -t {odin_device_tar} >> {odin_device_tar}\n"
|
||||
f"mv {odin_device_tar} {odin_device_tar_md5}\n")
|
||||
f"mv {odin_device_tar} {odin_device_tar_md5}\n"
|
||||
)
|
||||
|
||||
commands = [["mkdir", "-p", temp_folder],
|
||||
["cat", "/tmp/_odin.sh"], # for the log
|
||||
["sh", "/tmp/_odin.sh"],
|
||||
["rm", "/tmp/_odin.sh"]
|
||||
]
|
||||
commands = [
|
||||
["mkdir", "-p", temp_folder],
|
||||
["cat", "/tmp/_odin.sh"], # for the log
|
||||
["sh", "/tmp/_odin.sh"],
|
||||
["rm", "/tmp/_odin.sh"],
|
||||
]
|
||||
for command in commands:
|
||||
pmb.chroot.root(command, suffix)
|
||||
|
||||
# Move Odin flashable tar to native chroot and cleanup temp folder
|
||||
pmb.chroot.user(["mkdir", "-p", "/home/pmos/rootfs"])
|
||||
pmb.chroot.root(["mv", f"/mnt/rootfs_{context.device}{temp_folder}"
|
||||
f"/{odin_device_tar_md5}", "/home/pmos/rootfs/"]),
|
||||
pmb.chroot.root(["chown", "pmos:pmos",
|
||||
f"/home/pmos/rootfs/{odin_device_tar_md5}"])
|
||||
(
|
||||
pmb.chroot.root(
|
||||
[
|
||||
"mv",
|
||||
f"/mnt/rootfs_{context.device}{temp_folder}" f"/{odin_device_tar_md5}",
|
||||
"/home/pmos/rootfs/",
|
||||
]
|
||||
),
|
||||
)
|
||||
pmb.chroot.root(["chown", "pmos:pmos", f"/home/pmos/rootfs/{odin_device_tar_md5}"])
|
||||
pmb.chroot.root(["rmdir", temp_folder], suffix)
|
||||
|
||||
# Create the symlink
|
||||
|
|
|
@ -18,7 +18,7 @@ def symlinks(flavor, folder: Path):
|
|||
"""
|
||||
Create convenience symlinks to the rootfs and boot files.
|
||||
"""
|
||||
|
||||
|
||||
context = get_context()
|
||||
arch = pmb.parse.deviceinfo(context.device).arch
|
||||
|
||||
|
@ -30,8 +30,9 @@ def symlinks(flavor, folder: Path):
|
|||
|
||||
# File descriptions
|
||||
info = {
|
||||
f"boot.img{suffix}": ("Fastboot compatible boot.img file,"
|
||||
" contains initramfs and kernel"),
|
||||
f"boot.img{suffix}": (
|
||||
"Fastboot compatible boot.img file," " contains initramfs and kernel"
|
||||
),
|
||||
"dtbo.img": "Fastboot compatible dtbo image",
|
||||
f"initramfs{suffix}": "Initramfs",
|
||||
f"initramfs{suffix}-extra": "Extra initramfs files in /boot",
|
||||
|
@ -54,13 +55,14 @@ def symlinks(flavor, folder: Path):
|
|||
path_boot / f"uInitrd{suffix}",
|
||||
path_boot / f"uImage{suffix}",
|
||||
path_boot / f"vmlinuz{suffix}",
|
||||
path_boot / "dtbo.img",
|
||||
path_boot / "dtbo.img",
|
||||
chroot_native / "home/pmos/rootfs" / f"{context.device}.img",
|
||||
chroot_native / "home/pmos/rootfs" / f"{context.device}-boot.img",
|
||||
chroot_native / "home/pmos/rootfs" / f"{context.device}-root.img",
|
||||
chroot_buildroot / "var/libpostmarketos-android-recovery-installer" /
|
||||
f"pmos-{context.device}.zip",
|
||||
path_boot / "lk2nd.img"
|
||||
chroot_buildroot
|
||||
/ "var/libpostmarketos-android-recovery-installer"
|
||||
/ f"pmos-{context.device}.zip",
|
||||
path_boot / "lk2nd.img",
|
||||
]
|
||||
|
||||
files += list(path_boot.glob(f"initramfs{suffix}*"))
|
||||
|
|
|
@ -33,14 +33,14 @@ def kernel(deviceinfo: Deviceinfo, method: str, boot: bool = False, autoinstall:
|
|||
else:
|
||||
logging.info("(native) flash kernel " + flavor)
|
||||
pmb.flasher.run(deviceinfo, method, "flash_kernel", flavor)
|
||||
logging.info("You will get an IP automatically assigned to your "
|
||||
"USB interface shortly.")
|
||||
logging.info("Then you can connect to your device using ssh after pmOS has"
|
||||
" booted:")
|
||||
logging.info("You will get an IP automatically assigned to your " "USB interface shortly.")
|
||||
logging.info("Then you can connect to your device using ssh after pmOS has" " booted:")
|
||||
logging.info(f"ssh {get_context().config.user}@{pmb.config.default_ip}")
|
||||
logging.info("NOTE: If you enabled full disk encryption, you should make"
|
||||
" sure that Unl0kr has been properly configured for your"
|
||||
" device")
|
||||
logging.info(
|
||||
"NOTE: If you enabled full disk encryption, you should make"
|
||||
" sure that Unl0kr has been properly configured for your"
|
||||
" device"
|
||||
)
|
||||
|
||||
|
||||
def list_flavors(device: str):
|
||||
|
@ -57,17 +57,16 @@ def rootfs(deviceinfo: Deviceinfo, method: str):
|
|||
|
||||
img_path = Chroot.native() / "home/pmos/rootfs" / f"{deviceinfo.codename}{suffix}"
|
||||
if not img_path.exists():
|
||||
raise RuntimeError("The rootfs has not been generated yet, please run"
|
||||
" 'pmbootstrap install' first.")
|
||||
raise RuntimeError(
|
||||
"The rootfs has not been generated yet, please run" " 'pmbootstrap install' first."
|
||||
)
|
||||
|
||||
# Do not flash if using fastboot & image is too large
|
||||
if method.startswith("fastboot") \
|
||||
and deviceinfo.flash_fastboot_max_size:
|
||||
if method.startswith("fastboot") and deviceinfo.flash_fastboot_max_size:
|
||||
img_size = img_path.stat().st_size / 1024**2
|
||||
max_size = int(deviceinfo.flash_fastboot_max_size)
|
||||
if img_size > max_size:
|
||||
raise RuntimeError("The rootfs is too large for fastboot to"
|
||||
" flash.")
|
||||
raise RuntimeError("The rootfs is too large for fastboot to" " flash.")
|
||||
|
||||
# Run the flasher
|
||||
logging.info("(native) flash rootfs image")
|
||||
|
@ -85,15 +84,20 @@ def sideload(deviceinfo: Deviceinfo, method: str):
|
|||
# Mount the buildroot
|
||||
chroot = Chroot.buildroot(deviceinfo.arch)
|
||||
mountpoint = "/mnt/" / chroot
|
||||
pmb.helpers.mount.bind(chroot.path,
|
||||
Chroot.native().path / mountpoint)
|
||||
pmb.helpers.mount.bind(chroot.path, Chroot.native().path / mountpoint)
|
||||
|
||||
# Missing recovery zip error
|
||||
if not (Chroot.native() / mountpoint / "var/lib/postmarketos-android-recovery-installer"
|
||||
/ f"pmos-{deviceinfo.codename}.zip").exists():
|
||||
raise RuntimeError("The recovery zip has not been generated yet,"
|
||||
" please run 'pmbootstrap install' with the"
|
||||
" '--android-recovery-zip' parameter first!")
|
||||
if not (
|
||||
Chroot.native()
|
||||
/ mountpoint
|
||||
/ "var/lib/postmarketos-android-recovery-installer"
|
||||
/ f"pmos-{deviceinfo.codename}.zip"
|
||||
).exists():
|
||||
raise RuntimeError(
|
||||
"The recovery zip has not been generated yet,"
|
||||
" please run 'pmbootstrap install' with the"
|
||||
" '--android-recovery-zip' parameter first!"
|
||||
)
|
||||
|
||||
pmb.flasher.run(deviceinfo, method, "sideload")
|
||||
|
||||
|
@ -105,13 +109,16 @@ def flash_lk2nd(deviceinfo: Deviceinfo, method: str):
|
|||
# manually since supporting the codepath with heimdall requires more effort.
|
||||
pmb.flasher.init(deviceinfo.codename, method)
|
||||
logging.info("(native) checking current fastboot product")
|
||||
output = pmb.chroot.root(["fastboot", "getvar", "product"],
|
||||
output="interactive", output_return=True)
|
||||
output = pmb.chroot.root(
|
||||
["fastboot", "getvar", "product"], output="interactive", output_return=True
|
||||
)
|
||||
# Variable "product" is e.g. "LK2ND_MSM8974" or "lk2nd-msm8226" depending
|
||||
# on the lk2nd version.
|
||||
if "lk2nd" in output.lower():
|
||||
raise RuntimeError("You are currently running lk2nd. Please reboot into the regular"
|
||||
" bootloader mode to re-flash lk2nd.")
|
||||
raise RuntimeError(
|
||||
"You are currently running lk2nd. Please reboot into the regular"
|
||||
" bootloader mode to re-flash lk2nd."
|
||||
)
|
||||
|
||||
# Get the lk2nd package (which is a dependency of the device package)
|
||||
device_pkg = f"device-{deviceinfo.codename}"
|
||||
|
@ -141,8 +148,7 @@ def frontend(args: PmbArgs):
|
|||
deviceinfo = pmb.parse.deviceinfo()
|
||||
method = args.flash_method or deviceinfo.flash_method
|
||||
|
||||
if method == "none" and action in ["boot", "flash_kernel", "flash_rootfs",
|
||||
"flash_lk2nd"]:
|
||||
if method == "none" and action in ["boot", "flash_kernel", "flash_rootfs", "flash_lk2nd"]:
|
||||
logging.info("This device doesn't support any flash method.")
|
||||
return
|
||||
|
||||
|
|
|
@ -11,29 +11,30 @@ from pmb.core import Chroot, ChrootType
|
|||
|
||||
def install_depends(method: str) -> None:
|
||||
if method not in pmb.config.flashers:
|
||||
raise RuntimeError(f"Flash method {method} is not supported by the"
|
||||
" current configuration. However, adding a new"
|
||||
" flash method is not that hard, when the flashing"
|
||||
" application already exists.\n"
|
||||
"Make sure, it is packaged for Alpine Linux, or"
|
||||
" package it yourself, and then add it to"
|
||||
" pmb/config/__init__.py.")
|
||||
raise RuntimeError(
|
||||
f"Flash method {method} is not supported by the"
|
||||
" current configuration. However, adding a new"
|
||||
" flash method is not that hard, when the flashing"
|
||||
" application already exists.\n"
|
||||
"Make sure, it is packaged for Alpine Linux, or"
|
||||
" package it yourself, and then add it to"
|
||||
" pmb/config/__init__.py."
|
||||
)
|
||||
depends = pmb.config.flashers[method]["depends"]
|
||||
|
||||
# Depends for some flash methods may be different for various pmaports
|
||||
# branches, so read them from pmaports.cfg.
|
||||
if method == "fastboot":
|
||||
pmaports_cfg = pmb.config.pmaports.read_config()
|
||||
depends = pmaports_cfg.get("supported_fastboot_depends",
|
||||
"android-tools,avbtool").split(",")
|
||||
depends = pmaports_cfg.get("supported_fastboot_depends", "android-tools,avbtool").split(",")
|
||||
elif method == "heimdall-bootimg":
|
||||
pmaports_cfg = pmb.config.pmaports.read_config()
|
||||
depends = pmaports_cfg.get("supported_heimdall_depends",
|
||||
"heimdall,avbtool").split(",")
|
||||
depends = pmaports_cfg.get("supported_heimdall_depends", "heimdall,avbtool").split(",")
|
||||
elif method == "mtkclient":
|
||||
pmaports_cfg = pmb.config.pmaports.read_config()
|
||||
depends = pmaports_cfg.get("supported_mtkclient_depends",
|
||||
"mtkclient,android-tools").split(",")
|
||||
depends = pmaports_cfg.get("supported_mtkclient_depends", "mtkclient,android-tools").split(
|
||||
","
|
||||
)
|
||||
|
||||
pmb.chroot.apk.install(depends, Chroot.native())
|
||||
|
||||
|
|
|
@ -12,9 +12,16 @@ def check_partition_blacklist(deviceinfo: Deviceinfo, key, value):
|
|||
|
||||
name = deviceinfo.name
|
||||
if value in (deviceinfo.partition_blacklist or "").split(","):
|
||||
raise RuntimeError("'" + value + "'" + " partition is blacklisted " +
|
||||
"from being flashed! See the " + name + " device " +
|
||||
"wiki page for more information.")
|
||||
raise RuntimeError(
|
||||
"'"
|
||||
+ value
|
||||
+ "'"
|
||||
+ " partition is blacklisted "
|
||||
+ "from being flashed! See the "
|
||||
+ name
|
||||
+ " device "
|
||||
+ "wiki page for more information."
|
||||
)
|
||||
|
||||
|
||||
def run(deviceinfo: Deviceinfo, method: str, action: str, flavor=None):
|
||||
|
@ -25,12 +32,14 @@ def run(deviceinfo: Deviceinfo, method: str, action: str, flavor=None):
|
|||
if not isinstance(cfg["actions"], dict):
|
||||
raise TypeError(f"Flashers misconfigured! {method} key 'actions' should be a dictionary")
|
||||
if action not in cfg["actions"]:
|
||||
raise RuntimeError("action " + action + " is not"
|
||||
" configured for method " + method + "!"
|
||||
" You can use the '--method' option to specify a"
|
||||
" different flash method. See also:"
|
||||
" <https://wiki.postmarketos.org/wiki/"
|
||||
"Deviceinfo_flash_methods>")
|
||||
raise RuntimeError(
|
||||
"action " + action + " is not"
|
||||
" configured for method " + method + "!"
|
||||
" You can use the '--method' option to specify a"
|
||||
" different flash method. See also:"
|
||||
" <https://wiki.postmarketos.org/wiki/"
|
||||
"Deviceinfo_flash_methods>"
|
||||
)
|
||||
|
||||
# Variable setup
|
||||
# FIXME: handle argparsing and pass in only the args we need.
|
||||
|
@ -39,30 +48,36 @@ def run(deviceinfo: Deviceinfo, method: str, action: str, flavor=None):
|
|||
|
||||
# vbmeta flasher requires vbmeta partition to be explicitly specified
|
||||
if action == "flash_vbmeta" and not fvars["$PARTITION_VBMETA"]:
|
||||
raise RuntimeError("Your device does not have 'vbmeta' partition"
|
||||
" specified; set"
|
||||
" 'deviceinfo_flash_fastboot_partition_vbmeta'"
|
||||
" or 'deviceinfo_flash_heimdall_partition_vbmeta'"
|
||||
" in deviceinfo file. See also:"
|
||||
" <https://wiki.postmarketos.org/wiki/"
|
||||
"Deviceinfo_reference>")
|
||||
raise RuntimeError(
|
||||
"Your device does not have 'vbmeta' partition"
|
||||
" specified; set"
|
||||
" 'deviceinfo_flash_fastboot_partition_vbmeta'"
|
||||
" or 'deviceinfo_flash_heimdall_partition_vbmeta'"
|
||||
" in deviceinfo file. See also:"
|
||||
" <https://wiki.postmarketos.org/wiki/"
|
||||
"Deviceinfo_reference>"
|
||||
)
|
||||
|
||||
# dtbo flasher requires dtbo partition to be explicitly specified
|
||||
if action == "flash_dtbo" and not fvars["$PARTITION_DTBO"]:
|
||||
raise RuntimeError("Your device does not have 'dtbo' partition"
|
||||
" specified; set"
|
||||
" 'deviceinfo_flash_fastboot_partition_dtbo'"
|
||||
" in deviceinfo file. See also:"
|
||||
" <https://wiki.postmarketos.org/wiki/"
|
||||
"Deviceinfo_reference>")
|
||||
raise RuntimeError(
|
||||
"Your device does not have 'dtbo' partition"
|
||||
" specified; set"
|
||||
" 'deviceinfo_flash_fastboot_partition_dtbo'"
|
||||
" in deviceinfo file. See also:"
|
||||
" <https://wiki.postmarketos.org/wiki/"
|
||||
"Deviceinfo_reference>"
|
||||
)
|
||||
|
||||
if args.no_reboot and ("flash" not in action or method != "heimdall-bootimg"):
|
||||
raise RuntimeError("The '--no-reboot' option is only"
|
||||
" supported when flashing with heimall-bootimg.")
|
||||
raise RuntimeError(
|
||||
"The '--no-reboot' option is only" " supported when flashing with heimall-bootimg."
|
||||
)
|
||||
|
||||
if args.resume and ("flash" not in action or method != "heimdall-bootimg"):
|
||||
raise RuntimeError("The '--resume' option is only"
|
||||
" supported when flashing with heimall-bootimg.")
|
||||
raise RuntimeError(
|
||||
"The '--resume' option is only" " supported when flashing with heimall-bootimg."
|
||||
)
|
||||
|
||||
# Run the commands of each action
|
||||
for command in cfg["actions"][action]:
|
||||
|
@ -71,15 +86,17 @@ def run(deviceinfo: Deviceinfo, method: str, action: str, flavor=None):
|
|||
for i in range(len(command)):
|
||||
if key in command[i]:
|
||||
if value is None:
|
||||
raise RuntimeError(f"Variable {key} found in action"
|
||||
f" {action} for method {method},"
|
||||
" but the value for this variable"
|
||||
" is None! Is that missing in your"
|
||||
" deviceinfo?")
|
||||
raise RuntimeError(
|
||||
f"Variable {key} found in action"
|
||||
f" {action} for method {method},"
|
||||
" but the value for this variable"
|
||||
" is None! Is that missing in your"
|
||||
" deviceinfo?"
|
||||
)
|
||||
check_partition_blacklist(deviceinfo, key, value)
|
||||
command[i] = command[i].replace(key, value)
|
||||
|
||||
# Remove empty strings
|
||||
command = [x for x in command if x != '']
|
||||
command = [x for x in command if x != ""]
|
||||
# Run the action
|
||||
pmb.chroot.root(command, output="interactive")
|
||||
|
|
|
@ -24,40 +24,36 @@ def variables(args: PmbArgs, flavor: str, method: str):
|
|||
_partition_rootfs: Optional[str]
|
||||
|
||||
if method.startswith("fastboot"):
|
||||
_partition_kernel = deviceinfo.flash_fastboot_partition_kernel\
|
||||
or "boot"
|
||||
_partition_rootfs = deviceinfo.flash_fastboot_partition_rootfs\
|
||||
or deviceinfo.flash_fastboot_partition_system or "userdata"
|
||||
_partition_vbmeta = deviceinfo.flash_fastboot_partition_vbmeta\
|
||||
or None
|
||||
_partition_dtbo = deviceinfo.flash_fastboot_partition_dtbo\
|
||||
or None
|
||||
_partition_kernel = deviceinfo.flash_fastboot_partition_kernel or "boot"
|
||||
_partition_rootfs = (
|
||||
deviceinfo.flash_fastboot_partition_rootfs
|
||||
or deviceinfo.flash_fastboot_partition_system
|
||||
or "userdata"
|
||||
)
|
||||
_partition_vbmeta = deviceinfo.flash_fastboot_partition_vbmeta or None
|
||||
_partition_dtbo = deviceinfo.flash_fastboot_partition_dtbo or None
|
||||
# Require that the partitions are specified in deviceinfo for now
|
||||
elif method.startswith("rkdeveloptool"):
|
||||
_partition_kernel = deviceinfo.flash_rk_partition_kernel\
|
||||
or None
|
||||
_partition_rootfs = deviceinfo.flash_rk_partition_rootfs\
|
||||
or deviceinfo.flash_rk_partition_system or None
|
||||
_partition_kernel = deviceinfo.flash_rk_partition_kernel or None
|
||||
_partition_rootfs = (
|
||||
deviceinfo.flash_rk_partition_rootfs or deviceinfo.flash_rk_partition_system or None
|
||||
)
|
||||
_partition_vbmeta = None
|
||||
_partition_dtbo = None
|
||||
elif method.startswith("mtkclient"):
|
||||
_partition_kernel = deviceinfo.flash_mtkclient_partition_kernel\
|
||||
or "boot"
|
||||
_partition_rootfs = deviceinfo.flash_mtkclient_partition_rootfs\
|
||||
or "userdata"
|
||||
_partition_vbmeta = deviceinfo.flash_mtkclient_partition_vbmeta\
|
||||
or None
|
||||
_partition_dtbo = deviceinfo.flash_mtkclient_partition_dtbo\
|
||||
or None
|
||||
_partition_kernel = deviceinfo.flash_mtkclient_partition_kernel or "boot"
|
||||
_partition_rootfs = deviceinfo.flash_mtkclient_partition_rootfs or "userdata"
|
||||
_partition_vbmeta = deviceinfo.flash_mtkclient_partition_vbmeta or None
|
||||
_partition_dtbo = deviceinfo.flash_mtkclient_partition_dtbo or None
|
||||
else:
|
||||
_partition_kernel = deviceinfo.flash_heimdall_partition_kernel\
|
||||
or "KERNEL"
|
||||
_partition_rootfs = deviceinfo.flash_heimdall_partition_rootfs\
|
||||
or deviceinfo.flash_heimdall_partition_system or "SYSTEM"
|
||||
_partition_vbmeta = deviceinfo.flash_heimdall_partition_vbmeta\
|
||||
or None
|
||||
_partition_dtbo = deviceinfo.flash_heimdall_partition_dtbo\
|
||||
or None
|
||||
_partition_kernel = deviceinfo.flash_heimdall_partition_kernel or "KERNEL"
|
||||
_partition_rootfs = (
|
||||
deviceinfo.flash_heimdall_partition_rootfs
|
||||
or deviceinfo.flash_heimdall_partition_system
|
||||
or "SYSTEM"
|
||||
)
|
||||
_partition_vbmeta = deviceinfo.flash_heimdall_partition_vbmeta or None
|
||||
_partition_dtbo = deviceinfo.flash_heimdall_partition_dtbo or None
|
||||
|
||||
if "partition" in args and args.partition:
|
||||
# Only one operation is done at same time so it doesn't matter
|
||||
|
@ -72,11 +68,11 @@ def variables(args: PmbArgs, flavor: str, method: str):
|
|||
_dtb = "-dtb"
|
||||
|
||||
_no_reboot = ""
|
||||
if getattr(args, 'no_reboot', False):
|
||||
if getattr(args, "no_reboot", False):
|
||||
_no_reboot = "--no-reboot"
|
||||
|
||||
_resume = ""
|
||||
if getattr(args,'resume', False):
|
||||
if getattr(args, "resume", False):
|
||||
_resume = "--resume"
|
||||
|
||||
fvars = {
|
||||
|
@ -93,12 +89,12 @@ def variables(args: PmbArgs, flavor: str, method: str):
|
|||
"$PARTITION_DTBO": _partition_dtbo,
|
||||
"$FLASH_PAGESIZE": flash_pagesize,
|
||||
"$RECOVERY_ZIP": f"/mnt/{Chroot.buildroot(deviceinfo.arch)}"
|
||||
"/var/lib/postmarketos-android-recovery-installer"
|
||||
f"/pmos-{device}.zip",
|
||||
"/var/lib/postmarketos-android-recovery-installer"
|
||||
f"/pmos-{device}.zip",
|
||||
"$UUU_SCRIPT": f"/mnt/{Chroot.rootfs(deviceinfo.codename)}"
|
||||
"/usr/share/uuu/flash_script.lst",
|
||||
"/usr/share/uuu/flash_script.lst",
|
||||
"$NO_REBOOT": _no_reboot,
|
||||
"$RESUME": _resume
|
||||
"$RESUME": _resume,
|
||||
}
|
||||
|
||||
# Backwards compatibility with old mkinitfs (pma#660)
|
||||
|
|
|
@ -13,6 +13,7 @@ import pmb.helpers.run_core
|
|||
import pmb.parse.version
|
||||
from pmb.core.context import get_context
|
||||
|
||||
|
||||
def _prepare_fifo():
|
||||
"""Prepare the progress fifo for reading / writing.
|
||||
|
||||
|
@ -54,7 +55,7 @@ def _compute_progress(line):
|
|||
"""
|
||||
if not line:
|
||||
return 1
|
||||
cur_tot = line.rstrip().split('/')
|
||||
cur_tot = line.rstrip().split("/")
|
||||
if len(cur_tot) != 2:
|
||||
return 0
|
||||
cur = float(cur_tot[0])
|
||||
|
@ -80,17 +81,14 @@ def apk_with_progress(command: Sequence[PathString]):
|
|||
_command.append(os.fspath(c))
|
||||
command_with_progress = _create_command_with_progress(_command, fifo)
|
||||
log_msg = " ".join(_command)
|
||||
with pmb.helpers.run.root(['cat', fifo],
|
||||
output="pipe") as p_cat:
|
||||
with pmb.helpers.run.root(command_with_progress,
|
||||
output="background") as p_apk:
|
||||
with pmb.helpers.run.root(["cat", fifo], output="pipe") as p_cat:
|
||||
with pmb.helpers.run.root(command_with_progress, output="background") as p_apk:
|
||||
while p_apk.poll() is None:
|
||||
line = p_cat.stdout.readline().decode('utf-8')
|
||||
line = p_cat.stdout.readline().decode("utf-8")
|
||||
progress = _compute_progress(line)
|
||||
pmb.helpers.cli.progress_print(progress)
|
||||
pmb.helpers.cli.progress_flush()
|
||||
pmb.helpers.run_core.check_return_code(p_apk.returncode,
|
||||
log_msg)
|
||||
pmb.helpers.run_core.check_return_code(p_apk.returncode, log_msg)
|
||||
|
||||
|
||||
def check_outdated(version_installed, action_msg):
|
||||
|
@ -111,6 +109,8 @@ def check_outdated(version_installed, action_msg):
|
|||
if pmb.parse.version.compare(version_installed, version_min) >= 0:
|
||||
return
|
||||
|
||||
raise RuntimeError("Found an outdated version of the 'apk' package"
|
||||
f" manager ({version_installed}, expected at least:"
|
||||
f" {version_min}). {action_msg}")
|
||||
raise RuntimeError(
|
||||
"Found an outdated version of the 'apk' package"
|
||||
f" manager ({version_installed}, expected at least:"
|
||||
f" {version_min}). {action_msg}"
|
||||
)
|
||||
|
|
|
@ -34,17 +34,18 @@ def init_req_headers() -> None:
|
|||
if req_headers and req_headers_github:
|
||||
return
|
||||
# Generic request headers
|
||||
req_headers = {
|
||||
'User-Agent': f'pmbootstrap/{pmb.__version__} aportupgrade'}
|
||||
req_headers = {"User-Agent": f"pmbootstrap/{pmb.__version__} aportupgrade"}
|
||||
|
||||
# Request headers specific to GitHub
|
||||
req_headers_github = dict(req_headers)
|
||||
if os.getenv("GITHUB_TOKEN") is not None:
|
||||
token = os.getenv("GITHUB_TOKEN")
|
||||
req_headers_github['Authorization'] = f'token {token}'
|
||||
req_headers_github["Authorization"] = f"token {token}"
|
||||
else:
|
||||
logging.info("NOTE: Consider using a GITHUB_TOKEN environment variable"
|
||||
" to increase your rate limit")
|
||||
logging.info(
|
||||
"NOTE: Consider using a GITHUB_TOKEN environment variable"
|
||||
" to increase your rate limit"
|
||||
)
|
||||
|
||||
|
||||
def get_package_version_info_github(repo_name: str, ref: Optional[str]):
|
||||
|
@ -57,8 +58,8 @@ def get_package_version_info_github(repo_name: str, ref: Optional[str]):
|
|||
|
||||
# Get the commits for the repository
|
||||
commits = pmb.helpers.http.retrieve_json(
|
||||
f"{GITHUB_API_BASE}/repos/{repo_name}/commits{ref_arg}",
|
||||
headers=req_headers_github)
|
||||
f"{GITHUB_API_BASE}/repos/{repo_name}/commits{ref_arg}", headers=req_headers_github
|
||||
)
|
||||
latest_commit = commits[0]
|
||||
commit_date = latest_commit["commit"]["committer"]["date"]
|
||||
# Extract the time from the field
|
||||
|
@ -69,11 +70,10 @@ def get_package_version_info_github(repo_name: str, ref: Optional[str]):
|
|||
}
|
||||
|
||||
|
||||
def get_package_version_info_gitlab(gitlab_host: str, repo_name: str,
|
||||
ref: Optional[str]):
|
||||
def get_package_version_info_gitlab(gitlab_host: str, repo_name: str, ref: Optional[str]):
|
||||
logging.debug("Trying GitLab repository: {}".format(repo_name))
|
||||
|
||||
repo_name_safe = urllib.parse.quote(repo_name, safe='')
|
||||
repo_name_safe = urllib.parse.quote(repo_name, safe="")
|
||||
|
||||
# Get the URL argument to request a special ref, if needed
|
||||
ref_arg = ""
|
||||
|
@ -82,9 +82,9 @@ def get_package_version_info_gitlab(gitlab_host: str, repo_name: str,
|
|||
|
||||
# Get the commits for the repository
|
||||
commits = pmb.helpers.http.retrieve_json(
|
||||
f"{gitlab_host}/api/v4/projects/{repo_name_safe}/repository"
|
||||
f"/commits{ref_arg}",
|
||||
headers=req_headers)
|
||||
f"{gitlab_host}/api/v4/projects/{repo_name_safe}/repository" f"/commits{ref_arg}",
|
||||
headers=req_headers,
|
||||
)
|
||||
latest_commit = commits[0]
|
||||
commit_date = latest_commit["committed_date"]
|
||||
# Extract the time from the field
|
||||
|
@ -108,21 +108,20 @@ def upgrade_git_package(args: PmbArgs, pkgname: str, package) -> None:
|
|||
if 1 <= len(source) <= 2:
|
||||
source = source[-1]
|
||||
else:
|
||||
raise RuntimeError("Unhandled number of source elements. Please open"
|
||||
f" a bug report: {source}")
|
||||
raise RuntimeError(
|
||||
"Unhandled number of source elements. Please open" f" a bug report: {source}"
|
||||
)
|
||||
|
||||
verinfo = None
|
||||
|
||||
github_match = re.match(
|
||||
r"https://github\.com/(.+)/(?:archive|releases)", source)
|
||||
gitlab_match = re.match(
|
||||
fr"({'|'.join(GITLAB_HOSTS)})/(.+)/-/archive/", source)
|
||||
github_match = re.match(r"https://github\.com/(.+)/(?:archive|releases)", source)
|
||||
gitlab_match = re.match(rf"({'|'.join(GITLAB_HOSTS)})/(.+)/-/archive/", source)
|
||||
if github_match:
|
||||
verinfo = get_package_version_info_github(
|
||||
github_match.group(1), args.ref)
|
||||
verinfo = get_package_version_info_github(github_match.group(1), args.ref)
|
||||
elif gitlab_match:
|
||||
verinfo = get_package_version_info_gitlab(
|
||||
gitlab_match.group(1), gitlab_match.group(2), args.ref)
|
||||
gitlab_match.group(1), gitlab_match.group(2), args.ref
|
||||
)
|
||||
|
||||
if verinfo is None:
|
||||
# ignore for now
|
||||
|
@ -183,11 +182,13 @@ def upgrade_stable_package(args: PmbArgs, pkgname: str, package) -> None:
|
|||
# Looking up if there's a custom mapping from postmarketOS package name
|
||||
# to Anitya project name.
|
||||
mappings = pmb.helpers.http.retrieve_json(
|
||||
f"{ANITYA_API_BASE}/packages/?distribution=postmarketOS"
|
||||
f"&name={pkgname}", headers=req_headers)
|
||||
f"{ANITYA_API_BASE}/packages/?distribution=postmarketOS" f"&name={pkgname}",
|
||||
headers=req_headers,
|
||||
)
|
||||
if mappings["total_items"] < 1:
|
||||
projects = pmb.helpers.http.retrieve_json(
|
||||
f"{ANITYA_API_BASE}/projects/?name={pkgname}", headers=req_headers)
|
||||
f"{ANITYA_API_BASE}/projects/?name={pkgname}", headers=req_headers
|
||||
)
|
||||
if projects["total_items"] < 1:
|
||||
logging.warning(f"{pkgname}: failed to get Anitya project")
|
||||
return
|
||||
|
@ -195,17 +196,19 @@ def upgrade_stable_package(args: PmbArgs, pkgname: str, package) -> None:
|
|||
project_name = mappings["items"][0]["project"]
|
||||
ecosystem = mappings["items"][0]["ecosystem"]
|
||||
projects = pmb.helpers.http.retrieve_json(
|
||||
f"{ANITYA_API_BASE}/projects/?name={project_name}&"
|
||||
f"ecosystem={ecosystem}",
|
||||
headers=req_headers)
|
||||
f"{ANITYA_API_BASE}/projects/?name={project_name}&" f"ecosystem={ecosystem}",
|
||||
headers=req_headers,
|
||||
)
|
||||
|
||||
if projects["total_items"] < 1:
|
||||
logging.warning(f"{pkgname}: didn't find any projects, can't upgrade!")
|
||||
return
|
||||
if projects["total_items"] > 1:
|
||||
logging.warning(f"{pkgname}: found more than one project, can't "
|
||||
f"upgrade! Please create an explicit mapping of "
|
||||
f"\"project\" to the package name.")
|
||||
logging.warning(
|
||||
f"{pkgname}: found more than one project, can't "
|
||||
f"upgrade! Please create an explicit mapping of "
|
||||
f'"project" to the package name.'
|
||||
)
|
||||
return
|
||||
|
||||
# Get the first, best-matching item
|
||||
|
@ -234,8 +237,7 @@ def upgrade_stable_package(args: PmbArgs, pkgname: str, package) -> None:
|
|||
pkgrel_new = 0
|
||||
|
||||
if not pmb.parse.version.validate(pkgver_new):
|
||||
logging.warning(f"{pkgname}: would upgrade to invalid pkgver:"
|
||||
f" {pkgver_new}, ignoring")
|
||||
logging.warning(f"{pkgname}: would upgrade to invalid pkgver:" f" {pkgver_new}, ignoring")
|
||||
return
|
||||
|
||||
logging.info("{}: upgrading pmaport".format(pkgname))
|
||||
|
@ -285,5 +287,4 @@ def upgrade_all(args: PmbArgs) -> None:
|
|||
if skip:
|
||||
continue
|
||||
|
||||
upgrade(args, pkgname, args.all or args.all_git,
|
||||
args.all or args.all_stable)
|
||||
upgrade(args, pkgname, args.all or args.all_git, args.all or args.all_stable)
|
||||
|
|
|
@ -55,8 +55,9 @@ def init(args: PmbArgs) -> PmbArgs:
|
|||
config = pmb.config.load(args.config)
|
||||
|
||||
if args.aports and not args.aports.exists():
|
||||
raise ValueError("pmaports path (specified with --aports) does"
|
||||
f" not exist: {args.aports}")
|
||||
raise ValueError(
|
||||
"pmaports path (specified with --aports) does" f" not exist: {args.aports}"
|
||||
)
|
||||
|
||||
# Override config at runtime with command line arguments
|
||||
for key, _ in vars(config).items():
|
||||
|
@ -97,8 +98,16 @@ def init(args: PmbArgs) -> PmbArgs:
|
|||
pmb.helpers.logging.init(context.log, args.verbose, context.details_to_stdout)
|
||||
|
||||
# Initialization code which may raise errors
|
||||
if args.action not in ["init", "checksum", "config", "bootimg_analyze", "log",
|
||||
"pull", "shutdown", "zap"]:
|
||||
if args.action not in [
|
||||
"init",
|
||||
"checksum",
|
||||
"config",
|
||||
"bootimg_analyze",
|
||||
"log",
|
||||
"pull",
|
||||
"shutdown",
|
||||
"zap",
|
||||
]:
|
||||
pmb.config.pmaports.read_config()
|
||||
pmb.helpers.git.parse_channels_cfg(pkgrepo_default_path())
|
||||
|
||||
|
@ -112,15 +121,15 @@ def init(args: PmbArgs) -> PmbArgs:
|
|||
delattr(args, "force")
|
||||
if hasattr(args, "device"):
|
||||
delattr(args, "device")
|
||||
|
||||
|
||||
# Copy all properties from args to out that don't start with underscores
|
||||
for key, value in vars(args).items():
|
||||
if not key.startswith("_") and not key == "from_argparse":
|
||||
setattr(__args, key, value)
|
||||
|
||||
#print(json.dumps(__args.__dict__))
|
||||
# print(json.dumps(__args.__dict__))
|
||||
|
||||
#sys.exit(0)
|
||||
# sys.exit(0)
|
||||
|
||||
return __args
|
||||
|
||||
|
@ -143,8 +152,10 @@ def init(args: PmbArgs) -> PmbArgs:
|
|||
# for key in vars(args_new):
|
||||
# setattr(args, key, getattr(args_new, key))
|
||||
|
||||
|
||||
def please_i_really_need_args() -> PmbArgs:
|
||||
import traceback
|
||||
|
||||
traceback.print_stack()
|
||||
print("FIXME: retrieved args where it shouldn't be needed!")
|
||||
return __args
|
||||
|
|
|
@ -27,8 +27,7 @@ class ReadlineTabCompleter:
|
|||
# First time: build match list
|
||||
if iteration == 0:
|
||||
if input_text:
|
||||
self.matches = [s for s in self.options
|
||||
if s and s.startswith(input_text)]
|
||||
self.matches = [s for s in self.options if s and s.startswith(input_text)]
|
||||
else:
|
||||
self.matches = self.options[:]
|
||||
|
||||
|
@ -38,8 +37,14 @@ class ReadlineTabCompleter:
|
|||
return None
|
||||
|
||||
|
||||
def ask(question="Continue?", choices=["y", "n"], default="n",
|
||||
lowercase_answer=True, validation_regex=None, complete=None):
|
||||
def ask(
|
||||
question="Continue?",
|
||||
choices=["y", "n"],
|
||||
default="n",
|
||||
lowercase_answer=True,
|
||||
validation_regex=None,
|
||||
complete=None,
|
||||
):
|
||||
"""Ask a question on the terminal.
|
||||
|
||||
:param question: display prompt
|
||||
|
@ -62,13 +67,12 @@ def ask(question="Continue?", choices=["y", "n"], default="n",
|
|||
line = f"[{date}] {line}"
|
||||
|
||||
if complete:
|
||||
readline.parse_and_bind('tab: complete')
|
||||
readline.parse_and_bind("tab: complete")
|
||||
delims = readline.get_completer_delims()
|
||||
if '-' in delims:
|
||||
delims = delims.replace('-', '')
|
||||
if "-" in delims:
|
||||
delims = delims.replace("-", "")
|
||||
readline.set_completer_delims(delims)
|
||||
readline.set_completer(
|
||||
ReadlineTabCompleter(complete).completer_func)
|
||||
readline.set_completer(ReadlineTabCompleter(complete).completer_func)
|
||||
|
||||
ret = input(f"{line_color}: ")
|
||||
|
||||
|
@ -93,8 +97,11 @@ def ask(question="Continue?", choices=["y", "n"], default="n",
|
|||
if pattern.match(ret):
|
||||
return ret
|
||||
|
||||
logging.fatal("ERROR: Input did not pass validation (regex: " +
|
||||
validation_regex + "). Please try again.")
|
||||
logging.fatal(
|
||||
"ERROR: Input did not pass validation (regex: "
|
||||
+ validation_regex
|
||||
+ "). Please try again."
|
||||
)
|
||||
|
||||
|
||||
def confirm(question="Continue?", default=False, no_assumptions=False):
|
||||
|
|
|
@ -6,7 +6,7 @@ from typing import Optional
|
|||
from pmb.core.pkgrepo import pkgrepo_glob_one, pkgrepo_iglob
|
||||
|
||||
|
||||
def find_path(codename: str, file='') -> Optional[Path]:
|
||||
def find_path(codename: str, file="") -> Optional[Path]:
|
||||
"""Find path to device APKBUILD under `device/*/device-`.
|
||||
|
||||
:param codename: device codename
|
||||
|
@ -29,10 +29,10 @@ def list_codenames(vendor=None, archived=True):
|
|||
"""
|
||||
ret = []
|
||||
for path in pkgrepo_iglob("device/*/device-*"):
|
||||
if not archived and 'archived' in path.parts:
|
||||
if not archived and "archived" in path.parts:
|
||||
continue
|
||||
device = os.path.basename(path).split("-", 1)[1]
|
||||
if (vendor is None) or device.startswith(vendor + '-'):
|
||||
if (vendor is None) or device.startswith(vendor + "-"):
|
||||
ret.append(device)
|
||||
return ret
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ class BuildFailedError(Exception):
|
|||
"""Exception to be raised when pmbootstrap fails to build a package. This is handled
|
||||
separately from NonBugError as it needs to be treated differently as we want to hint
|
||||
to users that they can check the log for more information when a build fails."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
|
@ -13,4 +14,5 @@ class NonBugError(Exception):
|
|||
"""Exception which originates from a problem not caused by pmbootstrap's code. This
|
||||
could for example be raised if there is an error in a package pmboostrap is
|
||||
interacting with in some way."""
|
||||
|
||||
pass
|
||||
|
|
|
@ -39,8 +39,8 @@ def replace_apkbuild(args: PmbArgs, pkgname, key, new, in_quotes=False):
|
|||
line_old = '{}="{}"'.format(key, old)
|
||||
line_new = '{}="{}"'.format(key, new)
|
||||
else:
|
||||
line_old = '{}={}'.format(key, old)
|
||||
line_new = '{}={}'.format(key, new)
|
||||
line_old = "{}={}".format(key, old)
|
||||
line_new = "{}={}".format(key, new)
|
||||
|
||||
# Replace
|
||||
replace(path, "\n" + line_old + "\n", "\n" + line_new + "\n")
|
||||
|
@ -49,10 +49,11 @@ def replace_apkbuild(args: PmbArgs, pkgname, key, new, in_quotes=False):
|
|||
pmb.parse.apkbuild.cache_clear()
|
||||
apkbuild = pmb.parse.apkbuild(path)
|
||||
if apkbuild[key] != str(new):
|
||||
raise RuntimeError("Failed to set '{}' for pmaport '{}'. Make sure"
|
||||
" that there's a line with exactly the string '{}'"
|
||||
" and nothing else in: {}".format(key, pkgname,
|
||||
line_old, path))
|
||||
raise RuntimeError(
|
||||
"Failed to set '{}' for pmaport '{}'. Make sure"
|
||||
" that there's a line with exactly the string '{}'"
|
||||
" and nothing else in: {}".format(key, pkgname, line_old, path)
|
||||
)
|
||||
|
||||
|
||||
def is_up_to_date(path_sources, path_target=None, lastmod_target=None):
|
||||
|
@ -67,8 +68,7 @@ def is_up_to_date(path_sources, path_target=None, lastmod_target=None):
|
|||
"""
|
||||
|
||||
if path_target and lastmod_target:
|
||||
raise RuntimeError(
|
||||
"Specify path_target *or* lastmod_target, not both!")
|
||||
raise RuntimeError("Specify path_target *or* lastmod_target, not both!")
|
||||
|
||||
lastmod_source = None
|
||||
for path_source in path_sources:
|
||||
|
@ -93,8 +93,7 @@ def is_older_than(path, seconds):
|
|||
def symlink(file: Path, link: Path):
|
||||
"""Check if the symlink is already present, otherwise create it."""
|
||||
if os.path.exists(link):
|
||||
if (os.path.islink(link) and
|
||||
os.path.realpath(os.readlink(link)) == os.path.realpath(file)):
|
||||
if os.path.islink(link) and os.path.realpath(os.readlink(link)) == os.path.realpath(file):
|
||||
return
|
||||
raise RuntimeError(f"File exists: {link}")
|
||||
elif link.is_symlink():
|
||||
|
|
|
@ -52,13 +52,13 @@ def _parse_flavor(device: str, autoinstall=True):
|
|||
# "postmarketos-<manufacturer>-<device/chip>", e.g.
|
||||
# "postmarketos-qcom-sdm845"
|
||||
chroot = Chroot(ChrootType.ROOTFS, device)
|
||||
flavor = pmb.chroot.other.kernel_flavor_installed(
|
||||
chroot, autoinstall)
|
||||
flavor = pmb.chroot.other.kernel_flavor_installed(chroot, autoinstall)
|
||||
|
||||
if not flavor:
|
||||
raise RuntimeError(
|
||||
f"No kernel flavors installed in chroot '{chroot}'! Please let"
|
||||
" your device package depend on a package starting with 'linux-'.")
|
||||
f"No kernel flavors installed in chroot '{chroot}'! Please let"
|
||||
" your device package depend on a package starting with 'linux-'."
|
||||
)
|
||||
return flavor
|
||||
|
||||
|
||||
|
@ -93,11 +93,13 @@ def _install_ondev_verify_no_rootfs(device: str, ondev_cp: List[Tuple[str, str]]
|
|||
if chroot_dest_cp == chroot_dest:
|
||||
return
|
||||
|
||||
raise ValueError(f"--no-rootfs set, but rootfs.img not found in install"
|
||||
" chroot. Either run 'pmbootstrap install' without"
|
||||
" --no-rootfs first to let it generate the postmarketOS"
|
||||
" rootfs once, or supply a rootfs file with:"
|
||||
f" --cp os.img:{chroot_dest}")
|
||||
raise ValueError(
|
||||
f"--no-rootfs set, but rootfs.img not found in install"
|
||||
" chroot. Either run 'pmbootstrap install' without"
|
||||
" --no-rootfs first to let it generate the postmarketOS"
|
||||
" rootfs once, or supply a rootfs file with:"
|
||||
f" --cp os.img:{chroot_dest}"
|
||||
)
|
||||
|
||||
|
||||
def aportgen(args: PmbArgs):
|
||||
|
@ -116,8 +118,7 @@ def build(args: PmbArgs):
|
|||
return
|
||||
|
||||
# Set src and force
|
||||
src = os.path.realpath(os.path.expanduser(args.src[0])) \
|
||||
if args.src else None
|
||||
src = os.path.realpath(os.path.expanduser(args.src[0])) if args.src else None
|
||||
force = True if src else get_context().force
|
||||
if src and not os.path.exists(src):
|
||||
raise RuntimeError("Invalid path specified for --src: " + src)
|
||||
|
@ -125,19 +126,21 @@ def build(args: PmbArgs):
|
|||
# Ensure repo_bootstrap is done for all arches we intend to build for
|
||||
for package in args.packages:
|
||||
arch_package = args.arch or pmb.build.autodetect.arch(package)
|
||||
pmb.helpers.pmaports.require_bootstrap(arch_package,
|
||||
f"build {package} for {arch_package}")
|
||||
pmb.helpers.pmaports.require_bootstrap(arch_package, f"build {package} for {arch_package}")
|
||||
|
||||
context = get_context()
|
||||
# Build all packages
|
||||
built = pmb.build.packages(context, args.packages, args.arch, force,
|
||||
strict=args.strict, src=src)
|
||||
built = pmb.build.packages(
|
||||
context, args.packages, args.arch, force, strict=args.strict, src=src
|
||||
)
|
||||
|
||||
# Notify about packages that weren't built
|
||||
for package in set(args.packages) - set(built):
|
||||
logging.info("NOTE: Package '" + package + "' is up to date. Use"
|
||||
" 'pmbootstrap build " + package + " --force'"
|
||||
" if needed.")
|
||||
logging.info(
|
||||
"NOTE: Package '" + package + "' is up to date. Use"
|
||||
" 'pmbootstrap build " + package + " --force'"
|
||||
" if needed."
|
||||
)
|
||||
|
||||
|
||||
def build_init(args: PmbArgs):
|
||||
|
@ -157,8 +160,7 @@ def sideload(args: PmbArgs):
|
|||
arch = args.arch
|
||||
user = get_context().config.user
|
||||
host = args.host
|
||||
pmb.sideload.sideload(args, user, host, args.port, arch, args.install_key,
|
||||
args.packages)
|
||||
pmb.sideload.sideload(args, user, host, args.port, arch, args.install_key, args.packages)
|
||||
|
||||
|
||||
def netboot(args: PmbArgs):
|
||||
|
@ -171,10 +173,12 @@ def chroot(args: PmbArgs):
|
|||
# Suffix
|
||||
chroot = _parse_suffix(args)
|
||||
user = args.user
|
||||
if (user and chroot != Chroot.native() and
|
||||
chroot.type not in [ChrootType.BUILDROOT, ChrootType.IMAGE]):
|
||||
raise RuntimeError("--user is only supported for native or"
|
||||
" buildroot_* chroots.")
|
||||
if (
|
||||
user
|
||||
and chroot != Chroot.native()
|
||||
and chroot.type not in [ChrootType.BUILDROOT, ChrootType.IMAGE]
|
||||
):
|
||||
raise RuntimeError("--user is only supported for native or" " buildroot_* chroots.")
|
||||
if args.xauth and chroot != Chroot.native():
|
||||
raise RuntimeError("--xauth is only supported for native chroot.")
|
||||
|
||||
|
@ -197,28 +201,26 @@ def chroot(args: PmbArgs):
|
|||
|
||||
# Install blockdevice
|
||||
if args.install_blockdev:
|
||||
logging.warning("--install-blockdev is deprecated for the chroot command"
|
||||
" and will be removed in a future release. If you need this"
|
||||
" for some reason, please open an issue on"
|
||||
" https://gitlab.com/postmarketOS/pmbootstrap.git")
|
||||
logging.warning(
|
||||
"--install-blockdev is deprecated for the chroot command"
|
||||
" and will be removed in a future release. If you need this"
|
||||
" for some reason, please open an issue on"
|
||||
" https://gitlab.com/postmarketOS/pmbootstrap.git"
|
||||
)
|
||||
size_boot = 128 # 128 MiB
|
||||
size_root = 4096 # 4 GiB
|
||||
size_reserve = 2048 # 2 GiB
|
||||
pmb.install.blockdevice.create_and_mount_image(args, size_boot,
|
||||
size_root, size_reserve)
|
||||
pmb.install.blockdevice.create_and_mount_image(args, size_boot, size_root, size_reserve)
|
||||
|
||||
pmb.chroot.apk.update_repository_list(chroot, user_repository=True)
|
||||
|
||||
# Run the command as user/root
|
||||
if user:
|
||||
logging.info(f"({chroot}) % su pmos -c '" +
|
||||
" ".join(args.command) + "'")
|
||||
pmb.chroot.user(args.command, chroot, output=args.output,
|
||||
env=env)
|
||||
logging.info(f"({chroot}) % su pmos -c '" + " ".join(args.command) + "'")
|
||||
pmb.chroot.user(args.command, chroot, output=args.output, env=env)
|
||||
else:
|
||||
logging.info(f"({chroot}) % " + " ".join(args.command))
|
||||
pmb.chroot.root(args.command, chroot, output=args.output,
|
||||
env=env)
|
||||
pmb.chroot.root(args.command, chroot, output=args.output, env=env)
|
||||
|
||||
|
||||
def config(args: PmbArgs):
|
||||
|
@ -259,8 +261,7 @@ def config(args: PmbArgs):
|
|||
|
||||
|
||||
def repo_missing(args: PmbArgs):
|
||||
missing = pmb.helpers.repo_missing.generate(args.arch, args.overview,
|
||||
args.package, args.built)
|
||||
missing = pmb.helpers.repo_missing.generate(args.arch, args.overview, args.package, args.built)
|
||||
print(json.dumps(missing, indent=4))
|
||||
|
||||
|
||||
|
@ -273,52 +274,52 @@ def install(args: PmbArgs):
|
|||
device = config.device
|
||||
deviceinfo = pmb.parse.deviceinfo(device)
|
||||
if args.no_fde:
|
||||
logging.warning("WARNING: --no-fde is deprecated,"
|
||||
" as it is now the default.")
|
||||
logging.warning("WARNING: --no-fde is deprecated," " as it is now the default.")
|
||||
if args.rsync and args.full_disk_encryption:
|
||||
raise ValueError("Installation using rsync is not compatible with full"
|
||||
" disk encryption.")
|
||||
raise ValueError("Installation using rsync is not compatible with full" " disk encryption.")
|
||||
if args.rsync and not args.disk:
|
||||
raise ValueError("Installation using rsync only works with --disk.")
|
||||
|
||||
if args.rsync and args.filesystem == "btrfs":
|
||||
raise ValueError("Installation using rsync"
|
||||
" is not currently supported on btrfs filesystem.")
|
||||
raise ValueError(
|
||||
"Installation using rsync" " is not currently supported on btrfs filesystem."
|
||||
)
|
||||
|
||||
pmb.helpers.pmaports.require_bootstrap(deviceinfo.arch,
|
||||
f"do 'pmbootstrap install' for {deviceinfo.arch}"
|
||||
" (deviceinfo_arch)")
|
||||
pmb.helpers.pmaports.require_bootstrap(
|
||||
deviceinfo.arch, f"do 'pmbootstrap install' for {deviceinfo.arch}" " (deviceinfo_arch)"
|
||||
)
|
||||
|
||||
# On-device installer checks
|
||||
# Note that this can't be in the mutually exclusive group that has most of
|
||||
# the conflicting options, because then it would not work with --disk.
|
||||
if args.on_device_installer:
|
||||
if args.full_disk_encryption:
|
||||
raise ValueError("--on-device-installer cannot be combined with"
|
||||
" --fde. The user can choose to encrypt their"
|
||||
" installation later in the on-device installer.")
|
||||
raise ValueError(
|
||||
"--on-device-installer cannot be combined with"
|
||||
" --fde. The user can choose to encrypt their"
|
||||
" installation later in the on-device installer."
|
||||
)
|
||||
if args.android_recovery_zip:
|
||||
raise ValueError("--on-device-installer cannot be combined with"
|
||||
" --android-recovery-zip (patches welcome)")
|
||||
raise ValueError(
|
||||
"--on-device-installer cannot be combined with"
|
||||
" --android-recovery-zip (patches welcome)"
|
||||
)
|
||||
if args.no_image:
|
||||
raise ValueError("--on-device-installer cannot be combined with"
|
||||
" --no-image")
|
||||
raise ValueError("--on-device-installer cannot be combined with" " --no-image")
|
||||
if args.rsync:
|
||||
raise ValueError("--on-device-installer cannot be combined with"
|
||||
" --rsync")
|
||||
raise ValueError("--on-device-installer cannot be combined with" " --rsync")
|
||||
if args.filesystem:
|
||||
raise ValueError("--on-device-installer cannot be combined with"
|
||||
" --filesystem")
|
||||
raise ValueError("--on-device-installer cannot be combined with" " --filesystem")
|
||||
|
||||
if deviceinfo.cgpt_kpart:
|
||||
raise ValueError("--on-device-installer cannot be used with"
|
||||
" ChromeOS devices")
|
||||
raise ValueError("--on-device-installer cannot be used with" " ChromeOS devices")
|
||||
else:
|
||||
if args.ondev_cp:
|
||||
raise ValueError("--cp can only be combined with --ondev")
|
||||
if args.ondev_no_rootfs:
|
||||
raise ValueError("--no-rootfs can only be combined with --ondev."
|
||||
" Do you mean --no-image?")
|
||||
raise ValueError(
|
||||
"--no-rootfs can only be combined with --ondev." " Do you mean --no-image?"
|
||||
)
|
||||
if args.ondev_no_rootfs:
|
||||
_install_ondev_verify_no_rootfs(device, args.ondev_cp)
|
||||
|
||||
|
@ -330,9 +331,11 @@ def install(args: PmbArgs):
|
|||
# optionally add a new user for SSH that must not have the same
|
||||
# username etc.)
|
||||
if config.user != "user":
|
||||
logging.warning(f"WARNING: custom username '{config.user}' will be"
|
||||
" replaced with 'user' for the on-device"
|
||||
" installer.")
|
||||
logging.warning(
|
||||
f"WARNING: custom username '{config.user}' will be"
|
||||
" replaced with 'user' for the on-device"
|
||||
" installer."
|
||||
)
|
||||
config.user = "user"
|
||||
|
||||
if not args.disk and args.split is None:
|
||||
|
@ -343,17 +346,24 @@ def install(args: PmbArgs):
|
|||
|
||||
# Android recovery zip related
|
||||
if args.android_recovery_zip and args.filesystem:
|
||||
raise ValueError("--android-recovery-zip cannot be combined with"
|
||||
" --filesystem (patches welcome)")
|
||||
raise ValueError(
|
||||
"--android-recovery-zip cannot be combined with" " --filesystem (patches welcome)"
|
||||
)
|
||||
if args.android_recovery_zip and args.full_disk_encryption:
|
||||
logging.info("WARNING: --fde is rarely used in combination with"
|
||||
" --android-recovery-zip. If this does not work, consider"
|
||||
" using another method (e.g. installing via netcat)")
|
||||
logging.info("WARNING: the kernel of the recovery system (e.g. TWRP)"
|
||||
f" must support the cryptsetup cipher '{args.cipher}'.")
|
||||
logging.info("If you know what you are doing, consider setting a"
|
||||
" different cipher with 'pmbootstrap install --cipher=..."
|
||||
" --fde --android-recovery-zip'.")
|
||||
logging.info(
|
||||
"WARNING: --fde is rarely used in combination with"
|
||||
" --android-recovery-zip. If this does not work, consider"
|
||||
" using another method (e.g. installing via netcat)"
|
||||
)
|
||||
logging.info(
|
||||
"WARNING: the kernel of the recovery system (e.g. TWRP)"
|
||||
f" must support the cryptsetup cipher '{args.cipher}'."
|
||||
)
|
||||
logging.info(
|
||||
"If you know what you are doing, consider setting a"
|
||||
" different cipher with 'pmbootstrap install --cipher=..."
|
||||
" --fde --android-recovery-zip'."
|
||||
)
|
||||
|
||||
# Don't install locally compiled packages and package signing keys
|
||||
if not args.install_local_pkgs:
|
||||
|
@ -363,9 +373,11 @@ def install(args: PmbArgs):
|
|||
|
||||
# Safest way to avoid installing local packages is having none
|
||||
if (config.work / "packages").glob("*"):
|
||||
raise ValueError("--no-local-pkgs specified, but locally built"
|
||||
" packages found. Consider 'pmbootstrap zap -p'"
|
||||
" to delete them.")
|
||||
raise ValueError(
|
||||
"--no-local-pkgs specified, but locally built"
|
||||
" packages found. Consider 'pmbootstrap zap -p'"
|
||||
" to delete them."
|
||||
)
|
||||
|
||||
# Verify that the root filesystem is supported by current pmaports branch
|
||||
pmb.install.get_root_filesystem(args)
|
||||
|
@ -384,12 +396,16 @@ def export(args: PmbArgs):
|
|||
def update(args: PmbArgs):
|
||||
existing_only = not args.non_existing
|
||||
if not pmb.helpers.repo.update(args.arch, True, existing_only):
|
||||
logging.info("No APKINDEX files exist, so none have been updated."
|
||||
" The pmbootstrap command downloads the APKINDEX files on"
|
||||
" demand.")
|
||||
logging.info("If you want to force downloading the APKINDEX files for"
|
||||
" all architectures (not recommended), use:"
|
||||
" pmbootstrap update --non-existing")
|
||||
logging.info(
|
||||
"No APKINDEX files exist, so none have been updated."
|
||||
" The pmbootstrap command downloads the APKINDEX files on"
|
||||
" demand."
|
||||
)
|
||||
logging.info(
|
||||
"If you want to force downloading the APKINDEX files for"
|
||||
" all architectures (not recommended), use:"
|
||||
" pmbootstrap update --non-existing"
|
||||
)
|
||||
|
||||
|
||||
def newapkbuild(args: PmbArgs):
|
||||
|
@ -402,8 +418,9 @@ def newapkbuild(args: PmbArgs):
|
|||
|
||||
# Sanity check: -n is only allowed with SRCURL
|
||||
if args.pkgname and not is_url:
|
||||
raise RuntimeError("You can only specify a pkgname (-n) when using"
|
||||
" SRCURL as last parameter.")
|
||||
raise RuntimeError(
|
||||
"You can only specify a pkgname (-n) when using" " SRCURL as last parameter."
|
||||
)
|
||||
|
||||
# Passthrough: Strings (e.g. -d "my description")
|
||||
pass_through = []
|
||||
|
@ -413,8 +430,10 @@ def newapkbuild(args: PmbArgs):
|
|||
pass_through += [entry[0], value]
|
||||
|
||||
# Passthrough: Switches (e.g. -C for CMake)
|
||||
for entry in (pmb.config.newapkbuild_arguments_switches_pkgtypes +
|
||||
pmb.config.newapkbuild_arguments_switches_other):
|
||||
for entry in (
|
||||
pmb.config.newapkbuild_arguments_switches_pkgtypes
|
||||
+ pmb.config.newapkbuild_arguments_switches_other
|
||||
):
|
||||
if getattr(args, entry[1]) is True:
|
||||
pass_through.append(entry[0])
|
||||
|
||||
|
@ -434,8 +453,7 @@ def kconfig(args: PmbArgs):
|
|||
|
||||
# Handle passing a file directly
|
||||
if args.file:
|
||||
if pmb.parse.kconfig.check_file(args.file, components_list,
|
||||
details=details):
|
||||
if pmb.parse.kconfig.check_file(args.file, components_list, details=details):
|
||||
logging.info("kconfig check succeeded!")
|
||||
return
|
||||
raise RuntimeError("kconfig check failed!")
|
||||
|
@ -458,15 +476,13 @@ def kconfig(args: PmbArgs):
|
|||
packages.sort()
|
||||
for package in packages:
|
||||
if not get_context().force:
|
||||
pkgname = package if package.startswith("linux-") \
|
||||
else "linux-" + package
|
||||
pkgname = package if package.startswith("linux-") else "linux-" + package
|
||||
aport = pmb.helpers.pmaports.find(pkgname)
|
||||
apkbuild = pmb.parse.apkbuild(aport)
|
||||
if "!pmb:kconfigcheck" in apkbuild["options"]:
|
||||
skipped += 1
|
||||
continue
|
||||
if not pmb.parse.kconfig.check(package, components_list,
|
||||
details=details):
|
||||
if not pmb.parse.kconfig.check(package, components_list, details=details):
|
||||
error = True
|
||||
|
||||
# At least one failure
|
||||
|
@ -474,8 +490,10 @@ def kconfig(args: PmbArgs):
|
|||
raise RuntimeError("kconfig check failed!")
|
||||
else:
|
||||
if skipped:
|
||||
logging.info("NOTE: " + str(skipped) + " kernel(s) was skipped"
|
||||
" (consider 'pmbootstrap kconfig check -f')")
|
||||
logging.info(
|
||||
"NOTE: " + str(skipped) + " kernel(s) was skipped"
|
||||
" (consider 'pmbootstrap kconfig check -f')"
|
||||
)
|
||||
logging.info("kconfig check succeeded!")
|
||||
elif args.action_kconfig in ["edit", "migrate"]:
|
||||
if args.package:
|
||||
|
@ -496,8 +514,7 @@ def deviceinfo_parse(args: PmbArgs):
|
|||
kernel = args.deviceinfo_parse_kernel
|
||||
for device in devices:
|
||||
print(f"{device}, with kernel={kernel}:")
|
||||
print(json.dumps(pmb.parse.deviceinfo(device, kernel), indent=4,
|
||||
sort_keys=True))
|
||||
print(json.dumps(pmb.parse.deviceinfo(device, kernel), indent=4, sort_keys=True))
|
||||
|
||||
|
||||
def apkbuild_parse(args: PmbArgs):
|
||||
|
@ -510,8 +527,7 @@ def apkbuild_parse(args: PmbArgs):
|
|||
for package in packages:
|
||||
print(package + ":")
|
||||
aport = pmb.helpers.pmaports.find(package)
|
||||
print(json.dumps(pmb.parse.apkbuild(aport), indent=4,
|
||||
sort_keys=True))
|
||||
print(json.dumps(pmb.parse.apkbuild(aport), indent=4, sort_keys=True))
|
||||
|
||||
|
||||
def apkindex_parse(args: PmbArgs):
|
||||
|
@ -574,11 +590,16 @@ def work_migrate(args: PmbArgs):
|
|||
|
||||
|
||||
def zap(args: PmbArgs):
|
||||
pmb.chroot.zap(dry=args.dry, http=args.http,
|
||||
distfiles=args.distfiles, pkgs_local=args.pkgs_local,
|
||||
pkgs_local_mismatch=args.pkgs_local_mismatch,
|
||||
pkgs_online_mismatch=args.pkgs_online_mismatch,
|
||||
rust=args.rust, netboot=args.netboot)
|
||||
pmb.chroot.zap(
|
||||
dry=args.dry,
|
||||
http=args.http,
|
||||
distfiles=args.distfiles,
|
||||
pkgs_local=args.pkgs_local,
|
||||
pkgs_local_mismatch=args.pkgs_local_mismatch,
|
||||
pkgs_online_mismatch=args.pkgs_online_mismatch,
|
||||
rust=args.rust,
|
||||
netboot=args.netboot,
|
||||
)
|
||||
|
||||
# Don't write the "Done" message
|
||||
pmb.helpers.logging.disable()
|
||||
|
@ -587,8 +608,7 @@ def zap(args: PmbArgs):
|
|||
def bootimg_analyze(args: PmbArgs):
|
||||
bootimg = pmb.parse.bootimg(args.path)
|
||||
tmp_output = "Put these variables in the deviceinfo file of your device:\n"
|
||||
for line in pmb.aportgen.device.\
|
||||
generate_deviceinfo_fastboot_content(bootimg).split("\n"):
|
||||
for line in pmb.aportgen.device.generate_deviceinfo_fastboot_content(bootimg).split("\n"):
|
||||
tmp_output += "\n" + line.lstrip()
|
||||
logging.info(tmp_output)
|
||||
|
||||
|
@ -611,28 +631,33 @@ def status(args: PmbArgs) -> None:
|
|||
def ci(args: PmbArgs):
|
||||
topdir = pmb.helpers.git.get_topdir(Path.cwd())
|
||||
if not os.path.exists(topdir):
|
||||
logging.error("ERROR: change your current directory to a git"
|
||||
" repository (e.g. pmbootstrap, pmaports) before running"
|
||||
" 'pmbootstrap ci'.")
|
||||
logging.error(
|
||||
"ERROR: change your current directory to a git"
|
||||
" repository (e.g. pmbootstrap, pmaports) before running"
|
||||
" 'pmbootstrap ci'."
|
||||
)
|
||||
exit(1)
|
||||
|
||||
scripts_available = pmb.ci.get_ci_scripts(topdir)
|
||||
scripts_available = pmb.ci.sort_scripts_by_speed(scripts_available)
|
||||
if not scripts_available:
|
||||
logging.error("ERROR: no supported CI scripts found in current git"
|
||||
" repository, see https://postmarketos.org/pmb-ci")
|
||||
logging.error(
|
||||
"ERROR: no supported CI scripts found in current git"
|
||||
" repository, see https://postmarketos.org/pmb-ci"
|
||||
)
|
||||
exit(1)
|
||||
|
||||
scripts_selected = {}
|
||||
if args.scripts:
|
||||
if args.all:
|
||||
raise RuntimeError("Combining --all with script names doesn't"
|
||||
" make sense")
|
||||
raise RuntimeError("Combining --all with script names doesn't" " make sense")
|
||||
for script in args.scripts:
|
||||
if script not in scripts_available:
|
||||
logging.error(f"ERROR: script '{script}' not found in git"
|
||||
" repository, found these:"
|
||||
f" {', '.join(scripts_available.keys())}")
|
||||
logging.error(
|
||||
f"ERROR: script '{script}' not found in git"
|
||||
" repository, found these:"
|
||||
f" {', '.join(scripts_available.keys())}"
|
||||
)
|
||||
exit(1)
|
||||
scripts_selected[script] = scripts_available[script]
|
||||
elif args.all:
|
||||
|
|
|
@ -106,8 +106,11 @@ def get_upstream_remote(aports: Path):
|
|||
for line in output.split("\n"):
|
||||
if any(u in line for u in urls):
|
||||
return line.split("\t", 1)[0]
|
||||
raise RuntimeError("{}: could not find remote name for any URL '{}' in git"
|
||||
" repository: {}".format(name_repo, urls, aports))
|
||||
raise RuntimeError(
|
||||
"{}: could not find remote name for any URL '{}' in git" " repository: {}".format(
|
||||
name_repo, urls, aports
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@Cache("aports")
|
||||
|
@ -131,11 +134,14 @@ def parse_channels_cfg(aports: Path):
|
|||
try:
|
||||
cfg.read_string(stdout)
|
||||
except configparser.MissingSectionHeaderError:
|
||||
logging.info("NOTE: fix this by fetching your pmaports.git, e.g."
|
||||
" with 'pmbootstrap pull'")
|
||||
raise RuntimeError("Failed to read channels.cfg from"
|
||||
f" '{remote}/master' branch of your local"
|
||||
" pmaports clone")
|
||||
logging.info(
|
||||
"NOTE: fix this by fetching your pmaports.git, e.g." " with 'pmbootstrap pull'"
|
||||
)
|
||||
raise RuntimeError(
|
||||
"Failed to read channels.cfg from"
|
||||
f" '{remote}/master' branch of your local"
|
||||
" pmaports clone"
|
||||
)
|
||||
|
||||
# Meta section
|
||||
ret: Dict[str, Dict[str, str | Dict[str, str]]] = {"channels": {}}
|
||||
|
@ -149,11 +155,10 @@ def parse_channels_cfg(aports: Path):
|
|||
channel_new = pmb.helpers.pmaports.get_channel_new(channel)
|
||||
|
||||
ret["channels"][channel_new] = {}
|
||||
for key in ["description", "branch_pmaports", "branch_aports",
|
||||
"mirrordir_alpine"]:
|
||||
for key in ["description", "branch_pmaports", "branch_aports", "mirrordir_alpine"]:
|
||||
value = cfg.get(channel, key)
|
||||
# FIXME: how to type this properly??
|
||||
ret["channels"][channel_new][key] = value # type: ignore[index]
|
||||
ret["channels"][channel_new][key] = value # type: ignore[index]
|
||||
|
||||
return ret
|
||||
|
||||
|
@ -199,8 +204,10 @@ def pull(repo_name: str):
|
|||
official_looking_branches = "master, v24.06, …"
|
||||
else:
|
||||
official_looking_branches = "master, 3.20-stable, …"
|
||||
logging.warning(f"{msg_start} not on one of the official branches"
|
||||
f" ({official_looking_branches}), skipping pull!")
|
||||
logging.warning(
|
||||
f"{msg_start} not on one of the official branches"
|
||||
f" ({official_looking_branches}), skipping pull!"
|
||||
)
|
||||
return -1
|
||||
|
||||
# Skip if workdir is not clean
|
||||
|
@ -212,9 +219,11 @@ def pull(repo_name: str):
|
|||
branch_upstream = get_upstream_remote(repo) + "/" + branch
|
||||
remote_ref = rev_parse(repo, branch + "@{u}", ["--abbrev-ref"])
|
||||
if remote_ref != branch_upstream:
|
||||
logging.warning("{} is tracking unexpected remote branch '{}' instead"
|
||||
" of '{}'".format(msg_start, remote_ref,
|
||||
branch_upstream))
|
||||
logging.warning(
|
||||
"{} is tracking unexpected remote branch '{}' instead" " of '{}'".format(
|
||||
msg_start, remote_ref, branch_upstream
|
||||
)
|
||||
)
|
||||
return -3
|
||||
|
||||
# Fetch (exception on failure, meaning connection to server broke)
|
||||
|
@ -229,9 +238,11 @@ def pull(repo_name: str):
|
|||
|
||||
# Skip if we can't fast-forward
|
||||
if not can_fast_forward(repo, branch_upstream):
|
||||
logging.warning("{} can't fast-forward to {}, looks like you changed"
|
||||
" the git history of your local branch. Skipping pull!"
|
||||
"".format(msg_start, branch_upstream))
|
||||
logging.warning(
|
||||
"{} can't fast-forward to {}, looks like you changed"
|
||||
" the git history of your local branch. Skipping pull!"
|
||||
"".format(msg_start, branch_upstream)
|
||||
)
|
||||
return -4
|
||||
|
||||
# Fast-forward now (should not fail due to checks above, so it's fine to
|
||||
|
@ -247,8 +258,9 @@ def get_topdir(repo: Path):
|
|||
:returns: a string with the top dir of the git repository,
|
||||
or an empty string if it's not a git repository.
|
||||
"""
|
||||
res = pmb.helpers.run.user(["git", "rev-parse", "--show-toplevel"],
|
||||
repo, output_return=True, check=False)
|
||||
res = pmb.helpers.run.user(
|
||||
["git", "rev-parse", "--show-toplevel"], repo, output_return=True, check=False
|
||||
)
|
||||
if not isinstance(res, str):
|
||||
raise RuntimeError("Not a git repository: " + str(repo))
|
||||
return res.strip()
|
||||
|
@ -265,9 +277,9 @@ def get_files(repo: Path):
|
|||
"""
|
||||
ret = []
|
||||
files = pmb.helpers.run.user_output(["git", "ls-files"], repo).split("\n")
|
||||
files += pmb.helpers.run.user_output(["git", "ls-files",
|
||||
"--exclude-standard", "--other"],
|
||||
repo).split("\n")
|
||||
files += pmb.helpers.run.user_output(
|
||||
["git", "ls-files", "--exclude-standard", "--other"], repo
|
||||
).split("\n")
|
||||
for file in files:
|
||||
if os.path.exists(f"{repo}/{file}"):
|
||||
ret += [file]
|
||||
|
|
|
@ -11,13 +11,13 @@ import urllib.request
|
|||
from pmb.core.context import get_context
|
||||
import pmb.helpers.run
|
||||
|
||||
|
||||
def cache_file(prefix: str, url: str) -> Path:
|
||||
prefix = prefix.replace("/", "_")
|
||||
return Path(f"{prefix}_{hashlib.sha256(url.encode('utf-8')).hexdigest()}")
|
||||
|
||||
|
||||
def download(url, prefix, cache=True, loglevel=logging.INFO,
|
||||
allow_404=False):
|
||||
def download(url, prefix, cache=True, loglevel=logging.INFO, allow_404=False):
|
||||
"""Download a file to disk.
|
||||
|
||||
:param url: the http(s) address of to the file to download
|
||||
|
@ -47,8 +47,7 @@ def download(url, prefix, cache=True, loglevel=logging.INFO,
|
|||
|
||||
# Offline and not cached
|
||||
if context.offline:
|
||||
raise RuntimeError("File not found in cache and offline flag is"
|
||||
f" enabled: {url}")
|
||||
raise RuntimeError("File not found in cache and offline flag is" f" enabled: {url}")
|
||||
|
||||
# Download the file
|
||||
logging.log(loglevel, "Download " + url)
|
||||
|
@ -72,9 +71,9 @@ def retrieve(url, headers=None, allow_404=False):
|
|||
|
||||
:param url: the http(s) address of to the resource to fetch
|
||||
:param headers: dict of HTTP headers to use
|
||||
:param allow_404: do not raise an exception when the server responds with a
|
||||
:param allow_404: do not raise an exception when the server responds with a
|
||||
404 Not Found error. Only display a warning
|
||||
|
||||
|
||||
:returns: str with the content of the response
|
||||
"""
|
||||
# Download the file
|
||||
|
|
|
@ -12,6 +12,7 @@ import pmb.build
|
|||
import pmb.helpers.run
|
||||
import pmb.helpers.pmaports
|
||||
|
||||
|
||||
# FIXME: dest_paths[repo], repo expected to be a Literal.
|
||||
# We should really make Config.mirrors not a TypedDict.
|
||||
# mypy: disable-error-code="index"
|
||||
|
@ -57,8 +58,11 @@ def check(pkgnames: Sequence[str]):
|
|||
|
||||
# For each pkgrepo run the linter on the relevant packages
|
||||
for pkgrepo, apkbuild_paths in apkbuilds.items():
|
||||
pmb.chroot.root(["apkbuild-lint"] + apkbuild_paths,
|
||||
check=False, output="stdout",
|
||||
output_return=True,
|
||||
working_dir=dest_paths[repo],
|
||||
env={"CUSTOM_VALID_OPTIONS": " ".join(options)})
|
||||
pmb.chroot.root(
|
||||
["apkbuild-lint"] + apkbuild_paths,
|
||||
check=False,
|
||||
output="stdout",
|
||||
output_return=True,
|
||||
working_dir=dest_paths[repo],
|
||||
env={"CUSTOM_VALID_OPTIONS": " ".join(options)},
|
||||
)
|
||||
|
|
|
@ -19,10 +19,11 @@ DEBUG = logging.DEBUG
|
|||
NOTSET = logging.NOTSET
|
||||
VERBOSE = 5
|
||||
|
||||
|
||||
class log_handler(logging.StreamHandler):
|
||||
"""Write to stdout and to the already opened log file."""
|
||||
|
||||
def __init__(self, details_to_stdout: bool=False, quiet: bool=False):
|
||||
|
||||
def __init__(self, details_to_stdout: bool = False, quiet: bool = False):
|
||||
super().__init__()
|
||||
self.details_to_stdout = details_to_stdout
|
||||
self.quiet = False
|
||||
|
@ -32,9 +33,7 @@ class log_handler(logging.StreamHandler):
|
|||
msg = self.format(record)
|
||||
|
||||
# INFO or higher: Write to stdout
|
||||
if (self.details_to_stdout or
|
||||
(not self.quiet and
|
||||
record.levelno >= logging.INFO)):
|
||||
if self.details_to_stdout or (not self.quiet and record.levelno >= logging.INFO):
|
||||
stream = self.stream
|
||||
|
||||
styles = pmb.config.styles
|
||||
|
@ -102,14 +101,17 @@ def add_verbose_log_level():
|
|||
"""
|
||||
setattr(logging, "VERBOSE", VERBOSE)
|
||||
logging.addLevelName(VERBOSE, "VERBOSE")
|
||||
setattr(logging.Logger, "verbose", lambda inst, msg, * \
|
||||
args, **kwargs: inst.log(VERBOSE, msg, *args, **kwargs))
|
||||
setattr(logging, "verbose", lambda msg, *args, **kwargs: logging.log(VERBOSE,
|
||||
msg, *args,
|
||||
**kwargs))
|
||||
setattr(
|
||||
logging.Logger,
|
||||
"verbose",
|
||||
lambda inst, msg, *args, **kwargs: inst.log(VERBOSE, msg, *args, **kwargs),
|
||||
)
|
||||
setattr(
|
||||
logging, "verbose", lambda msg, *args, **kwargs: logging.log(VERBOSE, msg, *args, **kwargs)
|
||||
)
|
||||
|
||||
|
||||
def init(logfile: Path, verbose: bool, details_to_stdout: bool=False):
|
||||
def init(logfile: Path, verbose: bool, details_to_stdout: bool = False):
|
||||
"""Set log format and add the log file descriptor to logfd, add the verbose log level."""
|
||||
global logfd
|
||||
|
||||
|
@ -131,8 +133,7 @@ def init(logfile: Path, verbose: bool, details_to_stdout: bool=False):
|
|||
# Set log format
|
||||
root_logger = logging.getLogger()
|
||||
root_logger.handlers = []
|
||||
formatter = logging.Formatter("[%(asctime)s] %(message)s",
|
||||
datefmt="%H:%M:%S")
|
||||
formatter = logging.Formatter("[%(asctime)s] %(message)s", datefmt="%H:%M:%S")
|
||||
|
||||
# Set log level
|
||||
add_verbose_log_level()
|
||||
|
@ -147,7 +148,7 @@ def init(logfile: Path, verbose: bool, details_to_stdout: bool=False):
|
|||
|
||||
logging.debug(f"Pmbootstrap v{pmb.__version__} (Python {sys.version})")
|
||||
if "--password" in sys.argv:
|
||||
sys.argv[sys.argv.index("--password")+1] = "[REDACTED]"
|
||||
sys.argv[sys.argv.index("--password") + 1] = "[REDACTED]"
|
||||
logging.debug(f"$ pmbootstrap {' '.join(sys.argv)}")
|
||||
|
||||
|
||||
|
@ -159,6 +160,7 @@ def disable():
|
|||
# We have our own logging wrappers so we can make mypy happy
|
||||
# by not calling the (undefined) logging.verbose() function.
|
||||
|
||||
|
||||
def critical(msg: object, *args, **kwargs):
|
||||
logging.critical(msg, *args, **kwargs)
|
||||
|
||||
|
@ -184,7 +186,7 @@ def debug(msg: object, *args, **kwargs):
|
|||
|
||||
|
||||
def verbose(msg: object, *args, **kwargs):
|
||||
logging.verbose(msg, *args, **kwargs) # type: ignore[attr-defined]
|
||||
logging.verbose(msg, *args, **kwargs) # type: ignore[attr-defined]
|
||||
|
||||
|
||||
def log(level: int, msg: object, *args, **kwargs):
|
||||
|
|
|
@ -69,11 +69,10 @@ def bind_file(source: Path, destination: Path, create_folders=False):
|
|||
pmb.helpers.run.root(["touch", destination])
|
||||
|
||||
# Mount
|
||||
pmb.helpers.run.root(["mount", "--bind", source,
|
||||
destination])
|
||||
pmb.helpers.run.root(["mount", "--bind", source, destination])
|
||||
|
||||
|
||||
def umount_all_list(prefix: Path, source: Path=Path("/proc/mounts")) -> List[Path]:
|
||||
def umount_all_list(prefix: Path, source: Path = Path("/proc/mounts")) -> List[Path]:
|
||||
"""Parse `/proc/mounts` for all folders beginning with a prefix.
|
||||
|
||||
:source: can be changed for testcases
|
||||
|
@ -89,7 +88,7 @@ def umount_all_list(prefix: Path, source: Path=Path("/proc/mounts")) -> List[Pat
|
|||
if len(words) < 2:
|
||||
raise RuntimeError(f"Failed to parse line in {source}: {line}")
|
||||
mountpoint = Path(words[1].replace(r"\040(deleted)", ""))
|
||||
if mountpoint.is_relative_to(prefix): # is subpath
|
||||
if mountpoint.is_relative_to(prefix): # is subpath
|
||||
ret.append(mountpoint)
|
||||
ret.sort(reverse=True)
|
||||
return ret
|
||||
|
@ -112,6 +111,5 @@ def mount_device_rootfs(chroot_rootfs: Chroot) -> PurePath:
|
|||
:returns: the mountpoint (relative to the native chroot)
|
||||
"""
|
||||
mountpoint = PurePath("/mnt", chroot_rootfs.dirname)
|
||||
pmb.helpers.mount.bind(chroot_rootfs.path,
|
||||
Chroot.native() / mountpoint)
|
||||
pmb.helpers.mount.bind(chroot_rootfs.path, Chroot.native() / mountpoint)
|
||||
return mountpoint
|
||||
|
|
|
@ -15,7 +15,6 @@ import pmb.helpers.run
|
|||
from typing import Dict, Any
|
||||
|
||||
|
||||
|
||||
def folder_size(path: Path):
|
||||
"""Run `du` to calculate the size of a folder.
|
||||
|
||||
|
@ -24,8 +23,7 @@ def folder_size(path: Path):
|
|||
|
||||
:returns: folder size in kilobytes
|
||||
"""
|
||||
output = pmb.helpers.run.root(["du", "-ks",
|
||||
path], output_return=True)
|
||||
output = pmb.helpers.run.root(["du", "-ks", path], output_return=True)
|
||||
|
||||
# Only look at last line to filter out sudo garbage (#1766)
|
||||
last_line = output.split("\n")[-2]
|
||||
|
@ -44,8 +42,9 @@ def check_grsec():
|
|||
if not os.path.exists(path):
|
||||
return
|
||||
|
||||
raise RuntimeError("You're running a kernel based on the grsec"
|
||||
" patchset. This is not supported.")
|
||||
raise RuntimeError(
|
||||
"You're running a kernel based on the grsec" " patchset. This is not supported."
|
||||
)
|
||||
|
||||
|
||||
def check_binfmt_misc():
|
||||
|
@ -64,8 +63,9 @@ def check_binfmt_misc():
|
|||
pmb.helpers.run.root(["modprobe", "binfmt_misc"], check=False)
|
||||
|
||||
# check=False: we check it below and print a more helpful message on error
|
||||
pmb.helpers.run.root(["mount", "-t", "binfmt_misc", "none",
|
||||
"/proc/sys/fs/binfmt_misc"], check=False)
|
||||
pmb.helpers.run.root(
|
||||
["mount", "-t", "binfmt_misc", "none", "/proc/sys/fs/binfmt_misc"], check=False
|
||||
)
|
||||
|
||||
if not os.path.exists(path):
|
||||
link = "https://postmarketos.org/binfmt_misc"
|
||||
|
@ -91,9 +91,10 @@ def migrate_work_folder(args: PmbArgs):
|
|||
required = pmb.config.work_version
|
||||
if current == required:
|
||||
return
|
||||
logging.info("WARNING: Your work folder version needs to be migrated"
|
||||
" (from version " + str(current) + " to " + str(required) +
|
||||
")!")
|
||||
logging.info(
|
||||
"WARNING: Your work folder version needs to be migrated"
|
||||
" (from version " + str(current) + " to " + str(required) + ")!"
|
||||
)
|
||||
|
||||
# 0 => 1
|
||||
if current == 0:
|
||||
|
@ -110,8 +111,7 @@ def migrate_work_folder(args: PmbArgs):
|
|||
pmb.chroot.zap(False)
|
||||
conf = context.config.work / "config_abuild/abuild.conf"
|
||||
if os.path.exists(conf):
|
||||
pmb.helpers.run.root(["sed", "-i",
|
||||
"s./home/user/./home/pmos/.g", conf])
|
||||
pmb.helpers.run.root(["sed", "-i", "s./home/user/./home/pmos/.g", conf])
|
||||
# Update version file
|
||||
migrate_success(context.config.work, 1)
|
||||
current = 1
|
||||
|
@ -128,9 +128,11 @@ def migrate_work_folder(args: PmbArgs):
|
|||
|
||||
# Fix permissions
|
||||
dir = "/var/cache/distfiles"
|
||||
for cmd in [["chown", "-R", "root:abuild", dir],
|
||||
["chmod", "-R", "664", dir],
|
||||
["chmod", "a+X", dir]]:
|
||||
for cmd in [
|
||||
["chown", "-R", "root:abuild", dir],
|
||||
["chmod", "-R", "664", dir],
|
||||
["chmod", "a+X", dir],
|
||||
]:
|
||||
pmb.chroot.root(cmd)
|
||||
migrate_success(context.config.work, 2)
|
||||
current = 2
|
||||
|
@ -196,13 +198,14 @@ def migrate_work_folder(args: PmbArgs):
|
|||
new_path = edge_path / arch
|
||||
if old_path.exists():
|
||||
if new_path.exists():
|
||||
raise RuntimeError(f"Won't move '{old_path}' to"
|
||||
f" '{new_path}', destination already"
|
||||
" exists! Consider 'pmbootstrap zap -p'"
|
||||
f" to delete '{context.config.work}/packages'.")
|
||||
raise RuntimeError(
|
||||
f"Won't move '{old_path}' to"
|
||||
f" '{new_path}', destination already"
|
||||
" exists! Consider 'pmbootstrap zap -p'"
|
||||
f" to delete '{context.config.work}/packages'."
|
||||
)
|
||||
pmb.helpers.run.root(["mv", old_path, new_path])
|
||||
pmb.helpers.run.root(["chown", pmb.config.chroot_uid_user,
|
||||
edge_path])
|
||||
pmb.helpers.run.root(["chown", pmb.config.chroot_uid_user, edge_path])
|
||||
|
||||
# Update version file
|
||||
migrate_success(context.config.work, 5)
|
||||
|
@ -238,12 +241,14 @@ def migrate_work_folder(args: PmbArgs):
|
|||
|
||||
# Can't migrate, user must delete it
|
||||
if current != required:
|
||||
raise RuntimeError("Sorry, we can't migrate that automatically. Please"
|
||||
" run 'pmbootstrap shutdown', then delete your"
|
||||
" current work folder manually ('sudo rm -rf "
|
||||
f"{context.config.work}') and start over with 'pmbootstrap"
|
||||
" init'. All your binary packages and caches will"
|
||||
" be lost.")
|
||||
raise RuntimeError(
|
||||
"Sorry, we can't migrate that automatically. Please"
|
||||
" run 'pmbootstrap shutdown', then delete your"
|
||||
" current work folder manually ('sudo rm -rf "
|
||||
f"{context.config.work}') and start over with 'pmbootstrap"
|
||||
" init'. All your binary packages and caches will"
|
||||
" be lost."
|
||||
)
|
||||
|
||||
|
||||
def validate_hostname(hostname):
|
||||
|
@ -259,14 +264,15 @@ def validate_hostname(hostname):
|
|||
|
||||
# Check that it only contains valid chars
|
||||
if not re.match(r"^[0-9a-z-\.]*$", hostname):
|
||||
logging.fatal("ERROR: Hostname must only contain letters (a-z),"
|
||||
" digits (0-9), minus signs (-), or periods (.)")
|
||||
logging.fatal(
|
||||
"ERROR: Hostname must only contain letters (a-z),"
|
||||
" digits (0-9), minus signs (-), or periods (.)"
|
||||
)
|
||||
return False
|
||||
|
||||
# Check that doesn't begin or end with a minus sign or period
|
||||
if re.search(r"^-|^\.|-$|\.$", hostname):
|
||||
logging.fatal("ERROR: Hostname must not begin or end with a minus"
|
||||
" sign or period")
|
||||
logging.fatal("ERROR: Hostname must not begin or end with a minus" " sign or period")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
|
|
@ -8,6 +8,7 @@ See also:
|
|||
|
||||
- pmb/helpers/repo.py (work with binary package repos)
|
||||
"""
|
||||
|
||||
import copy
|
||||
from typing import Any, Dict
|
||||
from pmb.core.arch import Arch
|
||||
|
@ -52,11 +53,13 @@ def get(pkgname, arch, replace_subpkgnames=False, must_exist=True):
|
|||
ret: Dict[str, Any] = {}
|
||||
pmaport = pmb.helpers.pmaports.get(pkgname, False)
|
||||
if pmaport:
|
||||
ret = {"arch": pmaport["arch"],
|
||||
"depends": pmb.build._package.get_depends(get_context(), pmaport),
|
||||
"pkgname": pmaport["pkgname"],
|
||||
"provides": pmaport["provides"],
|
||||
"version": pmaport["pkgver"] + "-r" + pmaport["pkgrel"]}
|
||||
ret = {
|
||||
"arch": pmaport["arch"],
|
||||
"depends": pmb.build._package.get_depends(get_context(), pmaport),
|
||||
"pkgname": pmaport["pkgname"],
|
||||
"provides": pmaport["provides"],
|
||||
"version": pmaport["pkgver"] + "-r" + pmaport["pkgrel"],
|
||||
}
|
||||
|
||||
# Find in APKINDEX (given arch)
|
||||
if not ret or not pmb.helpers.pmaports.check_arches(ret["arch"], arch):
|
||||
|
@ -93,8 +96,7 @@ def get(pkgname, arch, replace_subpkgnames=False, must_exist=True):
|
|||
for depend in ret["depends"]:
|
||||
depend_data = get(depend, arch, must_exist=False)
|
||||
if not depend_data:
|
||||
logging.warning(f"WARNING: {pkgname}: failed to resolve"
|
||||
f" dependency '{depend}'")
|
||||
logging.warning(f"WARNING: {pkgname}: failed to resolve" f" dependency '{depend}'")
|
||||
# Can't replace potential subpkgname
|
||||
if depend not in depends_new:
|
||||
depends_new += [depend]
|
||||
|
@ -111,8 +113,10 @@ def get(pkgname, arch, replace_subpkgnames=False, must_exist=True):
|
|||
# Could not find the package
|
||||
if not must_exist:
|
||||
return None
|
||||
raise RuntimeError("Package '" + pkgname + "': Could not find aport, and"
|
||||
" could not find this package in any APKINDEX!")
|
||||
raise RuntimeError(
|
||||
"Package '" + pkgname + "': Could not find aport, and"
|
||||
" could not find this package in any APKINDEX!"
|
||||
)
|
||||
|
||||
|
||||
@Cache("pkgname", "arch")
|
||||
|
|
|
@ -25,8 +25,16 @@ def package(args: PmbArgs, pkgname, reason="", dry=False):
|
|||
pkgrel_new = pkgrel + 1
|
||||
|
||||
# Display the message, bail out in dry mode
|
||||
logging.info("Increase '" + pkgname + "' pkgrel (" + str(pkgrel) + " -> " +
|
||||
str(pkgrel_new) + ")" + reason)
|
||||
logging.info(
|
||||
"Increase '"
|
||||
+ pkgname
|
||||
+ "' pkgrel ("
|
||||
+ str(pkgrel)
|
||||
+ " -> "
|
||||
+ str(pkgrel_new)
|
||||
+ ")"
|
||||
+ reason
|
||||
)
|
||||
if dry:
|
||||
return
|
||||
|
||||
|
@ -39,9 +47,11 @@ def package(args: PmbArgs, pkgname, reason="", dry=False):
|
|||
pmb.parse.apkbuild.cache_clear()
|
||||
apkbuild = pmb.parse.apkbuild(path)
|
||||
if int(apkbuild["pkgrel"]) != pkgrel_new:
|
||||
raise RuntimeError(f"Failed to bump pkgrel for package '{pkgname}'."
|
||||
" Make sure that there's a line with exactly the"
|
||||
f" string '{old.strip()}' and nothing else in: {path}")
|
||||
raise RuntimeError(
|
||||
f"Failed to bump pkgrel for package '{pkgname}'."
|
||||
" Make sure that there's a line with exactly the"
|
||||
f" string '{old.strip()}' and nothing else in: {path}"
|
||||
)
|
||||
|
||||
|
||||
def auto_apkindex_package(args: PmbArgs, arch, aport, apk, dry=False):
|
||||
|
@ -62,42 +72,38 @@ def auto_apkindex_package(args: PmbArgs, arch, aport, apk, dry=False):
|
|||
# Skip when aport version != binary package version
|
||||
compare = pmb.parse.version.compare(version_aport, version_apk)
|
||||
if compare == -1:
|
||||
logging.warning("{}: skipping, because the aport version {} is lower"
|
||||
" than the binary version {}".format(pkgname,
|
||||
version_aport,
|
||||
version_apk))
|
||||
logging.warning(
|
||||
"{}: skipping, because the aport version {} is lower"
|
||||
" than the binary version {}".format(pkgname, version_aport, version_apk)
|
||||
)
|
||||
return
|
||||
if compare == 1:
|
||||
logging.verbose("{}: skipping, because the aport version {} is higher"
|
||||
" than the binary version {}".format(pkgname,
|
||||
version_aport,
|
||||
version_apk))
|
||||
logging.verbose(
|
||||
"{}: skipping, because the aport version {} is higher"
|
||||
" than the binary version {}".format(pkgname, version_aport, version_apk)
|
||||
)
|
||||
return
|
||||
|
||||
# Find missing depends
|
||||
depends = apk["depends"]
|
||||
logging.verbose("{}: checking depends: {}".format(pkgname,
|
||||
", ".join(depends)))
|
||||
logging.verbose("{}: checking depends: {}".format(pkgname, ", ".join(depends)))
|
||||
missing = []
|
||||
for depend in depends:
|
||||
if depend.startswith("!"):
|
||||
# Ignore conflict-dependencies
|
||||
continue
|
||||
|
||||
providers = pmb.parse.apkindex.providers(depend, arch,
|
||||
must_exist=False)
|
||||
providers = pmb.parse.apkindex.providers(depend, arch, must_exist=False)
|
||||
if providers == {}:
|
||||
# We're only interested in missing depends starting with "so:"
|
||||
# (which means dynamic libraries that the package was linked
|
||||
# against) and packages for which no aport exists.
|
||||
if (depend.startswith("so:") or
|
||||
not pmb.helpers.pmaports.find_optional(depend)):
|
||||
if depend.startswith("so:") or not pmb.helpers.pmaports.find_optional(depend):
|
||||
missing.append(depend)
|
||||
|
||||
# Increase pkgrel
|
||||
if len(missing):
|
||||
package(args, pkgname, reason=", missing depend(s): " +
|
||||
", ".join(missing), dry=dry)
|
||||
package(args, pkgname, reason=", missing depend(s): " + ", ".join(missing), dry=dry)
|
||||
return True
|
||||
|
||||
|
||||
|
@ -113,13 +119,11 @@ def auto(args: PmbArgs, dry=False):
|
|||
origin = apk["origin"]
|
||||
# Only increase once!
|
||||
if origin in ret:
|
||||
logging.verbose(
|
||||
f"{pkgname}: origin '{origin}' found again")
|
||||
logging.verbose(f"{pkgname}: origin '{origin}' found again")
|
||||
continue
|
||||
aport_path = pmb.helpers.pmaports.find_optional(origin)
|
||||
if not aport_path:
|
||||
logging.warning("{}: origin '{}' aport not found".format(
|
||||
pkgname, origin))
|
||||
logging.warning("{}: origin '{}' aport not found".format(pkgname, origin))
|
||||
continue
|
||||
aport = pmb.parse.apkbuild(aport_path)
|
||||
if auto_apkindex_package(args, arch, aport, apk, dry):
|
||||
|
|
|
@ -6,6 +6,7 @@ See also:
|
|||
- pmb/helpers/repo.py (work with binary package repos)
|
||||
- pmb/helpers/package.py (work with both)
|
||||
"""
|
||||
|
||||
from pmb.core.context import get_context
|
||||
from pmb.core.arch import Arch
|
||||
from pmb.core.pkgrepo import pkgrepo_iter_package_dirs
|
||||
|
@ -16,6 +17,7 @@ from typing import Any, Optional, Sequence, Dict, Tuple
|
|||
from pmb.meta import Cache
|
||||
import pmb.parse
|
||||
|
||||
|
||||
def _find_apkbuilds(skip_extra_repos=False) -> Dict[str, Path]:
|
||||
# Try to get a cached result first (we assume that the aports don't change
|
||||
# in one pmbootstrap call)
|
||||
|
@ -27,8 +29,10 @@ def _find_apkbuilds(skip_extra_repos=False) -> Dict[str, Path]:
|
|||
for package in pkgrepo_iter_package_dirs(skip_extra_repos=skip_extra_repos):
|
||||
pkgname = package.name
|
||||
if pkgname in apkbuilds:
|
||||
raise RuntimeError(f"Package {pkgname} found in multiple aports "
|
||||
"subfolders. Please put it only in one folder.")
|
||||
raise RuntimeError(
|
||||
f"Package {pkgname} found in multiple aports "
|
||||
"subfolders. Please put it only in one folder."
|
||||
)
|
||||
apkbuilds[pkgname] = package / "APKBUILD"
|
||||
|
||||
# Sort dictionary so we don't need to do it over and over again in
|
||||
|
@ -42,7 +46,7 @@ def _find_apkbuilds(skip_extra_repos=False) -> Dict[str, Path]:
|
|||
|
||||
|
||||
def get_list() -> Sequence[str]:
|
||||
""" :returns: list of all pmaport pkgnames (["hello-world", ...]) """
|
||||
""":returns: list of all pmaport pkgnames (["hello-world", ...])"""
|
||||
return list(_find_apkbuilds().keys())
|
||||
|
||||
|
||||
|
@ -57,13 +61,18 @@ def guess_main_dev(subpkgname) -> Optional[Path]:
|
|||
pkgname = subpkgname[:-4]
|
||||
path = _find_apkbuilds().get(pkgname)
|
||||
if path:
|
||||
logging.verbose(subpkgname + ": guessed to be a subpackage of " +
|
||||
pkgname + " (just removed '-dev')")
|
||||
logging.verbose(
|
||||
subpkgname + ": guessed to be a subpackage of " + pkgname + " (just removed '-dev')"
|
||||
)
|
||||
return path.parent
|
||||
|
||||
logging.verbose(subpkgname + ": guessed to be a subpackage of " + pkgname +
|
||||
", which we can't find in pmaports, so it's probably in"
|
||||
" Alpine")
|
||||
logging.verbose(
|
||||
subpkgname
|
||||
+ ": guessed to be a subpackage of "
|
||||
+ pkgname
|
||||
+ ", which we can't find in pmaports, so it's probably in"
|
||||
" Alpine"
|
||||
)
|
||||
return None
|
||||
|
||||
|
||||
|
@ -99,12 +108,12 @@ def guess_main(subpkgname) -> Optional[Path]:
|
|||
# Look in pmaports
|
||||
path = _find_apkbuilds().get(pkgname)
|
||||
if path:
|
||||
logging.verbose(subpkgname + ": guessed to be a subpackage of " +
|
||||
pkgname)
|
||||
logging.verbose(subpkgname + ": guessed to be a subpackage of " + pkgname)
|
||||
return path.parent
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def _find_package_in_apkbuild(package: str, path: Path) -> bool:
|
||||
"""Look through subpackages and all provides to see if the APKBUILD at the specified path
|
||||
contains (or provides) the specified package.
|
||||
|
@ -190,8 +199,7 @@ def find(package, must_exist=True, subpackages=True, skip_extra_repos=False):
|
|||
|
||||
# Crash when necessary
|
||||
if ret is None and must_exist:
|
||||
raise RuntimeError("Could not find aport for package: " +
|
||||
package)
|
||||
raise RuntimeError("Could not find aport for package: " + package)
|
||||
|
||||
return ret
|
||||
|
||||
|
@ -205,7 +213,9 @@ def find_optional(package: str) -> Optional[Path]:
|
|||
|
||||
# The only caller with subpackages=False is ui.check_option()
|
||||
@Cache("pkgname", subpackages=True)
|
||||
def get_with_path(pkgname, must_exist=True, subpackages=True, skip_extra_repos=False) -> Tuple[Optional[Path], Optional[Dict[str, Any]]]:
|
||||
def get_with_path(
|
||||
pkgname, must_exist=True, subpackages=True, skip_extra_repos=False
|
||||
) -> Tuple[Optional[Path], Optional[Dict[str, Any]]]:
|
||||
"""Find and parse an APKBUILD file.
|
||||
|
||||
Run 'pmbootstrap apkbuild_parse hello-world' for a full output example.
|
||||
|
@ -229,7 +239,7 @@ def get_with_path(pkgname, must_exist=True, subpackages=True, skip_extra_repos=F
|
|||
pkgname = pmb.helpers.package.remove_operators(pkgname)
|
||||
pmaport = find(pkgname, must_exist, subpackages, skip_extra_repos)
|
||||
if pmaport:
|
||||
return pmaport, pmb.parse.apkbuild(pmaport / "APKBUILD")
|
||||
return pmaport, pmb.parse.apkbuild(pmaport / "APKBUILD")
|
||||
return None, None
|
||||
|
||||
|
||||
|
@ -237,7 +247,7 @@ def get(pkgname, must_exist=True, subpackages=True, skip_extra_repos=False) -> D
|
|||
return get_with_path(pkgname, must_exist, subpackages, skip_extra_repos)[1]
|
||||
|
||||
|
||||
def find_providers( provide):
|
||||
def find_providers(provide):
|
||||
"""Search for providers of the specified (virtual) package in pmaports.
|
||||
|
||||
Note: Currently only providers from a single APKBUILD are returned.
|
||||
|
@ -257,8 +267,7 @@ def find_providers( provide):
|
|||
if provides.split("=", 1)[0] == provide:
|
||||
providers[subpkgname] = subpkg
|
||||
|
||||
return sorted(providers.items(), reverse=True,
|
||||
key=lambda p: p[1].get('provider_priority', 0))
|
||||
return sorted(providers.items(), reverse=True, key=lambda p: p[1].get("provider_priority", 0))
|
||||
|
||||
|
||||
# FIXME (#2324): split into an _optional variant or drop must_exist
|
||||
|
@ -315,6 +324,7 @@ def get_channel_new(channel: str) -> str:
|
|||
return ret
|
||||
return channel
|
||||
|
||||
|
||||
def require_bootstrap_error(repo, arch, trigger_str):
|
||||
"""
|
||||
Tell the user that they need to do repo_bootstrap, with some context.
|
||||
|
@ -323,10 +333,13 @@ def require_bootstrap_error(repo, arch, trigger_str):
|
|||
:param arch: for which architecture
|
||||
:param trigger_str: message for the user to understand what caused this
|
||||
"""
|
||||
logging.info(f"ERROR: Trying to {trigger_str} with {repo} enabled, but the"
|
||||
f" {repo} repo needs to be bootstrapped first.")
|
||||
raise RuntimeError(f"Run 'pmbootstrap repo_bootstrap {repo} --arch={arch}'"
|
||||
" and then try again.")
|
||||
logging.info(
|
||||
f"ERROR: Trying to {trigger_str} with {repo} enabled, but the"
|
||||
f" {repo} repo needs to be bootstrapped first."
|
||||
)
|
||||
raise RuntimeError(
|
||||
f"Run 'pmbootstrap repo_bootstrap {repo} --arch={arch}'" " and then try again."
|
||||
)
|
||||
|
||||
|
||||
def require_bootstrap(arch, trigger_str):
|
||||
|
@ -338,8 +351,6 @@ def require_bootstrap(arch, trigger_str):
|
|||
"""
|
||||
if pmb.config.other.is_systemd_selected(get_context().config):
|
||||
pmb.helpers.repo.update(arch)
|
||||
pkg = pmb.parse.apkindex.package("postmarketos-base-systemd",
|
||||
arch, False)
|
||||
pkg = pmb.parse.apkindex.package("postmarketos-base-systemd", arch, False)
|
||||
if not pkg:
|
||||
require_bootstrap_error("systemd", arch, trigger_str)
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ See also:
|
|||
- pmb/helpers/pmaports.py (work with pmaports)
|
||||
- pmb/helpers/package.py (work with both)
|
||||
"""
|
||||
|
||||
import os
|
||||
import hashlib
|
||||
from pmb.core.context import get_context
|
||||
|
@ -23,7 +24,7 @@ import pmb.helpers.run
|
|||
import pmb.helpers.other
|
||||
|
||||
|
||||
def apkindex_hash(url: str, length: int=8) -> Path:
|
||||
def apkindex_hash(url: str, length: int = 8) -> Path:
|
||||
r"""Generate the hash that APK adds to the APKINDEX and apk packages in its apk cache folder.
|
||||
|
||||
It is the "12345678" part in this example:
|
||||
|
@ -44,8 +45,8 @@ def apkindex_hash(url: str, length: int=8) -> Path:
|
|||
|
||||
ret = ""
|
||||
for i in range(csum_bytes):
|
||||
ret += xd[(binary[i] >> 4) & 0xf]
|
||||
ret += xd[binary[i] & 0xf]
|
||||
ret += xd[(binary[i] >> 4) & 0xF]
|
||||
ret += xd[binary[i] & 0xF]
|
||||
|
||||
return Path(f"APKINDEX.{ret}.tar.gz")
|
||||
|
||||
|
@ -102,8 +103,9 @@ def urls(user_repository=False, mirrors_exclude: List[str] = []):
|
|||
return ret
|
||||
|
||||
|
||||
def apkindex_files(arch: Optional[Arch]=None, user_repository=True,
|
||||
exclude_mirrors: List[str] = []) -> List[Path]:
|
||||
def apkindex_files(
|
||||
arch: Optional[Arch] = None, user_repository=True, exclude_mirrors: List[str] = []
|
||||
) -> List[Path]:
|
||||
"""Get a list of outside paths to all resolved APKINDEX.tar.gz files for a specific arch.
|
||||
|
||||
:param arch: defaults to native
|
||||
|
@ -128,7 +130,7 @@ def apkindex_files(arch: Optional[Arch]=None, user_repository=True,
|
|||
|
||||
|
||||
@Cache("arch", force=False)
|
||||
def update(arch: Optional[Arch]=None, force=False, existing_only=False):
|
||||
def update(arch: Optional[Arch] = None, force=False, existing_only=False):
|
||||
"""Download the APKINDEX files for all URLs depending on the architectures.
|
||||
|
||||
:param arch: * one Alpine architecture name ("x86_64", "armhf", ...)
|
||||
|
@ -190,14 +192,18 @@ def update(arch: Optional[Arch]=None, force=False, existing_only=False):
|
|||
# Bail out or show log message
|
||||
if not len(outdated):
|
||||
return False
|
||||
logging.info("Update package index for " + ", ".join([str(a) for a in outdated_arches]) +
|
||||
" (" + str(len(outdated)) + " file(s))")
|
||||
logging.info(
|
||||
"Update package index for "
|
||||
+ ", ".join([str(a) for a in outdated_arches])
|
||||
+ " ("
|
||||
+ str(len(outdated))
|
||||
+ " file(s))"
|
||||
)
|
||||
|
||||
# Download and move to right location
|
||||
for (i, (url, target)) in enumerate(outdated.items()):
|
||||
for i, (url, target) in enumerate(outdated.items()):
|
||||
pmb.helpers.cli.progress_print(i / len(outdated))
|
||||
temp = pmb.helpers.http.download(url, "APKINDEX", False,
|
||||
logging.DEBUG, True)
|
||||
temp = pmb.helpers.http.download(url, "APKINDEX", False, logging.DEBUG, True)
|
||||
if not temp:
|
||||
pmb.helpers.other.cache[cache_key]["404"].append(url)
|
||||
continue
|
||||
|
@ -210,7 +216,7 @@ def update(arch: Optional[Arch]=None, force=False, existing_only=False):
|
|||
return True
|
||||
|
||||
|
||||
def alpine_apkindex_path(repo="main", arch: Optional[Arch]=None):
|
||||
def alpine_apkindex_path(repo="main", arch: Optional[Arch] = None):
|
||||
"""Get the path to a specific Alpine APKINDEX file on disk and download it if necessary.
|
||||
|
||||
:param repo: Alpine repository name (e.g. "main")
|
||||
|
|
|
@ -71,15 +71,19 @@ def get_relevant_packages(arch, pkgname=None, built=False):
|
|||
if built:
|
||||
ret = filter_aport_packages(ret)
|
||||
if not len(ret):
|
||||
logging.info("NOTE: no aport found for any package in the"
|
||||
" dependency tree, it seems they are all provided by"
|
||||
" upstream (Alpine).")
|
||||
logging.info(
|
||||
"NOTE: no aport found for any package in the"
|
||||
" dependency tree, it seems they are all provided by"
|
||||
" upstream (Alpine)."
|
||||
)
|
||||
else:
|
||||
ret = filter_missing_packages(arch, ret)
|
||||
if not len(ret):
|
||||
logging.info("NOTE: all relevant packages are up to date, use"
|
||||
" --built to include the ones that have already been"
|
||||
" built.")
|
||||
logging.info(
|
||||
"NOTE: all relevant packages are up to date, use"
|
||||
" --built to include the ones that have already been"
|
||||
" built."
|
||||
)
|
||||
|
||||
# Sort alphabetically (to get a deterministic build order)
|
||||
ret.sort()
|
||||
|
@ -105,10 +109,14 @@ def generate_output_format(arch, pkgnames):
|
|||
ret = []
|
||||
for pkgname in pkgnames:
|
||||
entry = pmb.helpers.package.get(pkgname, arch, True)
|
||||
ret += [{"pkgname": entry["pkgname"],
|
||||
"repo": pmb.helpers.pmaports.get_repo(pkgname),
|
||||
"version": entry["version"],
|
||||
"depends": entry["depends"]}]
|
||||
ret += [
|
||||
{
|
||||
"pkgname": entry["pkgname"],
|
||||
"repo": pmb.helpers.pmaports.get_repo(pkgname),
|
||||
"version": entry["version"],
|
||||
"depends": entry["depends"],
|
||||
}
|
||||
]
|
||||
return ret
|
||||
|
||||
|
||||
|
@ -124,8 +132,7 @@ def generate(arch, overview, pkgname=None, built=False):
|
|||
"""
|
||||
# Log message
|
||||
packages_str = pkgname if pkgname else "all packages"
|
||||
logging.info("Calculate packages that need to be built ({}, {})"
|
||||
"".format(packages_str, arch))
|
||||
logging.info("Calculate packages that need to be built ({}, {})" "".format(packages_str, arch))
|
||||
|
||||
# Order relevant packages
|
||||
ret = get_relevant_packages(arch, pkgname, built)
|
||||
|
|
|
@ -9,8 +9,15 @@ from typing import Optional, Sequence
|
|||
from pmb.types import Env, PathString
|
||||
|
||||
|
||||
def user(cmd: Sequence[PathString], working_dir: Optional[Path] = None, output: str = "log", output_return: bool = False,
|
||||
check: Optional[bool] = None, env: Env = {}, sudo: bool = False) -> str | int | subprocess.Popen:
|
||||
def user(
|
||||
cmd: Sequence[PathString],
|
||||
working_dir: Optional[Path] = None,
|
||||
output: str = "log",
|
||||
output_return: bool = False,
|
||||
check: Optional[bool] = None,
|
||||
env: Env = {},
|
||||
sudo: bool = False,
|
||||
) -> str | int | subprocess.Popen:
|
||||
"""
|
||||
Run a command on the host system as user.
|
||||
|
||||
|
@ -40,12 +47,20 @@ def user(cmd: Sequence[PathString], working_dir: Optional[Path] = None, output:
|
|||
pmb.helpers.run_core.add_proxy_env_vars(env)
|
||||
if env:
|
||||
cmd_parts = ["sh", "-c", pmb.helpers.run_core.flat_cmd([cmd_parts], env=env)]
|
||||
return pmb.helpers.run_core.core(msg, cmd_parts, working_dir, output,
|
||||
output_return, check, sudo)
|
||||
return pmb.helpers.run_core.core(
|
||||
msg, cmd_parts, working_dir, output, output_return, check, sudo
|
||||
)
|
||||
|
||||
|
||||
# FIXME: should probably use some kind of wrapper class / builder pattern for all these parameters...
|
||||
def user_output(cmd: Sequence[PathString], working_dir: Optional[Path] = None, output: str = "log",
|
||||
check: Optional[bool] = None, env: Env = {}, sudo: bool = False) -> str:
|
||||
def user_output(
|
||||
cmd: Sequence[PathString],
|
||||
working_dir: Optional[Path] = None,
|
||||
output: str = "log",
|
||||
check: Optional[bool] = None,
|
||||
env: Env = {},
|
||||
sudo: bool = False,
|
||||
) -> str:
|
||||
ret = user(cmd, working_dir, output, output_return=True, check=check, env=env, sudo=sudo)
|
||||
if not isinstance(ret, str):
|
||||
raise TypeError("Expected str output, got " + str(ret))
|
||||
|
@ -53,8 +68,14 @@ def user_output(cmd: Sequence[PathString], working_dir: Optional[Path] = None, o
|
|||
return ret
|
||||
|
||||
|
||||
def root(cmd: Sequence[PathString], working_dir=None, output="log", output_return=False,
|
||||
check=None, env={}):
|
||||
def root(
|
||||
cmd: Sequence[PathString],
|
||||
working_dir=None,
|
||||
output="log",
|
||||
output_return=False,
|
||||
check=None,
|
||||
env={},
|
||||
):
|
||||
"""Run a command on the host system as root, with sudo or doas.
|
||||
|
||||
:param env: dict of environment variables to be passed to the command, e.g.
|
||||
|
@ -70,5 +91,4 @@ def root(cmd: Sequence[PathString], working_dir=None, output="log", output_retur
|
|||
cmd = ["sh", "-c", pmb.helpers.run_core.flat_cmd([cmd], env=env)]
|
||||
cmd = pmb.config.sudo(cmd)
|
||||
|
||||
return user(cmd, working_dir, output, output_return, check, env,
|
||||
True)
|
||||
return user(cmd, working_dir, output, output_return, check, env, True)
|
||||
|
|
|
@ -21,7 +21,9 @@ import pmb.helpers.run
|
|||
called by core(). """
|
||||
|
||||
|
||||
def flat_cmd(cmds: Sequence[Sequence[PathString]], working_dir: Optional[Path]=None, env: Env={}):
|
||||
def flat_cmd(
|
||||
cmds: Sequence[Sequence[PathString]], working_dir: Optional[Path] = None, env: Env = {}
|
||||
):
|
||||
"""Convert a shell command passed as list into a flat shell string with proper escaping.
|
||||
|
||||
:param cmds: list of commands as list, e.g. ["echo", "string with spaces"]
|
||||
|
@ -74,24 +76,34 @@ def sanity_checks(output="log", output_return=False, check=None):
|
|||
|
||||
def background(cmd, working_dir=None):
|
||||
"""Run a subprocess in background and redirect its output to the log."""
|
||||
ret = subprocess.Popen(cmd, stdout=pmb.helpers.logging.logfd,
|
||||
stderr=pmb.helpers.logging.logfd, cwd=working_dir)
|
||||
ret = subprocess.Popen(
|
||||
cmd, stdout=pmb.helpers.logging.logfd, stderr=pmb.helpers.logging.logfd, cwd=working_dir
|
||||
)
|
||||
logging.debug(f"New background process: pid={ret.pid}, output=background")
|
||||
return ret
|
||||
|
||||
|
||||
def pipe(cmd, working_dir=None):
|
||||
"""Run a subprocess in background and redirect its output to a pipe."""
|
||||
ret = subprocess.Popen(cmd, stdout=subprocess.PIPE,
|
||||
stdin=subprocess.DEVNULL,
|
||||
stderr=pmb.helpers.logging.logfd, cwd=working_dir)
|
||||
ret = subprocess.Popen(
|
||||
cmd,
|
||||
stdout=subprocess.PIPE,
|
||||
stdin=subprocess.DEVNULL,
|
||||
stderr=pmb.helpers.logging.logfd,
|
||||
cwd=working_dir,
|
||||
)
|
||||
logging.verbose(f"New background process: pid={ret.pid}, output=pipe")
|
||||
return ret
|
||||
|
||||
|
||||
# FIXME (#2324): These types make no sense at all
|
||||
def pipe_read(process, output_to_stdout=False, output_log=True, output_return=False,
|
||||
output_return_buffer=False):
|
||||
def pipe_read(
|
||||
process,
|
||||
output_to_stdout=False,
|
||||
output_log=True,
|
||||
output_return=False,
|
||||
output_return_buffer=False,
|
||||
):
|
||||
"""Read all output from a subprocess, copy it to the log and optionally stdout and a buffer variable.
|
||||
|
||||
This is only meant to be called by foreground_pipe() below.
|
||||
|
@ -130,13 +142,11 @@ def kill_process_tree(pid, ppids, sudo):
|
|||
:param sudo: use sudo to kill the process
|
||||
"""
|
||||
if sudo:
|
||||
pmb.helpers.run.root(["kill", "-9", str(pid)],
|
||||
check=False)
|
||||
pmb.helpers.run.root(["kill", "-9", str(pid)], check=False)
|
||||
else:
|
||||
pmb.helpers.run.user(["kill", "-9", str(pid)],
|
||||
check=False)
|
||||
pmb.helpers.run.user(["kill", "-9", str(pid)], check=False)
|
||||
|
||||
for (child_pid, child_ppid) in ppids:
|
||||
for child_pid, child_ppid in ppids:
|
||||
if child_ppid == str(pid):
|
||||
kill_process_tree(child_pid, ppids, sudo)
|
||||
|
||||
|
@ -150,7 +160,7 @@ def kill_command(pid, sudo):
|
|||
cmd = ["ps", "-e", "-o", "pid,ppid"]
|
||||
ret = subprocess.run(cmd, check=True, stdout=subprocess.PIPE)
|
||||
ppids = []
|
||||
proc_entries = ret.stdout.decode("utf-8").rstrip().split('\n')[1:]
|
||||
proc_entries = ret.stdout.decode("utf-8").rstrip().split("\n")[1:]
|
||||
for row in proc_entries:
|
||||
items = row.split()
|
||||
if len(items) != 2:
|
||||
|
@ -160,9 +170,16 @@ def kill_command(pid, sudo):
|
|||
kill_process_tree(pid, ppids, sudo)
|
||||
|
||||
|
||||
def foreground_pipe(cmd, working_dir=None, output_to_stdout=False,
|
||||
output_return=False, output_log=True, output_timeout=True,
|
||||
sudo=False, stdin=None):
|
||||
def foreground_pipe(
|
||||
cmd,
|
||||
working_dir=None,
|
||||
output_to_stdout=False,
|
||||
output_return=False,
|
||||
output_log=True,
|
||||
output_timeout=True,
|
||||
sudo=False,
|
||||
stdin=None,
|
||||
):
|
||||
"""Run a subprocess in foreground with redirected output.
|
||||
|
||||
Optionally kill it after being silent for too long.
|
||||
|
@ -182,9 +199,9 @@ def foreground_pipe(cmd, working_dir=None, output_to_stdout=False,
|
|||
"""
|
||||
context = pmb.core.context.get_context()
|
||||
# Start process in background (stdout and stderr combined)
|
||||
process = subprocess.Popen(cmd, stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT, cwd=working_dir,
|
||||
stdin=stdin)
|
||||
process = subprocess.Popen(
|
||||
cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=working_dir, stdin=stdin
|
||||
)
|
||||
|
||||
# Make process.stdout non-blocking
|
||||
stdout = process.stdout or None
|
||||
|
@ -210,16 +227,15 @@ def foreground_pipe(cmd, working_dir=None, output_to_stdout=False,
|
|||
if output_timeout:
|
||||
wait_end = time.perf_counter()
|
||||
if wait_end - wait_start >= timeout:
|
||||
logging.info("Process did not write any output for " +
|
||||
str(timeout) + " seconds. Killing it.")
|
||||
logging.info("NOTE: The timeout can be increased with"
|
||||
" 'pmbootstrap -t'.")
|
||||
logging.info(
|
||||
"Process did not write any output for " + str(timeout) + " seconds. Killing it."
|
||||
)
|
||||
logging.info("NOTE: The timeout can be increased with" " 'pmbootstrap -t'.")
|
||||
kill_command(process.pid, sudo)
|
||||
continue
|
||||
|
||||
# Read all currently available output
|
||||
pipe_read(process, output_to_stdout, output_log, output_return,
|
||||
output_buffer)
|
||||
pipe_read(process, output_to_stdout, output_log, output_return, output_buffer)
|
||||
|
||||
# There may still be output after the process quit
|
||||
pipe_read(process, output_to_stdout, output_log, output_return, output_buffer)
|
||||
|
@ -236,8 +252,7 @@ def foreground_tui(cmd, working_dir=None):
|
|||
This is the only way text-based user interfaces (ncurses programs like
|
||||
vim, nano or the kernel's menuconfig) work properly.
|
||||
"""
|
||||
logging.debug("*** output passed to pmbootstrap stdout, not to this log"
|
||||
" ***")
|
||||
logging.debug("*** output passed to pmbootstrap stdout, not to this log" " ***")
|
||||
process = subprocess.Popen(cmd, cwd=working_dir)
|
||||
return process.wait()
|
||||
|
||||
|
@ -254,10 +269,11 @@ def check_return_code(code, log_message):
|
|||
if code:
|
||||
logging.debug("^" * 70)
|
||||
log_file = get_context().log
|
||||
logging.info("NOTE: The failed command's output is above the ^^^ line"
|
||||
f" in the log file: {log_file}")
|
||||
raise RuntimeError(f"Command failed (exit code {str(code)}): " +
|
||||
log_message)
|
||||
logging.info(
|
||||
"NOTE: The failed command's output is above the ^^^ line"
|
||||
f" in the log file: {log_file}"
|
||||
)
|
||||
raise RuntimeError(f"Command failed (exit code {str(code)}): " + log_message)
|
||||
|
||||
|
||||
def sudo_timer_iterate():
|
||||
|
@ -291,8 +307,7 @@ def add_proxy_env_vars(env):
|
|||
"FTP_PROXY",
|
||||
"HTTPS_PROXY",
|
||||
"HTTP_PROXY",
|
||||
"HTTP_PROXY_AUTH"
|
||||
"ftp_proxy",
|
||||
"HTTP_PROXY_AUTH" "ftp_proxy",
|
||||
"http_proxy",
|
||||
"https_proxy",
|
||||
]
|
||||
|
@ -302,8 +317,16 @@ def add_proxy_env_vars(env):
|
|||
env[var] = os.environ[var]
|
||||
|
||||
|
||||
def core(log_message, cmd, working_dir=None, output="log",
|
||||
output_return=False, check=None, sudo=False, disable_timeout=False):
|
||||
def core(
|
||||
log_message,
|
||||
cmd,
|
||||
working_dir=None,
|
||||
output="log",
|
||||
output_return=False,
|
||||
check=None,
|
||||
sudo=False,
|
||||
disable_timeout=False,
|
||||
):
|
||||
"""Run a command and create a log entry.
|
||||
|
||||
This is a low level function not meant to be used directly. Use one of the
|
||||
|
@ -397,12 +420,16 @@ def core(log_message, cmd, working_dir=None, output="log",
|
|||
|
||||
stdin = subprocess.DEVNULL if output in ["log", "stdout"] else None
|
||||
|
||||
(code, output_after_run) = foreground_pipe(cmd, working_dir,
|
||||
output_to_stdout,
|
||||
output_return,
|
||||
output!="null",
|
||||
output_timeout,
|
||||
sudo, stdin)
|
||||
(code, output_after_run) = foreground_pipe(
|
||||
cmd,
|
||||
working_dir,
|
||||
output_to_stdout,
|
||||
output_return,
|
||||
output != "null",
|
||||
output_timeout,
|
||||
sudo,
|
||||
stdin,
|
||||
)
|
||||
|
||||
# Check the return code
|
||||
if check is not False:
|
||||
|
|
|
@ -53,8 +53,8 @@ def print_systemd(config: Config) -> None:
|
|||
|
||||
|
||||
def print_status(args: PmbArgs) -> None:
|
||||
""" :param details: if True, print each passing check instead of a summary
|
||||
:returns: True if all checks passed, False otherwise """
|
||||
""":param details: if True, print each passing check instead of a summary
|
||||
:returns: True if all checks passed, False otherwise"""
|
||||
config = get_context().config
|
||||
print_channel(config)
|
||||
print_device(config)
|
||||
|
|
|
@ -13,9 +13,14 @@ def list_ui(arch):
|
|||
:param arch: device architecture, for which the UIs must be available
|
||||
:returns: [("none", "No graphical..."), ("weston", "Wayland reference...")]
|
||||
"""
|
||||
ret = [("none", "Bare minimum OS image for testing and manual"
|
||||
" customization. The \"console\" UI should be selected if"
|
||||
" a graphical UI is not desired.")]
|
||||
ret = [
|
||||
(
|
||||
"none",
|
||||
"Bare minimum OS image for testing and manual"
|
||||
' customization. The "console" UI should be selected if'
|
||||
" a graphical UI is not desired.",
|
||||
)
|
||||
]
|
||||
for path in sorted(pkgrepo_iglob("main/postmarketos-ui-*")):
|
||||
apkbuild = pmb.parse.apkbuild(path)
|
||||
ui = os.path.basename(path).split("-", 2)[2]
|
||||
|
@ -29,5 +34,7 @@ def check_option(ui, option, skip_extra_repos=False):
|
|||
Check if an option, such as pmb:systemd, is inside an UI's APKBUILD.
|
||||
"""
|
||||
pkgname = f"postmarketos-ui-{ui}"
|
||||
apkbuild = pmb.helpers.pmaports.get(pkgname, subpackages=False, skip_extra_repos=skip_extra_repos)
|
||||
apkbuild = pmb.helpers.pmaports.get(
|
||||
pkgname, subpackages=False, skip_extra_repos=skip_extra_repos
|
||||
)
|
||||
return option in apkbuild["options"]
|
||||
|
|
|
@ -67,7 +67,7 @@ def get_nonfree_packages(device):
|
|||
["device-nokia-n900-nonfree-firmware"]
|
||||
"""
|
||||
# Read subpackages
|
||||
device_path = pmb.helpers.devices.find_path(device, 'APKBUILD')
|
||||
device_path = pmb.helpers.devices.find_path(device, "APKBUILD")
|
||||
if not device_path:
|
||||
raise RuntimeError(f"Device package not found for {device}")
|
||||
|
||||
|
@ -100,9 +100,11 @@ def get_kernel_package(config: Config):
|
|||
|
||||
# Sanity check
|
||||
if config.kernel not in kernels:
|
||||
raise RuntimeError("Selected kernel (" + config.kernel + ") is not"
|
||||
" valid for device " + config.device + ". Please"
|
||||
" run 'pmbootstrap init' to select a valid kernel.")
|
||||
raise RuntimeError(
|
||||
"Selected kernel (" + config.kernel + ") is not"
|
||||
" valid for device " + config.device + ". Please"
|
||||
" run 'pmbootstrap init' to select a valid kernel."
|
||||
)
|
||||
|
||||
# Selected kernel subpackage
|
||||
return ["device-" + config.device + "-kernel-" + config.kernel]
|
||||
|
@ -145,24 +147,24 @@ def copy_files_from_chroot(args: PmbArgs, chroot: Chroot):
|
|||
rsync_flags = "-a"
|
||||
if args.verbose:
|
||||
rsync_flags += "vP"
|
||||
pmb.chroot.root(["rsync", rsync_flags, "--delete"] + folders +
|
||||
["/mnt/install/"], working_dir=mountpoint)
|
||||
pmb.chroot.root(
|
||||
["rsync", rsync_flags, "--delete"] + folders + ["/mnt/install/"], working_dir=mountpoint
|
||||
)
|
||||
pmb.chroot.root(["rm", "-rf", "/mnt/install/home"])
|
||||
else:
|
||||
pmb.chroot.root(["cp", "-a"] + folders + ["/mnt/install/"],
|
||||
working_dir=mountpoint)
|
||||
pmb.chroot.root(["cp", "-a"] + folders + ["/mnt/install/"], working_dir=mountpoint)
|
||||
|
||||
|
||||
def create_home_from_skel(filesystem: str, user: str):
|
||||
"""
|
||||
Create /home/{user} from /etc/skel
|
||||
"""
|
||||
rootfs = (Chroot.native() / "mnt/install")
|
||||
rootfs = Chroot.native() / "mnt/install"
|
||||
# In btrfs, home subvol & home dir is created in format.py
|
||||
if filesystem != "btrfs":
|
||||
pmb.helpers.run.root(["mkdir", rootfs / "home"])
|
||||
|
||||
home = (rootfs / "home" / user)
|
||||
home = rootfs / "home" / user
|
||||
if (rootfs / "etc/skel").exists():
|
||||
pmb.helpers.run.root(["cp", "-a", (rootfs / "etc/skel"), home])
|
||||
else:
|
||||
|
@ -184,19 +186,21 @@ def configure_apk(args: PmbArgs):
|
|||
keys_dir = get_context().config.work / "config_apk_keys"
|
||||
|
||||
# Copy over keys
|
||||
rootfs = (Chroot.native() / "mnt/install")
|
||||
rootfs = Chroot.native() / "mnt/install"
|
||||
for key in keys_dir.glob("*.pub"):
|
||||
pmb.helpers.run.root(["cp", key, rootfs / "etc/apk/keys/"])
|
||||
|
||||
# Copy over the corresponding APKINDEX files from cache
|
||||
index_files = pmb.helpers.repo.apkindex_files(arch=pmb.parse.deviceinfo().arch,
|
||||
user_repository=False)
|
||||
index_files = pmb.helpers.repo.apkindex_files(
|
||||
arch=pmb.parse.deviceinfo().arch, user_repository=False
|
||||
)
|
||||
for f in index_files:
|
||||
pmb.helpers.run.root(["cp", f, rootfs / "var/cache/apk/"])
|
||||
|
||||
# Disable pmbootstrap repository
|
||||
pmb.helpers.run.root(["sed", "-i", r"/\/mnt\/pmbootstrap\/packages/d",
|
||||
rootfs / "etc/apk/repositories"])
|
||||
pmb.helpers.run.root(
|
||||
["sed", "-i", r"/\/mnt\/pmbootstrap\/packages/d", rootfs / "etc/apk/repositories"]
|
||||
)
|
||||
pmb.helpers.run.user(["cat", rootfs / "etc/apk/repositories"])
|
||||
|
||||
|
||||
|
@ -210,18 +214,17 @@ def set_user(config: Config):
|
|||
"""
|
||||
chroot = Chroot.rootfs(config.device)
|
||||
if not pmb.chroot.user_exists(config.user, chroot):
|
||||
pmb.chroot.root(["adduser", "-D", "-u", "10000", config.user],
|
||||
chroot)
|
||||
pmb.chroot.root(["adduser", "-D", "-u", "10000", config.user], chroot)
|
||||
|
||||
pmaports_cfg = pmb.config.pmaports.read_config()
|
||||
groups = []
|
||||
groups += pmaports_cfg.get("install_user_groups",
|
||||
"audio,input,netdev,plugdev,video,wheel").split(",")
|
||||
groups += pmaports_cfg.get(
|
||||
"install_user_groups", "audio,input,netdev,plugdev,video,wheel"
|
||||
).split(",")
|
||||
groups += pmb.install.ui.get_groups(config)
|
||||
|
||||
for group in groups:
|
||||
pmb.chroot.root(["addgroup", "-S", group], chroot,
|
||||
check=False)
|
||||
pmb.chroot.root(["addgroup", "-S", group], chroot, check=False)
|
||||
pmb.chroot.root(["addgroup", config.user, group], chroot)
|
||||
|
||||
|
||||
|
@ -243,8 +246,7 @@ def setup_login_chpasswd_user_from_arg(args: PmbArgs, user: str, chroot: Chroot)
|
|||
with open(path_outside, "w", encoding="utf-8") as handle:
|
||||
handle.write(f"{user}:{args.password}")
|
||||
|
||||
pmb.chroot.root(["sh", "-c", f"cat {shlex.quote(path)} | chpasswd"],
|
||||
chroot)
|
||||
pmb.chroot.root(["sh", "-c", f"cat {shlex.quote(path)} | chpasswd"], chroot)
|
||||
|
||||
os.unlink(path_outside)
|
||||
|
||||
|
@ -257,8 +259,9 @@ def is_root_locked(chroot: Chroot):
|
|||
|
||||
:param suffix: either rootfs_{args.device} or installer_{args.device}
|
||||
"""
|
||||
shadow_root = pmb.chroot.root(["grep", "^root:!:", "/etc/shadow"],
|
||||
chroot, output_return=True, check=False)
|
||||
shadow_root = pmb.chroot.root(
|
||||
["grep", "^root:!:", "/etc/shadow"], chroot, output_return=True, check=False
|
||||
)
|
||||
return shadow_root.startswith("root:!:")
|
||||
|
||||
|
||||
|
@ -278,12 +281,10 @@ def setup_login(args: PmbArgs, config: Config, chroot: Chroot):
|
|||
else:
|
||||
while True:
|
||||
try:
|
||||
pmb.chroot.root(["passwd", config.user], chroot,
|
||||
output="interactive")
|
||||
pmb.chroot.root(["passwd", config.user], chroot, output="interactive")
|
||||
break
|
||||
except RuntimeError:
|
||||
logging.info("WARNING: Failed to set the password. Try it"
|
||||
" one more time.")
|
||||
logging.info("WARNING: Failed to set the password. Try it" " one more time.")
|
||||
|
||||
# Disable root login
|
||||
if is_root_locked(chroot):
|
||||
|
@ -305,9 +306,11 @@ def copy_ssh_keys(config: Config):
|
|||
keys += infile.readlines()
|
||||
|
||||
if not len(keys):
|
||||
logging.info("NOTE: Public SSH keys not found. Since no SSH keys "
|
||||
"were copied, you will need to use SSH password "
|
||||
"authentication!")
|
||||
logging.info(
|
||||
"NOTE: Public SSH keys not found. Since no SSH keys "
|
||||
"were copied, you will need to use SSH password "
|
||||
"authentication!"
|
||||
)
|
||||
return
|
||||
|
||||
authorized_keys = Chroot.native() / "tmp/authorized_keys"
|
||||
|
@ -333,20 +336,20 @@ def setup_keymap(config: Config):
|
|||
if not deviceinfo.keymaps or deviceinfo.keymaps.strip() == "":
|
||||
logging.info("NOTE: No valid keymap specified for device")
|
||||
return
|
||||
options = deviceinfo.keymaps.split(' ')
|
||||
if (config.keymap != "" and
|
||||
config.keymap is not None and
|
||||
config.keymap in options):
|
||||
options = deviceinfo.keymaps.split(" ")
|
||||
if config.keymap != "" and config.keymap is not None and config.keymap in options:
|
||||
layout, variant = config.keymap.split("/")
|
||||
pmb.chroot.root(["setup-keymap", layout, variant], chroot,
|
||||
output="interactive")
|
||||
pmb.chroot.root(["setup-keymap", layout, variant], chroot, output="interactive")
|
||||
|
||||
# Check xorg config
|
||||
xconfig = None
|
||||
if (chroot / "etc/X11/xorg.conf.d").exists():
|
||||
xconfig = pmb.chroot.root(["grep", "-rl", "XkbLayout",
|
||||
"/etc/X11/xorg.conf.d/"],
|
||||
chroot, check=False, output_return=True)
|
||||
xconfig = pmb.chroot.root(
|
||||
["grep", "-rl", "XkbLayout", "/etc/X11/xorg.conf.d/"],
|
||||
chroot,
|
||||
check=False,
|
||||
output_return=True,
|
||||
)
|
||||
if xconfig:
|
||||
# Nokia n900 (RX-51) randomly merges some keymaps so we
|
||||
# have to specify a composite keymap for a few countries. See:
|
||||
|
@ -359,10 +362,9 @@ def setup_keymap(config: Config):
|
|||
layout = "ptes"
|
||||
# Multiple files can contain the keyboard layout, take last
|
||||
xconfig = xconfig.splitlines()[-1]
|
||||
old_text = "Option *\\\"XkbLayout\\\" *\\\".*\\\""
|
||||
new_text = "Option \\\"XkbLayout\\\" \\\"" + layout + "\\\""
|
||||
pmb.chroot.root(["sed", "-i", "s/" + old_text + "/" +
|
||||
new_text + "/", xconfig], chroot)
|
||||
old_text = 'Option *\\"XkbLayout\\" *\\".*\\"'
|
||||
new_text = 'Option \\"XkbLayout\\" \\"' + layout + '\\"'
|
||||
pmb.chroot.root(["sed", "-i", "s/" + old_text + "/" + new_text + "/", xconfig], chroot)
|
||||
else:
|
||||
logging.info("NOTE: No valid keymap specified for device")
|
||||
|
||||
|
@ -400,16 +402,18 @@ def setup_hostname(device: str, hostname: Optional[str]):
|
|||
elif not pmb.helpers.other.validate_hostname(hostname):
|
||||
# Invalid hostname set by the user e.g., via pmb init, this should
|
||||
# fail so they can fix it
|
||||
raise RuntimeError("Hostname '" + hostname + "' is not valid, please"
|
||||
" run 'pmbootstrap init' to configure it.")
|
||||
raise RuntimeError(
|
||||
"Hostname '" + hostname + "' is not valid, please"
|
||||
" run 'pmbootstrap init' to configure it."
|
||||
)
|
||||
|
||||
suffix = Chroot(ChrootType.ROOTFS, device)
|
||||
# Generate /etc/hostname
|
||||
pmb.chroot.root(["sh", "-c", "echo " + shlex.quote(hostname) +
|
||||
" > /etc/hostname"], suffix)
|
||||
pmb.chroot.root(["sh", "-c", "echo " + shlex.quote(hostname) + " > /etc/hostname"], suffix)
|
||||
# Update /etc/hosts
|
||||
regex = (r"s/^127\.0\.0\.1.*/127.0.0.1\t" + re.escape(hostname) +
|
||||
" localhost.localdomain localhost/")
|
||||
regex = (
|
||||
r"s/^127\.0\.0\.1.*/127.0.0.1\t" + re.escape(hostname) + " localhost.localdomain localhost/"
|
||||
)
|
||||
pmb.chroot.root(["sed", "-i", "-e", regex, "/etc/hosts"], suffix)
|
||||
|
||||
|
||||
|
@ -423,23 +427,31 @@ def setup_appstream(offline: bool, chroot: Chroot):
|
|||
if "alpine-appstream-downloader" not in installed_pkgs or offline:
|
||||
return
|
||||
|
||||
if not pmb.chroot.root(["alpine-appstream-downloader",
|
||||
"/mnt/appstream-data"], chroot, check=False):
|
||||
if not pmb.chroot.root(
|
||||
["alpine-appstream-downloader", "/mnt/appstream-data"], chroot, check=False
|
||||
):
|
||||
pmb.chroot.root(["mkdir", "-p", "/var/lib/swcatalog"], chroot)
|
||||
pmb.chroot.root(["cp", "-r", "/mnt/appstream-data/icons",
|
||||
"/mnt/appstream-data/xml",
|
||||
"-t", "/var/lib/swcatalog"], chroot)
|
||||
pmb.chroot.root(
|
||||
[
|
||||
"cp",
|
||||
"-r",
|
||||
"/mnt/appstream-data/icons",
|
||||
"/mnt/appstream-data/xml",
|
||||
"-t",
|
||||
"/var/lib/swcatalog",
|
||||
],
|
||||
chroot,
|
||||
)
|
||||
|
||||
|
||||
def disable_sshd(chroot: Chroot):
|
||||
# check=False: rc-update doesn't exit with 0 if already disabled
|
||||
pmb.chroot.root(["rc-update", "del", "sshd", "default"], chroot,
|
||||
check=False)
|
||||
pmb.chroot.root(["rc-update", "del", "sshd", "default"], chroot, check=False)
|
||||
|
||||
# Verify that it's gone
|
||||
sshd_files = pmb.helpers.run.root(
|
||||
["find", "-name", "sshd"], output_return=True,
|
||||
working_dir=chroot / "etc/runlevels")
|
||||
["find", "-name", "sshd"], output_return=True, working_dir=chroot / "etc/runlevels"
|
||||
)
|
||||
if sshd_files:
|
||||
raise RuntimeError(f"Failed to disable sshd service: {sshd_files}")
|
||||
|
||||
|
@ -453,27 +465,29 @@ def print_sshd_info(args: PmbArgs):
|
|||
logging.info("SSH daemon is disabled (--no-sshd).")
|
||||
else:
|
||||
logging.info("SSH daemon is enabled (disable with --no-sshd).")
|
||||
logging.info(f"Login as '{get_context().config.user}' with the password given"
|
||||
" during installation.")
|
||||
logging.info(
|
||||
f"Login as '{get_context().config.user}' with the password given"
|
||||
" during installation."
|
||||
)
|
||||
|
||||
if args.on_device_installer:
|
||||
# We don't disable sshd in the installer OS. If the device is reachable
|
||||
# on the network by default (e.g. Raspberry Pi), one can lock down the
|
||||
# installer OS down by disabling the debug user (see wiki page).
|
||||
logging.info("SSH daemon is enabled in the installer OS, to allow"
|
||||
" debugging the installer image.")
|
||||
logging.info(
|
||||
"SSH daemon is enabled in the installer OS, to allow" " debugging the installer image."
|
||||
)
|
||||
logging.info("More info: https://postmarketos.org/ondev-debug")
|
||||
|
||||
|
||||
def disable_firewall(chroot: Chroot):
|
||||
# check=False: rc-update doesn't exit with 0 if already disabled
|
||||
pmb.chroot.root(["rc-update", "del", "nftables", "default"], chroot,
|
||||
check=False)
|
||||
pmb.chroot.root(["rc-update", "del", "nftables", "default"], chroot, check=False)
|
||||
|
||||
# Verify that it's gone
|
||||
nftables_files = pmb.helpers.run.root(
|
||||
["find", "-name", "nftables"], output_return=True,
|
||||
working_dir=chroot / "etc/runlevels")
|
||||
["find", "-name", "nftables"], output_return=True, working_dir=chroot / "etc/runlevels"
|
||||
)
|
||||
if nftables_files:
|
||||
raise RuntimeError(f"Failed to disable firewall: {nftables_files}")
|
||||
|
||||
|
@ -499,20 +513,21 @@ def print_firewall_info(disabled: bool, arch: Arch):
|
|||
logging.info("*** FIREWALL INFORMATION ***")
|
||||
|
||||
if not pmaports_ok:
|
||||
logging.info("Firewall is not supported in checked out pmaports"
|
||||
" branch.")
|
||||
logging.info("Firewall is not supported in checked out pmaports" " branch.")
|
||||
elif disabled:
|
||||
logging.info("Firewall is disabled (--no-firewall).")
|
||||
elif not apkbuild_found:
|
||||
logging.info("Firewall is enabled, but may not work (couldn't"
|
||||
" determine if kernel supports nftables).")
|
||||
logging.info(
|
||||
"Firewall is enabled, but may not work (couldn't"
|
||||
" determine if kernel supports nftables)."
|
||||
)
|
||||
elif apkbuild_has_opt:
|
||||
logging.info("Firewall is enabled and supported by kernel.")
|
||||
else:
|
||||
logging.info("Firewall is enabled, but will not work (no support in"
|
||||
" kernel config for nftables).")
|
||||
logging.info("If/when the kernel supports it in the future, it"
|
||||
" will work automatically.")
|
||||
logging.info(
|
||||
"Firewall is enabled, but will not work (no support in" " kernel config for nftables)."
|
||||
)
|
||||
logging.info("If/when the kernel supports it in the future, it" " will work automatically.")
|
||||
|
||||
logging.info("For more information: https://postmarketos.org/firewall")
|
||||
|
||||
|
@ -532,34 +547,39 @@ def generate_binary_list(args: PmbArgs, chroot: Chroot, step):
|
|||
binaries = pmb.parse.deviceinfo().sd_embed_firmware.split(",")
|
||||
|
||||
for binary_offset in binaries:
|
||||
binary, _offset = binary_offset.split(':')
|
||||
binary, _offset = binary_offset.split(":")
|
||||
try:
|
||||
offset = int(_offset)
|
||||
except ValueError:
|
||||
raise RuntimeError("Value for firmware binary offset is "
|
||||
f"not valid: {offset}")
|
||||
raise RuntimeError("Value for firmware binary offset is " f"not valid: {offset}")
|
||||
binary_path = chroot / "usr/share" / binary
|
||||
if not os.path.exists(binary_path):
|
||||
raise RuntimeError("The following firmware binary does not "
|
||||
f"exist in the {chroot} chroot: "
|
||||
f"/usr/share/{binary}")
|
||||
raise RuntimeError(
|
||||
"The following firmware binary does not "
|
||||
f"exist in the {chroot} chroot: "
|
||||
f"/usr/share/{binary}"
|
||||
)
|
||||
# Insure that embedding the firmware will not overrun the
|
||||
# first partition
|
||||
boot_part_start = pmb.parse.deviceinfo().boot_part_start or "2048"
|
||||
max_size = (int(boot_part_start) * 512) - (offset * step)
|
||||
binary_size = os.path.getsize(binary_path)
|
||||
if binary_size > max_size:
|
||||
raise RuntimeError("The firmware is too big to embed in the "
|
||||
f"disk image {binary_size}B > {max_size}B")
|
||||
raise RuntimeError(
|
||||
"The firmware is too big to embed in the "
|
||||
f"disk image {binary_size}B > {max_size}B"
|
||||
)
|
||||
# Insure that the firmware does not conflict with any other firmware
|
||||
# that will be embedded
|
||||
binary_start = offset * step
|
||||
binary_end = binary_start + binary_size
|
||||
for start, end in binary_ranges.items():
|
||||
if ((binary_start >= start and binary_start < end) or
|
||||
(binary_end > start and binary_end <= end)):
|
||||
raise RuntimeError("The firmware overlaps with at least one "
|
||||
f"other firmware image: {binary}")
|
||||
if (binary_start >= start and binary_start < end) or (
|
||||
binary_end > start and binary_end <= end
|
||||
):
|
||||
raise RuntimeError(
|
||||
"The firmware overlaps with at least one " f"other firmware image: {binary}"
|
||||
)
|
||||
|
||||
binary_ranges[binary_start] = binary_end
|
||||
binary_list.append((binary, offset))
|
||||
|
@ -586,9 +606,11 @@ def embed_firmware(args: PmbArgs, suffix: Chroot):
|
|||
try:
|
||||
step = int(pmb.parse.deviceinfo().sd_embed_firmware_step_size)
|
||||
except ValueError:
|
||||
raise RuntimeError("Value for "
|
||||
"deviceinfo_sd_embed_firmware_step_size "
|
||||
"is not valid: {}".format(step))
|
||||
raise RuntimeError(
|
||||
"Value for " "deviceinfo_sd_embed_firmware_step_size " "is not valid: {}".format(
|
||||
step
|
||||
)
|
||||
)
|
||||
|
||||
device_rootfs = mount_device_rootfs(suffix)
|
||||
binary_list = generate_binary_list(args, suffix, step)
|
||||
|
@ -596,11 +618,15 @@ def embed_firmware(args: PmbArgs, suffix: Chroot):
|
|||
# Write binaries to disk
|
||||
for binary, offset in binary_list:
|
||||
binary_file = os.path.join("/usr/share", binary)
|
||||
logging.info("Embed firmware {} in the SD card image at offset {} with"
|
||||
" step size {}".format(binary, offset, step))
|
||||
logging.info(
|
||||
"Embed firmware {} in the SD card image at offset {} with" " step size {}".format(
|
||||
binary, offset, step
|
||||
)
|
||||
)
|
||||
filename = os.path.join(device_rootfs, binary_file.lstrip("/"))
|
||||
pmb.chroot.root(["dd", "if=" + filename, "of=/dev/install",
|
||||
"bs=" + str(step), "seek=" + str(offset)])
|
||||
pmb.chroot.root(
|
||||
["dd", "if=" + filename, "of=/dev/install", "bs=" + str(step), "seek=" + str(offset)]
|
||||
)
|
||||
|
||||
|
||||
def write_cgpt_kpart(args: PmbArgs, layout, suffix: Chroot):
|
||||
|
@ -615,8 +641,7 @@ def write_cgpt_kpart(args: PmbArgs, layout, suffix: Chroot):
|
|||
|
||||
device_rootfs = mount_device_rootfs(suffix)
|
||||
filename = f"{device_rootfs}{pmb.parse.deviceinfo()['cgpt_kpart']}"
|
||||
pmb.chroot.root(
|
||||
["dd", f"if={filename}", f"of=/dev/installp{layout['kernel']}"])
|
||||
pmb.chroot.root(["dd", f"if={filename}", f"of=/dev/installp{layout['kernel']}"])
|
||||
|
||||
|
||||
def sanity_check_boot_size():
|
||||
|
@ -624,11 +649,14 @@ def sanity_check_boot_size():
|
|||
config = get_context().config
|
||||
if int(config.boot_size) >= int(default):
|
||||
return
|
||||
logging.error("ERROR: your pmbootstrap has a small/invalid boot_size of"
|
||||
f" {config.boot_size} configured, probably because the config"
|
||||
" has been created with an old version.")
|
||||
logging.error("This can lead to problems later on, we recommend setting it"
|
||||
f" to {default} MiB.")
|
||||
logging.error(
|
||||
"ERROR: your pmbootstrap has a small/invalid boot_size of"
|
||||
f" {config.boot_size} configured, probably because the config"
|
||||
" has been created with an old version."
|
||||
)
|
||||
logging.error(
|
||||
"This can lead to problems later on, we recommend setting it" f" to {default} MiB."
|
||||
)
|
||||
logging.error(f"Run 'pmbootstrap config boot_size {default}' and try again.")
|
||||
sys.exit(1)
|
||||
|
||||
|
@ -638,17 +666,17 @@ def sanity_check_disk(args: PmbArgs):
|
|||
device_name = os.path.basename(device)
|
||||
if not os.path.exists(device):
|
||||
raise RuntimeError(f"{device} doesn't exist, is the disk plugged?")
|
||||
if os.path.isdir('/sys/class/block/{}'.format(device_name)):
|
||||
with open('/sys/class/block/{}/ro'.format(device_name), 'r') as handle:
|
||||
if os.path.isdir("/sys/class/block/{}".format(device_name)):
|
||||
with open("/sys/class/block/{}/ro".format(device_name), "r") as handle:
|
||||
ro = handle.read()
|
||||
if ro == '1\n':
|
||||
if ro == "1\n":
|
||||
raise RuntimeError(f"{device} is read-only, maybe a locked SD card?")
|
||||
|
||||
|
||||
def sanity_check_disk_size(args: PmbArgs):
|
||||
device = args.disk
|
||||
devpath = os.path.realpath(device)
|
||||
sysfs = '/sys/class/block/{}/size'.format(devpath.replace('/dev/', ''))
|
||||
sysfs = "/sys/class/block/{}/size".format(devpath.replace("/dev/", ""))
|
||||
if not os.path.isfile(sysfs):
|
||||
# This is a best-effort sanity check, continue if it's not checkable
|
||||
return
|
||||
|
@ -662,11 +690,13 @@ def sanity_check_disk_size(args: PmbArgs):
|
|||
|
||||
# Warn if the size is larger than 100GiB
|
||||
if not args.assume_yes and size > (100 * 2 * 1024 * 1024):
|
||||
if not pmb.helpers.cli.confirm(f"WARNING: The target disk ({devpath}) "
|
||||
"is larger than a usual SD card "
|
||||
"(>100GiB). Are you sure you want to "
|
||||
f"overwrite this {human} disk?",
|
||||
no_assumptions=True):
|
||||
if not pmb.helpers.cli.confirm(
|
||||
f"WARNING: The target disk ({devpath}) "
|
||||
"is larger than a usual SD card "
|
||||
"(>100GiB). Are you sure you want to "
|
||||
f"overwrite this {human} disk?",
|
||||
no_assumptions=True,
|
||||
):
|
||||
raise RuntimeError("Aborted.")
|
||||
|
||||
|
||||
|
@ -680,10 +710,12 @@ def sanity_check_ondev_version(args: PmbArgs):
|
|||
ver_pkg = get_ondev_pkgver(args)
|
||||
ver_min = pmb.config.ondev_min_version
|
||||
if pmb.parse.version.compare(ver_pkg, ver_min) == -1:
|
||||
raise RuntimeError("This version of pmbootstrap requires"
|
||||
f" postmarketos-ondev version {ver_min} or"
|
||||
" higher. The postmarketos-ondev found in pmaports"
|
||||
f" / in the binary packages has version {ver_pkg}.")
|
||||
raise RuntimeError(
|
||||
"This version of pmbootstrap requires"
|
||||
f" postmarketos-ondev version {ver_min} or"
|
||||
" higher. The postmarketos-ondev found in pmaports"
|
||||
f" / in the binary packages has version {ver_pkg}."
|
||||
)
|
||||
|
||||
|
||||
def get_partition_layout(reserve, kernel):
|
||||
|
@ -721,11 +753,13 @@ def get_uuid(args: PmbArgs, partition: Path):
|
|||
return pmb.chroot.root(
|
||||
[
|
||||
"blkid",
|
||||
"-s", "UUID",
|
||||
"-o", "value",
|
||||
"-s",
|
||||
"UUID",
|
||||
"-o",
|
||||
"value",
|
||||
partition,
|
||||
],
|
||||
output_return=True
|
||||
output_return=True,
|
||||
).rstrip()
|
||||
|
||||
|
||||
|
@ -762,8 +796,9 @@ def create_fstab(args: PmbArgs, layout, chroot: Chroot):
|
|||
root_dev = Path(f"/dev/installp{layout['root']}")
|
||||
|
||||
boot_mount_point = f"UUID={get_uuid(args, boot_dev)}"
|
||||
root_mount_point = "/dev/mapper/root" if args.full_disk_encryption \
|
||||
else f"UUID={get_uuid(args, root_dev)}"
|
||||
root_mount_point = (
|
||||
"/dev/mapper/root" if args.full_disk_encryption else f"UUID={get_uuid(args, root_dev)}"
|
||||
)
|
||||
|
||||
boot_options = "nodev,nosuid,noexec"
|
||||
boot_filesystem = pmb.parse.deviceinfo().boot_filesystem or "ext2"
|
||||
|
@ -798,9 +833,17 @@ def create_fstab(args: PmbArgs, layout, chroot: Chroot):
|
|||
pmb.chroot.root(["mv", "/tmp/fstab", "/etc/fstab"], chroot)
|
||||
|
||||
|
||||
def install_system_image(args: PmbArgs, size_reserve, chroot: Chroot, step, steps,
|
||||
boot_label="pmOS_boot", root_label="pmOS_root",
|
||||
split=False, disk: Optional[Path]=None):
|
||||
def install_system_image(
|
||||
args: PmbArgs,
|
||||
size_reserve,
|
||||
chroot: Chroot,
|
||||
step,
|
||||
steps,
|
||||
boot_label="pmOS_boot",
|
||||
root_label="pmOS_root",
|
||||
split=False,
|
||||
disk: Optional[Path] = None,
|
||||
):
|
||||
"""
|
||||
:param size_reserve: empty partition between root and boot in MiB (pma#463)
|
||||
:param suffix: the chroot suffix, where the rootfs that will be installed
|
||||
|
@ -818,15 +861,14 @@ def install_system_image(args: PmbArgs, size_reserve, chroot: Chroot, step, step
|
|||
logging.info(f"*** ({step}/{steps}) PREPARE INSTALL BLOCKDEVICE ***")
|
||||
pmb.helpers.mount.umount_all(chroot.path)
|
||||
(size_boot, size_root) = get_subpartitions_size(chroot)
|
||||
layout = get_partition_layout(size_reserve, pmb.parse.deviceinfo().cgpt_kpart \
|
||||
and args.install_cgpt)
|
||||
layout = get_partition_layout(
|
||||
size_reserve, pmb.parse.deviceinfo().cgpt_kpart and args.install_cgpt
|
||||
)
|
||||
if not args.rsync:
|
||||
pmb.install.blockdevice.create(args, size_boot, size_root,
|
||||
size_reserve, split, disk)
|
||||
pmb.install.blockdevice.create(args, size_boot, size_root, size_reserve, split, disk)
|
||||
if not split:
|
||||
if pmb.parse.deviceinfo().cgpt_kpart and args.install_cgpt:
|
||||
pmb.install.partition_cgpt(
|
||||
args, layout, size_boot, size_reserve)
|
||||
pmb.install.partition_cgpt(args, layout, size_boot, size_reserve)
|
||||
else:
|
||||
pmb.install.partition(args, layout, size_boot, size_reserve)
|
||||
if not split:
|
||||
|
@ -867,8 +909,7 @@ def install_system_image(args: PmbArgs, size_reserve, chroot: Chroot, step, step
|
|||
write_cgpt_kpart(args, layout, chroot)
|
||||
|
||||
if disk:
|
||||
logging.info(f"Unmounting disk {disk} (this may take a while "
|
||||
"to sync, please wait)")
|
||||
logging.info(f"Unmounting disk {disk} (this may take a while " "to sync, please wait)")
|
||||
pmb.chroot.shutdown(True)
|
||||
|
||||
# Convert rootfs to sparse using img2simg
|
||||
|
@ -882,10 +923,8 @@ def install_system_image(args: PmbArgs, size_reserve, chroot: Chroot, step, step
|
|||
pmb.chroot.apk.install(["android-tools"], Chroot.native())
|
||||
sys_image = device + ".img"
|
||||
sys_image_sparse = device + "-sparse.img"
|
||||
pmb.chroot.user(["img2simg", sys_image, sys_image_sparse],
|
||||
working_dir=workdir)
|
||||
pmb.chroot.user(["mv", "-f", sys_image_sparse, sys_image],
|
||||
working_dir=workdir)
|
||||
pmb.chroot.user(["img2simg", sys_image, sys_image_sparse], working_dir=workdir)
|
||||
pmb.chroot.user(["mv", "-f", sys_image_sparse, sys_image], working_dir=workdir)
|
||||
|
||||
# patch sparse image for Samsung devices if specified
|
||||
samsungify_strategy = pmb.parse.deviceinfo().flash_sparse_samsung_format
|
||||
|
@ -894,16 +933,23 @@ def install_system_image(args: PmbArgs, size_reserve, chroot: Chroot, step, step
|
|||
pmb.chroot.apk.install(["sm-sparse-image-tool"], Chroot.native())
|
||||
sys_image = f"{device}.img"
|
||||
sys_image_patched = f"{device}-patched.img"
|
||||
pmb.chroot.user(["sm_sparse_image_tool", "samsungify", "--strategy",
|
||||
samsungify_strategy, sys_image, sys_image_patched],
|
||||
working_dir=workdir)
|
||||
pmb.chroot.user(["mv", "-f", sys_image_patched, sys_image],
|
||||
working_dir=workdir)
|
||||
pmb.chroot.user(
|
||||
[
|
||||
"sm_sparse_image_tool",
|
||||
"samsungify",
|
||||
"--strategy",
|
||||
samsungify_strategy,
|
||||
sys_image,
|
||||
sys_image_patched,
|
||||
],
|
||||
working_dir=workdir,
|
||||
)
|
||||
pmb.chroot.user(["mv", "-f", sys_image_patched, sys_image], working_dir=workdir)
|
||||
|
||||
|
||||
def print_flash_info(device: str, deviceinfo: Deviceinfo, split: bool, have_disk: bool):
|
||||
""" Print flashing information, based on the deviceinfo data and the
|
||||
pmbootstrap arguments. """
|
||||
"""Print flashing information, based on the deviceinfo data and the
|
||||
pmbootstrap arguments."""
|
||||
logging.info("") # make the note stand out
|
||||
logging.info("*** FLASHING INFORMATION ***")
|
||||
|
||||
|
@ -916,41 +962,42 @@ def print_flash_info(device: str, deviceinfo: Deviceinfo, split: bool, have_disk
|
|||
requires_split = flasher.get("split", False)
|
||||
|
||||
if method == "none":
|
||||
logging.info("Refer to the installation instructions of your device,"
|
||||
" or the generic install instructions in the wiki.")
|
||||
logging.info("https://wiki.postmarketos.org/wiki/Installation_guide"
|
||||
"#pmbootstrap_flash")
|
||||
logging.info(
|
||||
"Refer to the installation instructions of your device,"
|
||||
" or the generic install instructions in the wiki."
|
||||
)
|
||||
logging.info("https://wiki.postmarketos.org/wiki/Installation_guide" "#pmbootstrap_flash")
|
||||
return
|
||||
|
||||
logging.info("Run the following to flash your installation to the"
|
||||
" target device:")
|
||||
logging.info("Run the following to flash your installation to the" " target device:")
|
||||
|
||||
if "flash_rootfs" in flasher_actions and not have_disk and \
|
||||
bool(split) == requires_split:
|
||||
if "flash_rootfs" in flasher_actions and not have_disk and bool(split) == requires_split:
|
||||
logging.info("* pmbootstrap flasher flash_rootfs")
|
||||
logging.info(" Flashes the generated rootfs image to your device:")
|
||||
if split:
|
||||
logging.info(f" {Chroot.native() / 'home/pmos/rootfs' / device}-rootfs.img")
|
||||
else:
|
||||
logging.info(f" {Chroot.native() / 'home/pmos/rootfs' / device}.img")
|
||||
logging.info(" (NOTE: This file has a partition table, which"
|
||||
" contains /boot and / subpartitions. That way we"
|
||||
" don't need to change the partition layout on your"
|
||||
" device.)")
|
||||
logging.info(
|
||||
" (NOTE: This file has a partition table, which"
|
||||
" contains /boot and / subpartitions. That way we"
|
||||
" don't need to change the partition layout on your"
|
||||
" device.)"
|
||||
)
|
||||
|
||||
# if current flasher supports vbmeta and partition is explicitly specified
|
||||
# in deviceinfo
|
||||
if "flash_vbmeta" in flasher_actions and \
|
||||
(deviceinfo.flash_fastboot_partition_vbmeta or
|
||||
deviceinfo.flash_heimdall_partition_vbmeta):
|
||||
if "flash_vbmeta" in flasher_actions and (
|
||||
deviceinfo.flash_fastboot_partition_vbmeta or deviceinfo.flash_heimdall_partition_vbmeta
|
||||
):
|
||||
logging.info("* pmbootstrap flasher flash_vbmeta")
|
||||
logging.info(" Flashes vbmeta image with verification disabled flag.")
|
||||
|
||||
# if current flasher supports dtbo and partition is explicitly specified
|
||||
# in deviceinfo
|
||||
if "flash_dtbo" in flasher_actions and \
|
||||
(deviceinfo.flash_fastboot_partition_dtbo or
|
||||
deviceinfo.flash_heimdall_partition_dtbo):
|
||||
if "flash_dtbo" in flasher_actions and (
|
||||
deviceinfo.flash_fastboot_partition_dtbo or deviceinfo.flash_heimdall_partition_dtbo
|
||||
):
|
||||
logging.info("* pmbootstrap flasher flash_dtbo")
|
||||
logging.info(" Flashes dtbo image.")
|
||||
|
||||
|
@ -958,8 +1005,7 @@ def print_flash_info(device: str, deviceinfo: Deviceinfo, split: bool, have_disk
|
|||
# (e.g. an Android boot image is generated). In that case, "flash_kernel"
|
||||
# works even when partitions are split or installing to disk. This is not
|
||||
# possible if the flash method requires split partitions.
|
||||
if "flash_kernel" in flasher_actions and \
|
||||
(not requires_split or split):
|
||||
if "flash_kernel" in flasher_actions and (not requires_split or split):
|
||||
logging.info("* pmbootstrap flasher flash_kernel")
|
||||
logging.info(" Flashes the kernel + initramfs to your device:")
|
||||
if requires_split:
|
||||
|
@ -968,21 +1014,29 @@ def print_flash_info(device: str, deviceinfo: Deviceinfo, split: bool, have_disk
|
|||
logging.info(f" {Chroot(ChrootType.ROOTFS, device) / 'boot'}")
|
||||
|
||||
if "boot" in flasher_actions:
|
||||
logging.info(" (NOTE: " + method + " also supports booting"
|
||||
" the kernel/initramfs directly without flashing."
|
||||
" Use 'pmbootstrap flasher boot' to do that.)")
|
||||
logging.info(
|
||||
" (NOTE: " + method + " also supports booting"
|
||||
" the kernel/initramfs directly without flashing."
|
||||
" Use 'pmbootstrap flasher boot' to do that.)"
|
||||
)
|
||||
|
||||
if "flash_lk2nd" in flasher_actions and \
|
||||
(Chroot(ChrootType.ROOTFS, device) / "boot/lk2nd.img").exists():
|
||||
logging.info("* Your device supports and may even require"
|
||||
" flashing lk2nd. You should flash it before"
|
||||
" flashing anything else. Use 'pmbootstrap flasher"
|
||||
" flash_lk2nd' to do that.")
|
||||
if (
|
||||
"flash_lk2nd" in flasher_actions
|
||||
and (Chroot(ChrootType.ROOTFS, device) / "boot/lk2nd.img").exists()
|
||||
):
|
||||
logging.info(
|
||||
"* Your device supports and may even require"
|
||||
" flashing lk2nd. You should flash it before"
|
||||
" flashing anything else. Use 'pmbootstrap flasher"
|
||||
" flash_lk2nd' to do that."
|
||||
)
|
||||
|
||||
# Export information
|
||||
logging.info("* If the above steps do not work, you can also create"
|
||||
" symlinks to the generated files with 'pmbootstrap export'"
|
||||
" and flash outside of pmbootstrap.")
|
||||
logging.info(
|
||||
"* If the above steps do not work, you can also create"
|
||||
" symlinks to the generated files with 'pmbootstrap export'"
|
||||
" and flash outside of pmbootstrap."
|
||||
)
|
||||
|
||||
|
||||
def install_recovery_zip(args: PmbArgs, device: str, arch: Arch, steps):
|
||||
|
@ -1002,17 +1056,17 @@ def install_on_device_installer(args: PmbArgs, step, steps):
|
|||
config = get_context().config
|
||||
if not args.ondev_no_rootfs:
|
||||
suffix_rootfs = Chroot.rootfs(config.device)
|
||||
install_system_image(args, 0, suffix_rootfs, step=step, steps=steps,
|
||||
split=True)
|
||||
install_system_image(args, 0, suffix_rootfs, step=step, steps=steps, split=True)
|
||||
step += 2
|
||||
|
||||
# Prepare the installer chroot
|
||||
logging.info(f"*** ({step}/{steps}) CREATE ON-DEVICE INSTALLER ROOTFS ***")
|
||||
step += 1
|
||||
packages = ([f"device-{config.device}",
|
||||
"postmarketos-ondev"] +
|
||||
get_kernel_package(config) +
|
||||
get_nonfree_packages(config.device))
|
||||
packages = (
|
||||
[f"device-{config.device}", "postmarketos-ondev"]
|
||||
+ get_kernel_package(config)
|
||||
+ get_nonfree_packages(config.device)
|
||||
)
|
||||
|
||||
chroot_installer = Chroot(ChrootType.INSTALLER, config.device)
|
||||
pmb.chroot.apk.install(packages, chroot_installer)
|
||||
|
@ -1033,24 +1087,24 @@ def install_on_device_installer(args: PmbArgs, step, steps):
|
|||
logging.info(f"({chroot_installer}) ondev-prepare")
|
||||
channel = pmb.config.pmaports.read_config()["channel"]
|
||||
channel_cfg = pmb.config.pmaports.read_config_channel()
|
||||
env = {"ONDEV_CHANNEL": channel,
|
||||
"ONDEV_CHANNEL_BRANCH_APORTS": channel_cfg["branch_aports"],
|
||||
"ONDEV_CHANNEL_BRANCH_PMAPORTS": channel_cfg["branch_pmaports"],
|
||||
"ONDEV_CHANNEL_DESCRIPTION": channel_cfg["description"],
|
||||
"ONDEV_CHANNEL_MIRRORDIR_ALPINE": channel_cfg["mirrordir_alpine"],
|
||||
"ONDEV_CIPHER": args.cipher,
|
||||
"ONDEV_PMBOOTSTRAP_VERSION": pmb.__version__,
|
||||
"ONDEV_UI": config.ui}
|
||||
env = {
|
||||
"ONDEV_CHANNEL": channel,
|
||||
"ONDEV_CHANNEL_BRANCH_APORTS": channel_cfg["branch_aports"],
|
||||
"ONDEV_CHANNEL_BRANCH_PMAPORTS": channel_cfg["branch_pmaports"],
|
||||
"ONDEV_CHANNEL_DESCRIPTION": channel_cfg["description"],
|
||||
"ONDEV_CHANNEL_MIRRORDIR_ALPINE": channel_cfg["mirrordir_alpine"],
|
||||
"ONDEV_CIPHER": args.cipher,
|
||||
"ONDEV_PMBOOTSTRAP_VERSION": pmb.__version__,
|
||||
"ONDEV_UI": config.ui,
|
||||
}
|
||||
pmb.chroot.root(["ondev-prepare"], chroot_installer, env=env)
|
||||
|
||||
# Copy files specified with 'pmbootstrap install --ondev --cp'
|
||||
if args.ondev_cp:
|
||||
for host_src, chroot_dest in args.ondev_cp:
|
||||
host_dest = chroot_installer / chroot_dest
|
||||
logging.info(f"({chroot_installer}) add {host_src} as"
|
||||
f" {chroot_dest}")
|
||||
pmb.helpers.run.root(["install", "-Dm644", host_src,
|
||||
host_dest])
|
||||
logging.info(f"({chroot_installer}) add {host_src} as" f" {chroot_dest}")
|
||||
pmb.helpers.run.root(["install", "-Dm644", host_src, host_dest])
|
||||
|
||||
# Remove $DEVICE-boot.img (we will generate a new one if --split was
|
||||
# specified, otherwise the separate boot image is not needed)
|
||||
|
@ -1065,10 +1119,18 @@ def install_on_device_installer(args: PmbArgs, step, steps):
|
|||
# Generate installer image
|
||||
size_reserve = round(os.path.getsize(img_path_dest) / 1024 / 1024) + 200
|
||||
pmaports_cfg = pmb.config.pmaports.read_config()
|
||||
boot_label = pmaports_cfg.get("supported_install_boot_label",
|
||||
"pmOS_inst_boot")
|
||||
install_system_image(args, size_reserve, chroot_installer, step, steps,
|
||||
boot_label, "pmOS_install", args.split, args.disk)
|
||||
boot_label = pmaports_cfg.get("supported_install_boot_label", "pmOS_inst_boot")
|
||||
install_system_image(
|
||||
args,
|
||||
size_reserve,
|
||||
chroot_installer,
|
||||
step,
|
||||
steps,
|
||||
boot_label,
|
||||
"pmOS_install",
|
||||
args.split,
|
||||
args.disk,
|
||||
)
|
||||
|
||||
|
||||
def get_selected_providers(args: PmbArgs, packages):
|
||||
|
@ -1099,11 +1161,10 @@ def get_selected_providers(args: PmbArgs, packages):
|
|||
apkbuild = pmb.helpers.pmaports.get(package, subpackages=False, must_exist=False)
|
||||
if not apkbuild:
|
||||
continue
|
||||
for select in apkbuild['_pmb_select']:
|
||||
for select in apkbuild["_pmb_select"]:
|
||||
if select in get_context().config.providers:
|
||||
ret += [get_context().config.providers[select]]
|
||||
logging.verbose(f"{package}: install selected_providers:"
|
||||
f" {', '.join(ret)}")
|
||||
logging.verbose(f"{package}: install selected_providers:" f" {', '.join(ret)}")
|
||||
# Also iterate through dependencies to collect any providers they have
|
||||
depends = apkbuild["depends"]
|
||||
if depends:
|
||||
|
@ -1157,8 +1218,7 @@ def get_recommends(args: PmbArgs, packages) -> Sequence[str]:
|
|||
continue
|
||||
recommends = apkbuild["_pmb_recommends"]
|
||||
if recommends:
|
||||
logging.debug(f"{package}: install _pmb_recommends:"
|
||||
f" {', '.join(recommends)}")
|
||||
logging.debug(f"{package}: install _pmb_recommends:" f" {', '.join(recommends)}")
|
||||
ret += recommends
|
||||
# Call recursively in case recommends have pmb_recommends of their
|
||||
# own.
|
||||
|
@ -1177,8 +1237,7 @@ def create_device_rootfs(args: PmbArgs, step, steps):
|
|||
context = get_context()
|
||||
config = context.config
|
||||
device = context.config.device
|
||||
logging.info(f'*** ({step}/{steps}) CREATE DEVICE ROOTFS ("{device}")'
|
||||
' ***')
|
||||
logging.info(f'*** ({step}/{steps}) CREATE DEVICE ROOTFS ("{device}")' " ***")
|
||||
|
||||
chroot = Chroot(ChrootType.ROOTFS, device)
|
||||
pmb.chroot.init(chroot)
|
||||
|
@ -1187,11 +1246,9 @@ def create_device_rootfs(args: PmbArgs, step, steps):
|
|||
set_user(context.config)
|
||||
|
||||
# Fill install_packages
|
||||
install_packages = (pmb.config.install_device_packages +
|
||||
["device-" + device])
|
||||
install_packages = pmb.config.install_device_packages + ["device-" + device]
|
||||
if not args.install_base:
|
||||
install_packages = [p for p in install_packages
|
||||
if p != "postmarketos-base"]
|
||||
install_packages = [p for p in install_packages if p != "postmarketos-base"]
|
||||
if config.ui.lower() != "none":
|
||||
install_packages += ["postmarketos-ui-" + config.ui]
|
||||
|
||||
|
@ -1210,7 +1267,7 @@ def create_device_rootfs(args: PmbArgs, step, steps):
|
|||
install_packages += context.config.extra_packages.split(",")
|
||||
if args.add:
|
||||
install_packages += args.add.split(",")
|
||||
locale_is_set = (config.locale != Config().locale)
|
||||
locale_is_set = config.locale != Config().locale
|
||||
if locale_is_set:
|
||||
install_packages += ["lang", "musl-locales"]
|
||||
|
||||
|
@ -1226,7 +1283,8 @@ def create_device_rootfs(args: PmbArgs, step, steps):
|
|||
# Pick the most suitable unlocker depending on the packages
|
||||
# selected for installation
|
||||
unlocker = pmb.parse.depends.package_provider(
|
||||
"postmarketos-fde-unlocker", install_packages, chroot)
|
||||
"postmarketos-fde-unlocker", install_packages, chroot
|
||||
)
|
||||
if unlocker["pkgname"] not in install_packages:
|
||||
install_packages += [unlocker["pkgname"]]
|
||||
else:
|
||||
|
@ -1260,8 +1318,9 @@ def create_device_rootfs(args: PmbArgs, step, steps):
|
|||
# alpine-baselayout by /etc/profile. Since they don't override the
|
||||
# locale if it exists, it warranties we have preference
|
||||
line = f"export LANG=${{LANG:-{shlex.quote(config.locale)}}}"
|
||||
pmb.chroot.root(["sh", "-c", f"echo {shlex.quote(line)}"
|
||||
" > /etc/profile.d/10locale-pmos.sh"], chroot)
|
||||
pmb.chroot.root(
|
||||
["sh", "-c", f"echo {shlex.quote(line)}" " > /etc/profile.d/10locale-pmos.sh"], chroot
|
||||
)
|
||||
|
||||
# Set the hostname as the device name
|
||||
setup_hostname(device, config.hostname)
|
||||
|
@ -1303,8 +1362,7 @@ def install(args: PmbArgs):
|
|||
step = 1
|
||||
logging.info(f"*** ({step}/{steps}) PREPARE NATIVE CHROOT ***")
|
||||
pmb.chroot.init(Chroot.native())
|
||||
pmb.chroot.apk.install(pmb.config.install_native_packages, Chroot.native(),
|
||||
build=False)
|
||||
pmb.chroot.apk.install(pmb.config.install_native_packages, Chroot.native(), build=False)
|
||||
step += 1
|
||||
|
||||
if not args.ondev_no_rootfs:
|
||||
|
@ -1320,10 +1378,11 @@ def install(args: PmbArgs):
|
|||
# Runs install_system_image twice
|
||||
install_on_device_installer(args, step, steps)
|
||||
else:
|
||||
install_system_image(args, 0, chroot, step, steps,
|
||||
split=args.split, disk=args.disk)
|
||||
install_system_image(args, 0, chroot, step, steps, split=args.split, disk=args.disk)
|
||||
|
||||
print_flash_info(device, deviceinfo, args.split, True if args.disk and args.disk.is_absolute() else False)
|
||||
print_flash_info(
|
||||
device, deviceinfo, args.split, True if args.disk and args.disk.is_absolute() else False
|
||||
)
|
||||
print_sshd_info(args)
|
||||
print_firewall_info(args.no_firewall, deviceinfo.arch)
|
||||
|
||||
|
|
|
@ -25,22 +25,22 @@ def previous_install(path: Path):
|
|||
if not os.path.exists(blockdevice_outside):
|
||||
continue
|
||||
blockdevice_inside = "/dev/diskp1"
|
||||
pmb.helpers.mount.bind_file(blockdevice_outside,
|
||||
Chroot.native() / blockdevice_inside)
|
||||
pmb.helpers.mount.bind_file(blockdevice_outside, Chroot.native() / blockdevice_inside)
|
||||
try:
|
||||
label = pmb.chroot.root(["blkid", "-s", "LABEL",
|
||||
"-o", "value",
|
||||
blockdevice_inside],
|
||||
output_return=True)
|
||||
label = pmb.chroot.root(
|
||||
["blkid", "-s", "LABEL", "-o", "value", blockdevice_inside], output_return=True
|
||||
)
|
||||
except RuntimeError:
|
||||
logging.info("WARNING: Could not get block device label,"
|
||||
" assume no previous installation on that partition")
|
||||
logging.info(
|
||||
"WARNING: Could not get block device label,"
|
||||
" assume no previous installation on that partition"
|
||||
)
|
||||
|
||||
pmb.helpers.run.root(["umount", Chroot.native() / blockdevice_inside])
|
||||
return "pmOS_boot" in label
|
||||
|
||||
|
||||
def mount_disk( path: Path):
|
||||
def mount_disk(path: Path):
|
||||
"""
|
||||
:param path: path to disk block device (e.g. /dev/mmcblk0)
|
||||
"""
|
||||
|
@ -49,23 +49,20 @@ def mount_disk( path: Path):
|
|||
raise RuntimeError(f"The disk block device does not exist: {path}")
|
||||
for path_mount in path.parent.glob(f"{path.name}*"):
|
||||
if pmb.helpers.mount.ismount(path_mount):
|
||||
raise RuntimeError(f"{path_mount} is mounted! Will not attempt to"
|
||||
" format this!")
|
||||
raise RuntimeError(f"{path_mount} is mounted! Will not attempt to" " format this!")
|
||||
logging.info(f"(native) mount /dev/install (host: {path})")
|
||||
pmb.helpers.mount.bind_file(path, Chroot.native() / "dev/install")
|
||||
if previous_install(path):
|
||||
if not pmb.helpers.cli.confirm("WARNING: This device has a"
|
||||
" previous installation of pmOS."
|
||||
" CONTINUE?"):
|
||||
if not pmb.helpers.cli.confirm(
|
||||
"WARNING: This device has a" " previous installation of pmOS." " CONTINUE?"
|
||||
):
|
||||
raise RuntimeError("Aborted.")
|
||||
else:
|
||||
if not pmb.helpers.cli.confirm(f"EVERYTHING ON {path} WILL BE"
|
||||
" ERASED! CONTINUE?"):
|
||||
if not pmb.helpers.cli.confirm(f"EVERYTHING ON {path} WILL BE" " ERASED! CONTINUE?"):
|
||||
raise RuntimeError("Aborted.")
|
||||
|
||||
|
||||
def create_and_mount_image(args: PmbArgs, size_boot, size_root, size_reserve,
|
||||
split=False):
|
||||
def create_and_mount_image(args: PmbArgs, size_boot, size_root, size_reserve, split=False):
|
||||
"""
|
||||
Create a new image file, and mount it as /dev/install.
|
||||
|
||||
|
@ -96,8 +93,10 @@ def create_and_mount_image(args: PmbArgs, size_boot, size_root, size_reserve,
|
|||
disk_data = os.statvfs(get_context().config.work)
|
||||
free = round((disk_data.f_bsize * disk_data.f_bavail) / (1024**2))
|
||||
if size_mb > free:
|
||||
raise RuntimeError("Not enough free space to create rootfs image! "
|
||||
f"(free: {free}M, required: {size_mb}M)")
|
||||
raise RuntimeError(
|
||||
"Not enough free space to create rootfs image! "
|
||||
f"(free: {free}M, required: {size_mb}M)"
|
||||
)
|
||||
|
||||
# Create empty image files
|
||||
pmb.chroot.user(["mkdir", "-p", "/home/pmos/rootfs"])
|
||||
|
@ -106,18 +105,15 @@ def create_and_mount_image(args: PmbArgs, size_boot, size_root, size_reserve,
|
|||
size_mb_root = str(round(size_root)) + "M"
|
||||
images = {img_path_full: size_mb_full}
|
||||
if split:
|
||||
images = {img_path_boot: size_mb_boot,
|
||||
img_path_root: size_mb_root}
|
||||
images = {img_path_boot: size_mb_boot, img_path_root: size_mb_root}
|
||||
for img_path, size_mb in images.items():
|
||||
logging.info(f"(native) create {img_path.name} "
|
||||
f"({size_mb})")
|
||||
logging.info(f"(native) create {img_path.name} " f"({size_mb})")
|
||||
pmb.chroot.root(["truncate", "-s", size_mb, img_path])
|
||||
|
||||
# Mount to /dev/install
|
||||
mount_image_paths = {img_path_full: "/dev/install"}
|
||||
if split:
|
||||
mount_image_paths = {img_path_boot: "/dev/installp1",
|
||||
img_path_root: "/dev/installp2"}
|
||||
mount_image_paths = {img_path_boot: "/dev/installp1", img_path_root: "/dev/installp2"}
|
||||
|
||||
for img_path, mount_point in mount_image_paths.items():
|
||||
logging.info(f"(native) mount {mount_point} ({img_path.name})")
|
||||
|
@ -140,5 +136,4 @@ def create(args: PmbArgs, size_boot, size_root, size_reserve, split, disk: Optio
|
|||
if disk:
|
||||
mount_disk(disk)
|
||||
else:
|
||||
create_and_mount_image(args, size_boot, size_root, size_reserve,
|
||||
split)
|
||||
create_and_mount_image(args, size_boot, size_root, size_reserve, split)
|
||||
|
|
|
@ -7,7 +7,7 @@ from pmb.types import PmbArgs
|
|||
|
||||
|
||||
def install_fsprogs(filesystem):
|
||||
""" Install the package required to format a specific filesystem. """
|
||||
"""Install the package required to format a specific filesystem."""
|
||||
fsprogs = pmb.config.filesystems.get(filesystem)
|
||||
if not fsprogs:
|
||||
raise RuntimeError(f"Unsupported filesystem: {filesystem}")
|
||||
|
@ -25,20 +25,15 @@ def format_and_mount_boot(args: PmbArgs, device, boot_label):
|
|||
mountpoint = "/mnt/install/boot"
|
||||
filesystem = pmb.parse.deviceinfo().boot_filesystem or "ext2"
|
||||
install_fsprogs(filesystem)
|
||||
logging.info(f"(native) format {device} (boot, {filesystem}), mount to"
|
||||
f" {mountpoint}")
|
||||
logging.info(f"(native) format {device} (boot, {filesystem}), mount to" f" {mountpoint}")
|
||||
if filesystem == "fat16":
|
||||
pmb.chroot.root(["mkfs.fat", "-F", "16", "-n", boot_label,
|
||||
device])
|
||||
pmb.chroot.root(["mkfs.fat", "-F", "16", "-n", boot_label, device])
|
||||
elif filesystem == "fat32":
|
||||
pmb.chroot.root(["mkfs.fat", "-F", "32", "-n", boot_label,
|
||||
device])
|
||||
pmb.chroot.root(["mkfs.fat", "-F", "32", "-n", boot_label, device])
|
||||
elif filesystem == "ext2":
|
||||
pmb.chroot.root(["mkfs.ext2", "-F", "-q", "-L", boot_label,
|
||||
device])
|
||||
pmb.chroot.root(["mkfs.ext2", "-F", "-q", "-L", boot_label, device])
|
||||
elif filesystem == "btrfs":
|
||||
pmb.chroot.root(["mkfs.btrfs", "-f", "-q", "-L", boot_label,
|
||||
device])
|
||||
pmb.chroot.root(["mkfs.btrfs", "-f", "-q", "-L", boot_label, device])
|
||||
else:
|
||||
raise RuntimeError("Filesystem " + filesystem + " is not supported!")
|
||||
pmb.chroot.root(["mkdir", "-p", mountpoint])
|
||||
|
@ -51,21 +46,27 @@ def format_luks_root(args: PmbArgs, device):
|
|||
"""
|
||||
mountpoint = "/dev/mapper/pm_crypt"
|
||||
|
||||
logging.info(f"(native) format {device} (root, luks), mount to"
|
||||
f" {mountpoint}")
|
||||
logging.info(f"(native) format {device} (root, luks), mount to" f" {mountpoint}")
|
||||
logging.info(" *** TYPE IN THE FULL DISK ENCRYPTION PASSWORD (TWICE!) ***")
|
||||
|
||||
# Avoid cryptsetup warning about missing locking directory
|
||||
pmb.chroot.root(["mkdir", "-p", "/run/cryptsetup"])
|
||||
|
||||
pmb.chroot.root(["cryptsetup", "luksFormat",
|
||||
"-q",
|
||||
"--cipher", args.cipher,
|
||||
"--iter-time", args.iter_time,
|
||||
"--use-random",
|
||||
device], output="interactive")
|
||||
pmb.chroot.root(["cryptsetup", "luksOpen", device, "pm_crypt"],
|
||||
output="interactive")
|
||||
pmb.chroot.root(
|
||||
[
|
||||
"cryptsetup",
|
||||
"luksFormat",
|
||||
"-q",
|
||||
"--cipher",
|
||||
args.cipher,
|
||||
"--iter-time",
|
||||
args.iter_time,
|
||||
"--use-random",
|
||||
device,
|
||||
],
|
||||
output="interactive",
|
||||
)
|
||||
pmb.chroot.root(["cryptsetup", "luksOpen", device, "pm_crypt"], output="interactive")
|
||||
|
||||
if not (Chroot.native() / mountpoint).exists():
|
||||
raise RuntimeError("Failed to open cryptdevice!")
|
||||
|
@ -79,11 +80,13 @@ def get_root_filesystem(args: PmbArgs):
|
|||
supported_list = supported.split(",")
|
||||
|
||||
if ret not in supported_list:
|
||||
raise ValueError(f"Root filesystem {ret} is not supported by your"
|
||||
" currently checked out pmaports branch. Update your"
|
||||
" branch ('pmbootstrap pull'), change it"
|
||||
" ('pmbootstrap init'), or select one of these"
|
||||
f" filesystems: {', '.join(supported_list)}")
|
||||
raise ValueError(
|
||||
f"Root filesystem {ret} is not supported by your"
|
||||
" currently checked out pmaports branch. Update your"
|
||||
" branch ('pmbootstrap pull'), change it"
|
||||
" ('pmbootstrap init'), or select one of these"
|
||||
f" filesystems: {', '.join(supported_list)}"
|
||||
)
|
||||
return ret
|
||||
|
||||
|
||||
|
@ -99,32 +102,29 @@ def prepare_btrfs_subvolumes(args: PmbArgs, device, mountpoint):
|
|||
/snapshots should be a separate subvol so that changing the root subvol
|
||||
doesn't affect snapshots
|
||||
"""
|
||||
subvolume_list = ["@",
|
||||
"@home",
|
||||
"@root",
|
||||
"@snapshots",
|
||||
"@srv",
|
||||
"@tmp",
|
||||
"@var"]
|
||||
subvolume_list = ["@", "@home", "@root", "@snapshots", "@srv", "@tmp", "@var"]
|
||||
|
||||
for subvolume in subvolume_list:
|
||||
pmb.chroot.root(["btrfs", "subvol", "create",
|
||||
f"{mountpoint}/{subvolume}"])
|
||||
pmb.chroot.root(["btrfs", "subvol", "create", f"{mountpoint}/{subvolume}"])
|
||||
|
||||
# Set the default root subvolume to be separate from top level btrfs
|
||||
# subvol. This lets us easily swap out current root subvol with an
|
||||
# earlier snapshot.
|
||||
pmb.chroot.root(["btrfs", "subvol", "set-default", f"{mountpoint}/@"])
|
||||
pmb.chroot.root(["btrfs", "subvol", "set-default", f"{mountpoint}/@"])
|
||||
|
||||
# Make directories to mount subvols onto
|
||||
pmb.chroot.root(["umount", mountpoint])
|
||||
pmb.chroot.root(["mount", device, mountpoint])
|
||||
pmb.chroot.root(["mkdir",
|
||||
f"{mountpoint}/home",
|
||||
f"{mountpoint}/root",
|
||||
f"{mountpoint}/.snapshots",
|
||||
f"{mountpoint}/srv",
|
||||
f"{mountpoint}/var"])
|
||||
pmb.chroot.root(
|
||||
[
|
||||
"mkdir",
|
||||
f"{mountpoint}/home",
|
||||
f"{mountpoint}/root",
|
||||
f"{mountpoint}/.snapshots",
|
||||
f"{mountpoint}/srv",
|
||||
f"{mountpoint}/var",
|
||||
]
|
||||
)
|
||||
|
||||
# snapshots contain sensitive information,
|
||||
# and should only be readable by root.
|
||||
|
@ -132,16 +132,11 @@ def prepare_btrfs_subvolumes(args: PmbArgs, device, mountpoint):
|
|||
pmb.chroot.root(["chmod", "700", f"{mountpoint}/.snapshots"])
|
||||
|
||||
# Mount subvols
|
||||
pmb.chroot.root(["mount", "-o", "subvol=@var",
|
||||
device, f"{mountpoint}/var"])
|
||||
pmb.chroot.root(["mount", "-o", "subvol=@home",
|
||||
device, f"{mountpoint}/home"])
|
||||
pmb.chroot.root(["mount", "-o", "subvol=@root",
|
||||
device, f"{mountpoint}/root"])
|
||||
pmb.chroot.root(["mount", "-o", "subvol=@srv",
|
||||
device, f"{mountpoint}/srv"])
|
||||
pmb.chroot.root(["mount", "-o", "subvol=@snapshots",
|
||||
device, f"{mountpoint}/.snapshots"])
|
||||
pmb.chroot.root(["mount", "-o", "subvol=@var", device, f"{mountpoint}/var"])
|
||||
pmb.chroot.root(["mount", "-o", "subvol=@home", device, f"{mountpoint}/home"])
|
||||
pmb.chroot.root(["mount", "-o", "subvol=@root", device, f"{mountpoint}/root"])
|
||||
pmb.chroot.root(["mount", "-o", "subvol=@srv", device, f"{mountpoint}/srv"])
|
||||
pmb.chroot.root(["mount", "-o", "subvol=@snapshots", device, f"{mountpoint}/.snapshots"])
|
||||
|
||||
# Disable CoW for /var, to avoid write multiplication
|
||||
# and slowdown on databases, containers and VM images.
|
||||
|
@ -162,8 +157,7 @@ def format_and_mount_root(args: PmbArgs, device, root_label, disk):
|
|||
# Some downstream kernels don't support metadata_csum (#1364).
|
||||
# When changing the options of mkfs.ext4, also change them in the
|
||||
# recovery zip code (see 'grep -r mkfs\.ext4')!
|
||||
mkfs_root_args = ["mkfs.ext4", "-O", "^metadata_csum", "-F",
|
||||
"-q", "-L", root_label]
|
||||
mkfs_root_args = ["mkfs.ext4", "-O", "^metadata_csum", "-F", "-q", "-L", root_label]
|
||||
# When we don't know the file system size before hand like
|
||||
# with non-block devices, we need to explicitly set a number of
|
||||
# inodes. See #1717 and #1845 for details
|
||||
|
|
|
@ -33,8 +33,7 @@ def mount(img_path: Path):
|
|||
for i in range(0, 5):
|
||||
# Retry
|
||||
if i > 0:
|
||||
logging.debug("loop module might not be initialized yet, retry in"
|
||||
" one second...")
|
||||
logging.debug("loop module might not be initialized yet, retry in" " one second...")
|
||||
time.sleep(1)
|
||||
|
||||
# Mount and return on success
|
||||
|
@ -61,8 +60,7 @@ def device_by_back_file(back_file: Path) -> Path:
|
|||
"""
|
||||
|
||||
# Get list from losetup
|
||||
losetup_output = pmb.chroot.root(["losetup", "--json", "--list"],
|
||||
output_return=True)
|
||||
losetup_output = pmb.chroot.root(["losetup", "--json", "--list"], output_return=True)
|
||||
if not losetup_output:
|
||||
raise RuntimeError("losetup failed")
|
||||
|
||||
|
@ -86,12 +84,12 @@ def umount(img_path: Path):
|
|||
logging.debug(f"(native) umount {device}")
|
||||
pmb.chroot.root(["losetup", "-d", device])
|
||||
|
||||
|
||||
def detach_all():
|
||||
"""
|
||||
Detach all loop devices used by pmbootstrap
|
||||
"""
|
||||
losetup_output = pmb.helpers.run.root(["losetup", "--json", "--list"],
|
||||
output_return=True)
|
||||
losetup_output = pmb.helpers.run.root(["losetup", "--json", "--list"], output_return=True)
|
||||
if not losetup_output:
|
||||
return
|
||||
losetup = json.loads(losetup_output)
|
||||
|
|
|
@ -40,13 +40,16 @@ def partitions_mount(device: str, layout, disk: Optional[Path]):
|
|||
if os.path.exists(f"{partition_prefix}1"):
|
||||
found = True
|
||||
break
|
||||
logging.debug(f"NOTE: ({i + 1}/{tries}) failed to find the install "
|
||||
"partition. Retrying...")
|
||||
logging.debug(
|
||||
f"NOTE: ({i + 1}/{tries}) failed to find the install " "partition. Retrying..."
|
||||
)
|
||||
time.sleep(0.1)
|
||||
|
||||
if not found:
|
||||
raise RuntimeError(f"Unable to find the first partition of {disk}, "
|
||||
f"expected it to be at {partition_prefix}1!")
|
||||
raise RuntimeError(
|
||||
f"Unable to find the first partition of {disk}, "
|
||||
f"expected it to be at {partition_prefix}1!"
|
||||
)
|
||||
|
||||
partitions = [layout["boot"], layout["root"]]
|
||||
|
||||
|
@ -77,8 +80,10 @@ def partition(args: PmbArgs, layout, size_boot, size_reserve):
|
|||
mb_boot = f"{round(size_boot)}M"
|
||||
mb_reserved = f"{round(size_reserve)}M"
|
||||
mb_root_start = f"{round(size_boot) + round(size_reserve)}M"
|
||||
logging.info(f"(native) partition /dev/install (boot: {mb_boot},"
|
||||
f" reserved: {mb_reserved}, root: the rest)")
|
||||
logging.info(
|
||||
f"(native) partition /dev/install (boot: {mb_boot},"
|
||||
f" reserved: {mb_reserved}, root: the rest)"
|
||||
)
|
||||
|
||||
filesystem = pmb.parse.deviceinfo().boot_filesystem or "ext2"
|
||||
|
||||
|
@ -92,7 +97,7 @@ def partition(args: PmbArgs, layout, size_boot, size_reserve):
|
|||
|
||||
commands = [
|
||||
["mktable", partition_type],
|
||||
["mkpart", "primary", filesystem, boot_part_start + 's', mb_boot],
|
||||
["mkpart", "primary", filesystem, boot_part_start + "s", mb_boot],
|
||||
]
|
||||
|
||||
if size_reserve:
|
||||
|
@ -101,7 +106,7 @@ def partition(args: PmbArgs, layout, size_boot, size_reserve):
|
|||
|
||||
commands += [
|
||||
["mkpart", "primary", mb_root_start, "100%"],
|
||||
["set", str(layout["boot"]), "boot", "on"]
|
||||
["set", str(layout["boot"]), "boot", "on"],
|
||||
]
|
||||
|
||||
# Not strictly necessary if the device doesn't use EFI boot, but marking
|
||||
|
@ -114,8 +119,7 @@ def partition(args: PmbArgs, layout, size_boot, size_reserve):
|
|||
commands += [["set", str(layout["boot"]), "esp", "on"]]
|
||||
|
||||
for command in commands:
|
||||
pmb.chroot.root(["parted", "-s", "/dev/install"] +
|
||||
command, check=False)
|
||||
pmb.chroot.root(["parted", "-s", "/dev/install"] + command, check=False)
|
||||
|
||||
|
||||
def partition_cgpt(args: PmbArgs, layout, size_boot, size_reserve):
|
||||
|
@ -131,68 +135,88 @@ def partition_cgpt(args: PmbArgs, layout, size_boot, size_reserve):
|
|||
pmb.chroot.apk.install(["cgpt"], Chroot.native(), build=False)
|
||||
|
||||
cgpt = {
|
||||
'kpart_start': pmb.parse.deviceinfo().cgpt_kpart_start,
|
||||
'kpart_size': pmb.parse.deviceinfo().cgpt_kpart_size,
|
||||
"kpart_start": pmb.parse.deviceinfo().cgpt_kpart_start,
|
||||
"kpart_size": pmb.parse.deviceinfo().cgpt_kpart_size,
|
||||
}
|
||||
|
||||
# Convert to MB and print info
|
||||
mb_boot = f"{round(size_boot)}M"
|
||||
mb_reserved = f"{round(size_reserve)}M"
|
||||
logging.info(f"(native) partition /dev/install (boot: {mb_boot},"
|
||||
f" reserved: {mb_reserved}, root: the rest)")
|
||||
logging.info(
|
||||
f"(native) partition /dev/install (boot: {mb_boot},"
|
||||
f" reserved: {mb_reserved}, root: the rest)"
|
||||
)
|
||||
|
||||
boot_part_start = str(int(cgpt['kpart_start']) + int(cgpt['kpart_size']))
|
||||
boot_part_start = str(int(cgpt["kpart_start"]) + int(cgpt["kpart_size"]))
|
||||
|
||||
# Convert to sectors
|
||||
s_boot = str(int(size_boot * 1024 * 1024 / 512))
|
||||
s_root_start = str(int(
|
||||
int(boot_part_start) + int(s_boot) + size_reserve * 1024 * 1024 / 512
|
||||
))
|
||||
s_root_start = str(int(int(boot_part_start) + int(s_boot) + size_reserve * 1024 * 1024 / 512))
|
||||
|
||||
commands = [
|
||||
["parted", "-s", "/dev/install", "mktable", "gpt"],
|
||||
["cgpt", "create", "/dev/install"],
|
||||
[
|
||||
"cgpt", "add",
|
||||
"-i", str(layout["kernel"]),
|
||||
"-t", "kernel",
|
||||
"-b", cgpt['kpart_start'],
|
||||
"-s", cgpt['kpart_size'],
|
||||
"-l", "pmOS_kernel",
|
||||
"-S", "1", # Successful flag
|
||||
"-T", "5", # Tries flag
|
||||
"-P", "10", # Priority flag
|
||||
"/dev/install"
|
||||
"cgpt",
|
||||
"add",
|
||||
"-i",
|
||||
str(layout["kernel"]),
|
||||
"-t",
|
||||
"kernel",
|
||||
"-b",
|
||||
cgpt["kpart_start"],
|
||||
"-s",
|
||||
cgpt["kpart_size"],
|
||||
"-l",
|
||||
"pmOS_kernel",
|
||||
"-S",
|
||||
"1", # Successful flag
|
||||
"-T",
|
||||
"5", # Tries flag
|
||||
"-P",
|
||||
"10", # Priority flag
|
||||
"/dev/install",
|
||||
],
|
||||
[
|
||||
"cgpt", "add",
|
||||
"cgpt",
|
||||
"add",
|
||||
# pmOS_boot is second partition, the first will be ChromeOS kernel
|
||||
# partition
|
||||
"-i", str(layout["boot"]), # Partition number
|
||||
"-t", "efi", # Mark this partition as bootable for u-boot
|
||||
"-b", boot_part_start,
|
||||
"-s", s_boot,
|
||||
"-l", "pmOS_boot",
|
||||
"/dev/install"
|
||||
"-i",
|
||||
str(layout["boot"]), # Partition number
|
||||
"-t",
|
||||
"efi", # Mark this partition as bootable for u-boot
|
||||
"-b",
|
||||
boot_part_start,
|
||||
"-s",
|
||||
s_boot,
|
||||
"-l",
|
||||
"pmOS_boot",
|
||||
"/dev/install",
|
||||
],
|
||||
]
|
||||
|
||||
dev_size = pmb.chroot.root(
|
||||
["blockdev", "--getsz", "/dev/install"], output_return=True)
|
||||
dev_size = pmb.chroot.root(["blockdev", "--getsz", "/dev/install"], output_return=True)
|
||||
# 33: Sec GPT table (32) + Sec GPT header (1)
|
||||
root_size = str(int(dev_size) - int(s_root_start) - 33)
|
||||
|
||||
commands += [
|
||||
[
|
||||
"cgpt", "add",
|
||||
"-i", str(layout["root"]),
|
||||
"-t", "data",
|
||||
"-b", s_root_start,
|
||||
"-s", root_size,
|
||||
"-l", "pmOS_root",
|
||||
"/dev/install"
|
||||
"cgpt",
|
||||
"add",
|
||||
"-i",
|
||||
str(layout["root"]),
|
||||
"-t",
|
||||
"data",
|
||||
"-b",
|
||||
s_root_start,
|
||||
"-s",
|
||||
root_size,
|
||||
"-l",
|
||||
"pmOS_root",
|
||||
"/dev/install",
|
||||
],
|
||||
["partx", "-a", "/dev/install"]
|
||||
["partx", "-a", "/dev/install"],
|
||||
]
|
||||
|
||||
for command in commands:
|
||||
|
|
|
@ -24,8 +24,7 @@ def create_zip(args: PmbArgs, chroot: Chroot, device: str):
|
|||
fvars = pmb.flasher.variables(args, flavor, method)
|
||||
|
||||
# Install recovery installer package in buildroot
|
||||
pmb.chroot.apk.install(["postmarketos-android-recovery-installer"],
|
||||
chroot)
|
||||
pmb.chroot.apk.install(["postmarketos-android-recovery-installer"], chroot)
|
||||
|
||||
logging.info(f"({chroot}) create recovery zip")
|
||||
|
||||
|
@ -65,12 +64,12 @@ def create_zip(args: PmbArgs, chroot: Chroot, device: str):
|
|||
# Move config file from /tmp/ to zip root
|
||||
["mv", "/tmp/install_options", "chroot/install_options"],
|
||||
# Create tar archive of the rootfs
|
||||
["tar", "-pcf", "rootfs.tar", "--exclude", "./home", "-C", rootfs,
|
||||
"."],
|
||||
["tar", "-pcf", "rootfs.tar", "--exclude", "./home", "-C", rootfs, "."],
|
||||
# Append packages keys
|
||||
["tar", "-prf", "rootfs.tar", "-C", "/", "./etc/apk/keys"],
|
||||
# Compress with -1 for speed improvement
|
||||
["gzip", "-f1", "rootfs.tar"],
|
||||
["build-recovery-zip", device]]
|
||||
["build-recovery-zip", device],
|
||||
]
|
||||
for command in commands:
|
||||
pmb.chroot.root(command, chroot, working_dir=zip_root)
|
||||
|
|
|
@ -8,11 +8,11 @@ import pmb.helpers.pmaports
|
|||
|
||||
|
||||
def get_groups(config: Config) -> List[str]:
|
||||
""" Get all groups to which the user additionally must be added.
|
||||
The list of groups are listed in _pmb_groups of the UI and
|
||||
UI-extras package.
|
||||
"""Get all groups to which the user additionally must be added.
|
||||
The list of groups are listed in _pmb_groups of the UI and
|
||||
UI-extras package.
|
||||
|
||||
:returns: list of groups, e.g. ["feedbackd", "udev"] """
|
||||
:returns: list of groups, e.g. ["feedbackd", "udev"]"""
|
||||
ret: List[str] = []
|
||||
if config.ui == "none":
|
||||
return ret
|
||||
|
@ -22,8 +22,7 @@ def get_groups(config: Config) -> List[str]:
|
|||
apkbuild = pmb.helpers.pmaports.get(meta)
|
||||
groups = apkbuild["_pmb_groups"]
|
||||
if groups:
|
||||
logging.debug(f"{meta}: install _pmb_groups:"
|
||||
f" {', '.join(groups)}")
|
||||
logging.debug(f"{meta}: install _pmb_groups:" f" {', '.join(groups)}")
|
||||
ret += groups
|
||||
|
||||
# UI-extras subpackage
|
||||
|
@ -31,8 +30,7 @@ def get_groups(config: Config) -> List[str]:
|
|||
if config.ui_extras and meta_extras in apkbuild["subpackages"]:
|
||||
groups = apkbuild["subpackages"][meta_extras]["_pmb_groups"]
|
||||
if groups:
|
||||
logging.debug(f"{meta_extras}: install _pmb_groups:"
|
||||
f" {', '.join(groups)}")
|
||||
logging.debug(f"{meta_extras}: install _pmb_groups:" f" {', '.join(groups)}")
|
||||
ret += groups
|
||||
|
||||
return ret
|
||||
|
|
|
@ -17,7 +17,6 @@ class Wrapper:
|
|||
self.hits = 0
|
||||
self.misses = 0
|
||||
|
||||
|
||||
# When someone attempts to call a cached function, they'll
|
||||
# actually end up here. We first check if we have a cached
|
||||
# result and if not then we do the actual function call and
|
||||
|
@ -74,7 +73,6 @@ class Cache:
|
|||
self.kwargs = kwargs
|
||||
self.cache_deepcopy = cache_deepcopy
|
||||
|
||||
|
||||
# Build the cache key, or return None to not cache in the case where
|
||||
# we only cache when an argument has a specific value
|
||||
def build_key(self, func: Callable, *args, **kwargs) -> Optional[str]:
|
||||
|
@ -95,12 +93,16 @@ class Cache:
|
|||
elif val.default != inspect.Parameter.empty:
|
||||
passed_args[k] = val.default
|
||||
else:
|
||||
raise ValueError(f"Invalid cache key argument {k}"
|
||||
f" in function {func.__module__}.{func.__name__}")
|
||||
raise ValueError(
|
||||
f"Invalid cache key argument {k}"
|
||||
f" in function {func.__module__}.{func.__name__}"
|
||||
)
|
||||
|
||||
for k, v in self.kwargs.items():
|
||||
if k not in signature.parameters.keys():
|
||||
raise ValueError(f"Cache key attribute {k} is not a valid parameter to {func.__name__}()")
|
||||
raise ValueError(
|
||||
f"Cache key attribute {k} is not a valid parameter to {func.__name__}()"
|
||||
)
|
||||
passed_val = passed_args[k]
|
||||
if passed_val != v:
|
||||
# Don't cache
|
||||
|
@ -114,20 +116,22 @@ class Cache:
|
|||
if v.__str__ != object.__str__:
|
||||
key += f"{v}~"
|
||||
else:
|
||||
raise ValueError(f"Cache key argument {k} to function"
|
||||
f" {func.__name__} must be a stringable type")
|
||||
raise ValueError(
|
||||
f"Cache key argument {k} to function"
|
||||
f" {func.__name__} must be a stringable type"
|
||||
)
|
||||
|
||||
return key
|
||||
|
||||
|
||||
def __call__(self, func: Callable):
|
||||
argnames = func.__code__.co_varnames
|
||||
for a in self.params:
|
||||
if a not in argnames:
|
||||
raise ValueError(f"Cache key attribute {a} is not a valid parameter to {func.__name__}()")
|
||||
raise ValueError(
|
||||
f"Cache key attribute {a} is not a valid parameter to {func.__name__}()"
|
||||
)
|
||||
|
||||
return Wrapper(self, func)
|
||||
|
||||
|
||||
def clear(self):
|
||||
self.cache.clear()
|
||||
|
|
|
@ -6,9 +6,9 @@ from . import Cache, Wrapper
|
|||
def test_cache_hits_basic():
|
||||
def multiply_2(x: int) -> int:
|
||||
return x * 2
|
||||
|
||||
|
||||
multiply_2_cached = Cache("x")(multiply_2)
|
||||
|
||||
|
||||
assert isinstance(multiply_2_cached, Wrapper)
|
||||
|
||||
assert multiply_2(2) == 4
|
||||
|
@ -28,6 +28,7 @@ def test_cache_hits_basic():
|
|||
assert multiply_2_cached(3) == 6
|
||||
assert multiply_2_cached.hits == 2
|
||||
|
||||
|
||||
def test_cache_hits_kwargs():
|
||||
def multiply_2(x: int, y: int = 2, z: List[int] = []) -> int:
|
||||
return x * y + sum(z)
|
||||
|
@ -56,7 +57,7 @@ def test_cache_hits_kwargs():
|
|||
|
||||
assert multiply_2_cached_y3(1, 1, [1, 1]) == 3
|
||||
assert multiply_2_cached_y3.misses == 1
|
||||
|
||||
|
||||
assert multiply_2_cached_y3(1, 1, [1, 1]) == 3
|
||||
assert multiply_2_cached_y3.misses == 2
|
||||
|
||||
|
@ -65,6 +66,7 @@ def test_cache_hits_kwargs():
|
|||
assert multiply_2_cached_y3(1, 3, [4, 1]) == 8
|
||||
assert multiply_2_cached_y3.hits == 1
|
||||
|
||||
|
||||
def test_build_key():
|
||||
def multiply_2(x: int, y: int = 2, z: List[int] = []) -> int:
|
||||
return x * y + sum(z)
|
||||
|
|
|
@ -19,7 +19,7 @@ def start_nbd_server(device: str, replace: bool, ip="172.16.42.2", port=9999):
|
|||
:param port: port of nbd server
|
||||
"""
|
||||
|
||||
pmb.chroot.apk.install(['nbd'], Chroot.native())
|
||||
pmb.chroot.apk.install(["nbd"], Chroot.native())
|
||||
|
||||
chroot = Chroot.native()
|
||||
|
||||
|
@ -27,18 +27,20 @@ def start_nbd_server(device: str, replace: bool, ip="172.16.42.2", port=9999):
|
|||
if not (chroot / rootfs_path).exists() or replace:
|
||||
rootfs_path2 = Path("/home/pmos/rootfs") / f"{device}.img"
|
||||
if not (chroot / rootfs_path2).exists():
|
||||
raise RuntimeError("The rootfs has not been generated yet, please "
|
||||
"run 'pmbootstrap install' first.")
|
||||
if replace and not \
|
||||
pmb.helpers.cli.confirm(f"Are you sure you want to "
|
||||
f"replace the rootfs for "
|
||||
f"{device}?"):
|
||||
raise RuntimeError(
|
||||
"The rootfs has not been generated yet, please " "run 'pmbootstrap install' first."
|
||||
)
|
||||
if replace and not pmb.helpers.cli.confirm(
|
||||
f"Are you sure you want to " f"replace the rootfs for " f"{device}?"
|
||||
):
|
||||
return
|
||||
pmb.chroot.root(["cp", rootfs_path2, rootfs_path])
|
||||
logging.info(f"NOTE: Copied device image to {get_context().config.work}"
|
||||
f"/images_netboot/. The image will persist \"pmbootstrap "
|
||||
f"zap\" for your convenience. Use \"pmbootstrap netboot "
|
||||
f"serve --help\" for more options.")
|
||||
logging.info(
|
||||
f"NOTE: Copied device image to {get_context().config.work}"
|
||||
f'/images_netboot/. The image will persist "pmbootstrap '
|
||||
f'zap" for your convenience. Use "pmbootstrap netboot '
|
||||
f'serve --help" for more options.'
|
||||
)
|
||||
|
||||
logging.info(f"Running nbd server for {device} on {ip} port {port}.")
|
||||
|
||||
|
@ -62,8 +64,9 @@ def start_nbd_server(device: str, replace: bool, ip="172.16.42.2", port=9999):
|
|||
break
|
||||
|
||||
logging.info("Found postmarketOS device, serving image...")
|
||||
pmb.chroot.root(["nbd-server", f"{ip}@{port}", rootfs_path, "-d"],
|
||||
check=False, disable_timeout=True)
|
||||
pmb.chroot.root(
|
||||
["nbd-server", f"{ip}@{port}", rootfs_path, "-d"], check=False, disable_timeout=True
|
||||
)
|
||||
logging.info("nbd-server quit. Connection lost?")
|
||||
# On a reboot nbd-server will quit, but the IP address sticks around
|
||||
# for a bit longer, so wait.
|
||||
|
|
|
@ -33,15 +33,19 @@ revar5 = re.compile(r"([a-zA-Z_]+[a-zA-Z0-9_]*)=")
|
|||
|
||||
def replace_variable(apkbuild, value: str) -> str:
|
||||
def log_key_not_found(match):
|
||||
logging.verbose(f"{apkbuild['pkgname']}: key '{match.group(1)}' for"
|
||||
f" replacing '{match.group(0)}' not found, ignoring")
|
||||
logging.verbose(
|
||||
f"{apkbuild['pkgname']}: key '{match.group(1)}' for"
|
||||
f" replacing '{match.group(0)}' not found, ignoring"
|
||||
)
|
||||
|
||||
# ${foo}
|
||||
for match in revar.finditer(value):
|
||||
try:
|
||||
logging.verbose("{}: replace '{}' with '{}'".format(
|
||||
apkbuild["pkgname"], match.group(0),
|
||||
apkbuild[match.group(1)]))
|
||||
logging.verbose(
|
||||
"{}: replace '{}' with '{}'".format(
|
||||
apkbuild["pkgname"], match.group(0), apkbuild[match.group(1)]
|
||||
)
|
||||
)
|
||||
value = value.replace(match.group(0), apkbuild[match.group(1)], 1)
|
||||
except KeyError:
|
||||
log_key_not_found(match)
|
||||
|
@ -50,9 +54,9 @@ def replace_variable(apkbuild, value: str) -> str:
|
|||
for match in revar2.finditer(value):
|
||||
try:
|
||||
newvalue = apkbuild[match.group(1)]
|
||||
logging.verbose("{}: replace '{}' with '{}'".format(
|
||||
apkbuild["pkgname"], match.group(0),
|
||||
newvalue))
|
||||
logging.verbose(
|
||||
"{}: replace '{}' with '{}'".format(apkbuild["pkgname"], match.group(0), newvalue)
|
||||
)
|
||||
value = value.replace(match.group(0), newvalue, 1)
|
||||
except KeyError:
|
||||
log_key_not_found(match)
|
||||
|
@ -66,8 +70,9 @@ def replace_variable(apkbuild, value: str) -> str:
|
|||
if replacement is None: # arg 3 is optional
|
||||
replacement = ""
|
||||
newvalue = newvalue.replace(search, replacement, 1)
|
||||
logging.verbose("{}: replace '{}' with '{}'".format(
|
||||
apkbuild["pkgname"], match.group(0), newvalue))
|
||||
logging.verbose(
|
||||
"{}: replace '{}' with '{}'".format(apkbuild["pkgname"], match.group(0), newvalue)
|
||||
)
|
||||
value = value.replace(match.group(0), newvalue, 1)
|
||||
except KeyError:
|
||||
log_key_not_found(match)
|
||||
|
@ -80,8 +85,9 @@ def replace_variable(apkbuild, value: str) -> str:
|
|||
substr = match.group(2)
|
||||
if newvalue.startswith(substr):
|
||||
newvalue = newvalue.replace(substr, "", 1)
|
||||
logging.verbose("{}: replace '{}' with '{}'".format(
|
||||
apkbuild["pkgname"], match.group(0), newvalue))
|
||||
logging.verbose(
|
||||
"{}: replace '{}' with '{}'".format(apkbuild["pkgname"], match.group(0), newvalue)
|
||||
)
|
||||
value = value.replace(match.group(0), newvalue, 1)
|
||||
except KeyError:
|
||||
log_key_not_found(match)
|
||||
|
@ -123,7 +129,7 @@ def read_file(path: Path):
|
|||
"""
|
||||
with path.open(encoding="utf-8") as handle:
|
||||
lines = handle.readlines()
|
||||
if handle.newlines != '\n':
|
||||
if handle.newlines != "\n":
|
||||
raise RuntimeError(f"Wrong line endings in APKBUILD: {path}")
|
||||
return lines
|
||||
|
||||
|
@ -155,12 +161,12 @@ def parse_next_attribute(lines, i, path):
|
|||
if not rematch5:
|
||||
return (None, None, i)
|
||||
attribute = rematch5.group(0)
|
||||
value = lines[i][len(attribute):-1]
|
||||
value = lines[i][len(attribute) : -1]
|
||||
attribute = rematch5.group(0).rstrip("=")
|
||||
|
||||
# Determine end quote sign
|
||||
end_char = None
|
||||
for char in ["'", "\""]:
|
||||
for char in ["'", '"']:
|
||||
if value.startswith(char):
|
||||
end_char = char
|
||||
value = value[1:]
|
||||
|
@ -185,8 +191,9 @@ def parse_next_attribute(lines, i, path):
|
|||
value += line.strip()
|
||||
i += 1
|
||||
|
||||
raise RuntimeError(f"Can't find closing quote sign ({end_char}) for"
|
||||
f" attribute '{attribute}' in: {path}")
|
||||
raise RuntimeError(
|
||||
f"Can't find closing quote sign ({end_char}) for" f" attribute '{attribute}' in: {path}"
|
||||
)
|
||||
|
||||
|
||||
def _parse_attributes(path, lines, apkbuild_attributes, ret):
|
||||
|
@ -245,7 +252,7 @@ def _parse_subpackage(path, lines, apkbuild, subpackages, subpkg):
|
|||
"""
|
||||
subpkgparts = subpkg.split(":")
|
||||
subpkgname = subpkgparts[0]
|
||||
subpkgsplit = subpkgname[subpkgname.rfind("-") + 1:]
|
||||
subpkgsplit = subpkgname[subpkgname.rfind("-") + 1 :]
|
||||
# If there are multiple sections to the subpackage, the middle one (item 1 in the
|
||||
# sequence in this case) is the custom function name which we should use instead of
|
||||
# the deduced one. For example:
|
||||
|
@ -280,13 +287,15 @@ def _parse_subpackage(path, lines, apkbuild, subpackages, subpkg):
|
|||
subpackages[subpkgname] = None
|
||||
logging.verbose(
|
||||
f"{apkbuild['pkgname']}: subpackage function '{subpkgsplit}' for "
|
||||
f"subpackage '{subpkgname}' not found, ignoring")
|
||||
f"subpackage '{subpkgname}' not found, ignoring"
|
||||
)
|
||||
return
|
||||
|
||||
if not end:
|
||||
raise RuntimeError(
|
||||
f"Could not find end of subpackage function, no line starts with "
|
||||
f"'}}' after '{prefix}' in {path}")
|
||||
f"'}}' after '{prefix}' in {path}"
|
||||
)
|
||||
|
||||
lines = lines[start:end]
|
||||
# Strip tabs before lines in function
|
||||
|
@ -303,8 +312,7 @@ def _parse_subpackage(path, lines, apkbuild, subpackages, subpkg):
|
|||
apkbuild["_pmb_recommends"] = ""
|
||||
|
||||
# Parse relevant attributes for the subpackage
|
||||
_parse_attributes(
|
||||
path, lines, pmb.config.apkbuild_package_attributes, apkbuild)
|
||||
_parse_attributes(path, lines, pmb.config.apkbuild_package_attributes, apkbuild)
|
||||
|
||||
# Return only properties interesting for subpackages
|
||||
ret = {}
|
||||
|
@ -347,17 +355,18 @@ def apkbuild(path: Path, check_pkgver=True, check_pkgname=True):
|
|||
if not os.path.realpath(path).endswith(suffix):
|
||||
logging.info(f"Folder: '{os.path.dirname(path)}'")
|
||||
logging.info(f"Pkgname: '{ret['pkgname']}'")
|
||||
raise RuntimeError("The pkgname must be equal to the name of"
|
||||
" the folder that contains the APKBUILD!")
|
||||
raise RuntimeError(
|
||||
"The pkgname must be equal to the name of" " the folder that contains the APKBUILD!"
|
||||
)
|
||||
|
||||
# Sanity check: pkgver
|
||||
if check_pkgver:
|
||||
if not pmb.parse.version.validate(ret["pkgver"]):
|
||||
logging.info(
|
||||
"NOTE: Valid pkgvers are described here: "
|
||||
"https://wiki.alpinelinux.org/wiki/APKBUILD_Reference#pkgver")
|
||||
raise RuntimeError(f"Invalid pkgver '{ret['pkgver']}' in"
|
||||
f" APKBUILD: {path}")
|
||||
"https://wiki.alpinelinux.org/wiki/APKBUILD_Reference#pkgver"
|
||||
)
|
||||
raise RuntimeError(f"Invalid pkgver '{ret['pkgver']}' in" f" APKBUILD: {path}")
|
||||
|
||||
# Fill cache
|
||||
return ret
|
||||
|
@ -375,7 +384,7 @@ def kernels(device: str):
|
|||
"downstream": "Downstream description"}
|
||||
"""
|
||||
# Read the APKBUILD
|
||||
apkbuild_path = pmb.helpers.devices.find_path(device, 'APKBUILD')
|
||||
apkbuild_path = pmb.helpers.devices.find_path(device, "APKBUILD")
|
||||
if apkbuild_path is None:
|
||||
return None
|
||||
subpackages = apkbuild(apkbuild_path)["subpackages"]
|
||||
|
@ -387,9 +396,8 @@ def kernels(device: str):
|
|||
if not subpkgname.startswith(subpackage_prefix):
|
||||
continue
|
||||
if subpkg is None:
|
||||
raise RuntimeError(
|
||||
f"Cannot find subpackage function for: {subpkgname}")
|
||||
name = subpkgname[len(subpackage_prefix):]
|
||||
raise RuntimeError(f"Cannot find subpackage function for: {subpkgname}")
|
||||
name = subpkgname[len(subpackage_prefix) :]
|
||||
ret[name] = subpkg["pkgdesc"]
|
||||
|
||||
# Return
|
||||
|
@ -407,11 +415,11 @@ def _parse_comment_tags(lines, tag):
|
|||
:param tag: the tag to parse, e.g. Maintainer
|
||||
:returns: array of values of the tag, one per line
|
||||
"""
|
||||
prefix = f'# {tag}:'
|
||||
prefix = f"# {tag}:"
|
||||
ret = []
|
||||
for line in lines:
|
||||
if line.startswith(prefix):
|
||||
ret.append(line[len(prefix):].strip())
|
||||
ret.append(line[len(prefix) :].strip())
|
||||
return ret
|
||||
|
||||
|
||||
|
@ -425,7 +433,7 @@ def maintainers(path):
|
|||
:returns: array of (at least one) maintainer, or None
|
||||
"""
|
||||
lines = read_file(path)
|
||||
maintainers = _parse_comment_tags(lines, 'Maintainer')
|
||||
maintainers = _parse_comment_tags(lines, "Maintainer")
|
||||
if not maintainers:
|
||||
return None
|
||||
|
||||
|
@ -434,8 +442,8 @@ def maintainers(path):
|
|||
if len(maintainers) > 1:
|
||||
raise RuntimeError("Multiple Maintainer: lines in APKBUILD")
|
||||
|
||||
maintainers += _parse_comment_tags(lines, 'Co-Maintainer')
|
||||
if '' in maintainers:
|
||||
maintainers += _parse_comment_tags(lines, "Co-Maintainer")
|
||||
if "" in maintainers:
|
||||
raise RuntimeError("Empty (Co-)Maintainer: tag")
|
||||
return maintainers
|
||||
|
||||
|
@ -448,7 +456,7 @@ def archived(path):
|
|||
:param path: full path to the APKBUILD
|
||||
:returns: reason why APKBUILD is archived, or None
|
||||
"""
|
||||
archived = _parse_comment_tags(read_file(path), 'Archived')
|
||||
archived = _parse_comment_tags(read_file(path), "Archived")
|
||||
if not archived:
|
||||
return None
|
||||
return '\n'.join(archived)
|
||||
return "\n".join(archived)
|
||||
|
|
|
@ -25,6 +25,7 @@ apkindex_map = {
|
|||
|
||||
required_apkindex_keys = ["arch", "pkgname", "version"]
|
||||
|
||||
|
||||
def parse_next_block(path: Path, lines: List[str]):
|
||||
"""Parse the next block in an APKINDEX.
|
||||
|
||||
|
@ -49,7 +50,7 @@ def parse_next_block(path: Path, lines: List[str]):
|
|||
"""
|
||||
# Parse until we hit an empty line or end of file
|
||||
ret: Dict[str, Any] = {}
|
||||
required_found = 0 # Count the required keys we found
|
||||
required_found = 0 # Count the required keys we found
|
||||
line = ""
|
||||
while len(lines):
|
||||
# We parse backwards for performance (pop(0) is super slow)
|
||||
|
@ -62,7 +63,7 @@ def parse_next_block(path: Path, lines: List[str]):
|
|||
|
||||
# The checksum key is always the FIRST in the block, so when we find
|
||||
# it we know we're done.
|
||||
if k == 'C':
|
||||
if k == "C":
|
||||
break
|
||||
if key:
|
||||
if key in ret:
|
||||
|
@ -79,10 +80,11 @@ def parse_next_block(path: Path, lines: List[str]):
|
|||
if required_found != len(required_apkindex_keys):
|
||||
for key in required_apkindex_keys:
|
||||
if key not in ret:
|
||||
raise RuntimeError(f"Missing required key '{key}' in block "
|
||||
f"{ret}, file: {path}")
|
||||
raise RuntimeError(f"Expected {len(required_apkindex_keys)} required keys,"
|
||||
f" but found {required_found} in block: {ret}, file: {path}")
|
||||
raise RuntimeError(f"Missing required key '{key}' in block " f"{ret}, file: {path}")
|
||||
raise RuntimeError(
|
||||
f"Expected {len(required_apkindex_keys)} required keys,"
|
||||
f" but found {required_found} in block: {ret}, file: {path}"
|
||||
)
|
||||
|
||||
# Format optional lists
|
||||
for key in ["provides", "depends"]:
|
||||
|
@ -151,7 +153,7 @@ def parse(path: Path, multiple_providers=True):
|
|||
APKINDEX files from a repository (#1122), but
|
||||
not when parsing apk's installed packages DB.
|
||||
:returns: (without multiple_providers)
|
||||
|
||||
|
||||
Generic format:
|
||||
``{ pkgname: block, ... }``
|
||||
|
||||
|
@ -171,8 +173,10 @@ def parse(path: Path, multiple_providers=True):
|
|||
"""
|
||||
# Require the file to exist
|
||||
if not path.is_file():
|
||||
logging.verbose("NOTE: APKINDEX not found, assuming no binary packages"
|
||||
f" exist for that architecture: {path}")
|
||||
logging.verbose(
|
||||
"NOTE: APKINDEX not found, assuming no binary packages"
|
||||
f" exist for that architecture: {path}"
|
||||
)
|
||||
return {}
|
||||
|
||||
# Try to get a cached result first
|
||||
|
@ -191,7 +195,7 @@ def parse(path: Path, multiple_providers=True):
|
|||
lines: Sequence[str]
|
||||
if tarfile.is_tarfile(path):
|
||||
with tarfile.open(path, "r:gz") as tar:
|
||||
with tar.extractfile(tar.getmember("APKINDEX")) as handle: # type:ignore[union-attr]
|
||||
with tar.extractfile(tar.getmember("APKINDEX")) as handle: # type:ignore[union-attr]
|
||||
lines = handle.read().decode().splitlines()
|
||||
else:
|
||||
with path.open("r", encoding="utf-8") as handle:
|
||||
|
@ -205,7 +209,7 @@ def parse(path: Path, multiple_providers=True):
|
|||
# Parse the whole APKINDEX file
|
||||
ret: Dict[str, Any] = collections.OrderedDict()
|
||||
if lines[-1] == "\n":
|
||||
lines.pop() # Strip the trailing newline
|
||||
lines.pop() # Strip the trailing newline
|
||||
while True:
|
||||
block = parse_next_block(path, lines)
|
||||
if not block:
|
||||
|
@ -213,8 +217,7 @@ def parse(path: Path, multiple_providers=True):
|
|||
|
||||
# Skip virtual packages
|
||||
if "timestamp" not in block:
|
||||
logging.verbose(f"Skipped virtual package {block} in"
|
||||
f" file: {path}")
|
||||
logging.verbose(f"Skipped virtual package {block} in" f" file: {path}")
|
||||
continue
|
||||
|
||||
# Add the next package and all aliases
|
||||
|
@ -245,7 +248,7 @@ def parse_blocks(path: Path):
|
|||
"""
|
||||
# Parse all lines
|
||||
with tarfile.open(path, "r:gz") as tar:
|
||||
with tar.extractfile(tar.getmember("APKINDEX")) as handle: # type:ignore[union-attr]
|
||||
with tar.extractfile(tar.getmember("APKINDEX")) as handle: # type:ignore[union-attr]
|
||||
lines = handle.read().decode().splitlines()
|
||||
|
||||
# Parse lines into blocks
|
||||
|
@ -273,12 +276,14 @@ def clear_cache(path: Path):
|
|||
del pmb.helpers.other.cache["apkindex"][key]
|
||||
return True
|
||||
else:
|
||||
logging.verbose("Nothing to do, path was not in cache:" +
|
||||
str(pmb.helpers.other.cache["apkindex"].keys()))
|
||||
logging.verbose(
|
||||
"Nothing to do, path was not in cache:"
|
||||
+ str(pmb.helpers.other.cache["apkindex"].keys())
|
||||
)
|
||||
return False
|
||||
|
||||
|
||||
def providers(package, arch: Optional[Arch]=None, must_exist=True, indexes=None):
|
||||
def providers(package, arch: Optional[Arch] = None, must_exist=True, indexes=None):
|
||||
"""
|
||||
Get all packages, which provide one package.
|
||||
|
||||
|
@ -311,8 +316,10 @@ def providers(package, arch: Optional[Arch]=None, must_exist=True, indexes=None)
|
|||
if provider_pkgname in ret:
|
||||
version_last = ret[provider_pkgname]["version"]
|
||||
if pmb.parse.version.compare(version, version_last) == -1:
|
||||
logging.verbose(f"{package}: provided by: {provider_pkgname}-{version}"
|
||||
f"in {path} (but {version_last} is higher)")
|
||||
logging.verbose(
|
||||
f"{package}: provided by: {provider_pkgname}-{version}"
|
||||
f"in {path} (but {version_last} is higher)"
|
||||
)
|
||||
continue
|
||||
|
||||
# Add the provider to ret
|
||||
|
@ -321,6 +328,7 @@ def providers(package, arch: Optional[Arch]=None, must_exist=True, indexes=None)
|
|||
|
||||
if ret == {} and must_exist:
|
||||
import os
|
||||
|
||||
logging.debug(f"Searched in APKINDEX files: {', '.join([os.fspath(x) for x in indexes])}")
|
||||
raise RuntimeError("Could not find package '" + package + "'!")
|
||||
|
||||
|
@ -346,7 +354,8 @@ def provider_highest_priority(providers, pkgname):
|
|||
if priority_providers:
|
||||
logging.debug(
|
||||
f"{pkgname}: picked provider(s) with highest priority "
|
||||
f"{max_priority}: {', '.join(priority_providers.keys())}")
|
||||
f"{max_priority}: {', '.join(priority_providers.keys())}"
|
||||
)
|
||||
return priority_providers
|
||||
|
||||
# None of the providers seems to have a provider_priority defined
|
||||
|
@ -365,11 +374,12 @@ def provider_shortest(providers, pkgname):
|
|||
if len(providers) != 1:
|
||||
logging.debug(
|
||||
f"{pkgname}: has multiple providers ("
|
||||
f"{', '.join(providers.keys())}), picked shortest: {ret}")
|
||||
f"{', '.join(providers.keys())}), picked shortest: {ret}"
|
||||
)
|
||||
return providers[ret]
|
||||
|
||||
|
||||
def package(package, arch: Optional[Arch]=None, must_exist=True, indexes=None):
|
||||
def package(package, arch: Optional[Arch] = None, must_exist=True, indexes=None):
|
||||
"""
|
||||
Get a specific package's data from an apkindex.
|
||||
|
||||
|
@ -398,6 +408,5 @@ def package(package, arch: Optional[Arch]=None, must_exist=True, indexes=None):
|
|||
|
||||
# No provider
|
||||
if must_exist:
|
||||
raise RuntimeError("Package '" + package + "' not found in any"
|
||||
" APKINDEX.")
|
||||
raise RuntimeError("Package '" + package + "' not found in any" " APKINDEX.")
|
||||
return None
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -14,7 +14,7 @@ def binfmt_info(arch_qemu):
|
|||
logging.verbose(f"parsing: {info}")
|
||||
with open(info, "r") as handle:
|
||||
for line in handle:
|
||||
if line.startswith('#') or "=" not in line:
|
||||
if line.startswith("#") or "=" not in line:
|
||||
continue
|
||||
split = line.split("=")
|
||||
key = split[0].strip()
|
||||
|
@ -26,8 +26,7 @@ def binfmt_info(arch_qemu):
|
|||
for type in ["mask", "magic"]:
|
||||
key = arch_qemu + "_" + type
|
||||
if key not in full:
|
||||
raise RuntimeError(
|
||||
f"Could not find key {key} in binfmt info file: {info}")
|
||||
raise RuntimeError(f"Could not find key {key} in binfmt info file: {info}")
|
||||
ret[type] = full[key]
|
||||
logging.verbose("=> " + str(ret))
|
||||
return ret
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue