| // 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 "gn/commands.h" |
| #include "gn/err.h" |
| #include "gn/filesystem_utils.h" |
| #include "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; |
| } |
| |
| bool CleanOneDir(const std::string& dir) { |
| // Deliberately leaked to avoid expensive process teardown. |
| Setup* setup = new Setup; |
| if (!setup->DoSetup(dir, false)) |
| return false; |
| |
| 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 false; |
| } |
| |
| // 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 false; |
| } |
| |
| 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 false; |
| } |
| |
| // 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 false; |
| } |
| |
| return true; |
| } |
| |
| } // 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.empty()) { |
| Err(Location(), "Missing argument.", "Usage: \"gn clean <out_dir>...\"") |
| .PrintToStdout(); |
| return 1; |
| } |
| |
| for (const auto& dir : args) { |
| if (!CleanOneDir(dir)) |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| } // namespace commands |