From 5499bdeaa106b8514e0a46c180bf4568ebd03ab9 Mon Sep 17 00:00:00 2001 From: Tau Date: Tue, 19 Aug 2025 03:26:51 +0300 Subject: [PATCH] feat: `zero` deps format --- src/build.cc | 36 +++++++++++++++-------- src/build_test.cc | 75 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 99 insertions(+), 12 deletions(-) diff --git a/src/build.cc b/src/build.cc index 7ad08117be..a5d34d64b5 100644 --- a/src/build.cc +++ b/src/build.cc @@ -1000,10 +1000,10 @@ bool Builder::ExtractDeps(CommandRunner::Result* result, // complexity in IncludesNormalize::Relativize. deps_nodes->push_back(state_->GetNode(*i, ~0u)); } - } else if (deps_type == "gcc") { + } else if (deps_type == "gcc" || deps_type == "zero") { string depfile = result->edge->GetUnescapedDepfile(); if (depfile.empty()) { - *err = string("edge with deps=gcc but no depfile makes no sense"); + *err = string("edge with deps=") + deps_type + string(" but no depfile makes no sense"); return false; } @@ -1021,17 +1021,29 @@ bool Builder::ExtractDeps(CommandRunner::Result* result, if (content.empty()) return true; - DepfileParser deps(config_.depfile_parser_options); - if (!deps.Parse(&content, err)) - return false; + if (deps_type == "gcc") { + DepfileParser deps(config_.depfile_parser_options); + if (!deps.Parse(&content, err)) + return false; - // XXX check depfile matches expected output. - deps_nodes->reserve(deps.ins_.size()); - for (vector::iterator i = deps.ins_.begin(); - i != deps.ins_.end(); ++i) { - uint64_t slash_bits; - CanonicalizePath(const_cast(i->str_), &i->len_, &slash_bits); - deps_nodes->push_back(state_->GetNode(*i, slash_bits)); + // XXX check depfile matches expected output. + deps_nodes->reserve(deps.ins_.size()); + for (vector::iterator i = deps.ins_.begin(); + i != deps.ins_.end(); ++i) { + uint64_t slash_bits; + CanonicalizePath(const_cast(i->str_), &i->len_, &slash_bits); + deps_nodes->push_back(state_->GetNode(*i, slash_bits)); + } + } else { + const char* ptr = content.c_str(); + const char* const end = &content.back() + 1; + while (ptr < end) { + StringPiece piece = ptr; + ptr += piece.size() + 1; + uint64_t slash_bits; + CanonicalizePath(const_cast(piece.str_), &piece.len_, &slash_bits); + deps_nodes->push_back(state_->GetNode(piece, slash_bits)); + } } if (!g_keep_depfile) { diff --git a/src/build_test.cc b/src/build_test.cc index 05fb949474..7d0146a137 100644 --- a/src/build_test.cc +++ b/src/build_test.cc @@ -641,6 +641,8 @@ bool FakeCommandRunner::StartCommand(Edge* edge) { edge->rule().name() == "cc" || edge->rule().name() == "cp_multi_msvc" || edge->rule().name() == "cp_multi_gcc" || + edge->rule().name() == "cp_multi_zero" || + edge->rule().name() == "cp_multi_zero_del" || edge->rule().name() == "touch" || edge->rule().name() == "touch-interrupt" || edge->rule().name() == "touch-fail-tick2") { @@ -2538,6 +2540,79 @@ TEST_F(BuildWithQueryDepsLogTest, TwoOutputsDepFileGCCOnlySecondaryOutput) { EXPECT_EQ("in2", out2_deps->nodes[1]->path()); } +/// Test nul-separated deps log with multiple outputs. +TEST_F(BuildWithQueryDepsLogTest, TwoOutputsDepFileZero) { + ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, +"rule cp_multi_zero\n" +" command = printf 'in1\\0in2\\0' > in.d && for file in $out; do cp in1 $$file; done\n" +" deps = zero\n" +" depfile = in.d\n" +"build out1 out2: cp_multi_zero in1 in2\n")); + + std::string err; + EXPECT_TRUE(builder_.AddTarget("out1", &err)); + ASSERT_EQ("", err); + + std::string cont = "in1"; + cont.push_back('\0'); + cont.append("in2"); + cont.push_back('\0'); + + fs_.Create("in.d", cont); + EXPECT_EQ(builder_.Build(&err), ExitSuccess); + EXPECT_EQ("", err); + ASSERT_EQ(1u, command_runner_.commands_ran_.size()); + EXPECT_EQ("printf 'in1\\0in2\\0' > in.d && for file in out1 out2; do cp in1 $file; done", command_runner_.commands_ran_[0]); + + Node* out1_node = state_.LookupNode("out1"); + DepsLog::Deps* out1_deps = log_.GetDeps(out1_node); + EXPECT_EQ(2, out1_deps->node_count); + EXPECT_EQ("in1", out1_deps->nodes[0]->path()); + EXPECT_EQ("in2", out1_deps->nodes[1]->path()); + + Node* out2_node = state_.LookupNode("out2"); + DepsLog::Deps* out2_deps = log_.GetDeps(out2_node); + EXPECT_EQ(2, out2_deps->node_count); + EXPECT_EQ("in1", out2_deps->nodes[0]->path()); + EXPECT_EQ("in2", out2_deps->nodes[1]->path()); +} + +/// Test nul-separated deps log with multiple outputs and no trailing nul. +TEST_F(BuildWithQueryDepsLogTest, TwoOutputsDepFileZeroDel) { + ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, +"rule cp_multi_zero_del\n" +" command = printf 'in1\\0in2' > in.d && for file in $out; do cp in1 $$file; done\n" +" deps = zero\n" +" depfile = in.d\n" +"build out1 out2: cp_multi_zero_del in1 in2\n")); + + std::string err; + EXPECT_TRUE(builder_.AddTarget("out1", &err)); + ASSERT_EQ("", err); + + std::string cont = "in1"; + cont.push_back('\0'); + cont.append("in2"); + + fs_.Create("in.d", cont); + EXPECT_EQ(builder_.Build(&err), ExitSuccess); + EXPECT_EQ("", err); + ASSERT_EQ(1u, command_runner_.commands_ran_.size()); + EXPECT_EQ("printf 'in1\\0in2' > in.d && for file in out1 out2; do cp in1 $file; done", command_runner_.commands_ran_[0]); + + Node* out1_node = state_.LookupNode("out1"); + DepsLog::Deps* out1_deps = log_.GetDeps(out1_node); + EXPECT_EQ(2, out1_deps->node_count); + EXPECT_EQ("in1", out1_deps->nodes[0]->path()); + EXPECT_EQ("in2", out1_deps->nodes[1]->path()); + + Node* out2_node = state_.LookupNode("out2"); + DepsLog::Deps* out2_deps = log_.GetDeps(out2_node); + EXPECT_EQ(2, out2_deps->node_count); + EXPECT_EQ("in1", out2_deps->nodes[0]->path()); + EXPECT_EQ("in2", out2_deps->nodes[1]->path()); +} + /// Tests of builds involving deps logs necessarily must span /// multiple builds. We reuse methods on BuildTest but not the /// builder_ it sets up, because we want pristine objects for