diff --git a/tools/gn/builder.cc b/tools/gn/builder.cc index dccc095a949c66..65c5f601e96736 100644 --- a/tools/gn/builder.cc +++ b/tools/gn/builder.cc @@ -418,7 +418,9 @@ bool Builder::ResolveItem(BuilderRecord* record, Err* err) { } record->set_resolved(true); - record->item()->OnResolved(); + + if (!record->item()->OnResolved(err)) + return false; if (!resolved_callback_.is_null()) resolved_callback_.Run(record); diff --git a/tools/gn/command_gen.cc b/tools/gn/command_gen.cc index 0df121a954f0d1..5e6e724852af05 100644 --- a/tools/gn/command_gen.cc +++ b/tools/gn/command_gen.cc @@ -25,20 +25,9 @@ const char kSwitchQuiet[] = "q"; const char kSwitchCheck[] = "check"; -void BackgroundDoWrite(const Target* target, - const std::vector& deps_for_visibility) { - // Validate visibility. - Err err; - for (size_t i = 0; i < deps_for_visibility.size(); i++) { - if (!Visibility::CheckItemVisibility(target, deps_for_visibility[i], - &err)) { - g_scheduler->FailWithError(err); - break; // Don't return early since we need DecrementWorkCount below. - } - } - - if (!err.has_error()) - NinjaTargetWriter::RunAndWriteFile(target); +// Called on worker thread to write the ninja file. +void BackgroundDoWrite(const Target* target) { + NinjaTargetWriter::RunAndWriteFile(target); g_scheduler->DecrementWorkCount(); } @@ -51,16 +40,8 @@ void ItemResolvedCallback(base::subtle::Atomic32* write_counter, const Item* item = record->item(); const Target* target = item->AsTarget(); if (target) { - // Collect all dependencies. - std::vector deps; - for (BuilderRecord::BuilderRecordSet::const_iterator iter = - record->all_deps().begin(); - iter != record->all_deps().end(); - ++iter) - deps.push_back((*iter)->item()); - g_scheduler->IncrementWorkCount(); - g_scheduler->ScheduleWork(base::Bind(&BackgroundDoWrite, target, deps)); + g_scheduler->ScheduleWork(base::Bind(&BackgroundDoWrite, target)); } } diff --git a/tools/gn/config_values_extractors_unittest.cc b/tools/gn/config_values_extractors_unittest.cc index a4082265cebb94..b0c65cafd28bfd 100644 --- a/tools/gn/config_values_extractors_unittest.cc +++ b/tools/gn/config_values_extractors_unittest.cc @@ -28,6 +28,7 @@ struct IncludeWriter { TEST(ConfigValuesExtractors, IncludeOrdering) { TestWithScope setup; + Err err; // Construct a chain of dependencies: target -> dep1 -> dep2 // Add representative values: cflags (opaque, always copied) and include_dirs @@ -100,9 +101,9 @@ TEST(ConfigValuesExtractors, IncludeOrdering) { SourceDir("//target/")); // Mark targets resolved. This should push dependent configs. - dep2.OnResolved(); - dep1.OnResolved(); - target.OnResolved(); + ASSERT_TRUE(dep2.OnResolved(&err)); + ASSERT_TRUE(dep1.OnResolved(&err)); + ASSERT_TRUE(target.OnResolved(&err)); // Verify cflags by serializing. std::ostringstream flag_out; diff --git a/tools/gn/item.cc b/tools/gn/item.cc index f2fe9fead983f3..7dfd4ad8eaf97f 100644 --- a/tools/gn/item.cc +++ b/tools/gn/item.cc @@ -32,3 +32,7 @@ std::string Item::GetItemTypeName() const { NOTREACHED(); return "this thing that I have no idea what it is"; } + +bool Item::OnResolved(Err* err) { + return true; +} diff --git a/tools/gn/item.h b/tools/gn/item.h index 4e582bfca305de..069c3edc558484 100644 --- a/tools/gn/item.h +++ b/tools/gn/item.h @@ -48,8 +48,9 @@ class Item { std::string GetItemTypeName() const; // Called when this item is resolved, meaning it and all of its dependents - // have no unresolved deps. - virtual void OnResolved() {} + // have no unresolved deps. Returns true on success. Sets the error and + // returns false on failure. + virtual bool OnResolved(Err* err); private: const Settings* settings_; diff --git a/tools/gn/label_ptr.h b/tools/gn/label_ptr.h index 771b14ba3fce1a..4f1a54f1098766 100644 --- a/tools/gn/label_ptr.h +++ b/tools/gn/label_ptr.h @@ -36,7 +36,11 @@ struct LabelPtrPair { Label label; const T* ptr; // May be NULL. - const ParseNode* origin; // May be NULL. + + // The origin of this dependency. This will be null for internally generated + // dependencies. This happens when a group is automatically expanded and that + // group's members are added to the target that depends on that group. + const ParseNode* origin; }; typedef LabelPtrPair LabelConfigPair; diff --git a/tools/gn/ninja_action_target_writer_unittest.cc b/tools/gn/ninja_action_target_writer_unittest.cc index b0161d665037de..f7cf62f3339719 100644 --- a/tools/gn/ninja_action_target_writer_unittest.cc +++ b/tools/gn/ninja_action_target_writer_unittest.cc @@ -13,6 +13,8 @@ TEST(NinjaActionTargetWriter, WriteOutputFilesForBuildLine) { TestWithScope setup; + Err err; + setup.build_settings()->SetBuildDir(SourceDir("//out/Debug/")); Target target(setup.settings(), Label(SourceDir("//foo/"), "bar")); @@ -22,7 +24,7 @@ TEST(NinjaActionTargetWriter, WriteOutputFilesForBuildLine) { "//out/Debug/gen/{{source_name_part}}.cc"); target.SetToolchain(setup.toolchain()); - target.OnResolved(); + ASSERT_TRUE(target.OnResolved(&err)); std::ostringstream out; NinjaActionTargetWriter writer(&target, out); @@ -37,6 +39,8 @@ TEST(NinjaActionTargetWriter, WriteOutputFilesForBuildLine) { // Tests an action with no sources. TEST(NinjaActionTargetWriter, ActionNoSources) { TestWithScope setup; + Err err; + setup.build_settings()->SetBuildDir(SourceDir("//out/Debug/")); Target target(setup.settings(), Label(SourceDir("//foo/"), "bar")); target.set_output_type(Target::ACTION); @@ -48,7 +52,7 @@ TEST(NinjaActionTargetWriter, ActionNoSources) { SubstitutionList::MakeForTest("//out/Debug/foo.out"); target.SetToolchain(setup.toolchain()); - target.OnResolved(); + ASSERT_TRUE(target.OnResolved(&err)); setup.settings()->set_target_os(Settings::LINUX); setup.build_settings()->set_python_path(base::FilePath(FILE_PATH_LITERAL( @@ -76,6 +80,8 @@ TEST(NinjaActionTargetWriter, ActionNoSources) { // both sources and inputs (ACTION_FOREACH treats the sources differently). TEST(NinjaActionTargetWriter, ActionWithSources) { TestWithScope setup; + Err err; + setup.build_settings()->SetBuildDir(SourceDir("//out/Debug/")); Target target(setup.settings(), Label(SourceDir("//foo/"), "bar")); target.set_output_type(Target::ACTION); @@ -89,7 +95,7 @@ TEST(NinjaActionTargetWriter, ActionWithSources) { SubstitutionList::MakeForTest("//out/Debug/foo.out"); target.SetToolchain(setup.toolchain()); - target.OnResolved(); + ASSERT_TRUE(target.OnResolved(&err)); // Posix. { @@ -146,6 +152,8 @@ TEST(NinjaActionTargetWriter, ActionWithSources) { TEST(NinjaActionTargetWriter, ForEach) { TestWithScope setup; + Err err; + setup.build_settings()->SetBuildDir(SourceDir("//out/Debug/")); // Some dependencies that the action can depend on. Use actions for these @@ -155,12 +163,12 @@ TEST(NinjaActionTargetWriter, ForEach) { Target dep(setup.settings(), Label(SourceDir("//foo/"), "dep")); dep.set_output_type(Target::ACTION); dep.SetToolchain(setup.toolchain()); - dep.OnResolved(); + ASSERT_TRUE(dep.OnResolved(&err)); Target datadep(setup.settings(), Label(SourceDir("//foo/"), "datadep")); datadep.set_output_type(Target::ACTION); datadep.SetToolchain(setup.toolchain()); - datadep.OnResolved(); + ASSERT_TRUE(datadep.OnResolved(&err)); Target target(setup.settings(), Label(SourceDir("//foo/"), "bar")); target.set_output_type(Target::ACTION_FOREACH); @@ -182,7 +190,7 @@ TEST(NinjaActionTargetWriter, ForEach) { target.inputs().push_back(SourceFile("//foo/included.txt")); target.SetToolchain(setup.toolchain()); - target.OnResolved(); + ASSERT_TRUE(target.OnResolved(&err)); // Posix. { @@ -268,6 +276,8 @@ TEST(NinjaActionTargetWriter, ForEach) { TEST(NinjaActionTargetWriter, ForEachWithDepfile) { TestWithScope setup; + Err err; + setup.build_settings()->SetBuildDir(SourceDir("//out/Debug/")); Target target(setup.settings(), Label(SourceDir("//foo/"), "bar")); target.set_output_type(Target::ACTION_FOREACH); @@ -278,10 +288,9 @@ TEST(NinjaActionTargetWriter, ForEachWithDepfile) { target.action_values().set_script(SourceFile("//foo/script.py")); target.SetToolchain(setup.toolchain()); - target.OnResolved(); + ASSERT_TRUE(target.OnResolved(&err)); SubstitutionPattern depfile; - Err err; ASSERT_TRUE( depfile.Parse("//out/Debug/gen/{{source_name_part}}.d", NULL, &err)); target.action_values().set_depfile(depfile); diff --git a/tools/gn/ninja_binary_target_writer_unittest.cc b/tools/gn/ninja_binary_target_writer_unittest.cc index 369e6324785c01..e78c896ef207db 100644 --- a/tools/gn/ninja_binary_target_writer_unittest.cc +++ b/tools/gn/ninja_binary_target_writer_unittest.cc @@ -11,6 +11,8 @@ TEST(NinjaBinaryTargetWriter, SourceSet) { TestWithScope setup; + Err err; + setup.build_settings()->SetBuildDir(SourceDir("//out/Debug/")); setup.settings()->set_target_os(Settings::WIN); @@ -23,7 +25,7 @@ TEST(NinjaBinaryTargetWriter, SourceSet) { target.sources().push_back(SourceFile("//foo/input3.o")); target.sources().push_back(SourceFile("//foo/input4.obj")); target.SetToolchain(setup.toolchain()); - target.OnResolved(); + ASSERT_TRUE(target.OnResolved(&err)); // Source set itself. { @@ -57,7 +59,7 @@ TEST(NinjaBinaryTargetWriter, SourceSet) { shlib_target.set_output_type(Target::SHARED_LIBRARY); shlib_target.deps().push_back(LabelTargetPair(&target)); shlib_target.SetToolchain(setup.toolchain()); - shlib_target.OnResolved(); + ASSERT_TRUE(shlib_target.OnResolved(&err)); { std::ostringstream out; @@ -94,7 +96,7 @@ TEST(NinjaBinaryTargetWriter, SourceSet) { stlib_target.set_output_type(Target::STATIC_LIBRARY); stlib_target.deps().push_back(LabelTargetPair(&target)); stlib_target.SetToolchain(setup.toolchain()); - stlib_target.OnResolved(); + ASSERT_TRUE(stlib_target.OnResolved(&err)); { std::ostringstream out; @@ -129,6 +131,8 @@ TEST(NinjaBinaryTargetWriter, SourceSet) { // are applied. TEST(NinjaBinaryTargetWriter, ProductExtensionAndInputDeps) { TestWithScope setup; + Err err; + setup.build_settings()->SetBuildDir(SourceDir("//out/Debug/")); setup.settings()->set_target_os(Settings::LINUX); @@ -136,7 +140,7 @@ TEST(NinjaBinaryTargetWriter, ProductExtensionAndInputDeps) { Target action(setup.settings(), Label(SourceDir("//foo/"), "action")); action.set_output_type(Target::ACTION_FOREACH); action.SetToolchain(setup.toolchain()); - action.OnResolved(); + ASSERT_TRUE(action.OnResolved(&err)); // A shared library w/ the product_extension set to a custom value. Target target(setup.settings(), Label(SourceDir("//foo/"), "shlib")); @@ -146,7 +150,7 @@ TEST(NinjaBinaryTargetWriter, ProductExtensionAndInputDeps) { target.sources().push_back(SourceFile("//foo/input2.cc")); target.deps().push_back(LabelTargetPair(&action)); target.SetToolchain(setup.toolchain()); - target.OnResolved(); + ASSERT_TRUE(target.OnResolved(&err)); std::ostringstream out; NinjaBinaryTargetWriter writer(&target, out); @@ -185,6 +189,8 @@ TEST(NinjaBinaryTargetWriter, ProductExtensionAndInputDeps) { TEST(NinjaBinaryTargetWriter, EmptyProductExtension) { TestWithScope setup; + Err err; + setup.build_settings()->SetBuildDir(SourceDir("//out/Debug/")); setup.settings()->set_target_os(Settings::LINUX); @@ -197,7 +203,7 @@ TEST(NinjaBinaryTargetWriter, EmptyProductExtension) { target.sources().push_back(SourceFile("//foo/input2.cc")); target.SetToolchain(setup.toolchain()); - target.OnResolved(); + ASSERT_TRUE(target.OnResolved(&err)); std::ostringstream out; NinjaBinaryTargetWriter writer(&target, out); diff --git a/tools/gn/ninja_copy_target_writer_unittest.cc b/tools/gn/ninja_copy_target_writer_unittest.cc index 2e2169361cbdce..56942db2f84165 100644 --- a/tools/gn/ninja_copy_target_writer_unittest.cc +++ b/tools/gn/ninja_copy_target_writer_unittest.cc @@ -13,6 +13,7 @@ // Tests mutliple files with an output pattern and no toolchain dependency. TEST(NinjaCopyTargetWriter, Run) { TestWithScope setup; + Err err; setup.settings()->set_target_os(Settings::LINUX); setup.build_settings()->SetBuildDir(SourceDir("//out/Debug/")); @@ -26,7 +27,7 @@ TEST(NinjaCopyTargetWriter, Run) { SubstitutionList::MakeForTest("//out/Debug/{{source_name_part}}.out"); target.SetToolchain(setup.toolchain()); - target.OnResolved(); + ASSERT_TRUE(target.OnResolved(&err)); std::ostringstream out; NinjaCopyTargetWriter writer(&target, out); @@ -44,6 +45,7 @@ TEST(NinjaCopyTargetWriter, Run) { // Tests a single file with no output pattern. TEST(NinjaCopyTargetWriter, ToolchainDeps) { TestWithScope setup; + Err err; setup.settings()->set_target_os(Settings::LINUX); setup.build_settings()->SetBuildDir(SourceDir("//out/Debug/")); @@ -56,7 +58,7 @@ TEST(NinjaCopyTargetWriter, ToolchainDeps) { SubstitutionList::MakeForTest("//out/Debug/output.out"); target.SetToolchain(setup.toolchain()); - target.OnResolved(); + ASSERT_TRUE(target.OnResolved(&err)); std::ostringstream out; NinjaCopyTargetWriter writer(&target, out); diff --git a/tools/gn/ninja_target_writer_unittest.cc b/tools/gn/ninja_target_writer_unittest.cc index 67b55e185649d0..08cde8fccdffbb 100644 --- a/tools/gn/ninja_target_writer_unittest.cc +++ b/tools/gn/ninja_target_writer_unittest.cc @@ -32,6 +32,7 @@ class TestingNinjaTargetWriter : public NinjaTargetWriter { TEST(NinjaTargetWriter, WriteInputDepsStampAndGetDep) { TestWithScope setup; + Err err; // Make a base target that's a hard dep (action). Target base_target(setup.settings(), Label(SourceDir("//foo/"), "base")); @@ -57,9 +58,9 @@ TEST(NinjaTargetWriter, WriteInputDepsStampAndGetDep) { action.sources().push_back(SourceFile("//foo/action_source.txt")); action.deps().push_back(LabelTargetPair(&target)); - base_target.OnResolved(); - target.OnResolved(); - action.OnResolved(); + ASSERT_TRUE(base_target.OnResolved(&err)); + ASSERT_TRUE(target.OnResolved(&err)); + ASSERT_TRUE(action.OnResolved(&err)); // Input deps for the base (should be only the script itself). { @@ -105,6 +106,7 @@ TEST(NinjaTargetWriter, WriteInputDepsStampAndGetDep) { // Tests WriteInputDepsStampAndGetDep when toolchain deps are present. TEST(NinjaTargetWriter, WriteInputDepsStampAndGetDepWithToolchainDeps) { TestWithScope setup; + Err err; // Toolchain dependency. Here we make a target in the same toolchain for // simplicity, but in real life (using the Builder) this would be rejected @@ -114,14 +116,14 @@ TEST(NinjaTargetWriter, WriteInputDepsStampAndGetDepWithToolchainDeps) { Label(SourceDir("//foo/"), "setup")); toolchain_dep_target.set_output_type(Target::ACTION); toolchain_dep_target.SetToolchain(setup.toolchain()); - toolchain_dep_target.OnResolved(); + ASSERT_TRUE(toolchain_dep_target.OnResolved(&err)); setup.toolchain()->deps().push_back(LabelTargetPair(&toolchain_dep_target)); // Make a binary target Target target(setup.settings(), Label(SourceDir("//foo/"), "target")); target.set_output_type(Target::EXECUTABLE); target.SetToolchain(setup.toolchain()); - target.OnResolved(); + ASSERT_TRUE(target.OnResolved(&err)); std::ostringstream stream; TestingNinjaTargetWriter writer(&target, setup.toolchain(), stream); diff --git a/tools/gn/substitution_writer_unittest.cc b/tools/gn/substitution_writer_unittest.cc index fc6cc74f9a9fc4..6542f77e0bc3bd 100644 --- a/tools/gn/substitution_writer_unittest.cc +++ b/tools/gn/substitution_writer_unittest.cc @@ -173,11 +173,12 @@ TEST(SubstutitionWriter, SourceSubstitutions) { TEST(SubstitutionWriter, TargetSubstitutions) { TestWithScope setup; + Err err; Target target(setup.settings(), Label(SourceDir("//foo/bar/"), "baz")); target.set_output_type(Target::STATIC_LIBRARY); target.SetToolchain(setup.toolchain()); - target.OnResolved(); + ASSERT_TRUE(target.OnResolved(&err)); std::string result; EXPECT_TRUE(SubstitutionWriter::GetTargetSubstitution( @@ -207,11 +208,12 @@ TEST(SubstitutionWriter, TargetSubstitutions) { TEST(SubstitutionWriter, CompilerSubstitutions) { TestWithScope setup; + Err err; Target target(setup.settings(), Label(SourceDir("//foo/bar/"), "baz")); target.set_output_type(Target::STATIC_LIBRARY); target.SetToolchain(setup.toolchain()); - target.OnResolved(); + ASSERT_TRUE(target.OnResolved(&err)); // The compiler substitution is just source + target combined. So test one // of each of those classes of things to make sure this is hooked up. @@ -227,11 +229,12 @@ TEST(SubstitutionWriter, CompilerSubstitutions) { TEST(SubstitutionWriter, LinkerSubstitutions) { TestWithScope setup; + Err err; Target target(setup.settings(), Label(SourceDir("//foo/bar/"), "baz")); target.set_output_type(Target::SHARED_LIBRARY); target.SetToolchain(setup.toolchain()); - target.OnResolved(); + ASSERT_TRUE(target.OnResolved(&err)); const Tool* tool = setup.toolchain()->GetToolForTargetFinalOutput(&target); @@ -246,7 +249,6 @@ TEST(SubstitutionWriter, LinkerSubstitutions) { // Test that we handle paths that end up in the root build dir properly // (no leading "./" or "/"). - Err err; SubstitutionPattern pattern; ASSERT_TRUE( pattern.Parse("{{root_out_dir}}/{{target_output_name}}.so", NULL, &err)); diff --git a/tools/gn/target.cc b/tools/gn/target.cc index 58743995b01b11..592cceac8b4958 100644 --- a/tools/gn/target.cc +++ b/tools/gn/target.cc @@ -39,6 +39,33 @@ void MergeAllDependentConfigsFrom(const Target* from_target, } } +Err MakeTestOnlyError(const Target* from, const Target* to) { + return Err(from->defined_from(), "Test-only dependency not allowed.", + from->label().GetUserVisibleName(false) + "\n" + "which is NOT marked testonly can't depend on\n" + + to->label().GetUserVisibleName(false) + "\n" + "which is marked testonly. Only targets with \"testonly = true\"\n" + "can depend on other test-only targets.\n" + "\n" + "Either mark it test-only or don't do this dependency."); +} + +// Inserts the given groups dependencies, starting at the given index of the +// given vector. Returns the number of items inserted. +size_t InsertGroupDeps(LabelTargetVector* vector, + size_t insert_at, + const Target* group) { + const LabelTargetVector& deps = group->deps(); + vector->insert(vector->begin() + insert_at, deps.begin(), deps.end()); + + // Clear the origin of each of the insertions. This marks these dependencies + // as internally generated. + for (size_t i = insert_at; i < deps.size() + insert_at; i++) + (*vector)[i].origin = NULL; + + return deps.size(); +} + } // namespace Target::Target(const Settings* settings, const Label& label) @@ -46,6 +73,7 @@ Target::Target(const Settings* settings, const Label& label) output_type_(UNKNOWN), all_headers_public_(true), check_includes_(true), + testonly_(false), hard_dep_(false), toolchain_(NULL) { } @@ -87,22 +115,11 @@ const Target* Target::AsTarget() const { return this; } -void Target::OnResolved() { +bool Target::OnResolved(Err* err) { DCHECK(output_type_ != UNKNOWN); DCHECK(toolchain_) << "Toolchain should have been set before resolving."; - // Convert any groups we depend on to just direct dependencies on that - // group's deps. We insert the new deps immediately after the group so that - // the ordering is preserved. We need to keep the original group so that any - // flags, etc. that it specifies itself are applied to us. - for (size_t i = 0; i < deps_.size(); i++) { - const Target* dep = deps_[i].ptr; - if (dep->output_type_ == GROUP) { - // TODO(brettw) bug 403488 this should also handle datadeps. - deps_.insert(deps_.begin() + i + 1, dep->deps_.begin(), dep->deps_.end()); - i += dep->deps_.size(); - } - } + ExpandGroups(); // Copy our own dependent configs to the list of configs applying to us. configs_.Append(all_dependent_configs_.begin(), all_dependent_configs_.end()); @@ -130,6 +147,13 @@ void Target::OnResolved() { PullRecursiveHardDeps(); FillOutputFiles(); + + if (!CheckVisibility(err)) + return false; + if (!CheckTestonly(err)) + return false; + + return true; } bool Target::IsLinkable() const { @@ -183,6 +207,19 @@ bool Target::SetToolchain(const Toolchain* toolchain, Err* err) { return false; } +void Target::ExpandGroups() { + // Convert any groups we depend on to just direct dependencies on that + // group's deps. We insert the new deps immediately after the group so that + // the ordering is preserved. We need to keep the original group so that any + // flags, etc. that it specifies itself are applied to us. + // TODO(brettw) bug 403488 this should also handle datadeps. + for (size_t i = 0; i < deps_.size(); i++) { + const Target* dep = deps_[i].ptr; + if (dep->output_type_ == GROUP) + i += InsertGroupDeps(&deps_, i + 1, dep); + } +} + void Target::PullDependentTargetInfo() { // Gather info from our dependents we need. for (size_t dep_i = 0; dep_i < deps_.size(); dep_i++) { @@ -306,3 +343,48 @@ void Target::FillOutputFiles() { NOTREACHED(); } } + +bool Target::CheckVisibility(Err* err) const { + // Only check visibility when the origin of the dependency is non-null. These + // are dependencies added by the GN files. Internally added dependencies + // (expanded groups) will have a null origin. We don't want to check + // visibility for these, since the point of a group would often be to + // forward visibility. + for (size_t i = 0; i < deps_.size(); i++) { + if (deps_[i].origin && + !Visibility::CheckItemVisibility(this, deps_[i].ptr, err)) + return false; + } + + for (size_t i = 0; i < datadeps_.size(); i++) { + if (deps_[i].origin && + !Visibility::CheckItemVisibility(this, datadeps_[i].ptr, err)) + return false; + } + + return true; +} + +bool Target::CheckTestonly(Err* err) const { + // If there current target is marked testonly, it can include both testonly + // and non-testonly targets, so there's nothing to check. + if (testonly()) + return true; + + // Verify no deps have "testonly" set. + for (size_t i = 0; i < deps_.size(); i++) { + if (deps_[i].ptr->testonly()) { + *err = MakeTestOnlyError(this, deps_[i].ptr); + return false; + } + } + + for (size_t i = 0; i < datadeps_.size(); i++) { + if (datadeps_[i].ptr->testonly()) { + *err = MakeTestOnlyError(this, datadeps_[i].ptr); + return false; + } + } + + return true; +} diff --git a/tools/gn/target.h b/tools/gn/target.h index aff7706cbe7105..bdb3de52073ff8 100644 --- a/tools/gn/target.h +++ b/tools/gn/target.h @@ -53,7 +53,7 @@ class Target : public Item { // Item overrides. virtual Target* AsTarget() OVERRIDE; virtual const Target* AsTarget() const OVERRIDE; - virtual void OnResolved() OVERRIDE; + virtual bool OnResolved(Err* err) OVERRIDE; OutputType output_type() const { return output_type_; } void set_output_type(OutputType t) { output_type_ = t; } @@ -95,6 +95,9 @@ class Target : public Item { bool check_includes() const { return check_includes_; } void set_check_includes(bool ci) { check_includes_ = ci; } + bool testonly() const { return testonly_; } + void set_testonly(bool value) { testonly_ = value; } + // Compile-time extra dependencies. const FileList& inputs() const { return inputs_; } FileList& inputs() { return inputs_; } @@ -210,6 +213,8 @@ class Target : public Item { } private: + void ExpandGroups(); + // Pulls necessary information from dependencies to this one when all // dependencies have been resolved. void PullDependentTargetInfo(); @@ -222,6 +227,10 @@ class Target : public Item { // Fills the link and dependency output files when a target is resolved. void FillOutputFiles(); + // Validates the given thing when a target is resolved. + bool CheckVisibility(Err* err) const; + bool CheckTestonly(Err* err) const; + OutputType output_type_; std::string output_name_; std::string output_extension_; @@ -230,6 +239,7 @@ class Target : public Item { bool all_headers_public_; FileList public_headers_; bool check_includes_; + bool testonly_; FileList inputs_; FileList data_; diff --git a/tools/gn/target_generator.cc b/tools/gn/target_generator.cc index 657581ac016656..02c055e336e748 100644 --- a/tools/gn/target_generator.cc +++ b/tools/gn/target_generator.cc @@ -48,6 +48,10 @@ void TargetGenerator::Run() { if (err_->has_error()) return; + FillTestonly(); + if (err_->has_error()) + return; + if (!Visibility::FillItemVisibility(target_, scope_, err_)) return; @@ -217,6 +221,15 @@ void TargetGenerator::FillDependencies() { return; } +void TargetGenerator::FillTestonly() { + const Value* value = scope_->GetValue(variables::kTestonly, true); + if (value) { + if (!value->VerifyTypeIs(Value::BOOLEAN, err_)) + return; + target_->set_testonly(value->boolean_value()); + } +} + void TargetGenerator::FillOutputs(bool allow_substitutions) { const Value* value = scope_->GetValue(variables::kOutputs, true); if (!value) diff --git a/tools/gn/target_generator.h b/tools/gn/target_generator.h index fce599fff64e14..c09567065b18bf 100644 --- a/tools/gn/target_generator.h +++ b/tools/gn/target_generator.h @@ -70,6 +70,7 @@ class TargetGenerator { void FillDependentConfigs(); // Includes all types of dependent configs. void FillData(); void FillDependencies(); // Includes data dependencies. + void FillTestonly(); // Reads configs/deps from the given var name, and uses the given setting on // the target to save them. diff --git a/tools/gn/target_unittest.cc b/tools/gn/target_unittest.cc index 9bf0c30df28b3d..25e3aabc4578cf 100644 --- a/tools/gn/target_unittest.cc +++ b/tools/gn/target_unittest.cc @@ -14,37 +14,47 @@ // deps. TEST(Target, GroupDeps) { TestWithScope setup; + Err err; + + // Used as the origin for the not-automatically-set deps. + IdentifierNode origin; // Two low-level targets. Target x(setup.settings(), Label(SourceDir("//component/"), "x")); x.set_output_type(Target::STATIC_LIBRARY); x.SetToolchain(setup.toolchain()); - x.OnResolved(); + ASSERT_TRUE(x.OnResolved(&err)); Target y(setup.settings(), Label(SourceDir("//component/"), "y")); y.set_output_type(Target::STATIC_LIBRARY); y.SetToolchain(setup.toolchain()); - y.OnResolved(); + ASSERT_TRUE(y.OnResolved(&err)); // Make a group for both x and y. Target g(setup.settings(), Label(SourceDir("//group/"), "g")); g.set_output_type(Target::GROUP); + g.visibility().SetPublic(); g.deps().push_back(LabelTargetPair(&x)); + g.deps()[0].origin = &origin; g.deps().push_back(LabelTargetPair(&y)); + g.deps()[1].origin = &origin; // Random placeholder target so we can see the group's deps get inserted at // the right place. Target b(setup.settings(), Label(SourceDir("//app/"), "b")); b.set_output_type(Target::STATIC_LIBRARY); b.SetToolchain(setup.toolchain()); - b.OnResolved(); + b.visibility().SetPublic(); + ASSERT_TRUE(b.OnResolved(&err)); // Make a target depending on the group and "b". OnResolved will expand. Target a(setup.settings(), Label(SourceDir("//app/"), "a")); a.set_output_type(Target::EXECUTABLE); a.deps().push_back(LabelTargetPair(&g)); + a.deps()[0].origin = &origin; a.deps().push_back(LabelTargetPair(&b)); + a.deps()[1].origin = &origin; a.SetToolchain(setup.toolchain()); - a.OnResolved(); + ASSERT_TRUE(a.OnResolved(&err)); // The group's deps should be inserted after the group itself in the deps // list, so we should get "g, x, y, b" @@ -53,12 +63,20 @@ TEST(Target, GroupDeps) { EXPECT_EQ(&x, a.deps()[1].ptr); EXPECT_EQ(&y, a.deps()[2].ptr); EXPECT_EQ(&b, a.deps()[3].ptr); + + // The "regular" deps on a should have the origin set. The automatically + // expanded ones will have a null origin so we know they are generated. + EXPECT_EQ(&origin, a.deps()[0].origin); + EXPECT_EQ(NULL, a.deps()[1].origin); + EXPECT_EQ(NULL, a.deps()[2].origin); + EXPECT_EQ(&origin, a.deps()[3].origin); } // Tests that lib[_dir]s are inherited across deps boundaries for static // libraries but not executables. TEST(Target, LibInheritance) { TestWithScope setup; + Err err; const std::string lib("foo"); const SourceDir libdir("/foo_dir/"); @@ -69,7 +87,7 @@ TEST(Target, LibInheritance) { z.config_values().libs().push_back(lib); z.config_values().lib_dirs().push_back(libdir); z.SetToolchain(setup.toolchain()); - z.OnResolved(); + ASSERT_TRUE(z.OnResolved(&err)); // All lib[_dir]s should be set when target is resolved. ASSERT_EQ(1u, z.all_libs().size()); @@ -87,7 +105,7 @@ TEST(Target, LibInheritance) { shared.config_values().lib_dirs().push_back(second_libdir); shared.deps().push_back(LabelTargetPair(&z)); shared.SetToolchain(setup.toolchain()); - shared.OnResolved(); + ASSERT_TRUE(shared.OnResolved(&err)); ASSERT_EQ(2u, shared.all_libs().size()); EXPECT_EQ(second_lib, shared.all_libs()[0]); @@ -101,7 +119,7 @@ TEST(Target, LibInheritance) { exec.set_output_type(Target::EXECUTABLE); exec.deps().push_back(LabelTargetPair(&shared)); exec.SetToolchain(setup.toolchain()); - exec.OnResolved(); + ASSERT_TRUE(exec.OnResolved(&err)); EXPECT_EQ(0u, exec.all_libs().size()); EXPECT_EQ(0u, exec.all_lib_dirs().size()); } @@ -110,6 +128,7 @@ TEST(Target, LibInheritance) { // forward_dependent_configs_from TEST(Target, DependentConfigs) { TestWithScope setup; + Err err; // Set up a dependency chain of a -> b -> c Target a(setup.settings(), Label(SourceDir("//foo/"), "a")); @@ -136,9 +155,9 @@ TEST(Target, DependentConfigs) { Config direct(setup.settings(), Label(SourceDir("//foo/"), "direct")); c.direct_dependent_configs().push_back(LabelConfigPair(&direct)); - c.OnResolved(); - b.OnResolved(); - a.OnResolved(); + ASSERT_TRUE(c.OnResolved(&err)); + ASSERT_TRUE(b.OnResolved(&err)); + ASSERT_TRUE(a.OnResolved(&err)); // B should have gotten both dependent configs from C. ASSERT_EQ(2u, b.configs().size()); @@ -163,8 +182,8 @@ TEST(Target, DependentConfigs) { b_fwd.deps().push_back(LabelTargetPair(&c)); b_fwd.forward_dependent_configs().push_back(LabelTargetPair(&c)); - b_fwd.OnResolved(); - a_fwd.OnResolved(); + ASSERT_TRUE(b_fwd.OnResolved(&err)); + ASSERT_TRUE(a_fwd.OnResolved(&err)); // A_fwd should now have both configs. ASSERT_EQ(2u, a_fwd.configs().size()); @@ -178,6 +197,7 @@ TEST(Target, DependentConfigs) { // group's deps' dependent configs. TEST(Target, ForwardDependentConfigsFromGroups) { TestWithScope setup; + Err err; Target a(setup.settings(), Label(SourceDir("//foo/"), "a")); a.set_output_type(Target::EXECUTABLE); @@ -198,9 +218,9 @@ TEST(Target, ForwardDependentConfigsFromGroups) { // A forwards the dependent configs from B. a.forward_dependent_configs().push_back(LabelTargetPair(&b)); - c.OnResolved(); - b.OnResolved(); - a.OnResolved(); + ASSERT_TRUE(c.OnResolved(&err)); + ASSERT_TRUE(b.OnResolved(&err)); + ASSERT_TRUE(a.OnResolved(&err)); // The config should now be on A, and in A's direct dependent configs. ASSERT_EQ(1u, a.configs().size()); @@ -211,6 +231,7 @@ TEST(Target, ForwardDependentConfigsFromGroups) { TEST(Target, InheritLibs) { TestWithScope setup; + Err err; // Create a dependency chain: // A (executable) -> B (shared lib) -> C (static lib) -> D (source set) @@ -230,10 +251,10 @@ TEST(Target, InheritLibs) { b.deps().push_back(LabelTargetPair(&c)); c.deps().push_back(LabelTargetPair(&d)); - d.OnResolved(); - c.OnResolved(); - b.OnResolved(); - a.OnResolved(); + ASSERT_TRUE(d.OnResolved(&err)); + ASSERT_TRUE(c.OnResolved(&err)); + ASSERT_TRUE(b.OnResolved(&err)); + ASSERT_TRUE(a.OnResolved(&err)); // C should have D in its inherited libs. const UniqueVector& c_inherited = c.inherited_libraries(); @@ -255,13 +276,14 @@ TEST(Target, InheritLibs) { TEST(Target, GetComputedOutputName) { TestWithScope setup; + Err err; // Basic target with no prefix (executable type tool in the TestWithScope has // no prefix) or output name. Target basic(setup.settings(), Label(SourceDir("//foo/"), "bar")); basic.set_output_type(Target::EXECUTABLE); basic.SetToolchain(setup.toolchain()); - basic.OnResolved(); + ASSERT_TRUE(basic.OnResolved(&err)); EXPECT_EQ("bar", basic.GetComputedOutputName(false)); EXPECT_EQ("bar", basic.GetComputedOutputName(true)); @@ -270,7 +292,7 @@ TEST(Target, GetComputedOutputName) { with_name.set_output_type(Target::EXECUTABLE); with_name.set_output_name("myoutput"); with_name.SetToolchain(setup.toolchain()); - with_name.OnResolved(); + ASSERT_TRUE(with_name.OnResolved(&err)); EXPECT_EQ("myoutput", with_name.GetComputedOutputName(false)); EXPECT_EQ("myoutput", with_name.GetComputedOutputName(true)); @@ -279,7 +301,7 @@ TEST(Target, GetComputedOutputName) { Target with_prefix(setup.settings(), Label(SourceDir("//foo/"), "bar")); with_prefix.set_output_type(Target::STATIC_LIBRARY); with_prefix.SetToolchain(setup.toolchain()); - with_prefix.OnResolved(); + ASSERT_TRUE(with_prefix.OnResolved(&err)); EXPECT_EQ("bar", with_prefix.GetComputedOutputName(false)); EXPECT_EQ("libbar", with_prefix.GetComputedOutputName(true)); @@ -289,7 +311,94 @@ TEST(Target, GetComputedOutputName) { dup_prefix.set_output_type(Target::STATIC_LIBRARY); dup_prefix.set_output_name("libbar"); dup_prefix.SetToolchain(setup.toolchain()); - dup_prefix.OnResolved(); + ASSERT_TRUE(dup_prefix.OnResolved(&err)); EXPECT_EQ("libbar", dup_prefix.GetComputedOutputName(false)); EXPECT_EQ("libbar", dup_prefix.GetComputedOutputName(true)); } + +// Test visibility failure case. +TEST(Target, VisibilityFails) { + TestWithScope setup; + Err err; + + Target b(setup.settings(), Label(SourceDir("//private/"), "b")); + b.set_output_type(Target::STATIC_LIBRARY); + b.SetToolchain(setup.toolchain()); + b.visibility().SetPrivate(b.label().dir()); + ASSERT_TRUE(b.OnResolved(&err)); + + // Make a target depending on "b". The dependency must have an origin to mark + // it as user-set so we check visibility. This check should fail. + Target a(setup.settings(), Label(SourceDir("//app/"), "a")); + a.set_output_type(Target::EXECUTABLE); + a.deps().push_back(LabelTargetPair(&b)); + IdentifierNode origin; // Dummy origin. + a.deps()[0].origin = &origin; + a.SetToolchain(setup.toolchain()); + ASSERT_FALSE(a.OnResolved(&err)); +} + +// Tests that A -> Group -> B where the group is visible from A but B isn't, +// passes visibility even though the group's deps get expanded into A. +TEST(Target, VisibilityGroup) { + TestWithScope setup; + Err err; + + IdentifierNode origin; // Dummy origin. + + // B has private visibility. This lets the group see it since the group is in + // the same directory. + Target b(setup.settings(), Label(SourceDir("//private/"), "b")); + b.set_output_type(Target::STATIC_LIBRARY); + b.SetToolchain(setup.toolchain()); + b.visibility().SetPrivate(b.label().dir()); + ASSERT_TRUE(b.OnResolved(&err)); + + // The group has public visibility and depends on b. + Target g(setup.settings(), Label(SourceDir("//private/"), "g")); + g.set_output_type(Target::GROUP); + g.SetToolchain(setup.toolchain()); + g.deps().push_back(LabelTargetPair(&b)); + g.deps()[0].origin = &origin; + g.visibility().SetPublic(); + ASSERT_TRUE(b.OnResolved(&err)); + + // Make a target depending on "g". This should succeed. + Target a(setup.settings(), Label(SourceDir("//app/"), "a")); + a.set_output_type(Target::EXECUTABLE); + a.deps().push_back(LabelTargetPair(&g)); + a.deps()[0].origin = &origin; + a.SetToolchain(setup.toolchain()); + ASSERT_TRUE(a.OnResolved(&err)); +} + +// Verifies that only testonly targets can depend on other testonly targets. +// Many of the above dependency checking cases covered the non-testonly +// case. +TEST(Target, Testonly) { + TestWithScope setup; + Err err; + + // "testlib" is a test-only library. + Target testlib(setup.settings(), Label(SourceDir("//test/"), "testlib")); + testlib.set_testonly(true); + testlib.set_output_type(Target::STATIC_LIBRARY); + testlib.SetToolchain(setup.toolchain()); + ASSERT_TRUE(testlib.OnResolved(&err)); + + // "test" is a test-only executable depending on testlib, this is OK. + Target test(setup.settings(), Label(SourceDir("//test/"), "test")); + test.set_testonly(true); + test.set_output_type(Target::EXECUTABLE); + test.deps().push_back(LabelTargetPair(&testlib)); + test.SetToolchain(setup.toolchain()); + ASSERT_TRUE(test.OnResolved(&err)); + + // "product" is a non-test depending on testlib. This should fail. + Target product(setup.settings(), Label(SourceDir("//app/"), "product")); + product.set_testonly(false); + product.set_output_type(Target::EXECUTABLE); + product.deps().push_back(LabelTargetPair(&testlib)); + product.SetToolchain(setup.toolchain()); + ASSERT_FALSE(product.OnResolved(&err)); +} diff --git a/tools/gn/variables.cc b/tools/gn/variables.cc index 1bf301af1c90f6..be758bda6f412d 100644 --- a/tools/gn/variables.cc +++ b/tools/gn/variables.cc @@ -795,6 +795,28 @@ const char kSources_Help[] = "\n" " A list of files relative to the current buildfile.\n"; +const char kTestonly[] = "testonly"; +const char kTestonly_HelpShort[] = + "testonly: [boolean] Declares a target must only be used for testing."; +const char kTestonly_Help[] = + "testonly: Declares a target must only be used for testing.\n" + "\n" + " Boolean. Defaults to false.\n" + "\n" + " When a target is marked \"testonly = true\", it must only be depended\n" + " on by other test-only targets. Otherwise, GN will issue an error\n" + " that the depenedency is not allowed.\n" + "\n" + " This feature is intended to prevent accidentally shipping test code\n" + " in a final product.\n" + "\n" + "Example\n" + "\n" + " source_set(\"test_support\") {\n" + " testonly = true\n" + " ...\n" + " }\n"; + const char kVisibility[] = "visibility"; const char kVisibility_HelpShort[] = "visibility: [label list] A list of labels that can depend on a target."; @@ -915,6 +937,7 @@ const VariableInfoMap& GetTargetVariables() { INSERT_VARIABLE(Public) INSERT_VARIABLE(Script) INSERT_VARIABLE(Sources) + INSERT_VARIABLE(Testonly) INSERT_VARIABLE(Visibility) } return info_map; diff --git a/tools/gn/variables.h b/tools/gn/variables.h index 4d32bb280cb04d..4a219fb2067312 100644 --- a/tools/gn/variables.h +++ b/tools/gn/variables.h @@ -175,6 +175,10 @@ extern const char kSources[]; extern const char kSources_HelpShort[]; extern const char kSources_Help[]; +extern const char kTestonly[]; +extern const char kTestonly_HelpShort[]; +extern const char kTestonly_Help[]; + extern const char kVisibility[]; extern const char kVisibility_HelpShort[]; extern const char kVisibility_Help[];