forked from Mirror/pmbootstrap
Here are the changes necessary in pmbootstrap to make proprietary software installed onto the device (firmware and userspace drivers) optional (#756). To full close the issue, we need to apply this concept to all device packages we already have in a follow-up PR. Changes: * New config file options nonfree_firmware and nonfree_userland, which we ask for during "pmbootstrap init" if there are non-free components for the selected device. * We find that out by checking the APKBUILD's subpakages: The non-free packages are called $pkgname-nonfree-firmware and $pkgname-nonfree-userland. * During "pmbootstrap init" we also show the pkgdesc of these subpackages. Parsing that is implemented in pmb.parse._apkbuild.subpkgdesc(). It was not implemented as part of the regular APKBUILD parsing, as this would need a change in the output format, and it is a lot *less* code if done like in this commit. * pmb/parse/apkbuild.py was renamed to _apkbuild.py, and pmb/install/install.py to _install.py: needed to call the function in the usual way (e.g. pmb.parse.apkbuild()) but still being able to test the individual functions from these files in the test suite. We did the same thing for pmb/build/_package.py already. * Install: New function get_nonfree_packages() returns the non-free packages that will be installed, based on the user's choice in "pmbootstrap init" and on the subpackages the device has. * Added test cases and test data (APKBUILDs) for all new code, refactored test/test_questions.py to have multiple functions for testing the various questions / question types from "pmbootstrap init" instead of having it all in one big function. This allows to use another aport folder for testing the new non-free related questions in init.
303 lines
12 KiB
Python
303 lines
12 KiB
Python
"""
|
|
Copyright 2018 Oliver Smith
|
|
|
|
This file is part of pmbootstrap.
|
|
|
|
pmbootstrap is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
pmbootstrap is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with pmbootstrap. If not, see <http://www.gnu.org/licenses/>.
|
|
"""
|
|
import logging
|
|
import glob
|
|
import os
|
|
|
|
import pmb.config
|
|
import pmb.helpers.cli
|
|
import pmb.helpers.devices
|
|
import pmb.helpers.run
|
|
import pmb.helpers.ui
|
|
import pmb.chroot.zap
|
|
import pmb.parse.deviceinfo
|
|
import pmb.parse._apkbuild
|
|
|
|
|
|
def ask_for_work_path(args):
|
|
"""
|
|
Ask for the work path, until we can create it (when it does not exist) and
|
|
write into it.
|
|
:returns: the work path
|
|
"""
|
|
logging.info("Location of the 'work' path. Multiple chroots"
|
|
" (native, device arch, device rootfs) will be created"
|
|
" in there.")
|
|
while True:
|
|
try:
|
|
ret = os.path.expanduser(pmb.helpers.cli.ask(
|
|
args, "Work path", None, args.work, False))
|
|
ret = os.path.realpath(ret)
|
|
|
|
# Work must not be inside the pmbootstrap path
|
|
if ret == pmb.config.pmb_src or ret.startswith(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
|
|
if not os.path.exists(ret):
|
|
os.makedirs(ret, 0o700, True)
|
|
with open(ret + "/version", "w") as handle:
|
|
handle.write(pmb.config.work_version + "\n")
|
|
|
|
# Make sure, that we can write into it
|
|
os.makedirs(ret + "/cache_http", 0o700, True)
|
|
return ret
|
|
except OSError:
|
|
logging.fatal("ERROR: Could not create this folder, or write"
|
|
" inside it! Please try again.")
|
|
|
|
|
|
def ask_for_ui(args):
|
|
ui_list = pmb.helpers.ui.list(args)
|
|
logging.info("Available user interfaces (" +
|
|
str(len(ui_list) - 1) + "): ")
|
|
for ui in ui_list:
|
|
logging.info("* " + ui[0] + ": " + ui[1])
|
|
while True:
|
|
ret = pmb.helpers.cli.ask(args, "User interface", None, args.ui, True)
|
|
if ret in dict(ui_list).keys():
|
|
return ret
|
|
logging.fatal("ERROR: Invalid user interface specified, please type in"
|
|
" one from the list above.")
|
|
|
|
|
|
def ask_for_keymaps(args, device):
|
|
info = pmb.parse.deviceinfo(args, device)
|
|
if "keymaps" not in info or info["keymaps"].strip() == "":
|
|
return ""
|
|
options = info["keymaps"].split(' ')
|
|
logging.info("Available keymaps for device (" + str(len(options)) +
|
|
"): " + ", ".join(options))
|
|
if args.keymap is "":
|
|
args.keymap = options[0]
|
|
|
|
while True:
|
|
ret = pmb.helpers.cli.ask(args, "Keymap", None, args.keymap, True)
|
|
if ret in options:
|
|
return ret
|
|
logging.fatal("ERROR: Invalid keymap specified, please type in"
|
|
" one from the list above.")
|
|
|
|
|
|
def ask_for_timezone(args):
|
|
localtimes = ["/etc/zoneinfo/localtime", "/etc/localtime"]
|
|
zoneinfo_path = "/usr/share/zoneinfo/"
|
|
for localtime in localtimes:
|
|
if not os.path.exists(localtime):
|
|
continue
|
|
tz = ""
|
|
if os.path.exists(localtime):
|
|
tzpath = os.path.realpath(localtime)
|
|
tzpath = tzpath.rstrip()
|
|
if os.path.exists(tzpath):
|
|
try:
|
|
_, tz = tzpath.split(zoneinfo_path)
|
|
except:
|
|
pass
|
|
if tz:
|
|
logging.info("Your host timezone: " + tz)
|
|
if pmb.helpers.cli.confirm(args, "Use this timezone instead of GMT?",
|
|
default="y"):
|
|
return tz
|
|
logging.info("WARNING: Unable to determine timezone configuration on host,"
|
|
" using GMT.")
|
|
return "GMT"
|
|
|
|
|
|
def ask_for_device_nonfree(args, device):
|
|
"""
|
|
Ask the user about enabling proprietary firmware (e.g. Wifi) and userland
|
|
(e.g. GPU drivers). All proprietary components are in subpackages
|
|
$pkgname-nonfree-firmware and $pkgname-nonfree-userland, and we show the
|
|
description of these subpackages (so they can indicate which peripherals
|
|
are affected).
|
|
|
|
:returns: answers as dict, e.g. {"firmware": True, "userland": False}
|
|
"""
|
|
# Parse existing APKBUILD or return defaults (when called from test case)
|
|
apkbuild_path = args.aports + "/device/device-" + device + "/APKBUILD"
|
|
ret = {"firmware": args.nonfree_firmware,
|
|
"userland": args.nonfree_userland}
|
|
if not os.path.exists(apkbuild_path):
|
|
return ret
|
|
apkbuild = pmb.parse.apkbuild(args, apkbuild_path)
|
|
|
|
# Only run when there is a "nonfree" subpackage
|
|
nonfree_found = False
|
|
for subpackage in apkbuild["subpackages"]:
|
|
if subpackage.startswith("device-" + device + "-nonfree"):
|
|
nonfree_found = True
|
|
if not nonfree_found:
|
|
return ret
|
|
|
|
# Short explanation
|
|
logging.info("This device has proprietary components, which trade some of"
|
|
" your freedom with making more peripherals work.")
|
|
logging.info("We would like to offer full functionality without hurting"
|
|
" your freedom, but this is currently not possible for your"
|
|
" device.")
|
|
|
|
# Ask for firmware and userland individually
|
|
for type in ["firmware", "userland"]:
|
|
subpkgname = "device-" + device + "-nonfree-" + type
|
|
if subpkgname in apkbuild["subpackages"]:
|
|
subpkgdesc = pmb.parse._apkbuild.subpkgdesc(apkbuild_path,
|
|
"nonfree_" + type)
|
|
logging.info(subpkgname + ": " + subpkgdesc)
|
|
ret[type] = pmb.helpers.cli.confirm(args, "Enable this package?",
|
|
default=ret[type])
|
|
return ret
|
|
|
|
|
|
def ask_for_device(args):
|
|
devices = sorted(pmb.helpers.devices.list(args))
|
|
logging.info("Target device (either an existing one, or a new one for"
|
|
" porting).")
|
|
logging.info("Available (" + str(len(devices)) + "): " +
|
|
", ".join(devices))
|
|
while True:
|
|
device = pmb.helpers.cli.ask(args, "Device", None, args.device, False,
|
|
"[a-z0-9]+-[a-z0-9]+")
|
|
device_exists = os.path.exists(args.aports + "/device/device-" +
|
|
device + "/deviceinfo")
|
|
if not device_exists:
|
|
logging.info("You are about to do a new device port for '" +
|
|
device + "'.")
|
|
if not pmb.helpers.cli.confirm(args, default=True):
|
|
continue
|
|
|
|
pmb.aportgen.generate(args, "device-" + device)
|
|
pmb.aportgen.generate(args, "linux-" + device)
|
|
break
|
|
|
|
nonfree = ask_for_device_nonfree(args, device)
|
|
return (device, device_exists, nonfree)
|
|
|
|
|
|
def ask_for_qemu_native_mesa_driver(args, device, arch_native):
|
|
# Native Qemu device selected? (e.g. qemu-amd64 on x86_64)
|
|
if not pmb.parse.arch.qemu_check_device(device, arch_native):
|
|
return None
|
|
|
|
drivers = pmb.config.qemu_native_mesa_drivers
|
|
logging.info("Which mesa driver do you prefer for your native Qemu device?"
|
|
" Only select something other than the default if you are"
|
|
" having graphical problems (such as glitches).")
|
|
while True:
|
|
ret = pmb.helpers.cli.ask(args, "Mesa driver", drivers,
|
|
args.qemu_native_mesa_driver)
|
|
if ret in drivers:
|
|
return ret
|
|
logging.fatal("ERROR: Please specify a driver from the list. To change"
|
|
" it, see qemu_native_mesa_drivers in"
|
|
" pmb/config/__init__.py.")
|
|
|
|
|
|
def ask_for_build_options(args, cfg):
|
|
# Allow to skip build options
|
|
logging.info("Build options: Parallel jobs: " + args.jobs +
|
|
", ccache per arch: " + args.ccache_size)
|
|
|
|
if not pmb.helpers.cli.confirm(args, "Change them?",
|
|
default=False):
|
|
return
|
|
|
|
# Parallel job count
|
|
logging.info("How many jobs should run parallel on this machine, when"
|
|
" compiling?")
|
|
answer = pmb.helpers.cli.ask(args, "Jobs", None, args.jobs,
|
|
validation_regex="[1-9][0-9]*")
|
|
cfg["pmbootstrap"]["jobs"] = 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.")
|
|
regex = "0|[0-9]+(k|M|G|T|Ki|Mi|Gi|Ti)"
|
|
answer = pmb.helpers.cli.ask(args, "Ccache size", None, args.ccache_size,
|
|
lowercase_answer=False, validation_regex=regex)
|
|
cfg["pmbootstrap"]["ccache_size"] = answer
|
|
|
|
|
|
def frontend(args):
|
|
cfg = pmb.config.load(args)
|
|
|
|
# Work folder (needs to be first, so boot.img analyze works: #1066)
|
|
cfg["pmbootstrap"]["work"] = args.work = ask_for_work_path(args)
|
|
|
|
# Device
|
|
device, device_exists, nonfree = ask_for_device(args)
|
|
cfg["pmbootstrap"]["device"] = device
|
|
cfg["pmbootstrap"]["nonfree_firmware"] = str(nonfree["firmware"])
|
|
cfg["pmbootstrap"]["nonfree_userland"] = str(nonfree["userland"])
|
|
|
|
# Qemu mesa driver
|
|
if cfg["pmbootstrap"]["device"].startswith("qemu-"):
|
|
driver = ask_for_qemu_native_mesa_driver(args, device, args.arch_native)
|
|
if driver:
|
|
cfg["pmbootstrap"]["qemu_native_mesa_driver"] = driver
|
|
|
|
# Device keymap
|
|
if device_exists:
|
|
cfg["pmbootstrap"]["keymap"] = ask_for_keymaps(args, device)
|
|
|
|
# Username
|
|
cfg["pmbootstrap"]["user"] = pmb.helpers.cli.ask(args, "Username", None,
|
|
args.user, False,
|
|
"[a-z_][a-z0-9_-]*")
|
|
# UI and various build options
|
|
cfg["pmbootstrap"]["ui"] = ask_for_ui(args)
|
|
ask_for_build_options(args, cfg)
|
|
|
|
# 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\"")
|
|
cfg["pmbootstrap"]["extra_packages"] = pmb.helpers.cli.ask(args, "Extra packages",
|
|
None, args.extra_packages,
|
|
validation_regex="^(|[-.+\w\s]+(?:,[-.+\w\s]*)*)$")
|
|
|
|
# Configure timezone info
|
|
cfg["pmbootstrap"]["timezone"] = ask_for_timezone(args)
|
|
|
|
# Save config
|
|
pmb.config.save(args, cfg)
|
|
|
|
# Zap existing chroots
|
|
setattr(args, "work", cfg["pmbootstrap"]["work"])
|
|
if (device_exists and
|
|
len(glob.glob(args.work + "/chroot_*")) and
|
|
pmb.helpers.cli.confirm(args, "Zap existing chroots to apply configuration?", default=True)):
|
|
setattr(args, "deviceinfo", pmb.parse.deviceinfo(args, device=device))
|
|
|
|
# Do not zap any existing packages or cache_http directories
|
|
pmb.chroot.zap(args, confirm=False)
|
|
|
|
logging.info(
|
|
"WARNING: The applications in the chroots do not get updated automatically.")
|
|
logging.info("Run 'pmbootstrap zap' to delete all chroots once a day before"
|
|
" working with pmbootstrap!")
|
|
logging.info("It only takes a few seconds, and all packages are cached.")
|
|
|
|
logging.info("Done!")
|