// Copyright 2019 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 "gn/rust_tool.h"

#include "gn/rust_substitution_type.h"
#include "gn/target.h"

const char* RustTool::kRsToolBin = "rust_bin";
const char* RustTool::kRsToolCDylib = "rust_cdylib";
const char* RustTool::kRsToolDylib = "rust_dylib";
const char* RustTool::kRsToolMacro = "rust_macro";
const char* RustTool::kRsToolRlib = "rust_rlib";
const char* RustTool::kRsToolStaticlib = "rust_staticlib";

RustTool::RustTool(const char* n) : Tool(n) {
  CHECK(ValidateName(n));
  // TODO: should these be settable in toolchain definition? They would collide
  // with the same switch names for C tools, however. So reading them from the
  // toolchain definition would produce the incorrect switch unless we separate
  // their names somehow.
  set_framework_switch("-lframework=");
  // NOTE: No support for weak_framework in rustc, so we just use `-lframework`
  // for now, to avoid more cryptic compiler errors.
  // https://doc.rust-lang.org/rustc/command-line-arguments.html#-l-link-the-generated-crate-to-a-native-library
  set_weak_framework_switch("-lframework=");
  set_framework_dir_switch("-Lframework=");
  set_lib_dir_switch("-Lnative=");
  set_lib_switch("-l");
  set_linker_arg("-Clink-arg=");
  set_dynamic_link_switch("-Clink-arg=-Bdynamic");
}

RustTool::~RustTool() = default;

RustTool* RustTool::AsRust() {
  return this;
}
const RustTool* RustTool::AsRust() const {
  return this;
}

bool RustTool::ValidateName(const char* name) const {
  return name == kRsToolBin || name == kRsToolCDylib || name == kRsToolDylib ||
         name == kRsToolMacro || name == kRsToolRlib ||
         name == kRsToolStaticlib;
}

bool RustTool::MayLink() const {
  return name_ == kRsToolBin || name_ == kRsToolCDylib ||
         name_ == kRsToolDylib || name_ == kRsToolMacro;
}

void RustTool::SetComplete() {
  SetToolComplete();
  link_output_.FillRequiredTypes(&substitution_bits_);
  depend_output_.FillRequiredTypes(&substitution_bits_);
}

std::string_view RustTool::GetSysroot() const {
  return rust_sysroot_;
}

bool RustTool::SetOutputExtension(const Value* value,
                                  std::string* var,
                                  Err* err) {
  DCHECK(!complete_);
  if (!value)
    return true;  // Not present is fine.
  if (!value->VerifyTypeIs(Value::STRING, err))
    return false;
  if (value->string_value().empty())
    return true;

  *var = std::move(value->string_value());
  return true;
}

bool RustTool::ReadOutputsPatternList(Scope* scope,
                                      const char* var,
                                      SubstitutionList* field,
                                      Err* err) {
  DCHECK(!complete_);
  const Value* value = scope->GetValue(var, true);
  if (!value)
    return true;  // Not present is fine.
  if (!value->VerifyTypeIs(Value::LIST, err))
    return false;

  SubstitutionList list;
  if (!list.Parse(*value, err))
    return false;

  // Validate the right kinds of patterns are used.
  if (list.list().empty()) {
    *err = Err(defined_from(), "\"outputs\" must be specified for this tool.");
    return false;
  }

  for (const auto& cur_type : list.required_types()) {
    if (!IsValidRustSubstitution(cur_type)) {
      *err = Err(*value, "Pattern not valid here.",
                 "You used the pattern " + std::string(cur_type->name) +
                     " which is not valid\nfor this variable.");
      return false;
    }
  }

  *field = std::move(list);
  return true;
}

bool RustTool::ValidateRuntimeOutputs(Err* err) {
  if (runtime_outputs().list().empty())
    return true;  // Empty is always OK.

  if (name_ == kRsToolRlib || name_ == kRsToolStaticlib) {
    *err =
        Err(defined_from(), "This tool specifies runtime_outputs.",
            "This is only valid for linker tools (rust_rlib doesn't count).");
    return false;
  }

  for (const SubstitutionPattern& pattern : runtime_outputs().list()) {
    if (!IsPatternInOutputList(outputs(), pattern)) {
      *err = Err(defined_from(), "This tool's runtime_outputs is bad.",
                 "It must be a subset of the outputs. The bad one is:\n  " +
                     pattern.AsString());
      return false;
    }
  }
  return true;
}

// Validates either link_output or depend_output. To generalize to either, pass
// the associated pattern, and the variable name that should appear in error
// messages.
bool RustTool::ValidateLinkAndDependOutput(const SubstitutionPattern& pattern,
                                           const char* variable_name,
                                           Err* err) {
  if (pattern.empty())
    return true;  // Empty is always OK.

  // It should only be specified for certain tool types.
  if (name_ == kRsToolRlib || name_ == kRsToolStaticlib) {
    *err = Err(defined_from(),
               "This tool specifies a " + std::string(variable_name) + ".",
               "This is only valid for linking tools, not rust_rlib or "
               "rust_staticlib.");
    return false;
  }

  if (!IsPatternInOutputList(outputs(), pattern)) {
    *err = Err(defined_from(), "This tool's link_output is bad.",
               "It must match one of the outputs.");
    return false;
  }

  return true;
}

bool RustTool::InitTool(Scope* scope, Toolchain* toolchain, Err* err) {
  // Initialize default vars.
  if (!Tool::InitTool(scope, toolchain, err)) {
    return false;
  }

  // All Rust tools should have outputs.
  if (!ReadOutputsPatternList(scope, "outputs", &outputs_, err)) {
    return false;
  }

  // Check for a sysroot. Sets an empty string when not explicitly set.
  if (!ReadString(scope, "rust_sysroot", &rust_sysroot_, err)) {
    return false;
  }

  if (MayLink()) {
    if (!ReadString(scope, "rust_swiftmodule_switch", &swiftmodule_switch_,
                    err) ||
        !ReadString(scope, "dynamic_link_switch", &dynamic_link_switch_, err)) {
      return false;
    }
  }

  if (!ValidateRuntimeOutputs(err)) {
    return false;
  }

  if (!ReadPattern(scope, "link_output", &link_output_, err) ||
      !ReadPattern(scope, "depend_output", &depend_output_, err)) {
    return false;
  }

  // Validate link_output and depend_output.
  if (!ValidateLinkAndDependOutput(link_output(), "link_output", err)) {
    return false;
  }
  if (!ValidateLinkAndDependOutput(depend_output(), "depend_output", err)) {
    return false;
  }
  if (link_output().empty() != depend_output().empty()) {
    *err = Err(defined_from(),
               "Both link_output and depend_output should either "
               "be specified or they should both be empty.");
    return false;
  }

  return true;
}

bool RustTool::ValidateSubstitution(const Substitution* sub_type) const {
  if (MayLink())
    return IsValidRustLinkerSubstitution(sub_type);
  if (ValidateName(name_))
    return IsValidRustSubstitution(sub_type);
  NOTREACHED();
  return false;
}