#!/usr/bin/env python3 import os import re import shutil import subprocess ## import git # https://pypi.org/project/GitPython/ import patch_ng # https://pypi.org/project/patch-ng/ patches = ['git-version.patch', 'banner.patch', 'efi-iso.patch'] monkeypatch = { 'config/general.h': { 'enable': [ 'NET_PROTO_IPV6', 'DOWNLOAD_PROTO_HTTPS', 'DOWNLOAD_PROTO_FTP', 'DOWNLOAD_PROTO_NFS', 'HTTP_AUTH_NTLM', # 'HTTP_ENC_PEERDIST', # 'HTTP_HACK_GCE', 'NSLOOKUP_CMD', 'TIME_CMD', 'DIGEST_CMD', 'LOTEST_CMD', 'VLAN_CMD', # 'PXE_CMD', # Causes EFI to fail. 'REBOOT_CMD', 'POWEROFF_CMD', # 'IMAGE_NBI', # I *think* this causes EFI to fail. Can I build it directly as a target w/o enabling? # 'IMAGE_ELF', # Causes EFI to fail, and is auto-enabled for what needs it. # 'IMAGE_MULTIBOOT', # Also enabled by default for MBR builds # 'IMAGE_SCRIPT', # Enabled where needed etc. # 'IMAGE_BZIMAGE', # http://lists.ipxe.org/pipermail/ipxe-devel/2017-March/005510.html # 'IMAGE_COMBOOT', # Not really necessary since iPXE has native menus. # 'IMAGE_EFI', # Enabled by default for EFI builds, and causes build error on bin/ipxe.dsk.tmp # 'IMAGE_PXE', # EFI builds fail with this. related to PXE_STACK/PXE_CMD? 'IMAGE_TRUST_CMD', 'PCI_CMD', 'PARAM_CMD', 'NEIGHBOUR_CMD', 'PING_CMD', 'CONSOLE_CMD', 'IPSTAT_CMD', 'PROFSTAT_CMD', 'NTP_CMD', 'CERT_CMD' ], 'disable': [ # 'CRYPTO_80211_WEP', # 'CRYPTO_80211_WPA', # 'CRYPTO_80211_WPA2', # 'IWMGMT_CMD' ]}, 'config/console.h': { 'enable': [ 'CONSOLE_FRAMEBUFFER' ], 'disable': [ # Disables would go here. ]}} rootdir = '/opt/builds' logdir = os.path.join(rootdir, 'logs', 'ipxe') patchdir = os.path.join(rootdir, 'patches', 'ipxe') buildroot = os.path.join(rootdir, 'ipxe_build') srcdir = os.path.join(buildroot, 'src') configdir = os.path.join(rootdir, 'configs', 'ipxe') destdir = os.path.join(rootdir, 'built', 'ipxe') repo = git.Repo(buildroot) master = repo.branches.master remote = repo.remote('origin') rom_suffixes = ('rom', 'mrom') rom_types = ('rtl8139', '8086100e', '80861209', '10500940', '10222000', '10ec8139', '1af41000', '8086100f', '808610d3', '15ad07b0') def doMonkeypatch(fname, changeset): enable_re = None disable_re = None fpath = os.path.join(srcdir, fname) if changeset['enable']: enable_re = re.compile((r'^\s*(//#define|#undef)\s+' r'(?P{0})' r'(?P\s+/\*.*)?\s*$').format('|'.join(changeset['enable']))) if changeset['disable']: disable_re = re.compile((r'^(#define|//#undef)\s+' r'(?P{0})' r'(?P\s+/\*.*)?\s*$').format('|'.join(changeset['disable']))) with open(fpath, 'r') as fh: configstr = fh.read() configlines = configstr.splitlines() for idx, line in enumerate(configlines[:]): if enable_re: r = enable_re.search(line) if r: configlines[idx] = '#define {0}{1}'.format(r.group('optname'), r.group('comment')) if disable_re: r = disable_re.search(line) if r: configlines[idx] = '#undef {0}{1}'.format(r.group('optname'), r.group('comment')) with open(fpath, 'w') as fh: fh.write('\n'.join(configlines)) fh.write('\n') return() def main(): # Cleanup the repo. repo.head.reset(commit = 'HEAD', working_tree = True) if repo.active_branch != master: master.checkout() repo.head.reset(commit = 'HEAD', working_tree = True) repo.git.clean('-xdf') try: remote.pull() except BrokenPipeError: pass # Patch for p in patches: with open(os.path.join(patchdir, p), 'rb') as fh: patchset = patch_ng.PatchSet(fh) patchset.apply(strip = 1, root = buildroot) # "Monkeypatch" - sed-like. for f, changeset in monkeypatch.items(): doMonkeypatch(f, changeset) # Build. Finally! # TODO: ARM support! # for d in ('default', 'efi', 'iso', 'legacy', 'roms', 'arm'): for d in ('default', 'efi', 'iso', 'legacy', 'roms'): dpath = os.path.join(destdir, d) os.makedirs(dpath, exist_ok = True) os.makedirs(logdir, exist_ok = True) os.chdir(srcdir) ## Base files # TODO: ARM support! # TODO: efi-sb support (secureboot)! # http://ipxe.org/appnote/buildtargets # http://ipxe.org/appnote/buildtargets#special_targets ## BOOTSTRAP ## with open(os.path.join(logdir, 'all_bootstrap.stderr'), 'wb') as stderr, \ open(os.path.join(logdir, 'all_bootstrap.stdout'), 'wb') as stdout: subprocess.run(['make', 'all', 'EMBED={0}'.format(os.path.join(configdir, 'bootstrap.ipxe'))], stdout = stdout, stderr = stderr) for fsrc, fdest in (('undionly.kpxe', 'legacy/bootstrap_{f}'), ('ipxe.iso', 'iso/bootstrap_bios.iso'), ('ipxe.usb', 'iso/bootstrap_pxe_usb.img'), ('ipxe.dsk', 'iso/bootstrap_pxe_floppy.img')): srcpath = os.path.join(srcdir, 'bin', fsrc) fname = os.path.basename(srcpath) destpath = os.path.join(destdir, fdest.format(f = fname)) shutil.copy2(srcpath, destpath) for rom in rom_types: for s in rom_suffixes: fname = '{0}.{1}'.format(rom, s) fpath = os.path.join(srcdir, 'bin', fname) if os.path.isfile(fpath): shutil.copy2(fpath, os.path.join(destdir, 'roms', 'bootstrap_{0}'.format(fname))) # http://ipxe.org/howto/romburning # https://libvirt.org/formatdomain.html#elementsNICSROM if rom == '1af41000': os.symlink(fpath, os.path.join(destdir, 'roms', 'bootstrap_virtio.rom')) ## EMBEDDED ## with open(os.path.join(logdir, 'all.stderr'), 'wb') as stderr, \ open(os.path.join(logdir, 'all.stdout'), 'wb') as stdout: subprocess.run(['make', 'all', 'EMBED={0}'.format(os.path.join(configdir, 'chain-default.ipxe'))], stdout = stdout, stderr = stderr) for fsrc, fdest in (('undionly.kpxe', 'legacy/{f}'), ('ipxe.iso', 'iso/bios.iso'), ('ipxe.usb', 'iso/pxe_usb.img'), ('ipxe.dsk', 'iso/pxe_floppy.img')): srcpath = os.path.join(srcdir, 'bin', fsrc) fname = os.path.basename(srcpath) destpath = os.path.join(destdir, fdest.format(f = fname)) shutil.copy2(srcpath, destpath) for rom in rom_types: for s in rom_suffixes: fname = '{0}.{1}'.format(rom, s) fpath = os.path.join(srcdir, 'bin', fname) if os.path.isfile(fpath): shutil.copy2(fpath, os.path.join(destdir, 'roms', fname)) if rom == '1af41000': os.symlink(fpath, os.path.join(destdir, 'roms', 'virtio.rom')) # DOS/MBR sector/HDD img. ## BOOTSTRAP ## with open(os.path.join(logdir, 'bootstrap_hdd.stderr'), 'wb') as stderr, \ open(os.path.join(logdir, 'bootstrap_hdd.stdout'), 'wb') as stdout: subprocess.run(['make', 'bin/ipxe.hd', 'EMBED={0}'.format(os.path.join(configdir, 'bootstrap.ipxe'))], stdout = stdout, stderr = stderr) shutil.copy2(os.path.join(srcdir, 'bin/ipxe.hd'), os.path.join(destdir, 'iso', 'bootstrap_legacy_mbr.img')) ## EMBEDDED ## with open(os.path.join(logdir, 'hdd.stderr'), 'wb') as stderr, \ open(os.path.join(logdir, 'hdd.stdout'), 'wb') as stdout: subprocess.run(['make', 'bin/ipxe.hd', 'EMBED={0}'.format(os.path.join(configdir, 'chain-default.ipxe'))], stdout = stdout, stderr = stderr) shutil.copy2(os.path.join(srcdir, 'bin/ipxe.hd'), os.path.join(destdir, 'iso', 'legacy_mbr.img')) # PXE loaders ## BOOTSTRAP ## with open(os.path.join(logdir, 'bootstrap_loader.stderr'), 'wb') as stderr, \ open(os.path.join(logdir, 'bootstrap_loader.stdout'), 'wb') as stdout: subprocess.run(['make', 'bin/ipxe.pxe', 'EMBED={0}'.format(os.path.join(configdir, 'bootstrap.ipxe'))], stdout = stdout, stderr = stderr) os.rename(os.path.join(srcdir, 'bin/ipxe.pxe'), os.path.join(destdir, 'default', 'bootstrap_loader.pxe')) ## EMBEDDED ## with open(os.path.join(logdir, 'loader.stderr'), 'wb') as stderr, \ open(os.path.join(logdir, 'loader.stdout'), 'wb') as stdout: subprocess.run(['make', 'bin/ipxe.pxe', 'EMBED={0}'.format(os.path.join(configdir, 'chain-default.ipxe'))], stdout = stdout, stderr = stderr) os.rename(os.path.join(srcdir, 'bin/ipxe.pxe'), os.path.join(destdir, 'default', 'loader.pxe')) # EFI binaries and ISO images # These have to be done grouped because the eiso stuff APPARENTLY doesn't parse EMBED or lack thereof correctly? ## BOOTSTRAP ## ### EFI ### with open(os.path.join(logdir, 'bootstrap_efi.stderr'), 'wb') as stderr, \ open(os.path.join(logdir, 'bootstrap_efi.stdout'), 'wb') as stdout: subprocess.run(['make', 'bin-i386-efi/ipxe.efi', 'bin-x86_64-efi/ipxe.efi', 'EMBED={0}'.format(os.path.join(configdir, 'bootstrap.ipxe'))], stdout = stdout, stderr = stderr) shutil.copy2(os.path.join(srcdir, 'bin-i386-efi/ipxe.efi'), os.path.join(destdir, 'efi', 'bootstrap_32.efi')) shutil.copy2(os.path.join(srcdir, 'bin-x86_64-efi/ipxe.efi'), os.path.join(destdir, 'efi', 'bootstrap_64.efi')) ### UEFI ISO ### with open(os.path.join(logdir, 'bootstrap_iso.stderr'), 'wb') as stderr, \ open(os.path.join(logdir, 'bootstrap_iso.stdout'), 'wb') as stdout: subprocess.run(['make', 'bin/ipxe.liso', 'bin/ipxe.eiso', 'EMBED={0}'.format(os.path.join(configdir, 'bootstrap.ipxe'))], stdout = stdout, stderr = stderr) os.rename(os.path.join(srcdir, 'bin/ipxe.liso'), os.path.join(destdir, 'iso', 'bootstrap_legacy.iso')) os.rename(os.path.join(srcdir, 'bin/ipxe.eiso'), os.path.join(destdir, 'iso', 'bootstrap_uefi.iso')) ## EMBEDDED ## ### EFI ### with open(os.path.join(logdir, 'efi.stderr'), 'wb') as stderr, \ open(os.path.join(logdir, 'efi.stdout'), 'wb') as stdout: subprocess.run(['make', 'bin-i386-efi/ipxe.efi', 'bin-x86_64-efi/ipxe.efi', 'EMBED={0}'.format(os.path.join(configdir, 'chain-default.ipxe'))], stdout = stdout, stderr = stderr) shutil.copy2(os.path.join(srcdir, 'bin-i386-efi/ipxe.efi'), os.path.join(destdir, 'efi', '32.efi')) shutil.copy2(os.path.join(srcdir, 'bin-x86_64-efi/ipxe.efi'), os.path.join(destdir, 'efi', '64.efi')) ### UEFI ISO ### with open(os.path.join(logdir, 'iso.stderr'), 'wb') as stderr, \ open(os.path.join(logdir, 'iso.stdout'), 'wb') as stdout: subprocess.run(['make', 'bin/ipxe.liso', 'bin/ipxe.eiso', 'EMBED={0}'.format(os.path.join(configdir, 'chain-default.ipxe'))], stdout = stdout, stderr = stderr) os.rename(os.path.join(srcdir, 'bin/ipxe.liso'), os.path.join(destdir, 'iso', 'legacy.iso')) os.rename(os.path.join(srcdir, 'bin/ipxe.eiso'), os.path.join(destdir, 'iso', 'uefi.iso')) return() if __name__ == '__main__': main()