Merge pull request #107 from neonichu/no-last-upgrade-check

Update project to get rid of last upgrade check
diff --git a/include/llbuild/BuildSystem/BuildDescription.h b/include/llbuild/BuildSystem/BuildDescription.h
new file mode 100644
index 0000000..c13df98
--- /dev/null
+++ b/include/llbuild/BuildSystem/BuildDescription.h
@@ -0,0 +1,326 @@
+//===- BuildDescription.h ---------------------------------------*- C++ -*-===//
+//
+// This source file is part of the Swift.org open source project
+//
+// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
+// Licensed under Apache License v2.0 with Runtime Library Exception
+//
+// See http://swift.org/LICENSE.txt for license information
+// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLBUILD_BUILDSYSTEM_BUILDDESCRIPTION_H
+#define LLBUILD_BUILDSYSTEM_BUILDDESCRIPTION_H
+
+#include "llbuild/Basic/Compiler.h"
+#include "llbuild/Basic/LLVM.h"
+
+#include "llvm/ADT/StringRef.h"
+
+#include <memory>
+#include <string>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+namespace llbuild {
+
+namespace core {
+
+class Task;
+
+}
+
+namespace buildsystem {
+
+/// The type used to pass parsed properties to the delegate.
+typedef std::vector<std::pair<std::string, std::string>> property_list_type;
+
+class BuildSystem;
+class BuildSystemCommandInterface;
+class BuildKey;
+class BuildValue;
+class Command;
+class Node;
+
+/// Context for information that may be needed for a configuration action.
+//
+// FIXME: This is currently commingled with the build file loading, even though
+// it should ideally be possible to create a build description decoupled
+// entirely from the build file representation.
+struct ConfigureContext;
+
+/// Abstract tool definition used by the build file.
+class Tool {
+  // DO NOT COPY
+  Tool(const Tool&) LLBUILD_DELETED_FUNCTION;
+  void operator=(const Tool&) LLBUILD_DELETED_FUNCTION;
+  Tool &operator=(Tool&& rhs) LLBUILD_DELETED_FUNCTION;
+    
+  std::string name;
+
+public:
+  explicit Tool(StringRef name) : name(name) {}
+  virtual ~Tool();
+
+  StringRef getName() const { return name; }
+
+  /// Called by the build file loader to configure a specified tool property.
+  virtual bool configureAttribute(const ConfigureContext&, StringRef name,
+                                  StringRef value) = 0;
+  /// Called by the build file loader to configure a specified tool property.
+  virtual bool configureAttribute(const ConfigureContext&, StringRef name,
+                                  ArrayRef<StringRef> values) = 0;
+  /// Called by the build file loader to configure a specified node property.
+  virtual bool configureAttribute(
+      const ConfigureContext&, StringRef name,
+      ArrayRef<std::pair<StringRef, StringRef>> values) = 0;
+
+  /// Called by the build file loader to create a command which uses this tool.
+  ///
+  /// \param name - The name of the command.
+  virtual std::unique_ptr<Command> createCommand(StringRef name) = 0;
+
+  /// Called by the build system to create a custom command with the given name.
+  ///
+  /// The tool should return null if it does not understand how to create the
+  /// a custom command for the given key.
+  ///
+  /// \param key - The custom build key to create a command for.
+  /// \returns The command to use, or null.
+  virtual std::unique_ptr<Command> createCustomCommand(const BuildKey& key);
+};
+
+/// Each Target declares a name that can be used to reference it, and a list of
+/// the top-level nodes which must be built to bring that target up to date.
+class Target {
+  /// The name of the target.
+  std::string name;
+
+  /// The list of nodes that should be computed to build this target.
+  std::vector<Node*> nodes;
+
+public:
+  explicit Target(std::string name) : name(name) { }
+
+  StringRef getName() const { return name; }
+
+  std::vector<Node*>& getNodes() { return nodes; }
+  const std::vector<Node*>& getNodes() const { return nodes; }
+};
+
+/// Abstract definition for a Node used by the build file.
+class Node {
+  // DO NOT COPY
+  Node(const Node&) LLBUILD_DELETED_FUNCTION;
+  void operator=(const Node&) LLBUILD_DELETED_FUNCTION;
+  Node &operator=(Node&& rhs) LLBUILD_DELETED_FUNCTION;
+    
+  /// The name used to identify the node.
+  std::string name;
+
+  /// The list of commands which can produce this node.
+  //
+  // FIXME: Optimize for single entry list.
+  std::vector<Command*> producers;
+  
+public:
+  explicit Node(StringRef name) : name(name) {}
+  virtual ~Node();
+
+  StringRef getName() const { return name; }
+  
+  std::vector<Command*>& getProducers() { return producers; }
+
+  const std::vector<Command*>& getProducers() const { return producers; }
+  
+  /// Called by the build file loader to configure a specified node property.
+  virtual bool configureAttribute(const ConfigureContext&, StringRef name,
+                                  StringRef value) = 0;
+  /// Called by the build file loader to configure a specified node property.
+  virtual bool configureAttribute(const ConfigureContext&, StringRef name,
+                                  ArrayRef<StringRef> values) = 0;
+  /// Called by the build file loader to configure a specified node property.
+  virtual bool configureAttribute(
+      const ConfigureContext&, StringRef name,
+      ArrayRef<std::pair<StringRef, StringRef>> values) = 0;
+};
+
+/// Abstract command definition used by the build file.
+class Command {
+  // DO NOT COPY
+  Command(const Command&) LLBUILD_DELETED_FUNCTION;
+  void operator=(const Command&) LLBUILD_DELETED_FUNCTION;
+  Command &operator=(Command&& rhs) LLBUILD_DELETED_FUNCTION;
+    
+  std::string name;
+
+public:
+  explicit Command(StringRef name) : name(name) {}
+  virtual ~Command();
+
+  StringRef getName() const { return name; }
+
+  /// @name Command Information
+  /// @{
+  //
+  // FIXME: These probably don't belong here, clients generally can just manage
+  // the information from their commands directly and our predefined interfaces
+  // won't necessarily match what they want. However, we use them now to allow
+  // extracting generic status information from the builtin commands. An
+  // alternate solution would be to simply expose those command classes directly
+  // and provide some kind of dynamic dispatching mechanism (llvm::cast<>, for
+  // example) over commands.
+
+  /// Controls whether the default status reporting shows status for the
+  /// command.
+  virtual bool shouldShowStatus() { return true; }
+  
+  /// Get a short description of the command, for use in status reporting.
+  virtual void getShortDescription(SmallVectorImpl<char> &result) = 0;
+
+  /// Get a verbose description of the command, for use in status reporting.
+  virtual void getVerboseDescription(SmallVectorImpl<char> &result) = 0;
+  
+  /// @}
+
+  /// @name File Loading
+  /// @{
+
+  /// Called by the build file loader to set the description.
+  virtual void configureDescription(const ConfigureContext&,
+                                    StringRef description) = 0;
+
+  /// Called by the build file loader to pass the list of input nodes.
+  virtual void configureInputs(const ConfigureContext&,
+                               const std::vector<Node*>& inputs) = 0;
+
+  /// Called by the build file loader to pass the list of output nodes.
+  virtual void configureOutputs(const ConfigureContext&,
+                                const std::vector<Node*>& outputs) = 0;
+                               
+  /// Called by the build file loader to configure a specified command property.
+  virtual bool configureAttribute(const ConfigureContext&, StringRef name,
+                                  StringRef value) = 0;
+  /// Called by the build file loader to configure a specified command property.
+  virtual bool configureAttribute(const ConfigureContext&, StringRef name,
+                                  ArrayRef<StringRef> values) = 0;
+  /// Called by the build file loader to configure a specified command property.
+  virtual bool configureAttribute(
+      const ConfigureContext&, StringRef name,
+      ArrayRef<std::pair<StringRef, StringRef>> values) = 0;
+
+  /// @}
+
+  /// @name Node Interfaces
+  ///
+  /// @description These are the interfaces which allow the build system to
+  /// coordinate between the abstract command and node objects.
+  //
+  // FIXME: This feels awkward, maybe this isn't the right way to manage
+  // this. However, we want the system to be able to provide the plumbing
+  // between pluggable comands and nodes, so it feels like it has to live
+  // somewhere.
+  //
+  /// @{
+  
+  /// Get the appropriate output for a particular node (known to be produced by
+  /// this command) given the command's result.
+  virtual BuildValue getResultForOutput(Node* node,
+                                        const BuildValue& value) = 0;
+
+  /// @}
+  
+  /// @name Command Execution
+  ///
+  /// @description These APIs directly mirror the APIs available in the
+  /// lower-level BuildEngine, but with additional services provided by the
+  /// BuildSystem. See the BuildEngine documentation for more information.
+  ///
+  /// @{
+
+  virtual bool isResultValid(BuildSystem& system, const BuildValue& value) = 0;
+  
+  virtual void start(BuildSystemCommandInterface&, core::Task*) = 0;
+
+  virtual void providePriorValue(BuildSystemCommandInterface&, core::Task*,
+                                 const BuildValue& value) = 0;
+
+  virtual void provideValue(BuildSystemCommandInterface&, core::Task*,
+                            uintptr_t inputID, const BuildValue& value) = 0;
+
+  virtual void inputsAvailable(BuildSystemCommandInterface&, core::Task*) = 0;
+  
+  /// @}
+};
+
+
+/// A complete description of a build.
+class BuildDescription {
+public:
+  // FIXME: This is an inefficent map, the string is duplicated.
+  typedef std::unordered_map<std::string, std::unique_ptr<Node>> node_set;
+  
+  // FIXME: This is an inefficent map, the string is duplicated.
+  typedef std::unordered_map<std::string, std::unique_ptr<Target>> target_set;
+
+  // FIXME: This is an inefficent map, the string is duplicated.
+  typedef std::unordered_map<std::string, std::unique_ptr<Command>> command_set;
+  
+  // FIXME: This is an inefficent map, the string is duplicated.
+  typedef std::unordered_map<std::string, std::unique_ptr<Tool>> tool_set;
+
+private:
+  node_set nodes;
+
+  target_set targets;
+
+  command_set commands;
+  
+  tool_set tools;
+
+  /// The default target.
+  std::string defaultTarget;
+
+public:
+  /// @name Accessors
+  /// @{
+
+  /// Get the set of declared nodes for the file.
+  node_set& getNodes() { return nodes; }
+
+  /// Get the set of declared nodes for the file.
+  const node_set& getNodes() const { return nodes; }
+
+  /// Get the set of declared targets for the file.
+  target_set& getTargets() { return targets; }
+
+  /// Get the set of declared targets for the file.
+  const target_set& getTargets() const { return targets; }
+
+  /// Get the default target.
+  std::string& getDefaultTarget() { return defaultTarget; }
+
+  /// Get the default target.
+  const std::string& getDefaultTarget() const { return defaultTarget; }
+
+  /// Get the set of declared commands for the file.
+  command_set& getCommands() { return commands; }
+
+  /// Get the set of declared commands for the file.
+  const command_set& getCommands() const { return commands; }
+
+  /// Get the set of all tools used by the file.
+  tool_set& getTools() { return tools; }
+
+  /// Get the set of all tools used by the file.
+  const tool_set& getTools() const { return tools; }
+
+  /// @}
+};
+
+}
+}
+
+#endif
diff --git a/include/llbuild/BuildSystem/BuildFile.h b/include/llbuild/BuildSystem/BuildFile.h
index 932d1ce..d935a21 100644
--- a/include/llbuild/BuildSystem/BuildFile.h
+++ b/include/llbuild/BuildSystem/BuildFile.h
@@ -2,7 +2,7 @@
 //
 // This source file is part of the Swift.org open source project
 //
-// Copyright (c) 2014 - 2015 Apple Inc. and the Swift project authors
+// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
 // Licensed under Apache License v2.0 with Runtime Library Exception
 //
 // See http://swift.org/LICENSE.txt for license information
@@ -31,24 +31,21 @@
 
 }
 
-namespace core {
-
-class Task;
-
-}
-
 namespace buildsystem {
 
 /// The type used to pass parsed properties to the delegate.
 typedef std::vector<std::pair<std::string, std::string>> property_list_type;
 
+class BuildDescription;
 class BuildFileDelegate;
+class BuildKey;
 class BuildSystem;
 class BuildSystemCommandInterface;
-class BuildKey;
 class BuildValue;
 class Command;
 class Node;
+class Target;
+class Tool;
 
 /// Minimal token object representing the range where a diagnostic occurred.
 struct BuildFileToken {
@@ -71,210 +68,6 @@
   void error(const Twine& message) const;
 };
 
-/// Abstract tool definition used by the build file.
-class Tool {
-  // DO NOT COPY
-  Tool(const Tool&) LLBUILD_DELETED_FUNCTION;
-  void operator=(const Tool&) LLBUILD_DELETED_FUNCTION;
-  Tool &operator=(Tool&& rhs) LLBUILD_DELETED_FUNCTION;
-    
-  std::string name;
-
-public:
-  explicit Tool(StringRef name) : name(name) {}
-  virtual ~Tool();
-
-  StringRef getName() const { return name; }
-
-  /// Called by the build file loader to configure a specified tool property.
-  virtual bool configureAttribute(const ConfigureContext&, StringRef name,
-                                  StringRef value) = 0;
-  /// Called by the build file loader to configure a specified tool property.
-  virtual bool configureAttribute(const ConfigureContext&, StringRef name,
-                                  ArrayRef<StringRef> values) = 0;
-  /// Called by the build file loader to configure a specified node property.
-  virtual bool configureAttribute(
-      const ConfigureContext&, StringRef name,
-      ArrayRef<std::pair<StringRef, StringRef>> values) = 0;
-
-  /// Called by the build file loader to create a command which uses this tool.
-  ///
-  /// \param name - The name of the command.
-  virtual std::unique_ptr<Command> createCommand(StringRef name) = 0;
-
-  /// Called by the build system to create a custom command with the given name.
-  ///
-  /// The tool should return null if it does not understand how to create the
-  /// a custom command for the given key.
-  ///
-  /// \param key - The custom build key to create a command for.
-  /// \returns The command to use, or null.
-  virtual std::unique_ptr<Command> createCustomCommand(const BuildKey& key);
-};
-
-/// Each Target declares a name that can be used to reference it, and a list of
-/// the top-level nodes which must be built to bring that target up to date.
-class Target {
-  /// The name of the target.
-  std::string name;
-
-  /// The list of nodes that should be computed to build this target.
-  std::vector<Node*> nodes;
-
-public:
-  explicit Target(std::string name) : name(name) { }
-
-  StringRef getName() const { return name; }
-
-  std::vector<Node*>& getNodes() { return nodes; }
-  const std::vector<Node*>& getNodes() const { return nodes; }
-};
-
-/// Abstract definition for a Node used by the build file.
-class Node {
-  // DO NOT COPY
-  Node(const Node&) LLBUILD_DELETED_FUNCTION;
-  void operator=(const Node&) LLBUILD_DELETED_FUNCTION;
-  Node &operator=(Node&& rhs) LLBUILD_DELETED_FUNCTION;
-    
-  /// The name used to identify the node.
-  std::string name;
-
-  /// The list of commands which can produce this node.
-  //
-  // FIXME: Optimize for single entry list.
-  std::vector<Command*> producers;
-  
-public:
-  explicit Node(StringRef name) : name(name) {}
-  virtual ~Node();
-
-  StringRef getName() const { return name; }
-  
-  std::vector<Command*>& getProducers() { return producers; }
-
-  const std::vector<Command*>& getProducers() const { return producers; }
-  
-  /// Called by the build file loader to configure a specified node property.
-  virtual bool configureAttribute(const ConfigureContext&, StringRef name,
-                                  StringRef value) = 0;
-  /// Called by the build file loader to configure a specified node property.
-  virtual bool configureAttribute(const ConfigureContext&, StringRef name,
-                                  ArrayRef<StringRef> values) = 0;
-  /// Called by the build file loader to configure a specified node property.
-  virtual bool configureAttribute(
-      const ConfigureContext&, StringRef name,
-      ArrayRef<std::pair<StringRef, StringRef>> values) = 0;
-};
-
-/// Abstract command definition used by the build file.
-class Command {
-  // DO NOT COPY
-  Command(const Command&) LLBUILD_DELETED_FUNCTION;
-  void operator=(const Command&) LLBUILD_DELETED_FUNCTION;
-  Command &operator=(Command&& rhs) LLBUILD_DELETED_FUNCTION;
-    
-  std::string name;
-
-public:
-  explicit Command(StringRef name) : name(name) {}
-  virtual ~Command();
-
-  StringRef getName() const { return name; }
-
-  /// @name Command Information
-  /// @{
-  //
-  // FIXME: These probably don't belong here, clients generally can just manage
-  // the information from their commands directly and our predefined interfaces
-  // won't necessarily match what they want. However, we use them now to allow
-  // extracting generic status information from the builtin commands. An
-  // alternate solution would be to simply expose those command classes directly
-  // and provide some kind of dynamic dispatching mechanism (llvm::cast<>, for
-  // example) over commands.
-
-  /// Controls whether the default status reporting shows status for the
-  /// command.
-  virtual bool shouldShowStatus() { return true; }
-  
-  /// Get a short description of the command, for use in status reporting.
-  virtual void getShortDescription(SmallVectorImpl<char> &result) = 0;
-
-  /// Get a verbose description of the command, for use in status reporting.
-  virtual void getVerboseDescription(SmallVectorImpl<char> &result) = 0;
-  
-  /// @}
-
-  /// @name File Loading
-  /// @{
-
-  /// Called by the build file loader to set the description.
-  virtual void configureDescription(const ConfigureContext&,
-                                    StringRef description) = 0;
-
-  /// Called by the build file loader to pass the list of input nodes.
-  virtual void configureInputs(const ConfigureContext&,
-                               const std::vector<Node*>& inputs) = 0;
-
-  /// Called by the build file loader to pass the list of output nodes.
-  virtual void configureOutputs(const ConfigureContext&,
-                                const std::vector<Node*>& outputs) = 0;
-                               
-  /// Called by the build file loader to configure a specified command property.
-  virtual bool configureAttribute(const ConfigureContext&, StringRef name,
-                                  StringRef value) = 0;
-  /// Called by the build file loader to configure a specified command property.
-  virtual bool configureAttribute(const ConfigureContext&, StringRef name,
-                                  ArrayRef<StringRef> values) = 0;
-  /// Called by the build file loader to configure a specified command property.
-  virtual bool configureAttribute(
-      const ConfigureContext&, StringRef name,
-      ArrayRef<std::pair<StringRef, StringRef>> values) = 0;
-
-  /// @}
-
-  /// @name Node Interfaces
-  ///
-  /// @description These are the interfaces which allow the build system to
-  /// coordinate between the abstract command and node objects.
-  //
-  // FIXME: This feels awkward, maybe this isn't the right way to manage
-  // this. However, we want the system to be able to provide the plumbing
-  // between pluggable comands and nodes, so it feels like it has to live
-  // somewhere.
-  //
-  /// @{
-  
-  /// Get the appropriate output for a particular node (known to be produced by
-  /// this command) given the command's result.
-  virtual BuildValue getResultForOutput(Node* node,
-                                        const BuildValue& value) = 0;
-
-  /// @}
-  
-  /// @name Command Execution
-  ///
-  /// @description These APIs directly mirror the APIs available in the
-  /// lower-level BuildEngine, but with additional services provided by the
-  /// BuildSystem. See the BuildEngine documentation for more information.
-  ///
-  /// @{
-
-  virtual bool isResultValid(BuildSystem& system, const BuildValue& value) = 0;
-  
-  virtual void start(BuildSystemCommandInterface&, core::Task*) = 0;
-
-  virtual void providePriorValue(BuildSystemCommandInterface&, core::Task*,
-                                 const BuildValue& value) = 0;
-
-  virtual void provideValue(BuildSystemCommandInterface&, core::Task*,
-                            uintptr_t inputID, const BuildValue& value) = 0;
-
-  virtual void inputsAvailable(BuildSystemCommandInterface&, core::Task*) = 0;
-  
-  /// @}
-};
-
 class BuildFileDelegate {
 public:
   virtual ~BuildFileDelegate();
@@ -342,19 +135,6 @@
 /// The BuildFile class supports the "llbuild"-native build description file
 /// format.
 class BuildFile {
-public:
-  // FIXME: This is an inefficent map, the string is duplicated.
-  typedef std::unordered_map<std::string, std::unique_ptr<Node>> node_set;
-  
-  // FIXME: This is an inefficent map, the string is duplicated.
-  typedef std::unordered_map<std::string, std::unique_ptr<Target>> target_set;
-
-  // FIXME: This is an inefficent map, the string is duplicated.
-  typedef std::unordered_map<std::string, std::unique_ptr<Command>> command_set;
-  
-  // FIXME: This is an inefficent map, the string is duplicated.
-  typedef std::unordered_map<std::string, std::unique_ptr<Tool>> tool_set;
-
 private:
   void *impl;
 
@@ -369,37 +149,10 @@
   /// Return the delegate the engine was configured with.
   BuildFileDelegate* getDelegate();
 
-  /// @name Parse Actions
-  /// @{
-
   /// Load the build file from the provided filename.
   ///
-  /// This method should only be called once on the BuildFile, and it should be
-  /// called before any other operations.
-  ///
-  /// \returns True on success.
-  bool load();
-
-  /// @}
-  /// @name Accessors
-  /// @{
-
-  /// Get the set of declared nodes for the file.
-  const node_set& getNodes() const;
-
-  /// Get the set of declared targets for the file.
-  const target_set& getTargets() const;
-
-  /// Get the default target.
-  const StringRef getDefaultTarget() const;
-
-  /// Get the set of declared commands for the file.
-  const command_set& getCommands() const;
-
-  /// Get the set of all tools used by the file.
-  const tool_set& getTools() const;
-
-  /// @}
+  /// \returns A non-null build description on success.
+  std::unique_ptr<BuildDescription> load();
 };
 
 }
diff --git a/include/llbuild/BuildSystem/BuildKey.h b/include/llbuild/BuildSystem/BuildKey.h
index 274909b..a77e9f8 100644
--- a/include/llbuild/BuildSystem/BuildKey.h
+++ b/include/llbuild/BuildSystem/BuildKey.h
@@ -16,7 +16,7 @@
 #include "llbuild/Basic/Compiler.h"
 #include "llbuild/Basic/LLVM.h"
 #include "llbuild/Core/BuildEngine.h"
-#include "llbuild/BuildSystem/BuildFile.h"
+#include "llbuild/BuildSystem/BuildDescription.h"
 
 #include "llvm/ADT/StringRef.h"
 
diff --git a/include/llbuild/BuildSystem/BuildNode.h b/include/llbuild/BuildSystem/BuildNode.h
index 7f9bfcb..650f743 100644
--- a/include/llbuild/BuildSystem/BuildNode.h
+++ b/include/llbuild/BuildSystem/BuildNode.h
@@ -13,6 +13,8 @@
 #ifndef LLBUILD_BUILDSYSTEM_BUILDNODE_H
 #define LLBUILD_BUILDSYSTEM_BUILDNODE_H
 
+#include "BuildDescription.h"
+
 #include "llbuild/Basic/LLVM.h"
 #include "llbuild/BuildSystem/BuildFile.h"
 
diff --git a/include/llbuild/BuildSystem/BuildSystem.h b/include/llbuild/BuildSystem/BuildSystem.h
index 6ffd6c0..216407d 100644
--- a/include/llbuild/BuildSystem/BuildSystem.h
+++ b/include/llbuild/BuildSystem/BuildSystem.h
@@ -212,6 +212,8 @@
 
   /// Build the named target.
   ///
+  /// This will automatically load the build file, if necessary.
+  ///
   /// \returns True on success, or false if the build was aborted (for example,
   /// if a cycle was discovered).
   bool build(StringRef target);
diff --git a/include/llbuild/BuildSystem/ExternalCommand.h b/include/llbuild/BuildSystem/ExternalCommand.h
index 59da21c..28251f0 100644
--- a/include/llbuild/BuildSystem/ExternalCommand.h
+++ b/include/llbuild/BuildSystem/ExternalCommand.h
@@ -13,7 +13,7 @@
 #ifndef LLBUILD_BUILDSYSTEM_EXTERNALCOMMAND_H
 #define LLBUILD_BUILDSYSTEM_EXTERNALCOMMAND_H
 
-#include "llbuild/BuildSystem/BuildFile.h"
+#include "llbuild/BuildSystem/BuildDescription.h"
 #include "llbuild/BuildSystem/BuildSystem.h"
 #include "llbuild/BuildSystem/BuildValue.h"
 
diff --git a/lib/BuildSystem/BuildDescription.cpp b/lib/BuildSystem/BuildDescription.cpp
new file mode 100644
index 0000000..67c8aaa
--- /dev/null
+++ b/lib/BuildSystem/BuildDescription.cpp
@@ -0,0 +1,26 @@
+//===-- BuildDescription.cpp ----------------------------------------------===//
+//
+// This source file is part of the Swift.org open source project
+//
+// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
+// Licensed under Apache License v2.0 with Runtime Library Exception
+//
+// See http://swift.org/LICENSE.txt for license information
+// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
+//
+//===----------------------------------------------------------------------===//
+
+#include "llbuild/BuildSystem/BuildDescription.h"
+
+using namespace llbuild;
+using namespace llbuild::buildsystem;
+
+Node::~Node() {}
+
+Command::~Command() {}
+
+Tool::~Tool() {}
+
+std::unique_ptr<Command> Tool::createCustomCommand(const BuildKey& key) {
+  return nullptr;
+}
diff --git a/lib/BuildSystem/BuildFile.cpp b/lib/BuildSystem/BuildFile.cpp
index 4e3dddf..dca72f2 100644
--- a/lib/BuildSystem/BuildFile.cpp
+++ b/lib/BuildSystem/BuildFile.cpp
@@ -2,7 +2,7 @@
 //
 // This source file is part of the Swift.org open source project
 //
-// Copyright (c) 2014 - 2015 Apple Inc. and the Swift project authors
+// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
 // Licensed under Apache License v2.0 with Runtime Library Exception
 //
 // See http://swift.org/LICENSE.txt for license information
@@ -14,6 +14,7 @@
 
 #include "llbuild/Basic/FileSystem.h"
 #include "llbuild/Basic/LLVM.h"
+#include "llbuild/BuildSystem/BuildDescription.h"
 
 #include "llvm/ADT/STLExtras.h"
 #include "llvm/Support/MemoryBuffer.h"
@@ -29,16 +30,6 @@
 
 BuildFileDelegate::~BuildFileDelegate() {}
 
-Node::~Node() {}
-
-Command::~Command() {}
-
-Tool::~Tool() {}
-
-std::unique_ptr<Command> Tool::createCustomCommand(const BuildKey& key) {
-  return nullptr;
-}
-
 #pragma mark - BuildFile implementation
 
 namespace {
@@ -109,19 +100,19 @@
   BuildFileDelegate& delegate;
 
   /// The set of all registered tools.
-  BuildFile::tool_set tools;
+  BuildDescription::tool_set tools;
 
   /// The set of all declared targets.
-  BuildFile::target_set targets;
+  BuildDescription::target_set targets;
 
   /// Default target name
   std::string defaultTarget;
 
   /// The set of all declared nodes.
-  BuildFile::node_set nodes;
+  BuildDescription::node_set nodes;
 
   /// The set of all declared commands.
-  BuildFile::command_set commands;
+  BuildDescription::command_set commands;
   
   /// The number of parsing errors.
   int numErrors = 0;
@@ -832,7 +823,7 @@
   /// @name Parse Actions
   /// @{
 
-  bool load() {
+  std::unique_ptr<BuildDescription> load() {
     // Create a memory buffer for the input.
     //
     // FIXME: Lift the file access into the delegate, like we do for Ninja.
@@ -840,7 +831,7 @@
     auto input = delegate.getFileSystem().getFileContents(mainFilename);
     if (!input) {
       error("unable to open '" + mainFilename + "'");
-      return false;
+      return nullptr;
     }
 
     delegate.setFileContentsBeingParsed(input->getBuffer());
@@ -852,36 +843,31 @@
     auto it = stream.begin();
     if (it == stream.end()) {
       error("missing document in stream");
-      return false;
+      return nullptr;
     }
 
     auto& document = *it;
     if (!parseRootNode(document.getRoot())) {
-      return false;
+      return nullptr;
     }
 
     if (++it != stream.end()) {
       error(it->getRoot(), "unexpected additional document in stream");
-      return false;
+      return nullptr;
     }
 
-    return numErrors == 0;
+    // Create the actual description from our constructed elements.
+    //
+    // FIXME: This is historical, We should tidy up this class to reflect that
+    // it is now just a builder.
+    auto description = std::make_unique<BuildDescription>();
+    std::swap(description->getNodes(), nodes);
+    std::swap(description->getTargets(), targets);
+    std::swap(description->getDefaultTarget(), defaultTarget);
+    std::swap(description->getCommands(), commands);
+    std::swap(description->getTools(), tools);
+    return description;
   }
-
-  /// @name Accessors
-  /// @{
-
-  const BuildFile::node_set& getNodes() const { return nodes; }
-
-  const BuildFile::target_set& getTargets() const { return targets; }
-
-  const StringRef getDefaultTarget() const { return defaultTarget; }
-
-  const BuildFile::command_set& getCommands() const { return commands; }
-
-  const BuildFile::tool_set& getTools() const { return tools; }
-
-  /// @}
 };
 
 }
@@ -898,30 +884,7 @@
   delete static_cast<BuildFileImpl*>(impl);
 }
 
-BuildFileDelegate* BuildFile::getDelegate() {
-  return static_cast<BuildFileImpl*>(impl)->getDelegate();
-}
-
-const BuildFile::node_set& BuildFile::getNodes() const {
-  return static_cast<BuildFileImpl*>(impl)->getNodes();
-}
-
-const BuildFile::target_set& BuildFile::getTargets() const {
-  return static_cast<BuildFileImpl*>(impl)->getTargets();
-}
-
-const StringRef BuildFile::getDefaultTarget() const {
-  return static_cast<BuildFileImpl*>(impl)->getDefaultTarget();
-}
-
-const BuildFile::command_set& BuildFile::getCommands() const {
-  return static_cast<BuildFileImpl*>(impl)->getCommands();
-}
-
-const BuildFile::tool_set& BuildFile::getTools() const {
-  return static_cast<BuildFileImpl*>(impl)->getTools();
-}
-
-bool BuildFile::load() {
+std::unique_ptr<BuildDescription> BuildFile::load() {
+  // Create the build description.
   return static_cast<BuildFileImpl*>(impl)->load();
 }
diff --git a/lib/BuildSystem/BuildKey.cpp b/lib/BuildSystem/BuildKey.cpp
index 57c88b7..50a1c54 100644
--- a/lib/BuildSystem/BuildKey.cpp
+++ b/lib/BuildSystem/BuildKey.cpp
@@ -13,6 +13,7 @@
 #include "llbuild/BuildSystem/BuildKey.h"
 
 #include "llbuild/Basic/LLVM.h"
+#include "llbuild/BuildSystem/BuildDescription.h"
 
 #include "llvm/Support/raw_ostream.h"
 
diff --git a/lib/BuildSystem/BuildSystem.cpp b/lib/BuildSystem/BuildSystem.cpp
index 3aaa600..35b99ef 100644
--- a/lib/BuildSystem/BuildSystem.cpp
+++ b/lib/BuildSystem/BuildSystem.cpp
@@ -2,7 +2,7 @@
 //
 // This source file is part of the Swift.org open source project
 //
-// Copyright (c) 2014 - 2015 Apple Inc. and the Swift project authors
+// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
 // Licensed under Apache License v2.0 with Runtime Library Exception
 //
 // See http://swift.org/LICENSE.txt for license information
@@ -114,7 +114,7 @@
   /// The custom tasks which are owned by the build system.
   std::vector<std::unique_ptr<Command>> customTasks;
 
-  BuildFile& getBuildFile();
+  const BuildDescription& getBuildDescription() const;
 
   virtual Rule lookupRule(const KeyType& keyData) override;
   virtual void cycleDetected(const std::vector<Rule*>& items) override;
@@ -146,6 +146,9 @@
   /// The build file the system is building.
   BuildFile buildFile;
 
+  /// The build description, once loaded.
+  std::unique_ptr<BuildDescription> buildDescription;
+
   /// The delegate used for building the file contents.
   BuildSystemEngineDelegate engineDelegate;
 
@@ -222,8 +225,9 @@
     return *this;
   }
 
-  BuildFile& getBuildFile() {
-    return buildFile;
+  const BuildDescription& getBuildDescription() const {
+    assert(buildDescription);
+    return *buildDescription;
   }
 
   void error(StringRef filename, const Twine& message) {
@@ -600,8 +604,8 @@
   using Task::Task;
 };
 
-BuildFile& BuildSystemEngineDelegate::getBuildFile() {
-  return system.getBuildFile();
+const BuildDescription& BuildSystemEngineDelegate::getBuildDescription() const {
+  return system.getBuildDescription();
 }
 
 static BuildSystemDelegate::CommandStatusKind
@@ -628,8 +632,8 @@
     
   case BuildKey::Kind::Command: {
     // Find the comand.
-    auto it = getBuildFile().getCommands().find(key.getCommandName());
-    if (it == getBuildFile().getCommands().end()) {
+    auto it = getBuildDescription().getCommands().find(key.getCommandName());
+    if (it == getBuildDescription().getCommands().end()) {
       // If there is no such command, produce an error task.
       return Rule{
         keyData,
@@ -670,7 +674,7 @@
     // FIXME: We should most likely have some kind of registration process so we
     // can do an efficient query here, but exactly how this should look isn't
     // clear yet.
-    for (const auto& it: getBuildFile().getTools()) {
+    for (const auto& it: getBuildDescription().getTools()) {
       auto result = it.second->createCustomCommand(key);
       if (!result) continue;
 
@@ -708,9 +712,9 @@
     
   case BuildKey::Kind::Node: {
     // Find the node.
-    auto it = getBuildFile().getNodes().find(key.getNodeName());
+    auto it = getBuildDescription().getNodes().find(key.getNodeName());
     BuildNode* node;
-    if (it != getBuildFile().getNodes().end()) {
+    if (it != getBuildDescription().getNodes().end()) {
       node = static_cast<BuildNode*>(it->second.get());
     } else {
       auto it = dynamicNodes.find(key.getNodeName());
@@ -763,8 +767,8 @@
 
   case BuildKey::Kind::Target: {
     // Find the target.
-    auto it = getBuildFile().getTargets().find(key.getTargetName());
-    if (it == getBuildFile().getTargets().end()) {
+    auto it = getBuildDescription().getTargets().find(key.getTargetName());
+    if (it == getBuildDescription().getTargets().end()) {
       // FIXME: Invalid target name, produce an error.
       assert(0 && "FIXME: invalid target");
       abort();
@@ -843,16 +847,17 @@
 }
 
 bool BuildSystemImpl::build(StringRef target) {
-  // Load the build file.
+  // Load the build file, if necessary.
   //
   // FIXME: Eventually, we may want to support something fancier where we load
   // the build file in the background so we can immediately start building
   // things as they show up.
-  //
-  // FIXME: We need to load this only once.
-  if (!getBuildFile().load()) {
-    error(getMainFilename(), "unable to load build file");
-    return false;
+  if (!buildDescription) {
+    buildDescription = buildFile.load();
+    if (!buildDescription) {
+      error(getMainFilename(), "unable to load build file");
+      return false;
+    }
   }    
 
   // Create the execution queue.
@@ -861,7 +866,7 @@
   // If target name is not passed then we try to load the default target name
   // from manifest file
   if (target.empty()) {
-    target = getBuildFile().getDefaultTarget();
+    target = getBuildDescription().getDefaultTarget();
   }
 
   // Build the target.
diff --git a/lib/BuildSystem/BuildSystemFrontend.cpp b/lib/BuildSystem/BuildSystemFrontend.cpp
index 1078a19..d27c6dd 100644
--- a/lib/BuildSystem/BuildSystemFrontend.cpp
+++ b/lib/BuildSystem/BuildSystemFrontend.cpp
@@ -15,6 +15,7 @@
 #include "llbuild/Basic/FileSystem.h"
 #include "llbuild/Basic/LLVM.h"
 #include "llbuild/Basic/PlatformUtility.h"
+#include "llbuild/BuildSystem/BuildDescription.h"
 #include "llbuild/BuildSystem/BuildExecutionQueue.h"
 #include "llbuild/BuildSystem/BuildFile.h"
 
diff --git a/lib/BuildSystem/CMakeLists.txt b/lib/BuildSystem/CMakeLists.txt
index c0397be..6923ffc 100644
--- a/lib/BuildSystem/CMakeLists.txt
+++ b/lib/BuildSystem/CMakeLists.txt
@@ -1,4 +1,5 @@
 add_llbuild_library(llbuildBuildSystem
+  BuildDescription.cpp
   BuildExecutionQueue.cpp
   BuildFile.cpp
   BuildKey.cpp
diff --git a/lib/Commands/BuildSystemCommand.cpp b/lib/Commands/BuildSystemCommand.cpp
index 5b9e2bd..9e90fcc 100644
--- a/lib/Commands/BuildSystemCommand.cpp
+++ b/lib/Commands/BuildSystemCommand.cpp
@@ -15,6 +15,7 @@
 #include "llbuild/Basic/FileSystem.h"
 #include "llbuild/Basic/LLVM.h"
 #include "llbuild/Basic/PlatformUtility.h"
+#include "llbuild/BuildSystem/BuildDescription.h"
 #include "llbuild/BuildSystem/BuildFile.h"
 #include "llbuild/BuildSystem/BuildSystem.h"
 #include "llbuild/BuildSystem/BuildSystemFrontend.h"
diff --git a/llbuild.xcodeproj/project.pbxproj b/llbuild.xcodeproj/project.pbxproj
index 82efd71..a569237 100644
--- a/llbuild.xcodeproj/project.pbxproj
+++ b/llbuild.xcodeproj/project.pbxproj
@@ -93,6 +93,7 @@
 		E1192CEF1C49DBA900F85890 /* Core-C-API.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E1DD22741C47259900555A5D /* Core-C-API.cpp */; };
 		E1192CF11C49DC3300F85890 /* libllbuildBuildSystem.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E1B839571B541BFD00DB876B /* libllbuildBuildSystem.a */; };
 		E1192CF21C49DC4F00F85890 /* libcurses.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = E15B6EC61B546A2C00643066 /* libcurses.dylib */; };
+		E11F2B7F1E4D255B00176BAD /* BuildDescription.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E11F2B7E1E4D255B00176BAD /* BuildDescription.cpp */; };
 		E12BFF181C4972D900B8D20F /* libsqlite3.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = E1E221081A00B82100957481 /* libsqlite3.dylib */; };
 		E12BFF191C4972E000B8D20F /* libcurses.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = E15B6EC61B546A2C00643066 /* libcurses.dylib */; };
 		E12BFF1A1C4972F000B8D20F /* libllbuildBuildSystem.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E1B839571B541BFD00DB876B /* libllbuildBuildSystem.a */; };
@@ -720,6 +721,8 @@
 		E11470921B752E7000ED84CF /* BuildKey.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BuildKey.h; sourceTree = "<group>"; };
 		E11470931B7554F800ED84CF /* FileInfo.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FileInfo.cpp; sourceTree = "<group>"; };
 		E1192CEC1C49D84500F85890 /* buildsystem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = buildsystem.h; sourceTree = "<group>"; };
+		E11F2B7D1E4D253B00176BAD /* BuildDescription.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BuildDescription.h; sourceTree = "<group>"; };
+		E11F2B7E1E4D255B00176BAD /* BuildDescription.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = BuildDescription.cpp; sourceTree = "<group>"; };
 		E12E12A71AD50AE500ACE7B3 /* CommandLineStatusOutput.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CommandLineStatusOutput.cpp; sourceTree = "<group>"; };
 		E12E12A81AD50AE500ACE7B3 /* CommandLineStatusOutput.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CommandLineStatusOutput.h; sourceTree = "<group>"; };
 		E138129C1C536CFC000092C0 /* FileSystem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FileSystem.h; sourceTree = "<group>"; };
@@ -1781,6 +1784,7 @@
 			children = (
 				E1B8395C1B541C4300DB876B /* Headers */,
 				E1B8395A1B541C1F00DB876B /* CMakeLists.txt */,
+				E11F2B7E1E4D255B00176BAD /* BuildDescription.cpp */,
 				E1BE0AA51C458EB000AD0883 /* BuildExecutionQueue.cpp */,
 				E1B839591B541C1F00DB876B /* BuildFile.cpp */,
 				E1E4A5B31BFC1394001BFFC4 /* BuildKey.cpp */,
@@ -1798,17 +1802,18 @@
 		E1B8395C1B541C4300DB876B /* Headers */ = {
 			isa = PBXGroup;
 			children = (
-				54E187B61CD296EA00F7EC89 /* BuildNode.h */,
-				9D2589311E38221D006C76F4 /* CommandResult.h */,
-				54E187B71CD296EA00F7EC89 /* ExternalCommand.h */,
-				54E187B81CD296EA00F7EC89 /* SwiftTools.h */,
+				E11F2B7D1E4D253B00176BAD /* BuildDescription.h */,
 				E10FE0D51B6FF2000059D086 /* BuildExecutionQueue.h */,
 				E1B8395D1B541C4300DB876B /* BuildFile.h */,
 				E11470921B752E7000ED84CF /* BuildKey.h */,
+				54E187B61CD296EA00F7EC89 /* BuildNode.h */,
 				E1B49EFA1B6BD45D0031AFC2 /* BuildSystem.h */,
 				E1B49EF91B6BD45D0031AFC2 /* BuildSystemCommandInterface.h */,
 				E1AAD28B1BC60A0F00F54680 /* BuildSystemFrontend.h */,
 				E11470911B7517C800ED84CF /* BuildValue.h */,
+				9D2589311E38221D006C76F4 /* CommandResult.h */,
+				54E187B71CD296EA00F7EC89 /* ExternalCommand.h */,
+				54E187B81CD296EA00F7EC89 /* SwiftTools.h */,
 			);
 			name = Headers;
 			path = include/llbuild/BuildSystem;
@@ -2799,6 +2804,7 @@
 				E1AAD28E1BC65A1900F54680 /* BuildNode.cpp in Sources */,
 				E1FC67F91BB1F427004EBC54 /* BuildValue.cpp in Sources */,
 				E1BE0AA61C458EB000AD0883 /* BuildExecutionQueue.cpp in Sources */,
+				E11F2B7F1E4D255B00176BAD /* BuildDescription.cpp in Sources */,
 				E1AAD2921BC65B5000F54680 /* SwiftTools.cpp in Sources */,
 				E1E4A5B41BFC1394001BFFC4 /* BuildKey.cpp in Sources */,
 				E1B8395E1B541C5900DB876B /* BuildFile.cpp in Sources */,
diff --git a/products/swift-build-tool/swift-build-tool.cpp b/products/swift-build-tool/swift-build-tool.cpp
index 0026fc0..0ea1c9c 100644
--- a/products/swift-build-tool/swift-build-tool.cpp
+++ b/products/swift-build-tool/swift-build-tool.cpp
@@ -14,6 +14,7 @@
 
 #include "llbuild/Basic/FileSystem.h"
 #include "llbuild/Basic/Version.h"
+#include "llbuild/BuildSystem/BuildDescription.h"
 #include "llbuild/BuildSystem/BuildFile.h"
 #include "llbuild/BuildSystem/SwiftTools.h"
 
diff --git a/products/ui/llbuildui/static/style.css b/products/ui/llbuildui/static/style.css
new file mode 100644
index 0000000..355149b
--- /dev/null
+++ b/products/ui/llbuildui/static/style.css
@@ -0,0 +1,8 @@
+body {
+    padding-top: 50px;
+}
+
+.page-header {
+    padding: 40px 15px;
+    text-align: center;
+}
diff --git a/products/ui/llbuildui/templates/config.html b/products/ui/llbuildui/templates/config.html
new file mode 100644
index 0000000..a0b341e
--- /dev/null
+++ b/products/ui/llbuildui/templates/config.html
@@ -0,0 +1,12 @@
+{% extends "layout.html" %}
+{% block title %}Configuration{% endblock %}
+{% block body %}
+
+        
+Select the database:
+<form action="" method="post">
+  <p><input type=text name=db_path>
+  <p><input type=submit value="Set Path">
+</form>
+
+{% endblock %}
diff --git a/products/ui/llbuildui/templates/index.html b/products/ui/llbuildui/templates/index.html
new file mode 100644
index 0000000..ce4709b
--- /dev/null
+++ b/products/ui/llbuildui/templates/index.html
@@ -0,0 +1,12 @@
+{% extends "layout.html" %}
+{% block title %}Root Node{% endblock %}
+{% block body %}
+
+<p>
+  Roots:
+  {% for root in roots %}
+  <a href="{{ url_for('main.rule_result', id=root.id) }}">{{ root.key.name }}</a> (id: {{ root.id }})<br>
+  {% endfor %}
+</p>
+
+{% endblock %}
diff --git a/products/ui/llbuildui/templates/layout.html b/products/ui/llbuildui/templates/layout.html
new file mode 100644
index 0000000..2f95ea3
--- /dev/null
+++ b/products/ui/llbuildui/templates/layout.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="utf-8">
+  <meta http-equiv="X-UA-Compatible" content="IE=edge">
+  <meta name="viewport" content="width=device-width, initial-scale=1">
+  
+  <!-- Latest compiled and minified CSS -->
+  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
+
+  <!-- Optional theme -->
+  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous">
+
+  <link href="{{ url_for('static', filename='style.css') }}" rel="stylesheet">
+  
+  {% block head %} {% endblock %}
+</head>
+<body>
+    <nav class="navbar navbar-inverse navbar-fixed-top">
+      <div class="container">
+        <div class="navbar-header">
+          <a class="navbar-brand" href="{{ url_for('main.index') }}">llbuild</a>
+        </div>
+        <div id="navbar" class="collapse navbar-collapse">
+          <ul class="nav navbar-nav">
+            <li><a href="{{ url_for('main.config') }}"><i>Database:</i> {{ db_path }}</a></li>
+          </ul>
+        </div><!--/.nav-collapse -->
+      </div>
+    </nav>
+
+    <div class="container">
+      <div class="page-header">
+        <h1>{% block title %}{% endblock %}</h1>
+      </div>
+      <div class="row content">
+        <div class="col-sm-8">
+          {% block body %}{% endblock %}
+        </div>
+      </div>
+    </div>
+
+    </div><!-- /.container -->
+  <!-- Latest compiled and minified JavaScript -->
+  <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
+</body>
+</html>
diff --git a/products/ui/llbuildui/templates/rule_result.html b/products/ui/llbuildui/templates/rule_result.html
new file mode 100644
index 0000000..df463e8
--- /dev/null
+++ b/products/ui/llbuildui/templates/rule_result.html
@@ -0,0 +1,21 @@
+{% extends "layout.html" %}
+{% block title %}Rule Result: {{ rule_result.key.name }} {% endblock %}
+{% block body %}
+
+<h4>Dependencies</h4>
+
+{% for dependency in dependency_results %}
+<a href="{{ url_for('main.rule_result', id=dependency.id) }}">
+  {{ dependency.key.name }} (id: {{ dependency.id }})
+</a><br>
+{% endfor %}
+
+<h4>Dependents</h4>
+
+{% for dependent in dependents_results %}
+<a href="{{ url_for('main.rule_result', id=dependent.id) }}">
+  {{ dependent.key.name }} (id: {{ dependent.id }})
+</a><br>
+{% endfor %}
+
+{% endblock %}
diff --git a/products/ui/llbuildui/views.py b/products/ui/llbuildui/views.py
index 5b9d4a3..4c0f658 100644
--- a/products/ui/llbuildui/views.py
+++ b/products/ui/llbuildui/views.py
@@ -22,29 +22,16 @@
                   .filter(model.RuleResult.key_id.notin_(
                       s.query(model.RuleDependency.key_id)))
 
-    output =  """Using Database: %s (<a href="%s">change</a>)<br>\n""" % (
-        db_path, url_for('main.config'))
-    output += "<br>"
-    output = "Roots:<br>"
-    for root in roots_query:
-        output += '''<a href="%s">%s</a> (id: %d)<br>''' % (
-            url_for('main.rule_result', id=root.id),
-            flask.escape(root.key.name),
-            root.id)
-    return output
+    return flask.render_template(
+        "index.html",
+        db_path=db_path, roots=roots_query.all())
 
 @main.route('/config', methods=['GET', 'POST'])
 def config():
     if request.method == 'POST':
         session['db'] = request.form['db_path']
         return redirect(url_for('main.index'))
-    return '''
-Select the database:
-    <form action="" method="post">
-            <p><input type=text name=db_path>
-            <p><input type=submit value="Set Path">
-        </form>
-    '''
+    return flask.render_template("config.html", db_path=session.get("db"))
 
 # MARK: Model Object Views
 
@@ -53,34 +40,19 @@
     # Get the result.
     s = current_app.database_session
     rule_result = s.query(model.RuleResult).filter_by(id=id).one()
-
-    output = "Rule Result<br>"
-    output += "Name:%s<br>" % (flask.escape(rule_result.key.name),)
-    output += "<br>"
-
-    output += "Dependencies:<br>"
     dependency_results = [
         s.query(model.RuleResult).filter_by(
             key=dependency.key).one()
         for dependency in rule_result.dependencies]
-    for dependency in sorted(dependency_results, key=lambda d: d.key.name):
-        # Find the result for this key.
-        output += '''<a href="%s">%s</a> (id: %d)<br>''' % (
-            url_for('main.rule_result', id=dependency.id),
-            flask.escape(dependency.key.name),
-            dependency.id)
-    output += "<br>"
-    
-    output += "Dependents:<br>"
     dependents_results = s.query(model.RuleResult) \
                           .filter(model.RuleResult.id.in_(
                               s.query(model.RuleDependency.rule_id).filter_by(
                                   key=rule_result.key)))
-    for dependent in sorted(dependents_results, key=lambda d: d.key.name):
-        # Find the result for this key.
-        output += '''<a href="%s">%s</a> (id: %d)<br>''' % (
-            url_for('main.rule_result', id=dependent.id),
-            flask.escape(dependent.key.name),
-            dependent.id)
-    return output
+    dependency_results = sorted(dependency_results, key=lambda d: d.key.name)
+    dependents_results = sorted(dependents_results, key=lambda d: d.key.name)
     
+    return flask.render_template(
+        "rule_result.html",
+        db_path=session.get("db"), rule_result=rule_result,
+        dependency_results=dependency_results,
+        dependents_results=dependents_results)
diff --git a/unittests/BuildSystem/BuildSystemFrontendTest.cpp b/unittests/BuildSystem/BuildSystemFrontendTest.cpp
index 510e996..ca5fff1 100644
--- a/unittests/BuildSystem/BuildSystemFrontendTest.cpp
+++ b/unittests/BuildSystem/BuildSystemFrontendTest.cpp
@@ -10,11 +10,13 @@
 //
 //===----------------------------------------------------------------------===//
 
-#include "llbuild/Basic/FileSystem.h"
-#include "llbuild/BuildSystem/BuildSystemFrontend.h"
-#include "llbuild/BuildSystem/BuildFile.h"
 #include "TempDir.hpp"
 
+#include "llbuild/Basic/FileSystem.h"
+#include "llbuild/BuildSystem/BuildDescription.h"
+#include "llbuild/BuildSystem/BuildFile.h"
+#include "llbuild/BuildSystem/BuildSystemFrontend.h"
+
 #include "llvm/Support/ErrorHandling.h"
 #include "llvm/Support/FileSystem.h"
 #include "llvm/Support/SourceMgr.h"