aboutsummaryrefslogtreecommitdiff
path: root/nixpkgs/nixos/lib
diff options
context:
space:
mode:
authorKatharina Fey <kookie@spacekookie.de>2019-10-05 12:43:18 +0000
committerKatharina Fey <kookie@spacekookie.de>2019-10-05 12:44:52 +0000
commitcf85056ba64caf3267d43255ef4a1243e9c8ee3b (patch)
tree3051519e9c8275b870aac43f80af875715c9d124 /nixpkgs/nixos/lib
parent1148b1d122bc03e9a3665856c9b7bb96bd4e3994 (diff)
parent2436c27541b2f52deea3a4c1691216a02152e729 (diff)
Add 'nixpkgs/' from commit '2436c27541b2f52deea3a4c1691216a02152e729'
git-subtree-dir: nixpkgs git-subtree-mainline: 1148b1d122bc03e9a3665856c9b7bb96bd4e3994 git-subtree-split: 2436c27541b2f52deea3a4c1691216a02152e729
Diffstat (limited to 'nixpkgs/nixos/lib')
-rw-r--r--nixpkgs/nixos/lib/build-vms.nix101
-rw-r--r--nixpkgs/nixos/lib/eval-config.nix67
-rw-r--r--nixpkgs/nixos/lib/from-env.nix4
-rw-r--r--nixpkgs/nixos/lib/make-channel.nix31
-rw-r--r--nixpkgs/nixos/lib/make-disk-image.nix247
-rw-r--r--nixpkgs/nixos/lib/make-ext4-fs.nix91
-rw-r--r--nixpkgs/nixos/lib/make-iso9660-image.nix65
-rw-r--r--nixpkgs/nixos/lib/make-iso9660-image.sh136
-rw-r--r--nixpkgs/nixos/lib/make-options-doc/default.nix164
-rw-r--r--nixpkgs/nixos/lib/make-options-doc/options-to-docbook.xsl236
-rw-r--r--nixpkgs/nixos/lib/make-options-doc/postprocess-option-descriptions.xsl115
-rw-r--r--nixpkgs/nixos/lib/make-squashfs.nix28
-rw-r--r--nixpkgs/nixos/lib/make-system-tarball.nix56
-rw-r--r--nixpkgs/nixos/lib/make-system-tarball.sh57
-rw-r--r--nixpkgs/nixos/lib/qemu-flags.nix25
-rw-r--r--nixpkgs/nixos/lib/test-driver/Logger.pm75
-rw-r--r--nixpkgs/nixos/lib/test-driver/Machine.pm734
-rw-r--r--nixpkgs/nixos/lib/test-driver/log2html.xsl135
-rw-r--r--nixpkgs/nixos/lib/test-driver/logfile.css129
-rw-r--r--nixpkgs/nixos/lib/test-driver/test-driver.pl191
-rw-r--r--nixpkgs/nixos/lib/test-driver/treebits.js30
-rw-r--r--nixpkgs/nixos/lib/testing.nix269
-rw-r--r--nixpkgs/nixos/lib/testing/jquery-ui.nix24
-rw-r--r--nixpkgs/nixos/lib/testing/jquery.nix36
-rw-r--r--nixpkgs/nixos/lib/utils.nix139
25 files changed, 3185 insertions, 0 deletions
diff --git a/nixpkgs/nixos/lib/build-vms.nix b/nixpkgs/nixos/lib/build-vms.nix
new file mode 100644
index 00000000000..1bad63b9194
--- /dev/null
+++ b/nixpkgs/nixos/lib/build-vms.nix
@@ -0,0 +1,101 @@
+{ system
+, # Use a minimal kernel?
+ minimal ? false
+, # Ignored
+ config ? null
+ # Nixpkgs, for qemu, lib and more
+, pkgs
+, # NixOS configuration to add to the VMs
+ extraConfigurations ? []
+}:
+
+with pkgs.lib;
+with import ../lib/qemu-flags.nix { inherit pkgs; };
+
+rec {
+
+ inherit pkgs;
+
+ qemu = pkgs.qemu_test;
+
+
+ # Build a virtual network from an attribute set `{ machine1 =
+ # config1; ... machineN = configN; }', where `machineX' is the
+ # hostname and `configX' is a NixOS system configuration. Each
+ # machine is given an arbitrary IP address in the virtual network.
+ buildVirtualNetwork =
+ nodes: let nodesOut = mapAttrs (n: buildVM nodesOut) (assignIPAddresses nodes); in nodesOut;
+
+
+ buildVM =
+ nodes: configurations:
+
+ import ./eval-config.nix {
+ inherit system;
+ modules = configurations ++ extraConfigurations;
+ baseModules = (import ../modules/module-list.nix) ++
+ [ ../modules/virtualisation/qemu-vm.nix
+ ../modules/testing/test-instrumentation.nix # !!! should only get added for automated test runs
+ { key = "no-manual"; documentation.nixos.enable = false; }
+ { key = "qemu"; system.build.qemu = qemu; }
+ { key = "nodes"; _module.args.nodes = nodes; }
+ ] ++ optional minimal ../modules/testing/minimal-kernel.nix;
+ };
+
+
+ # Given an attribute set { machine1 = config1; ... machineN =
+ # configN; }, sequentially assign IP addresses in the 192.168.1.0/24
+ # range to each machine, and set the hostname to the attribute name.
+ assignIPAddresses = nodes:
+
+ let
+
+ machines = attrNames nodes;
+
+ machinesNumbered = zipLists machines (range 1 254);
+
+ nodes_ = forEach machinesNumbered (m: nameValuePair m.fst
+ [ ( { config, nodes, ... }:
+ let
+ interfacesNumbered = zipLists config.virtualisation.vlans (range 1 255);
+ interfaces = forEach interfacesNumbered ({ fst, snd }:
+ nameValuePair "eth${toString snd}" { ipv4.addresses =
+ [ { address = "192.168.${toString fst}.${toString m.snd}";
+ prefixLength = 24;
+ } ];
+ });
+ in
+ { key = "ip-address";
+ config =
+ { networking.hostName = mkDefault m.fst;
+
+ networking.interfaces = listToAttrs interfaces;
+
+ networking.primaryIPAddress =
+ optionalString (interfaces != []) (head (head interfaces).value.ipv4.addresses).address;
+
+ # Put the IP addresses of all VMs in this machine's
+ # /etc/hosts file. If a machine has multiple
+ # interfaces, use the IP address corresponding to
+ # the first interface (i.e. the first network in its
+ # virtualisation.vlans option).
+ networking.extraHosts = flip concatMapStrings machines
+ (m': let config = (getAttr m' nodes).config; in
+ optionalString (config.networking.primaryIPAddress != "")
+ ("${config.networking.primaryIPAddress} " +
+ optionalString (config.networking.domain != null)
+ "${config.networking.hostName}.${config.networking.domain} " +
+ "${config.networking.hostName}\n"));
+
+ virtualisation.qemu.options =
+ forEach interfacesNumbered
+ ({ fst, snd }: qemuNICFlags snd fst m.snd);
+ };
+ }
+ )
+ (getAttr m.fst nodes)
+ ] );
+
+ in listToAttrs nodes_;
+
+}
diff --git a/nixpkgs/nixos/lib/eval-config.nix b/nixpkgs/nixos/lib/eval-config.nix
new file mode 100644
index 00000000000..77490ca3762
--- /dev/null
+++ b/nixpkgs/nixos/lib/eval-config.nix
@@ -0,0 +1,67 @@
+# From an end-user configuration file (`configuration.nix'), build a NixOS
+# configuration object (`config') from which we can retrieve option
+# values.
+
+# !!! Please think twice before adding to this argument list!
+# Ideally eval-config.nix would be an extremely thin wrapper
+# around lib.evalModules, so that modular systems that have nixos configs
+# as subcomponents (e.g. the container feature, or nixops if network
+# expressions are ever made modular at the top level) can just use
+# types.submodule instead of using eval-config.nix
+{ # !!! system can be set modularly, would be nice to remove
+ system ? builtins.currentSystem
+, # !!! is this argument needed any more? The pkgs argument can
+ # be set modularly anyway.
+ pkgs ? null
+, # !!! what do we gain by making this configurable?
+ baseModules ? import ../modules/module-list.nix
+, # !!! See comment about args in lib/modules.nix
+ extraArgs ? {}
+, # !!! See comment about args in lib/modules.nix
+ specialArgs ? {}
+, modules
+, # !!! See comment about check in lib/modules.nix
+ check ? true
+, prefix ? []
+, lib ? import ../../lib
+}:
+
+let extraArgs_ = extraArgs; pkgs_ = pkgs;
+ extraModules = let e = builtins.getEnv "NIXOS_EXTRA_MODULE_PATH";
+ in if e == "" then [] else [(import e)];
+in
+
+let
+ pkgsModule = rec {
+ _file = ./eval-config.nix;
+ key = _file;
+ config = {
+ # Explicit `nixpkgs.system` or `nixpkgs.localSystem` should override
+ # this. Since the latter defaults to the former, the former should
+ # default to the argument. That way this new default could propagate all
+ # they way through, but has the last priority behind everything else.
+ nixpkgs.system = lib.mkDefault system;
+ _module.args.pkgs = lib.mkIf (pkgs_ != null) (lib.mkForce pkgs_);
+ };
+ };
+
+in rec {
+
+ # Merge the option definitions in all modules, forming the full
+ # system configuration.
+ inherit (lib.evalModules {
+ inherit prefix check;
+ modules = baseModules ++ extraModules ++ [ pkgsModule ] ++ modules;
+ args = extraArgs;
+ specialArgs =
+ { modulesPath = builtins.toString ../modules; } // specialArgs;
+ }) config options;
+
+ # These are the extra arguments passed to every module. In
+ # particular, Nixpkgs is passed through the "pkgs" argument.
+ extraArgs = extraArgs_ // {
+ inherit baseModules extraModules modules;
+ };
+
+ inherit (config._module.args) pkgs;
+}
diff --git a/nixpkgs/nixos/lib/from-env.nix b/nixpkgs/nixos/lib/from-env.nix
new file mode 100644
index 00000000000..6bd71e40e9a
--- /dev/null
+++ b/nixpkgs/nixos/lib/from-env.nix
@@ -0,0 +1,4 @@
+# TODO: remove this file. There is lib.maybeEnv now
+name: default:
+let value = builtins.getEnv name; in
+if value == "" then default else value
diff --git a/nixpkgs/nixos/lib/make-channel.nix b/nixpkgs/nixos/lib/make-channel.nix
new file mode 100644
index 00000000000..9b920b989fc
--- /dev/null
+++ b/nixpkgs/nixos/lib/make-channel.nix
@@ -0,0 +1,31 @@
+/* Build a channel tarball. These contain, in addition to the nixpkgs
+ * expressions themselves, files that indicate the version of nixpkgs
+ * that they represent.
+ */
+{ pkgs, nixpkgs, version, versionSuffix }:
+
+pkgs.releaseTools.makeSourceTarball {
+ name = "nixos-channel";
+
+ src = nixpkgs;
+
+ officialRelease = false; # FIXME: fix this in makeSourceTarball
+ inherit version versionSuffix;
+
+ buildInputs = [ pkgs.nix ];
+
+ distPhase = ''
+ rm -rf .git
+ echo -n $VERSION_SUFFIX > .version-suffix
+ echo -n ${nixpkgs.rev or nixpkgs.shortRev} > .git-revision
+ releaseName=nixos-$VERSION$VERSION_SUFFIX
+ mkdir -p $out/tarballs
+ cp -prd . ../$releaseName
+ chmod -R u+w ../$releaseName
+ ln -s . ../$releaseName/nixpkgs # hack to make ‘<nixpkgs>’ work
+ NIX_STATE_DIR=$TMPDIR nix-env -f ../$releaseName/default.nix -qaP --meta --xml \* > /dev/null
+ cd ..
+ chmod -R u+w $releaseName
+ tar cfJ $out/tarballs/$releaseName.tar.xz $releaseName
+ '';
+}
diff --git a/nixpkgs/nixos/lib/make-disk-image.nix b/nixpkgs/nixos/lib/make-disk-image.nix
new file mode 100644
index 00000000000..5e86ea479d5
--- /dev/null
+++ b/nixpkgs/nixos/lib/make-disk-image.nix
@@ -0,0 +1,247 @@
+{ pkgs
+, lib
+
+, # The NixOS configuration to be installed onto the disk image.
+ config
+
+, # The size of the disk, in megabytes.
+ diskSize
+
+ # The files and directories to be placed in the target file system.
+ # This is a list of attribute sets {source, target} where `source'
+ # is the file system object (regular file or directory) to be
+ # grafted in the file system at path `target'.
+, contents ? []
+
+, # Type of partition table to use; either "legacy", "efi", or "none".
+ # For "efi" images, the GPT partition table is used and a mandatory ESP
+ # partition of reasonable size is created in addition to the root partition.
+ # If `installBootLoader` is true, GRUB will be installed in EFI mode.
+ # For "legacy", the msdos partition table is used and a single large root
+ # partition is created. If `installBootLoader` is true, GRUB will be
+ # installed in legacy mode.
+ # For "none", no partition table is created. Enabling `installBootLoader`
+ # most likely fails as GRUB will probably refuse to install.
+ partitionTableType ? "legacy"
+
+, # The root file system type.
+ fsType ? "ext4"
+
+, # Filesystem label
+ label ? "nixos"
+
+, # The initial NixOS configuration file to be copied to
+ # /etc/nixos/configuration.nix.
+ configFile ? null
+
+, # Shell code executed after the VM has finished.
+ postVM ? ""
+
+, name ? "nixos-disk-image"
+
+, # Disk image format, one of qcow2, qcow2-compressed, vpc, raw.
+ format ? "raw"
+}:
+
+assert partitionTableType == "legacy" || partitionTableType == "efi" || partitionTableType == "none";
+# We use -E offset=X below, which is only supported by e2fsprogs
+assert partitionTableType != "none" -> fsType == "ext4";
+
+with lib;
+
+let format' = format; in let
+
+ format = if format' == "qcow2-compressed" then "qcow2" else format';
+
+ compress = optionalString (format' == "qcow2-compressed") "-c";
+
+ filename = "nixos." + {
+ qcow2 = "qcow2";
+ vpc = "vhd";
+ raw = "img";
+ }.${format};
+
+ rootPartition = { # switch-case
+ legacy = "1";
+ efi = "2";
+ }.${partitionTableType};
+
+ partitionDiskScript = { # switch-case
+ legacy = ''
+ parted --script $diskImage -- \
+ mklabel msdos \
+ mkpart primary ext4 1MiB -1
+ '';
+ efi = ''
+ parted --script $diskImage -- \
+ mklabel gpt \
+ mkpart ESP fat32 8MiB 256MiB \
+ set 1 boot on \
+ mkpart primary ext4 256MiB -1
+ '';
+ none = "";
+ }.${partitionTableType};
+
+ nixpkgs = cleanSource pkgs.path;
+
+ # FIXME: merge with channel.nix / make-channel.nix.
+ channelSources = pkgs.runCommand "nixos-${config.system.nixos.version}" {} ''
+ mkdir -p $out
+ cp -prd ${nixpkgs.outPath} $out/nixos
+ chmod -R u+w $out/nixos
+ if [ ! -e $out/nixos/nixpkgs ]; then
+ ln -s . $out/nixos/nixpkgs
+ fi
+ rm -rf $out/nixos/.git
+ echo -n ${config.system.nixos.versionSuffix} > $out/nixos/.version-suffix
+ '';
+
+ binPath = with pkgs; makeBinPath (
+ [ rsync
+ utillinux
+ parted
+ e2fsprogs
+ lkl
+ config.system.build.nixos-install
+ config.system.build.nixos-enter
+ nix
+ ] ++ stdenv.initialPath);
+
+ # I'm preserving the line below because I'm going to search for it across nixpkgs to consolidate
+ # image building logic. The comment right below this now appears in 4 different places in nixpkgs :)
+ # !!! should use XML.
+ sources = map (x: x.source) contents;
+ targets = map (x: x.target) contents;
+
+ closureInfo = pkgs.closureInfo { rootPaths = [ config.system.build.toplevel channelSources ]; };
+
+ prepareImage = ''
+ export PATH=${binPath}
+
+ # Yes, mkfs.ext4 takes different units in different contexts. Fun.
+ sectorsToKilobytes() {
+ echo $(( ( "$1" * 512 ) / 1024 ))
+ }
+
+ sectorsToBytes() {
+ echo $(( "$1" * 512 ))
+ }
+
+ mkdir $out
+ diskImage=nixos.raw
+ truncate -s ${toString diskSize}M $diskImage
+
+ ${partitionDiskScript}
+
+ ${if partitionTableType != "none" then ''
+ # Get start & length of the root partition in sectors to $START and $SECTORS.
+ eval $(partx $diskImage -o START,SECTORS --nr ${rootPartition} --pairs)
+
+ mkfs.${fsType} -F -L ${label} $diskImage -E offset=$(sectorsToBytes $START) $(sectorsToKilobytes $SECTORS)K
+ '' else ''
+ mkfs.${fsType} -F -L ${label} $diskImage
+ ''}
+
+ root="$PWD/root"
+ mkdir -p $root
+
+ # Copy arbitrary other files into the image
+ # Semi-shamelessly copied from make-etc.sh. I (@copumpkin) shall factor this stuff out as part of
+ # https://github.com/NixOS/nixpkgs/issues/23052.
+ set -f
+ sources_=(${concatStringsSep " " sources})
+ targets_=(${concatStringsSep " " targets})
+ set +f
+
+ for ((i = 0; i < ''${#targets_[@]}; i++)); do
+ source="''${sources_[$i]}"
+ target="''${targets_[$i]}"
+
+ if [[ "$source" =~ '*' ]]; then
+ # If the source name contains '*', perform globbing.
+ mkdir -p $root/$target
+ for fn in $source; do
+ rsync -a --no-o --no-g "$fn" $root/$target/
+ done
+ else
+ mkdir -p $root/$(dirname $target)
+ if ! [ -e $root/$target ]; then
+ rsync -a --no-o --no-g $source $root/$target
+ else
+ echo "duplicate entry $target -> $source"
+ exit 1
+ fi
+ fi
+ done
+
+ export HOME=$TMPDIR
+
+ # Provide a Nix database so that nixos-install can copy closures.
+ export NIX_STATE_DIR=$TMPDIR/state
+ nix-store --load-db < ${closureInfo}/registration
+
+ echo "running nixos-install..."
+ nixos-install --root $root --no-bootloader --no-root-passwd \
+ --system ${config.system.build.toplevel} --channel ${channelSources} --substituters ""
+
+ echo "copying staging root to image..."
+ cptofs -p ${optionalString (partitionTableType != "none") "-P ${rootPartition}"} -t ${fsType} -i $diskImage $root/* /
+ '';
+in pkgs.vmTools.runInLinuxVM (
+ pkgs.runCommand name
+ { preVM = prepareImage;
+ buildInputs = with pkgs; [ utillinux e2fsprogs dosfstools ];
+ postVM = ''
+ ${if format == "raw" then ''
+ mv $diskImage $out/${filename}
+ '' else ''
+ ${pkgs.qemu}/bin/qemu-img convert -f raw -O ${format} ${compress} $diskImage $out/${filename}
+ ''}
+ diskImage=$out/${filename}
+ ${postVM}
+ '';
+ memSize = 1024;
+ }
+ ''
+ export PATH=${binPath}:$PATH
+
+ rootDisk=${if partitionTableType != "none" then "/dev/vda${rootPartition}" else "/dev/vda"}
+
+ # Some tools assume these exist
+ ln -s vda /dev/xvda
+ ln -s vda /dev/sda
+
+ mountPoint=/mnt
+ mkdir $mountPoint
+ mount $rootDisk $mountPoint
+
+ # Create the ESP and mount it. Unlike e2fsprogs, mkfs.vfat doesn't support an
+ # '-E offset=X' option, so we can't do this outside the VM.
+ ${optionalString (partitionTableType == "efi") ''
+ mkdir -p /mnt/boot
+ mkfs.vfat -n ESP /dev/vda1
+ mount /dev/vda1 /mnt/boot
+ ''}
+
+ # Install a configuration.nix
+ mkdir -p /mnt/etc/nixos
+ ${optionalString (configFile != null) ''
+ cp ${configFile} /mnt/etc/nixos/configuration.nix
+ ''}
+
+ # Set up core system link, GRUB, etc.
+ NIXOS_INSTALL_BOOTLOADER=1 nixos-enter --root $mountPoint -- /nix/var/nix/profiles/system/bin/switch-to-configuration boot
+
+ # The above scripts will generate a random machine-id and we don't want to bake a single ID into all our images
+ rm -f $mountPoint/etc/machine-id
+
+ umount -R /mnt
+
+ # Make sure resize2fs works. Note that resize2fs has stricter criteria for resizing than a normal
+ # mount, so the `-c 0` and `-i 0` don't affect it. Setting it to `now` doesn't produce deterministic
+ # output, of course, but we can fix that when/if we start making images deterministic.
+ ${optionalString (fsType == "ext4") ''
+ tune2fs -T now -c 0 -i 0 $rootDisk
+ ''}
+ ''
+)
diff --git a/nixpkgs/nixos/lib/make-ext4-fs.nix b/nixpkgs/nixos/lib/make-ext4-fs.nix
new file mode 100644
index 00000000000..932adcd9796
--- /dev/null
+++ b/nixpkgs/nixos/lib/make-ext4-fs.nix
@@ -0,0 +1,91 @@
+# Builds an ext4 image containing a populated /nix/store with the closure
+# of store paths passed in the storePaths parameter, in addition to the
+# contents of a directory that can be populated with commands. The
+# generated image is sized to only fit its contents, with the expectation
+# that a script resizes the filesystem at boot time.
+{ pkgs
+# List of derivations to be included
+, storePaths
+# Shell commands to populate the ./files directory.
+# All files in that directory are copied to the root of the FS.
+, populateImageCommands ? ""
+, volumeLabel
+, uuid ? "44444444-4444-4444-8888-888888888888"
+, e2fsprogs
+, libfaketime
+, perl
+, lkl
+}:
+
+let
+ sdClosureInfo = pkgs.buildPackages.closureInfo { rootPaths = storePaths; };
+in
+
+pkgs.stdenv.mkDerivation {
+ name = "ext4-fs.img";
+
+ nativeBuildInputs = [e2fsprogs.bin libfaketime perl lkl];
+
+ buildCommand =
+ ''
+ (
+ mkdir -p ./files
+ ${populateImageCommands}
+ )
+ # Add the closures of the top-level store objects.
+ storePaths=$(cat ${sdClosureInfo}/store-paths)
+
+ # Make a crude approximation of the size of the target image.
+ # If the script starts failing, increase the fudge factors here.
+ numInodes=$(find $storePaths ./files | wc -l)
+ numDataBlocks=$(du -s -c -B 4096 --apparent-size $storePaths ./files | tail -1 | awk '{ print int($1 * 1.03) }')
+ bytes=$((2 * 4096 * $numInodes + 4096 * $numDataBlocks))
+ echo "Creating an EXT4 image of $bytes bytes (numInodes=$numInodes, numDataBlocks=$numDataBlocks)"
+
+ truncate -s $bytes $out
+ faketime -f "1970-01-01 00:00:01" mkfs.ext4 -L ${volumeLabel} -U ${uuid} $out
+
+ # Also include a manifest of the closures in a format suitable for nix-store --load-db.
+ cp ${sdClosureInfo}/registration nix-path-registration
+ cptofs -t ext4 -i $out nix-path-registration /
+
+ # Create nix/store before copying paths
+ faketime -f "1970-01-01 00:00:01" mkdir -p nix/store
+ cptofs -t ext4 -i $out nix /
+
+ echo "copying store paths to image..."
+ cptofs -t ext4 -i $out $storePaths /nix/store/
+
+ (
+ echo "copying files to image..."
+ cd ./files
+ cptofs -t ext4 -i $out ./* /
+ )
+
+ # I have ended up with corrupted images sometimes, I suspect that happens when the build machine's disk gets full during the build.
+ if ! fsck.ext4 -n -f $out; then
+ echo "--- Fsck failed for EXT4 image of $bytes bytes (numInodes=$numInodes, numDataBlocks=$numDataBlocks) ---"
+ cat errorlog
+ return 1
+ fi
+
+ (
+ # Resizes **snugly** to its actual limits (or closer to)
+ free=$(dumpe2fs $out | grep '^Free blocks:')
+ blocksize=$(dumpe2fs $out | grep '^Block size:')
+ blocks=$(dumpe2fs $out | grep '^Block count:')
+ blocks=$((''${blocks##*:})) # format the number.
+ blocksize=$((''${blocksize##*:})) # format the number.
+ # System can't boot with 0 blocks free.
+ # Add 16MiB of free space
+ fudge=$(( 16 * 1024 * 1024 / blocksize ))
+ size=$(( blocks - ''${free##*:} + fudge ))
+
+ echo "Resizing from $blocks blocks to $size blocks. (~ $((size*blocksize/1024/1024))MiB)"
+ EXT2FS_NO_MTAB_OK=yes resize2fs $out -f $size
+ )
+
+ # And a final fsck, because of the previous truncating.
+ fsck.ext4 -n -f $out
+ '';
+}
diff --git a/nixpkgs/nixos/lib/make-iso9660-image.nix b/nixpkgs/nixos/lib/make-iso9660-image.nix
new file mode 100644
index 00000000000..8cd19b6e187
--- /dev/null
+++ b/nixpkgs/nixos/lib/make-iso9660-image.nix
@@ -0,0 +1,65 @@
+{ stdenv, closureInfo, xorriso, syslinux
+
+, # The file name of the resulting ISO image.
+ isoName ? "cd.iso"
+
+, # The files and directories to be placed in the ISO file system.
+ # This is a list of attribute sets {source, target} where `source'
+ # is the file system object (regular file or directory) to be
+ # grafted in the file system at path `target'.
+ contents
+
+, # In addition to `contents', the closure of the store paths listed
+ # in `packages' are also placed in the Nix store of the CD. This is
+ # a list of attribute sets {object, symlink} where `object' if a
+ # store path whose closure will be copied, and `symlink' is a
+ # symlink to `object' that will be added to the CD.
+ storeContents ? []
+
+, # Whether this should be an El-Torito bootable CD.
+ bootable ? false
+
+, # Whether this should be an efi-bootable El-Torito CD.
+ efiBootable ? false
+
+, # Whether this should be an hybrid CD (bootable from USB as well as CD).
+ usbBootable ? false
+
+, # The path (in the ISO file system) of the boot image.
+ bootImage ? ""
+
+, # The path (in the ISO file system) of the efi boot image.
+ efiBootImage ? ""
+
+, # The path (outside the ISO file system) of the isohybrid-mbr image.
+ isohybridMbrImage ? ""
+
+, # Whether to compress the resulting ISO image with bzip2.
+ compressImage ? false
+
+, # The volume ID.
+ volumeID ? ""
+}:
+
+assert bootable -> bootImage != "";
+assert efiBootable -> efiBootImage != "";
+assert usbBootable -> isohybridMbrImage != "";
+
+stdenv.mkDerivation {
+ name = isoName;
+ builder = ./make-iso9660-image.sh;
+ buildInputs = [ xorriso syslinux ];
+
+ inherit isoName bootable bootImage compressImage volumeID efiBootImage efiBootable isohybridMbrImage usbBootable;
+
+ # !!! should use XML.
+ sources = map (x: x.source) contents;
+ targets = map (x: x.target) contents;
+
+ # !!! should use XML.
+ objects = map (x: x.object) storeContents;
+ symlinks = map (x: x.symlink) storeContents;
+
+ # For obtaining the closure of `storeContents'.
+ closureInfo = closureInfo { rootPaths = map (x: x.object) storeContents; };
+}
diff --git a/nixpkgs/nixos/lib/make-iso9660-image.sh b/nixpkgs/nixos/lib/make-iso9660-image.sh
new file mode 100644
index 00000000000..b7b1ab52a63
--- /dev/null
+++ b/nixpkgs/nixos/lib/make-iso9660-image.sh
@@ -0,0 +1,136 @@
+source $stdenv/setup
+
+sources_=($sources)
+targets_=($targets)
+
+objects=($objects)
+symlinks=($symlinks)
+
+
+# Remove the initial slash from a path, since genisofs likes it that way.
+stripSlash() {
+ res="$1"
+ if test "${res:0:1}" = /; then res=${res:1}; fi
+}
+
+# Escape potential equal signs (=) with backslash (\=)
+escapeEquals() {
+ echo "$1" | sed -e 's/\\/\\\\/g' -e 's/=/\\=/g'
+}
+
+# Queues an file/directory to be placed on the ISO.
+# An entry consists of a local source path (2) and
+# a destination path on the ISO (1).
+addPath() {
+ target="$1"
+ source="$2"
+ echo "$(escapeEquals "$target")=$(escapeEquals "$source")" >> pathlist
+}
+
+stripSlash "$bootImage"; bootImage="$res"
+
+
+if test -n "$bootable"; then
+
+ # The -boot-info-table option modifies the $bootImage file, so
+ # find it in `contents' and make a copy of it (since the original
+ # is read-only in the Nix store...).
+ for ((i = 0; i < ${#targets_[@]}; i++)); do
+ stripSlash "${targets_[$i]}"
+ if test "$res" = "$bootImage"; then
+ echo "copying the boot image ${sources_[$i]}"
+ cp "${sources_[$i]}" boot.img
+ chmod u+w boot.img
+ sources_[$i]=boot.img
+ fi
+ done
+
+ isoBootFlags="-eltorito-boot ${bootImage}
+ -eltorito-catalog .boot.cat
+ -no-emul-boot -boot-load-size 4 -boot-info-table
+ --sort-weight 1 /isolinux" # Make sure isolinux is near the beginning of the ISO
+fi
+
+if test -n "$usbBootable"; then
+ usbBootFlags="-isohybrid-mbr ${isohybridMbrImage}"
+fi
+
+if test -n "$efiBootable"; then
+ efiBootFlags="-eltorito-alt-boot
+ -e $efiBootImage
+ -no-emul-boot
+ -isohybrid-gpt-basdat"
+fi
+
+touch pathlist
+
+
+# Add the individual files.
+for ((i = 0; i < ${#targets_[@]}; i++)); do
+ stripSlash "${targets_[$i]}"
+ addPath "$res" "${sources_[$i]}"
+done
+
+
+# Add the closures of the top-level store objects.
+for i in $(< $closureInfo/store-paths); do
+ addPath "${i:1}" "$i"
+done
+
+
+# Also include a manifest of the closures in a format suitable for
+# nix-store --load-db.
+if [[ ${#objects[*]} != 0 ]]; then
+ cp $closureInfo/registration nix-path-registration
+ addPath "nix-path-registration" "nix-path-registration"
+fi
+
+
+# Add symlinks to the top-level store objects.
+for ((n = 0; n < ${#objects[*]}; n++)); do
+ object=${objects[$n]}
+ symlink=${symlinks[$n]}
+ if test "$symlink" != "none"; then
+ mkdir -p $(dirname ./$symlink)
+ ln -s $object ./$symlink
+ addPath "$symlink" "./$symlink"
+ fi
+done
+
+mkdir -p $out/iso
+
+xorriso="xorriso
+ -as mkisofs
+ -iso-level 3
+ -volid ${volumeID}
+ -appid nixos
+ -publisher nixos
+ -graft-points
+ -full-iso9660-filenames
+ ${isoBootFlags}
+ ${usbBootFlags}
+ ${efiBootFlags}
+ -r
+ -path-list pathlist
+ --sort-weight 0 /
+"
+
+$xorriso -output $out/iso/$isoName
+
+if test -n "$usbBootable"; then
+ echo "Making image hybrid..."
+ if test -n "$efiBootable"; then
+ isohybrid --uefi $out/iso/$isoName
+ else
+ isohybrid $out/iso/$isoName
+ fi
+fi
+
+if test -n "$compressImage"; then
+ echo "Compressing image..."
+ bzip2 $out/iso/$isoName
+fi
+
+mkdir -p $out/nix-support
+echo $system > $out/nix-support/system
+echo "file iso $out/iso/$isoName" >> $out/nix-support/hydra-build-products
diff --git a/nixpkgs/nixos/lib/make-options-doc/default.nix b/nixpkgs/nixos/lib/make-options-doc/default.nix
new file mode 100644
index 00000000000..35c8b543dec
--- /dev/null
+++ b/nixpkgs/nixos/lib/make-options-doc/default.nix
@@ -0,0 +1,164 @@
+/* Generate JSON, XML and DocBook documentation for given NixOS options.
+
+ Minimal example:
+
+ { pkgs, }:
+
+ let
+ eval = import (pkgs.path + "/nixos/lib/eval-config.nix") {
+ baseModules = [
+ ../module.nix
+ ];
+ modules = [];
+ };
+ in pkgs.nixosOptionsDoc {
+ options = eval.options;
+ }
+
+*/
+{ pkgs
+, lib
+, options
+, transformOptions ? lib.id # function for additional tranformations of the options
+, revision ? "" # Specify revision for the options
+}:
+
+let
+ # Replace functions by the string <function>
+ substFunction = x:
+ if builtins.isAttrs x then lib.mapAttrs (name: substFunction) x
+ else if builtins.isList x then map substFunction x
+ else if lib.isFunction x then "<function>"
+ else x;
+
+ optionsListDesc = lib.flip map optionsListVisible
+ (opt: transformOptions opt
+ // lib.optionalAttrs (opt ? example) { example = substFunction opt.example; }
+ // lib.optionalAttrs (opt ? default) { default = substFunction opt.default; }
+ // lib.optionalAttrs (opt ? type) { type = substFunction opt.type; }
+ // lib.optionalAttrs (opt ? relatedPackages && opt.relatedPackages != []) { relatedPackages = genRelatedPackages opt.relatedPackages; }
+ );
+
+ # Generate DocBook documentation for a list of packages. This is
+ # what `relatedPackages` option of `mkOption` from
+ # ../../../lib/options.nix influences.
+ #
+ # Each element of `relatedPackages` can be either
+ # - a string: that will be interpreted as an attribute name from `pkgs`,
+ # - a list: that will be interpreted as an attribute path from `pkgs`,
+ # - an attrset: that can specify `name`, `path`, `package`, `comment`
+ # (either of `name`, `path` is required, the rest are optional).
+ genRelatedPackages = packages:
+ let
+ unpack = p: if lib.isString p then { name = p; }
+ else if lib.isList p then { path = p; }
+ else p;
+ describe = args:
+ let
+ title = args.title or null;
+ name = args.name or (lib.concatStringsSep "." args.path);
+ path = args.path or [ args.name ];
+ package = args.package or (lib.attrByPath path (throw "Invalid package attribute path `${toString path}'") pkgs);
+ in "<listitem>"
+ + "<para><literal>${lib.optionalString (title != null) "${title} aka "}pkgs.${name} (${package.meta.name})</literal>"
+ + lib.optionalString (!package.meta.available) " <emphasis>[UNAVAILABLE]</emphasis>"
+ + ": ${package.meta.description or "???"}.</para>"
+ + lib.optionalString (args ? comment) "\n<para>${args.comment}</para>"
+ # Lots of `longDescription's break DocBook, so we just wrap them into <programlisting>
+ + lib.optionalString (package.meta ? longDescription) "\n<programlisting>${package.meta.longDescription}</programlisting>"
+ + "</listitem>";
+ in "<itemizedlist>${lib.concatStringsSep "\n" (map (p: describe (unpack p)) packages)}</itemizedlist>";
+
+ # Custom "less" that pushes up all the things ending in ".enable*"
+ # and ".package*"
+ optionLess = a: b:
+ let
+ ise = lib.hasPrefix "enable";
+ isp = lib.hasPrefix "package";
+ cmp = lib.splitByAndCompare ise lib.compare
+ (lib.splitByAndCompare isp lib.compare lib.compare);
+ in lib.compareLists cmp a.loc b.loc < 0;
+
+ # Remove invisible and internal options.
+ optionsListVisible = lib.filter (opt: opt.visible && !opt.internal) (lib.optionAttrSetToDocList options);
+
+ # Customly sort option list for the man page.
+ optionsList = lib.sort optionLess optionsListDesc;
+
+ # Convert the list of options into an XML file.
+ optionsXML = builtins.toFile "options.xml" (builtins.toXML optionsList);
+
+ optionsNix = builtins.listToAttrs (map (o: { name = o.name; value = removeAttrs o ["name" "visible" "internal"]; }) optionsList);
+
+ # TODO: declarations: link to github
+ singleAsciiDoc = name: value: ''
+ == ${name}
+
+ ${value.description}
+
+ [discrete]
+ === details
+
+ Type:: ${value.type}
+ ${ if lib.hasAttr "default" value
+ then ''
+ Default::
+ +
+ ----
+ ${builtins.toJSON value.default}
+ ----
+ ''
+ else "No Default:: {blank}"
+ }
+ ${ if value.readOnly
+ then "Read Only:: {blank}"
+ else ""
+ }
+ ${ if lib.hasAttr "example" value
+ then ''
+ Example::
+ +
+ ----
+ ${builtins.toJSON value.example}
+ ----
+ ''
+ else "No Example:: {blank}"
+ }
+ '';
+
+in {
+ inherit optionsNix;
+
+ optionsAsciiDoc = lib.concatStringsSep "\n" (lib.mapAttrsToList singleAsciiDoc optionsNix);
+
+ optionsJSON = pkgs.runCommand "options.json"
+ { meta.description = "List of NixOS options in JSON format";
+ }
+ ''
+ # Export list of options in different format.
+ dst=$out/share/doc/nixos
+ mkdir -p $dst
+
+ cp ${builtins.toFile "options.json" (builtins.unsafeDiscardStringContext (builtins.toJSON optionsNix))} $dst/options.json
+
+ mkdir -p $out/nix-support
+ echo "file json $dst/options.json" >> $out/nix-support/hydra-build-products
+ ''; # */
+
+ optionsDocBook = pkgs.runCommand "options-docbook.xml" {} ''
+ optionsXML=${optionsXML}
+ if grep /nixpkgs/nixos/modules $optionsXML; then
+ echo "The manual appears to depend on the location of Nixpkgs, which is bad"
+ echo "since this prevents sharing via the NixOS channel. This is typically"
+ echo "caused by an option default that refers to a relative path (see above"
+ echo "for hints about the offending path)."
+ exit 1
+ fi
+
+ ${pkgs.libxslt.bin}/bin/xsltproc \
+ --stringparam revision '${revision}' \
+ -o intermediate.xml ${./options-to-docbook.xsl} $optionsXML
+ ${pkgs.libxslt.bin}/bin/xsltproc \
+ -o "$out" ${./postprocess-option-descriptions.xsl} intermediate.xml
+ '';
+}
diff --git a/nixpkgs/nixos/lib/make-options-doc/options-to-docbook.xsl b/nixpkgs/nixos/lib/make-options-doc/options-to-docbook.xsl
new file mode 100644
index 00000000000..72ac89d4ff6
--- /dev/null
+++ b/nixpkgs/nixos/lib/make-options-doc/options-to-docbook.xsl
@@ -0,0 +1,236 @@
+<?xml version="1.0"?>
+
+<xsl:stylesheet version="1.0"
+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:str="http://exslt.org/strings"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:nixos="tag:nixos.org"
+ xmlns="http://docbook.org/ns/docbook"
+ extension-element-prefixes="str"
+ >
+
+ <xsl:output method='xml' encoding="UTF-8" />
+
+ <xsl:param name="revision" />
+ <xsl:param name="program" />
+
+
+ <xsl:template match="/expr/list">
+ <appendix xml:id="appendix-configuration-options">
+ <title>Configuration Options</title>
+ <variablelist xml:id="configuration-variable-list">
+ <xsl:for-each select="attrs">
+ <xsl:variable name="id" select="concat('opt-', str:replace(str:replace(str:replace(str:replace(attr[@name = 'name']/string/@value, '*', '_'), '&lt;', '_'), '>', '_'), '?', '_'))" />
+ <varlistentry>
+ <term xlink:href="#{$id}">
+ <xsl:attribute name="xml:id"><xsl:value-of select="$id"/></xsl:attribute>
+ <option>
+ <xsl:value-of select="attr[@name = 'name']/string/@value" />
+ </option>
+ </term>
+
+ <listitem>
+
+ <nixos:option-description>
+ <para>
+ <xsl:value-of disable-output-escaping="yes"
+ select="attr[@name = 'description']/string/@value" />
+ </para>
+ </nixos:option-description>
+
+ <xsl:if test="attr[@name = 'type']">
+ <para>
+ <emphasis>Type:</emphasis>
+ <xsl:text> </xsl:text>
+ <xsl:value-of select="attr[@name = 'type']/string/@value"/>
+ <xsl:if test="attr[@name = 'readOnly']/bool/@value = 'true'">
+ <xsl:text> </xsl:text>
+ <emphasis>(read only)</emphasis>
+ </xsl:if>
+ </para>
+ </xsl:if>
+
+ <xsl:if test="attr[@name = 'default']">
+ <para>
+ <emphasis>Default:</emphasis>
+ <xsl:text> </xsl:text>
+ <xsl:apply-templates select="attr[@name = 'default']" mode="top" />
+ </para>
+ </xsl:if>
+
+ <xsl:if test="attr[@name = 'example']">
+ <para>
+ <emphasis>Example:</emphasis>
+ <xsl:text> </xsl:text>
+ <xsl:choose>
+ <xsl:when test="attr[@name = 'example']/attrs[attr[@name = '_type' and string[@value = 'literalExample']]]">
+ <programlisting><xsl:value-of select="attr[@name = 'example']/attrs/attr[@name = 'text']/string/@value" /></programlisting>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:apply-templates select="attr[@name = 'example']" mode="top" />
+ </xsl:otherwise>
+ </xsl:choose>
+ </para>
+ </xsl:if>
+
+ <xsl:if test="attr[@name = 'relatedPackages']">
+ <para>
+ <emphasis>Related packages:</emphasis>
+ <xsl:text> </xsl:text>
+ <xsl:value-of disable-output-escaping="yes"
+ select="attr[@name = 'relatedPackages']/string/@value" />
+ </para>
+ </xsl:if>
+
+ <xsl:if test="count(attr[@name = 'declarations']/list/*) != 0">
+ <para>
+ <emphasis>Declared by:</emphasis>
+ </para>
+ <xsl:apply-templates select="attr[@name = 'declarations']" />
+ </xsl:if>
+
+ <xsl:if test="count(attr[@name = 'definitions']/list/*) != 0">
+ <para>
+ <emphasis>Defined by:</emphasis>
+ </para>
+ <xsl:apply-templates select="attr[@name = 'definitions']" />
+ </xsl:if>
+
+ </listitem>
+
+ </varlistentry>
+
+ </xsl:for-each>
+
+ </variablelist>
+ </appendix>
+ </xsl:template>
+
+
+ <xsl:template match="*" mode="top">
+ <xsl:choose>
+ <xsl:when test="string[contains(@value, '&#010;')]">
+<programlisting>
+<xsl:text>''
+</xsl:text><xsl:value-of select='str:replace(string/@value, "${", "&apos;&apos;${")' /><xsl:text>''</xsl:text></programlisting>
+ </xsl:when>
+ <xsl:otherwise>
+ <literal><xsl:apply-templates /></literal>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:template>
+
+
+ <xsl:template match="null">
+ <xsl:text>null</xsl:text>
+ </xsl:template>
+
+
+ <xsl:template match="string">
+ <xsl:choose>
+ <xsl:when test="(contains(@value, '&quot;') or contains(@value, '\')) and not(contains(@value, '&#010;'))">
+ <xsl:text>''</xsl:text><xsl:value-of select='str:replace(@value, "${", "&apos;&apos;${")' /><xsl:text>''</xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:text>"</xsl:text><xsl:value-of select="str:replace(str:replace(str:replace(str:replace(@value, '\', '\\'), '&quot;', '\&quot;'), '&#010;', '\n'), '$', '\$')" /><xsl:text>"</xsl:text>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:template>
+
+
+ <xsl:template match="int">
+ <xsl:value-of select="@value" />
+ </xsl:template>
+
+
+ <xsl:template match="bool[@value = 'true']">
+ <xsl:text>true</xsl:text>
+ </xsl:template>
+
+
+ <xsl:template match="bool[@value = 'false']">
+ <xsl:text>false</xsl:text>
+ </xsl:template>
+
+
+ <xsl:template match="list">
+ [
+ <xsl:for-each select="*">
+ <xsl:apply-templates select="." />
+ <xsl:text> </xsl:text>
+ </xsl:for-each>
+ ]
+ </xsl:template>
+
+
+ <xsl:template match="attrs[attr[@name = '_type' and string[@value = 'literalExample']]]">
+ <xsl:value-of select="attr[@name = 'text']/string/@value" />
+ </xsl:template>
+
+
+ <xsl:template match="attrs">
+ {
+ <xsl:for-each select="attr">
+ <xsl:value-of select="@name" />
+ <xsl:text> = </xsl:text>
+ <xsl:apply-templates select="*" /><xsl:text>; </xsl:text>
+ </xsl:for-each>
+ }
+ </xsl:template>
+
+
+ <xsl:template match="derivation">
+ <replaceable>(build of <xsl:value-of select="attr[@name = 'name']/string/@value" />)</replaceable>
+ </xsl:template>
+
+ <xsl:template match="attr[@name = 'declarations' or @name = 'definitions']">
+ <simplelist>
+ <xsl:for-each select="list/string">
+ <member><filename>
+ <!-- Hyperlink the filename either to the NixOS Subversion
+ repository (if it’s a module and we have a revision number),
+ or to the local filesystem. -->
+ <xsl:choose>
+ <xsl:when test="not(starts-with(@value, '/'))">
+ <xsl:choose>
+ <xsl:when test="$revision = 'local'">
+ <xsl:attribute name="xlink:href">https://github.com/NixOS/nixpkgs/blob/master/<xsl:value-of select="@value"/></xsl:attribute>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:attribute name="xlink:href">https://github.com/NixOS/nixpkgs/blob/<xsl:value-of select="$revision"/>/<xsl:value-of select="@value"/></xsl:attribute>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:when>
+ <xsl:when test="$revision != 'local' and $program = 'nixops' and contains(@value, '/nix/')">
+ <xsl:attribute name="xlink:href">https://github.com/NixOS/nixops/blob/<xsl:value-of select="$revision"/>/nix/<xsl:value-of select="substring-after(@value, '/nix/')"/></xsl:attribute>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:attribute name="xlink:href">file://<xsl:value-of select="@value"/></xsl:attribute>
+ </xsl:otherwise>
+ </xsl:choose>
+ <!-- Print the filename and make it user-friendly by replacing the
+ /nix/store/<hash> prefix by the default location of nixos
+ sources. -->
+ <xsl:choose>
+ <xsl:when test="not(starts-with(@value, '/'))">
+ &lt;nixpkgs/<xsl:value-of select="@value"/>&gt;
+ </xsl:when>
+ <xsl:when test="contains(@value, 'nixops') and contains(@value, '/nix/')">
+ &lt;nixops/<xsl:value-of select="substring-after(@value, '/nix/')"/>&gt;
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="@value" />
+ </xsl:otherwise>
+ </xsl:choose>
+ </filename></member>
+ </xsl:for-each>
+ </simplelist>
+ </xsl:template>
+
+
+ <xsl:template match="function">
+ <xsl:text>λ</xsl:text>
+ </xsl:template>
+
+
+</xsl:stylesheet>
diff --git a/nixpkgs/nixos/lib/make-options-doc/postprocess-option-descriptions.xsl b/nixpkgs/nixos/lib/make-options-doc/postprocess-option-descriptions.xsl
new file mode 100644
index 00000000000..1201c7612c2
--- /dev/null
+++ b/nixpkgs/nixos/lib/make-options-doc/postprocess-option-descriptions.xsl
@@ -0,0 +1,115 @@
+<?xml version="1.0"?>
+
+<xsl:stylesheet version="1.0"
+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:str="http://exslt.org/strings"
+ xmlns:exsl="http://exslt.org/common"
+ xmlns:db="http://docbook.org/ns/docbook"
+ xmlns:nixos="tag:nixos.org"
+ extension-element-prefixes="str exsl">
+ <xsl:output method='xml' encoding="UTF-8" />
+
+ <xsl:template match="@*|node()">
+ <xsl:copy>
+ <xsl:apply-templates select="@*|node()" />
+ </xsl:copy>
+ </xsl:template>
+
+ <xsl:template name="break-up-description">
+ <xsl:param name="input" />
+ <xsl:param name="buffer" />
+
+ <!-- Every time we have two newlines following each other, we want to
+ break it into </para><para>. -->
+ <xsl:variable name="parbreak" select="'&#xa;&#xa;'" />
+
+ <!-- Similar to "(head:tail) = input" in Haskell. -->
+ <xsl:variable name="head" select="$input[1]" />
+ <xsl:variable name="tail" select="$input[position() &gt; 1]" />
+
+ <xsl:choose>
+ <xsl:when test="$head/self::text() and contains($head, $parbreak)">
+ <!-- If the haystack provided to str:split() directly starts or
+ ends with $parbreak, it doesn't generate a <token/> for that,
+ so we are doing this here. -->
+ <xsl:variable name="splitted-raw">
+ <xsl:if test="starts-with($head, $parbreak)"><token /></xsl:if>
+ <xsl:for-each select="str:split($head, $parbreak)">
+ <token><xsl:value-of select="node()" /></token>
+ </xsl:for-each>
+ <!-- Something like ends-with($head, $parbreak), but there is
+ no ends-with() in XSLT, so we need to use substring(). -->
+ <xsl:if test="
+ substring($head, string-length($head) -
+ string-length($parbreak) + 1) = $parbreak
+ "><token /></xsl:if>
+ </xsl:variable>
+ <xsl:variable name="splitted"
+ select="exsl:node-set($splitted-raw)/token" />
+ <!-- The buffer we had so far didn't contain any text nodes that
+ contain a $parbreak, so we can put the buffer along with the
+ first token of $splitted into a para element. -->
+ <para xmlns="http://docbook.org/ns/docbook">
+ <xsl:apply-templates select="exsl:node-set($buffer)" />
+ <xsl:apply-templates select="$splitted[1]/node()" />
+ </para>
+ <!-- We have already emitted the first splitted result, so the
+ last result is going to be set as the new $buffer later
+ because its contents may not be directly followed up by a
+ $parbreak. -->
+ <xsl:for-each select="$splitted[position() &gt; 1
+ and position() &lt; last()]">
+ <para xmlns="http://docbook.org/ns/docbook">
+ <xsl:apply-templates select="node()" />
+ </para>
+ </xsl:for-each>
+ <xsl:call-template name="break-up-description">
+ <xsl:with-param name="input" select="$tail" />
+ <xsl:with-param name="buffer" select="$splitted[last()]/node()" />
+ </xsl:call-template>
+ </xsl:when>
+ <!-- Either non-text node or one without $parbreak, which we just
+ want to buffer and continue recursing. -->
+ <xsl:when test="$input">
+ <xsl:call-template name="break-up-description">
+ <xsl:with-param name="input" select="$tail" />
+ <!-- This essentially appends $head to $buffer. -->
+ <xsl:with-param name="buffer">
+ <xsl:if test="$buffer">
+ <xsl:for-each select="exsl:node-set($buffer)">
+ <xsl:apply-templates select="." />
+ </xsl:for-each>
+ </xsl:if>
+ <xsl:apply-templates select="$head" />
+ </xsl:with-param>
+ </xsl:call-template>
+ </xsl:when>
+ <!-- No more $input, just put the remaining $buffer in a para. -->
+ <xsl:otherwise>
+ <para xmlns="http://docbook.org/ns/docbook">
+ <xsl:apply-templates select="exsl:node-set($buffer)" />
+ </para>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:template>
+
+ <xsl:template match="nixos:option-description">
+ <xsl:choose>
+ <!--
+ Only process nodes that are comprised of a single <para/> element,
+ because if that's not the case the description already contains
+ </para><para> in between and we need no further processing.
+ -->
+ <xsl:when test="count(db:para) > 1">
+ <xsl:apply-templates select="node()" />
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:call-template name="break-up-description">
+ <xsl:with-param name="input"
+ select="exsl:node-set(db:para/node())" />
+ </xsl:call-template>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:template>
+
+</xsl:stylesheet>
diff --git a/nixpkgs/nixos/lib/make-squashfs.nix b/nixpkgs/nixos/lib/make-squashfs.nix
new file mode 100644
index 00000000000..ee76c9c5bf2
--- /dev/null
+++ b/nixpkgs/nixos/lib/make-squashfs.nix
@@ -0,0 +1,28 @@
+{ stdenv, squashfsTools, closureInfo
+
+, # The root directory of the squashfs filesystem is filled with the
+ # closures of the Nix store paths listed here.
+ storeContents ? []
+, # Compression parameters.
+ # For zstd compression you can use "zstd -Xcompression-level 6".
+ comp ? "xz -Xdict-size 100%"
+}:
+
+stdenv.mkDerivation {
+ name = "squashfs.img";
+
+ nativeBuildInputs = [ squashfsTools ];
+
+ buildCommand =
+ ''
+ closureInfo=${closureInfo { rootPaths = storeContents; }}
+
+ # Also include a manifest of the closures in a format suitable
+ # for nix-store --load-db.
+ cp $closureInfo/registration nix-path-registration
+
+ # Generate the squashfs image.
+ mksquashfs nix-path-registration $(cat $closureInfo/store-paths) $out \
+ -keep-as-directory -all-root -b 1048576 -comp ${comp}
+ '';
+}
diff --git a/nixpkgs/nixos/lib/make-system-tarball.nix b/nixpkgs/nixos/lib/make-system-tarball.nix
new file mode 100644
index 00000000000..dee91a6ce3f
--- /dev/null
+++ b/nixpkgs/nixos/lib/make-system-tarball.nix
@@ -0,0 +1,56 @@
+{ stdenv, closureInfo, pixz
+
+, # The file name of the resulting tarball
+ fileName ? "nixos-system-${stdenv.hostPlatform.system}"
+
+, # The files and directories to be placed in the tarball.
+ # This is a list of attribute sets {source, target} where `source'
+ # is the file system object (regular file or directory) to be
+ # grafted in the file system at path `target'.
+ contents
+
+, # In addition to `contents', the closure of the store paths listed
+ # in `packages' are also placed in the Nix store of the tarball. This is
+ # a list of attribute sets {object, symlink} where `object' if a
+ # store path whose closure will be copied, and `symlink' is a
+ # symlink to `object' that will be added to the tarball.
+ storeContents ? []
+
+ # Extra commands to be executed before archiving files
+, extraCommands ? ""
+
+ # Extra tar arguments
+, extraArgs ? ""
+ # Command used for compression
+, compressCommand ? "pixz"
+ # Extension for the compressed tarball
+, compressionExtension ? ".xz"
+ # extra inputs, like the compressor to use
+, extraInputs ? [ pixz ]
+}:
+
+let
+ symlinks = map (x: x.symlink) storeContents;
+ objects = map (x: x.object) storeContents;
+in
+
+stdenv.mkDerivation {
+ name = "tarball";
+ builder = ./make-system-tarball.sh;
+ buildInputs = extraInputs;
+
+ inherit fileName extraArgs extraCommands compressCommand;
+
+ # !!! should use XML.
+ sources = map (x: x.source) contents;
+ targets = map (x: x.target) contents;
+
+ # !!! should use XML.
+ inherit symlinks objects;
+
+ closureInfo = closureInfo {
+ rootPaths = objects;
+ };
+
+ extension = compressionExtension;
+}
diff --git a/nixpkgs/nixos/lib/make-system-tarball.sh b/nixpkgs/nixos/lib/make-system-tarball.sh
new file mode 100644
index 00000000000..1a0017a1799
--- /dev/null
+++ b/nixpkgs/nixos/lib/make-system-tarball.sh
@@ -0,0 +1,57 @@
+source $stdenv/setup
+
+sources_=($sources)
+targets_=($targets)
+
+objects=($objects)
+symlinks=($symlinks)
+
+
+# Remove the initial slash from a path, since genisofs likes it that way.
+stripSlash() {
+ res="$1"
+ if test "${res:0:1}" = /; then res=${res:1}; fi
+}
+
+# Add the individual files.
+for ((i = 0; i < ${#targets_[@]}; i++)); do
+ stripSlash "${targets_[$i]}"
+ mkdir -p "$(dirname "$res")"
+ cp -a "${sources_[$i]}" "$res"
+done
+
+
+# Add the closures of the top-level store objects.
+chmod +w .
+mkdir -p nix/store
+for i in $(< $closureInfo/store-paths); do
+ cp -a "$i" "${i:1}"
+done
+
+
+# TODO tar ruxo
+# Also include a manifest of the closures in a format suitable for
+# nix-store --load-db.
+cp $closureInfo/registration nix-path-registration
+
+# Add symlinks to the top-level store objects.
+for ((n = 0; n < ${#objects[*]}; n++)); do
+ object=${objects[$n]}
+ symlink=${symlinks[$n]}
+ if test "$symlink" != "none"; then
+ mkdir -p $(dirname ./$symlink)
+ ln -s $object ./$symlink
+ fi
+done
+
+$extraCommands
+
+mkdir -p $out/tarball
+
+rm env-vars
+
+time tar --sort=name --mtime='@1' --owner=0 --group=0 --numeric-owner -c * $extraArgs | $compressCommand > $out/tarball/$fileName.tar${extension}
+
+mkdir -p $out/nix-support
+echo $system > $out/nix-support/system
+echo "file system-tarball $out/tarball/$fileName.tar${extension}" > $out/nix-support/hydra-build-products
diff --git a/nixpkgs/nixos/lib/qemu-flags.nix b/nixpkgs/nixos/lib/qemu-flags.nix
new file mode 100644
index 00000000000..774f66b4804
--- /dev/null
+++ b/nixpkgs/nixos/lib/qemu-flags.nix
@@ -0,0 +1,25 @@
+# QEMU flags shared between various Nix expressions.
+{ pkgs }:
+
+let
+ zeroPad = n: if n < 10 then "0${toString n}" else toString n;
+in
+
+{
+
+ qemuNICFlags = nic: net: machine:
+ [ "-device virtio-net-pci,netdev=vlan${toString nic},mac=52:54:00:12:${zeroPad net}:${zeroPad machine}"
+ "-netdev vde,id=vlan${toString nic},sock=$QEMU_VDE_SOCKET_${toString net}"
+ ];
+
+ qemuSerialDevice = if pkgs.stdenv.isi686 || pkgs.stdenv.isx86_64 then "ttyS0"
+ else if pkgs.stdenv.isAarch32 || pkgs.stdenv.isAarch64 then "ttyAMA0"
+ else throw "Unknown QEMU serial device for system '${pkgs.stdenv.hostPlatform.system}'";
+
+ qemuBinary = qemuPkg: {
+ x86_64-linux = "${qemuPkg}/bin/qemu-kvm -cpu kvm64";
+ armv7l-linux = "${qemuPkg}/bin/qemu-system-arm -enable-kvm -machine virt -cpu host";
+ aarch64-linux = "${qemuPkg}/bin/qemu-system-aarch64 -enable-kvm -machine virt,gic-version=host -cpu host";
+ x86_64-darwin = "${qemuPkg}/bin/qemu-kvm -cpu kvm64";
+ }.${pkgs.stdenv.hostPlatform.system} or "${qemuPkg}/bin/qemu-kvm";
+}
diff --git a/nixpkgs/nixos/lib/test-driver/Logger.pm b/nixpkgs/nixos/lib/test-driver/Logger.pm
new file mode 100644
index 00000000000..080310ea34e
--- /dev/null
+++ b/nixpkgs/nixos/lib/test-driver/Logger.pm
@@ -0,0 +1,75 @@
+package Logger;
+
+use strict;
+use Thread::Queue;
+use XML::Writer;
+use Encode qw(decode encode);
+use Time::HiRes qw(clock_gettime CLOCK_MONOTONIC);
+
+sub new {
+ my ($class) = @_;
+
+ my $logFile = defined $ENV{LOGFILE} ? "$ENV{LOGFILE}" : "/dev/null";
+ my $log = new XML::Writer(OUTPUT => new IO::File(">$logFile"));
+
+ my $self = {
+ log => $log,
+ logQueue => Thread::Queue->new()
+ };
+
+ $self->{log}->startTag("logfile");
+
+ bless $self, $class;
+ return $self;
+}
+
+sub close {
+ my ($self) = @_;
+ $self->{log}->endTag("logfile");
+ $self->{log}->end;
+}
+
+sub drainLogQueue {
+ my ($self) = @_;
+ while (defined (my $item = $self->{logQueue}->dequeue_nb())) {
+ $self->{log}->dataElement("line", sanitise($item->{msg}), 'machine' => $item->{machine}, 'type' => 'serial');
+ }
+}
+
+sub maybePrefix {
+ my ($msg, $attrs) = @_;
+ $msg = $attrs->{machine} . ": " . $msg if defined $attrs->{machine};
+ return $msg;
+}
+
+sub nest {
+ my ($self, $msg, $coderef, $attrs) = @_;
+ print STDERR maybePrefix("$msg\n", $attrs);
+ $self->{log}->startTag("nest");
+ $self->{log}->dataElement("head", $msg, %{$attrs});
+ my $now = clock_gettime(CLOCK_MONOTONIC);
+ $self->drainLogQueue();
+ eval { &$coderef };
+ my $res = $@;
+ $self->drainLogQueue();
+ $self->log(sprintf("(%.2f seconds)", clock_gettime(CLOCK_MONOTONIC) - $now));
+ $self->{log}->endTag("nest");
+ die $@ if $@;
+}
+
+sub sanitise {
+ my ($s) = @_;
+ $s =~ s/[[:cntrl:]\xff]//g;
+ $s = decode('UTF-8', $s, Encode::FB_DEFAULT);
+ return encode('UTF-8', $s, Encode::FB_CROAK);
+}
+
+sub log {
+ my ($self, $msg, $attrs) = @_;
+ chomp $msg;
+ print STDERR maybePrefix("$msg\n", $attrs);
+ $self->drainLogQueue();
+ $self->{log}->dataElement("line", $msg, %{$attrs});
+}
+
+1;
diff --git a/nixpkgs/nixos/lib/test-driver/Machine.pm b/nixpkgs/nixos/lib/test-driver/Machine.pm
new file mode 100644
index 00000000000..4d3d63cd2db
--- /dev/null
+++ b/nixpkgs/nixos/lib/test-driver/Machine.pm
@@ -0,0 +1,734 @@
+package Machine;
+
+use strict;
+use threads;
+use Socket;
+use IO::Handle;
+use POSIX qw(dup2);
+use FileHandle;
+use Cwd;
+use File::Basename;
+use File::Path qw(make_path);
+use File::Slurp;
+use Time::HiRes qw(clock_gettime CLOCK_MONOTONIC);
+
+
+my $showGraphics = defined $ENV{'DISPLAY'};
+
+my $sharedDir;
+
+
+sub new {
+ my ($class, $args) = @_;
+
+ my $startCommand = $args->{startCommand};
+
+ my $name = $args->{name};
+ if (!$name) {
+ $startCommand =~ /run-(.*)-vm$/ if defined $startCommand;
+ $name = $1 || "machine";
+ }
+
+ if (!$startCommand) {
+ # !!! merge with qemu-vm.nix.
+ my $netBackend = "-netdev user,id=net0";
+ my $netFrontend = "-device virtio-net-pci,netdev=net0";
+
+ $netBackend .= "," . $args->{netBackendArgs}
+ if defined $args->{netBackendArgs};
+
+ $netFrontend .= "," . $args->{netFrontendArgs}
+ if defined $args->{netFrontendArgs};
+
+ $startCommand =
+ "qemu-kvm -m 384 $netBackend $netFrontend \$QEMU_OPTS ";
+
+ if (defined $args->{hda}) {
+ if ($args->{hdaInterface} eq "scsi") {
+ $startCommand .= "-drive id=hda,file="
+ . Cwd::abs_path($args->{hda})
+ . ",werror=report,if=none "
+ . "-device scsi-hd,drive=hda ";
+ } else {
+ $startCommand .= "-drive file=" . Cwd::abs_path($args->{hda})
+ . ",if=" . $args->{hdaInterface}
+ . ",werror=report ";
+ }
+ }
+
+ $startCommand .= "-cdrom $args->{cdrom} "
+ if defined $args->{cdrom};
+ $startCommand .= "-device piix3-usb-uhci -drive id=usbdisk,file=$args->{usb},if=none,readonly -device usb-storage,drive=usbdisk "
+ if defined $args->{usb};
+ $startCommand .= "-bios $args->{bios} "
+ if defined $args->{bios};
+ $startCommand .= $args->{qemuFlags} || "";
+ }
+
+ my $tmpDir = $ENV{'TMPDIR'} || "/tmp";
+ unless (defined $sharedDir) {
+ $sharedDir = $tmpDir . "/xchg-shared";
+ make_path($sharedDir, { mode => 0700, owner => $< });
+ }
+
+ my $allowReboot = 0;
+ $allowReboot = $args->{allowReboot} if defined $args->{allowReboot};
+
+ my $self = {
+ startCommand => $startCommand,
+ name => $name,
+ allowReboot => $allowReboot,
+ booted => 0,
+ pid => 0,
+ connected => 0,
+ socket => undef,
+ stateDir => "$tmpDir/vm-state-$name",
+ monitor => undef,
+ log => $args->{log},
+ redirectSerial => $args->{redirectSerial} // 1,
+ };
+
+ mkdir $self->{stateDir}, 0700;
+
+ bless $self, $class;
+ return $self;
+}
+
+
+sub log {
+ my ($self, $msg) = @_;
+ $self->{log}->log($msg, { machine => $self->{name} });
+}
+
+
+sub nest {
+ my ($self, $msg, $coderef, $attrs) = @_;
+ $self->{log}->nest($msg, $coderef, { %{$attrs || {}}, machine => $self->{name} });
+}
+
+
+sub name {
+ my ($self) = @_;
+ return $self->{name};
+}
+
+
+sub stateDir {
+ my ($self) = @_;
+ return $self->{stateDir};
+}
+
+
+sub start {
+ my ($self) = @_;
+ return if $self->{booted};
+
+ $self->log("starting vm");
+
+ # Create a socket pair for the serial line input/output of the VM.
+ my ($serialP, $serialC);
+ socketpair($serialP, $serialC, PF_UNIX, SOCK_STREAM, 0) or die;
+
+ # Create a Unix domain socket to which QEMU's monitor will connect.
+ my $monitorPath = $self->{stateDir} . "/monitor";
+ unlink $monitorPath;
+ my $monitorS;
+ socket($monitorS, PF_UNIX, SOCK_STREAM, 0) or die;
+ bind($monitorS, sockaddr_un($monitorPath)) or die "cannot bind monitor socket: $!";
+ listen($monitorS, 1) or die;
+
+ # Create a Unix domain socket to which the root shell in the guest will connect.
+ my $shellPath = $self->{stateDir} . "/shell";
+ unlink $shellPath;
+ my $shellS;
+ socket($shellS, PF_UNIX, SOCK_STREAM, 0) or die;
+ bind($shellS, sockaddr_un($shellPath)) or die "cannot bind shell socket: $!";
+ listen($shellS, 1) or die;
+
+ # Start the VM.
+ my $pid = fork();
+ die if $pid == -1;
+
+ if ($pid == 0) {
+ close $serialP;
+ close $monitorS;
+ close $shellS;
+ if ($self->{redirectSerial}) {
+ open NUL, "</dev/null" or die;
+ dup2(fileno(NUL), fileno(STDIN));
+ dup2(fileno($serialC), fileno(STDOUT));
+ dup2(fileno($serialC), fileno(STDERR));
+ }
+ $ENV{TMPDIR} = $self->{stateDir};
+ $ENV{SHARED_DIR} = $sharedDir;
+ $ENV{USE_TMPDIR} = 1;
+ $ENV{QEMU_OPTS} =
+ ($self->{allowReboot} ? "" : "-no-reboot ") .
+ "-monitor unix:./monitor -chardev socket,id=shell,path=./shell " .
+ "-device virtio-serial -device virtconsole,chardev=shell " .
+ "-device virtio-rng-pci " .
+ ($showGraphics ? "-serial stdio" : "-nographic") . " " . ($ENV{QEMU_OPTS} || "");
+ chdir $self->{stateDir} or die;
+ exec $self->{startCommand};
+ die "running VM script: $!";
+ }
+
+ # Process serial line output.
+ close $serialC;
+
+ threads->create(\&processSerialOutput, $self, $serialP)->detach;
+
+ sub processSerialOutput {
+ my ($self, $serialP) = @_;
+ while (<$serialP>) {
+ chomp;
+ s/\r$//;
+ print STDERR $self->{name}, "# $_\n";
+ $self->{log}->{logQueue}->enqueue({msg => $_, machine => $self->{name}}); # !!!
+ }
+ }
+
+ eval {
+ local $SIG{CHLD} = sub { die "QEMU died prematurely\n"; };
+
+ # Wait until QEMU connects to the monitor.
+ accept($self->{monitor}, $monitorS) or die;
+
+ # Wait until QEMU connects to the root shell socket. QEMU
+ # does so immediately; this doesn't mean that the root shell
+ # has connected yet inside the guest.
+ accept($self->{socket}, $shellS) or die;
+ $self->{socket}->autoflush(1);
+ };
+ die "$@" if $@;
+
+ $self->waitForMonitorPrompt;
+
+ $self->log("QEMU running (pid $pid)");
+
+ $self->{pid} = $pid;
+ $self->{booted} = 1;
+}
+
+
+# Send a command to the monitor and wait for it to finish. TODO: QEMU
+# also has a JSON-based monitor interface now, but it doesn't support
+# all commands yet. We should use it once it does.
+sub sendMonitorCommand {
+ my ($self, $command) = @_;
+ $self->log("sending monitor command: $command");
+ syswrite $self->{monitor}, "$command\n";
+ return $self->waitForMonitorPrompt;
+}
+
+
+# Wait until the monitor sends "(qemu) ".
+sub waitForMonitorPrompt {
+ my ($self) = @_;
+ my $res = "";
+ my $s;
+ while (sysread($self->{monitor}, $s, 1024)) {
+ $res .= $s;
+ last if $res =~ s/\(qemu\) $//;
+ }
+ return $res;
+}
+
+
+# Call the given code reference repeatedly, with 1 second intervals,
+# until it returns 1 or a timeout is reached.
+sub retry {
+ my ($coderef) = @_;
+ my $n;
+ for ($n = 899; $n >=0; $n--) {
+ return if &$coderef($n);
+ sleep 1;
+ }
+ die "action timed out after $n seconds";
+}
+
+
+sub connect {
+ my ($self) = @_;
+ return if $self->{connected};
+
+ $self->nest("waiting for the VM to finish booting", sub {
+
+ $self->start;
+
+ my $now = clock_gettime(CLOCK_MONOTONIC);
+ local $SIG{ALRM} = sub { die "timed out waiting for the VM to connect\n"; };
+ alarm 600;
+ readline $self->{socket} or die "the VM quit before connecting\n";
+ alarm 0;
+
+ $self->log("connected to guest root shell");
+ # We're interested in tracking how close we are to `alarm`.
+ $self->log(sprintf("(connecting took %.2f seconds)", clock_gettime(CLOCK_MONOTONIC) - $now));
+ $self->{connected} = 1;
+
+ });
+}
+
+
+sub waitForShutdown {
+ my ($self) = @_;
+ return unless $self->{booted};
+
+ $self->nest("waiting for the VM to power off", sub {
+ waitpid $self->{pid}, 0;
+ $self->{pid} = 0;
+ $self->{booted} = 0;
+ $self->{connected} = 0;
+ });
+}
+
+
+sub isUp {
+ my ($self) = @_;
+ return $self->{booted} && $self->{connected};
+}
+
+
+sub execute_ {
+ my ($self, $command) = @_;
+
+ $self->connect;
+
+ print { $self->{socket} } ("( $command ); echo '|!=EOF' \$?\n");
+
+ my $out = "";
+
+ while (1) {
+ my $line = readline($self->{socket});
+ die "connection to VM lost unexpectedly" unless defined $line;
+ #$self->log("got line: $line");
+ if ($line =~ /^(.*)\|\!\=EOF\s+(\d+)$/) {
+ $out .= $1;
+ $self->log("exit status $2");
+ return ($2, $out);
+ }
+ $out .= $line;
+ }
+}
+
+
+sub execute {
+ my ($self, $command) = @_;
+ my @res;
+ $self->nest("running command: $command", sub {
+ @res = $self->execute_($command);
+ });
+ return @res;
+}
+
+
+sub succeed {
+ my ($self, @commands) = @_;
+
+ my $res;
+ foreach my $command (@commands) {
+ $self->nest("must succeed: $command", sub {
+ my ($status, $out) = $self->execute_($command);
+ if ($status != 0) {
+ $self->log("output: $out");
+ die "command `$command' did not succeed (exit code $status)\n";
+ }
+ $res .= $out;
+ });
+ }
+
+ return $res;
+}
+
+
+sub mustSucceed {
+ succeed @_;
+}
+
+
+sub waitUntilSucceeds {
+ my ($self, $command) = @_;
+ $self->nest("waiting for success: $command", sub {
+ retry sub {
+ my ($status, $out) = $self->execute($command);
+ return 1 if $status == 0;
+ };
+ });
+}
+
+
+sub waitUntilFails {
+ my ($self, $command) = @_;
+ $self->nest("waiting for failure: $command", sub {
+ retry sub {
+ my ($status, $out) = $self->execute($command);
+ return 1 if $status != 0;
+ };
+ });
+}
+
+
+sub fail {
+ my ($self, $command) = @_;
+ $self->nest("must fail: $command", sub {
+ my ($status, $out) = $self->execute_($command);
+ die "command `$command' unexpectedly succeeded"
+ if $status == 0;
+ });
+}
+
+
+sub mustFail {
+ fail @_;
+}
+
+
+sub getUnitInfo {
+ my ($self, $unit, $user) = @_;
+ my ($status, $lines) = $self->systemctl("--no-pager show \"$unit\"", $user);
+ return undef if $status != 0;
+ my $info = {};
+ foreach my $line (split '\n', $lines) {
+ $line =~ /^([^=]+)=(.*)$/ or next;
+ $info->{$1} = $2;
+ }
+ return $info;
+}
+
+sub systemctl {
+ my ($self, $q, $user) = @_;
+ if ($user) {
+ $q =~ s/'/\\'/g;
+ return $self->execute("su -l $user -c \$'XDG_RUNTIME_DIR=/run/user/`id -u` systemctl --user $q'");
+ }
+
+ return $self->execute("systemctl $q");
+}
+
+# Fail if the given systemd unit is not in the "active" state.
+sub requireActiveUnit {
+ my ($self, $unit) = @_;
+ $self->nest("checking if unit ‘$unit’ has reached state 'active'", sub {
+ my $info = $self->getUnitInfo($unit);
+ my $state = $info->{ActiveState};
+ if ($state ne "active") {
+ die "Expected unit ‘$unit’ to to be in state 'active' but it is in state ‘$state’\n";
+ };
+ });
+}
+
+# Wait for a systemd unit to reach the "active" state.
+sub waitForUnit {
+ my ($self, $unit, $user) = @_;
+ $self->nest("waiting for unit ‘$unit’", sub {
+ retry sub {
+ my $info = $self->getUnitInfo($unit, $user);
+ my $state = $info->{ActiveState};
+ die "unit ‘$unit’ reached state ‘$state’\n" if $state eq "failed";
+ if ($state eq "inactive") {
+ # If there are no pending jobs, then assume this unit
+ # will never reach active state.
+ my ($status, $jobs) = $self->systemctl("list-jobs --full 2>&1", $user);
+ if ($jobs =~ /No jobs/) { # FIXME: fragile
+ # Handle the case where the unit may have started
+ # between the previous getUnitInfo() and
+ # list-jobs.
+ my $info2 = $self->getUnitInfo($unit);
+ die "unit ‘$unit’ is inactive and there are no pending jobs\n"
+ if $info2->{ActiveState} eq $state;
+ }
+ }
+ return 1 if $state eq "active";
+ };
+ });
+}
+
+
+sub waitForJob {
+ my ($self, $jobName) = @_;
+ return $self->waitForUnit($jobName);
+}
+
+
+# Wait until the specified file exists.
+sub waitForFile {
+ my ($self, $fileName) = @_;
+ $self->nest("waiting for file ‘$fileName’", sub {
+ retry sub {
+ my ($status, $out) = $self->execute("test -e $fileName");
+ return 1 if $status == 0;
+ }
+ });
+}
+
+sub startJob {
+ my ($self, $jobName, $user) = @_;
+ $self->systemctl("start $jobName", $user);
+ # FIXME: check result
+}
+
+sub stopJob {
+ my ($self, $jobName, $user) = @_;
+ $self->systemctl("stop $jobName", $user);
+}
+
+
+# Wait until the machine is listening on the given TCP port.
+sub waitForOpenPort {
+ my ($self, $port) = @_;
+ $self->nest("waiting for TCP port $port", sub {
+ retry sub {
+ my ($status, $out) = $self->execute("nc -z localhost $port");
+ return 1 if $status == 0;
+ }
+ });
+}
+
+
+# Wait until the machine is not listening on the given TCP port.
+sub waitForClosedPort {
+ my ($self, $port) = @_;
+ retry sub {
+ my ($status, $out) = $self->execute("nc -z localhost $port");
+ return 1 if $status != 0;
+ }
+}
+
+
+sub shutdown {
+ my ($self) = @_;
+ return unless $self->{booted};
+
+ print { $self->{socket} } ("poweroff\n");
+
+ $self->waitForShutdown;
+}
+
+
+sub crash {
+ my ($self) = @_;
+ return unless $self->{booted};
+
+ $self->log("forced crash");
+
+ $self->sendMonitorCommand("quit");
+
+ $self->waitForShutdown;
+}
+
+
+# Make the machine unreachable by shutting down eth1 (the multicast
+# interface used to talk to the other VMs). We keep eth0 up so that
+# the test driver can continue to talk to the machine.
+sub block {
+ my ($self) = @_;
+ $self->sendMonitorCommand("set_link virtio-net-pci.1 off");
+}
+
+
+# Make the machine reachable.
+sub unblock {
+ my ($self) = @_;
+ $self->sendMonitorCommand("set_link virtio-net-pci.1 on");
+}
+
+
+# Take a screenshot of the X server on :0.0.
+sub screenshot {
+ my ($self, $filename) = @_;
+ my $dir = $ENV{'out'} || Cwd::abs_path(".");
+ $filename = "$dir/${filename}.png" if $filename =~ /^\w+$/;
+ my $tmp = "${filename}.ppm";
+ my $name = basename($filename);
+ $self->nest("making screenshot ‘$name’", sub {
+ $self->sendMonitorCommand("screendump $tmp");
+ system("pnmtopng $tmp > ${filename}") == 0
+ or die "cannot convert screenshot";
+ unlink $tmp;
+ }, { image => $name } );
+}
+
+# Get the text of TTY<n>
+sub getTTYText {
+ my ($self, $tty) = @_;
+
+ my ($status, $out) = $self->execute("fold -w\$(stty -F /dev/tty${tty} size | awk '{print \$2}') /dev/vcs${tty}");
+ return $out;
+}
+
+# Wait until TTY<n>'s text matches a particular regular expression
+sub waitUntilTTYMatches {
+ my ($self, $tty, $regexp) = @_;
+
+ $self->nest("waiting for $regexp to appear on tty $tty", sub {
+ retry sub {
+ my ($retries_remaining) = @_;
+ if ($retries_remaining == 0) {
+ $self->log("Last chance to match /$regexp/ on TTY$tty, which currently contains:");
+ $self->log($self->getTTYText($tty));
+ }
+
+ return 1 if $self->getTTYText($tty) =~ /$regexp/;
+ }
+ });
+}
+
+# Debugging: Dump the contents of the TTY<n>
+sub dumpTTYContents {
+ my ($self, $tty) = @_;
+
+ $self->execute("fold -w 80 /dev/vcs${tty} | systemd-cat");
+}
+
+# Take a screenshot and return the result as text using optical character
+# recognition.
+sub getScreenText {
+ my ($self) = @_;
+
+ system("command -v tesseract &> /dev/null") == 0
+ or die "getScreenText used but enableOCR is false";
+
+ my $text;
+ $self->nest("performing optical character recognition", sub {
+ my $tmpbase = Cwd::abs_path(".")."/ocr";
+ my $tmpin = $tmpbase."in.ppm";
+
+ $self->sendMonitorCommand("screendump $tmpin");
+
+ my $magickArgs = "-filter Catrom -density 72 -resample 300 "
+ . "-contrast -normalize -despeckle -type grayscale "
+ . "-sharpen 1 -posterize 3 -negate -gamma 100 "
+ . "-blur 1x65535";
+ my $tessArgs = "-c debug_file=/dev/null --psm 11 --oem 2";
+
+ $text = `convert $magickArgs $tmpin tiff:- | tesseract - - $tessArgs`;
+ my $status = $? >> 8;
+ unlink $tmpin;
+
+ die "OCR failed with exit code $status" if $status != 0;
+ });
+ return $text;
+}
+
+
+# Wait until a specific regexp matches the textual contents of the screen.
+sub waitForText {
+ my ($self, $regexp) = @_;
+ $self->nest("waiting for $regexp to appear on the screen", sub {
+ retry sub {
+ my ($retries_remaining) = @_;
+ if ($retries_remaining == 0) {
+ $self->log("Last chance to match /$regexp/ on the screen, which currently contains:");
+ $self->log($self->getScreenText);
+ }
+
+ return 1 if $self->getScreenText =~ /$regexp/;
+ }
+ });
+}
+
+
+# Wait until it is possible to connect to the X server. Note that
+# testing the existence of /tmp/.X11-unix/X0 is insufficient.
+sub waitForX {
+ my ($self, $regexp) = @_;
+ $self->nest("waiting for the X11 server", sub {
+ retry sub {
+ my ($status, $out) = $self->execute("journalctl -b SYSLOG_IDENTIFIER=systemd | grep 'Reached target Current graphical'");
+ return 0 if $status != 0;
+ ($status, $out) = $self->execute("[ -e /tmp/.X11-unix/X0 ]");
+ return 1 if $status == 0;
+ }
+ });
+}
+
+
+sub getWindowNames {
+ my ($self) = @_;
+ my $res = $self->mustSucceed(
+ q{xwininfo -root -tree | sed 's/.*0x[0-9a-f]* \"\([^\"]*\)\".*/\1/; t; d'});
+ return split /\n/, $res;
+}
+
+
+sub waitForWindow {
+ my ($self, $regexp) = @_;
+ $self->nest("waiting for a window to appear", sub {
+ retry sub {
+ my @names = $self->getWindowNames;
+
+ my ($retries_remaining) = @_;
+ if ($retries_remaining == 0) {
+ $self->log("Last chance to match /$regexp/ on the the window list, which currently contains:");
+ $self->log(join(", ", @names));
+ }
+
+ foreach my $n (@names) {
+ return 1 if $n =~ /$regexp/;
+ }
+ }
+ });
+}
+
+
+sub copyFileFromHost {
+ my ($self, $from, $to) = @_;
+ my $s = `cat $from` or die;
+ $s =~ s/'/'\\''/g;
+ $self->mustSucceed("echo '$s' > $to");
+}
+
+
+my %charToKey = (
+ 'A' => "shift-a", 'N' => "shift-n", '-' => "0x0C", '_' => "shift-0x0C", '!' => "shift-0x02",
+ 'B' => "shift-b", 'O' => "shift-o", '=' => "0x0D", '+' => "shift-0x0D", '@' => "shift-0x03",
+ 'C' => "shift-c", 'P' => "shift-p", '[' => "0x1A", '{' => "shift-0x1A", '#' => "shift-0x04",
+ 'D' => "shift-d", 'Q' => "shift-q", ']' => "0x1B", '}' => "shift-0x1B", '$' => "shift-0x05",
+ 'E' => "shift-e", 'R' => "shift-r", ';' => "0x27", ':' => "shift-0x27", '%' => "shift-0x06",
+ 'F' => "shift-f", 'S' => "shift-s", '\'' => "0x28", '"' => "shift-0x28", '^' => "shift-0x07",
+ 'G' => "shift-g", 'T' => "shift-t", '`' => "0x29", '~' => "shift-0x29", '&' => "shift-0x08",
+ 'H' => "shift-h", 'U' => "shift-u", '\\' => "0x2B", '|' => "shift-0x2B", '*' => "shift-0x09",
+ 'I' => "shift-i", 'V' => "shift-v", ',' => "0x33", '<' => "shift-0x33", '(' => "shift-0x0A",
+ 'J' => "shift-j", 'W' => "shift-w", '.' => "0x34", '>' => "shift-0x34", ')' => "shift-0x0B",
+ 'K' => "shift-k", 'X' => "shift-x", '/' => "0x35", '?' => "shift-0x35",
+ 'L' => "shift-l", 'Y' => "shift-y", ' ' => "spc",
+ 'M' => "shift-m", 'Z' => "shift-z", "\n" => "ret",
+);
+
+
+sub sendKeys {
+ my ($self, @keys) = @_;
+ foreach my $key (@keys) {
+ $key = $charToKey{$key} if exists $charToKey{$key};
+ $self->sendMonitorCommand("sendkey $key");
+ }
+}
+
+
+sub sendChars {
+ my ($self, $chars) = @_;
+ $self->nest("sending keys ‘$chars’", sub {
+ $self->sendKeys(split //, $chars);
+ });
+}
+
+
+# Sleep N seconds (in virtual guest time, not real time).
+sub sleep {
+ my ($self, $time) = @_;
+ $self->succeed("sleep $time");
+}
+
+
+# Forward a TCP port on the host to a TCP port on the guest. Useful
+# during interactive testing.
+sub forwardPort {
+ my ($self, $hostPort, $guestPort) = @_;
+ $hostPort = 8080 unless defined $hostPort;
+ $guestPort = 80 unless defined $guestPort;
+ $self->sendMonitorCommand("hostfwd_add tcp::$hostPort-:$guestPort");
+}
+
+
+1;
diff --git a/nixpkgs/nixos/lib/test-driver/log2html.xsl b/nixpkgs/nixos/lib/test-driver/log2html.xsl
new file mode 100644
index 00000000000..0485412b4c8
--- /dev/null
+++ b/nixpkgs/nixos/lib/test-driver/log2html.xsl
@@ -0,0 +1,135 @@
+<?xml version="1.0"?>
+
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+
+ <xsl:output method='html' encoding="UTF-8"
+ doctype-public="-//W3C//DTD HTML 4.01//EN"
+ doctype-system="http://www.w3.org/TR/html4/strict.dtd" />
+
+ <xsl:template match="logfile">
+ <html>
+ <head>
+ <script type="text/javascript" src="jquery.min.js"></script>
+ <script type="text/javascript" src="jquery-ui.min.js"></script>
+ <script type="text/javascript" src="treebits.js" />
+ <link rel="stylesheet" href="logfile.css" type="text/css" />
+ <title>Log File</title>
+ </head>
+ <body>
+ <h1>VM build log</h1>
+ <p>
+ <a href="javascript:" class="logTreeExpandAll">Expand all</a> |
+ <a href="javascript:" class="logTreeCollapseAll">Collapse all</a>
+ </p>
+ <ul class='toplevel'>
+ <xsl:for-each select='line|nest'>
+ <li>
+ <xsl:apply-templates select='.'/>
+ </li>
+ </xsl:for-each>
+ </ul>
+
+ <xsl:if test=".//*[@image]">
+ <h1>Screenshots</h1>
+ <ul class="vmScreenshots">
+ <xsl:for-each select='.//*[@image]'>
+ <li><a href="{@image}"><xsl:value-of select="@image" /></a></li>
+ </xsl:for-each>
+ </ul>
+ </xsl:if>
+
+ </body>
+ </html>
+ </xsl:template>
+
+
+ <xsl:template match="nest">
+
+ <!-- The tree should be collapsed by default if all children are
+ unimportant or if the header is unimportant. -->
+ <xsl:variable name="collapsed" select="not(./head[@expanded]) and count(.//*[@error]) = 0"/>
+
+ <xsl:variable name="style"><xsl:if test="$collapsed">display: none;</xsl:if></xsl:variable>
+
+ <xsl:if test="line|nest">
+ <a href="javascript:" class="logTreeToggle">
+ <xsl:choose>
+ <xsl:when test="$collapsed"><xsl:text>+</xsl:text></xsl:when>
+ <xsl:otherwise><xsl:text>-</xsl:text></xsl:otherwise>
+ </xsl:choose>
+ </a>
+ <xsl:text> </xsl:text>
+ </xsl:if>
+
+ <xsl:apply-templates select='head'/>
+
+ <!-- Be careful to only generate <ul>s if there are <li>s, otherwise it’s malformed. -->
+ <xsl:if test="line|nest">
+
+ <ul class='nesting' style="{$style}">
+ <xsl:for-each select='line|nest'>
+
+ <!-- Is this the last line? If so, mark it as such so that it
+ can be rendered differently. -->
+ <xsl:variable name="class"><xsl:choose><xsl:when test="position() != last()">line</xsl:when><xsl:otherwise>lastline</xsl:otherwise></xsl:choose></xsl:variable>
+
+ <li class='{$class}'>
+ <span class='lineconn' />
+ <span class='linebody'>
+ <xsl:apply-templates select='.'/>
+ </span>
+ </li>
+ </xsl:for-each>
+ </ul>
+ </xsl:if>
+
+ </xsl:template>
+
+
+ <xsl:template match="head|line">
+ <code>
+ <xsl:if test="@error">
+ <xsl:attribute name="class">errorLine</xsl:attribute>
+ </xsl:if>
+ <xsl:if test="@warning">
+ <xsl:attribute name="class">warningLine</xsl:attribute>
+ </xsl:if>
+ <xsl:if test="@priority = 3">
+ <xsl:attribute name="class">prio3</xsl:attribute>
+ </xsl:if>
+
+ <xsl:if test="@type = 'serial'">
+ <xsl:attribute name="class">serial</xsl:attribute>
+ </xsl:if>
+
+ <xsl:if test="@machine">
+ <xsl:choose>
+ <xsl:when test="@type = 'serial'">
+ <span class="machine"><xsl:value-of select="@machine"/># </span>
+ </xsl:when>
+ <xsl:otherwise>
+ <span class="machine"><xsl:value-of select="@machine"/>: </span>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:if>
+
+ <xsl:choose>
+ <xsl:when test="@image">
+ <a href="{@image}"><xsl:apply-templates/></a>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:apply-templates/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </code>
+ </xsl:template>
+
+
+ <xsl:template match="storeref">
+ <em class='storeref'>
+ <span class='popup'><xsl:apply-templates/></span>
+ <span class='elided'>/...</span><xsl:apply-templates select='name'/><xsl:apply-templates select='path'/>
+ </em>
+ </xsl:template>
+
+</xsl:stylesheet>
diff --git a/nixpkgs/nixos/lib/test-driver/logfile.css b/nixpkgs/nixos/lib/test-driver/logfile.css
new file mode 100644
index 00000000000..a54d8504a86
--- /dev/null
+++ b/nixpkgs/nixos/lib/test-driver/logfile.css
@@ -0,0 +1,129 @@
+body {
+ font-family: sans-serif;
+ background: white;
+}
+
+h1
+{
+ color: #005aa0;
+ font-size: 180%;
+}
+
+a {
+ text-decoration: none;
+}
+
+
+ul.nesting, ul.toplevel {
+ padding: 0;
+ margin: 0;
+}
+
+ul.toplevel {
+ list-style-type: none;
+}
+
+.line, .head {
+ padding-top: 0em;
+}
+
+ul.nesting li.line, ul.nesting li.lastline {
+ position: relative;
+ list-style-type: none;
+}
+
+ul.nesting li.line {
+ padding-left: 2.0em;
+}
+
+ul.nesting li.lastline {
+ padding-left: 2.1em; /* for the 0.1em border-left in .lastline > .lineconn */
+}
+
+li.line {
+ border-left: 0.1em solid #6185a0;
+}
+
+li.line > span.lineconn, li.lastline > span.lineconn {
+ position: absolute;
+ height: 0.65em;
+ left: 0em;
+ width: 1.5em;
+ border-bottom: 0.1em solid #6185a0;
+}
+
+li.lastline > span.lineconn {
+ border-left: 0.1em solid #6185a0;
+}
+
+
+em.storeref {
+ color: #500000;
+ position: relative;
+ width: 100%;
+}
+
+em.storeref:hover {
+ background-color: #eeeeee;
+}
+
+*.popup {
+ display: none;
+/* background: url('http://losser.st-lab.cs.uu.nl/~mbravenb/menuback.png') repeat; */
+ background: #ffffcd;
+ border: solid #555555 1px;
+ position: absolute;
+ top: 0em;
+ left: 0em;
+ margin: 0;
+ padding: 0;
+ z-index: 100;
+}
+
+em.storeref:hover span.popup {
+ display: inline;
+ width: 40em;
+}
+
+
+.logTreeToggle {
+ text-decoration: none;
+ font-family: monospace;
+ font-size: larger;
+}
+
+.errorLine {
+ color: #ff0000;
+ font-weight: bold;
+}
+
+.warningLine {
+ color: darkorange;
+ font-weight: bold;
+}
+
+.prio3 {
+ font-style: italic;
+}
+
+code {
+ white-space: pre-wrap;
+}
+
+.serial {
+ color: #56115c;
+}
+
+.machine {
+ color: #002399;
+ font-style: italic;
+}
+
+ul.vmScreenshots {
+ padding-left: 1em;
+}
+
+ul.vmScreenshots li {
+ font-family: monospace;
+ list-style: square;
+}
diff --git a/nixpkgs/nixos/lib/test-driver/test-driver.pl b/nixpkgs/nixos/lib/test-driver/test-driver.pl
new file mode 100644
index 00000000000..a3354fb0e1e
--- /dev/null
+++ b/nixpkgs/nixos/lib/test-driver/test-driver.pl
@@ -0,0 +1,191 @@
+#! /somewhere/perl -w
+
+use strict;
+use Machine;
+use Term::ReadLine;
+use IO::File;
+use IO::Pty;
+use Logger;
+use Cwd;
+use POSIX qw(_exit dup2);
+use Time::HiRes qw(clock_gettime CLOCK_MONOTONIC);
+
+$SIG{PIPE} = 'IGNORE'; # because Unix domain sockets may die unexpectedly
+
+STDERR->autoflush(1);
+
+my $log = new Logger;
+
+
+# Start vde_switch for each network required by the test.
+my %vlans;
+foreach my $vlan (split / /, $ENV{VLANS} || "") {
+ next if defined $vlans{$vlan};
+ # Start vde_switch as a child process. We don't run it in daemon
+ # mode because we want the child process to be cleaned up when we
+ # die. Since we have to make sure that the control socket is
+ # ready, we send a dummy command to vde_switch (via stdin) and
+ # wait for a reply. Note that vde_switch requires stdin to be a
+ # TTY, so we create one.
+ $log->log("starting VDE switch for network $vlan");
+ my $socket = Cwd::abs_path "./vde$vlan.ctl";
+ my $pty = new IO::Pty;
+ my ($stdoutR, $stdoutW); pipe $stdoutR, $stdoutW;
+ my $pid = fork(); die "cannot fork" unless defined $pid;
+ if ($pid == 0) {
+ dup2(fileno($pty->slave), 0);
+ dup2(fileno($stdoutW), 1);
+ exec "vde_switch -s $socket --dirmode 0700" or _exit(1);
+ }
+ close $stdoutW;
+ print $pty "version\n";
+ readline $stdoutR or die "cannot start vde_switch";
+ $ENV{"QEMU_VDE_SOCKET_$vlan"} = $socket;
+ $vlans{$vlan} = $pty;
+ die unless -e "$socket/ctl";
+}
+
+
+my %vms;
+my $context = "";
+
+sub createMachine {
+ my ($args) = @_;
+ my $vm = Machine->new({%{$args}, log => $log, redirectSerial => ($ENV{USE_SERIAL} // "0") ne "1"});
+ $vms{$vm->name} = $vm;
+ $context .= "my \$" . $vm->name . " = \$vms{'" . $vm->name . "'}; ";
+ return $vm;
+}
+
+foreach my $vmScript (@ARGV) {
+ my $vm = createMachine({startCommand => $vmScript});
+}
+
+
+sub startAll {
+ $log->nest("starting all VMs", sub {
+ $_->start foreach values %vms;
+ });
+}
+
+
+# Wait until all VMs have terminated.
+sub joinAll {
+ $log->nest("waiting for all VMs to finish", sub {
+ $_->waitForShutdown foreach values %vms;
+ });
+}
+
+
+# In interactive tests, this allows the non-interactive test script to
+# be executed conveniently.
+sub testScript {
+ eval "$context $ENV{testScript};\n";
+ warn $@ if $@;
+}
+
+
+my $nrTests = 0;
+my $nrSucceeded = 0;
+
+
+sub subtest {
+ my ($name, $coderef) = @_;
+ $log->nest("subtest: $name", sub {
+ $nrTests++;
+ eval { &$coderef };
+ if ($@) {
+ $log->log("error: $@", { error => 1 });
+ } else {
+ $nrSucceeded++;
+ }
+ });
+}
+
+
+sub runTests {
+ if (defined $ENV{tests}) {
+ $log->nest("running the VM test script", sub {
+ eval "$context $ENV{tests}";
+ if ($@) {
+ $log->log("error: $@", { error => 1 });
+ die $@;
+ }
+ }, { expanded => 1 });
+ } else {
+ my $term = Term::ReadLine->new('nixos-vm-test');
+ $term->ReadHistory;
+ while (defined ($_ = $term->readline("> "))) {
+ eval "$context $_\n";
+ warn $@ if $@;
+ }
+ $term->WriteHistory;
+ }
+
+ # Copy the kernel coverage data for each machine, if the kernel
+ # has been compiled with coverage instrumentation.
+ $log->nest("collecting coverage data", sub {
+ foreach my $vm (values %vms) {
+ my $gcovDir = "/sys/kernel/debug/gcov";
+
+ next unless $vm->isUp();
+
+ my ($status, $out) = $vm->execute("test -e $gcovDir");
+ next if $status != 0;
+
+ # Figure out where to put the *.gcda files so that the
+ # report generator can find the corresponding kernel
+ # sources.
+ my $kernelDir = $vm->mustSucceed("echo \$(dirname \$(readlink -f /run/current-system/kernel))/.build/linux-*");
+ chomp $kernelDir;
+ my $coverageDir = "/tmp/xchg/coverage-data/$kernelDir";
+
+ # Copy all the *.gcda files.
+ $vm->execute("for d in $gcovDir/nix/store/*/.build/linux-*; do for i in \$(cd \$d && find -name '*.gcda'); do echo \$i; mkdir -p $coverageDir/\$(dirname \$i); cp -v \$d/\$i $coverageDir/\$i; done; done");
+ }
+ });
+
+ $log->nest("syncing", sub {
+ foreach my $vm (values %vms) {
+ next unless $vm->isUp();
+ $vm->execute("sync");
+ }
+ });
+
+ if ($nrTests != 0) {
+ $log->log("$nrSucceeded out of $nrTests tests succeeded",
+ ($nrSucceeded < $nrTests ? { error => 1 } : { }));
+ }
+}
+
+
+# Create an empty raw virtual disk with the given name and size (in
+# MiB).
+sub createDisk {
+ my ($name, $size) = @_;
+ system("qemu-img create -f raw $name ${size}M") == 0
+ or die "cannot create image of size $size";
+}
+
+
+END {
+ $log->nest("cleaning up", sub {
+ foreach my $vm (values %vms) {
+ if ($vm->{pid}) {
+ $log->log("killing " . $vm->{name} . " (pid " . $vm->{pid} . ")");
+ kill 9, $vm->{pid};
+ }
+ }
+ });
+ $log->close();
+}
+
+my $now1 = clock_gettime(CLOCK_MONOTONIC);
+
+runTests;
+
+my $now2 = clock_gettime(CLOCK_MONOTONIC);
+
+printf STDERR "test script finished in %.2fs\n", $now2 - $now1;
+
+exit ($nrSucceeded < $nrTests ? 1 : 0);
diff --git a/nixpkgs/nixos/lib/test-driver/treebits.js b/nixpkgs/nixos/lib/test-driver/treebits.js
new file mode 100644
index 00000000000..9754093dfd0
--- /dev/null
+++ b/nixpkgs/nixos/lib/test-driver/treebits.js
@@ -0,0 +1,30 @@
+$(document).ready(function() {
+
+ /* When a toggle is clicked, show or hide the subtree. */
+ $(".logTreeToggle").click(function() {
+ if ($(this).siblings("ul:hidden").length != 0) {
+ $(this).siblings("ul").show();
+ $(this).text("-");
+ } else {
+ $(this).siblings("ul").hide();
+ $(this).text("+");
+ }
+ });
+
+ /* Implementation of the expand all link. */
+ $(".logTreeExpandAll").click(function() {
+ $(".logTreeToggle", $(this).parent().siblings(".toplevel")).map(function() {
+ $(this).siblings("ul").show();
+ $(this).text("-");
+ });
+ });
+
+ /* Implementation of the collapse all link. */
+ $(".logTreeCollapseAll").click(function() {
+ $(".logTreeToggle", $(this).parent().siblings(".toplevel")).map(function() {
+ $(this).siblings("ul").hide();
+ $(this).text("+");
+ });
+ });
+
+});
diff --git a/nixpkgs/nixos/lib/testing.nix b/nixpkgs/nixos/lib/testing.nix
new file mode 100644
index 00000000000..76706877103
--- /dev/null
+++ b/nixpkgs/nixos/lib/testing.nix
@@ -0,0 +1,269 @@
+{ system
+, pkgs ? import ../.. { inherit system config; }
+ # Use a minimal kernel?
+, minimal ? false
+ # Ignored
+, config ? {}
+ # Modules to add to each VM
+, extraConfigurations ? [] }:
+
+with import ./build-vms.nix { inherit system pkgs minimal extraConfigurations; };
+with pkgs;
+
+let
+ jquery-ui = callPackage ./testing/jquery-ui.nix { };
+ jquery = callPackage ./testing/jquery.nix { };
+
+in rec {
+
+ inherit pkgs;
+
+
+ testDriver = stdenv.mkDerivation {
+ name = "nixos-test-driver";
+
+ buildInputs = [ makeWrapper perl ];
+
+ dontUnpack = true;
+
+ preferLocalBuild = true;
+
+ installPhase =
+ ''
+ mkdir -p $out/bin
+ cp ${./test-driver/test-driver.pl} $out/bin/nixos-test-driver
+ chmod u+x $out/bin/nixos-test-driver
+
+ libDir=$out/${perl.libPrefix}
+ mkdir -p $libDir
+ cp ${./test-driver/Machine.pm} $libDir/Machine.pm
+ cp ${./test-driver/Logger.pm} $libDir/Logger.pm
+
+ wrapProgram $out/bin/nixos-test-driver \
+ --prefix PATH : "${lib.makeBinPath [ qemu_test vde2 netpbm coreutils ]}" \
+ --prefix PERL5LIB : "${with perlPackages; makePerlPath [ TermReadLineGnu XMLWriter IOTty FileSlurp ]}:$out/${perl.libPrefix}"
+ '';
+ };
+
+
+ # Run an automated test suite in the given virtual network.
+ # `driver' is the script that runs the network.
+ runTests = driver:
+ stdenv.mkDerivation {
+ name = "vm-test-run-${driver.testName}";
+
+ requiredSystemFeatures = [ "kvm" "nixos-test" ];
+
+ buildInputs = [ libxslt ];
+
+ buildCommand =
+ ''
+ mkdir -p $out/nix-support
+
+ LOGFILE=$out/log.xml tests='eval $ENV{testScript}; die $@ if $@;' ${driver}/bin/nixos-test-driver
+
+ # Generate a pretty-printed log.
+ xsltproc --output $out/log.html ${./test-driver/log2html.xsl} $out/log.xml
+ ln -s ${./test-driver/logfile.css} $out/logfile.css
+ ln -s ${./test-driver/treebits.js} $out/treebits.js
+ ln -s ${jquery}/js/jquery.min.js $out/
+ ln -s ${jquery-ui}/js/jquery-ui.min.js $out/
+
+ touch $out/nix-support/hydra-build-products
+ echo "report testlog $out log.html" >> $out/nix-support/hydra-build-products
+
+ for i in */xchg/coverage-data; do
+ mkdir -p $out/coverage-data
+ mv $i $out/coverage-data/$(dirname $(dirname $i))
+ done
+ '';
+ };
+
+
+ makeTest =
+ { testScript
+ , makeCoverageReport ? false
+ , enableOCR ? false
+ , name ? "unnamed"
+ , ...
+ } @ t:
+
+ let
+ # A standard store path to the vm monitor is built like this:
+ # /tmp/nix-build-vm-test-run-$name.drv-0/vm-state-machine/monitor
+ # The max filename length of a unix domain socket is 108 bytes.
+ # This means $name can at most be 50 bytes long.
+ maxTestNameLen = 50;
+ testNameLen = builtins.stringLength name;
+
+ testDriverName = with builtins;
+ if testNameLen > maxTestNameLen then
+ abort ("The name of the test '${name}' must not be longer than ${toString maxTestNameLen} " +
+ "it's currently ${toString testNameLen} characters long.")
+ else
+ "nixos-test-driver-${name}";
+
+ nodes = buildVirtualNetwork (
+ t.nodes or (if t ? machine then { machine = t.machine; } else { }));
+
+ testScript' =
+ # Call the test script with the computed nodes.
+ if lib.isFunction testScript
+ then testScript { inherit nodes; }
+ else testScript;
+
+ vlans = map (m: m.config.virtualisation.vlans) (lib.attrValues nodes);
+
+ vms = map (m: m.config.system.build.vm) (lib.attrValues nodes);
+
+ ocrProg = tesseract4.override { enableLanguages = [ "eng" ]; };
+
+ imagemagick_tiff = imagemagick_light.override { inherit libtiff; };
+
+ # Generate onvenience wrappers for running the test driver
+ # interactively with the specified network, and for starting the
+ # VMs from the command line.
+ driver = runCommand testDriverName
+ { buildInputs = [ makeWrapper];
+ testScript = testScript';
+ preferLocalBuild = true;
+ testName = name;
+ }
+ ''
+ mkdir -p $out/bin
+ echo "$testScript" > $out/test-script
+ ln -s ${testDriver}/bin/nixos-test-driver $out/bin/
+ vms=($(for i in ${toString vms}; do echo $i/bin/run-*-vm; done))
+ wrapProgram $out/bin/nixos-test-driver \
+ --add-flags "''${vms[*]}" \
+ ${lib.optionalString enableOCR
+ "--prefix PATH : '${ocrProg}/bin:${imagemagick_tiff}/bin'"} \
+ --run "export testScript=\"\$(cat $out/test-script)\"" \
+ --set VLANS '${toString vlans}'
+ ln -s ${testDriver}/bin/nixos-test-driver $out/bin/nixos-run-vms
+ wrapProgram $out/bin/nixos-run-vms \
+ --add-flags "''${vms[*]}" \
+ ${lib.optionalString enableOCR "--prefix PATH : '${ocrProg}/bin'"} \
+ --set tests 'startAll; joinAll;' \
+ --set VLANS '${toString vlans}' \
+ ${lib.optionalString (builtins.length vms == 1) "--set USE_SERIAL 1"}
+ ''; # "
+
+ passMeta = drv: drv // lib.optionalAttrs (t ? meta) {
+ meta = (drv.meta or {}) // t.meta;
+ };
+
+ test = passMeta (runTests driver);
+ report = passMeta (releaseTools.gcovReport { coverageRuns = [ test ]; });
+
+ nodeNames = builtins.attrNames nodes;
+ invalidNodeNames = lib.filter
+ (node: builtins.match "^[A-z_][A-z0-9_]+$" node == null) nodeNames;
+
+ in
+ if lib.length invalidNodeNames > 0 then
+ throw ''
+ Cannot create machines out of (${lib.concatStringsSep ", " invalidNodeNames})!
+ All machines are referenced as perl variables in the testing framework which will break the
+ script when special characters are used.
+
+ Please stick to alphanumeric chars and underscores as separation.
+ ''
+ else
+ (if makeCoverageReport then report else test) // {
+ inherit nodes driver test;
+ };
+
+ runInMachine =
+ { drv
+ , machine
+ , preBuild ? ""
+ , postBuild ? ""
+ , ... # ???
+ }:
+ let
+ vm = buildVM { }
+ [ machine
+ { key = "run-in-machine";
+ networking.hostName = "client";
+ nix.readOnlyStore = false;
+ virtualisation.writableStore = false;
+ }
+ ];
+
+ buildrunner = writeText "vm-build" ''
+ source $1
+
+ ${coreutils}/bin/mkdir -p $TMPDIR
+ cd $TMPDIR
+
+ exec $origBuilder $origArgs
+ '';
+
+ testScript = ''
+ startAll;
+ $client->waitForUnit("multi-user.target");
+ ${preBuild}
+ $client->succeed("env -i ${bash}/bin/bash ${buildrunner} /tmp/xchg/saved-env >&2");
+ ${postBuild}
+ $client->succeed("sync"); # flush all data before pulling the plug
+ '';
+
+ vmRunCommand = writeText "vm-run" ''
+ xchg=vm-state-client/xchg
+ ${coreutils}/bin/mkdir $out
+ ${coreutils}/bin/mkdir -p $xchg
+
+ for i in $passAsFile; do
+ i2=''${i}Path
+ _basename=$(${coreutils}/bin/basename ''${!i2})
+ ${coreutils}/bin/cp ''${!i2} $xchg/$_basename
+ eval $i2=/tmp/xchg/$_basename
+ ${coreutils}/bin/ls -la $xchg
+ done
+
+ unset i i2 _basename
+ export | ${gnugrep}/bin/grep -v '^xchg=' > $xchg/saved-env
+ unset xchg
+
+ export tests='${testScript}'
+ ${testDriver}/bin/nixos-test-driver ${vm.config.system.build.vm}/bin/run-*-vm
+ ''; # */
+
+ in
+ lib.overrideDerivation drv (attrs: {
+ requiredSystemFeatures = [ "kvm" ];
+ builder = "${bash}/bin/sh";
+ args = ["-e" vmRunCommand];
+ origArgs = attrs.args;
+ origBuilder = attrs.builder;
+ });
+
+
+ runInMachineWithX = { require ? [], ... } @ args:
+ let
+ client =
+ { ... }:
+ {
+ inherit require;
+ virtualisation.memorySize = 1024;
+ services.xserver.enable = true;
+ services.xserver.displayManager.slim.enable = false;
+ services.xserver.displayManager.auto.enable = true;
+ services.xserver.windowManager.default = "icewm";
+ services.xserver.windowManager.icewm.enable = true;
+ services.xserver.desktopManager.default = "none";
+ };
+ in
+ runInMachine ({
+ machine = client;
+ preBuild =
+ ''
+ $client->waitForX;
+ '';
+ } // args);
+
+
+ simpleTest = as: (makeTest as).test;
+
+}
diff --git a/nixpkgs/nixos/lib/testing/jquery-ui.nix b/nixpkgs/nixos/lib/testing/jquery-ui.nix
new file mode 100644
index 00000000000..e65107a3c2f
--- /dev/null
+++ b/nixpkgs/nixos/lib/testing/jquery-ui.nix
@@ -0,0 +1,24 @@
+{ stdenv, fetchurl, unzip }:
+
+stdenv.mkDerivation rec {
+ name = "jquery-ui-1.11.4";
+
+ src = fetchurl {
+ url = "http://jqueryui.com/resources/download/${name}.zip";
+ sha256 = "0ciyaj1acg08g8hpzqx6whayq206fvf4whksz2pjgxlv207lqgjh";
+ };
+
+ buildInputs = [ unzip ];
+
+ installPhase =
+ ''
+ mkdir -p "$out/js"
+ cp -rv . "$out/js"
+ '';
+
+ meta = {
+ homepage = http://jqueryui.com/;
+ description = "A library of JavaScript widgets and effects";
+ platforms = stdenv.lib.platforms.all;
+ };
+}
diff --git a/nixpkgs/nixos/lib/testing/jquery.nix b/nixpkgs/nixos/lib/testing/jquery.nix
new file mode 100644
index 00000000000..e272f66a576
--- /dev/null
+++ b/nixpkgs/nixos/lib/testing/jquery.nix
@@ -0,0 +1,36 @@
+{ stdenv, fetchurl, compressed ? true }:
+
+with stdenv.lib;
+
+stdenv.mkDerivation rec {
+ name = "jquery-1.11.3";
+
+ src = if compressed then
+ fetchurl {
+ url = "http://code.jquery.com/${name}.min.js";
+ sha256 = "1f4glgxxn3jnvry3dpzmazj3207baacnap5w20gr2xlk789idfgc";
+ }
+ else
+ fetchurl {
+ url = "http://code.jquery.com/${name}.js";
+ sha256 = "1v956yf5spw0156rni5z77hzqwmby7ajwdcd6mkhb6zvl36awr90";
+ };
+
+ dontUnpack = true;
+
+ installPhase =
+ ''
+ mkdir -p "$out/js"
+ cp -v "$src" "$out/js/jquery.js"
+ ${optionalString compressed ''
+ (cd "$out/js" && ln -s jquery.js jquery.min.js)
+ ''}
+ '';
+
+ meta = with stdenv.lib; {
+ description = "JavaScript library designed to simplify the client-side scripting of HTML";
+ homepage = http://jquery.com/;
+ license = licenses.mit;
+ platforms = platforms.all;
+ };
+}
diff --git a/nixpkgs/nixos/lib/utils.nix b/nixpkgs/nixos/lib/utils.nix
new file mode 100644
index 00000000000..a522834e429
--- /dev/null
+++ b/nixpkgs/nixos/lib/utils.nix
@@ -0,0 +1,139 @@
+pkgs: with pkgs.lib;
+
+rec {
+
+ # Check whenever fileSystem is needed for boot
+ fsNeededForBoot = fs: fs.neededForBoot
+ || elem fs.mountPoint [ "/" "/nix" "/nix/store" "/var" "/var/log" "/var/lib" "/etc" ];
+
+ # Check whenever `b` depends on `a` as a fileSystem
+ fsBefore = a: b: a.mountPoint == b.device
+ || hasPrefix "${a.mountPoint}${optionalString (!(hasSuffix "/" a.mountPoint)) "/"}" b.mountPoint;
+
+ # Escape a path according to the systemd rules, e.g. /dev/xyzzy
+ # becomes dev-xyzzy. FIXME: slow.
+ escapeSystemdPath = s:
+ replaceChars ["/" "-" " "] ["-" "\\x2d" "\\x20"]
+ (if hasPrefix "/" s then substring 1 (stringLength s) s else s);
+
+ # Returns a system path for a given shell package
+ toShellPath = shell:
+ if types.shellPackage.check shell then
+ "/run/current-system/sw${shell.shellPath}"
+ else if types.package.check shell then
+ throw "${shell} is not a shell package"
+ else
+ shell;
+
+ /* Recurse into a list or an attrset, searching for attrs named like
+ the value of the "attr" parameter, and return an attrset where the
+ names are the corresponding jq path where the attrs were found and
+ the values are the values of the attrs.
+
+ Example:
+ recursiveGetAttrWithJqPrefix {
+ example = [
+ {
+ irrelevant = "not interesting";
+ }
+ {
+ ignored = "ignored attr";
+ relevant = {
+ secret = {
+ _secret = "/path/to/secret";
+ };
+ };
+ }
+ ];
+ } "_secret" -> { ".example[1].relevant.secret" = "/path/to/secret"; }
+ */
+ recursiveGetAttrWithJqPrefix = item: attr:
+ let
+ recurse = prefix: item:
+ if item ? ${attr} then
+ nameValuePair prefix item.${attr}
+ else if isAttrs item then
+ map (name: recurse (prefix + "." + name) item.${name}) (attrNames item)
+ else if isList item then
+ imap0 (index: item: recurse (prefix + "[${toString index}]") item) item
+ else
+ [];
+ in listToAttrs (flatten (recurse "" item));
+
+ /* Takes an attrset and a file path and generates a bash snippet that
+ outputs a JSON file at the file path with all instances of
+
+ { _secret = "/path/to/secret" }
+
+ in the attrset replaced with the contents of the file
+ "/path/to/secret" in the output JSON.
+
+ When a configuration option accepts an attrset that is finally
+ converted to JSON, this makes it possible to let the user define
+ arbitrary secret values.
+
+ Example:
+ If the file "/path/to/secret" contains the string
+ "topsecretpassword1234",
+
+ genJqSecretsReplacementSnippet {
+ example = [
+ {
+ irrelevant = "not interesting";
+ }
+ {
+ ignored = "ignored attr";
+ relevant = {
+ secret = {
+ _secret = "/path/to/secret";
+ };
+ };
+ }
+ ];
+ } "/path/to/output.json"
+
+ would generate a snippet that, when run, outputs the following
+ JSON file at "/path/to/output.json":
+
+ {
+ "example": [
+ {
+ "irrelevant": "not interesting"
+ },
+ {
+ "ignored": "ignored attr",
+ "relevant": {
+ "secret": "topsecretpassword1234"
+ }
+ }
+ ]
+ }
+ */
+ genJqSecretsReplacementSnippet = genJqSecretsReplacementSnippet' "_secret";
+
+ # Like genJqSecretsReplacementSnippet, but allows the name of the
+ # attr which identifies the secret to be changed.
+ genJqSecretsReplacementSnippet' = attr: set: output:
+ let
+ secrets = recursiveGetAttrWithJqPrefix set attr;
+ in ''
+ if [[ -h '${output}' ]]; then
+ rm '${output}'
+ fi
+ ''
+ + concatStringsSep
+ "\n"
+ (imap1 (index: name: "export secret${toString index}=$(<'${secrets.${name}}')")
+ (attrNames secrets))
+ + "\n"
+ + "${pkgs.jq}/bin/jq >'${output}' '"
+ + concatStringsSep
+ " | "
+ (imap1 (index: name: ''${name} = $ENV.secret${toString index}'')
+ (attrNames secrets))
+ + ''
+ ' <<'EOF'
+ ${builtins.toJSON set}
+ EOF
+ '';
+}