Skip to content

Commit

Permalink
Enable support for concatenated depfiles (depfile_group)
Browse files Browse the repository at this point in the history
  • Loading branch information
PetrWolf committed Mar 19, 2012
1 parent 5359825 commit 76ac158
Show file tree
Hide file tree
Showing 14 changed files with 612 additions and 67 deletions.
2 changes: 2 additions & 0 deletions configure.py
Expand Up @@ -212,6 +212,7 @@ def binary(name):
'build_log',
'clean',
'depfile_parser',
'depfile_reader',
'disk_interface',
'edit_distance',
'eval_env',
Expand Down Expand Up @@ -277,6 +278,7 @@ def binary(name):
'build_test',
'clean_test',
'depfile_parser_test',
'depfile_reader_test',
'disk_interface_test',
'edit_distance_test',
'graph_test',
Expand Down
3 changes: 2 additions & 1 deletion src/build.cc
Expand Up @@ -563,7 +563,8 @@ Node* Builder::AddTarget(const string& name, string* err) {
}

bool Builder::AddTarget(Node* node, string* err) {
node->StatIfNecessary(disk_interface_);
node->Stat(disk_interface_);
node->SetVisited();
if (Edge* in_edge = node->in_edge()) {
if (!in_edge->RecomputeDirty(state_, disk_interface_, err))
return false;
Expand Down
45 changes: 45 additions & 0 deletions src/build_test.cc
Expand Up @@ -518,6 +518,51 @@ TEST_F(BuildTest, DepFileParseError) {
err);
}

// Make sure a depfile_group attribute is correctly used
TEST_F(BuildTest, DepFileGroup) {
string err;
ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
"rule cc\n"
" command = cc $in\n "
" depfile = $out.d\n"
" depfile_group = group.D\n"
"build foo.o: cc foo.c\n"));
fs_.Create("foo.c", now_, "");
fs_.Create("foo.o.d", now_, "foo.o: old and wrong dependencies\n");

// The .D file is more recent than the .d file
now_++;
fs_.Create("group.D", now_, "foo.o: corect dependencies\n");

// dependency information from foo.o.d is used
EXPECT_TRUE(builder_.AddTarget("foo.o", &err));
ASSERT_EQ("", err);
ASSERT_EQ(1u, fs_.files_read_.size());
EXPECT_EQ("group.D", fs_.files_read_[0]);
}

// Make sure a depfile_group file is not used, if it is out of date
TEST_F(BuildTest, DepFileFallback) {
string err;
ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
"rule cc\n command = cc $in\n "
" depfile = $out.d\n"
" depfile_group = fallback.D\n"
"build foo.o: cc foo.c\n"));
fs_.Create("foo.c", now_, "");
fs_.Create("fallback.D", now_, "foo.o: old and wrong dependencies\n");

// The actual .o file is more recent than the .D file
now_++;
fs_.Create("foo.o", now_, "foo.o: corect dependencies\n");

This comment has been minimized.

Copy link
@usovalx

usovalx Mar 20, 2012

Shouldn't it be "foo.o.d"?
And if it should, why the tests works?

This comment has been minimized.

Copy link
@PetrWolf

PetrWolf Mar 20, 2012

Author Owner

The test is ok, but is misleading. I will update it.

To answer your question - with this patch, ninja uses timestamps of the output files, not the plain depfiles, and compares the newest of them against the timestamp on the depfile_group file.
So if timestamp(foo.o) > timestamp(group.D), ninja will use the depfile associated with foo.o.


// dependency information from foo.o.d is used
EXPECT_TRUE(builder_.AddTarget("foo.o", &err));
ASSERT_EQ("", err);
ASSERT_EQ(1u, fs_.files_read_.size());
EXPECT_EQ("foo.o.d", fs_.files_read_[0]);
}

TEST_F(BuildTest, OrderOnlyDeps) {
string err;
ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
Expand Down
9 changes: 9 additions & 0 deletions src/depfile_parser.h
Expand Up @@ -12,6 +12,9 @@
// See the License for the specific language governing permissions and
// limitations under the License.

#ifndef NINJA_DEPFILE_PARSER_H_
#define NINJA_DEPFILE_PARSER_H_

#include <string>
#include <vector>
using namespace std;
Expand All @@ -25,6 +28,12 @@ struct DepfileParser {
/// pointers within it.
bool Parse(string* content, string* err);

// Public getters for the parsed values
inline StringPiece& out() { return out_; }
inline vector<StringPiece> & ins() { return ins_; }
private:
StringPiece out_;
vector<StringPiece> ins_;
};

#endif // NINJA_DEPFILE_PARSER_H_
36 changes: 18 additions & 18 deletions src/depfile_parser_test.cc
Expand Up @@ -34,8 +34,8 @@ TEST_F(DepfileParserTest, Basic) {
"build/ninja.o: ninja.cc ninja.h eval_env.h manifest_parser.h\n",
&err));
ASSERT_EQ("", err);
EXPECT_EQ("build/ninja.o", parser_.out_.AsString());
EXPECT_EQ(4u, parser_.ins_.size());
EXPECT_EQ("build/ninja.o", parser_.out().AsString());
EXPECT_EQ(4u, parser_.ins().size());
}

TEST_F(DepfileParserTest, EarlyNewlineAndWhitespace) {
Expand All @@ -54,8 +54,8 @@ TEST_F(DepfileParserTest, Continuation) {
" bar.h baz.h\n",
&err));
ASSERT_EQ("", err);
EXPECT_EQ("foo.o", parser_.out_.AsString());
EXPECT_EQ(2u, parser_.ins_.size());
EXPECT_EQ("foo.o", parser_.out().AsString());
EXPECT_EQ(2u, parser_.ins().size());
}

TEST_F(DepfileParserTest, BackSlashes) {
Expand All @@ -69,8 +69,8 @@ TEST_F(DepfileParserTest, BackSlashes) {
&err));
ASSERT_EQ("", err);
EXPECT_EQ("Project\\Dir\\Build\\Release8\\Foo\\Foo.res",
parser_.out_.AsString());
EXPECT_EQ(4u, parser_.ins_.size());
parser_.out().AsString());
EXPECT_EQ(4u, parser_.ins().size());
}

TEST_F(DepfileParserTest, Spaces) {
Expand All @@ -80,14 +80,14 @@ TEST_F(DepfileParserTest, Spaces) {
&err));
ASSERT_EQ("", err);
EXPECT_EQ("a bc def",
parser_.out_.AsString());
ASSERT_EQ(3u, parser_.ins_.size());
parser_.out().AsString());
ASSERT_EQ(3u, parser_.ins().size());
EXPECT_EQ("a b",
parser_.ins_[0].AsString());
parser_.ins()[0].AsString());
EXPECT_EQ("c",
parser_.ins_[1].AsString());
parser_.ins()[1].AsString());
EXPECT_EQ("d",
parser_.ins_[2].AsString());
parser_.ins()[2].AsString());
}

TEST_F(DepfileParserTest, Escapes) {
Expand All @@ -99,19 +99,19 @@ TEST_F(DepfileParserTest, Escapes) {
&err));
ASSERT_EQ("", err);
EXPECT_EQ("\\!\\@#$\\%\\^\\&\\",
parser_.out_.AsString());
ASSERT_EQ(0u, parser_.ins_.size());
parser_.out().AsString());
ASSERT_EQ(0u, parser_.ins().size());
}

TEST_F(DepfileParserTest, UnifyMultupleOutputs) {
// check that multiple duplicate targets are properly unified
string err;
EXPECT_TRUE(Parse("foo foo: x y z", &err));
ASSERT_EQ(parser_.out_.AsString(), "foo");
ASSERT_EQ(parser_.ins_.size(), 3);
EXPECT_EQ("x", parser_.ins_[0].AsString());
EXPECT_EQ("y", parser_.ins_[1].AsString());
EXPECT_EQ("z", parser_.ins_[2].AsString());
ASSERT_EQ(parser_.out().AsString(), "foo");
ASSERT_EQ(parser_.ins().size(), 3);
EXPECT_EQ("x", parser_.ins()[0].AsString());
EXPECT_EQ("y", parser_.ins()[1].AsString());
EXPECT_EQ("z", parser_.ins()[2].AsString());
}

TEST_F(DepfileParserTest, RejectMultipleDifferentOutputs) {
Expand Down
140 changes: 140 additions & 0 deletions src/depfile_reader.cc
@@ -0,0 +1,140 @@
// Copyright 2012 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <sstream>
#include "depfile_reader.h"

// A static cache for already parsed, but not yet used depfiles
DepfileReader::DepfileCache DepfileReader::cache;

DepfileReader::~DepfileReader() {
delete contents_;
delete parser_;
}

bool DepfileReader::Init(const string& contents, string* error) {
// init and take a private copy of the string
contents_ = new string(contents);

This comment has been minimized.

Copy link
@usovalx

usovalx Mar 20, 2012

Superfluous copy of the string.

This comment has been minimized.

Copy link
@PetrWolf

PetrWolf Mar 20, 2012

Author Owner

Good point. Let me find a better way

if (contents_->empty())
return true;

parser_ = new DepfileParser();
if (!parser_->Parse(contents_, error)) {
return false;
}

return true;
}

// Method to open, read and divide and aggregated depfile, saving its individual
// components into internal cache of DepFileReader
bool DepfileReader::loadIntoCache(DiskInterface* disk_interface,
const string& depfile_path, string* err)
{
string contents = disk_interface->ReadFile(depfile_path, err);
if (!err->empty())
return false;

// create an entry in the cache
DepfileCache::mapped_type & fileMap = cache[depfile_path];

// Populate it
if (contents.empty())
return true;
istringstream stream(contents);
string line, depfile_contents;
while (getline(stream, line)) {
depfile_contents.append(line).append("\n");
if (line.empty() || line[line.length()-1] != '\\' || stream.eof()) {
// One depfile has ended here, let's parse it and put in cache
DepfileReader reader;

// Parse the depfile (to get the filename)
string depfile_err;
if (!reader.Init(depfile_contents, &depfile_err)) {
*err = depfile_path + ": " + depfile_err;
return false;
}

// Save it in cache
const string filename = reader.parser_->out().AsString();
swap(fileMap[filename], reader);

// Reset
depfile_contents.clear();
}
}

return true;
}

bool DepfileReader::ReadGroup(const string& depfile_path,
const string& output_name,
DiskInterface * disk_interface,
string* err) {
DepfileCache::iterator in_cache = cache.find(depfile_path);
if (cache.end() == in_cache) {
// file was not yet cached -> read it
loadIntoCache(disk_interface, depfile_path, err);
if (!err->empty()) {
return false;
}

in_cache = cache.find(depfile_path);
}

// locate the relevant part of the cached file
DepfileCache::mapped_type::iterator depfile_element = in_cache->second.find(output_name);
if (in_cache->second.end() == depfile_element) {
// no record for the underlying .d file -> not an error (it may be a new file)
return true;
}

// retrieve the cached contents
swap(*this, depfile_element->second);

// drop the element from the cache (it's only meant to be used once)
in_cache->second.erase(depfile_element);

return true;
}

bool DepfileReader::Read(const string& depfile_path,
const string& output_name,
DiskInterface * disk_interface,
string* err) {
const string contents = disk_interface->ReadFile(depfile_path, err);
if (!err->empty())
return false;

if (contents.empty())
return true;

// Save and parse the file
string depfile_err;
if (!Init(contents, &depfile_err)) {
*err = depfile_path + ": " + depfile_err;
return false;
}

// Check that this depfile matches our output.
StringPiece opath = StringPiece(output_name);
if (opath != parser_->out()) {
*err = "expected depfile '" + depfile_path + "' to mention '" +
output_name + "', got '" + parser_->out().AsString() + "'";
return false;
}

return true;
}
61 changes: 61 additions & 0 deletions src/depfile_reader.h
@@ -0,0 +1,61 @@
// Copyright 2011 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#ifndef NINJA_DEPFILE_READER_H_
#define NINJA_DEPFILE_READER_H_

#include <string>
#include <map>
#include "disk_interface.h"
#include "depfile_parser.h"

using namespace std;

// Holds a DepfileParser together with its associated data.
// In the case of grouped depfiles, uses internal cache to store
// depfiles which have been read, but not used yet
struct DepfileReader {
DepfileReader() : contents_(NULL), parser_(NULL) {};
~DepfileReader();

// Read a depfile from disk and parse it
bool Read(const string& depfile_path,
const string& output_name,
DiskInterface * disk_interface,
string* err);

// Read/retrieve from cache a part of a grouped depfile
// associated with the given output and parse it.
bool ReadGroup(const string& depfile_path,
const string& output_name,
DiskInterface * disk_interface,
string* err);

inline DepfileParser * Parser() {
return parser_;
}

private:
typedef map<string, map<string, DepfileReader> > DepfileCache;
static DepfileCache cache;
static bool loadIntoCache(DiskInterface* disk_interface, const string& depfile_path, string* err);
bool Init(const string& contents, string* error);

friend struct DepfileReaderTest; // needs access to cache

string* contents_;
DepfileParser* parser_;
};

#endif // NINJA_DEPFILE_READER_H_

0 comments on commit 76ac158

Please sign in to comment.