updated output

This commit is contained in:
Florian Schroegendorfer 2024-06-12 01:45:25 +02:00
parent 2d75643625
commit b703ed2605
Signed by: root
GPG Key ID: 17625E28D4D6E339
7 changed files with 97 additions and 82 deletions

View File

@ -21,7 +21,9 @@ jobs = "4"
# gentoo's fancy terminal output functions # gentoo's fancy terminal output functions
out = output.EOutput() out = output.EOutput()
out.print = lambda s: print(s) if not out.quiet else None out.print = lambda s: print(s) if not out.quiet else None
out.emph = lambda s: colorize("HILITE", str(s)) out.green = lambda s: colorize("green", s if isinstance(s, str) else str(s))
out.red = lambda s: colorize("red", s if isinstance(s, str) else str(s))
out.teal = lambda s: colorize("teal", s if isinstance(s, str) else str(s))
# disable colorization for pipes and redirects # disable colorization for pipes and redirects
if not sys.stdout.isatty(): if not sys.stdout.isatty():
@ -49,11 +51,11 @@ class Kernel:
"""Construct a Kernel based on a given source path.""" """Construct a Kernel based on a given source path."""
self.src = pathlib.Path(src) self.src = pathlib.Path(src)
if not self.src.exists(): if not self.src.exists():
raise ValueError(f"missing source: {src}") raise ValueError(f"error: missing source {src}")
try: try:
self.version = version(self.src.name) self.version = version(self.src.name)
except Exception as e: except Exception as e:
raise ValueError(f"illegal source: {src}") from e raise ValueError(f"error: illegal source {src}") from e
self.config = self.src / ".config" self.config = self.src / ".config"
self.bzImage = self.src / "arch/x86_64/boot/bzImage" self.bzImage = self.src / "arch/x86_64/boot/bzImage"
self.bkp = efi.img.parent / f"gentoo-{self.version.base_version}.efi" self.bkp = efi.img.parent / f"gentoo-{self.version.base_version}.efi"
@ -150,7 +152,7 @@ def efi (f):
# find currently running entry/image # find currently running entry/image
def loader (line, start=9): def loader (line, start=9):
i = line.find("File", start) i = line.find("File", start)
if i < 0: raise RuntimeError(f"error locating boot image:\n{line}") if i < 0: raise RuntimeError(f"error: missing boot image:\n{line}")
i += 6 i += 6
return pathlib.Path(l[i:line.find(")", i)].replace("\\", "/")) return pathlib.Path(l[i:line.find(")", i)].replace("\\", "/"))
for l in lines: for l in lines:
@ -183,8 +185,7 @@ def efi (f):
break break
else: continue else: continue
break break
else: else: raise RuntimeError("error: missing mountpoint of ESP")
raise RuntimeError("error finding ESP mountpoint")
try: try:
subprocess.run( subprocess.run(
["mount", str(efi.esp)], ["mount", str(efi.esp)],
@ -305,25 +306,25 @@ def configure (argv):
# make oldconfig # make oldconfig
if not kernel.config.exists() and oldconfig.exists(): if not kernel.config.exists() and oldconfig.exists():
# copy oldconfig # copy oldconfig
out.einfo(f"copying {out.emph(oldconfig)}") out.einfo(f"copying {out.teal(oldconfig)}")
shutil.copy(oldconfig, kernel.config) shutil.copy(oldconfig, kernel.config)
# store newly added options # store newly added options
out.einfo(f"running {out.emph('make listnewconfig')}") out.einfo(f"running {out.teal('make listnewconfig')}")
make = subprocess.run(["make", "listnewconfig"], capture_output=True) make = subprocess.run(["make", "listnewconfig"], capture_output=True)
newoptions.write_text(make.stdout.decode()) newoptions.write_text(make.stdout.decode())
# configure # configure
if not args.list: if not args.list:
out.einfo(f"running {out.emph('make oldconfig')}") out.einfo(f"running {out.teal('make oldconfig')}")
subprocess.run(["make", "oldconfig"], check=True) subprocess.run(["make", "oldconfig"], check=True)
# make menuconfig # make menuconfig
elif not args.list: elif not args.list:
out.einfo(f"running {out.emph('make menuconfig')}") out.einfo(f"running {out.teal('make menuconfig')}")
subprocess.run(["make", "menuconfig"], check=True) subprocess.run(["make", "menuconfig"], check=True)
# check if we should print new options # check if we should print new options
if args.list: if args.list:
if not newoptions.exists(): if not newoptions.exists():
raise FileNotFoundError(f"missing {newoptions}") raise FileNotFoundError(f"error: missing {newoptions}")
for l in newoptions.read_text().splitlines(): for l in newoptions.read_text().splitlines():
opt, val = l.split("=", maxsplit=1) opt, val = l.split("=", maxsplit=1)
out.print(f" {opt} = {val}") out.print(f" {opt} = {val}")
@ -392,15 +393,15 @@ def build (argv):
# check if config exists # check if config exists
if not kernel.config.exists(): if not kernel.config.exists():
raise FileNotFoundError(f"missing config: {kernel.config}") raise FileNotFoundError(f"error: missing config {kernel.config}")
# change directory # change directory
os.chdir(kernel.src) os.chdir(kernel.src)
# build and install modules # build and install modules
out.einfo(f"building {out.emph(kernel.src.name)}") out.einfo(f"building {out.teal(kernel.src)}")
subprocess.run(["make", "-j", str(args.jobs)], check=True) subprocess.run(["make", "-j", str(args.jobs)], check=True)
out.einfo("installing modules") out.einfo(f"installing modules {out.teal(kernel.modules)}")
subprocess.run(["make", "modules_install"], check=True) subprocess.run(["make", "modules_install"], check=True)
@cli @cli
@ -469,16 +470,44 @@ def install (argv):
kernel = Kernel(args.src) kernel = Kernel(args.src)
out.quiet = args.quiet out.quiet = args.quiet
# store running image for latter comparison
if args.bkp:
boot_bytes = efi.img.read_bytes()
# check if bzImage exists # check if bzImage exists
if not kernel.bzImage.exists(): if not kernel.bzImage.exists():
raise FileNotFoundError(f"missing bzImage {kernel.bzImage}") raise FileNotFoundError(f"error: missing bzImage {kernel.bzImage}")
# update symlink to the new source directory
out.einfo(
"updating symlink "
f"{out.teal(kernel.linux)}{out.teal(kernel.src)}"
)
subprocess.run(
["eselect", "kernel", "set", kernel.src.name],
check=True
)
# copy boot image
out.einfo(f"creating boot image {out.teal(efi.img)}")
shutil.copy(kernel.bzImage, efi.img)
# create backup
out.einfo(f"creating backup image {out.teal(kernel.bkp)}")
shutil.copy(kernel.bzImage, kernel.bkp)
# rebuild external modules
eargs = ["emerge", "@module-rebuild"]
if args.quiet:
eargs.insert(1, "-q")
out.einfo(f"running {out.teal(' '.join(eargs))}")
subprocess.run(eargs, check=True)
# create fallback boot entry # create fallback boot entry
if args.bkp: if args.bkp:
# path to backup image # path to backup image
bkp = None bkp = None
# find the currently running kernel's backup image # find the currently running kernel's backup image
boot_bytes = efi.img.read_bytes()
for f in efi.img.parent.glob("gentoo*.efi"): for f in efi.img.parent.glob("gentoo*.efi"):
if f.read_bytes() == boot_bytes: if f.read_bytes() == boot_bytes:
bkp = f bkp = f
@ -494,12 +523,10 @@ def install (argv):
capture_output=True, capture_output=True,
check=True check=True
) )
disk, part = filter( disk, part = re.search(r"([/a-z]+)(\d+)", dev.stdout.decode()).groups()
None,
re.search(r"([/a-z]+)(\d+)", dev.stdout.decode()).groups()
)
# remove previous entry # remove previous entry
if "num" in efi.bkp: if "num" in efi.bkp:
out.einfo(f"deleting boot entry {out.teal(efi.bkp['label'])}")
subprocess.run([ subprocess.run([
"efibootmgr", "efibootmgr",
"-q", "-q",
@ -507,6 +534,7 @@ def install (argv):
"-B" "-B"
], check=True) ], check=True)
# create entry # create entry
out.einfo(f"creating boot entry {out.teal(efi.bkp['label'])}")
subprocess.run([ subprocess.run([
"efibootmgr", "efibootmgr",
"-q", "-q",
@ -518,28 +546,6 @@ def install (argv):
], check=True) ], check=True)
efi.bkp["img"] = bkp efi.bkp["img"] = bkp
# update symlink to the new source directory
out.einfo(
"updating symlink "
f"{out.emph(kernel.linux)}{out.emph(kernel.src)}"
)
subprocess.run(
["eselect", "kernel", "set", kernel.src.name],
check=True
)
# copy boot image
out.einfo(f"creating boot image {out.emph(efi.img)}")
shutil.copy(kernel.bzImage, efi.img)
# create backup
out.einfo(f"creating backup image {out.emph(kernel.bkp)}")
shutil.copy(kernel.bzImage, kernel.bkp)
# rebuild external modules
out.einfo(f"rebuilding external kernel modules")
subprocess.run(["emerge", "@module-rebuild"], check=True)
@cli @cli
@efi @efi
def clean (argv): def clean (argv):
@ -593,7 +599,7 @@ def clean (argv):
args = parser.parse_args(argv) args = parser.parse_args(argv)
out.quiet = args.quiet out.quiet = args.quiet
if args.keep < 1: if args.keep < 1:
raise ValueError("at least one bootable kernel must be kept") raise ValueError("error: at least one bootable kernel must be kept")
# retained kernels # retained kernels
keep = {"kernels": []} keep = {"kernels": []}
@ -627,15 +633,18 @@ def clean (argv):
] ]
# run depclean # run depclean
out.einfo(f"running {out.emph('emerge -cq gentoo-sources')}")
if not args.dry: if not args.dry:
subprocess.run(["emerge", "-cq", "gentoo-sources"]) eargs = ["emerge", "-c", "gentoo-sources"]
if args.quiet:
eargs.insert(1, "-q")
out.einfo(f"running {out.teal(' '.join(eargs))}")
subprocess.run(eargs, check=True)
# remove files # remove files
for k, v in rm.items(): for k, v in rm.items():
out.einfo(f"deleting {k}:") out.einfo(f"deleting {k}:")
for p in v: for p in v:
out.print(f" {colorize('BAD', '')} {out.emph(p)}") out.print(f" {out.red('')} {out.teal(p)}")
if args.dry: continue if args.dry: continue
if p.is_dir(): if p.is_dir():
shutil.rmtree(p) shutil.rmtree(p)
@ -646,7 +655,7 @@ def clean (argv):
if efi.bkp and "img" in efi.bkp: if efi.bkp and "img" in efi.bkp:
bkp = efi.bkp["img"] bkp = efi.bkp["img"]
if not bkp.exists() or bkp in rm["images"]: if not bkp.exists() or bkp in rm["images"]:
out.einfo(f"deleting boot entry {out.emph(efi.bkp['label'])}") out.einfo(f"deleting boot entry {out.teal(efi.bkp['label'])}")
if not args.dry: if not args.dry:
subprocess.run([ subprocess.run([
"efibootmgr", "efibootmgr",
@ -689,14 +698,14 @@ def commit (argv):
def summarize (diff: list[str]): def summarize (diff: list[str]):
"""Generate the summary of changed options.""" """Generate the summary of changed options."""
def changes (s): def startswith (ch):
return dict([ return dict([
x[1:].split("=", maxsplit=1) x[1:].split("=", maxsplit=1)
for x in diff for x in diff
if x.startswith(s + "CONFIG") and "CC_VERSION" not in x if x.startswith(ch + "CONFIG") and "CC_VERSION" not in x
]) ])
additions = changes("+") additions = startswith("+")
deletions = changes("-") deletions = startswith("-")
changes = { changes = {
k: (deletions[k], additions[k]) k: (deletions[k], additions[k])
for k in additions.keys() & deletions.keys() for k in additions.keys() & deletions.keys()
@ -749,7 +758,7 @@ def commit (argv):
# ensure that a config exists # ensure that a config exists
if not kernel.config.exists(): if not kernel.config.exists():
raise FileNotFoundError(f"missing config: {kernel.config}") raise FileNotFoundError(f"error: missing config {kernel.config}")
# change to source directory # change to source directory
os.chdir(kernel.src) os.chdir(kernel.src)
@ -856,15 +865,15 @@ def commit (argv):
if removals or config_changed: if removals or config_changed:
out.einfo("changes to be committed:") out.einfo("changes to be committed:")
for l in removals: for l in removals:
out.print(f" {colorize('QAWARN', '-')} {out.emph(l)}") out.print(f" {out.red('')} {out.teal(l)}")
if config_changed: if config_changed:
out.print(f" {colorize('INFO', '+')} {out.emph(kernel.config)}") out.print(f" {out.green('')} {out.teal(kernel.config)}")
# print message # print message
if msg: if msg:
out.einfo("commit message:") out.einfo("commit message:")
for l in msg.getvalue().splitlines(): for l in msg.getvalue().splitlines():
out.print(f" {l}" if l else "") out.print(f" {out.teal(l)}" if l else "")
# dry run: revert staged changes # dry run: revert staged changes
if args.dry: if args.dry:
@ -874,7 +883,7 @@ def commit (argv):
# commit # commit
try: try:
out.ebegin("committing") out.ebegin("committing")
git(["commit", "-m", msg.getvalue()]) ret = git(["commit", "-m", msg.getvalue()])
out.eend(0) out.eend(0)
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
out.eend(1) out.eend(1)

View File

@ -57,8 +57,8 @@ class Tests (unittest.TestCase):
# emerge -cq gentoo-sources # emerge -cq gentoo-sources
tracer, (args, kwargs) = next(trace_it) tracer, (args, kwargs) = next(trace_it)
self.assertEqual(tracer.name, "subprocess.run") self.assertEqual(tracer.name, "subprocess.run")
self.assertEqual(args, (["emerge", "-cq", "gentoo-sources"],)) self.assertEqual(args, (["emerge", "-q", "-c", "gentoo-sources"],))
self.assertEqual(kwargs, {}) self.assertEqual(kwargs, {"check": True})
if keep < 3: if keep < 3:
# efibootmgr -b 0003 -B # efibootmgr -b 0003 -B
tracer, (args, kwargs) = next(trace_it) tracer, (args, kwargs) = next(trace_it)
@ -128,7 +128,6 @@ class Tests (unittest.TestCase):
"images": [k.bkp for k in kernels] "images": [k.bkp for k in kernels]
} }
expected = io.StringIO() expected = io.StringIO()
expected.write(" * running emerge -cq gentoo-sources\n")
for k, v in rm.items(): for k, v in rm.items():
expected.write(f" * deleting {k}:\n") expected.write(f" * deleting {k}:\n")
for p in v: for p in v:

View File

@ -154,7 +154,7 @@ class Tests (unittest.TestCase):
run() run()
self.assertEqual( self.assertEqual(
sys.stderr.getvalue(), sys.stderr.getvalue(),
f" * missing config: {self.latest.config}\n" f" * error: missing config {self.latest.config}\n"
) )
@colorless @colorless
@ -177,8 +177,8 @@ class Tests (unittest.TestCase):
self.assertEqual(run("-n"), 0) self.assertEqual(run("-n"), 0)
self.assertEqual(sys.stdout.getvalue(), self.assertEqual(sys.stdout.getvalue(),
" * changes to be committed:\n" " * changes to be committed:\n"
f" - {self.current.config}\n" f" {self.current.config}\n"
f" + {self.latest.config}\n" f" {self.latest.config}\n"
" * commit message:\n" " * commit message:\n"
" kernel update: " " kernel update: "
f"{self.current.version}{self.latest.version}\n" f"{self.current.version}{self.latest.version}\n"
@ -214,7 +214,7 @@ class Tests (unittest.TestCase):
self.assertEqual(run("-n", "-m", "details"), 0) self.assertEqual(run("-n", "-m", "details"), 0)
self.assertEqual(sys.stdout.getvalue(), self.assertEqual(sys.stdout.getvalue(),
" * changes to be committed:\n" " * changes to be committed:\n"
f" - {self.current.config}\n" f" {self.current.config}\n"
" * commit message:\n" " * commit message:\n"
" removed old kernel leftovers\n\n" " removed old kernel leftovers\n\n"
" details\n" " details\n"

View File

@ -112,7 +112,7 @@ class Tests (unittest.TestCase):
with self.assertRaises(SystemExit): with self.assertRaises(SystemExit):
run("-l") run("-l")
self.assertEqual(sys.stderr.getvalue(), self.assertEqual(sys.stderr.getvalue(),
f" * missing {self.kernel.newoptions}\n" f" * error: missing {self.kernel.newoptions}\n"
) )
@colorless @colorless
@ -122,7 +122,7 @@ class Tests (unittest.TestCase):
with self.assertRaises(SystemExit): with self.assertRaises(SystemExit):
run("-l") run("-l")
self.assertEqual(sys.stderr.getvalue(), self.assertEqual(sys.stderr.getvalue(),
f" * missing {self.kernel.newoptions}\n" f" * error: missing {self.kernel.newoptions}\n"
) )
@colorless @colorless

View File

@ -51,6 +51,20 @@ class Tests (unittest.TestCase):
self.assertEqual(tracer.name, "subprocess.run") self.assertEqual(tracer.name, "subprocess.run")
self.assertEqual(args, (["mount", "/boot"],)) self.assertEqual(args, (["mount", "/boot"],))
self.assertEqual(kwargs, {"capture_output": True, "check": True}) self.assertEqual(kwargs, {"capture_output": True, "check": True})
# eselect kernel set <name>
tracer, (args, kwargs) = next(trace_it)
self.assertEqual(tracer.name, "subprocess.run")
self.assertEqual(
args,
(["eselect", "kernel", "set", self.kernel.src.name],)
)
self.assertEqual(kwargs, {"check": True})
self.assertEqual(str(data.linux.readlink()), self.kernel.src.name)
# emerge @module-rebuild
tracer, (args, kwargs) = next(trace_it)
self.assertEqual(tracer.name, "subprocess.run")
self.assertEqual(args, (["emerge", "-q", "@module-rebuild"],))
self.assertEqual(kwargs, {"check": True})
if backup: if backup:
if data.boot.read_bytes() == b"missing image": if data.boot.read_bytes() == b"missing image":
# platform.release # platform.release
@ -79,20 +93,6 @@ class Tests (unittest.TestCase):
"-l", str(self.current.bkp) "-l", str(self.current.bkp)
],)) ],))
self.assertEqual(kwargs, {"check": True}) self.assertEqual(kwargs, {"check": True})
# eselect kernel set <name>
tracer, (args, kwargs) = next(trace_it)
self.assertEqual(tracer.name, "subprocess.run")
self.assertEqual(
args,
(["eselect", "kernel", "set", self.kernel.src.name],)
)
self.assertEqual(kwargs, {"check": True})
self.assertEqual(str(data.linux.readlink()), self.kernel.src.name)
# emerge @module-rebuild
tracer, (args, kwargs) = next(trace_it)
self.assertEqual(tracer.name, "subprocess.run")
self.assertEqual(args, (["emerge", "@module-rebuild"],))
self.assertEqual(kwargs, {"check": True})
# umount <boot> # umount <boot>
tracer, (args, kwargs) = next(trace_it) tracer, (args, kwargs) = next(trace_it)
self.assertEqual(tracer.name, "subprocess.run") self.assertEqual(tracer.name, "subprocess.run")

View File

@ -72,4 +72,4 @@ class Tests (unittest.TestCase):
shutil.rmtree(kernel.src) shutil.rmtree(kernel.src)
with self.assertRaises(ValueError) as e: with self.assertRaises(ValueError) as e:
ekernel.Kernel.current() ekernel.Kernel.current()
self.assertEqual(str(e.exception), f"missing source: {kernel.src}") self.assertEqual(str(e.exception), f"error: missing source {kernel.src}")

View File

@ -29,7 +29,9 @@ class Tests (unittest.TestCase):
# start interceptor # start interceptor
@data.efi @data.efi
def run (tracer, *args, **kwargs): def run (tracer, *args, **kwargs):
if args[0][0] == "make": if args[0][0] == "findmnt":
return subprocess.CompletedProcess("", 0, b"/dev/sda1")
elif args[0][0] == "make":
if args[0][1] == "listnewconfig": if args[0][1] == "listnewconfig":
make = subprocess.CompletedProcess("", 0) make = subprocess.CompletedProcess("", 0)
make.stdout = data.newoptions.encode() make.stdout = data.newoptions.encode()
@ -95,7 +97,12 @@ class Tests (unittest.TestCase):
self.assertEqual(run("-q", "-s", str(data.latest)), 0) self.assertEqual(run("-q", "-s", str(data.latest)), 0)
self.check_update() self.check_update()
def test_update_backup (self):
self.assertEqual(run("-q", "-b"), 0)
self.check_update()
def test_update_keep (self): def test_update_keep (self):
print()
current = Kernel.current() current = Kernel.current()
self.assertEqual(run("-q", "-k", "1"), 0) self.assertEqual(run("-q", "-k", "1"), 0)
self.check_update() self.check_update()