Support importing generated configs

Change-Id: I260851684f809e05d0b486f62950cccc61cc53bc
diff --git a/aos/configuration.cc b/aos/configuration.cc
index b0ff973..bcb4de6 100644
--- a/aos/configuration.cc
+++ b/aos/configuration.cc
@@ -84,14 +84,44 @@
              : filename.substr(0, last_slash_pos + 1);
 }
 
+std::string AbsolutePath(const std::string_view filename) {
+  // Uses an std::string so that we know the input will be null-terminated.
+  const std::string terminated_file(filename);
+  char buffer[PATH_MAX];
+  PCHECK(NULL != realpath(terminated_file.c_str(), buffer));
+  return buffer;
+}
+
 FlatbufferDetachedBuffer<Configuration> ReadConfig(
-    const std::string_view path, absl::btree_set<std::string> *visited_paths) {
+    const std::string_view path, absl::btree_set<std::string> *visited_paths,
+    const std::vector<std::string_view> &extra_import_paths) {
+  std::string raw_path(path);
+  if (!util::PathExists(path)) {
+    const bool path_is_absolute = path.size() > 0 && path[0] == '/';
+    if (path_is_absolute) {
+      CHECK(extra_import_paths.empty())
+          << "Can't specify extra import paths if attempting to read a config "
+             "file from an absolute path (path is "
+          << path << ").";
+    }
+
+    bool found_path = false;
+    for (const auto &import_path : extra_import_paths) {
+      raw_path = std::string(import_path) + "/" + std::string(path);
+      if (util::PathExists(raw_path)) {
+        found_path = true;
+        break;
+      }
+    }
+    CHECK(found_path) << ": Failed to find file " << path << ".";
+  }
   flatbuffers::DetachedBuffer buffer = JsonToFlatbuffer(
-      util::ReadFileToStringOrDie(path), ConfigurationTypeTable());
+      util::ReadFileToStringOrDie(raw_path), ConfigurationTypeTable());
 
   CHECK_GT(buffer.size(), 0u) << ": Failed to parse JSON file";
 
   FlatbufferDetachedBuffer<Configuration> config(std::move(buffer));
+
   // Depth first.  Take the following example:
   //
   // config1.json:
@@ -123,8 +153,16 @@
   // config.  That means that it needs to be merged into the imported configs,
   // not the other way around.
 
+  const std::string absolute_path = AbsolutePath(raw_path);
   // Track that we have seen this file before recursing.
-  visited_paths->insert(::std::string(path));
+  if (!visited_paths->insert(absolute_path).second) {
+    for (const auto &visited_path : *visited_paths) {
+      LOG(INFO) << "Already visited: " << visited_path;
+    }
+    LOG(FATAL)
+        << "Already imported " << path << " (i.e. " << absolute_path
+        << "). See above for the files that have already been processed.";
+  }
 
   if (config.message().has_imports()) {
     // Capture the imports.
@@ -138,18 +176,15 @@
     FlatbufferDetachedBuffer<Configuration> merged_config =
         FlatbufferDetachedBuffer<Configuration>::Empty();
 
-    const ::std::string folder(ExtractFolder(path));
-
+    const std::string path_folder(ExtractFolder(path));
     for (const flatbuffers::String *str : *v) {
-      const ::std::string included_config = folder + str->c_str();
-      // Abort on any paths we have already seen.
-      CHECK(visited_paths->find(included_config) == visited_paths->end())
-          << ": Found duplicate file " << included_config << " while reading "
-          << path;
+      const std::string included_config =
+          path_folder + "/" + std::string(str->string_view());
 
       // And them merge everything in.
       merged_config = MergeFlatBuffers(
-          merged_config, ReadConfig(included_config, visited_paths));
+          merged_config,
+          ReadConfig(included_config, visited_paths, extra_import_paths));
     }
 
     // Finally, merge this file in.
@@ -446,10 +481,11 @@
 }
 
 FlatbufferDetachedBuffer<Configuration> ReadConfig(
-    const std::string_view path) {
+    const std::string_view path,
+    const std::vector<std::string_view> &import_paths) {
   // We only want to read a file once.  So track the visited files in a set.
   absl::btree_set<std::string> visited_paths;
-  return MergeConfiguration(ReadConfig(path, &visited_paths));
+  return MergeConfiguration(ReadConfig(path, &visited_paths, import_paths));
 }
 
 FlatbufferDetachedBuffer<Configuration> MergeWithConfig(