text: // Copyright 2015 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 "base/files/file_enumerator.h"
  #include "base/files/file_path.h"
  #include "base/files/file_util.h"
  #include "base/strings/string_split.h"
  #include "base/strings/string_util.h"
  #include "base/strings/stringprintf.h"
  #include "tools/gn/commands.h"
  #include "tools/gn/err.h"
  #include "tools/gn/filesystem_utils.h"
  #include "tools/gn/setup.h"
  
  namespace {
  
  // Extracts from a build.ninja the commands to run GN.
  //
  // The commands to run GN are the gn rule and build.ninja build step at the top
  // of the build.ninja file. We want to keep these when deleting GN builds since
  // we want to preserve the command-line flags to GN.
  //
  // On error, returns the empty string.
  std::string ExtractGNBuildCommands(const base::FilePath& build_ninja_file) {
    std::string file_contents;
    if (!base::ReadFileToString(build_ninja_file, &file_contents))
      return std::string();
  
    std::vector<std::string_view> lines = base::SplitStringPiece(
        file_contents, "\n", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
  
    std::string result;
    int num_blank_lines = 0;
    for (const auto& line : lines) {
      result.append(line);
      result.push_back('\n');
      if (line.empty())
        ++num_blank_lines;
      if (num_blank_lines == 3)
        break;
    }
  
    return result;
  }
  
  }  // namespace
  
  namespace commands {
  
  const char kClean[] = "clean";
  const char kClean_HelpShort[] = "clean: Cleans the output directory.";
  const char kClean_Help[] =
      "gn clean <out_dir>\n"
      "\n"
      "  Deletes the contents of the output directory except for args.gn and\n"
      "  creates a Ninja build environment sufficient to regenerate the build.\n";
  
  int RunClean(const std::vector<std::string>& args) {
    if (args.size() != 1) {
      Err(Location(), "You're holding it wrong.", "Usage: \"gn clean <out_dir>\"")
          .PrintToStdout();
      return 1;
    }
  
    // Deliberately leaked to avoid expensive process teardown.
    Setup* setup = new Setup;
    if (!setup->DoSetup(args[0], false))
      return 1;
  
    base::FilePath build_dir(setup->build_settings().GetFullPath(
        SourceDir(setup->build_settings().build_dir().value())));
  
    // NOTE: Not all GN builds have args.gn file hence we check here
    // if a build.ninja.d files exists instead.
    base::FilePath build_ninja_d_file = build_dir.AppendASCII("build.ninja.d");
    if (!base::PathExists(build_ninja_d_file)) {
      Err(Location(),
          base::StringPrintf(
              "%s does not look like a build directory.\n",
              FilePathToUTF8(build_ninja_d_file.DirName().value()).c_str()))
          .PrintToStdout();
      return 1;
    }
  
    // Erase everything but the args file, and write a dummy build.ninja file that
    // will automatically rerun GN the next time Ninja is run.
    base::FilePath build_ninja_file = build_dir.AppendASCII("build.ninja");
    std::string build_commands = ExtractGNBuildCommands(build_ninja_file);
    if (build_commands.empty()) {
      // Couldn't parse the build.ninja file.
      Err(Location(), "Couldn't read build.ninja in this directory.",
          "Try running \"gn gen\" on it and then re-running \"gn clean\".")
          .PrintToStdout();
      return 1;
    }
  
    base::FileEnumerator traversal(
        build_dir, false,
        base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES);
    for (base::FilePath current = traversal.Next(); !current.empty();
         current = traversal.Next()) {
      if (base::ToLowerASCII(current.BaseName().value()) !=
          FILE_PATH_LITERAL("args.gn")) {
        base::DeleteFile(current, true);
      }
    }
  
    // Write the build.ninja file sufficiently to regenerate itself.
    if (base::WriteFile(build_ninja_file, build_commands.data(),
                        static_cast<int>(build_commands.size())) == -1) {
      Err(Location(), std::string("Failed to write build.ninja."))
          .PrintToStdout();
      return 1;
    }
  
    // Write a .d file for the build which references a nonexistant file.
    // This will make Ninja always mark the build as dirty.
    std::string dummy_content("build.ninja: nonexistant_file.gn\n");
    if (base::WriteFile(build_ninja_d_file, dummy_content.data(),
                        static_cast<int>(dummy_content.size())) == -1) {
      Err(Location(), std::string("Failed to write build.ninja.d."))
          .PrintToStdout();
      return 1;
    }
  
    return 0;
  }
  
  }  // namespace commands
  