1 // Copyright (c) 2013 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 "tools/gn/ninja_build_writer.h" 6 7 #include <fstream> 8 #include <map> 9 10 #include "base/command_line.h" 11 #include "base/files/file_util.h" 12 #include "base/path_service.h" 13 #include "base/process/process_handle.h" 14 #include "base/strings/string_util.h" 15 #include "base/strings/utf_string_conversions.h" 16 #include "build/build_config.h" 17 #include "tools/gn/build_settings.h" 18 #include "tools/gn/escape.h" 19 #include "tools/gn/filesystem_utils.h" 20 #include "tools/gn/input_file_manager.h" 21 #include "tools/gn/ninja_utils.h" 22 #include "tools/gn/scheduler.h" 23 #include "tools/gn/target.h" 24 #include "tools/gn/trace.h" 25 26 #if defined(OS_WIN) 27 #include <windows.h> 28 #endif 29 30 namespace { 31 32 std::string GetSelfInvocationCommand(const BuildSettings* build_settings) { 33 base::FilePath executable; 34 PathService::Get(base::FILE_EXE, &executable); 35 36 CommandLine cmdline(executable.NormalizePathSeparatorsTo('/')); 37 cmdline.AppendArg("gen"); 38 cmdline.AppendArg(build_settings->build_dir().value()); 39 cmdline.AppendSwitchPath("--root", build_settings->root_path()); 40 cmdline.AppendSwitch("-q"); // Don't write output. 41 42 EscapeOptions escape_shell; 43 escape_shell.mode = ESCAPE_NINJA_COMMAND; 44 #if defined(OS_WIN) 45 // The command line code quoting varies by platform. We have one string, 46 // possibly with spaces, that we want to quote. The Windows command line 47 // quotes again, so we don't want quoting. The Posix one doesn't. 48 escape_shell.inhibit_quoting = true; 49 #endif 50 51 const CommandLine& our_cmdline = *CommandLine::ForCurrentProcess(); 52 const CommandLine::SwitchMap& switches = our_cmdline.GetSwitches(); 53 for (CommandLine::SwitchMap::const_iterator i = switches.begin(); 54 i != switches.end(); ++i) { 55 // Only write arguments we haven't already written. Always skip "args" 56 // since those will have been written to the file and will be used 57 // implicitly in the future. Keeping --args would mean changes to the file 58 // would be ignored. 59 if (i->first != "q" && i->first != "root" && i->first != "args") { 60 std::string escaped_value = 61 EscapeString(FilePathToUTF8(i->second), escape_shell, NULL); 62 cmdline.AppendSwitchASCII(i->first, escaped_value); 63 } 64 } 65 66 #if defined(OS_WIN) 67 return base::WideToUTF8(cmdline.GetCommandLineString()); 68 #else 69 return cmdline.GetCommandLineString(); 70 #endif 71 } 72 73 } // namespace 74 75 NinjaBuildWriter::NinjaBuildWriter( 76 const BuildSettings* build_settings, 77 const std::vector<const Settings*>& all_settings, 78 const Toolchain* default_toolchain, 79 const std::vector<const Target*>& default_toolchain_targets, 80 std::ostream& out, 81 std::ostream& dep_out) 82 : build_settings_(build_settings), 83 all_settings_(all_settings), 84 default_toolchain_(default_toolchain), 85 default_toolchain_targets_(default_toolchain_targets), 86 out_(out), 87 dep_out_(dep_out), 88 path_output_(build_settings->build_dir(), ESCAPE_NINJA) { 89 } 90 91 NinjaBuildWriter::~NinjaBuildWriter() { 92 } 93 94 void NinjaBuildWriter::Run() { 95 WriteNinjaRules(); 96 WriteLinkPool(); 97 WriteSubninjas(); 98 WritePhonyAndAllRules(); 99 } 100 101 // static 102 bool NinjaBuildWriter::RunAndWriteFile( 103 const BuildSettings* build_settings, 104 const std::vector<const Settings*>& all_settings, 105 const Toolchain* default_toolchain, 106 const std::vector<const Target*>& default_toolchain_targets) { 107 ScopedTrace trace(TraceItem::TRACE_FILE_WRITE, "build.ninja"); 108 109 base::FilePath ninja_file(build_settings->GetFullPath( 110 SourceFile(build_settings->build_dir().value() + "build.ninja"))); 111 base::CreateDirectory(ninja_file.DirName()); 112 113 std::ofstream file; 114 file.open(FilePathToUTF8(ninja_file).c_str(), 115 std::ios_base::out | std::ios_base::binary); 116 if (file.fail()) 117 return false; 118 119 std::ofstream depfile; 120 depfile.open((FilePathToUTF8(ninja_file) + ".d").c_str(), 121 std::ios_base::out | std::ios_base::binary); 122 if (depfile.fail()) 123 return false; 124 125 NinjaBuildWriter gen(build_settings, all_settings, default_toolchain, 126 default_toolchain_targets, file, depfile); 127 gen.Run(); 128 return true; 129 } 130 131 void NinjaBuildWriter::WriteNinjaRules() { 132 out_ << "rule gn\n"; 133 out_ << " command = " << GetSelfInvocationCommand(build_settings_) << "\n"; 134 out_ << " description = Regenerating ninja files\n\n"; 135 136 // This rule will regenerate the ninja files when any input file has changed. 137 out_ << "build build.ninja: gn\n" 138 << " generator = 1\n" 139 << " depfile = build.ninja.d\n"; 140 141 // Input build files. These go in the ".d" file. If we write them as 142 // dependencies in the .ninja file itself, ninja will expect the files to 143 // exist and will error if they don't. When files are listed in a depfile, 144 // missing files are ignored. 145 dep_out_ << "build.ninja:"; 146 std::vector<base::FilePath> input_files; 147 g_scheduler->input_file_manager()->GetAllPhysicalInputFileNames(&input_files); 148 for (size_t i = 0; i < input_files.size(); i++) 149 dep_out_ << " " << FilePathToUTF8(input_files[i]); 150 151 // Other files read by the build. 152 std::vector<base::FilePath> other_files = g_scheduler->GetGenDependencies(); 153 for (size_t i = 0; i < other_files.size(); i++) 154 dep_out_ << " " << FilePathToUTF8(other_files[i]); 155 156 out_ << std::endl; 157 } 158 159 void NinjaBuildWriter::WriteLinkPool() { 160 out_ << "pool link_pool\n" 161 << " depth = " << default_toolchain_->concurrent_links() << std::endl 162 << std::endl; 163 } 164 165 void NinjaBuildWriter::WriteSubninjas() { 166 for (size_t i = 0; i < all_settings_.size(); i++) { 167 out_ << "subninja "; 168 path_output_.WriteFile(out_, GetNinjaFileForToolchain(all_settings_[i])); 169 out_ << std::endl; 170 } 171 out_ << std::endl; 172 } 173 174 void NinjaBuildWriter::WritePhonyAndAllRules() { 175 std::string all_rules; 176 177 // Write phony rules for all uniquely-named targets in the default toolchain. 178 // Don't do other toolchains or we'll get naming conflicts, and if the name 179 // isn't unique, also skip it. The exception is for the toplevel targets 180 // which we also find. 181 std::map<std::string, int> small_name_count; 182 std::vector<const Target*> toplevel_targets; 183 for (size_t i = 0; i < default_toolchain_targets_.size(); i++) { 184 const Target* target = default_toolchain_targets_[i]; 185 const Label& label = target->label(); 186 small_name_count[label.name()]++; 187 188 // Look for targets with a name of the form 189 // dir = "//foo/", name = "foo" 190 // i.e. where the target name matches the top level directory. We will 191 // always write phony rules for these even if there is another target with 192 // the same short name. 193 const std::string& dir_string = label.dir().value(); 194 if (dir_string.size() == label.name().size() + 3 && // Size matches. 195 dir_string[0] == '/' && dir_string[1] == '/' && // "//" at beginning. 196 dir_string[dir_string.size() - 1] == '/' && // "/" at end. 197 dir_string.compare(2, label.name().size(), label.name()) == 0) 198 toplevel_targets.push_back(target); 199 } 200 201 for (size_t i = 0; i < default_toolchain_targets_.size(); i++) { 202 const Target* target = default_toolchain_targets_[i]; 203 const Label& label = target->label(); 204 OutputFile target_file(target->dependency_output_file()); 205 // The output files may have leading "./" so normalize those away. 206 NormalizePath(&target_file.value()); 207 208 // Write the long name "foo/bar:baz" for the target "//foo/bar:baz". 209 std::string long_name = label.GetUserVisibleName(false); 210 base::TrimString(long_name, "/", &long_name); 211 WritePhonyRule(target, target_file, long_name); 212 213 // Write the directory name with no target name if they match 214 // (e.g. "//foo/bar:bar" -> "foo/bar"). 215 if (FindLastDirComponent(label.dir()) == label.name()) { 216 std::string medium_name = DirectoryWithNoLastSlash(label.dir()); 217 base::TrimString(medium_name, "/", &medium_name); 218 // That may have generated a name the same as the short name of the 219 // target which we already wrote. 220 if (medium_name != label.name()) 221 WritePhonyRule(target, target_file, medium_name); 222 } 223 224 // Write short names for ones which are unique. 225 if (small_name_count[label.name()] == 1) 226 WritePhonyRule(target, target_file, label.name()); 227 228 if (!all_rules.empty()) 229 all_rules.append(" $\n "); 230 all_rules.append(target_file.value()); 231 } 232 233 // Pick up phony rules for the toplevel targets with non-unique names (which 234 // would have been skipped in the above loop). 235 for (size_t i = 0; i < toplevel_targets.size(); i++) { 236 if (small_name_count[toplevel_targets[i]->label().name()] > 1) { 237 const Target* target = toplevel_targets[i]; 238 WritePhonyRule(target, target->dependency_output_file(), 239 target->label().name()); 240 } 241 } 242 243 if (!all_rules.empty()) { 244 out_ << "\nbuild all: phony " << all_rules << std::endl; 245 out_ << "default all" << std::endl; 246 } 247 } 248 249 void NinjaBuildWriter::WritePhonyRule(const Target* target, 250 const OutputFile& target_file, 251 const std::string& phony_name) { 252 if (target_file.value() == phony_name) 253 return; // No need for a phony rule. 254 255 EscapeOptions ninja_escape; 256 ninja_escape.mode = ESCAPE_NINJA; 257 258 // Escape for special chars Ninja will handle. 259 std::string escaped = EscapeString(phony_name, ninja_escape, NULL); 260 261 out_ << "build " << escaped << ": phony "; 262 path_output_.WriteFile(out_, target_file); 263 out_ << std::endl; 264 } 265