// Copyright 2018 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/ninja_target_command_util.h"

#include <string.h>

#include "gn/c_tool.h"
#include "gn/substitution_writer.h"

namespace {

// Returns the language-specific suffix for precompiled header files.
const char* GetPCHLangSuffixForToolType(const char* name) {
  if (name == CTool::kCToolCc)
    return "c";
  if (name == CTool::kCToolCxx)
    return "cc";
  if (name == CTool::kCToolObjC)
    return "m";
  if (name == CTool::kCToolObjCxx)
    return "mm";
  NOTREACHED() << "Not a valid PCH tool type: " << name;
  return "";
}

}  // namespace

// Returns the computed name of the Windows .pch file for the given
// tool type. The tool must support precompiled headers.
OutputFile GetWindowsPCHFile(const Target* target, const char* tool_name) {
  // Use "obj/{dir}/{target_name}_{lang}.pch" which ends up
  // looking like "obj/chrome/browser/browser_cc.pch"
  return GetOutputFile(*target, BuildDirType::OBJ, target->label().name(), "_",
                       GetPCHLangSuffixForToolType(tool_name), ".pch");
}

void WriteOneFlag(RecursiveWriterConfig config,
                  const Target* target,
                  const Substitution* subst_enum,
                  bool has_precompiled_headers,
                  const char* tool_name,
                  const std::vector<std::string>& (ConfigValues::*getter)()
                      const,
                  EscapeOptions flag_escape_options,
                  PathOutput& path_output,
                  std::ostream& out,
                  bool write_substitution,
                  bool indent) {
  if (!target->toolchain()->substitution_bits().used.count(subst_enum))
    return;

  if (indent)
    out << "  ";
  if (write_substitution)
    out << subst_enum->ninja_name << " =";

  if (has_precompiled_headers) {
    const CTool* tool = target->toolchain()->GetToolAsC(tool_name);
    if (tool && tool->precompiled_header_type() == CTool::PCH_MSVC) {
      // Name the .pch file.
      out << " /Fp";
      path_output.WriteFile(out, GetWindowsPCHFile(target, tool_name));

      // Enables precompiled headers and names the .h file. It's a string
      // rather than a file name (so no need to rebase or use path_output).
      out << " /Yu" << target->config_values().precompiled_header();
      RecursiveTargetConfigStringsToStream(config, target, getter,
                                           flag_escape_options, out);
    } else if (tool && tool->precompiled_header_type() == CTool::PCH_GCC) {
      // The targets to build the .gch files should omit the -include flag
      // below. To accomplish this, each substitution flag is overwritten in
      // the target rule and these values are repeated. The -include flag is
      // omitted in place of the required -x <header lang> flag for .gch
      // targets.
      RecursiveTargetConfigStringsToStream(config, target, getter,
                                           flag_escape_options, out);

      // Compute the gch file (it will be language-specific).
      std::vector<OutputFile> outputs;
      GetPCHOutputFiles(target, tool_name, &outputs);
      if (!outputs.empty()) {
        // Trim the .gch suffix for the -include flag.
        // e.g. for gch file foo/bar/target.precompiled.h.gch:
        //          -include foo/bar/target.precompiled.h
        std::string_view pch_file = outputs[0].value();
        out << " -include " << pch_file.substr(0, pch_file.length() - 4);
      }
    } else {
      RecursiveTargetConfigStringsToStream(config, target, getter,
                                           flag_escape_options, out);
    }
  } else {
    RecursiveTargetConfigStringsToStream(config, target, getter,
                                         flag_escape_options, out);
  }

  if (write_substitution)
    out << std::endl;
}

void GetPCHOutputFiles(const Target* target,
                       const char* tool_name,
                       std::vector<OutputFile>* outputs) {
  outputs->clear();

  // Compute the tool. This must use the tool type passed in rather than the
  // detected file type of the precompiled source file since the same
  // precompiled source file will be used for separate C/C++ compiles.
  const CTool* tool = target->toolchain()->GetToolAsC(tool_name);
  if (!tool)
    return;
  SubstitutionWriter::ApplyListToCompilerAsOutputFile(
      target, target->config_values().precompiled_source(), tool->outputs(),
      outputs);

  if (outputs->empty())
    return;
  if (outputs->size() > 1)
    outputs->resize(1);  // Only link the first output from the compiler tool.

  OutputFile& output_value = (*outputs)[0];
  std::string_view output_value_str = output_value.value();
  size_t extension_offset = FindExtensionOffset(output_value_str);
  if (extension_offset == std::string::npos) {
    // No extension found.
    return;
  }
  DCHECK(extension_offset >= 1);
  // Trim the "."
  extension_offset--;
  DCHECK(output_value_str[extension_offset] == '.');

  std::string output_extension;
  CTool::PrecompiledHeaderType header_type = tool->precompiled_header_type();
  switch (header_type) {
    case CTool::PCH_MSVC:
      output_extension = GetWindowsPCHObjectExtension(
          tool_name, output_value_str.substr(extension_offset));
      break;
    case CTool::PCH_GCC:
      output_extension = GetGCCPCHOutputExtension(tool_name);
      break;
    case CTool::PCH_NONE:
      NOTREACHED() << "No outputs for no PCH type.";
      break;
  }
  std::string path(output_value.value().substr(0, extension_offset));
  path.append(output_extension);
  output_value = OutputFile(std::move(path));
}

std::string GetGCCPCHOutputExtension(const char* tool_name) {
  const char* lang_suffix = GetPCHLangSuffixForToolType(tool_name);
  std::string result = ".";
  // For GCC, the output name must have a .gch suffix and be annotated with
  // the language type. For example:
  //   obj/foo/target_name.header.h ->
  //   obj/foo/target_name.header.h-cc.gch
  // In order for the compiler to pick it up, the output name (minus the .gch
  // suffix MUST match whatever is passed to the -include flag).
  result += "h-";
  result += lang_suffix;
  result += ".gch";
  return result;
}

std::string GetWindowsPCHObjectExtension(const char* tool_name,
                                         std::string_view obj_extension) {
  const char* lang_suffix = GetPCHLangSuffixForToolType(tool_name);
  std::string result = ".";
  // For MSVC, annotate the obj files with the language type. For example:
  //   obj/foo/target_name.precompile.obj ->
  //   obj/foo/target_name.precompile.cc.obj
  result += lang_suffix;
  result += obj_extension;
  return result;
}
