Implement and enable 'no_stamp_files' This is the last CL to enable the new 'no_stamp_files' build setting flag, which enables the generation of Ninja phony targets instead of stamp files. - Modify the Ninja target writers to understand phony aliases. - Allow no_stamp_files to be set in the build settings file (e.g. `.gn`) without asserting. Note that when this is not set, this CL should not change the output of GN at all. For testing the output of `gn gen` has been compared before and after this CL was applied with the Fuchsia source tree to verify that the output is the same. On the other hand, if `no_stamp_files = true` is added to the `.gn` file, the generated Ninja file go from 829 MiB to 797 MiB. On Chromium, size of Ninja files go from 401 MiB to 384 MiB. Test expecations are changed like https://gn-review.googlesource.com/c/gn/+/17620 if 'no_stamp_files' is enabled by default. Bug: 172 Change-Id: If26e64c759ed34467ce13b5c5c1005d56f435947 Reviewed-on: https://gn-review.googlesource.com/c/gn/+/12866 Reviewed-by: Philipp Wollermann <philwo@google.com> Reviewed-by: Takuto Ikuta <tikuta@google.com> Commit-Queue: Takuto Ikuta <tikuta@google.com>
diff --git a/docs/reference.md b/docs/reference.md index 82cfd80..f2e6c42 100644 --- a/docs/reference.md +++ b/docs/reference.md
@@ -2957,7 +2957,7 @@ process_file_template"). source sets and groups: this will return a list containing the path of the - "stamp" file that Ninja will produce once all outputs are generated. This + phony target that Ninja completes once all outputs are generated. This probably isn't very useful. ```
diff --git a/src/gn/commands.cc b/src/gn/commands.cc index ba7cff0..dbf2b48 100644 --- a/src/gn/commands.cc +++ b/src/gn/commands.cc
@@ -268,8 +268,13 @@ // Use the link output file if there is one, otherwise fall back to the // dependency output file (for actions, for example). OutputFile output_file = target->link_output_file(); + if (output_file.value().empty() && target->has_dependency_output()) + output_file = target->dependency_output(); + + // This output might be an omitted phony target, but that would mean we + // don't have an output file to list. if (output_file.value().empty()) - output_file = target->dependency_output_file(); + continue; SourceFile output_as_source = output_file.AsSourceFile(build_settings); std::string result =
diff --git a/src/gn/function_get_target_outputs.cc b/src/gn/function_get_target_outputs.cc index c9f10c0..2686348 100644 --- a/src/gn/function_get_target_outputs.cc +++ b/src/gn/function_get_target_outputs.cc
@@ -46,7 +46,7 @@ process_file_template"). source sets and groups: this will return a list containing the path of the - "stamp" file that Ninja will produce once all outputs are generated. This + phony target that Ninja completes once all outputs are generated. This probably isn't very useful. Example
diff --git a/src/gn/ninja_action_target_writer.cc b/src/gn/ninja_action_target_writer.cc index 06da244..0fe0c1b 100644 --- a/src/gn/ninja_action_target_writer.cc +++ b/src/gn/ninja_action_target_writer.cc
@@ -40,7 +40,9 @@ for (const Target* dep : target_deps.linked_deps()) { if (dep->IsDataOnly()) { - order_only_deps.push_back(dep->dependency_output_file()); + if (dep->has_dependency_output()) { + order_only_deps.push_back(dep->dependency_output()); + } } else { additional_hard_deps.push_back(dep); } @@ -60,16 +62,19 @@ // and on an incremental build, if the now-implicit dependencies are // 'dirty', this action will be considered 'dirty' as well. // - for (const Target* data_dep : target_deps.data_deps()) - order_only_deps.push_back(data_dep->dependency_output_file()); + for (const Target* data_dep : target_deps.data_deps()) { + if (data_dep->has_dependency_output()) { + order_only_deps.push_back(data_dep->dependency_output()); + } + } // For ACTIONs, the input deps appear only once in the generated ninja - // file, so WriteInputDepsStampAndGetDep() won't create a stamp file + // file, so WriteInputDepsStampOrPhonyAndGetDep() won't create a phony rule // and the action will just depend on all the input deps directly. - size_t num_stamp_uses = + size_t num_output_uses = target_->output_type() == Target::ACTION ? 1u : target_->sources().size(); - std::vector<OutputFile> input_deps = - WriteInputDepsStampAndGetDep(additional_hard_deps, num_stamp_uses); + std::vector<OutputFile> input_deps = WriteInputDepsStampOrPhonyAndGetDep( + additional_hard_deps, num_output_uses); out_ << std::endl; // Collects all output files for writing below. @@ -119,13 +124,12 @@ } out_ << std::endl; - // Write the stamp, which doesn't need to depend on the data deps because they + // Write the phony, which doesn't need to depend on the data deps because they // have been added as order-only deps of the action output itself. // // TODO(thakis): If the action has just a single output, make things depend - // on that output directly without writing a stamp file. std::vector<OutputFile> stamp_file_order_only_deps; - WriteStampForTarget(output_files, stamp_file_order_only_deps); + WriteStampOrPhonyForTarget(output_files, stamp_file_order_only_deps); } std::string NinjaActionTargetWriter::WriteRuleDefinition() {
diff --git a/src/gn/ninja_binary_target_writer.cc b/src/gn/ninja_binary_target_writer.cc index 7dc6ea8..c244c7c 100644 --- a/src/gn/ninja_binary_target_writer.cc +++ b/src/gn/ninja_binary_target_writer.cc
@@ -7,6 +7,7 @@ #include <sstream> #include "base/strings/string_util.h" +#include "gn/builtin_tool.h" #include "gn/config_values_extractors.h" #include "gn/deps_iterator.h" #include "gn/filesystem_utils.h" @@ -55,8 +56,9 @@ writer.Run(); } -std::vector<OutputFile> NinjaBinaryTargetWriter::WriteInputsStampAndGetDep( - size_t num_stamp_uses) const { +std::vector<OutputFile> +NinjaBinaryTargetWriter::WriteInputsStampOrPhonyAndGetDep( + size_t num_output_uses) const { CHECK(target_->toolchain()) << "Toolchain not set on target " << target_->label().GetUserVisibleName(true); @@ -70,8 +72,8 @@ if (inputs.size() == 0) return std::vector<OutputFile>(); // No inputs - // If we only have one input, return it directly instead of writing a stamp - // file for it. + // If we only have one input, return it directly instead of writing a phony + // target for it. if (inputs.size() == 1) { return std::vector<OutputFile>{ OutputFile(settings_->build_settings(), *inputs[0])}; @@ -81,22 +83,35 @@ for (const SourceFile* source : inputs) outs.push_back(OutputFile(settings_->build_settings(), *source)); - // If there are multiple inputs, but the stamp file would be referenced only + // If there are multiple inputs, but the phony target would be referenced only // once, don't write it but depend on the inputs directly. - if (num_stamp_uses == 1u) + if (num_output_uses == 1u) return outs; - // Make a stamp file. - OutputFile stamp_file = - GetBuildDirForTargetAsOutputFile(target_, BuildDirType::OBJ); - stamp_file.value().append(target_->label().name()); - stamp_file.value().append(".inputs.stamp"); + OutputFile stamp_or_phony; + std::string tool; + if (settings_->build_settings()->no_stamp_files()) { + // Make a phony target. We don't need to worry about an empty phony target, + // as those would have been peeled off already. + CHECK(!inputs.empty()); + stamp_or_phony = + GetBuildDirForTargetAsOutputFile(target_, BuildDirType::PHONY); + stamp_or_phony.value().append(target_->label().name()); + stamp_or_phony.value().append(".inputs"); + tool = BuiltinTool::kBuiltinToolPhony; + } else { + // Make a stamp target. + stamp_or_phony = + GetBuildDirForTargetAsOutputFile(target_, BuildDirType::OBJ); + stamp_or_phony.value().append(target_->label().name()); + stamp_or_phony.value().append(".inputs.stamp"); + tool = GetNinjaRulePrefixForToolchain(settings_) + + GeneralTool::kGeneralToolStamp; + } out_ << "build "; - WriteOutput(stamp_file); - - out_ << ": " << GetNinjaRulePrefixForToolchain(settings_) - << GeneralTool::kGeneralToolStamp; + WriteOutput(stamp_or_phony); + out_ << ": " << tool; // File inputs. for (const auto* input : inputs) { @@ -105,7 +120,7 @@ } out_ << std::endl; - return {stamp_file}; + return {stamp_or_phony}; } NinjaBinaryTargetWriter::ClassifiedDeps @@ -173,7 +188,7 @@ AddSourceSetFiles(dep, &classified_deps->extra_object_files); // Add the source set itself as a non-linkable dependency on the current - // target. This will make sure that anything the source set's stamp file + // target. This will make sure that anything the source set's phony target // depends on (like data deps) are also built before the current target // can be complete. Otherwise, these will be skipped since this target // will depend only on the source set's object files.
diff --git a/src/gn/ninja_binary_target_writer.h b/src/gn/ninja_binary_target_writer.h index c013cf8..29105b4 100644 --- a/src/gn/ninja_binary_target_writer.h +++ b/src/gn/ninja_binary_target_writer.h
@@ -32,15 +32,15 @@ UniqueVector<const Target*> swiftmodule_deps; }; - // Writes to the output stream a stamp rule for inputs, and - // returns the file to be appended to source rules that encodes the + // Writes to the output stream a phony rule for inputs, and + // returns the target to be appended to source rules that encodes the // implicit dependencies for the current target. - // If num_stamp_uses is small, this might return all input dependencies - // directly, without writing a stamp file. + // If num_output_uses is small, this might return all input dependencies + // directly, without writing a phony rule. // If there are no implicit dependencies and no extra target dependencies // are passed in, this returns an empty vector. - std::vector<OutputFile> WriteInputsStampAndGetDep( - size_t num_stamp_uses) const; + std::vector<OutputFile> WriteInputsStampOrPhonyAndGetDep( + size_t num_phony_uses) const; // Gets all target dependencies and classifies them, as well as accumulates // object files from source sets we need to link.
diff --git a/src/gn/ninja_build_writer.cc b/src/gn/ninja_build_writer.cc index 91e6860..f959bfa 100644 --- a/src/gn/ninja_build_writer.cc +++ b/src/gn/ninja_build_writer.cc
@@ -663,8 +663,10 @@ EscapeOptions ninja_escape; ninja_escape.mode = ESCAPE_NINJA; for (const Target* target : default_toolchain_targets_) { - out_ << " $\n "; - path_output_.WriteFile(out_, target->dependency_output_file()); + if (target->has_dependency_output()) { + out_ << " $\n "; + path_output_.WriteFile(out_, target->dependency_output()); + } } } out_ << std::endl; @@ -673,9 +675,12 @@ // Use the short name when available if (written_rules.find(StringAtom("default")) != written_rules.end()) { out_ << "\ndefault default" << std::endl; - } else { + } else if (default_target->has_dependency_output()) { + // If the default target does not have a dependency output file or phony, + // then the target specified as default is a no-op. We omit the default + // statement entirely to avoid ninja runtime failure. out_ << "\ndefault "; - path_output_.WriteFile(out_, default_target->dependency_output_file()); + path_output_.WriteFile(out_, default_target->dependency_output()); out_ << std::endl; } } else if (!default_toolchain_targets_.empty()) { @@ -693,7 +698,12 @@ // Escape for special chars Ninja will handle. std::string escaped = EscapeString(phony_name, ninja_escape, nullptr); + // If the target doesn't have a dependency_output(), we should + // still emit the phony rule, but with no dependencies. This allows users to + // continue to use the phony rule, but it will effectively be a no-op. out_ << "build " << escaped << ": phony "; - path_output_.WriteFile(out_, target->dependency_output_file()); + if (target->has_dependency_output()) { + path_output_.WriteFile(out_, target->dependency_output()); + } out_ << std::endl; }
diff --git a/src/gn/ninja_bundle_data_target_writer.cc b/src/gn/ninja_bundle_data_target_writer.cc index da3bb4e..4ba1733 100644 --- a/src/gn/ninja_bundle_data_target_writer.cc +++ b/src/gn/ninja_bundle_data_target_writer.cc
@@ -21,13 +21,15 @@ OutputFile(settings_->build_settings(), source_file)); } - std::vector<OutputFile> input_deps = WriteInputDepsStampAndGetDep( - std::vector<const Target*>(), /*num_stamp_uses=*/1); + std::vector<OutputFile> input_deps = WriteInputDepsStampOrPhonyAndGetDep( + std::vector<const Target*>(), /*num_output_uses=*/1); output_files.insert(output_files.end(), input_deps.begin(), input_deps.end()); std::vector<OutputFile> order_only_deps; - for (const Target* data_dep : resolved().GetDataDeps(target_)) - order_only_deps.push_back(data_dep->dependency_output_file()); + for (const Target* data_dep : resolved().GetDataDeps(target_)) { + if (data_dep->has_dependency_output()) + order_only_deps.push_back(data_dep->dependency_output()); + } - WriteStampForTarget(output_files, order_only_deps); + WriteStampOrPhonyForTarget(output_files, order_only_deps); }
diff --git a/src/gn/ninja_c_binary_target_writer.cc b/src/gn/ninja_c_binary_target_writer.cc index 730840c..96907f7 100644 --- a/src/gn/ninja_c_binary_target_writer.cc +++ b/src/gn/ninja_c_binary_target_writer.cc
@@ -134,10 +134,10 @@ WriteCompilerVars(module_dep_info); - size_t num_stamp_uses = target_->sources().size(); + size_t num_output_uses = target_->sources().size(); std::vector<OutputFile> input_deps = - WriteInputsStampAndGetDep(num_stamp_uses); + WriteInputsStampOrPhonyAndGetDep(num_output_uses); // The input dependencies will be an order-only dependency. This will cause // Ninja to make sure the inputs are up to date before compiling this source, @@ -166,11 +166,11 @@ // The order only deps are referenced by each source file compile, // but also by PCH compiles. The latter are annoying to count, so omit // them here. This means that binary targets with a single source file - // that also use PCH files won't have a stamp file even though having + // that also use PCH files won't have a phony target even though having // one would make output ninja file size a bit lower. That's ok, binary // targets with a single source are rare. - std::vector<OutputFile> order_only_deps = WriteInputDepsStampAndGetDep( - std::vector<const Target*>(), num_stamp_uses); + std::vector<OutputFile> order_only_deps = WriteInputDepsStampOrPhonyAndGetDep( + std::vector<const Target*>(), num_output_uses); // For GCC builds, the .gch files are not object files, but still need to be // added as explicit dependencies below. The .gch output files are placed in @@ -525,7 +525,9 @@ for (const Target* swiftmodule : resolved().GetSwiftModuleDependencies(target_)) { - swift_order_only_deps.push_back(swiftmodule->dependency_output_file()); + CHECK(swiftmodule->has_dependency_output()); { + swift_order_only_deps.push_back(swiftmodule->dependency_output()); + } } const Tool* tool = target_->swift_values().GetTool(target_); @@ -552,10 +554,13 @@ DCHECK(classified_deps.extra_object_files.empty()); std::vector<OutputFile> order_only_deps; - for (auto* dep : classified_deps.non_linkable_deps) - order_only_deps.push_back(dep->dependency_output_file()); + for (auto* dep : classified_deps.non_linkable_deps) { + if (dep->has_dependency_output()) { + order_only_deps.push_back(dep->dependency_output()); + } + } - WriteStampForTarget(object_files, order_only_deps); + WriteStampOrPhonyForTarget(object_files, order_only_deps); } void NinjaCBinaryTargetWriter::WriteLinkerStuff( @@ -569,8 +574,7 @@ out_ << "build"; WriteOutputs(output_files); - out_ << ": " << rule_prefix_ - << Tool::GetToolTypeForTargetFinalOutput(target_); + out_ << ": " << rule_prefix_ << tool_->name(); ClassifiedDeps classified_deps = GetClassifiedDeps(); @@ -591,11 +595,11 @@ cur->output_type() == Target::RUST_PROC_MACRO) continue; - if (cur->dependency_output_file().value() != - cur->link_output_file().value()) { + if (cur->has_dependency_output() && + cur->dependency_output().value() != cur->link_output_file().value()) { // This is a shared library with separate link and deps files. Save for // later. - implicit_deps.push_back(cur->dependency_output_file()); + implicit_deps.push_back(cur->dependency_output()); solibs.push_back(cur->link_output_file()); } else { // Normal case, just link to this target. @@ -625,12 +629,13 @@ } // If any target creates a framework bundle, then treat it as an implicit - // dependency via the .stamp file. This is a pessimisation as it is not + // dependency via the phony target. This is a pessimisation as it is not // always necessary to relink the current target if one of the framework // is regenerated, but it ensure that if one of the framework API changes, // any dependent target will relink it (see crbug.com/1037607). for (const Target* dep : classified_deps.framework_deps) { - implicit_deps.push_back(dep->dependency_output_file()); + if (dep->has_dependency_output()) + implicit_deps.push_back(dep->dependency_output()); } // The input dependency is only needed if there are no object files, as the @@ -646,6 +651,7 @@ for (const auto& inherited : resolved().GetInheritedLibraries(target_)) { const Target* dep = inherited.target(); if (dep->output_type() == Target::RUST_LIBRARY) { + CHECK(dep->has_dependency_output_file()); transitive_rustlibs.push_back(dep->dependency_output_file()); implicit_deps.push_back(dep->dependency_output_file()); } @@ -679,11 +685,11 @@ // this target. // // The action dependencies are not strictly necessary in this case. They - // should also have been collected via the input deps stamp that each source - // file has for an order-only dependency, and since this target depends on - // the sources, there is already an implicit order-only dependency. However, - // it's extra work to separate these out and there's no disadvantage to - // listing them again. + // should also have been collected via the input deps phony alias that each + // source file has for an order-only dependency, and since this target depends + // on the sources, there is already an implicit order-only dependency. + // However, it's extra work to separate these out and there's no disadvantage + // to listing them again. WriteOrderOnlyDependencies(classified_deps.non_linkable_deps); // End of the link "build" line. @@ -758,8 +764,10 @@ // Non-linkable targets. for (auto* non_linkable_dep : non_linkable_deps) { - out_ << " "; - path_output_.WriteFile(out_, non_linkable_dep->dependency_output_file()); + if (non_linkable_dep->has_dependency_output()) { + out_ << " "; + path_output_.WriteFile(out_, non_linkable_dep->dependency_output()); + } } } }
diff --git a/src/gn/ninja_c_binary_target_writer.h b/src/gn/ninja_c_binary_target_writer.h index a50d8c9..f60790f 100644 --- a/src/gn/ninja_c_binary_target_writer.h +++ b/src/gn/ninja_c_binary_target_writer.h
@@ -39,7 +39,7 @@ // non-object files (for instance, .gch files from a GCC toolchain, are // appended to |other_files|). // - // input_deps is the stamp file collecting the dependencies required before + // input_deps is the phony target collecting the dependencies required before // compiling this target. It will be empty if there are no input deps. void WritePCHCommands(const std::vector<OutputFile>& input_deps, const std::vector<OutputFile>& order_only_deps,
diff --git a/src/gn/ninja_copy_target_writer.cc b/src/gn/ninja_copy_target_writer.cc index 0ad7d67..47657c0 100644 --- a/src/gn/ninja_copy_target_writer.cc +++ b/src/gn/ninja_copy_target_writer.cc
@@ -34,29 +34,29 @@ return; } - const Tool* stamp_tool = - target_->toolchain()->GetTool(GeneralTool::kGeneralToolStamp); - if (!stamp_tool) { - g_scheduler->FailWithError(Err( - nullptr, "Stamp tool not defined", - "The toolchain " + - target_->toolchain()->label().GetUserVisibleName(false) + - "\n used by target " + target_->label().GetUserVisibleName(false) + - "\n doesn't define a \"stamp\" tool.")); - return; + SubstitutionBits required_bits = copy_tool->substitution_bits(); + if (!settings_->build_settings()->no_stamp_files()) { + const Tool* stamp_tool = + target_->toolchain()->GetTool(GeneralTool::kGeneralToolStamp); + if (!stamp_tool) { + g_scheduler->FailWithError( + Err(nullptr, "Stamp tool not defined", + "The toolchain " + + target_->toolchain()->label().GetUserVisibleName(false) + + "\n used by target " + + target_->label().GetUserVisibleName(false) + + "\n doesn't define a \"stamp\" tool.")); + } + required_bits.MergeFrom(stamp_tool->substitution_bits()); } - // Figure out the substitutions used by the copy and stamp tools. - SubstitutionBits required_bits = copy_tool->substitution_bits(); - required_bits.MergeFrom(stamp_tool->substitution_bits()); - - // General target-related substitutions needed by both tools. + // General target-related substitutions needed by the copy/stamp tool. WriteSharedVars(required_bits); std::vector<OutputFile> output_files; WriteCopyRules(&output_files); out_ << std::endl; - WriteStampForTarget(output_files, std::vector<OutputFile>()); + WriteStampOrPhonyForTarget(output_files, std::vector<OutputFile>()); } void NinjaCopyTargetWriter::WriteCopyRules( @@ -71,13 +71,15 @@ std::string tool_name = GetNinjaRulePrefixForToolchain(settings_) + GeneralTool::kGeneralToolCopy; - size_t num_stamp_uses = target_->sources().size(); - std::vector<OutputFile> input_deps = WriteInputDepsStampAndGetDep( - std::vector<const Target*>(), num_stamp_uses); + size_t num_output_uses = target_->sources().size(); + std::vector<OutputFile> input_deps = WriteInputDepsStampOrPhonyAndGetDep( + std::vector<const Target*>(), num_output_uses); std::vector<OutputFile> data_outs; - for (const Target* data_dep : resolved().GetDataDeps(target_)) - data_outs.push_back(data_dep->dependency_output_file()); + for (const Target* data_dep : resolved().GetDataDeps(target_)) { + if (data_dep->has_dependency_output()) + data_outs.push_back(data_dep->dependency_output()); + } // Note that we don't write implicit deps for copy steps. "copy" only // depends on the output files themselves, rather than having includes
diff --git a/src/gn/ninja_create_bundle_target_writer.cc b/src/gn/ninja_create_bundle_target_writer.cc index a453fce..87f0ab7 100644 --- a/src/gn/ninja_create_bundle_target_writer.cc +++ b/src/gn/ninja_create_bundle_target_writer.cc
@@ -7,6 +7,7 @@ #include <iterator> #include "base/strings/string_util.h" +#include "gn/builtin_tool.h" #include "gn/filesystem_utils.h" #include "gn/general_tool.h" #include "gn/ninja_utils.h" @@ -79,7 +80,7 @@ // Stamp users are CopyBundleData, CompileAssetsCatalog, PostProcessing and // StampForTarget. size_t num_stamp_uses = 4; - std::vector<OutputFile> order_only_deps = WriteInputDepsStampAndGetDep( + std::vector<OutputFile> order_only_deps = WriteInputDepsStampOrPhonyAndGetDep( std::vector<const Target*>(), num_stamp_uses); std::string post_processing_rule_name = WritePostProcessingRuleDefinition(); @@ -90,9 +91,17 @@ WritePostProcessingStep(post_processing_rule_name, order_only_deps, &output_files); - for (const Target* data_dep : resolved().GetDataDeps(target_)) - order_only_deps.push_back(data_dep->dependency_output_file()); - WriteStampForTarget(output_files, order_only_deps); + for (const Target* data_dep : resolved().GetDataDeps(target_)) { + if (data_dep->has_dependency_output()) + order_only_deps.push_back(data_dep->dependency_output()); + } + + // If the target does not have a phony target to write, then we have nothing + // left to do. + if (!target_->has_dependency_output()) + return; + + WriteStampOrPhonyForTarget(output_files, order_only_deps); // Write a phony target for the outer bundle directory. This allows other // targets to treat the entire bundle as a single unit, even though it is @@ -102,8 +111,8 @@ WriteOutput( OutputFile(settings_->build_settings(), target_->bundle_data().GetBundleRootDirOutput(settings_))); - - out_ << ": phony " << target_->dependency_output_file().value(); + out_ << ": " << BuiltinTool::kBuiltinToolPhony << " "; + out_ << target_->dependency_output().value(); out_ << std::endl; } @@ -219,7 +228,7 @@ return; } - OutputFile input_dep = WriteCompileAssetsCatalogInputDepsStamp( + OutputFile input_dep = WriteCompileAssetsCatalogInputDepsStampOrPhony( target_->bundle_data().assets_catalog_deps()); DCHECK(!input_dep.value().empty()); @@ -279,28 +288,45 @@ } OutputFile -NinjaCreateBundleTargetWriter::WriteCompileAssetsCatalogInputDepsStamp( +NinjaCreateBundleTargetWriter::WriteCompileAssetsCatalogInputDepsStampOrPhony( const std::vector<const Target*>& dependencies) { DCHECK(!dependencies.empty()); - if (dependencies.size() == 1) - return dependencies[0]->dependency_output_file(); + if (dependencies.size() == 1) { + return dependencies[0]->has_dependency_output() + ? dependencies[0]->dependency_output() + : OutputFile{}; + } - OutputFile xcassets_input_stamp_file = - GetBuildDirForTargetAsOutputFile(target_, BuildDirType::OBJ); - xcassets_input_stamp_file.value().append(target_->label().name()); - xcassets_input_stamp_file.value().append(".xcassets.inputdeps.stamp"); + OutputFile xcassets_input_stamp_or_phony; + std::string tool; + if (settings_->build_settings()->no_stamp_files()) { + xcassets_input_stamp_or_phony = + GetBuildDirForTargetAsOutputFile(target_, BuildDirType::PHONY); + + xcassets_input_stamp_or_phony.value().append(target_->label().name()); + xcassets_input_stamp_or_phony.value().append(".xcassets.inputdeps"); + tool = BuiltinTool::kBuiltinToolPhony; + } else { + xcassets_input_stamp_or_phony = + GetBuildDirForTargetAsOutputFile(target_, BuildDirType::OBJ); + xcassets_input_stamp_or_phony.value().append(target_->label().name()); + xcassets_input_stamp_or_phony.value().append(".xcassets.inputdeps.stamp"); + tool = GetNinjaRulePrefixForToolchain(settings_) + + GeneralTool::kGeneralToolStamp; + } out_ << "build "; - WriteOutput(xcassets_input_stamp_file); - out_ << ": " << GetNinjaRulePrefixForToolchain(settings_) - << GeneralTool::kGeneralToolStamp; + WriteOutput(xcassets_input_stamp_or_phony); + out_ << ": " << tool; for (const Target* target : dependencies) { - out_ << " "; - path_output_.WriteFile(out_, target->dependency_output_file()); + if (target->has_dependency_output()) { + out_ << " "; + path_output_.WriteFile(out_, target->dependency_output()); + } } out_ << std::endl; - return xcassets_input_stamp_file; + return xcassets_input_stamp_or_phony; } void NinjaCreateBundleTargetWriter::WritePostProcessingStep( @@ -311,7 +337,7 @@ return; OutputFile post_processing_input_stamp_file = - WritePostProcessingInputDepsStamp(order_only_deps, output_files); + WritePostProcessingInputDepsStampOrPhony(order_only_deps, output_files); DCHECK(!post_processing_input_stamp_file.value().empty()); out_ << "build"; @@ -332,7 +358,8 @@ out_ << std::endl; } -OutputFile NinjaCreateBundleTargetWriter::WritePostProcessingInputDepsStamp( +OutputFile +NinjaCreateBundleTargetWriter::WritePostProcessingInputDepsStampOrPhony( const std::vector<OutputFile>& order_only_deps, std::vector<OutputFile>* output_files) { std::vector<SourceFile> post_processing_input_files; @@ -352,16 +379,28 @@ return OutputFile(settings_->build_settings(), post_processing_input_files[0]); - OutputFile post_processing_input_stamp_file = - GetBuildDirForTargetAsOutputFile(target_, BuildDirType::OBJ); - post_processing_input_stamp_file.value().append(target_->label().name()); - post_processing_input_stamp_file.value().append( - ".postprocessing.inputdeps.stamp"); + OutputFile stamp_or_phony; + std::string tool; + if (settings_->build_settings()->no_stamp_files()) { + // Make a phony target. We don't need to worry about an empty phony target, + // as those would have been peeled off already. + stamp_or_phony = + GetBuildDirForTargetAsOutputFile(target_, BuildDirType::PHONY); + stamp_or_phony.value().append(target_->label().name()); + stamp_or_phony.value().append(".postprocessing.inputdeps"); + tool = BuiltinTool::kBuiltinToolPhony; + } else { + // Make a stamp target. + stamp_or_phony = + GetBuildDirForTargetAsOutputFile(target_, BuildDirType::OBJ); + stamp_or_phony.value().append(target_->label().name()); + stamp_or_phony.value().append(".postprocessing.inputdeps.stamp"); + tool = GeneralTool::kGeneralToolStamp; + } out_ << "build "; - WriteOutput(post_processing_input_stamp_file); - out_ << ": " << GetNinjaRulePrefixForToolchain(settings_) - << GeneralTool::kGeneralToolStamp; + WriteOutput(stamp_or_phony); + out_ << ": " << GetNinjaRulePrefixForToolchain(settings_) << tool; for (const SourceFile& source : post_processing_input_files) { out_ << " "; @@ -372,5 +411,5 @@ path_output_.WriteFiles(out_, order_only_deps); } out_ << std::endl; - return post_processing_input_stamp_file; + return stamp_or_phony; }
diff --git a/src/gn/ninja_create_bundle_target_writer.h b/src/gn/ninja_create_bundle_target_writer.h index 609bc6b..ee27557 100644 --- a/src/gn/ninja_create_bundle_target_writer.h +++ b/src/gn/ninja_create_bundle_target_writer.h
@@ -45,9 +45,9 @@ const std::vector<OutputFile>& order_only_deps, std::vector<OutputFile>* output_files); - // Writes the stamp file for the assets catalog compilation input + // Writes the phony target for the assets catalog compilation input // dependencies. - OutputFile WriteCompileAssetsCatalogInputDepsStamp( + OutputFile WriteCompileAssetsCatalogInputDepsStampOrPhony( const std::vector<const Target*>& dependencies); // Writes the post-processing step (if a script is defined). @@ -59,8 +59,9 @@ const std::vector<OutputFile>& order_only_deps, std::vector<OutputFile>* output_files); - // Writes the stamp file for the post-processing input dependencies. - OutputFile WritePostProcessingInputDepsStamp( + // Writes the stamp file or phony target for the post-processing input + // dependencies. + OutputFile WritePostProcessingInputDepsStampOrPhony( const std::vector<OutputFile>& order_only_deps, std::vector<OutputFile>* output_files);
diff --git a/src/gn/ninja_generated_file_target_writer.cc b/src/gn/ninja_generated_file_target_writer.cc index 307c12b..6de8207 100644 --- a/src/gn/ninja_generated_file_target_writer.cc +++ b/src/gn/ninja_generated_file_target_writer.cc
@@ -24,7 +24,7 @@ // Write the file. GenerateFile(); - // A generated_file target should generate a stamp file with dependencies + // A generated_file target should generate a phony target with dependencies // on each of the deps and data_deps in the target. The actual collection is // done at gen time, but to have correct input deps in ninja, we add output // from generated_file targets as deps for the stamp. @@ -32,17 +32,22 @@ std::vector<OutputFile> data_output_files; const auto& target_deps = resolved().GetTargetDeps(target_); for (const Target* dep : target_deps.linked_deps()) { + if (!dep->has_dependency_output()) { + continue; + } if (dep->IsDataOnly()) { - data_output_files.push_back(dep->dependency_output_file()); + data_output_files.push_back(dep->dependency_output()); } else { - output_files.push_back(dep->dependency_output_file()); + output_files.push_back(dep->dependency_output()); } } - for (const Target* data_dep : target_deps.data_deps()) - data_output_files.push_back(data_dep->dependency_output_file()); + for (const Target* data_dep : target_deps.data_deps()) { + if (data_dep->has_dependency_output()) + data_output_files.push_back(data_dep->dependency_output()); + } - WriteStampForTarget(output_files, data_output_files); + WriteStampOrPhonyForTarget(output_files, data_output_files); } void NinjaGeneratedFileTargetWriter::GenerateFile() {
diff --git a/src/gn/ninja_group_target_writer.cc b/src/gn/ninja_group_target_writer.cc index 58d6ce7..7db1a3a 100644 --- a/src/gn/ninja_group_target_writer.cc +++ b/src/gn/ninja_group_target_writer.cc
@@ -17,22 +17,26 @@ NinjaGroupTargetWriter::~NinjaGroupTargetWriter() = default; void NinjaGroupTargetWriter::Run() { - // A group rule just generates a stamp file with dependencies on each of + // A group rule just generates a phony target with dependencies on each of // the deps and data_deps in the group. std::vector<OutputFile> output_files; std::vector<OutputFile> data_output_files; const auto& target_deps = resolved().GetTargetDeps(target_); for (const Target* dep : target_deps.linked_deps()) { + if (!dep->has_dependency_output()) + continue; if (dep->IsDataOnly()) { - data_output_files.push_back(dep->dependency_output_file()); + data_output_files.push_back(dep->dependency_output()); } else { - output_files.push_back(dep->dependency_output_file()); + output_files.push_back(dep->dependency_output()); } } - for (const Target* data_dep : target_deps.data_deps()) - data_output_files.push_back(data_dep->dependency_output_file()); + for (const Target* data_dep : target_deps.data_deps()) { + if (data_dep->has_dependency_output()) + data_output_files.push_back(data_dep->dependency_output()); + } - WriteStampForTarget(output_files, data_output_files); + WriteStampOrPhonyForTarget(output_files, data_output_files); }
diff --git a/src/gn/ninja_rust_binary_target_writer.cc b/src/gn/ninja_rust_binary_target_writer.cc index a566dec..ba6b416 100644 --- a/src/gn/ninja_rust_binary_target_writer.cc +++ b/src/gn/ninja_rust_binary_target_writer.cc
@@ -111,10 +111,10 @@ void NinjaRustBinaryTargetWriter::Run() { DCHECK(target_->output_type() != Target::SOURCE_SET); - size_t num_stamp_uses = target_->sources().size(); + size_t num_output_uses = target_->sources().size(); std::vector<OutputFile> input_deps = - WriteInputsStampAndGetDep(num_stamp_uses); + WriteInputsStampOrPhonyAndGetDep(num_output_uses); WriteCompilerVars(); @@ -125,8 +125,8 @@ // Ninja to make sure the inputs are up to date before compiling this source, // but changes in the inputs deps won't cause the file to be recompiled. See // the comment on NinjaCBinaryTargetWriter::Run for more detailed explanation. - std::vector<OutputFile> order_only_deps = WriteInputDepsStampAndGetDep( - std::vector<const Target*>(), num_stamp_uses); + std::vector<OutputFile> order_only_deps = WriteInputDepsStampOrPhonyAndGetDep( + std::vector<const Target*>(), num_output_uses); std::copy(input_deps.begin(), input_deps.end(), std::back_inserter(order_only_deps)); @@ -146,7 +146,9 @@ classified_deps.extra_object_files.begin(), classified_deps.extra_object_files.end()); for (const auto* framework_dep : classified_deps.framework_deps) { - order_only_deps.push_back(framework_dep->dependency_output_file()); + if (framework_dep->has_dependency_output_file()) { + order_only_deps.push_back(framework_dep->dependency_output_file()); + } } if (target_->IsFinal()) { for (const Target* dep : classified_deps.swiftmodule_deps) { @@ -155,11 +157,13 @@ } } for (const auto* non_linkable_dep : classified_deps.non_linkable_deps) { - if (non_linkable_dep->source_types_used().RustSourceUsed() && - non_linkable_dep->output_type() != Target::SOURCE_SET) { - rustdeps.push_back(non_linkable_dep->dependency_output_file()); + if (non_linkable_dep->has_dependency_output()) { + if (non_linkable_dep->source_types_used().RustSourceUsed() && + non_linkable_dep->output_type() != Target::SOURCE_SET) { + rustdeps.push_back(non_linkable_dep->dependency_output()); + } + order_only_deps.push_back(non_linkable_dep->dependency_output()); } - order_only_deps.push_back(non_linkable_dep->dependency_output_file()); } for (const auto* linkable_dep : classified_deps.linkable_deps) { // Rust cdylibs are treated as non-Rust dependencies for linking purposes. @@ -169,6 +173,7 @@ } else { nonrustdeps.push_back(linkable_dep->link_output_file()); } + CHECK(linkable_dep->has_dependency_output_file()); implicit_deps.push_back(linkable_dep->dependency_output_file()); }
diff --git a/src/gn/ninja_target_writer.cc b/src/gn/ninja_target_writer.cc index b8b156f..01a1407 100644 --- a/src/gn/ninja_target_writer.cc +++ b/src/gn/ninja_target_writer.cc
@@ -8,6 +8,7 @@ #include "base/files/file_util.h" #include "base/strings/string_util.h" +#include "gn/builtin_tool.h" #include "gn/c_substitution_type.h" #include "gn/config_values_extractors.h" #include "gn/err.h" @@ -432,16 +433,16 @@ } } -std::vector<OutputFile> NinjaTargetWriter::WriteInputDepsStampAndGetDep( +std::vector<OutputFile> NinjaTargetWriter::WriteInputDepsStampOrPhonyAndGetDep( const std::vector<const Target*>& additional_hard_deps, - size_t num_stamp_uses) const { + size_t num_output_uses) const { CHECK(target_->toolchain()) << "Toolchain not set on target " << target_->label().GetUserVisibleName(true); // ---------- // Collect all input files that are input deps of this target. Knowing the // number before writing allows us to either skip writing the input deps - // stamp or optimize it. Use pointers to avoid copies here. + // phony or optimize it. Use pointers to avoid copies here. std::vector<const SourceFile*> input_deps_sources; input_deps_sources.reserve(32); @@ -496,7 +497,7 @@ // Toolchain dependencies. These must be resolved before doing anything. // This just writes all toolchain deps for simplicity. If we find that // toolchains often have more than one dependency, we could consider writing - // a toolchain-specific stamp file and only include the stamp here. + // a toolchain-specific phony target and only include the phony here. // Note that these are usually empty/small. const LabelTargetVector& toolchain_deps = target_->toolchain()->deps(); for (const auto& toolchain_dep : toolchain_deps) { @@ -513,14 +514,15 @@ return std::vector<OutputFile>(); // No input dependencies. // If we're only generating one input dependency, return it directly instead - // of writing a stamp file for it. + // of writing a phony target for it. if (input_deps_sources.size() == 1 && input_deps_targets.size() == 0) return std::vector<OutputFile>{ OutputFile(settings_->build_settings(), *input_deps_sources[0])}; if (input_deps_sources.size() == 0 && input_deps_targets.size() == 1) { - const OutputFile& dep = input_deps_targets[0]->dependency_output_file(); - DCHECK(!dep.value().empty()); - return std::vector<OutputFile>{dep}; + const auto& dep = *input_deps_targets[0]; + if (!dep.has_dependency_output()) + return std::vector<OutputFile>(); + return std::vector<OutputFile>{dep.dependency_output()}; } std::vector<OutputFile> outs; @@ -534,48 +536,85 @@ input_deps_targets.begin(), input_deps_targets.end(), [](const Target* a, const Target* b) { return a->label() < b->label(); }); for (auto* dep : input_deps_targets) { - DCHECK(!dep->dependency_output_file().value().empty()); - outs.push_back(dep->dependency_output_file()); + if (dep->has_dependency_output()) + outs.push_back(dep->dependency_output()); } - // If there are multiple inputs, but the stamp file would be referenced only + // If there are multiple inputs, but the phony target would be referenced only // once, don't write it but depend on the inputs directly. - if (num_stamp_uses == 1u) + if (num_output_uses == 1u) return outs; - // Make a stamp file. - OutputFile input_stamp_file = - GetBuildDirForTargetAsOutputFile(target_, BuildDirType::OBJ); - input_stamp_file.value().append(target_->label().name()); - input_stamp_file.value().append(".inputdeps.stamp"); + OutputFile input_stamp_or_phony; + std::string tool; + if (settings_->build_settings()->no_stamp_files()) { + // Make a phony target. We don't need to worry about an empty phony target, + // as we would return early if there were no inputs. + CHECK(!outs.empty()); + input_stamp_or_phony = + GetBuildDirForTargetAsOutputFile(target_, BuildDirType::PHONY); + input_stamp_or_phony.value().append(target_->label().name()); + input_stamp_or_phony.value().append(".inputdeps"); + tool = BuiltinTool::kBuiltinToolPhony; + } else { + // Make a stamp file. + input_stamp_or_phony = + GetBuildDirForTargetAsOutputFile(target_, BuildDirType::OBJ); + input_stamp_or_phony.value().append(target_->label().name()); + input_stamp_or_phony.value().append(".inputdeps.stamp"); + + tool = GetNinjaRulePrefixForToolchain(settings_) + + GeneralTool::kGeneralToolStamp; + } out_ << "build "; - WriteOutput(input_stamp_file); - - out_ << ": " << GetNinjaRulePrefixForToolchain(settings_) - << GeneralTool::kGeneralToolStamp; - path_output_.WriteFiles(out_, outs); - + WriteOutput(input_stamp_or_phony); + out_ << ": " << tool; + WriteOutputs(outs); out_ << "\n"; - return std::vector<OutputFile>{input_stamp_file}; + return std::vector<OutputFile>{input_stamp_or_phony}; } -void NinjaTargetWriter::WriteStampForTarget( +void NinjaTargetWriter::WriteStampOrPhonyForTarget( const std::vector<OutputFile>& files, const std::vector<OutputFile>& order_only_deps) { - const OutputFile& stamp_file = target_->dependency_output_file(); + // We should have already discerned whether this target is a stamp or a phony. + // If there's a dependency_output_file, it should be a stamp. Else is a phony + // or omitted phony (in which case, we don't write it). + if (target_->has_dependency_output_file()) { + // Make a stamp target. + const OutputFile& stamp_file = target_->dependency_output_file(); - // First validate that the target's dependency is a stamp file. Otherwise, - // we shouldn't have gotten here! - CHECK(base::EndsWithCaseInsensitiveASCII(stamp_file.value(), ".stamp")) - << "Output should end in \".stamp\" for stamp file output. Instead got: " - << "\"" << stamp_file.value() << "\""; + // First validate that the target's dependency is a stamp file. Otherwise, + // we shouldn't have gotten here! + CHECK(base::EndsWithCaseInsensitiveASCII(stamp_file.value(), ".stamp")) + << "Output should end in \".stamp\" for stamp file output. Instead " + "got: " + << "\"" << stamp_file.value() << "\""; - out_ << "build "; - WriteOutput(std::move(stamp_file)); + out_ << "build "; + WriteOutput(stamp_file); - out_ << ": " << GetNinjaRulePrefixForToolchain(settings_) - << GeneralTool::kGeneralToolStamp; + out_ << ": " << GetNinjaRulePrefixForToolchain(settings_) + << GeneralTool::kGeneralToolStamp; + } else if (target_->has_dependency_output_alias()) { + // Make a phony target. + const OutputFile& phony_target = target_->dependency_output_alias(); + CHECK(!phony_target.value().empty()); + + out_ << "build "; + WriteOutput(phony_target); + + out_ << ": " << BuiltinTool::kBuiltinToolPhony; + + } else { + // This is the omitted phony case. We should not get here if there were any + // dependencies, so ensure that none got added. + CHECK(files.empty()); + CHECK(order_only_deps.empty()); + return; + } + path_output_.WriteFiles(out_, files); if (!order_only_deps.empty()) {
diff --git a/src/gn/ninja_target_writer.h b/src/gn/ninja_target_writer.h index 9655c72..68bc0a3 100644 --- a/src/gn/ninja_target_writer.h +++ b/src/gn/ninja_target_writer.h
@@ -82,22 +82,22 @@ bool indent, bool always_write); - // Writes to the output stream a stamp rule for input dependencies, and + // Writes to the output stream a phony rule for input dependencies, and // returns the file to be appended to source rules that encodes the // order-only dependencies for the current target. - // If num_stamp_uses is small, this might return all input dependencies - // directly, without writing a stamp file. + // If num_output_uses is small, this might return all input dependencies + // directly, without writing a phony rule. // If there are no implicit dependencies and no additional target dependencies // are passed in, this returns an empty vector. - std::vector<OutputFile> WriteInputDepsStampAndGetDep( + std::vector<OutputFile> WriteInputDepsStampOrPhonyAndGetDep( const std::vector<const Target*>& additional_hard_deps, - size_t num_stamp_uses) const; + size_t num_output_uses) const; - // Writes to the output file a final stamp rule for the target that stamps - // the given list of files. This function assumes the stamp is for the target - // as a whole so the stamp file is set as the target's dependency output. - void WriteStampForTarget(const std::vector<OutputFile>& deps, - const std::vector<OutputFile>& order_only_deps); + // Writes to the output file a final phony rule for the target that aliases + // the given list of files. + void WriteStampOrPhonyForTarget( + const std::vector<OutputFile>& deps, + const std::vector<OutputFile>& order_only_deps); const Settings* settings_; // Non-owning. const Target* target_; // Non-owning.
diff --git a/src/gn/ninja_target_writer_unittest.cc b/src/gn/ninja_target_writer_unittest.cc index 682de45..6b88674 100644 --- a/src/gn/ninja_target_writer_unittest.cc +++ b/src/gn/ninja_target_writer_unittest.cc
@@ -22,11 +22,11 @@ void Run() override {} // Make this public so the test can call it. - std::vector<OutputFile> WriteInputDepsStampAndGetDep( + std::vector<OutputFile> WriteInputDepsStampOrPhonyAndGetDep( const std::vector<const Target*>& additional_hard_deps, size_t num_stamp_uses) { - return NinjaTargetWriter::WriteInputDepsStampAndGetDep(additional_hard_deps, - num_stamp_uses); + return NinjaTargetWriter::WriteInputDepsStampOrPhonyAndGetDep( + additional_hard_deps, num_stamp_uses); } }; @@ -74,7 +74,7 @@ EXPECT_EQ(&resolved, &writer.resolved()); } -TEST(NinjaTargetWriter, WriteInputDepsStampAndGetDep) { +TEST(NinjaTargetWriter, WriteInputDepsStampOrPhonyAndGetDep) { TestWithScope setup; Err err; @@ -113,8 +113,8 @@ { std::ostringstream stream; TestingNinjaTargetWriter writer(&base_target, setup.toolchain(), stream); - std::vector<OutputFile> dep = - writer.WriteInputDepsStampAndGetDep(std::vector<const Target*>(), 10u); + std::vector<OutputFile> dep = writer.WriteInputDepsStampOrPhonyAndGetDep( + std::vector<const Target*>(), 10u); // Since there is only one dependency, it should just be returned and // nothing written to the stream. @@ -127,8 +127,8 @@ { std::ostringstream stream; TestingNinjaTargetWriter writer(&target, setup.toolchain(), stream); - std::vector<OutputFile> dep = - writer.WriteInputDepsStampAndGetDep(std::vector<const Target*>(), 10u); + std::vector<OutputFile> dep = writer.WriteInputDepsStampOrPhonyAndGetDep( + std::vector<const Target*>(), 10u); // Since there is only one dependency, a stamp file will be returned // directly without writing any additional rules. @@ -158,8 +158,8 @@ { std::ostringstream stream; TestingNinjaTargetWriter writer(&action, setup.toolchain(), stream); - std::vector<OutputFile> dep = - writer.WriteInputDepsStampAndGetDep(std::vector<const Target*>(), 10u); + std::vector<OutputFile> dep = writer.WriteInputDepsStampOrPhonyAndGetDep( + std::vector<const Target*>(), 10u); ASSERT_EQ(1u, dep.size()); EXPECT_EQ("obj/foo/action.inputdeps.stamp", dep[0].value()); @@ -170,8 +170,8 @@ } } -// Tests WriteInputDepsStampAndGetDep when toolchain deps are present. -TEST(NinjaTargetWriter, WriteInputDepsStampAndGetDepWithToolchainDeps) { +// Tests WriteInputDepsStampOrPhonyAndGetDep when toolchain deps are present. +TEST(NinjaTargetWriter, WriteInputDepsStampOrPhonyAndGetDepWithToolchainDeps) { TestWithScope setup; Err err; @@ -194,8 +194,8 @@ std::ostringstream stream; TestingNinjaTargetWriter writer(&target, setup.toolchain(), stream); - std::vector<OutputFile> dep = - writer.WriteInputDepsStampAndGetDep(std::vector<const Target*>(), 10u); + std::vector<OutputFile> dep = writer.WriteInputDepsStampOrPhonyAndGetDep( + std::vector<const Target*>(), 10u); // Since there is more than one dependency, a stamp file will be returned // and the rule for the stamp file will be written to the stream.
diff --git a/src/gn/runtime_deps.cc b/src/gn/runtime_deps.cc index 9ca016d..546d63e 100644 --- a/src/gn/runtime_deps.cc +++ b/src/gn/runtime_deps.cc
@@ -177,7 +177,7 @@ return false; } - OutputFile output_file; + std::optional<OutputFile> output_file; const char extension[] = ".runtime_deps"; if (target->output_type() == Target::SHARED_LIBRARY || target->output_type() == Target::LOADABLE_MODULE) { @@ -186,11 +186,20 @@ CHECK(!target->computed_outputs().empty()); output_file = OutputFile(target->computed_outputs()[0].value() + extension); - } else { + } else if (target->has_dependency_output_file()) { output_file = OutputFile(target->dependency_output_file().value() + extension); + } else { + // If there is no dependency_output_file, this target's dependency output + // is either a phony alias or was elided entirely (due to lack of real + // inputs). In either case, there is no file to add an additional + // extension to, so we should compute our own name in the OBJ BuildDir. + output_file = GetBuildDirForTargetAsOutputFile(target, BuildDirType::OBJ); + output_file->value().append(target->GetComputedOutputName()); + output_file->value().append(extension); } - files_to_write->push_back(std::make_pair(output_file, target)); + if (output_file) + files_to_write->push_back(std::make_pair(*output_file, target)); } return true; }
diff --git a/src/gn/setup.cc b/src/gn/setup.cc index b83e904..c9e6b8b 100644 --- a/src/gn/setup.cc +++ b/src/gn/setup.cc
@@ -203,6 +203,10 @@ required version is 1.7.2. Specifying a higher version might enable the use of some of newer features that can make the build more efficient. + no_stamp_files [optional] + A boolean flag that can be set to generate Ninja files that use phony + rules instead of stamp files whenever possible. This results in smaller + Ninja build plans, but requires at least Ninja 1.11. Example .gn file contents @@ -1135,8 +1139,6 @@ return false; } build_settings_.set_no_stamp_files(no_stamp_files_value->boolean_value()); - CHECK(!build_settings_.no_stamp_files()) - << "no_stamp_files does not work yet!"; } // Export compile commands.
diff --git a/src/gn/switches.cc b/src/gn/switches.cc index 95b0b14..67c18a1 100644 --- a/src/gn/switches.cc +++ b/src/gn/switches.cc
@@ -233,9 +233,12 @@ an output file "bar.so", GN will create a file "bar.so.runtime_deps" in the build directory. - If a source set, action, copy, or group is listed, the runtime deps file will - correspond to the .stamp file corresponding to that target. This is probably - not useful; the use-case for this feature is generally executable targets. + For targets that don't generate an output file (such as source set, action, + copy or group), the runtime deps file will be in the output directory where an + output file would have been located. For example, the source_set target + "//foo:bar" would result in a runtime dependency file being written to + "<output_dir>/obj/foo/bar.runtime_deps". This is probably not useful; the + use-case for this feature is generally executable targets. The runtime dependency file will list one file per line, with no escaping. The files will be relative to the root_build_dir. The first line of the file
diff --git a/src/gn/target.cc b/src/gn/target.cc index 0dd5689..3697843 100644 --- a/src/gn/target.cc +++ b/src/gn/target.cc
@@ -784,10 +784,12 @@ // This check is only necessary if this target will result in a phony target. // Phony targets with no real inputs are treated as always dirty. - // Actions always have at least one input file: the script used to execute - // the action. As such, they will never have an input-less phony target. We - // check this first to elide the common checks. - if (output_type() == ACTION || output_type() == ACTION_FOREACH) { + // Actions and generated_file always have at least one input file: the script + // used to execute the action or generated file itself. As such, they will + // never have an input-less phony target. We check this first to elide the + // common checks. + if (output_type() == ACTION || output_type() == ACTION_FOREACH || + output_type() == GENERATED_FILE) { return true; }
diff --git a/src/gn/target.h b/src/gn/target.h index f794035..b6947f9 100644 --- a/src/gn/target.h +++ b/src/gn/target.h
@@ -379,28 +379,28 @@ bool has_dependency_output() const { return has_dependency_output_file() || has_dependency_output_alias(); } - // Returns the output dependency file path or phony alias if one is defined, + // Return the output dependency file path or phony alias if one is defined, // or an empty string otherwise. const OutputFile& dependency_output() const { return has_dependency_output_file() ? dependency_output_file_ : dependency_output_alias_; } - // Returns true if there is a dependency file path defined for this target. + // Return true if there is a dependency file path defined for this target. bool has_dependency_output_file() const { return !dependency_output_file_.value().empty(); } - // Returns the dependency output file path for this target if defined, or + // Return the dependency output file path for this target if defined, or // an empty string otherwise. const OutputFile& dependency_output_file() const { return dependency_output_file_; } - // Returns true if there is a dependency output alias defined for this target. + // Return true if there is a dependency output alias defined for this target. bool has_dependency_output_alias() const { return !dependency_output_alias_.value().empty(); } - // Returns the dependency output alias if any, or an empty string otherwise. + // Return the dependency output alias if any, or an empty string otherwise. const OutputFile& dependency_output_alias() const { return dependency_output_alias_; }
diff --git a/src/gn/target_unittest.cc b/src/gn/target_unittest.cc index c510ba1..cac3670 100644 --- a/src/gn/target_unittest.cc +++ b/src/gn/target_unittest.cc
@@ -547,6 +547,7 @@ ASSERT_TRUE(target.OnResolved(&err)); EXPECT_EQ("./liba.so", target.link_output_file().value()); + ASSERT_TRUE(target.has_dependency_output_file()); EXPECT_EQ("./liba.so.TOC", target.dependency_output_file().value()); ASSERT_EQ(1u, target.runtime_outputs().size()); @@ -635,6 +636,7 @@ ASSERT_TRUE(target.OnResolved(&err)); EXPECT_EQ("./a.dll.lib", target.link_output_file().value()); + ASSERT_TRUE(target.has_dependency_output_file()); EXPECT_EQ("./a.dll.lib", target.dependency_output_file().value()); ASSERT_EQ(2u, target.runtime_outputs().size()); @@ -718,6 +720,7 @@ Target target(setup.settings(), Label(SourceDir("//a/"), "a")); target.set_output_type(Target::SOURCE_SET); + target.sources().push_back(SourceFile("//a/source_file1.cc")); target.SetToolchain(&toolchain); Err err; ASSERT_TRUE(target.OnResolved(&err));
diff --git a/src/gn/toolchain.h b/src/gn/toolchain.h index 270fada..cc18098 100644 --- a/src/gn/toolchain.h +++ b/src/gn/toolchain.h
@@ -101,8 +101,8 @@ // Returns the tool that produces the final output for the given target type. // This isn't necessarily the tool you would expect. For copy target, this - // will return the stamp tool instead since the final output of a copy - // target is to stamp the set of copies done so there is one output. + // will return the phony tool instead since the final output of a copy + // target is a phony alias to the set of copies done so there is one output. const Tool* GetToolForTargetFinalOutput(const Target* target) const; const CTool* GetToolForTargetFinalOutputAsC(const Target* target) const; const GeneralTool* GetToolForTargetFinalOutputAsGeneral(
diff --git a/src/gn/visual_studio_writer.cc b/src/gn/visual_studio_writer.cc index 037450e..243fedd 100644 --- a/src/gn/visual_studio_writer.cc +++ b/src/gn/visual_studio_writer.cc
@@ -542,7 +542,7 @@ project.SubElement("PropertyGroup", XmlAttributes("Label", "UserMacros")); - std::string ninja_target = GetNinjaTarget(target); + auto [ninja_target, ninja_target_is_phony] = GetNinjaTarget(target); std::string ninja_exe = GetNinjaExecutable(ninja_executable); { @@ -550,7 +550,7 @@ project.SubElement("PropertyGroup"); properties->SubElement("OutDir")->Text("$(SolutionDir)"); properties->SubElement("TargetName")->Text("$(ProjectName)"); - if (target->output_type() != Target::GROUP) { + if (target->output_type() != Target::GROUP && !ninja_target_is_phony) { properties->SubElement("TargetPath")->Text("$(OutDir)\\" + ninja_target); } } @@ -927,13 +927,21 @@ } } -std::string VisualStudioWriter::GetNinjaTarget(const Target* target) { +std::pair<std::string, bool> VisualStudioWriter::GetNinjaTarget( + const Target* target) { std::ostringstream ninja_target_out; - DCHECK(!target->dependency_output_file().value().empty()); - ninja_path_output_.WriteFile(ninja_target_out, - target->dependency_output_file()); + bool is_phony = false; + OutputFile output_file; + if (target->has_dependency_output_file()) { + output_file = target->dependency_output_file(); + } else if (target->has_dependency_output_alias()) { + output_file = target->dependency_output_alias(); + is_phony = true; + } + + ninja_path_output_.WriteFile(ninja_target_out, output_file); std::string s = ninja_target_out.str(); if (s.compare(0, 2, "./") == 0) s = s.substr(2); - return s; + return std::make_pair(s, is_phony); }
diff --git a/src/gn/visual_studio_writer.h b/src/gn/visual_studio_writer.h index bcb9e22..7161481 100644 --- a/src/gn/visual_studio_writer.h +++ b/src/gn/visual_studio_writer.h
@@ -131,7 +131,8 @@ // and updates |root_folder_dir_|. Also sets |parent_folder| for |projects_|. void ResolveSolutionFolders(); - std::string GetNinjaTarget(const Target* target); + // Returns the ninja target string and whether the target is phony. + std::pair<std::string, bool> GetNinjaTarget(const Target* target); const BuildSettings* build_settings_;