text: // Copyright (c) 2013 The Chromium Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  #include "tools/gn/import_manager.h"
  
  #include <memory>
  
  #include "tools/gn/err.h"
  #include "tools/gn/parse_tree.h"
  #include "tools/gn/scheduler.h"
  #include "tools/gn/scope_per_file_provider.h"
  #include "tools/gn/trace.h"
  #include "util/ticks.h"
  
  namespace {
  
  // Returns a newly-allocated scope on success, null on failure.
  std::unique_ptr<Scope> UncachedImport(const Settings* settings,
                                        const SourceFile& file,
                                        const ParseNode* node_for_err,
                                        Err* err) {
    ScopedTrace load_trace(TraceItem::TRACE_IMPORT_LOAD, file.value());
    load_trace.SetToolchain(settings->toolchain_label());
  
    const ParseNode* node = g_scheduler->input_file_manager()->SyncLoadFile(
        node_for_err->GetRange(), settings->build_settings(), file, err);
    if (!node)
      return nullptr;
  
    std::unique_ptr<Scope> scope =
        std::make_unique<Scope>(settings->base_config());
    scope->set_source_dir(file.GetDir());
  
    // Don't allow ScopePerFileProvider to provide target-related variables.
    // These will be relative to the imported file, which is probably not what
    // people mean when they use these.
    ScopePerFileProvider per_file_provider(scope.get(), false);
  
    scope->SetProcessingImport();
    node->Execute(scope.get(), err);
    if (err->has_error()) {
      // If there was an error, append the caller location so the error message
      // displays a why the file was imported (esp. useful for failed asserts).
      err->AppendSubErr(Err(node_for_err, "whence it was imported."));
      return nullptr;
    }
    scope->ClearProcessingImport();
  
    return scope;
  }
  
  }  // namespace
  
  struct ImportManager::ImportInfo {
    ImportInfo() = default;
    ~ImportInfo() = default;
  
    // This lock protects the unique_ptr. Once the scope is computed,
    // it is const and can be accessed read-only outside of the lock.
    std::mutex load_lock;
  
    std::unique_ptr<const Scope> scope;
  
    // The result of loading the import. If the load failed, the scope will be
    // null but this will be set to error. In this case the thread should not
    // attempt to load the file, even if the scope is null.
    Err load_result;
  };
  
  ImportManager::ImportManager() = default;
  
  ImportManager::~ImportManager() = default;
  
  bool ImportManager::DoImport(const SourceFile& file,
                               const ParseNode* node_for_err,
                               Scope* scope,
                               Err* err) {
    // Key for the current import on the current thread in imports_in_progress_.
    std::stringstream ss;
    ss << std::this_thread::get_id() << file.value();
    std::string key = ss.str();
  
    // See if we have a cached import, but be careful to actually do the scope
    // copying outside of the lock.
    ImportInfo* import_info = nullptr;
    {
      std::lock_guard<std::mutex> lock(imports_lock_);
      std::unique_ptr<ImportInfo>& info_ptr = imports_[file];
      if (!info_ptr)
        info_ptr = std::make_unique<ImportInfo>();
  
      // Promote the ImportInfo to outside of the imports lock.
      import_info = info_ptr.get();
  
      if (imports_in_progress_.find(key) != imports_in_progress_.end()) {
        *err = Err(Location(), file.value() + " is part of an import loop.");
        return false;
      }
      imports_in_progress_.insert(key);
    }
  
    // Now use the per-import-file lock to block this thread if another thread
    // is already processing the import.
    const Scope* import_scope = nullptr;
    {
      Ticks import_block_begin = TicksNow();
      std::lock_guard<std::mutex> lock(import_info->load_lock);
  
      if (!import_info->scope) {
        // Only load if the import hasn't already failed.
        if (!import_info->load_result.has_error()) {
          import_info->scope = UncachedImport(
              scope->settings(), file, node_for_err, &import_info->load_result);
        }
        if (import_info->load_result.has_error()) {
          *err = import_info->load_result;
          return false;
        }
      } else {
        // Add trace if this thread was blocked for a long period of time and did
        // not load the import itself.
        Ticks import_block_end = TicksNow();
        constexpr auto kImportBlockTraceThresholdMS = 20;
        if (TracingEnabled() &&
            TicksDelta(import_block_end, import_block_begin).InMilliseconds() >
                kImportBlockTraceThresholdMS) {
          auto* import_block_trace =
              new TraceItem(TraceItem::TRACE_IMPORT_BLOCK, file.value(),
                            std::this_thread::get_id());
          import_block_trace->set_begin(import_block_begin);
          import_block_trace->set_end(import_block_end);
          import_block_trace->set_toolchain(
              scope->settings()->toolchain_label().GetUserVisibleName(false));
          AddTrace(import_block_trace);
        }
      }
  
      // Promote the now-read-only scope to outside the load lock.
      import_scope = import_info->scope.get();
    }
  
    Scope::MergeOptions options;
    options.skip_private_vars = true;
    options.mark_dest_used = true;  // Don't require all imported values be used.
  
    {
      std::lock_guard<std::mutex> lock(imports_lock_);
      imports_in_progress_.erase(key);
    }
  
    return import_scope->NonRecursiveMergeTo(scope, options, node_for_err,
                                             "import", err);
  }
  
  std::vector<SourceFile> ImportManager::GetImportedFiles() const {
    std::vector<SourceFile> imported_files;
    imported_files.resize(imports_.size());
    std::transform(imports_.begin(), imports_.end(), imported_files.begin(),
                   [](const ImportMap::value_type& val) { return val.first; });
    return imported_files;
  }
  