1 // Copyright 2024 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "gn/ninja_outputs_writer.h"
6
7 #include "base/command_line.h"
8 #include "base/files/file_path.h"
9 #include "base/files/file_util.h"
10 #include "base/files/scoped_temp_dir.h"
11 #include "gn/builder_record.h"
12 #include "gn/filesystem_utils.h"
13 #include "gn/ninja_target_writer.h"
14 #include "gn/setup.h"
15 #include "gn/switches.h"
16 #include "gn/test_with_scheduler.h"
17 #include "util/test/test.h"
18
19 using NinjaOutputsWriterTest = TestWithScheduler;
20 using NinjaOutputsMap = NinjaOutputsWriter::MapType;
21
WriteFile(const base::FilePath & file,const std::string & data)22 static void WriteFile(const base::FilePath& file, const std::string& data) {
23 CHECK_EQ(static_cast<int>(data.size()), // Way smaller than INT_MAX.
24 base::WriteFile(file, data.data(), data.size()));
25 }
26
27 // Collects Ninja outputs for each target. Used by multiple background threads.
28 struct TargetWriteInfo {
29 std::mutex lock;
30 NinjaOutputsMap ninja_outputs_map;
31 };
32
33 // Called on worker thread to write the ninja file.
BackgroundDoWrite(TargetWriteInfo * write_info,const Target * target)34 void BackgroundDoWrite(TargetWriteInfo* write_info, const Target* target) {
35 std::vector<OutputFile> target_ninja_outputs;
36 std::string rule = NinjaTargetWriter::RunAndWriteFile(target, nullptr,
37 &target_ninja_outputs);
38
39 DCHECK(!rule.empty());
40
41 std::lock_guard<std::mutex> lock(write_info->lock);
42 write_info->ninja_outputs_map.emplace(target,
43 std::move(target_ninja_outputs));
44 }
45
ItemResolvedAndGeneratedCallback(TargetWriteInfo * write_info,const BuilderRecord * record)46 static void ItemResolvedAndGeneratedCallback(TargetWriteInfo* write_info,
47 const BuilderRecord* record) {
48 const Item* item = record->item();
49 const Target* target = item->AsTarget();
50 if (target) {
51 g_scheduler->ScheduleWork(
52 [write_info, target]() { BackgroundDoWrite(write_info, target); });
53 }
54 }
55
TEST_F(NinjaOutputsWriterTest,OutputsFile)56 TEST_F(NinjaOutputsWriterTest, OutputsFile) {
57 base::CommandLine cmdline(base::CommandLine::NO_PROGRAM);
58
59 const char kDotfileContents[] = R"(
60 buildconfig = "//BUILDCONFIG.gn"
61 )";
62
63 const char kBuildConfigContents[] = R"(
64 set_default_toolchain("//toolchain:default")
65 )";
66
67 const char kToolchainBuildContents[] = R"##(
68 toolchain("default") {
69 tool("stamp") {
70 command = "stamp"
71 }
72 }
73
74 toolchain("secondary") {
75 tool("stamp") {
76 command = "stamp2"
77 }
78 }
79 )##";
80
81 const char kBuildGnContents[] = R"##(
82 group("foo") {
83 deps = [ ":bar", ":zoo(//toolchain:secondary)" ]
84 }
85
86 action("bar") {
87 script = "//:run_bar_script.py"
88 outputs = [ "$root_build_dir/bar.output" ]
89 args = []
90 }
91
92 group("zoo") {
93 }
94 )##";
95
96 // Create a temp directory containing the build.
97 base::ScopedTempDir in_temp_dir;
98 ASSERT_TRUE(in_temp_dir.CreateUniqueTempDir());
99 base::FilePath in_path = in_temp_dir.GetPath();
100
101 WriteFile(in_path.Append(FILE_PATH_LITERAL("BUILD.gn")), kBuildGnContents);
102 WriteFile(in_path.Append(FILE_PATH_LITERAL("BUILDCONFIG.gn")),
103 kBuildConfigContents);
104 WriteFile(in_path.Append(FILE_PATH_LITERAL(".gn")), kDotfileContents);
105
106 EXPECT_TRUE(
107 base::CreateDirectory(in_path.Append(FILE_PATH_LITERAL("toolchain"))));
108
109 WriteFile(in_path.Append(FILE_PATH_LITERAL("toolchain/BUILD.gn")),
110 kToolchainBuildContents);
111
112 cmdline.AppendSwitch(switches::kRoot, FilePathToUTF8(in_path));
113
114 base::FilePath outputs_json_path(FILE_PATH_LITERAL("ninja_outputs.json"));
115 cmdline.AppendSwitch("--ninja-outputs-file",
116 FilePathToUTF8(outputs_json_path));
117
118 // Create another temp dir for writing the generated files to.
119 base::ScopedTempDir build_temp_dir;
120 ASSERT_TRUE(build_temp_dir.CreateUniqueTempDir());
121
122 // Run setup
123 Setup setup;
124 EXPECT_TRUE(
125 setup.DoSetup(FilePathToUTF8(build_temp_dir.GetPath()), true, cmdline));
126
127 TargetWriteInfo write_info;
128
129 setup.builder().set_resolved_and_generated_callback(
130 [&write_info](const BuilderRecord* record) {
131 ItemResolvedAndGeneratedCallback(&write_info, record);
132 });
133
134 // Do the actual load.
135 ASSERT_TRUE(setup.Run());
136
137 StringOutputBuffer out =
138 NinjaOutputsWriter::GenerateJSON(write_info.ninja_outputs_map);
139
140 // Verify that the generated file is here.
141 std::string generated = out.str();
142 std::string expected = R"##({
143 "//:bar": [
144 "bar.output",
145 "obj/bar.stamp"
146 ],
147 "//:foo": [
148 "obj/foo.stamp"
149 ],
150 "//:zoo": [
151 "obj/zoo.stamp"
152 ],
153 "//:zoo(//toolchain:secondary)": [
154 "secondary/obj/zoo.stamp"
155 ]
156 })##";
157
158 EXPECT_EQ(generated, expected);
159 }
160