diff --git a/live-build/ubuntu-cpc/README.cpc.md b/live-build/ubuntu-cpc/README.cpc.md new file mode 100644 index 00000000..9d61b2d5 --- /dev/null +++ b/live-build/ubuntu-cpc/README.cpc.md @@ -0,0 +1,68 @@ +# TL;DR + +In order to generate the hooks for a specific image target set, call the +`make-hooks` script, located in `hooks.d` as + + ./make-hooks --hooks-dir ../hooks + +where `image_set` is the name of a series file (e.g. "vagrant") without leading +path components. Do *not* check in the `hooks` folder, it is automatically +generated by `auto/config` during Live Build runs. + + +# Hook placement and ordering + +Scripts live in subfolders below the `hooks.d` folder. Currently the folders +`chroot` and `base` exist. The folder with the name `extra` is reserved for +private scripts, which are not included in the source of livecd-rootfs. The +scripts are not numbered, instead the order of their execution depends on the +order in which they are listed in a *series* file. + +Series files are placed in subfolders `hooks.d/base/series` or +`hooks.d/extra/series`. Each series file contains a list of scripts to be +executed. Empty lines and lines starting with a `#` are ignored. + +Series files in `extra/series` override files in `base/series` with the same +name. For example, if a series file `base/series/cloudA` exists and a series +file `extra/series/cloudA`, then the latter will be preferred. + +A series file in `extra/series` may also list scripts that are located in the +`chroot` and `base` folders. In addition, series files can *depend* on other +series files. For example, the series files for most custom images look similar +to this: + + depends disk-image + depends extra-settings + extra/cloudB.binary + +Where `disk-image` and `extra-settings` may list scripts and dependencies which +are to be processed before the script `extra/cloudB.binary` is called. + +ACHTUNG: live build runs scripts with the suffix ".chroot" in a batch separate +from scripts ending in ".binary". Even if you arrange them interleaved in your +series files, the chroot scripts will be run before the binary scripts. + +# Image set selection for Live Build + +During a Live Build, enumerated symbolic links are generated based on the +contents of one or more series files. The series files are selected according +to the contents of the `IMAGE_TARGETS` environment variable. For example, in +order to trigger the build of `squashfs` and `vagrant`, list them in the +`IMAGE_TARGETS` variable as `squashfs,vagrant`. The separator can be a comma, +a semi-colon or whitespace. + +The generation of the symbolic links is triggered from the `auto/config` script, +from where the contents of the `IMAGE_TARGETS` environment variable are passed +on to the `make-hooks` script. + + +# Symlink generation + +Since Live Build itself does not know about series files, a traditional `hooks` +folder is generated using the `make-hooks` script. The script takes as arguments +the names of the series files to be processed. + +The script parses the series files and generates enumerated symbolic links for +all entries. Per default, these are placed into a directory named `hooks` next +to the `hooks.d` directory. This can be changed using the `--hooks-dir` +parameter. diff --git a/live-build/ubuntu-cpc/hooks.d/base/series/all b/live-build/ubuntu-cpc/hooks.d/base/series/all new file mode 120000 index 00000000..8681f8b8 --- /dev/null +++ b/live-build/ubuntu-cpc/hooks.d/base/series/all @@ -0,0 +1 @@ +base \ No newline at end of file diff --git a/live-build/ubuntu-cpc/hooks.d/base/series/base b/live-build/ubuntu-cpc/hooks.d/base/series/base new file mode 100644 index 00000000..f04bdec7 --- /dev/null +++ b/live-build/ubuntu-cpc/hooks.d/base/series/base @@ -0,0 +1,8 @@ +depends root-dir +depends tarball +depends squashfs +depends disk-image +depends qcow2 +depends vmdk +depends vagrant +depends wsl diff --git a/live-build/ubuntu-cpc/hooks.d/base/series/disk-image b/live-build/ubuntu-cpc/hooks.d/base/series/disk-image new file mode 100644 index 00000000..355c010c --- /dev/null +++ b/live-build/ubuntu-cpc/hooks.d/base/series/disk-image @@ -0,0 +1,3 @@ +base/disk-image.binary +base/disk-image-uefi.binary +base/disk-image-ppc64el.binary diff --git a/live-build/ubuntu-cpc/hooks.d/base/series/qcow2 b/live-build/ubuntu-cpc/hooks.d/base/series/qcow2 new file mode 100644 index 00000000..cc3ced35 --- /dev/null +++ b/live-build/ubuntu-cpc/hooks.d/base/series/qcow2 @@ -0,0 +1,2 @@ +depends disk-image +base/qcow2-image.binary diff --git a/live-build/ubuntu-cpc/hooks.d/base/series/root-dir b/live-build/ubuntu-cpc/hooks.d/base/series/root-dir new file mode 100644 index 00000000..b5d3b4e4 --- /dev/null +++ b/live-build/ubuntu-cpc/hooks.d/base/series/root-dir @@ -0,0 +1 @@ +base/create-root-dir.binary diff --git a/live-build/ubuntu-cpc/hooks.d/base/series/squashfs b/live-build/ubuntu-cpc/hooks.d/base/series/squashfs new file mode 100644 index 00000000..60332761 --- /dev/null +++ b/live-build/ubuntu-cpc/hooks.d/base/series/squashfs @@ -0,0 +1,2 @@ +depends root-dir +base/root-squashfs.binary diff --git a/live-build/ubuntu-cpc/hooks.d/base/series/tarball b/live-build/ubuntu-cpc/hooks.d/base/series/tarball new file mode 100644 index 00000000..2ea30bf2 --- /dev/null +++ b/live-build/ubuntu-cpc/hooks.d/base/series/tarball @@ -0,0 +1,2 @@ +depends root-dir +base/root-xz.binary diff --git a/live-build/ubuntu-cpc/hooks.d/base/series/vagrant b/live-build/ubuntu-cpc/hooks.d/base/series/vagrant new file mode 100644 index 00000000..a4eeb86f --- /dev/null +++ b/live-build/ubuntu-cpc/hooks.d/base/series/vagrant @@ -0,0 +1,2 @@ +depends disk-image +base/vagrant.binary diff --git a/live-build/ubuntu-cpc/hooks.d/base/series/vmdk b/live-build/ubuntu-cpc/hooks.d/base/series/vmdk new file mode 100644 index 00000000..ba0acbae --- /dev/null +++ b/live-build/ubuntu-cpc/hooks.d/base/series/vmdk @@ -0,0 +1,3 @@ +depends disk-image +base/vmdk-image.binary +base/vmdk-ova-image.binary diff --git a/live-build/ubuntu-cpc/hooks.d/base/series/wsl b/live-build/ubuntu-cpc/hooks.d/base/series/wsl new file mode 100644 index 00000000..0ff7a9a7 --- /dev/null +++ b/live-build/ubuntu-cpc/hooks.d/base/series/wsl @@ -0,0 +1,2 @@ +depends root-dir +base/wsl-gz.binary diff --git a/live-build/ubuntu-cpc/hooks.d/make-hooks b/live-build/ubuntu-cpc/hooks.d/make-hooks new file mode 100755 index 00000000..7796be3d --- /dev/null +++ b/live-build/ubuntu-cpc/hooks.d/make-hooks @@ -0,0 +1,237 @@ +#!/usr/bin/env python3 +#-*- encoding: utf-8 -*- +""" +This script parses a series file and its dependencies and generates a hooks +folder containing symbolic links to the scripts that need to be invoked for +a given image target set. + +For example, if you wish to build the image target sets "vmdk" and "vagrant", +you would call this script as + +./make-hooks --hooks-dir hooks vmdk vagrant + +Scripts live in subfolders below the "hooks.d" folder. Currently the folders +"chroot" and "base" exist. The folder with the name "extra" is reserved for +private scripts, which are not included in the source of livecd-rootfs. The +scripts are not numbered, instead the order of their execution depends on the +order in which they are listed in a series file. + +Series files are placed into the subfolders "base/series" or "extra/series". +Each series file contains a list of scripts to be executed. Empty lines and +lines starting with a '#' are ignored. Series files in "extra/series" override +files in "base/series" with the same name. For example, if a series file +"base/series/cloudA" exists and a series file "extra/series/cloudA", then the +latter will be preferred. + +A series file in "extra/series" may also list scripts that are located in the +"chroot" and "base" folders. In addition, series files can depend on other +series files. For example, the series files for most custom images look similar +to this: + + depends disk-image + depends extra-settings + extra/cloudB.binary + +Where "disk-image" and "extra-settings" may list scripts and dependencies which +are to be processed before the script "extra/cloudB.binary" is called. + +ACHTUNG: live build runs scripts with the suffix ".chroot" in a batch separate +from scripts ending in ".binary". Even if you arrange them interleaved in your +series files, the chroot scripts will be run before the binary scripts. +""" + +import argparse +import os +import re +import shutil +import sys +import yaml + +SCRIPT_DIR = os.path.normpath(os.path.dirname(os.path.realpath(sys.argv[0]))) +HOOKS_DIR = os.path.normpath(os.path.join(SCRIPT_DIR, "..", "hooks")) + +EXIT_OK = 0 +EXIT_ERR = 1 + +class MakeHooksError(Exception): + pass + +class MakeHooks: + """This class provides series file parsing and symlink generator + functionality.""" + + def __init__(self, hooks_dir=None, quiet=False): + """The hooks_dir parameter can be used to specify the path to the + directory, into which the hook symlinks to the actual script files + should be placed. + + If quiet is set to True, info messages during symlink creation will + be suppressed. Use this if your build is not private, but you would + like to hide which scripts are being run. + """ + self._script_dir = SCRIPT_DIR + self._hooks_dir = hooks_dir or HOOKS_DIR + self._quiet = quiet + self._hooks_list = [] + self._included = set() + + def reset(self): + """Reset the internal state allowing instance to be reused for + another run.""" + self._hooks_list.clear() + self._included.clear() + + def print_usage(self): + print( + "CPC live build hook generator script \n" + " \n" + "Usage: ./make-hooks.sh [OPTIONS] \n" + " \n" + "Options: \n" + " \n" + " --help, -h Show this message and exit. \n" + " --quiet, -q Only show warnings and error messages. \n" + " --hooks-dir, -d The directory where to write the symlinks.\n" + ) + + def find_series_file(self, image_set): + """Search for the series file requested in the image_set parameter. + + The image_set parameter should be a string containing the name of an + image target set represented by a series file. First the "extra/series" + folder is searched followed by the "base/series" folder. + + When a file with the given name is found, the search stops and the + full path to the file is returned. + """ + for subdir in ["extra", "base"]: + series_file = os.path.join(self._script_dir, subdir, "series", + image_set) + if os.path.isfile(series_file): + return series_file + return None + + def make_hooks(self, image_sets): + """Entry point for parsing series files and their dependencies and + for generating the symlinks in the hooks folder. + + The image_sets parameter must be an iterable containing the names of + the series files representing the corresponding image target sets, + e.g. "vmdk" or "vagrant". + """ + self.collect_chroot_hooks() + self.collect_binary_hooks(image_sets) + self.create_symlinks() + + def collect_chroot_hooks(self): + """Chroot hooks are numbered and not explicitly mentioned in series + files. Collect them, sort them and add them to the internal list of + paths to hook sripts. + """ + chroot_hooks_dir = os.path.join(self._script_dir, "chroot") + + chroot_entries = os.listdir(chroot_hooks_dir) + chroot_entries.sort() + + for entry in chroot_entries: + if not (entry.endswith(".chroot_early") or + entry.endswith(".chroot")): + continue + self._hooks_list.append(os.path.join("chroot", entry)) + + def collect_binary_hooks(self, image_sets): + """Search the series files for the given image_sets and parse them + and their dependencies to generate a list of hook scripts to be run + during image build. + + The image_sets parameter must be an iterable containing the names of + the series files representing the corresponding image target sets, + e.g. "vmdk" or "vagrant". + + Populates the internal list of paths to hook scripts in the order in + which the scripts are to be run. + """ + for image_set in image_sets: + series_file = self.find_series_file(image_set) + + if not series_file: + raise MakeHooksError( + "Series file for image set '%s' not found." % image_set) + + with open(series_file, "r", encoding="utf-8") as fp: + for line in fp: + line = line.strip() + if not line: + continue + m = re.match(r"^\s*depends\s+(\S+.*)$", line) + if m: + include_set = m.group(1) + if include_set in self._included: + continue + self._included.add(include_set) + self.collect_binary_hooks([include_set,]) + continue + if not line in self._hooks_list: + self._hooks_list.append(line) + + def create_symlinks(self): + """Once the internal list of hooks scripts has been populated by a + call to collect_?_hooks, this method is used to populate the hooks + folder with enumerated symbolic links to the hooks scripts. If the + folder does not exist, it will be created. If it exists, it must be + empty or a MakeHooksError will be thrown. + """ + if os.path.isdir(self._hooks_dir) and os.listdir(self._hooks_dir): + # Only print a warning, because directory might have been created + # by auto/config voodoo. + sys.stderr.write("WARNING: Hooks directory exists and is not empty.\n") + os.makedirs(self._hooks_dir, exist_ok=True) + + for counter, hook in enumerate(self._hooks_list, start=1): + hook_basename = os.path.basename(hook) + + m = re.match(r"^\d+-(?:\d+-)?(?P.*)$", hook_basename) + if m: + hook_basename = m.group("basename") + + linkname = ("%03d-" % counter) + hook_basename + linksrc = os.path.join(self._hooks_dir, linkname) + linkdest = os.path.relpath(os.path.join(self._script_dir, hook), + self._hooks_dir) + + if not self._quiet: + print("[HOOK] %s => %s" % (linkname, hook)) + os.symlink(linkdest, linksrc) + + def cli(self, args): + """Command line interface to the hooks generator.""" + parser = argparse.ArgumentParser() + + parser.add_argument("-q", "--quiet", dest="quiet", type=bool, + help="Only show warnings and error messages.") + parser.add_argument("-d", "--hooks-dir", dest="hooks_dir", type=str, + help="The directory where to create the symlinks.") + parser.add_argument("image_target", nargs="+", type=str, + help="") + + self.reset() + options = parser.parse_args(args) + + # Copy options to object attributes. + for key, value in vars(options).items(): + if value and hasattr(self, "_" + key): + setattr(self, "_" + key, value) + + # Take remaining command line arguments, sanitize and turn into list. + image_sets = re.sub(r";|,", " ", " ".join(options.image_target))\ + .split() + + self.make_hooks(image_sets) + + +if __name__ == "__main__": + try: + MakeHooks().cli(sys.argv[1:]) + except MakeHooksError as e: + sys.stderr.write("%s: %s\n" % (os.path.basename(sys.argv[0]), str(e))) + sys.exit(EXIT_ERR)