| // Copyright (c) 2013 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <algorithm> |
| #include <sstream> |
| |
| #include "tools/gn/ninja_action_target_writer.h" |
| #include "tools/gn/pool.h" |
| #include "tools/gn/substitution_list.h" |
| #include "tools/gn/target.h" |
| #include "tools/gn/test_with_scope.h" |
| #include "util/build_config.h" |
| #include "util/test/test.h" |
| |
| TEST(NinjaActionTargetWriter, WriteOutputFilesForBuildLine) { |
| Err err; |
| TestWithScope setup; |
| |
| Target target(setup.settings(), Label(SourceDir("//foo/"), "bar")); |
| target.set_output_type(Target::ACTION_FOREACH); |
| target.action_values().outputs() = |
| SubstitutionList::MakeForTest("//out/Debug/gen/a b{{source_name_part}}.h", |
| "//out/Debug/gen/{{source_name_part}}.cc"); |
| |
| target.SetToolchain(setup.toolchain()); |
| ASSERT_TRUE(target.OnResolved(&err)); |
| |
| std::ostringstream out; |
| NinjaActionTargetWriter writer(&target, out); |
| |
| SourceFile source("//foo/bar.in"); |
| std::vector<OutputFile> output_files; |
| writer.WriteOutputFilesForBuildLine(source, &output_files); |
| |
| EXPECT_EQ(" gen/a$ bbar.h gen/bar.cc", out.str()); |
| } |
| |
| // Tests an action with no sources. |
| TEST(NinjaActionTargetWriter, ActionNoSources) { |
| Err err; |
| TestWithScope setup; |
| |
| Target target(setup.settings(), Label(SourceDir("//foo/"), "bar")); |
| target.set_output_type(Target::ACTION); |
| |
| target.action_values().set_script(SourceFile("//foo/script.py")); |
| target.config_values().inputs().push_back(SourceFile("//foo/included.txt")); |
| |
| target.action_values().outputs() = |
| SubstitutionList::MakeForTest("//out/Debug/foo.out"); |
| |
| target.SetToolchain(setup.toolchain()); |
| ASSERT_TRUE(target.OnResolved(&err)); |
| |
| setup.build_settings()->set_python_path( |
| base::FilePath(FILE_PATH_LITERAL("/usr/bin/python"))); |
| |
| std::ostringstream out; |
| NinjaActionTargetWriter writer(&target, out); |
| writer.Run(); |
| |
| const char* expected = 1 /* skip initial newline */ + R"( |
| rule __foo_bar___rule |
| command = /usr/bin/python ../../foo/script.py |
| description = ACTION //foo:bar() |
| restat = 1 |
| |
| build foo.out: __foo_bar___rule | ../../foo/script.py ../../foo/included.txt |
| |
| build obj/foo/bar.stamp: stamp foo.out |
| )"; |
| EXPECT_EQ(expected, out.str()); |
| } |
| |
| // Tests an action with no sources and pool |
| TEST(NinjaActionTargetWriter, ActionNoSourcesConsole) { |
| Err err; |
| TestWithScope setup; |
| |
| Target target(setup.settings(), Label(SourceDir("//foo/"), "bar")); |
| target.set_output_type(Target::ACTION); |
| |
| target.action_values().set_script(SourceFile("//foo/script.py")); |
| target.config_values().inputs().push_back(SourceFile("//foo/included.txt")); |
| |
| target.action_values().outputs() = |
| SubstitutionList::MakeForTest("//out/Debug/foo.out"); |
| |
| Pool pool(setup.settings(), |
| Label(SourceDir("//"), "console", setup.toolchain()->label().dir(), |
| setup.toolchain()->label().name())); |
| pool.set_depth(1); |
| target.action_values().set_pool(LabelPtrPair<Pool>(&pool)); |
| |
| target.SetToolchain(setup.toolchain()); |
| ASSERT_TRUE(target.OnResolved(&err)); |
| |
| setup.build_settings()->set_python_path( |
| base::FilePath(FILE_PATH_LITERAL("/usr/bin/python"))); |
| |
| std::ostringstream out; |
| NinjaActionTargetWriter writer(&target, out); |
| writer.Run(); |
| |
| // The console pool's name must be mapped exactly to the string "console" |
| // which is a special pre-defined pool name in ninja. |
| const char* expected = 1 /* skip initial newline */ + R"( |
| rule __foo_bar___rule |
| command = /usr/bin/python ../../foo/script.py |
| description = ACTION //foo:bar() |
| restat = 1 |
| |
| build foo.out: __foo_bar___rule | ../../foo/script.py ../../foo/included.txt |
| pool = console |
| |
| build obj/foo/bar.stamp: stamp foo.out |
| )"; |
| EXPECT_EQ(expected, out.str()); |
| } |
| |
| // Makes sure that we write sources as input dependencies for actions with |
| // both sources and inputs (ACTION_FOREACH treats the sources differently). |
| TEST(NinjaActionTargetWriter, ActionWithSources) { |
| Err err; |
| TestWithScope setup; |
| |
| Target target(setup.settings(), Label(SourceDir("//foo/"), "bar")); |
| target.set_output_type(Target::ACTION); |
| |
| target.action_values().set_script(SourceFile("//foo/script.py")); |
| |
| target.sources().push_back(SourceFile("//foo/source.txt")); |
| target.config_values().inputs().push_back(SourceFile("//foo/included.txt")); |
| |
| target.action_values().outputs() = |
| SubstitutionList::MakeForTest("//out/Debug/foo.out"); |
| |
| target.SetToolchain(setup.toolchain()); |
| ASSERT_TRUE(target.OnResolved(&err)); |
| |
| setup.build_settings()->set_python_path( |
| base::FilePath(FILE_PATH_LITERAL("/usr/bin/python"))); |
| |
| std::ostringstream out; |
| NinjaActionTargetWriter writer(&target, out); |
| writer.Run(); |
| |
| const char expected_linux[] = |
| "rule __foo_bar___rule\n" |
| " command = /usr/bin/python ../../foo/script.py\n" |
| " description = ACTION //foo:bar()\n" |
| " restat = 1\n" |
| "\n" |
| "build foo.out: __foo_bar___rule | ../../foo/script.py " |
| "../../foo/included.txt ../../foo/source.txt\n" |
| "\n" |
| "build obj/foo/bar.stamp: stamp foo.out\n"; |
| EXPECT_EQ(expected_linux, out.str()); |
| } |
| |
| TEST(NinjaActionTargetWriter, ForEach) { |
| Err err; |
| TestWithScope setup; |
| |
| // Some dependencies that the action can depend on. Use actions for these |
| // so they have a nice platform-independent stamp file that can appear in the |
| // output (rather than having to worry about how the current platform names |
| // binaries). |
| Target dep(setup.settings(), Label(SourceDir("//foo/"), "dep")); |
| dep.set_output_type(Target::ACTION); |
| dep.visibility().SetPublic(); |
| dep.SetToolchain(setup.toolchain()); |
| ASSERT_TRUE(dep.OnResolved(&err)); |
| |
| Target datadep(setup.settings(), Label(SourceDir("//foo/"), "datadep")); |
| datadep.set_output_type(Target::ACTION); |
| datadep.visibility().SetPublic(); |
| datadep.SetToolchain(setup.toolchain()); |
| ASSERT_TRUE(datadep.OnResolved(&err)); |
| |
| Target target(setup.settings(), Label(SourceDir("//foo/"), "bar")); |
| target.set_output_type(Target::ACTION_FOREACH); |
| target.private_deps().push_back(LabelTargetPair(&dep)); |
| target.data_deps().push_back(LabelTargetPair(&datadep)); |
| |
| target.sources().push_back(SourceFile("//foo/input1.txt")); |
| target.sources().push_back(SourceFile("//foo/input2.txt")); |
| |
| target.action_values().set_script(SourceFile("//foo/script.py")); |
| |
| target.action_values().args() = SubstitutionList::MakeForTest( |
| "-i", "{{source}}", "--out=foo bar{{source_name_part}}.o"); |
| target.action_values().outputs() = |
| SubstitutionList::MakeForTest("//out/Debug/{{source_name_part}}.out"); |
| |
| target.config_values().inputs().push_back(SourceFile("//foo/included.txt")); |
| |
| target.SetToolchain(setup.toolchain()); |
| ASSERT_TRUE(target.OnResolved(&err)); |
| |
| setup.build_settings()->set_python_path( |
| base::FilePath(FILE_PATH_LITERAL("/usr/bin/python"))); |
| |
| std::ostringstream out; |
| NinjaActionTargetWriter writer(&target, out); |
| writer.Run(); |
| |
| const char expected_linux[] = |
| "rule __foo_bar___rule\n" |
| " command = /usr/bin/python ../../foo/script.py -i ${in} " |
| // Escaping is different between Windows and Posix. |
| #if defined(OS_WIN) |
| "\"--out=foo$ bar${source_name_part}.o\"\n" |
| #else |
| "--out=foo\\$ bar${source_name_part}.o\n" |
| #endif |
| " description = ACTION //foo:bar()\n" |
| " restat = 1\n" |
| "build obj/foo/bar.inputdeps.stamp: stamp ../../foo/script.py " |
| "../../foo/included.txt obj/foo/dep.stamp\n" |
| "\n" |
| "build input1.out: __foo_bar___rule ../../foo/input1.txt | " |
| "obj/foo/bar.inputdeps.stamp\n" |
| " source_name_part = input1\n" |
| "build input2.out: __foo_bar___rule ../../foo/input2.txt | " |
| "obj/foo/bar.inputdeps.stamp\n" |
| " source_name_part = input2\n" |
| "\n" |
| "build obj/foo/bar.stamp: " |
| "stamp input1.out input2.out || obj/foo/datadep.stamp\n"; |
| |
| std::string out_str = out.str(); |
| #if defined(OS_WIN) |
| std::replace(out_str.begin(), out_str.end(), '\\', '/'); |
| #endif |
| EXPECT_EQ(expected_linux, out_str); |
| } |
| |
| TEST(NinjaActionTargetWriter, ForEachWithDepfile) { |
| Err err; |
| TestWithScope setup; |
| |
| Target target(setup.settings(), Label(SourceDir("//foo/"), "bar")); |
| target.set_output_type(Target::ACTION_FOREACH); |
| |
| target.sources().push_back(SourceFile("//foo/input1.txt")); |
| target.sources().push_back(SourceFile("//foo/input2.txt")); |
| |
| target.action_values().set_script(SourceFile("//foo/script.py")); |
| |
| target.SetToolchain(setup.toolchain()); |
| ASSERT_TRUE(target.OnResolved(&err)); |
| |
| SubstitutionPattern depfile; |
| ASSERT_TRUE( |
| depfile.Parse("//out/Debug/gen/{{source_name_part}}.d", nullptr, &err)); |
| target.action_values().set_depfile(depfile); |
| |
| target.action_values().args() = SubstitutionList::MakeForTest( |
| "-i", "{{source}}", "--out=foo bar{{source_name_part}}.o"); |
| target.action_values().outputs() = |
| SubstitutionList::MakeForTest("//out/Debug/{{source_name_part}}.out"); |
| |
| target.config_values().inputs().push_back(SourceFile("//foo/included.txt")); |
| |
| setup.build_settings()->set_python_path( |
| base::FilePath(FILE_PATH_LITERAL("/usr/bin/python"))); |
| |
| std::ostringstream out; |
| NinjaActionTargetWriter writer(&target, out); |
| writer.Run(); |
| |
| const char expected_linux[] = |
| "rule __foo_bar___rule\n" |
| " command = /usr/bin/python ../../foo/script.py -i ${in} " |
| #if defined(OS_WIN) |
| "\"--out=foo$ bar${source_name_part}.o\"\n" |
| #else |
| "--out=foo\\$ bar${source_name_part}.o\n" |
| #endif |
| " description = ACTION //foo:bar()\n" |
| " restat = 1\n" |
| "build obj/foo/bar.inputdeps.stamp: stamp ../../foo/script.py " |
| "../../foo/included.txt\n" |
| "\n" |
| "build input1.out: __foo_bar___rule ../../foo/input1.txt" |
| " | obj/foo/bar.inputdeps.stamp\n" |
| " source_name_part = input1\n" |
| " depfile = gen/input1.d\n" |
| "build input2.out: __foo_bar___rule ../../foo/input2.txt" |
| " | obj/foo/bar.inputdeps.stamp\n" |
| " source_name_part = input2\n" |
| " depfile = gen/input2.d\n" |
| "\n" |
| "build obj/foo/bar.stamp: stamp input1.out input2.out\n"; |
| EXPECT_EQ(expected_linux, out.str()); |
| } |
| |
| TEST(NinjaActionTargetWriter, ForEachWithResponseFile) { |
| Err err; |
| TestWithScope setup; |
| |
| Target target(setup.settings(), Label(SourceDir("//foo/"), "bar")); |
| target.set_output_type(Target::ACTION_FOREACH); |
| |
| target.sources().push_back(SourceFile("//foo/input1.txt")); |
| target.action_values().set_script(SourceFile("//foo/script.py")); |
| |
| target.SetToolchain(setup.toolchain()); |
| ASSERT_TRUE(target.OnResolved(&err)); |
| |
| // Make sure we get interesting substitutions for both the args and the |
| // response file contents. |
| target.action_values().args() = SubstitutionList::MakeForTest( |
| "{{source}}", "{{source_file_part}}", "{{response_file_name}}"); |
| target.action_values().rsp_file_contents() = |
| SubstitutionList::MakeForTest("-j", "{{source_name_part}}"); |
| target.action_values().outputs() = |
| SubstitutionList::MakeForTest("//out/Debug/{{source_name_part}}.out"); |
| |
| setup.build_settings()->set_python_path( |
| base::FilePath(FILE_PATH_LITERAL("/usr/bin/python"))); |
| |
| std::ostringstream out; |
| NinjaActionTargetWriter writer(&target, out); |
| writer.Run(); |
| |
| const char expected_linux[] = |
| "rule __foo_bar___rule\n" |
| // This name is autogenerated from the target rule name. |
| " rspfile = __foo_bar___rule.$unique_name.rsp\n" |
| // These come from rsp_file_contents above. |
| " rspfile_content = -j ${source_name_part}\n" |
| // These come from the args. |
| " command = /usr/bin/python ../../foo/script.py ${in} " |
| "${source_file_part} ${rspfile}\n" |
| " description = ACTION //foo:bar()\n" |
| " restat = 1\n" |
| "\n" |
| "build input1.out: __foo_bar___rule ../../foo/input1.txt" |
| " | ../../foo/script.py\n" |
| // Necessary for the rspfile defined in the rule. |
| " unique_name = 0\n" |
| // Substitution for the args. |
| " source_file_part = input1.txt\n" |
| // Substitution for the rspfile contents. |
| " source_name_part = input1\n" |
| "\n" |
| "build obj/foo/bar.stamp: stamp input1.out\n"; |
| EXPECT_EQ(expected_linux, out.str()); |
| } |
| |
| TEST(NinjaActionTargetWriter, ForEachWithPool) { |
| Err err; |
| TestWithScope setup; |
| |
| Target target(setup.settings(), Label(SourceDir("//foo/"), "bar")); |
| target.set_output_type(Target::ACTION_FOREACH); |
| |
| target.sources().push_back(SourceFile("//foo/input1.txt")); |
| target.action_values().set_script(SourceFile("//foo/script.py")); |
| |
| Pool pool(setup.settings(), |
| Label(SourceDir("//foo/"), "pool", setup.toolchain()->label().dir(), |
| setup.toolchain()->label().name())); |
| pool.set_depth(5); |
| target.action_values().set_pool(LabelPtrPair<Pool>(&pool)); |
| |
| target.SetToolchain(setup.toolchain()); |
| ASSERT_TRUE(target.OnResolved(&err)); |
| |
| // Make sure we get interesting substitutions for both the args and the |
| // response file contents. |
| target.action_values().args() = |
| SubstitutionList::MakeForTest("{{source}}", "{{source_file_part}}"); |
| target.action_values().outputs() = |
| SubstitutionList::MakeForTest("//out/Debug/{{source_name_part}}.out"); |
| |
| setup.build_settings()->set_python_path( |
| base::FilePath(FILE_PATH_LITERAL("/usr/bin/python"))); |
| |
| std::ostringstream out; |
| NinjaActionTargetWriter writer(&target, out); |
| writer.Run(); |
| |
| const char expected_linux[] = |
| "rule __foo_bar___rule\n" |
| // These come from the args. |
| " command = /usr/bin/python ../../foo/script.py ${in} " |
| "${source_file_part}\n" |
| " description = ACTION //foo:bar()\n" |
| " restat = 1\n" |
| "\n" |
| "build input1.out: __foo_bar___rule ../../foo/input1.txt" |
| " | ../../foo/script.py\n" |
| // Substitution for the args. |
| " source_file_part = input1.txt\n" |
| " pool = foo_pool\n" |
| "\n" |
| "build obj/foo/bar.stamp: stamp input1.out\n"; |
| EXPECT_EQ(expected_linux, out.str()); |
| } |
| |
| TEST(NinjaActionTargetWriter, NoTransitiveHardDeps) { |
| Err err; |
| TestWithScope setup; |
| |
| setup.build_settings()->set_python_path( |
| base::FilePath(FILE_PATH_LITERAL("/usr/bin/python"))); |
| |
| Target dep(setup.settings(), Label(SourceDir("//foo/"), "dep")); |
| dep.set_output_type(Target::ACTION); |
| dep.visibility().SetPublic(); |
| dep.SetToolchain(setup.toolchain()); |
| ASSERT_TRUE(dep.OnResolved(&err)); |
| |
| Target foo(setup.settings(), Label(SourceDir("//foo/"), "foo")); |
| foo.set_output_type(Target::ACTION); |
| foo.visibility().SetPublic(); |
| foo.sources().push_back(SourceFile("//foo/input1.txt")); |
| foo.action_values().set_script(SourceFile("//foo/script.py")); |
| foo.private_deps().push_back(LabelTargetPair(&dep)); |
| foo.SetToolchain(setup.toolchain()); |
| foo.action_values().outputs() = |
| SubstitutionList::MakeForTest("//out/Debug/foo.out"); |
| ASSERT_TRUE(foo.OnResolved(&err)); |
| |
| { |
| std::ostringstream out; |
| NinjaActionTargetWriter writer(&foo, out); |
| writer.Run(); |
| |
| const char expected_linux[] = |
| "rule __foo_foo___rule\n" |
| // These come from the args. |
| " command = /usr/bin/python ../../foo/script.py\n" |
| " description = ACTION //foo:foo()\n" |
| " restat = 1\n" |
| "\n" |
| "build foo.out: __foo_foo___rule | ../../foo/script.py" |
| " ../../foo/input1.txt obj/foo/dep.stamp\n" |
| "\n" |
| "build obj/foo/foo.stamp: stamp foo.out\n"; |
| EXPECT_EQ(expected_linux, out.str()); |
| } |
| |
| Target bar(setup.settings(), Label(SourceDir("//bar/"), "bar")); |
| bar.set_output_type(Target::ACTION); |
| bar.sources().push_back(SourceFile("//bar/input1.txt")); |
| bar.action_values().set_script(SourceFile("//bar/script.py")); |
| bar.private_deps().push_back(LabelTargetPair(&foo)); |
| bar.SetToolchain(setup.toolchain()); |
| bar.action_values().outputs() = |
| SubstitutionList::MakeForTest("//out/Debug/bar.out"); |
| ASSERT_TRUE(bar.OnResolved(&err)) << err.message(); |
| |
| { |
| std::ostringstream out; |
| NinjaActionTargetWriter writer(&bar, out); |
| writer.Run(); |
| |
| const char expected_linux[] = |
| "rule __bar_bar___rule\n" |
| // These come from the args. |
| " command = /usr/bin/python ../../bar/script.py\n" |
| " description = ACTION //bar:bar()\n" |
| " restat = 1\n" |
| "\n" |
| // Do not have obj/foo/dep.stamp as dependency. |
| "build bar.out: __bar_bar___rule | ../../bar/script.py" |
| " ../../bar/input1.txt obj/foo/foo.stamp\n" |
| "\n" |
| "build obj/bar/bar.stamp: stamp bar.out\n"; |
| EXPECT_EQ(expected_linux, out.str()); |
| } |
| } |