Add Target::dependency_output_alias()
In order to support emitting Ninja phony targets instead of stamp,
this CL allows the Target class to provide either a
dependency_output_file() or a dependency_output_alias() as their
output. Only one of these can be set, though both can be empty
under certain circumstances. Code that doesn't care can also use
dependency_output().
This also modifies the behaviour of certain Target methods in
the case where the "no_stamp_files" build settings is enabled,
however the latter is currently disabled (see previous CL),
which means that no change in behaviour should occur due to
this CL.
The next CL will hook the target generator to these methods
and will ensure the phony rule generation works as expected.
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.
Bug: 172
Change-Id: Ide890dd4d016e7e27e633dc67906a87aa8d1acd4
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/12865
Reviewed-by: Philipp Wollermann <philwo@google.com>
Commit-Queue: Takuto Ikuta <tikuta@google.com>
Reviewed-by: Takuto Ikuta <tikuta@google.com>
diff --git a/src/gn/target.cc b/src/gn/target.cc
index fb8dbef..0dd5689 100644
--- a/src/gn/target.cc
+++ b/src/gn/target.cc
@@ -6,6 +6,8 @@
#include <stddef.h>
+#include <algorithm>
+
#include "base/stl_util.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
@@ -630,8 +632,7 @@
if (!bundle_data().GetOutputsAsSourceFiles(settings(), this, outputs, err))
return false;
} else if (IsBinary() && output_type() != Target::SOURCE_SET) {
- // Binary target with normal outputs (source sets have stamp outputs like
- // groups).
+ // Binary target with normal outputs (source sets have phony targets).
DCHECK(IsBinary()) << static_cast<int>(output_type());
if (!build_complete) {
// Can't access the toolchain for a target before the build is complete.
@@ -651,15 +652,20 @@
output_file.AsSourceFile(settings()->build_settings()));
}
} else {
- // Everything else (like a group or bundle_data) has a stamp output. The
- // dependency output file should have computed what this is. This won't be
+ // Everything else (like a group or bundle_data) has a phony output. The
+ // dependency output phony should have computed what this is. This won't be
// valid unless the build is complete.
if (!build_complete) {
*err = Err(loc_for_error, kBuildIncompleteMsg);
return false;
}
- outputs->push_back(
- dependency_output_file().AsSourceFile(settings()->build_settings()));
+
+ // The dependency output might be empty if there is no output file or a
+ // phony alias for a set of inputs.
+ if (has_dependency_output()) {
+ outputs->push_back(
+ dependency_output().AsSourceFile(settings()->build_settings()));
+ }
}
return true;
}
@@ -774,27 +780,77 @@
bundle_data().OnTargetResolved(this);
}
+bool Target::HasRealInputs() const {
+ // 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) {
+ return true;
+ }
+
+ // If any of this target's dependencies is non-phony target or a phony target
+ // with real inputs, then this target should be considered to have inputs.
+ for (const auto& pair : GetDeps(DEPS_ALL)) {
+ if (pair.ptr->has_dependency_output()) {
+ return true;
+ }
+ }
+
+ if (output_type() == BUNDLE_DATA) {
+ return !sources().empty();
+ }
+ if (output_type() == CREATE_BUNDLE) {
+ // CREATE_BUNDLE targets pick up most of their inputs in the form of
+ // dependencies on bundle_data targets, which were checked above when
+ // looping through GetDeps. This code handles the remaining possible
+ // CREATE_BUNDLE inputs.
+ return !bundle_data().assets_catalog_sources().empty() ||
+ !bundle_data().partial_info_plist().is_null() ||
+ !bundle_data().post_processing_script().is_null();
+ }
+
+ // If any of this target's sources will result in output files, then this
+ // target should be considered to have real inputs.
+ std::vector<OutputFile> tool_outputs;
+ return std::any_of(
+ sources().begin(), sources().end(), [&, this](const auto& source) {
+ const char* tool_name = Tool::kToolNone;
+ return GetOutputFilesForSource(source, &tool_name, &tool_outputs);
+ });
+}
+
bool Target::FillOutputFiles(Err* err) {
const Tool* tool = toolchain_->GetToolForTargetFinalOutput(this);
bool check_tool_outputs = false;
switch (output_type_) {
- case GROUP:
- case BUNDLE_DATA:
- case CREATE_BUNDLE:
- case SOURCE_SET:
- case COPY_FILES:
case ACTION:
case ACTION_FOREACH:
- case GENERATED_FILE: {
- // These don't get linked to and use stamps which should be the first
- // entry in the outputs. These stamps are named
- // "<target_out_dir>/<targetname>.stamp". Setting "output_name" does not
- // affect the stamp file name: it is always based on the original target
- // name.
- dependency_output_file_ =
- GetBuildDirForTargetAsOutputFile(this, BuildDirType::OBJ);
- dependency_output_file_.value().append(label().name());
- dependency_output_file_.value().append(".stamp");
+ case BUNDLE_DATA:
+ case COPY_FILES:
+ case CREATE_BUNDLE:
+ case GENERATED_FILE:
+ case GROUP:
+ case SOURCE_SET: {
+ if (settings()->build_settings()->no_stamp_files()) {
+ if (HasRealInputs()) {
+ dependency_output_alias_ =
+ GetBuildDirForTargetAsOutputFile(this, BuildDirType::PHONY);
+ dependency_output_alias_.value().append(label().name());
+ }
+ } else {
+ // These don't get linked to and use stamps which should be the first
+ // entry in the outputs. These stamps are named
+ // "<target_out_dir>/<targetname>.stamp". Setting "output_name" does not
+ // affect the stamp file name: it is always based on the original target
+ // name.
+ dependency_output_file_ =
+ GetBuildDirForTargetAsOutputFile(this, BuildDirType::OBJ);
+ dependency_output_file_.value().append(label().name());
+ dependency_output_file_.value().append(".stamp");
+ }
break;
}
case EXECUTABLE:
diff --git a/src/gn/target.h b/src/gn/target.h
index 5cf5360..f794035 100644
--- a/src/gn/target.h
+++ b/src/gn/target.h
@@ -345,7 +345,7 @@
// action or a copy step, and the output library or executable file(s) from
// binary targets.
//
- // It will NOT include stamp files and object files.
+ // It will NOT include phony targets or object files.
const std::vector<OutputFile>& computed_outputs() const {
return computed_outputs_;
}
@@ -358,16 +358,53 @@
// a dependency on this one. It could be the same as the link output file
// (this will be the case for static libraries). For shared libraries it
// could be the same or different than the link output file, depending on the
- // system. For actions this will be the stamp file.
+ // system.
+ //
+ // The dependency output alias is only set when the target does not have an
+ // output file and is using a Ninja phony target to represent it. The
+ // exception to this is for phony targets without any real inputs. Ninja
+ // treats empty phony targets as always dirty, so no other targets should
+ // depend on that target. In that scenario, both dependency_output_alias or
+ // dependency_output_file will be empty.
+ //
+ // Callers that do not care whether the dependency is represented by a file or
+ // an alias should use dependency_output().
//
// These are only known once the target is resolved and will be empty before
// that. This is a cache of the files to prevent every target that depends on
// a given library from recomputing the same pattern.
const OutputFile& link_output_file() const { return link_output_file_; }
+
+ // Returns true if there is an output dependency file or phony alias.
+ 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,
+ // 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.
+ bool has_dependency_output_file() const {
+ return !dependency_output_file_.value().empty();
+ }
+ // Returns 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.
+ bool has_dependency_output_alias() const {
+ return !dependency_output_alias_.value().empty();
+ }
+ // Returns the dependency output alias if any, or an empty string otherwise.
+ const OutputFile& dependency_output_alias() const {
+ return dependency_output_alias_;
+ }
+
// The subset of computed_outputs that are considered runtime outputs.
const std::vector<OutputFile>& runtime_outputs() const {
return runtime_outputs_;
@@ -391,6 +428,11 @@
// The |loc_for_error| is used to blame a location for any errors produced. It
// can be empty if there is no range (like this is being called based on the
// command-line.
+ //
+ // It is possible for |outputs| to be returned empty without an error being
+ // reported. This can occur when the output type will result in a phony alias
+ // target (like a source_set) that is omitted from build files when they have
+ // no real inputs.
bool GetOutputsAsSourceFiles(const LocationRange& loc_for_error,
bool build_complete,
std::vector<SourceFile>* outputs,
@@ -418,6 +460,7 @@
private:
FRIEND_TEST_ALL_PREFIXES(TargetTest, ResolvePrecompiledHeaders);
+ FRIEND_TEST_ALL_PREFIXES(TargetTest, HasRealInputs);
// Pulls necessary information from dependencies to this one when all
// dependencies have been resolved.
@@ -427,6 +470,11 @@
void PullRecursiveHardDeps();
void PullRecursiveBundleData();
+ // Checks to see whether this target or any of its dependencies have real
+ // inputs. If not, this target should be omitted as a dependency. This check
+ // only applies to targets that will result in a phony rule.
+ bool HasRealInputs() const;
+
// Fills the link and dependency output files when a target is resolved.
bool FillOutputFiles(Err* err);
@@ -498,6 +546,7 @@
std::vector<OutputFile> computed_outputs_;
OutputFile link_output_file_;
OutputFile dependency_output_file_;
+ OutputFile dependency_output_alias_;
std::vector<OutputFile> runtime_outputs_;
std::unique_ptr<Metadata> metadata_;
diff --git a/src/gn/target_unittest.cc b/src/gn/target_unittest.cc
index 5cc2182..c510ba1 100644
--- a/src/gn/target_unittest.cc
+++ b/src/gn/target_unittest.cc
@@ -862,6 +862,46 @@
EXPECT_EQ("//out/Debug/one", computed_outputs[0].value());
}
+TEST_F(TargetTest, HasRealInputs) {
+ TestWithScope setup;
+ Err err;
+ // Action always have real inputs.
+ TestTarget target_a(setup, "//a:a", Target::ACTION);
+ ASSERT_TRUE(target_a.FillOutputFiles(&err));
+ EXPECT_TRUE(target_a.HasRealInputs());
+
+ // A target with no inputs and no deps has no real inputs.
+ TestTarget target_b(setup, "//a:b", Target::GROUP);
+ ASSERT_TRUE(target_b.FillOutputFiles(&err));
+ EXPECT_FALSE(target_b.HasRealInputs());
+
+ // A target with no inputs and one dep with real inputs has real inputs.
+ target_b.private_deps().push_back(LabelTargetPair(&target_a));
+ ASSERT_TRUE(target_b.FillOutputFiles(&err));
+ EXPECT_TRUE(target_b.HasRealInputs());
+
+ // A target with one input with no tool, and no deps, has no real inputs.
+ TestTarget target_c(setup, "//a:c", Target::SOURCE_SET);
+ target_c.config_values().inputs().push_back(SourceFile("//a/no_tool.txt"));
+ ASSERT_TRUE(target_c.FillOutputFiles(&err));
+ EXPECT_FALSE(target_c.HasRealInputs()); // One input with no tool, no deps.
+
+ // The same, but with one dep without a dependency output.
+ TestTarget target_d(setup, "//a:c2", Target::GROUP);
+ target_c.private_deps().push_back(LabelTargetPair(&target_d));
+ ASSERT_TRUE(target_c.FillOutputFiles(&err));
+ EXPECT_FALSE(target_c.HasRealInputs());
+
+ // The same, but with one dep with a dependency output.
+ TestTarget target_e(setup, "//a:d", Target::EXECUTABLE);
+ target_e.sources().push_back(SourceFile("//a/source.cc"));
+ ASSERT_TRUE(target_e.FillOutputFiles(&err));
+ EXPECT_TRUE(target_e.HasRealInputs());
+ target_c.private_deps().push_back(LabelTargetPair(&target_e));
+ ASSERT_TRUE(target_c.FillOutputFiles(&err));
+ EXPECT_TRUE(target_c.HasRealInputs());
+}
+
TEST_F(TargetTest, GeneratedInputs) {
TestWithScope setup;
Err err;