summaryrefslogtreecommitdiffstats
path: root/tools/gn/command_clean.cc
blob: d14b22d84a39e7f3949c097c180f03bc9bade75e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
// 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_path.h"
#include "base/files/file_util.h"
#include "base/strings/string_split.h"
#include "base/strings/stringprintf.h"
#include "tools/gn/commands.h"
#include "tools/gn/err.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> lines = base::SplitString(
      file_contents, "\n", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);

  std::string result;
  int num_blank_lines = 0;
  for (const auto& line : lines) {
    result += line;
    result += "\n";
    if (line.empty()) {
      ++num_blank_lines;
    }
    if (num_blank_lines == 2)
      break;
  }

  return result;
}

const char kDefaultNinjaFile[] =
    "rule gn\n"
    "  command = gn -q gen //out/%s/\n"
    "  description = Regenerating ninja files\n"
    "\n"
    "build build.ninja: gn\n"
    "  generator = 1\n"
    "  depfile = build.ninja.d\n";

}  // 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;
  }

  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",
                           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);

  // Read the args.gn file, if any. Not all GN builds have one.
  base::FilePath gn_args_file = build_dir.AppendASCII("args.gn");
  std::string args_contents;
  base::ReadFileToString(gn_args_file, &args_contents);

  base::DeleteFile(build_dir, true);

  // Put back the args.gn file (if any).
  base::CreateDirectory(build_dir);
  if (!args_contents.empty()) {
    if (base::WriteFile(gn_args_file, args_contents.data(),
                        static_cast<int>(args_contents.size())) == -1) {
      Err(Location(), std::string("Failed to write args.gn.")).PrintToStdout();
      return 1;
    }
  }

  // Write the build.ninja file sufficiently to regenerate itself.
  if (!build_commands.empty()) {
    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;
    }
  } else {
    // Couldn't parse the build.ninja file, write a default thing.
    std::vector<base::FilePath::StringType> components;
    build_ninja_file.GetComponents(&components);
    std::string default_build_file = base::StringPrintf(
        kDefaultNinjaFile, components[components.size() - 2].c_str());
    if (base::WriteFile(build_ninja_file, default_build_file.data(),
                        static_cast<int>(default_build_file.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