1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "base/files/file_enumerator.h"
6 #include "base/files/file_path.h"
7 #include "base/files/file_util.h"
8 #include "base/strings/string_split.h"
9 #include "base/strings/string_util.h"
10 #include "base/strings/stringprintf.h"
11 #include "gn/commands.h"
12 #include "gn/err.h"
13 #include "gn/filesystem_utils.h"
14 #include "gn/setup.h"
15
16 namespace {
17
18 // Extracts from a build.ninja the commands to run GN.
19 //
20 // The commands to run GN are the gn rule and build.ninja build step at the top
21 // of the build.ninja file. We want to keep these when deleting GN builds since
22 // we want to preserve the command-line flags to GN.
23 //
24 // On error, returns the empty string.
ExtractGNBuildCommands(const base::FilePath & build_ninja_file)25 std::string ExtractGNBuildCommands(const base::FilePath& build_ninja_file) {
26 std::string file_contents;
27 if (!base::ReadFileToString(build_ninja_file, &file_contents))
28 return std::string();
29
30 std::vector<std::string_view> lines = base::SplitStringPiece(
31 file_contents, "\n", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
32
33 std::string result;
34 int num_blank_lines = 0;
35 for (const auto& line : lines) {
36 result.append(line);
37 result.push_back('\n');
38 if (line.empty())
39 ++num_blank_lines;
40 if (num_blank_lines == 3)
41 break;
42 }
43
44 return result;
45 }
46
47 } // namespace
48
49 namespace commands {
50
51 const char kClean[] = "clean";
52 const char kClean_HelpShort[] = "clean: Cleans the output directory.";
53 const char kClean_Help[] =
54 "gn clean <out_dir>\n"
55 "\n"
56 " Deletes the contents of the output directory except for args.gn and\n"
57 " creates a Ninja build environment sufficient to regenerate the build.\n";
58
RunClean(const std::vector<std::string> & args)59 int RunClean(const std::vector<std::string>& args) {
60 if (args.size() != 1) {
61 Err(Location(), "You're holding it wrong.", "Usage: \"gn clean <out_dir>\"")
62 .PrintToStdout();
63 return 1;
64 }
65
66 // Deliberately leaked to avoid expensive process teardown.
67 Setup* setup = new Setup;
68 if (!setup->DoSetup(args[0], false))
69 return 1;
70
71 base::FilePath build_dir(setup->build_settings().GetFullPath(
72 SourceDir(setup->build_settings().build_dir().value())));
73
74 // NOTE: Not all GN builds have args.gn file hence we check here
75 // if a build.ninja.d files exists instead.
76 base::FilePath build_ninja_d_file = build_dir.AppendASCII("build.ninja.d");
77 if (!base::PathExists(build_ninja_d_file)) {
78 Err(Location(),
79 base::StringPrintf(
80 "%s does not look like a build directory.\n",
81 FilePathToUTF8(build_ninja_d_file.DirName().value()).c_str()))
82 .PrintToStdout();
83 return 1;
84 }
85
86 // Erase everything but the args file, and write a dummy build.ninja file that
87 // will automatically rerun GN the next time Ninja is run.
88 base::FilePath build_ninja_file = build_dir.AppendASCII("build.ninja");
89 std::string build_commands = ExtractGNBuildCommands(build_ninja_file);
90 if (build_commands.empty()) {
91 // Couldn't parse the build.ninja file.
92 Err(Location(), "Couldn't read build.ninja in this directory.",
93 "Try running \"gn gen\" on it and then re-running \"gn clean\".")
94 .PrintToStdout();
95 return 1;
96 }
97
98 base::FileEnumerator traversal(
99 build_dir, false,
100 base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES);
101 for (base::FilePath current = traversal.Next(); !current.empty();
102 current = traversal.Next()) {
103 if (base::ToLowerASCII(current.BaseName().value()) !=
104 FILE_PATH_LITERAL("args.gn")) {
105 base::DeleteFile(current, true);
106 }
107 }
108
109 // Write the build.ninja file sufficiently to regenerate itself.
110 if (base::WriteFile(build_ninja_file, build_commands.data(),
111 static_cast<int>(build_commands.size())) == -1) {
112 Err(Location(), std::string("Failed to write build.ninja."))
113 .PrintToStdout();
114 return 1;
115 }
116
117 // Write a .d file for the build which references a nonexistant file.
118 // This will make Ninja always mark the build as dirty.
119 std::string dummy_content("build.ninja: nonexistant_file.gn\n");
120 if (base::WriteFile(build_ninja_d_file, dummy_content.data(),
121 static_cast<int>(dummy_content.size())) == -1) {
122 Err(Location(), std::string("Failed to write build.ninja.d."))
123 .PrintToStdout();
124 return 1;
125 }
126
127 return 0;
128 }
129
130 } // namespace commands
131