updated output
This commit is contained in:
parent
2d75643625
commit
b703ed2605
121
ekernel.py
121
ekernel.py
@ -21,7 +21,9 @@ jobs = "4"
|
||||
# gentoo's fancy terminal output functions
|
||||
out = output.EOutput()
|
||||
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
|
||||
if not sys.stdout.isatty():
|
||||
@ -49,11 +51,11 @@ class Kernel:
|
||||
"""Construct a Kernel based on a given source path."""
|
||||
self.src = pathlib.Path(src)
|
||||
if not self.src.exists():
|
||||
raise ValueError(f"missing source: {src}")
|
||||
raise ValueError(f"error: missing source {src}")
|
||||
try:
|
||||
self.version = version(self.src.name)
|
||||
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.bzImage = self.src / "arch/x86_64/boot/bzImage"
|
||||
self.bkp = efi.img.parent / f"gentoo-{self.version.base_version}.efi"
|
||||
@ -150,7 +152,7 @@ def efi (f):
|
||||
# find currently running entry/image
|
||||
def loader (line, start=9):
|
||||
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
|
||||
return pathlib.Path(l[i:line.find(")", i)].replace("\\", "/"))
|
||||
for l in lines:
|
||||
@ -183,8 +185,7 @@ def efi (f):
|
||||
break
|
||||
else: continue
|
||||
break
|
||||
else:
|
||||
raise RuntimeError("error finding ESP mountpoint")
|
||||
else: raise RuntimeError("error: missing mountpoint of ESP")
|
||||
try:
|
||||
subprocess.run(
|
||||
["mount", str(efi.esp)],
|
||||
@ -305,25 +306,25 @@ def configure (argv):
|
||||
# make oldconfig
|
||||
if not kernel.config.exists() and oldconfig.exists():
|
||||
# copy oldconfig
|
||||
out.einfo(f"copying {out.emph(oldconfig)}")
|
||||
out.einfo(f"copying {out.teal(oldconfig)}")
|
||||
shutil.copy(oldconfig, kernel.config)
|
||||
# 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)
|
||||
newoptions.write_text(make.stdout.decode())
|
||||
# configure
|
||||
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)
|
||||
# make menuconfig
|
||||
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)
|
||||
|
||||
# check if we should print new options
|
||||
if args.list:
|
||||
if not newoptions.exists():
|
||||
raise FileNotFoundError(f"missing {newoptions}")
|
||||
raise FileNotFoundError(f"error: missing {newoptions}")
|
||||
for l in newoptions.read_text().splitlines():
|
||||
opt, val = l.split("=", maxsplit=1)
|
||||
out.print(f" {opt} = {val}")
|
||||
@ -392,15 +393,15 @@ def build (argv):
|
||||
|
||||
# check if config exists
|
||||
if not kernel.config.exists():
|
||||
raise FileNotFoundError(f"missing config: {kernel.config}")
|
||||
raise FileNotFoundError(f"error: missing config {kernel.config}")
|
||||
|
||||
# change directory
|
||||
os.chdir(kernel.src)
|
||||
|
||||
# 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)
|
||||
out.einfo("installing modules")
|
||||
out.einfo(f"installing modules {out.teal(kernel.modules)}")
|
||||
subprocess.run(["make", "modules_install"], check=True)
|
||||
|
||||
@cli
|
||||
@ -469,16 +470,44 @@ def install (argv):
|
||||
kernel = Kernel(args.src)
|
||||
out.quiet = args.quiet
|
||||
|
||||
# store running image for latter comparison
|
||||
if args.bkp:
|
||||
boot_bytes = efi.img.read_bytes()
|
||||
|
||||
# check if 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
|
||||
if args.bkp:
|
||||
# path to backup image
|
||||
bkp = None
|
||||
# find the currently running kernel's backup image
|
||||
boot_bytes = efi.img.read_bytes()
|
||||
for f in efi.img.parent.glob("gentoo*.efi"):
|
||||
if f.read_bytes() == boot_bytes:
|
||||
bkp = f
|
||||
@ -494,12 +523,10 @@ def install (argv):
|
||||
capture_output=True,
|
||||
check=True
|
||||
)
|
||||
disk, part = filter(
|
||||
None,
|
||||
re.search(r"([/a-z]+)(\d+)", dev.stdout.decode()).groups()
|
||||
)
|
||||
disk, part = re.search(r"([/a-z]+)(\d+)", dev.stdout.decode()).groups()
|
||||
# remove previous entry
|
||||
if "num" in efi.bkp:
|
||||
out.einfo(f"deleting boot entry {out.teal(efi.bkp['label'])}")
|
||||
subprocess.run([
|
||||
"efibootmgr",
|
||||
"-q",
|
||||
@ -507,6 +534,7 @@ def install (argv):
|
||||
"-B"
|
||||
], check=True)
|
||||
# create entry
|
||||
out.einfo(f"creating boot entry {out.teal(efi.bkp['label'])}")
|
||||
subprocess.run([
|
||||
"efibootmgr",
|
||||
"-q",
|
||||
@ -518,28 +546,6 @@ def install (argv):
|
||||
], check=True)
|
||||
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
|
||||
@efi
|
||||
def clean (argv):
|
||||
@ -593,7 +599,7 @@ def clean (argv):
|
||||
args = parser.parse_args(argv)
|
||||
out.quiet = args.quiet
|
||||
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
|
||||
keep = {"kernels": []}
|
||||
@ -627,15 +633,18 @@ def clean (argv):
|
||||
]
|
||||
|
||||
# run depclean
|
||||
out.einfo(f"running {out.emph('emerge -cq gentoo-sources')}")
|
||||
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
|
||||
for k, v in rm.items():
|
||||
out.einfo(f"deleting {k}:")
|
||||
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 p.is_dir():
|
||||
shutil.rmtree(p)
|
||||
@ -646,7 +655,7 @@ def clean (argv):
|
||||
if efi.bkp and "img" in efi.bkp:
|
||||
bkp = efi.bkp["img"]
|
||||
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:
|
||||
subprocess.run([
|
||||
"efibootmgr",
|
||||
@ -689,14 +698,14 @@ def commit (argv):
|
||||
|
||||
def summarize (diff: list[str]):
|
||||
"""Generate the summary of changed options."""
|
||||
def changes (s):
|
||||
def startswith (ch):
|
||||
return dict([
|
||||
x[1:].split("=", maxsplit=1)
|
||||
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("+")
|
||||
deletions = changes("-")
|
||||
additions = startswith("+")
|
||||
deletions = startswith("-")
|
||||
changes = {
|
||||
k: (deletions[k], additions[k])
|
||||
for k in additions.keys() & deletions.keys()
|
||||
@ -749,7 +758,7 @@ def commit (argv):
|
||||
|
||||
# ensure that a 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
|
||||
os.chdir(kernel.src)
|
||||
@ -856,15 +865,15 @@ def commit (argv):
|
||||
if removals or config_changed:
|
||||
out.einfo("changes to be committed:")
|
||||
for l in removals:
|
||||
out.print(f" {colorize('QAWARN', '-')} {out.emph(l)}")
|
||||
out.print(f" {out.red('✗')} {out.teal(l)}")
|
||||
if config_changed:
|
||||
out.print(f" {colorize('INFO', '+')} {out.emph(kernel.config)}")
|
||||
out.print(f" {out.green('✓')} {out.teal(kernel.config)}")
|
||||
|
||||
# print message
|
||||
if msg:
|
||||
out.einfo("commit message:")
|
||||
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
|
||||
if args.dry:
|
||||
@ -874,7 +883,7 @@ def commit (argv):
|
||||
# commit
|
||||
try:
|
||||
out.ebegin("committing")
|
||||
git(["commit", "-m", msg.getvalue()])
|
||||
ret = git(["commit", "-m", msg.getvalue()])
|
||||
out.eend(0)
|
||||
except subprocess.CalledProcessError as e:
|
||||
out.eend(1)
|
||||
|
@ -57,8 +57,8 @@ class Tests (unittest.TestCase):
|
||||
# emerge -cq gentoo-sources
|
||||
tracer, (args, kwargs) = next(trace_it)
|
||||
self.assertEqual(tracer.name, "subprocess.run")
|
||||
self.assertEqual(args, (["emerge", "-cq", "gentoo-sources"],))
|
||||
self.assertEqual(kwargs, {})
|
||||
self.assertEqual(args, (["emerge", "-q", "-c", "gentoo-sources"],))
|
||||
self.assertEqual(kwargs, {"check": True})
|
||||
if keep < 3:
|
||||
# efibootmgr -b 0003 -B
|
||||
tracer, (args, kwargs) = next(trace_it)
|
||||
@ -128,7 +128,6 @@ class Tests (unittest.TestCase):
|
||||
"images": [k.bkp for k in kernels]
|
||||
}
|
||||
expected = io.StringIO()
|
||||
expected.write(" * running emerge -cq gentoo-sources\n")
|
||||
for k, v in rm.items():
|
||||
expected.write(f" * deleting {k}:\n")
|
||||
for p in v:
|
||||
|
@ -154,7 +154,7 @@ class Tests (unittest.TestCase):
|
||||
run()
|
||||
self.assertEqual(
|
||||
sys.stderr.getvalue(),
|
||||
f" * missing config: {self.latest.config}\n"
|
||||
f" * error: missing config {self.latest.config}\n"
|
||||
)
|
||||
|
||||
@colorless
|
||||
@ -177,8 +177,8 @@ class Tests (unittest.TestCase):
|
||||
self.assertEqual(run("-n"), 0)
|
||||
self.assertEqual(sys.stdout.getvalue(),
|
||||
" * changes to be committed:\n"
|
||||
f" - {self.current.config}\n"
|
||||
f" + {self.latest.config}\n"
|
||||
f" ✗ {self.current.config}\n"
|
||||
f" ✓ {self.latest.config}\n"
|
||||
" * commit message:\n"
|
||||
" kernel update: "
|
||||
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(sys.stdout.getvalue(),
|
||||
" * changes to be committed:\n"
|
||||
f" - {self.current.config}\n"
|
||||
f" ✗ {self.current.config}\n"
|
||||
" * commit message:\n"
|
||||
" removed old kernel leftovers\n\n"
|
||||
" details\n"
|
||||
|
@ -112,7 +112,7 @@ class Tests (unittest.TestCase):
|
||||
with self.assertRaises(SystemExit):
|
||||
run("-l")
|
||||
self.assertEqual(sys.stderr.getvalue(),
|
||||
f" * missing {self.kernel.newoptions}\n"
|
||||
f" * error: missing {self.kernel.newoptions}\n"
|
||||
)
|
||||
|
||||
@colorless
|
||||
@ -122,7 +122,7 @@ class Tests (unittest.TestCase):
|
||||
with self.assertRaises(SystemExit):
|
||||
run("-l")
|
||||
self.assertEqual(sys.stderr.getvalue(),
|
||||
f" * missing {self.kernel.newoptions}\n"
|
||||
f" * error: missing {self.kernel.newoptions}\n"
|
||||
)
|
||||
|
||||
@colorless
|
||||
|
@ -51,6 +51,20 @@ class Tests (unittest.TestCase):
|
||||
self.assertEqual(tracer.name, "subprocess.run")
|
||||
self.assertEqual(args, (["mount", "/boot"],))
|
||||
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 data.boot.read_bytes() == b"missing image":
|
||||
# platform.release
|
||||
@ -79,20 +93,6 @@ class Tests (unittest.TestCase):
|
||||
"-l", str(self.current.bkp)
|
||||
],))
|
||||
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>
|
||||
tracer, (args, kwargs) = next(trace_it)
|
||||
self.assertEqual(tracer.name, "subprocess.run")
|
||||
|
@ -72,4 +72,4 @@ class Tests (unittest.TestCase):
|
||||
shutil.rmtree(kernel.src)
|
||||
with self.assertRaises(ValueError) as e:
|
||||
ekernel.Kernel.current()
|
||||
self.assertEqual(str(e.exception), f"missing source: {kernel.src}")
|
||||
self.assertEqual(str(e.exception), f"error: missing source {kernel.src}")
|
||||
|
@ -29,7 +29,9 @@ class Tests (unittest.TestCase):
|
||||
# start interceptor
|
||||
@data.efi
|
||||
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":
|
||||
make = subprocess.CompletedProcess("", 0)
|
||||
make.stdout = data.newoptions.encode()
|
||||
@ -95,7 +97,12 @@ class Tests (unittest.TestCase):
|
||||
self.assertEqual(run("-q", "-s", str(data.latest)), 0)
|
||||
self.check_update()
|
||||
|
||||
def test_update_backup (self):
|
||||
self.assertEqual(run("-q", "-b"), 0)
|
||||
self.check_update()
|
||||
|
||||
def test_update_keep (self):
|
||||
print()
|
||||
current = Kernel.current()
|
||||
self.assertEqual(run("-q", "-k", "1"), 0)
|
||||
self.check_update()
|
||||
|
Loading…
Reference in New Issue
Block a user