Build: generate initrd images for the hypervisor.

This packages the VMs to create the initial RAM disk.

Change-Id: I31f1a0650583eb4122233beb0abaa933909cb389
diff --git a/BUILD.gn b/BUILD.gn
index 6d5dae0..4b21696 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -5,7 +5,7 @@
 
   deps = [
     ":hypervisor",
-    "//test/vm:test_vm($arch_toolchain)",
+    "//test/vm:test_vm_initrd($arch_toolchain)",
   ]
 }
 
diff --git a/build/BUILD.gn b/build/BUILD.gn
index 73c58b4..b8ec9fc 100644
--- a/build/BUILD.gn
+++ b/build/BUILD.gn
@@ -13,6 +13,7 @@
 
   cflags_c = [
     "-std=c11",
+    "-Wdeclaration-after-statement",
   ]
 
   cflags_cc = [
@@ -43,10 +44,16 @@
       "-fno-builtin",
       "-ffreestanding",
       "-fpic",
-      "-Wno-extended-offsetof", # have clang give us some slack
     ]
 
+    if (!use_gcc) {
+      cflags += [
+        "-Wno-extended-offsetof", # have clang give us some slack
+      ]
+    }
+
     defines += [
+      # TODO: move these build args for the platforms
       "MAX_CPUS=8",
       "MAX_VMS=16",
       "STACK_SIZE=4096",
diff --git a/build/image/convert_to_binary.py b/build/image/convert_to_binary.py
index 7900cf2..bf50a8e 100644
--- a/build/image/convert_to_binary.py
+++ b/build/image/convert_to_binary.py
@@ -6,7 +6,6 @@
 """
 
 import argparse
-import os
 import subprocess
 import sys
 
@@ -16,7 +15,7 @@
   parser.add_argument("--input", required=True)
   parser.add_argument("--output", required=True)
   args = parser.parse_args()
-  raw = subprocess.check_output([
+  subprocess.check_call([
       "{}objcopy".format(args.tool_prefix),
       "-O",
       "binary",
diff --git a/build/image/extract_offsets.py b/build/image/extract_offsets.py
index cf4f5c7..23e711a 100644
--- a/build/image/extract_offsets.py
+++ b/build/image/extract_offsets.py
@@ -8,7 +8,6 @@
 """
 
 import argparse
-import os
 import re
 import subprocess
 import sys
diff --git a/build/image/generate_initrd.py b/build/image/generate_initrd.py
new file mode 100644
index 0000000..488745f
--- /dev/null
+++ b/build/image/generate_initrd.py
@@ -0,0 +1,55 @@
+#!/usr/bin/env python
+
+"""Generate an initial RAM disk for the hypervisor.
+
+Packages the VMs, initrds for the VMs and the list of secondary VMs (vms.txt)
+into an initial RAM disk image.
+"""
+
+import argparse
+import os
+import shutil
+import subprocess
+import sys
+
+def Main():
+  parser = argparse.ArgumentParser()
+  parser.add_argument("--primary_vm", required=True)
+  parser.add_argument("--primary_vm_initrd")
+  parser.add_argument("--secondary_vm", action="append", nargs=4, metavar=("MEMORY", "CORES", "NAME", "IMAGE"))
+  parser.add_argument("--staging", required=True)
+  parser.add_argument("--output", required=True)
+  args = parser.parse_args()
+  # Prepare the primary VM image.
+  staged_files = ["vmlinuz"]
+  shutil.copyfile(args.primary_vm, os.path.join(args.staging, "vmlinuz"))
+  # Prepare the primary VM's initrd. Currently, it just makes an empty one.
+  if args.primary_vm_initrd:
+    raise NotImplementedError("This doesn't copy the primary VM's initrd yet")
+  with open(os.path.join(args.staging, "initrd.img"), "w") as vms_txt:
+    staged_files.append("initrd.img")
+  # Prepare the secondary VMs.
+  with open(os.path.join(args.staging, "vms.txt"), "w") as vms_txt:
+    staged_files.append("vms.txt")
+    if args.secondary_vm:
+      for vm in args.secondary_vm:
+        (vm_memory, vm_cores, vm_name, vm_image) = vm
+        staged_files.append(vm_name)
+        shutil.copy(vm_image, os.path.join(args.staging, vm_name))
+        vms_txt.write("{} {} {}\n".format(vm_memory, vm_cores, vm_name))
+  # Package files into an initial RAM disk.
+  with open(args.output, "w") as initrd:
+    # Move into the staging directory so the file names taken by cpio don't
+    # include the path.
+    os.chdir(args.staging)
+    cpio = subprocess.Popen([
+        "cpio",
+        "--create"],
+        stdin=subprocess.PIPE,
+        stdout=initrd,
+        stderr=subprocess.PIPE)
+    cpio.communicate(input="\n".join(staged_files).encode("utf-8"))
+  return 0
+
+if __name__ == "__main__":
+  sys.exit(Main())
diff --git a/build/image/image.gni b/build/image/image.gni
index afc4e79..a24b81c 100644
--- a/build/image/image.gni
+++ b/build/image/image.gni
@@ -1,10 +1,11 @@
 import("//build/arch/common.gni")
 
+# Build image, link to an ELF file then convert to plain binary.
 template("image_binary") {
   assert(defined(invoker.image_name),
-         "image_binary() must specify a \"image_name\" value")
+         "image_binary() must specify an \"image_name\" value")
   assert(defined(invoker.origin_address),
-         "image_binary() must specify a \"origin_address\" value")
+         "image_binary() must specify an \"origin_address\" value")
 
   output_root = ""
   if (defined(invoker.output_path)) {
@@ -98,3 +99,66 @@
     origin_address = "0x1000"
   }
 }
+
+# Build the initial RAM disk for the hypervisor.
+template("initrd") {
+  assert(defined(invoker.primary_vm),
+         "initrd() must specify a \"primary_vm\" value")
+
+  action(target_name) {
+    forward_variables_from(invoker, [ "testonly" ])
+    script = "//build/image/generate_initrd.py"
+
+    initrd_base = "${root_out_dir}/initrd/${target_name}"
+    initrd_file = "${initrd_base}.img"
+    initrd_staging = "${initrd_base}"
+    vm_dir = rebase_path("${root_out_dir}/vm/")
+
+    deps = [
+      invoker.primary_vm,
+    ]
+
+    args = [
+      "--primary_vm",
+      vm_dir + get_label_info(invoker.primary_vm, "name") + ".bin",
+      "--staging",
+      rebase_path(initrd_staging),
+      "--output",
+      rebase_path(initrd_file),
+    ]
+
+    # Add the info about the secondary VMs. The information about the VMs is
+    # encoded in lists with the following elements:
+    #
+    #    1. Memory in bytes.
+    #    2. Number of cores.
+    #    3. File name for the VM image.
+    #    4. Build target for the VM.
+    #
+    # TODO: is there a less hacky way to do this?
+    if (defined(invoker.secondary_vms)) {
+      foreach(vm, invoker.secondary_vms) {
+        args += ["--secondary_vm"]
+
+        # This is a hack to find the field which names the build target for the
+        # VM so it can be added to the dependencies and the image path be
+        # passed to the script. The target will be a path or have a semicolon
+        # so getting the label name will yield a diferent value. The other
+        # fields are simple text so won't change.
+        foreach (field, vm) {
+          if (get_label_info(field, "name") != field) {
+            deps += [field]
+            args += [vm_dir + get_label_info(field, "name") + ".bin"]
+          } else {
+            args += [field]
+          }
+        }
+      }
+    }
+
+    outputs = [
+      initrd_file,
+      "${initrd_staging}/vms.txt",
+    ]
+  }
+}
diff --git a/test/vm/BUILD.gn b/test/vm/BUILD.gn
index 7216b67..82f6f64 100644
--- a/test/vm/BUILD.gn
+++ b/test/vm/BUILD.gn
@@ -14,3 +14,8 @@
     "//src/arch/${arch}:entry",
   ]
 }
+
+initrd("test_vm_initrd") {
+  testonly = true
+  primary_vm = ":test_vm"
+}