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