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_target_writer.h" 6 7 #include <fstream> 8 #include <sstream> 9 10 #include "base/file_util.h" 11 #include "base/logging.h" 12 #include "base/strings/string_util.h" 13 #include "tools/gn/config_values_extractors.h" 14 #include "tools/gn/err.h" 15 #include "tools/gn/escape.h" 16 #include "tools/gn/file_template.h" 17 #include "tools/gn/filesystem_utils.h" 18 #include "tools/gn/location.h" 19 #include "tools/gn/path_output.h" 20 #include "tools/gn/scheduler.h" 21 #include "tools/gn/string_utils.h" 22 #include "tools/gn/target.h" 23 24 namespace { 25 26 static const char kCustomTargetSourceKey[] = "{{source}}"; 27 static const char kCustomTargetSourceNamePartKey[] = "{{source_name_part}}"; 28 29 struct DefineWriter { 30 void operator()(const std::string& s, std::ostream& out) const { 31 out << " -D" << s; 32 } 33 }; 34 35 struct IncludeWriter { 36 IncludeWriter(PathOutput& path_output, 37 const NinjaHelper& h) 38 : helper(h), 39 path_output_(path_output), 40 old_inhibit_quoting_(path_output.inhibit_quoting()) { 41 // Inhibit quoting since we'll put quotes around the whole thing ourselves. 42 // Since we're writing in NINJA escaping mode, this won't actually do 43 // anything, but I think we may need to change to shell-and-then-ninja 44 // escaping for this in the future. 45 path_output_.set_inhibit_quoting(true); 46 } 47 ~IncludeWriter() { 48 path_output_.set_inhibit_quoting(old_inhibit_quoting_); 49 } 50 51 void operator()(const SourceDir& d, std::ostream& out) const { 52 out << " \"-I"; 53 // It's important not to include the trailing slash on directories or on 54 // Windows it will be a backslash and the compiler might think we're 55 // escaping the quote! 56 path_output_.WriteDir(out, d, PathOutput::DIR_NO_LAST_SLASH); 57 out << "\""; 58 } 59 60 const NinjaHelper& helper; 61 PathOutput& path_output_; 62 bool old_inhibit_quoting_; // So we can put the PathOutput back. 63 }; 64 65 } // namespace 66 67 NinjaTargetWriter::NinjaTargetWriter(const Target* target, std::ostream& out) 68 : settings_(target->settings()), 69 target_(target), 70 out_(out), 71 path_output_(settings_->build_settings()->build_dir(), 72 ESCAPE_NINJA, true), 73 helper_(settings_->build_settings()) { 74 } 75 76 NinjaTargetWriter::~NinjaTargetWriter() { 77 } 78 79 void NinjaTargetWriter::Run() { 80 // TODO(brettw) have a better way to do the environment setup on Windows. 81 if (target_->settings()->IsWin()) 82 out_ << "arch = environment.x86\n"; 83 84 if (target_->output_type() == Target::COPY_FILES) { 85 WriteCopyRules(); 86 } else if (target_->output_type() == Target::CUSTOM) { 87 WriteCustomRules(); 88 } else { 89 WriteCompilerVars(); 90 91 std::vector<OutputFile> obj_files; 92 WriteSources(&obj_files); 93 94 WriteLinkerStuff(obj_files); 95 } 96 } 97 98 // static 99 void NinjaTargetWriter::RunAndWriteFile(const Target* target) { 100 if (target->output_type() == Target::NONE) 101 return; 102 103 const Settings* settings = target->settings(); 104 NinjaHelper helper(settings->build_settings()); 105 106 base::FilePath ninja_file(settings->build_settings()->GetFullPath( 107 helper.GetNinjaFileForTarget(target).GetSourceFile( 108 settings->build_settings()))); 109 110 if (g_scheduler->verbose_logging()) 111 g_scheduler->Log("Writing", FilePathToUTF8(ninja_file)); 112 113 file_util::CreateDirectory(ninja_file.DirName()); 114 115 // It's rediculously faster to write to a string and then write that to 116 // disk in one operation than to use an fstream here. 117 std::stringstream file; 118 if (file.fail()) { 119 g_scheduler->FailWithError( 120 Err(Location(), "Error writing ninja file.", 121 "Unable to open \"" + FilePathToUTF8(ninja_file) + "\"\n" 122 "for writing.")); 123 return; 124 } 125 126 NinjaTargetWriter gen(target, file); 127 gen.Run(); 128 129 std::string contents = file.str(); 130 file_util::WriteFile(ninja_file, contents.c_str(), contents.size()); 131 } 132 133 void NinjaTargetWriter::WriteCopyRules() { 134 // The dest dir should be inside the output dir so we can just remove the 135 // prefix and get ninja-relative paths. 136 const std::string& output_dir = 137 settings_->build_settings()->build_dir().value(); 138 const std::string& dest_dir = target_->destdir().value(); 139 DCHECK(StartsWithASCII(dest_dir, output_dir, true)); 140 std::string relative_dest_dir(&dest_dir[output_dir.size()], 141 dest_dir.size() - output_dir.size()); 142 143 const Target::FileList& sources = target_->sources(); 144 std::vector<OutputFile> dest_files; 145 dest_files.reserve(sources.size()); 146 147 // Write out rules for each file copied. 148 for (size_t i = 0; i < sources.size(); i++) { 149 const SourceFile& input_file = sources[i]; 150 151 // The files should have the same name but in the dest dir. 152 base::StringPiece name_part = FindFilename(&input_file.value()); 153 OutputFile dest_file(relative_dest_dir); 154 AppendStringPiece(&dest_file.value(), name_part); 155 156 dest_files.push_back(dest_file); 157 158 out_ << "build "; 159 path_output_.WriteFile(out_, dest_file); 160 out_ << ": copy "; 161 path_output_.WriteFile(out_, input_file); 162 out_ << std::endl; 163 } 164 165 // Write out the rule for the target to copy all of them. 166 out_ << std::endl << "build "; 167 path_output_.WriteFile(out_, helper_.GetTargetOutputFile(target_)); 168 out_ << ": stamp"; 169 for (size_t i = 0; i < dest_files.size(); i++) { 170 out_ << " "; 171 path_output_.WriteFile(out_, dest_files[i]); 172 } 173 out_ << std::endl; 174 175 // TODO(brettw) need some kind of stamp file for depending on this, as well 176 // as order_only=prebuild. 177 } 178 179 void NinjaTargetWriter::WriteCustomRules() { 180 // Make a unique name for this rule. 181 std::string target_label = target_->label().GetUserVisibleName(true); 182 std::string custom_rule_name(target_label); 183 ReplaceChars(custom_rule_name, ":/()", "_", &custom_rule_name); 184 custom_rule_name.append("_rule"); 185 186 // Run the script from the dir of the BUILD file. This has no trailing 187 // slash. 188 const SourceDir& script_cd = target_->label().dir(); 189 std::string script_cd_to_root = InvertDir(script_cd); 190 if (script_cd_to_root.empty()) { 191 script_cd_to_root = "."; 192 } else { 193 // Remove trailing slash 194 DCHECK(script_cd_to_root[script_cd_to_root.size() - 1] == '/'); 195 script_cd_to_root.resize(script_cd_to_root.size() - 1); 196 } 197 198 std::string script_relative_to_cd = 199 script_cd_to_root + target_->script().value(); 200 201 bool no_sources = target_->sources().empty(); 202 203 // Use a unique name for the response file when there are multiple build 204 // steps so that they don't stomp on each other. 205 std::string rspfile = custom_rule_name; 206 if (!no_sources) 207 rspfile += ".$unique_name"; 208 rspfile += ".rsp"; 209 210 // First write the custom rule. 211 out_ << "rule " << custom_rule_name << std::endl; 212 out_ << " command = $pythonpath gyp-win-tool action-wrapper $arch " 213 << rspfile << " "; 214 path_output_.WriteDir(out_, script_cd, PathOutput::DIR_NO_LAST_SLASH); 215 out_ << std::endl; 216 out_ << " description = CUSTOM " << target_label << std::endl; 217 out_ << " restat = 1" << std::endl; 218 out_ << " rspfile = " << rspfile << std::endl; 219 220 // The build command goes in the rsp file. 221 out_ << " rspfile_content = $pythonpath " << script_relative_to_cd; 222 for (size_t i = 0; i < target_->script_args().size(); i++) { 223 const std::string& arg = target_->script_args()[i]; 224 out_ << " "; 225 WriteCustomArg(arg); 226 } 227 out_ << std::endl << std::endl; 228 229 // Precompute the common dependencies for each step. This includes the 230 // script itself (changing the script should force a rebuild) and any data 231 // files. 232 // 233 // TODO(brettW) this needs to be re-thought. "data" is supposed to be runtime 234 // data (i.e. for tests and such) rather than compile-time dependencies for 235 // each target. If we really need this, we need to have a different way to 236 // express it. 237 // 238 // One idea: add an "inputs" variable to specify this kind of thing. We 239 // should probably make it an error to specify data but no inputs for a 240 // script as a way to catch people doing the wrong way. 241 std::ostringstream common_deps_stream; 242 path_output_.WriteFile(common_deps_stream, target_->script()); 243 const Target::FileList& datas = target_->data(); 244 for (size_t i = 0; i < datas.size(); i++) { 245 common_deps_stream << " "; 246 path_output_.WriteFile(common_deps_stream, datas[i]); 247 } 248 const std::string& common_deps = common_deps_stream.str(); 249 250 // Collects all output files for writing below. 251 std::vector<OutputFile> output_files; 252 253 if (no_sources) { 254 // No sources, write a rule that invokes the script once with the 255 // outputs as outputs, and the data as inputs. 256 out_ << "build"; 257 const Target::FileList& outputs = target_->outputs(); 258 for (size_t i = 0; i < outputs.size(); i++) { 259 OutputFile output_path( 260 RemovePrefix(outputs[i].value(), 261 settings_->build_settings()->build_dir().value())); 262 output_files.push_back(output_path); 263 out_ << " "; 264 path_output_.WriteFile(out_, output_path); 265 } 266 out_ << ": " << custom_rule_name << " " << common_deps << std::endl; 267 } else { 268 // Write separate rules for each input source file. 269 WriteCustomSourceRules(custom_rule_name, common_deps, script_cd, 270 script_cd_to_root, &output_files); 271 } 272 out_ << std::endl; 273 274 // Last write a stamp rule to collect all outputs. 275 out_ << "build "; 276 path_output_.WriteFile(out_, helper_.GetTargetOutputFile(target_)); 277 out_ << ": stamp"; 278 for (size_t i = 0; i < output_files.size(); i++) { 279 out_ << " "; 280 path_output_.WriteFile(out_, output_files[i]); 281 } 282 out_ << std::endl; 283 } 284 285 void NinjaTargetWriter::WriteCustomArg(const std::string& arg) { 286 // This can be optimized if it's called a lot. 287 EscapeOptions options; 288 options.mode = ESCAPE_NINJA; 289 std::string output_str = EscapeString(arg, options); 290 291 // Do this substitution after escaping our our $ will be escaped (which we 292 // don't want). 293 ReplaceSubstringsAfterOffset(&output_str, 0, FileTemplate::kSource, 294 "${source}"); 295 ReplaceSubstringsAfterOffset(&output_str, 0, FileTemplate::kSourceNamePart, 296 "${source_name_part}"); 297 out_ << output_str; 298 } 299 300 void NinjaTargetWriter::WriteCustomSourceRules( 301 const std::string& custom_rule_name, 302 const std::string& common_deps, 303 const SourceDir& script_cd, 304 const std::string& script_cd_to_root, 305 std::vector<OutputFile>* output_files) { 306 // Construct the template for generating the output files from each source. 307 const Target::FileList& outputs = target_->outputs(); 308 std::vector<std::string> output_template_args; 309 for (size_t i = 0; i < outputs.size(); i++) { 310 // All outputs should be in the output dir. 311 output_template_args.push_back( 312 RemovePrefix(outputs[i].value(), 313 settings_->build_settings()->build_dir().value())); 314 } 315 FileTemplate output_template(output_template_args); 316 317 // Prevent re-allocating each time by initializing outside the loop. 318 std::vector<std::string> output_template_result; 319 320 // Path output formatter for wrigin source paths passed to the script. 321 PathOutput script_source_path_output(script_cd, ESCAPE_SHELL, true); 322 323 const Target::FileList& sources = target_->sources(); 324 for (size_t i = 0; i < sources.size(); i++) { 325 // Write outputs for this source file computed by the template. 326 out_ << "build"; 327 output_template.ApplyString(sources[i].value(), &output_template_result); 328 for (size_t out_i = 0; out_i < output_template_result.size(); out_i++) { 329 OutputFile output_path(output_template_result[out_i]); 330 output_files->push_back(output_path); 331 out_ << " "; 332 path_output_.WriteFile(out_, output_path); 333 } 334 335 out_ << ": " << custom_rule_name 336 << " " << common_deps 337 << " "; 338 path_output_.WriteFile(out_, sources[i]); 339 out_ << std::endl; 340 341 out_ << " unique_name = " << i << std::endl; 342 343 // The source file here should be relative to the script directory since 344 // this is the variable passed to the script. Here we slightly abuse the 345 // OutputFile object by putting a non-output-relative path in it to signal 346 // that the PathWriter should not prepend directories. 347 out_ << " source = "; 348 script_source_path_output.WriteFile(out_, sources[i]); 349 out_ << std::endl; 350 351 out_ << " source_name_part = " 352 << FindFilenameNoExtension(&sources[i].value()).as_string() 353 << std::endl; 354 } 355 } 356 357 void NinjaTargetWriter::WriteCompilerVars() { 358 // Defines. 359 out_ << "defines ="; 360 RecursiveTargetConfigToStream(target_, &ConfigValues::defines, 361 DefineWriter(), out_); 362 out_ << std::endl; 363 364 // Includes. 365 out_ << "includes ="; 366 RecursiveTargetConfigToStream(target_, &ConfigValues::includes, 367 IncludeWriter(path_output_, helper_), out_); 368 369 out_ << std::endl; 370 371 // C flags and friends. 372 #define WRITE_FLAGS(name) \ 373 out_ << #name " ="; \ 374 RecursiveTargetConfigStringsToStream(target_, &ConfigValues::name, out_); \ 375 out_ << std::endl; 376 377 WRITE_FLAGS(cflags) 378 WRITE_FLAGS(cflags_c) 379 WRITE_FLAGS(cflags_cc) 380 WRITE_FLAGS(cflags_objc) 381 WRITE_FLAGS(cflags_objcc) 382 383 #undef WRITE_FLAGS 384 385 out_ << std::endl; 386 } 387 388 void NinjaTargetWriter::WriteSources( 389 std::vector<OutputFile>* object_files) { 390 const Target::FileList& sources = target_->sources(); 391 object_files->reserve(sources.size()); 392 393 for (size_t i = 0; i < sources.size(); i++) { 394 const SourceFile& input_file = sources[i]; 395 396 SourceFileType input_file_type = GetSourceFileType(input_file, 397 settings_->target_os()); 398 if (input_file_type == SOURCE_UNKNOWN) 399 continue; // Skip unknown file types. 400 const char* command = GetCommandForSourceType(input_file_type); 401 if (!command) 402 continue; // Skip files not needing compilation. 403 404 OutputFile output_file = helper_.GetOutputFileForSource( 405 target_, input_file, input_file_type); 406 object_files->push_back(output_file); 407 408 out_ << "build "; 409 path_output_.WriteFile(out_, output_file); 410 out_ << ": " << command << " "; 411 path_output_.WriteFile(out_, input_file); 412 out_ << std::endl; 413 } 414 out_ << std::endl; 415 } 416 417 void NinjaTargetWriter::WriteLinkerStuff( 418 const std::vector<OutputFile>& object_files) { 419 // Manifest file on Windows. 420 // TODO(brettw) this seems not to be necessary for static libs, skip in 421 // that case? 422 OutputFile windows_manifest; 423 if (settings_->IsWin()) { 424 windows_manifest.value().assign(helper_.GetTargetOutputDir(target_)); 425 windows_manifest.value().append(target_->label().name()); 426 windows_manifest.value().append(".intermediate.manifest"); 427 out_ << "manifests = "; 428 path_output_.WriteFile(out_, windows_manifest); 429 out_ << std::endl; 430 } 431 432 // Linker flags, append manifest flag on Windows to reference our file. 433 out_ << "ldflags ="; 434 RecursiveTargetConfigStringsToStream(target_, &ConfigValues::ldflags, out_); 435 // HACK ERASEME BRETTW FIXME 436 if (settings_->IsWin()) { 437 out_ << " /MANIFEST /ManifestFile:"; 438 path_output_.WriteFile(out_, windows_manifest); 439 out_ << " /DEBUG /MACHINE:X86 /LIBPATH:\"C:\\Program Files (x86)\\Windows Kits\\8.0\\Lib\\win8\\um\\x86\" /DELAYLOAD:dbghelp.dll /DELAYLOAD:dwmapi.dll /DELAYLOAD:shell32.dll /DELAYLOAD:uxtheme.dll /safeseh /dynamicbase /ignore:4199 /ignore:4221 /nxcompat /SUBSYSTEM:CONSOLE /INCREMENTAL /FIXED:NO /DYNAMICBASE:NO wininet.lib dnsapi.lib version.lib msimg32.lib ws2_32.lib usp10.lib psapi.lib dbghelp.lib winmm.lib shlwapi.lib kernel32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib user32.lib uuid.lib odbc32.lib odbccp32.lib delayimp.lib /NXCOMPAT"; 440 } 441 out_ << std::endl; 442 443 // Libraries to link. 444 out_ << "libs ="; 445 if (settings_->IsMac()) { 446 // TODO(brettw) fix this. 447 out_ << " -framework AppKit -framework ApplicationServices -framework Carbon -framework CoreFoundation -framework Foundation -framework IOKit -framework Security"; 448 } 449 out_ << std::endl; 450 451 // The external output file is the one that other libs depend on. 452 OutputFile external_output_file = helper_.GetTargetOutputFile(target_); 453 454 // The internal output file is the "main thing" we think we're making. In 455 // the case of shared libraries, this is the shared library and the external 456 // output file is the import library. In other cases, the internal one and 457 // the external one are the same. 458 OutputFile internal_output_file; 459 if (target_->output_type() == Target::SHARED_LIBRARY) { 460 if (settings_->IsWin()) { 461 internal_output_file = OutputFile(target_->label().name() + ".dll"); 462 } else { 463 internal_output_file = external_output_file; 464 } 465 } else { 466 internal_output_file = external_output_file; 467 } 468 469 // In Python see "self.ninja.build(output, command, input," 470 WriteLinkCommand(external_output_file, internal_output_file, object_files); 471 472 if (target_->output_type() == Target::SHARED_LIBRARY) { 473 out_ << " soname = "; 474 path_output_.WriteFile(out_, internal_output_file); 475 out_ << std::endl; 476 477 out_ << " lib = "; 478 path_output_.WriteFile(out_, internal_output_file); 479 out_ << std::endl; 480 481 out_ << " dll = "; 482 path_output_.WriteFile(out_, internal_output_file); 483 out_ << std::endl; 484 485 if (settings_->IsWin()) { 486 out_ << " implibflag = /IMPLIB:"; 487 path_output_.WriteFile(out_, external_output_file); 488 out_ << std::endl; 489 } 490 491 // TODO(brettw) postbuild steps. 492 if (settings_->IsMac()) 493 out_ << " postbuilds = $ && (export BUILT_PRODUCTS_DIR=/Users/brettw/prj/src/out/gn; export CONFIGURATION=Debug; export DYLIB_INSTALL_NAME_BASE=@rpath; export EXECUTABLE_NAME=libbase.dylib; export EXECUTABLE_PATH=libbase.dylib; export FULL_PRODUCT_NAME=libbase.dylib; export LD_DYLIB_INSTALL_NAME=@rpath/libbase.dylib; export MACH_O_TYPE=mh_dylib; export PRODUCT_NAME=base; export PRODUCT_TYPE=com.apple.product-type.library.dynamic; export SDKROOT=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.7.sdk; export SRCROOT=/Users/brettw/prj/src/out/gn/../../base; export SOURCE_ROOT=\"$${SRCROOT}\"; export TARGET_BUILD_DIR=/Users/brettw/prj/src/out/gn; export TEMP_DIR=\"$${TMPDIR}\"; (cd ../../base && ../build/mac/strip_from_xcode); G=$$?; ((exit $$G) || rm -rf libbase.dylib) && exit $$G)"; 494 } 495 496 out_ << std::endl; 497 } 498 499 void NinjaTargetWriter::WriteLinkCommand( 500 const OutputFile& external_output_file, 501 const OutputFile& internal_output_file, 502 const std::vector<OutputFile>& object_files) { 503 out_ << "build "; 504 path_output_.WriteFile(out_, internal_output_file); 505 if (external_output_file != internal_output_file) { 506 out_ << " "; 507 path_output_.WriteFile(out_, external_output_file); 508 } 509 out_ << ": " << GetCommandForTargetType(); 510 511 // Object files. 512 for (size_t i = 0; i < object_files.size(); i++) { 513 out_ << " "; 514 path_output_.WriteFile(out_, object_files[i]); 515 } 516 517 // Library inputs (deps and inherited static libraries). 518 // 519 // Static libraries since they're just a collection of the object files so 520 // don't need libraries linked with them, but we still need to go through 521 // the list and find non-linkable data deps in the "deps" section. We'll 522 // collect all non-linkable deps and put it in the order-only deps below. 523 std::vector<const Target*> extra_data_deps; 524 const std::vector<const Target*>& deps = target_->deps(); 525 const std::set<const Target*>& inherited = target_->inherited_libraries(); 526 for (size_t i = 0; i < deps.size(); i++) { 527 if (inherited.find(deps[i]) != inherited.end()) 528 continue; 529 if (target_->output_type() != Target::STATIC_LIBRARY && 530 deps[i]->IsLinkable()) { 531 out_ << " "; 532 path_output_.WriteFile(out_, helper_.GetTargetOutputFile(deps[i])); 533 } else { 534 extra_data_deps.push_back(deps[i]); 535 } 536 } 537 for (std::set<const Target*>::const_iterator i = inherited.begin(); 538 i != inherited.end(); ++i) { 539 if (target_->output_type() == Target::STATIC_LIBRARY) { 540 extra_data_deps.push_back(*i); 541 } else { 542 out_ << " "; 543 path_output_.WriteFile(out_, helper_.GetTargetOutputFile(*i)); 544 } 545 } 546 547 // Append data dependencies as order-only dependencies. 548 const std::vector<const Target*>& datadeps = target_->datadeps(); 549 const std::vector<SourceFile>& data = target_->data(); 550 if (!extra_data_deps.empty() || !datadeps.empty() || !data.empty()) { 551 out_ << " ||"; 552 553 // Non-linkable deps in the deps section above. 554 for (size_t i = 0; i < extra_data_deps.size(); i++) { 555 out_ << " "; 556 path_output_.WriteFile(out_, 557 helper_.GetTargetOutputFile(extra_data_deps[i])); 558 } 559 560 // Data deps. 561 for (size_t i = 0; i < datadeps.size(); i++) { 562 out_ << " "; 563 path_output_.WriteFile(out_, helper_.GetTargetOutputFile(datadeps[i])); 564 } 565 566 // Data files. 567 const std::vector<SourceFile>& data = target_->data(); 568 for (size_t i = 0; i < data.size(); i++) { 569 out_ << " "; 570 path_output_.WriteFile(out_, data[i]); 571 } 572 } 573 574 out_ << std::endl; 575 } 576 577 const char* NinjaTargetWriter::GetCommandForSourceType( 578 SourceFileType type) const { 579 if (type == SOURCE_C) 580 return "cc"; 581 if (type == SOURCE_CC) 582 return "cxx"; 583 584 // TODO(brettw) asm files. 585 586 if (settings_->IsMac()) { 587 if (type == SOURCE_M) 588 return "objc"; 589 if (type == SOURCE_MM) 590 return "objcxx"; 591 } 592 593 if (settings_->IsWin()) { 594 if (type == SOURCE_RC) 595 return "rc"; 596 } 597 598 // TODO(brettw) stuff about "S" files on non-Windows. 599 return NULL; 600 } 601 602 const char* NinjaTargetWriter::GetCommandForTargetType() const { 603 if (target_->output_type() == Target::NONE) { 604 NOTREACHED(); 605 return ""; 606 } 607 608 if (target_->output_type() == Target::STATIC_LIBRARY) { 609 // TODO(brettw) stuff about standalong static libraryes on Unix in 610 // WriteTarget in the Python one, and lots of postbuild steps. 611 return "alink"; 612 } 613 614 if (target_->output_type() == Target::SHARED_LIBRARY) 615 return "solink"; 616 617 return "link"; 618 } 619