• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
CleanOneDir(const std::string & dir)47 bool CleanOneDir(const std::string& dir) {
48   // Deliberately leaked to avoid expensive process teardown.
49   Setup* setup = new Setup;
50   if (!setup->DoSetup(dir, false))
51     return false;
52 
53   base::FilePath build_dir(setup->build_settings().GetFullPath(
54       SourceDir(setup->build_settings().build_dir().value())));
55 
56   // NOTE: Not all GN builds have args.gn file hence we check here
57   // if a build.ninja.d files exists instead.
58   base::FilePath build_ninja_d_file = build_dir.AppendASCII("build.ninja.d");
59   if (!base::PathExists(build_ninja_d_file)) {
60     Err(Location(),
61         base::StringPrintf(
62             "%s does not look like a build directory.\n",
63             FilePathToUTF8(build_ninja_d_file.DirName().value()).c_str()))
64         .PrintToStdout();
65     return false;
66   }
67 
68   // Erase everything but the args file, and write a dummy build.ninja file that
69   // will automatically rerun GN the next time Ninja is run.
70   base::FilePath build_ninja_file = build_dir.AppendASCII("build.ninja");
71   std::string build_commands = ExtractGNBuildCommands(build_ninja_file);
72   if (build_commands.empty()) {
73     // Couldn't parse the build.ninja file.
74     Err(Location(), "Couldn't read build.ninja in this directory.",
75         "Try running \"gn gen\" on it and then re-running \"gn clean\".")
76         .PrintToStdout();
77     return false;
78   }
79 
80   base::FileEnumerator traversal(
81       build_dir, false,
82       base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES);
83   for (base::FilePath current = traversal.Next(); !current.empty();
84        current = traversal.Next()) {
85     if (base::ToLowerASCII(current.BaseName().value()) !=
86         FILE_PATH_LITERAL("args.gn")) {
87       base::DeleteFile(current, true);
88     }
89   }
90 
91   // Write the build.ninja file sufficiently to regenerate itself.
92   if (base::WriteFile(build_ninja_file, build_commands.data(),
93                       static_cast<int>(build_commands.size())) == -1) {
94     Err(Location(), std::string("Failed to write build.ninja."))
95         .PrintToStdout();
96     return false;
97   }
98 
99   // Write a .d file for the build which references a nonexistent file.
100   // This will make Ninja always mark the build as dirty.
101   std::string dummy_content("build.ninja: nonexistant_file.gn\n");
102   if (base::WriteFile(build_ninja_d_file, dummy_content.data(),
103                       static_cast<int>(dummy_content.size())) == -1) {
104     Err(Location(), std::string("Failed to write build.ninja.d."))
105         .PrintToStdout();
106     return false;
107   }
108 
109   return true;
110 }
111 
112 }  // namespace
113 
114 namespace commands {
115 
116 const char kClean[] = "clean";
117 const char kClean_HelpShort[] = "clean: Cleans the output directory.";
118 const char kClean_Help[] =
119     "gn clean <out_dir>...\n"
120     "\n"
121     "  Deletes the contents of the output directory except for args.gn and\n"
122     "  creates a Ninja build environment sufficient to regenerate the build.\n";
123 
RunClean(const std::vector<std::string> & args)124 int RunClean(const std::vector<std::string>& args) {
125   if (args.empty()) {
126     Err(Location(), "Missing argument.", "Usage: \"gn clean <out_dir>...\"")
127         .PrintToStdout();
128     return 1;
129   }
130 
131   for (const auto& dir : args) {
132     if (!CleanOneDir(dir))
133       return 1;
134   }
135 
136   return 0;
137 }
138 
139 }  // namespace commands
140