// Copyright 2020 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 <stddef.h>

#include <algorithm>

#include "base/command_line.h"
#include "base/strings/stringprintf.h"
#include "gn/commands.h"
#include "gn/setup.h"
#include "gn/standard_out.h"

namespace commands {

const char kOutputs[] = "outputs";
const char kOutputs_HelpShort[] = "outputs: Which files a source/target make.";
const char kOutputs_Help[] =
    R"(gn outputs <out_dir> <list of target or file names...>

  Lists the output files corresponding to the given target(s) or file name(s).
  There can be multiple outputs because there can be more than one output
  generated by a build step, and there can be more than one toolchain matched.
  You can also list multiple inputs which will generate a union of all the
  outputs from those inputs.

   - The input target/file names are relative to the current directory.

   - The output file names are relative to the root build directory.

   This command is useful for finding a ninja command that will build only a
   portion of the build.

Target outputs

  If the parameter is a target name that includes a toolchain, it will match
  only that target in that toolchain. If no toolchain is specified, it will
  match all targets with that name in any toolchain.

  The result will be the outputs specified by that target which could be a
  library, executable, output of an action, a stamp file, etc.

File outputs

  If the parameter is a file name it will compute the output for that compile
  step for all targets in all toolchains that contain that file as a source
  file.

  If the source is not compiled (e.g. a header or text file), the command will
  produce no output.

  If the source is listed as an "input" to a binary target or action will
  resolve to that target's outputs.

Example

  gn outputs out/debug some/directory:some_target
      Find the outputs of a given target.

  gn outputs out/debug src/project/my_file.cc | xargs ninja -C out/debug
      Compiles just the given source file in all toolchains it's referenced in.

  git diff --name-only | xargs gn outputs out/x64 | xargs ninja -C out/x64
      Compiles all files changed in git.
)";

int RunOutputs(const std::vector<std::string>& args) {
  if (args.size() < 2) {
    Err(Location(),
        "Expected a build dir and one or more input files or targets.\n"
        "Usage: \"gn outputs <out_dir> <target-or-file>*\"")
        .PrintToStdout();
    return 1;
  }

  // Deliberately leaked to avoid expensive process teardown.
  Setup* setup = new Setup;
  if (!setup->DoSetup(args[0], false))
    return 1;
  if (!setup->Run())
    return 1;

  std::vector<std::string> inputs(args.begin() + 1, args.end());

  UniqueVector<const Target*> target_matches;
  UniqueVector<const Config*> config_matches;
  UniqueVector<const Toolchain*> toolchain_matches;
  UniqueVector<SourceFile> file_matches;
  if (!ResolveFromCommandLineInput(setup, inputs, false, &target_matches,
                                   &config_matches, &toolchain_matches,
                                   &file_matches))
    return 1;

  // We only care about targets and files.
  if (target_matches.empty() && file_matches.empty()) {
    Err(Location(), "The input matched no targets or files.").PrintToStdout();
    return 1;
  }

  // Resulting outputs.
  std::vector<OutputFile> outputs;

  // Files. This must go first because it may add to the "targets" list.
  std::vector<const Target*> all_targets =
      setup->builder().GetAllResolvedTargets();
  for (const SourceFile& file : file_matches) {
    std::vector<TargetContainingFile> targets;
    GetTargetsContainingFile(setup, all_targets, file, false, &targets);
    if (targets.empty()) {
      Err(Location(), base::StringPrintf("No targets reference the file '%s'.",
                                         file.value().c_str()))
          .PrintToStdout();
      return 1;
    }

    // There can be more than one target that references this file, evaluate the
    // output name in all of them.
    for (const TargetContainingFile& pair : targets) {
      if (pair.second == HowTargetContainsFile::kInputs) {
        // Inputs maps to the target itself. This will be evaluated below.
        target_matches.push_back(pair.first);
      } else if (pair.second == HowTargetContainsFile::kSources) {
        // Source file, check it.
        const char* computed_tool = nullptr;
        std::vector<OutputFile> file_outputs;
        pair.first->GetOutputFilesForSource(file, &computed_tool,
                                            &file_outputs);
        outputs.insert(outputs.end(), file_outputs.begin(), file_outputs.end());
      }
    }
  }

  // Targets.
  for (const Target* target : target_matches) {
    std::vector<SourceFile> output_files;
    Err err;
    if (!target->GetOutputsAsSourceFiles(LocationRange(), true, &output_files,
                                         &err)) {
      err.PrintToStdout();
      return 1;
    }

    // Convert to OutputFiles.
    for (const SourceFile& file : output_files)
      outputs.emplace_back(&setup->build_settings(), file);
  }

  // Print.
  for (const OutputFile& output_file : outputs) {
    OutputString(output_file.value());
    OutputString("\n");
  }
  return 0;
}

}  // namespace commands
