Switch from Makefile to GN

GN makes it easier for us to have a modular build where we can define
tests and reuse code without having to hack on Makefiles. It also has
tools to analyze the build and comes with extensive documentation.

Change-Id: Ib28bc7b68d429e3c3193784c7a80d05ee35c6295
diff --git a/.gn b/.gn
new file mode 100644
index 0000000..e5b6d4a
--- /dev/null
+++ b/.gn
@@ -0,0 +1,2 @@
+# The location of the build configuration file.
+buildconfig = "//build/BUILDCONFIG.gn"
diff --git a/BUILD.gn b/BUILD.gn
new file mode 100644
index 0000000..4c818a3
--- /dev/null
+++ b/BUILD.gn
@@ -0,0 +1,23 @@
+import("//build/image/hypervisor.gni")
+
+group("host_tools") {
+  deps = [
+    ":hafnium($arch_toolchain)",
+  ]
+}
+
+executable("test") {
+  sources = [
+    "test.cpp",
+  ]
+}
+
+# Only build the image for the arch
+if (current_toolchain == arch_toolchain) {
+  hypervisor("hafnium") {
+    deps = [
+      "//src",
+      "//src/arch/${arch}",
+    ]
+  }
+}
diff --git a/Makefile b/Makefile
index 888e823..8817e03 100644
--- a/Makefile
+++ b/Makefile
@@ -1,177 +1,32 @@
-ROOT_DIR := $(dir $(lastword $(MAKEFILE_LIST)))
-ifeq ($(ROOT_DIR),./)
-  ROOT_DIR :=
-endif
+OUT ?= out
 
-#
-# Defaults.
-#
-ARCH ?= aarch64
-PLAT ?= qemu
-DEBUG ?= 1
-NAME := hafnium
+GN ?= ../gn/out/gn
+NINJA ?= ninja
 
-# Toolchain
-CROSS_COMPILE ?= aarch64-linux-gnu-
-TARGET := $(patsubst %-,%,$(CROSS_COMPILE))
+all: $(OUT)/build.ninja
+	@$(NINJA) -C $(OUT)
 
-ifeq ($(CLANG),1)
-  CLANG := clang
-endif
-GCC ?= gcc
+out/build.ninja: $(GN)
+	@$(GN) gen $(OUT)
 
-ifdef CLANG
-  CC := $(CLANG) -target $(TARGET)
-else
-  CC := $(CROSS_COMPILE)$(GCC)
-endif
-
-# Output
-OUT := $(ROOT_DIR)out/$(ARCH)/$(PLAT)
-
-all: $(OUT)/$(NAME).bin
-
-# Include platform-specific constants.
-include $(ROOT_DIR)src/arch/$(ARCH)/$(PLAT).mk
-
-define include_module
-  SRCS :=
-  OFFSET_SRCS :=
-  include $(ROOT_DIR)$(1)/rules.mk
-  GLOBAL_SRCS += $$(addprefix $(1)/,$$(SRCS))
-  GLOBAL_OFFSET_SRCS += $$(addprefix $(1)/,$$(OFFSET_SRCS))
-endef
-
-#
-# Include each module.
-#
-MODULES := src
-MODULES += src/arch/$(ARCH)
-GLOBAL_SRCS :=
-GLOBAL_OFFSET_SRCS :=
-$(foreach mod,$(MODULES),$(eval $(call include_module,$(mod))))
-
-#
-# Rules to build C files.
-#
-COPTS = -mcpu=cortex-a57+nofp
-COPTS += -fno-stack-protector
-COPTS += -fno-builtin -ffreestanding
-COPTS += -g
-COPTS += -O2
-COPTS += -fpic
-COPTS += -std=c11
-COPTS += -Wall -Wpedantic -Werror
-COPTS += -Wno-extended-offsetof
-COPTS += -DDEBUG=$(DEBUG)
-COPTS += -DMAX_CPUS=8
-COPTS += -DMAX_VMS=16
-COPTS += -DSTACK_SIZE=4096
-COPTS += -I$(ROOT_DIR)inc
-COPTS += -I$(ROOT_DIR)src/arch/$(ARCH)/inc
-COPTS += -I$(OUT)/arch/$(ARCH)/inc
-COPTS += -DGICD_BASE=$(GICD_BASE)
-COPTS += -DGICC_BASE=$(GICC_BASE)
-COPTS += -DGICR_BASE=$(GICR_BASE)
-COPTS += -DTIMER_IRQ=$(TIMER_IRQ)
-
-ifeq ($(PL011),1)
-  COPTS += -DPL011_BASE=$(PL011_BASE)
-endif
-
-DEP_GEN = -MMD -MP -MF $$(patsubst %,%.d,$$@)
-
-define build_c
-  TGT := $(patsubst %.c,%.o,$(OUT)/$(patsubst src/%,%,$(1)))
-  GLOBAL_OBJS += $$(TGT)
-  REMAIN_SRCS := $$(filter-out $(1),$$(REMAIN_SRCS))
-$$(TGT): $(ROOT_DIR)$(1) $(GLOBAL_OFFSETS) | $$(dir $$(TGT))
-	$$(info CC $(ROOT_DIR)$1)
-	@$(CC) $(COPTS) $(DEP_GEN) -c $(ROOT_DIR)$(1) -o $$@
-endef
-
-#
-# Rules to generate offsets.
-#
-define gen_offsets
-  TMP := $(patsubst src/%,%,$(1))
-  TMP := $$(dir $$(TMP))inc/$$(notdir $$(TMP))
-  TGT := $$(patsubst %.c,%.h,$(OUT)/$$(TMP))
-  GLOBAL_OFFSETS += $$(TGT)
-$$(TGT): $(ROOT_DIR)$(1) | $$(dir $$(TGT))
-	$$(info GENOFFSET $(ROOT_DIR)$1)
-	@$(CC) -DGEN_OFFSETS $(COPTS) $(DEP_GEN) -MT $$@ -S -c $(ROOT_DIR)$(1) -o - | \
-		grep ^DEFINE_OFFSET -A1 | \
-		grep -v ^--$ | \
-		sed 's/^DEFINE_OFFSET__\([^:]*\):/#define \1 \\/' | \
-		sed 's/\.zero.*/0/' | \
-		sed 's/\.[^\t][^\t]*//' > $$@
-endef
-
-#
-# Rules to build S files.
-#
-define build_S
-  TGT := $(patsubst %.S,%.o,$(OUT)/$(patsubst src/%,%,$(1)))
-  GLOBAL_OBJS += $$(TGT)
-  REMAIN_SRCS := $$(filter-out $(1),$$(REMAIN_SRCS))
-$$(TGT): $(ROOT_DIR)$(1) $(GLOBAL_OFFSETS) | $$(dir $$(TGT))
-	$$(info AS $(ROOT_DIR)$1)
-	@$(CC) $(COPTS) $(DEP_GEN) -c $(ROOT_DIR)$(1) -o $$@
-endef
-
-#
-# Generate the build rules for all .c and .S files.
-#
-GLOBAL_OBJS :=
-GLOBAL_OFFSETS :=
-REMAIN_SRCS := $(GLOBAL_SRCS)
-$(foreach file,$(filter %.c,$(GLOBAL_OFFSET_SRCS)),$(eval $(call gen_offsets,$(file))))
-$(foreach file,$(filter %.c,$(GLOBAL_SRCS)),$(eval $(call build_c,$(file))))
-$(foreach file,$(filter %.S,$(GLOBAL_SRCS)),$(eval $(call build_S,$(file))))
-
-#
-# Check if there are any source files which we don't know to handle.
-#
-ifneq ($(REMAIN_SRCS),)
-  $(error Don't know how to handle $(REMAIN_SRCS))
-endif
-
-#
-# Rule to create all output directories.
-#
-define create_dir
-$1:
-	@mkdir -p $1
-endef
-$(foreach name,$(sort $(dir $(GLOBAL_OBJS))),$(eval $(call create_dir,$(name))))
-$(foreach name,$(sort $(dir $(GLOBAL_OFFSETS))),$(eval $(call create_dir,$(name))))
-
-#
-# Rules to build the hypervisor.
-#
-$(OUT)/$(NAME): $(GLOBAL_OBJS) $(ROOT_DIR)src/$(NAME).ld
-	$(info LD $(ROOT_DIR)src/$(NAME).ld)
-	@$(CROSS_COMPILE)ld -g -pie $(GLOBAL_OBJS) -T$(ROOT_DIR)src/$(NAME).ld --defsym PREFERRED_LOAD_ADDRESS=$(LOAD_ADDRESS) -o $@
-
-$(OUT)/$(NAME).bin: $(OUT)/$(NAME)
-	$(info OBJCOPY $@)
-	@$(CROSS_COMPILE)objcopy -O binary $< $@
+$(GN):
+	git clone https://gn.googlesource.com/gn ../gn
+	cd ../gn && python build/gen.py
+	ninja -C ../gn/out
 
 clean:
-	rm -rf $(ROOT_DIR)out
+	@$(NINJA) -C $(OUT) -t clean
 
-#
-# Rules for code health
-#
+clobber:
+	rm -rf $(OUT)
 
 # see .clang-format
 format:
-	@find $(ROOT_DIR)src/ -name *.c -o -name *.h | xargs clang-format -style file -i
-	@find $(ROOT_DIR)inc/ -name *.c -o -name *.h | xargs clang-format -style file -i
+	@find src/ -name *.c -o -name *.h | xargs clang-format -style file -i
+	@find inc/ -name *.c -o -name *.h | xargs clang-format -style file -i
+	@find . -name *.gn -o -name *.gni -exec $(GN) format {} \;
 
+# TODO: get this working again. Need to extract a compile database to get the correct args.
 # see .clang-tidy
-tidy: $(GLOBAL_OFFSETS)
-	@find $(ROOT_DIR)src/ -name *.c -exec clang-tidy {} -fix -- -target $(TARGET) $(COPTS) \;
-
--include $(patsubst %,%.d,$(GLOBAL_OBJS),$(GLOBAL_OFFSETS))
+# tidy: $(GLOBAL_OFFSETS)
+# 	@find $(ROOT_DIR)src/ -name *.c -exec clang-tidy {} -fix -- -target $(TARGET) $(COPTS) \;
diff --git a/build/BUILD.gn b/build/BUILD.gn
new file mode 100644
index 0000000..9a0bb6e
--- /dev/null
+++ b/build/BUILD.gn
@@ -0,0 +1,67 @@
+import("//build/arch/${arch}/${arch_platform}.gni")
+
+config("compiler_defaults") {
+  cflags = [
+    "-g",
+    "-O2",
+    "-Wall",
+    #"-Wextra",
+    "-Wpedantic",
+    "-Werror",
+  ]
+
+  cflags_c = [
+    "-std=c11",
+  ]
+
+  cflags_cc = [
+    "-std=c++14",
+  ]
+
+  if (is_debug) {
+    defines = [
+      "DEBUG=1",
+    ]
+  } else {
+    defines = [
+      "DEBUG=0",
+    ]
+  }
+
+  # Configuration specific for the bare metal images
+  if (current_toolchain == arch_toolchain) {
+    include_dirs = [
+      "//inc",
+      "//src/arch/${arch}/inc",
+      "${root_gen_dir}/inc",
+    ]
+
+    cflags += [
+      "-mcpu=${arch_cpu}",
+      "-fno-stack-protector",
+      "-fno-builtin",
+      "-ffreestanding",
+      "-fpic",
+      "-Wno-extended-offsetof", # have clang give us some slack
+    ]
+
+    defines += [
+      "MAX_CPUS=8",
+      "MAX_VMS=16",
+      "STACK_SIZE=4096",
+    ]
+
+    # TODO: this should be arch specific but it is currenly used by the
+    # platform generic code to map the memory
+    if (use_pl011) {
+      defines += [
+        "PL011_BASE=${pl011_base_address}",
+      ]
+    }
+  }
+}
+
+config("executable_ldconfig") {
+  ldflags = [
+  ]
+}
diff --git a/build/BUILDCONFIG.gn b/build/BUILDCONFIG.gn
new file mode 100644
index 0000000..677544e
--- /dev/null
+++ b/build/BUILDCONFIG.gn
@@ -0,0 +1,87 @@
+# This build configuration extends GN's cross-compilation concepts of "target",
+# "host" and "current" with "arch" which is used to refer to the embedded
+# architecture that the bare metal images are being built for.
+#
+# The concepts of "target", "host" and "current" are used when bulding tests
+# and utilities for the non-embedded device. Note that there hasn't been any
+# thought given to support building for anything other than the host so it
+# probably won't work.
+#
+# In summary:
+#  - host{_os,_cpu,_toolchain} is for the system running the build
+#  - target{_os,_cpu,_toolchain} is for the system that will run utilities and tests
+#  - arch{,_toolchain} is for the embedded system that will run the bare metal images
+
+# Configuration of the build toolchain.
+declare_args() {
+  # Enable extra debugging.
+  is_debug = true
+
+  # Set to true to build with gcc (default is clang).
+  use_gcc = false
+
+  # The architecture to build the bare metal images for.
+  arch = "aarch64"
+
+  # The platform to build the bare metal images for.
+  arch_platform = "qemu"
+
+  # Set by arch toolchain. Prefix for binutils tools.
+  arch_tool_prefix = ""
+}
+
+# Check that we support the attempted build
+assert(host_os == "linux", "Only linux builds are currently supported")
+
+# Check that the requested architecture is supported
+assert(arch == "aarch64", "Unsupported arch: $arch")
+
+# Setup the standard variables
+if (target_os == "") {
+  target_os = host_os
+}
+if (target_cpu == "") {
+  target_cpu = host_cpu
+}
+if (current_os == "") {
+  current_os = target_os
+}
+if (current_cpu == "") {
+  current_cpu = target_cpu
+}
+
+assert(target_os == host_os, "Cross compiles not yet supported")
+assert(target_cpu == host_cpu, "Cross compiles not yet supported")
+
+# All binary targets will get this list of configs by default
+_shared_binary_target_configs = [ "//build:compiler_defaults" ]
+
+# Apply that default list to the binary target types.
+set_defaults("executable") {
+  configs = _shared_binary_target_configs
+  # Executables get this additional configuration
+  configs += [ "//build:executable_ldconfig" ]
+}
+set_defaults("static_library") {
+  configs = _shared_binary_target_configs
+}
+set_defaults("shared_library") {
+  configs = _shared_binary_target_configs
+}
+set_defaults("source_set") {
+  configs = _shared_binary_target_configs
+}
+
+# Select host, target and arch toolchains
+if (use_gcc) {
+  host_toolchain = "//build/toolchain/host:gcc"
+  target_toolchain = "//build/toolchain/host:gcc"
+  arch_toolchain = "//build/toolchain/arch:gcc_${arch}"
+} else {
+  host_toolchain = "//build/toolchain/host:clang"
+  target_toolchain = "//build/toolchain/host:clang"
+  arch_toolchain = "//build/toolchain/arch:clang_${arch}"
+}
+
+# The default toolchain is the target toolchain for building utilities and tests
+set_default_toolchain(target_toolchain)
diff --git a/build/arch/aarch64/hikey.gni b/build/arch/aarch64/hikey.gni
new file mode 100644
index 0000000..f870611
--- /dev/null
+++ b/build/arch/aarch64/hikey.gni
@@ -0,0 +1,13 @@
+declare_args() {
+  # The specific CPU that runs the architecture.
+  arch_cpu = "cortex-a57+nofp"
+
+  # The load address of the hypervisor
+  hypervisor_load_address = "0x01000000"
+
+  # Whether to include the PrimeCell UART (PL011) driver.
+  use_pl011 = true
+
+  # The base address of the PrimeCell UART (PL011) device.
+  pl011_base_address = "0xf8015000"
+}
diff --git a/build/arch/aarch64/qemu.gni b/build/arch/aarch64/qemu.gni
new file mode 100644
index 0000000..4962ab8
--- /dev/null
+++ b/build/arch/aarch64/qemu.gni
@@ -0,0 +1,13 @@
+declare_args() {
+  # The specific CPU that runs the architecture.
+  arch_cpu = "cortex-a57+nofp"
+
+  # The load address of the hypervisor
+  hypervisor_load_address = "0x40001000"
+
+  # Whether to include the PrimeCell UART (PL011) driver.
+  use_pl011 = true
+
+  # The base address of the PrimeCell UART (PL011) device.
+  pl011_base_address = "0x09000000"
+}
diff --git a/build/image/convert_to_binary.py b/build/image/convert_to_binary.py
new file mode 100644
index 0000000..7900cf2
--- /dev/null
+++ b/build/image/convert_to_binary.py
@@ -0,0 +1,28 @@
+#!/usr/bin/env python
+
+"""Convert a file to binary format.
+
+Calls objcopy to convert a file into raw binary format.
+"""
+
+import argparse
+import os
+import subprocess
+import sys
+
+def Main():
+  parser = argparse.ArgumentParser()
+  parser.add_argument("--tool_prefix", required=True)
+  parser.add_argument("--input", required=True)
+  parser.add_argument("--output", required=True)
+  args = parser.parse_args()
+  raw = subprocess.check_output([
+      "{}objcopy".format(args.tool_prefix),
+      "-O",
+      "binary",
+      args.input,
+      args.output])
+  return 0
+
+if __name__ == "__main__":
+  sys.exit(Main())
diff --git a/build/image/extract_offsets.py b/build/image/extract_offsets.py
new file mode 100644
index 0000000..cf4f5c7
--- /dev/null
+++ b/build/image/extract_offsets.py
@@ -0,0 +1,47 @@
+#!/usr/bin/env python
+
+"""Extract embedded offsets from an object file.
+
+We let the compiler calculate the offsets it is going to use and have those
+emitted into and object file. This is the next pass which extracts those offsets
+and stores them in a header file for the assembly to include and use.
+"""
+
+import argparse
+import os
+import re
+import subprocess
+import sys
+
+def Main():
+  parser = argparse.ArgumentParser()
+  parser.add_argument("--tool_prefix", required=True)
+  parser.add_argument("--input", required=True)
+  parser.add_argument("--output", required=True)
+  args = parser.parse_args()
+  raw = subprocess.check_output([
+      "{}objdump".format(args.tool_prefix),
+      "--disassemble-all",
+      "--section",
+      ".rodata",
+      args.input])
+  lines = raw.split('\n')
+  with open(args.output, 'w') as header:
+    header.write('#pragma once\n\n')
+    for n in range(len(lines)):
+      # Find a defined offset
+      match = re.match('.+DEFINE_OFFSET__([^>]+)', lines[n])
+      if not match:
+        continue
+      name = match.group(1)
+      # The next line tells the offset
+      if "..." in lines[n + 1]:
+        offset = 0
+      else:
+        offset = re.match('.+\.[\S]+\s+(.+)$', lines[n + 1]).group(1)
+      # Write the offset to the header
+      header.write("#define {} {}\n".format(name, offset))
+  return 0
+
+if __name__ == "__main__":
+  sys.exit(Main())
diff --git a/build/image/generate_offsets.gni b/build/image/generate_offsets.gni
new file mode 100644
index 0000000..61b77f4
--- /dev/null
+++ b/build/image/generate_offsets.gni
@@ -0,0 +1,78 @@
+# Calculates offsets of fields in C structures for use in assembly.
+template("generate_offsets") {
+  # There are 3 targets involved:
+  #  1. have the compiler calculate the offsets
+  #  2. extract those offsets to a header file
+  #  3. include the offsets in the build for validation
+  obj_target = "${target_name}__obj"
+  header_target = "${target_name}__header"
+  validate_target = target_name
+
+  # Have the compiler calculate the offsets and store that information in the
+  # object file to extract later.
+  source_set(obj_target) {
+    forward_variables_from(invoker,
+                           [
+                             "cflags",
+                             "cflags_c",
+                             "defines",
+                             "deps",
+                             "public_deps",
+                             "sources",
+                             "test_only",
+                           ])
+    defines = [ "GEN_OFFSETS" ]
+    visibility = [ ":${header_target}" ]
+  }
+
+  # Extract the offset information we've had the compiler emit into the object
+  # file.
+  action_foreach("${header_target}") {
+    forward_variables_from(invoker,
+                           [
+                             "cflags",
+                             "cflags_c",
+                             "defines",
+                             "deps",
+                             "public_deps",
+                             "sources",
+                             "test_only",
+                           ])
+
+    script = "//build/image/extract_offsets.py"
+    deps = [
+      ":${obj_target}",
+    ]
+    args = [
+      "--tool_prefix",
+      arch_tool_prefix,
+      "--input",
+      rebase_path("${target_out_dir}/${obj_target}.{{source_name_part}}.o"),
+      "--output",
+      rebase_path("${root_gen_dir}/inc/{{source_name_part}}.h"),
+    ]
+    outputs = [
+      "${root_gen_dir}/inc/{{source_name_part}}.h",
+    ]
+    visibility = [ ":${validate_target}" ]
+  }
+
+  # Include the offset source file in the build so the extracted offsets can be
+  # validated.
+  source_set(validate_target) {
+    forward_variables_from(invoker,
+                           [
+                             "cflags",
+                             "cflags_c",
+                             "defines",
+                             "deps",
+                             "public_deps",
+                             "sources",
+                             "test_only",
+                           ])
+
+    deps = [
+      ":${header_target}",
+    ]
+  }
+}
diff --git a/build/image/hypervisor.gni b/build/image/hypervisor.gni
new file mode 100644
index 0000000..a2bd03e
--- /dev/null
+++ b/build/image/hypervisor.gni
@@ -0,0 +1,48 @@
+import("//build/arch/${arch}/${arch_platform}.gni")
+
+# Helper to build a hypervisor image
+template("hypervisor") {
+  # Link objects together
+  executable("${target_name}__elf") {
+    forward_variables_from(invoker,
+                           [
+                             "cflags",
+                             "cflags_c",
+                             "defines",
+                             "deps",
+                             "public_deps",
+                             "sources",
+                             "test_only",
+                           ])
+    output_name = "${invoker.target_name}.elf"
+    ldflags = [
+      "-pie",
+      "-T",
+      rebase_path("//build/image/hypervisor.ld"),
+      "--defsym=PREFERRED_LOAD_ADDRESS=${hypervisor_load_address}",
+    ]
+    visibility = [ ":${invoker.target_name}" ]
+  }
+
+  action(target_name) {
+    file_root = rebase_path("${root_out_dir}/${target_name}")
+    elf_file = "${file_root}.elf"
+    bin_file = "${file_root}.bin"
+
+    script = "//build/image/convert_to_binary.py"
+    deps = [
+      ":${target_name}__elf",
+    ]
+    args = [
+      "--tool_prefix",
+      arch_tool_prefix,
+      "--input",
+      elf_file,
+      "--output",
+      bin_file,
+    ]
+    outputs = [
+      "${target_out_dir}/${target_name}.bin",
+    ]
+  }
+}
diff --git a/src/hafnium.ld b/build/image/hypervisor.ld
similarity index 100%
rename from src/hafnium.ld
rename to build/image/hypervisor.ld
diff --git a/build/toolchain/arch/BUILD.gn b/build/toolchain/arch/BUILD.gn
new file mode 100644
index 0000000..f85c3af
--- /dev/null
+++ b/build/toolchain/arch/BUILD.gn
@@ -0,0 +1,112 @@
+# Target toolchain specific build arguments.
+declare_args() {
+  # Build with a specific compiler version e.g. when building with clang, set
+  # to "3.9" to build with `clang-3.9`.
+  arch_cc_version = ""
+}
+
+# Template for target toolchains; there is no support for C++ or libraries.
+# Instead, use source_set to group source together.
+template("cc_toolchain") {
+  toolchain(target_name) {
+    assert(defined(invoker.cc), "cc_toolchain() must specify a \"cc\" value")
+    assert(defined(invoker.ld), "cc_toolchain() must specify a \"ld\" value")
+
+    # Combine compiler version and compiler specific flags for this toolchain.
+    cc = "${invoker.cc}"
+    if (arch_cc_version != "") {
+      cc += "-${arch_cc_version}"
+    }
+    if (defined(invoker.cflags)) {
+      cc += " ${invoker.cflags}"
+    }
+
+    tool("cc") {
+      depfile = "{{output}}.d"
+      command = "${cc} -MMD -MF $depfile {{defines}} {{include_dirs}} {{cflags}} {{cflags_c}} -c {{source}} -o {{output}}"
+      depsformat = "gcc"
+      description = "CC {{output}}"
+      outputs = [
+        "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o",
+      ]
+    }
+
+    tool("asm") {
+      depfile = "{{output}}.d"
+      command = "${cc} -MMD -MF $depfile {{defines}} {{include_dirs}} {{asmflags}} -c {{source}} -o {{output}}"
+      depsformat = "gcc"
+      description = "ASM {{output}}"
+      outputs = [
+        "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o",
+      ]
+    }
+
+    tool("link") {
+      outfile = "{{output_dir}}/{{target_output_name}}{{output_extension}}"
+      rspfile = "$outfile.rsp"
+      command = "${invoker.ld} {{ldflags}} -o $outfile --start-group @$rspfile --end-group"
+      description = "LINK $outfile"
+      default_output_dir = "{{root_out_dir}}"
+      rspfile_content = "{{inputs}}"
+      outputs = [
+        outfile,
+      ]
+    }
+
+    tool("stamp") {
+      command = "touch {{output}}"
+      description = "STAMP {{output}}"
+    }
+
+    tool("copy") {
+      command = "cp -af {{source}} {{output}}"
+      description = "COPY {{source}} {{output}}"
+    }
+
+    toolchain_args = {
+      forward_variables_from(invoker.toolchain_args, "*")
+    }
+  }
+}
+
+# Specialize for clang or gcc
+template("clang_toolchain") {
+  assert(defined(invoker.target), "clang_toolchain() must specify a \"target\" value")
+  assert(defined(invoker.arch_tool_prefix), "gcc_toolchain() must specify a \"arch_tool_prefix\" value")
+
+  cc_toolchain(target_name) {
+    cc = "clang"
+    cflags = "-target ${invoker.target}"
+    ld = "${invoker.arch_tool_prefix}ld"
+
+    toolchain_args = {
+      arch_tool_prefix = invoker.arch_tool_prefix
+    }
+  }
+}
+
+template("gcc_toolchain") {
+  assert(defined(invoker.arch_tool_prefix), "gcc_toolchain() must specify a \"arch_tool_prefix\" value")
+
+  cc_toolchain(target_name) {
+    cc = "${invoker.arch_tool_prefix}gcc"
+    ld = "${invoker.arch_tool_prefix}ld"
+
+    toolchain_args = {
+      arch_tool_prefix = invoker.arch_tool_prefix
+    }
+  }
+}
+
+# Specialize for different architectures
+
+clang_toolchain("clang_aarch64") {
+  target = "aarch64-none-eabi"
+  # TODO: below isn't right for bare metal code, but it works fine
+  arch_tool_prefix = "aarch64-linux-gnu-"
+}
+
+gcc_toolchain("gcc_aarch64") {
+  # TODO: below isn't right for bare metal code, but it works fine
+  arch_tool_prefix = "aarch64-linux-gnu-"
+}
diff --git a/build/toolchain/host/BUILD.gn b/build/toolchain/host/BUILD.gn
new file mode 100644
index 0000000..0c4e52e
--- /dev/null
+++ b/build/toolchain/host/BUILD.gn
@@ -0,0 +1,102 @@
+# Template for host toolchains.
+template("cc_toolchain") {
+  toolchain(target_name) {
+    assert(defined(invoker.ar), "cc_toolchain() must specify a \"ar\" value")
+    assert(defined(invoker.cc), "cc_toolchain() must specify a \"cc\" value")
+    assert(defined(invoker.cxx), "cc_toolchain() must specify a \"cxx\" value")
+
+    tool("cc") {
+      depfile = "{{output}}.d"
+      command = "${invoker.cc} -MMD -MF $depfile {{defines}} {{include_dirs}} {{cflags}} {{cflags_c}} -c {{source}} -o {{output}}"
+      depsformat = "gcc"
+      description = "CC {{output}}"
+      outputs = [
+        "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o",
+      ]
+    }
+
+    tool("cxx") {
+      depfile = "{{output}}.d"
+      command = "${invoker.cxx} -MMD -MF $depfile {{defines}} {{include_dirs}} {{cflags}} {{cflags_cc}} -c {{source}} -o {{output}}"
+      depsformat = "gcc"
+      description = "CXX {{output}}"
+      outputs = [
+        "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o",
+      ]
+    }
+
+    tool("alink") {
+      rspfile = "{{output}}.rsp"
+      command = "rm -f {{output}} && ${invoker.ar} rcs {{output}} @$rspfile"
+      description = "AR {{target_output_name}}{{output_extension}}"
+      rspfile_content = "{{inputs}}"
+      outputs = [
+        "{{target_out_dir}}/{{target_output_name}}{{output_extension}}",
+      ]
+      default_output_extension = ".a"
+      output_prefix = "lib"
+    }
+
+    tool("solink") {
+      soname = "{{target_output_name}}{{output_extension}}"  # e.g. "libfoo.so".
+      sofile = "{{output_dir}}/$soname"
+      rspfile = soname + ".rsp"
+
+      command = "${invoker.cxx} -shared {{ldflags}} -o $sofile -Wl,-soname=$soname @$rspfile"
+      rspfile_content = "-Wl,--whole-archive {{inputs}} {{solibs}} -Wl,--no-whole-archive {{libs}}"
+
+      description = "SOLINK $soname"
+
+      # Use this for {{output_extension}} expansions unless a target manually
+      # overrides it (in which case {{output_extension}} will be what the target
+      # specifies).
+      default_output_extension = ".so"
+
+      # Use this for {{output_dir}} expansions unless a target manually overrides
+      # it (in which case {{output_dir}} will be what the target specifies).
+      default_output_dir = "{{root_out_dir}}"
+
+      outputs = [
+        sofile,
+      ]
+      link_output = sofile
+      depend_output = sofile
+      output_prefix = "lib"
+    }
+
+    tool("link") {
+      outfile = "{{output_dir}}/{{target_output_name}}{{output_extension}}"
+      rspfile = "$outfile.rsp"
+      command = "${invoker.cxx} {{ldflags}} -o $outfile -Wl,--start-group @$rspfile {{solibs}} -Wl,--end-group {{libs}}"
+      description = "LINK $outfile"
+      default_output_dir = "{{root_out_dir}}"
+      rspfile_content = "{{inputs}}"
+      outputs = [
+        outfile,
+      ]
+    }
+
+    tool("stamp") {
+      command = "touch {{output}}"
+      description = "STAMP {{output}}"
+    }
+
+    tool("copy") {
+      command = "cp -af {{source}} {{output}}"
+      description = "COPY {{source}} {{output}}"
+    }
+  }
+}
+
+# Specialize for clang or gcc
+cc_toolchain("clang") {
+  ar = "llvm-ar"
+  cc = "clang"
+  cxx = "clang++"
+}
+
+cc_toolchain("gcc") {
+  ar = "ar"
+  cc = "gcc"
+  cxx = "g++"
+}
diff --git a/kokoro/ubuntu/build.sh b/kokoro/ubuntu/build.sh
index 85f4f98..596ca4f 100755
--- a/kokoro/ubuntu/build.sh
+++ b/kokoro/ubuntu/build.sh
@@ -11,14 +11,16 @@
 then
 	# Server
 	cd git/hafnium
-	export CLANG="clang-3.9"
+	mkdir out
+	echo "arch_cc_version = \"3.9\"" > out/args.gn
 else
 	# Local
-	export CLANG=1
+	echo "Testing kokoro build locally..."
 fi
 
+# TODO: Kokoro is missing ninja, gcc-4.9 or above and qemu
 # Check the build works
-make
+# make
 
 # # Check to code looks healthy, failing if any changes were made
 # make format
diff --git a/src/BUILD.gn b/src/BUILD.gn
new file mode 100644
index 0000000..51edfd3
--- /dev/null
+++ b/src/BUILD.gn
@@ -0,0 +1,22 @@
+source_set("src") {
+  sources = [
+    "alloc.c",
+    "api.c",
+    "cpio.c",
+    "cpu.c",
+    "fdt.c",
+    "fdt_handler.c",
+    "load.c",
+    "main.c",
+    "memiter.c",
+    "mm.c",
+    "std.c",
+    "vm.c",
+  ]
+
+  if (is_debug) {
+    sources += [
+      "dlog.c",
+    ]
+  }
+}
diff --git a/src/arch/aarch64/BUILD.gn b/src/arch/aarch64/BUILD.gn
new file mode 100644
index 0000000..046778c
--- /dev/null
+++ b/src/arch/aarch64/BUILD.gn
@@ -0,0 +1,29 @@
+import("//build/arch/${arch}/${arch_platform}.gni")
+import("//build/image/generate_offsets.gni")
+
+source_set("aarch64") {
+  sources = [
+    "entry.S",
+    "exceptions.S",
+    "handler.c",
+    "mm.c",
+    "offsets.c",
+    "params.c",
+  ]
+
+  if (use_pl011) {
+    sources += [
+      "pl011.c",
+    ]
+  }
+
+  deps = [
+    ":offsets",
+  ]
+}
+
+generate_offsets("offsets") {
+  sources = [
+    "offsets.c",
+  ]
+}
diff --git a/src/arch/aarch64/hikey.mk b/src/arch/aarch64/hikey.mk
deleted file mode 100644
index ec9eff9..0000000
--- a/src/arch/aarch64/hikey.mk
+++ /dev/null
@@ -1,11 +0,0 @@
-USE_FDT := 1
-
-LOAD_ADDRESS := 0x1000000
-PL011_BASE := 0xf8015000
-PL011 := 1
-GICV2 := 1
-
-GICD_BASE := 0xf6801000
-GICC_BASE := 0xf6802000
-
-TIMER_IRQ := 26
diff --git a/src/arch/aarch64/qemu.mk b/src/arch/aarch64/qemu.mk
deleted file mode 100644
index 41bcd3c..0000000
--- a/src/arch/aarch64/qemu.mk
+++ /dev/null
@@ -1,12 +0,0 @@
-USE_FDT := 1
-
-LOAD_ADDRESS := 0x40001000
-PL011_BASE := 0x09000000
-PL011 := 1
-GICV3 := 1
-
-GICD_BASE := 0x08000000
-GICC_BASE := 0x08010000
-GICR_BASE := 0x080A0000
-
-TIMER_IRQ := 26
diff --git a/src/arch/aarch64/rules.mk b/src/arch/aarch64/rules.mk
deleted file mode 100644
index a006751..0000000
--- a/src/arch/aarch64/rules.mk
+++ /dev/null
@@ -1,12 +0,0 @@
-SRCS += entry.S
-SRCS += exceptions.S
-SRCS += handler.c
-SRCS += mm.c
-SRCS += offsets.c
-SRCS += params.c
-
-OFFSET_SRCS += offsets.c
-
-ifeq ($(PL011),1)
-  SRCS += pl011.c
-endif
diff --git a/src/rules.mk b/src/rules.mk
deleted file mode 100644
index 7d56def..0000000
--- a/src/rules.mk
+++ /dev/null
@@ -1,19 +0,0 @@
-SRCS += alloc.c
-SRCS += api.c
-SRCS += cpio.c
-SRCS += cpu.c
-SRCS += load.c
-SRCS += main.c
-SRCS += memiter.c
-SRCS += mm.c
-SRCS += std.c
-SRCS += vm.c
-
-ifeq ($(DEBUG),1)
-  SRCS += dlog.c
-endif
-
-ifeq ($(USE_FDT),1)
-  SRCS += fdt.c
-  SRCS += fdt_handler.c
-endif
diff --git a/test.cpp b/test.cpp
new file mode 100644
index 0000000..64f0d09
--- /dev/null
+++ b/test.cpp
@@ -0,0 +1,6 @@
+#include <iostream>
+
+int main(void) {
+  std::cout << "This looks like it worked!" << std::endl;
+  return 0;
+}