summaryrefslogtreecommitdiffstats
path: root/scripts/runqemu
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/runqemu')
-rwxr-xr-xscripts/runqemu159
1 files changed, 97 insertions, 62 deletions
diff --git a/scripts/runqemu b/scripts/runqemu
index 69cd44864e..2be7a0f286 100755
--- a/scripts/runqemu
+++ b/scripts/runqemu
@@ -197,11 +197,13 @@ class BaseConfig(object):
197 self.portlocks = {} 197 self.portlocks = {}
198 self.bitbake_e = '' 198 self.bitbake_e = ''
199 self.snapshot = False 199 self.snapshot = False
200 self.wictypes = ('wic', 'wic.vmdk', 'wic.qcow2', 'wic.vdi', "wic.vhd", "wic.vhdx") 200 self.wictypes = ('wic.zst', 'wic', 'wic.vmdk', 'wic.qcow2', 'wic.vdi', "wic.vhd", "wic.vhdx")
201 self.fstypes = ('ext2', 'ext3', 'ext4', 'jffs2', 'nfs', 'btrfs', 201 self.fstypes = ('ext2', 'ext3', 'ext4', 'ext2.zst', 'ext3.zst', 'ext4.zst',
202 'cpio.gz', 'cpio', 'ramfs', 'tar.bz2', 'tar.gz', 202 'jffs2', 'nfs', 'btrfs', 'cpio.gz', 'cpio', 'ramfs',
203 'tar.bz2', 'tar.gz', 'tar.zst',
203 'squashfs', 'squashfs-xz', 'squashfs-lzo', 204 'squashfs', 'squashfs-xz', 'squashfs-lzo',
204 'squashfs-lz4', 'squashfs-zst') 205 'squashfs-lz4', 'squashfs-zst',
206 'erofs', 'erofs-lz4', 'erofs-lz4hc')
205 self.vmtypes = ('hddimg', 'iso') 207 self.vmtypes = ('hddimg', 'iso')
206 self.fsinfo = {} 208 self.fsinfo = {}
207 self.network_device = "-device e1000,netdev=net0,mac=@MAC@" 209 self.network_device = "-device e1000,netdev=net0,mac=@MAC@"
@@ -368,19 +370,20 @@ class BaseConfig(object):
368 - Check whether it is an NFS dir 370 - Check whether it is an NFS dir
369 - Check whether it is an OVMF flash file 371 - Check whether it is an OVMF flash file
370 """ 372 """
373 n = os.path.basename(p)
371 if p.endswith('.qemuboot.conf'): 374 if p.endswith('.qemuboot.conf'):
372 self.qemuboot = p 375 self.qemuboot = p
373 self.qbconfload = True 376 self.qbconfload = True
374 elif re.search('\\.bin$', p) or re.search('bzImage', p) or \ 377 elif re.search('\\.bin$', n) or re.search('bzImage', n) or \
375 re.search('zImage', p) or re.search('vmlinux', p) or \ 378 re.search('zImage', n) or re.search('vmlinux', n) or \
376 re.search('fitImage', p) or re.search('uImage', p): 379 re.search('fitImage', n) or re.search('uImage', n):
377 self.kernel = p 380 self.kernel = p
378 elif os.path.isfile(p) and ('-image-' in os.path.basename(p) or '.rootfs.' in os.path.basename(p)): 381 elif os.path.isfile(p) and ('-image-' in os.path.basename(p) or '.rootfs.' in os.path.basename(p)):
379 self.rootfs = p 382 self.rootfs = p
380 # Check filename against self.fstypes can handle <file>.cpio.gz, 383 # Check filename against self.fstypes can handle <file>.cpio.gz,
381 # otherwise, its type would be "gz", which is incorrect. 384 # otherwise, its type would be "gz", which is incorrect.
382 fst = "" 385 fst = ""
383 for t in self.fstypes: 386 for t in self.fstypes + self.vmtypes + self.wictypes:
384 if p.endswith(t): 387 if p.endswith(t):
385 fst = t 388 fst = t
386 break 389 break
@@ -418,6 +421,46 @@ class BaseConfig(object):
418 else: 421 else:
419 raise RunQemuError("Unknown path arg %s" % p) 422 raise RunQemuError("Unknown path arg %s" % p)
420 423
424 def uncompress_rootfs(self):
425 """Decompress ZST rootfs image if needed"""
426 if not self.rootfs or not self.fstype.endswith('.zst'):
427 return
428
429 # Ensure snapshot mode is active before allowing decompression.
430 if not self.snapshot:
431 raise RunQemuError(".zst images are only supported with snapshot mode. " \
432 "You can either use the \"snapshot\" option or use an uncompressed image.")
433
434 # Get the real path to the image to avoid issues when a symbolic link is passed.
435 # This ensures we always operate on the actual file.
436 image_path = os.path.realpath(self.rootfs)
437 # Extract target filename by removing .zst
438 image_dir = os.path.dirname(image_path)
439 uncompressed_name = os.path.basename(image_path).replace(".zst", "")
440 uncompressed_path = os.path.join(image_dir, uncompressed_name)
441
442 # If the decompressed image already exists (e.g., in the deploy directory),
443 # we use it directly to avoid overwriting artifacts generated by the build system.
444 # This prevents redundant decompression and preserves build outputs.
445 if os.path.exists(uncompressed_path):
446 logger.warning(f"Found existing decompressed image: {uncompressed_path}, Using it directly.")
447 else:
448 logger.info(f"Decompressing {self.rootfs} to {uncompressed_path}")
449 # Ensure the 'zstd' tool is installed before attempting to decompress.
450 if not shutil.which('zstd'):
451 raise RunQemuError(f"'zstd' is required to decompress {self.rootfs} but was not found in PATH")
452 try:
453 subprocess.check_call(['zstd', '-d', image_path, '-o', uncompressed_path])
454 except subprocess.CalledProcessError as e:
455 raise RunQemuError(f"Failed to decompress {self.rootfs}: {e}")
456 finally:
457 # Mark temporary file for deletion
458 self.cleanup_files.append(uncompressed_path)
459
460 # Use the decompressed image as the rootfs
461 self.rootfs = uncompressed_path
462 self.fstype = self.fstype.removesuffix(".zst")
463
421 def check_arg_machine(self, arg): 464 def check_arg_machine(self, arg):
422 """Check whether it is a machine""" 465 """Check whether it is a machine"""
423 if self.get('MACHINE') == arg: 466 if self.get('MACHINE') == arg:
@@ -468,9 +511,11 @@ class BaseConfig(object):
468 self.set("IMAGE_LINK_NAME", image_link_name) 511 self.set("IMAGE_LINK_NAME", image_link_name)
469 logger.debug('Using IMAGE_LINK_NAME = "%s"' % image_link_name) 512 logger.debug('Using IMAGE_LINK_NAME = "%s"' % image_link_name)
470 513
471 def set_dri_path(self): 514 def set_mesa_paths(self):
472 drivers_path = os.path.join(self.bindir_native, '../lib/dri') 515 drivers_path = os.path.join(self.bindir_native, '../lib/dri')
473 if not os.path.exists(drivers_path) or not os.listdir(drivers_path): 516 gbm_path = os.path.join(self.bindir_native, '../lib/gbm')
517 if not os.path.exists(drivers_path) or not os.listdir(drivers_path) \
518 or not os.path.exists(gbm_path) or not os.listdir(gbm_path):
474 raise RunQemuError(""" 519 raise RunQemuError("""
475qemu has been built without opengl support and accelerated graphics support is not available. 520qemu has been built without opengl support and accelerated graphics support is not available.
476To enable it, add: 521To enable it, add:
@@ -479,6 +524,7 @@ DISTRO_FEATURES_NATIVESDK:append = " opengl"
479to your build configuration. 524to your build configuration.
480""") 525""")
481 self.qemu_environ['LIBGL_DRIVERS_PATH'] = drivers_path 526 self.qemu_environ['LIBGL_DRIVERS_PATH'] = drivers_path
527 self.qemu_environ['GBM_BACKENDS_PATH'] = gbm_path
482 528
483 def check_args(self): 529 def check_args(self):
484 for debug in ("-d", "--debug"): 530 for debug in ("-d", "--debug"):
@@ -961,34 +1007,12 @@ to your build configuration.
961 if not self.bitbake_e: 1007 if not self.bitbake_e:
962 self.load_bitbake_env() 1008 self.load_bitbake_env()
963 1009
964 if self.bitbake_e: 1010 native_vars = ['STAGING_DIR_NATIVE']
965 native_vars = ['STAGING_DIR_NATIVE'] 1011 for nv in native_vars:
966 for nv in native_vars: 1012 s = re.search('^%s="(.*)"' % nv, self.bitbake_e, re.M)
967 s = re.search('^%s="(.*)"' % nv, self.bitbake_e, re.M) 1013 if s and s.group(1) != self.get(nv):
968 if s and s.group(1) != self.get(nv): 1014 logger.info('Overriding conf file setting of %s to %s from Bitbake environment' % (nv, s.group(1)))
969 logger.info('Overriding conf file setting of %s to %s from Bitbake environment' % (nv, s.group(1))) 1015 self.set(nv, s.group(1))
970 self.set(nv, s.group(1))
971 else:
972 # when we're invoked from a running bitbake instance we won't
973 # be able to call `bitbake -e`, then try:
974 # - get OE_TMPDIR from environment and guess paths based on it
975 # - get OECORE_NATIVE_SYSROOT from environment (for sdk)
976 tmpdir = self.get('OE_TMPDIR')
977 oecore_native_sysroot = self.get('OECORE_NATIVE_SYSROOT')
978 if tmpdir:
979 logger.info('Setting STAGING_DIR_NATIVE and STAGING_BINDIR_NATIVE relative to OE_TMPDIR (%s)' % tmpdir)
980 hostos, _, _, _, machine = os.uname()
981 buildsys = '%s-%s' % (machine, hostos.lower())
982 staging_dir_native = '%s/sysroots/%s' % (tmpdir, buildsys)
983 self.set('STAGING_DIR_NATIVE', staging_dir_native)
984 elif oecore_native_sysroot:
985 logger.info('Setting STAGING_DIR_NATIVE to OECORE_NATIVE_SYSROOT (%s)' % oecore_native_sysroot)
986 self.set('STAGING_DIR_NATIVE', oecore_native_sysroot)
987 if self.get('STAGING_DIR_NATIVE'):
988 # we have to assume that STAGING_BINDIR_NATIVE is at usr/bin
989 staging_bindir_native = '%s/usr/bin' % self.get('STAGING_DIR_NATIVE')
990 logger.info('Setting STAGING_BINDIR_NATIVE to %s' % staging_bindir_native)
991 self.set('STAGING_BINDIR_NATIVE', '%s/usr/bin' % self.get('STAGING_DIR_NATIVE'))
992 1016
993 def print_config(self): 1017 def print_config(self):
994 logoutput = ['Continuing with the following parameters:'] 1018 logoutput = ['Continuing with the following parameters:']
@@ -1008,6 +1032,9 @@ to your build configuration.
1008 logoutput.append('NFS_DIR: [%s]' % self.rootfs) 1032 logoutput.append('NFS_DIR: [%s]' % self.rootfs)
1009 else: 1033 else:
1010 logoutput.append('ROOTFS: [%s]' % self.rootfs) 1034 logoutput.append('ROOTFS: [%s]' % self.rootfs)
1035 logoutput.append('SNAPSHOT: [%s]' %
1036 "Enabled. Changes on rootfs won't be kept after QEMU shutdown." if self.snapshot
1037 else "Disabled. Changes on rootfs will be kept after QEMU shutdown.")
1011 if self.ovmf_bios: 1038 if self.ovmf_bios:
1012 logoutput.append('OVMF: %s' % self.ovmf_bios) 1039 logoutput.append('OVMF: %s' % self.ovmf_bios)
1013 if (self.ovmf_secboot_pkkek1): 1040 if (self.ovmf_secboot_pkkek1):
@@ -1192,19 +1219,22 @@ to your build configuration.
1192 raise RunQemuError("a new one with sudo.") 1219 raise RunQemuError("a new one with sudo.")
1193 1220
1194 gid = os.getgid() 1221 gid = os.getgid()
1195 uid = os.getuid()
1196 logger.info("Setting up tap interface under sudo") 1222 logger.info("Setting up tap interface under sudo")
1197 cmd = ('sudo', self.qemuifup, str(gid)) 1223 cmd = ('sudo', self.qemuifup, str(gid))
1198 try: 1224 for _ in range(5):
1199 tap = subprocess.check_output(cmd).decode('utf-8').strip() 1225 try:
1200 except subprocess.CalledProcessError as e: 1226 tap = subprocess.check_output(cmd).decode('utf-8').strip()
1201 logger.error('Setting up tap device failed:\n%s\nRun runqemu-gen-tapdevs to manually create one.' % str(e)) 1227 except subprocess.CalledProcessError as e:
1202 sys.exit(1) 1228 logger.error('Setting up tap device failed:\n%s\nRun runqemu-gen-tapdevs to manually create one.' % str(e))
1203 lockfile = os.path.join(lockdir, tap) 1229 sys.exit(1)
1204 self.taplock = lockfile + '.lock' 1230 lockfile = os.path.join(lockdir, tap)
1205 self.acquire_taplock() 1231 self.taplock = lockfile + '.lock'
1206 self.cleantap = True 1232 if self.acquire_taplock():
1207 logger.debug('Created tap: %s' % tap) 1233 self.cleantap = True
1234 logger.debug('Created tap: %s' % tap)
1235 break
1236 else:
1237 tap = None
1208 1238
1209 if not tap: 1239 if not tap:
1210 logger.error("Failed to setup tap device. Run runqemu-gen-tapdevs to manually create.") 1240 logger.error("Failed to setup tap device. Run runqemu-gen-tapdevs to manually create.")
@@ -1295,6 +1325,10 @@ to your build configuration.
1295 elif drive_type.startswith("/dev/hd"): 1325 elif drive_type.startswith("/dev/hd"):
1296 logger.info('Using ide drive') 1326 logger.info('Using ide drive')
1297 vm_drive = "-drive file=%s,format=%s" % (self.rootfs, rootfs_format) 1327 vm_drive = "-drive file=%s,format=%s" % (self.rootfs, rootfs_format)
1328 elif drive_type.startswith("/dev/mmcblk"):
1329 logger.info('Using sdcard drive')
1330 vm_drive = '-drive id=sdcard0,if=none,file=%s,format=%s -device sdhci-pci -device sd-card,drive=sdcard0' \
1331 % (self.rootfs, rootfs_format)
1298 elif drive_type.startswith("/dev/vdb"): 1332 elif drive_type.startswith("/dev/vdb"):
1299 logger.info('Using block virtio drive'); 1333 logger.info('Using block virtio drive');
1300 vm_drive = '-drive id=disk0,file=%s,if=none,format=%s -device virtio-blk-device,drive=disk0%s' \ 1334 vm_drive = '-drive id=disk0,file=%s,if=none,format=%s -device virtio-blk-device,drive=disk0%s' \
@@ -1454,7 +1488,7 @@ to your build configuration.
1454 self.qemu_opt += ' -display ' 1488 self.qemu_opt += ' -display '
1455 if self.egl_headless == True: 1489 if self.egl_headless == True:
1456 self.check_render_nodes() 1490 self.check_render_nodes()
1457 self.set_dri_path() 1491 self.set_mesa_paths()
1458 self.qemu_opt += 'egl-headless,' 1492 self.qemu_opt += 'egl-headless,'
1459 else: 1493 else:
1460 if self.sdl == True: 1494 if self.sdl == True:
@@ -1464,10 +1498,10 @@ to your build configuration.
1464 self.qemu_opt += 'gtk,' 1498 self.qemu_opt += 'gtk,'
1465 1499
1466 if self.gl == True: 1500 if self.gl == True:
1467 self.set_dri_path() 1501 self.set_mesa_paths()
1468 self.qemu_opt += 'gl=on,' 1502 self.qemu_opt += 'gl=on,'
1469 elif self.gl_es == True: 1503 elif self.gl_es == True:
1470 self.set_dri_path() 1504 self.set_mesa_paths()
1471 self.qemu_opt += 'gl=es,' 1505 self.qemu_opt += 'gl=es,'
1472 self.qemu_opt += 'show-cursor=on' 1506 self.qemu_opt += 'show-cursor=on'
1473 1507
@@ -1483,7 +1517,7 @@ to your build configuration.
1483 # If no serial or serialtcp options were specified, only ttyS0 is created 1517 # If no serial or serialtcp options were specified, only ttyS0 is created
1484 # and sysvinit shows an error trying to enable ttyS1: 1518 # and sysvinit shows an error trying to enable ttyS1:
1485 # INIT: Id "S1" respawning too fast: disabled for 5 minutes 1519 # INIT: Id "S1" respawning too fast: disabled for 5 minutes
1486 serial_num = len(re.findall("-serial", self.qemu_opt)) 1520 serial_num = len(re.findall("(^| )-serial ", self.qemu_opt))
1487 1521
1488 # Assume if the user passed serial options, they know what they want 1522 # Assume if the user passed serial options, they know what they want
1489 # and pad to two devices 1523 # and pad to two devices
@@ -1503,7 +1537,7 @@ to your build configuration.
1503 1537
1504 self.qemu_opt += " %s" % self.get("QB_SERIAL_OPT") 1538 self.qemu_opt += " %s" % self.get("QB_SERIAL_OPT")
1505 1539
1506 serial_num = len(re.findall("-serial", self.qemu_opt)) 1540 serial_num = len(re.findall("(^| )-serial ", self.qemu_opt))
1507 if serial_num < 2: 1541 if serial_num < 2:
1508 self.qemu_opt += " -serial null" 1542 self.qemu_opt += " -serial null"
1509 1543
@@ -1658,9 +1692,6 @@ to your build configuration.
1658 self.cleaned = True 1692 self.cleaned = True
1659 1693
1660 def run_bitbake_env(self, mach=None, target=''): 1694 def run_bitbake_env(self, mach=None, target=''):
1661 bitbake = shutil.which('bitbake')
1662 if not bitbake:
1663 return
1664 1695
1665 if not mach: 1696 if not mach:
1666 mach = self.get('MACHINE') 1697 mach = self.get('MACHINE')
@@ -1669,11 +1700,18 @@ to your build configuration.
1669 if multiconfig: 1700 if multiconfig:
1670 multiconfig = "mc:%s" % multiconfig 1701 multiconfig = "mc:%s" % multiconfig
1671 1702
1703 if self.rootfs and not target:
1704 target = self.rootfs
1705
1672 if mach: 1706 if mach:
1673 cmd = 'MACHINE=%s bitbake -e %s %s' % (mach, multiconfig, target) 1707 cmd = 'MACHINE=%s bitbake -e %s %s' % (mach, multiconfig, target)
1674 else: 1708 else:
1675 cmd = 'bitbake -e %s %s' % (multiconfig, target) 1709 cmd = 'bitbake -e %s %s' % (multiconfig, target)
1676 1710
1711 bitbake = shutil.which('bitbake')
1712 if not bitbake:
1713 raise OEPathError("Bitbake is needed to run '%s', but it is not found in PATH. Please source the bitbake build environment." % cmd.strip())
1714
1677 logger.info('Running %s...' % cmd) 1715 logger.info('Running %s...' % cmd)
1678 try: 1716 try:
1679 return subprocess.check_output(cmd, shell=True).decode('utf-8') 1717 return subprocess.check_output(cmd, shell=True).decode('utf-8')
@@ -1685,11 +1723,7 @@ to your build configuration.
1685 cmd = 'MACHINE=%s bitbake -e %s %s' % (mach, multiconfig, target) 1723 cmd = 'MACHINE=%s bitbake -e %s %s' % (mach, multiconfig, target)
1686 else: 1724 else:
1687 cmd = 'bitbake -e %s %s' % (multiconfig, target) 1725 cmd = 'bitbake -e %s %s' % (multiconfig, target)
1688 try: 1726 return subprocess.check_output(cmd, shell=True).decode('utf-8')
1689 return subprocess.check_output(cmd, shell=True).decode('utf-8')
1690 except subprocess.CalledProcessError as err:
1691 logger.warning("Couldn't run '%s' to gather environment information, giving up with 'bitbake -e':\n%s" % (cmd, err.output.decode('utf-8')))
1692 return ''
1693 1727
1694 1728
1695 def load_bitbake_env(self, mach=None, target=None): 1729 def load_bitbake_env(self, mach=None, target=None):
@@ -1749,6 +1783,7 @@ def main():
1749 config.check_args() 1783 config.check_args()
1750 config.read_qemuboot() 1784 config.read_qemuboot()
1751 config.check_and_set() 1785 config.check_and_set()
1786 config.uncompress_rootfs()
1752 # Check whether the combos is valid or not 1787 # Check whether the combos is valid or not
1753 config.validate_combos() 1788 config.validate_combos()
1754 config.print_config() 1789 config.print_config()