Make deps=msvc experimentally available on non-Windows.

This makes it possible to run most of the clparser tests on non-Windows,
and is potentially useful for cross-compiling on non-Windows hosts.
Also, the manual didn't document this as Windows-only previously.

If you use this on non-Windows, please let me know, else I might undo
this change again in the future.
This commit is contained in:
Nico Weber 2015-06-11 23:53:32 -07:00 committed by Nico Weber
parent aea5a4d41f
commit 3a88912624
9 changed files with 290 additions and 211 deletions

View File

@ -475,6 +475,7 @@ n.comment('Core source files all build into ninja library.')
for name in ['build',
'build_log',
'clean',
'clparser',
'debug_flags',
'depfile_parser',
'deps_log',
@ -540,6 +541,7 @@ objs = []
for name in ['build_log_test',
'build_test',
'clean_test',
'clparser_test',
'depfile_parser_test',
'deps_log_test',
'disk_interface_test',

View File

@ -25,12 +25,12 @@
#endif
#include "build_log.h"
#include "clparser.h"
#include "debug_flags.h"
#include "depfile_parser.h"
#include "deps_log.h"
#include "disk_interface.h"
#include "graph.h"
#include "msvc_helper.h"
#include "state.h"
#include "subprocess.h"
#include "util.h"
@ -854,7 +854,6 @@ bool Builder::ExtractDeps(CommandRunner::Result* result,
const string& deps_prefix,
vector<Node*>* deps_nodes,
string* err) {
#ifdef _WIN32
if (deps_type == "msvc") {
CLParser parser;
string output;
@ -870,7 +869,6 @@ bool Builder::ExtractDeps(CommandRunner::Result* result,
deps_nodes->push_back(state_->GetNode(*i, ~0u));
}
} else
#endif
if (deps_type == "gcc") {
string depfile = result->edge->GetUnescapedDepfile();
if (depfile.empty()) {

116
src/clparser.cc Normal file
View File

@ -0,0 +1,116 @@
// Copyright 2015 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 "clparser.h"
#include <algorithm>
#include <assert.h>
#include <string.h>
#ifdef _WIN32
#include "includes_normalize.h"
#else
#include "util.h"
#endif
namespace {
/// Return true if \a input ends with \a needle.
bool EndsWith(const string& input, const string& needle) {
return (input.size() >= needle.size() &&
input.substr(input.size() - needle.size()) == needle);
}
} // anonymous namespace
// static
string CLParser::FilterShowIncludes(const string& line,
const string& deps_prefix) {
const string kDepsPrefixEnglish = "Note: including file: ";
const char* in = line.c_str();
const char* end = in + line.size();
const string& prefix = deps_prefix.empty() ? kDepsPrefixEnglish : deps_prefix;
if (end - in > (int)prefix.size() &&
memcmp(in, prefix.c_str(), (int)prefix.size()) == 0) {
in += prefix.size();
while (*in == ' ')
++in;
return line.substr(in - line.c_str());
}
return "";
}
// static
bool CLParser::IsSystemInclude(string path) {
transform(path.begin(), path.end(), path.begin(), ::tolower);
// TODO: this is a heuristic, perhaps there's a better way?
return (path.find("program files") != string::npos ||
path.find("microsoft visual studio") != string::npos);
}
// static
bool CLParser::FilterInputFilename(string line) {
transform(line.begin(), line.end(), line.begin(), ::tolower);
// TODO: other extensions, like .asm?
return EndsWith(line, ".c") ||
EndsWith(line, ".cc") ||
EndsWith(line, ".cxx") ||
EndsWith(line, ".cpp");
}
// static
bool CLParser::Parse(const string& output, const string& deps_prefix,
string* filtered_output, string* err) {
// Loop over all lines in the output to process them.
assert(&output != filtered_output);
size_t start = 0;
while (start < output.size()) {
size_t end = output.find_first_of("\r\n", start);
if (end == string::npos)
end = output.size();
string line = output.substr(start, end - start);
string include = FilterShowIncludes(line, deps_prefix);
if (!include.empty()) {
string normalized;
#ifdef _WIN32
if (!IncludesNormalize::Normalize(include, NULL, &normalized, err))
return false;
#else
// TODO: should this make the path relative to cwd?
normalized = include;
unsigned int slash_bits;
if (!CanonicalizePath(&normalized, &slash_bits, err))
return false;
#endif
if (!IsSystemInclude(normalized))
includes_.insert(normalized);
} else if (FilterInputFilename(line)) {
// Drop it.
// TODO: if we support compiling multiple output files in a single
// cl.exe invocation, we should stash the filename.
} else {
filtered_output->append(line);
filtered_output->append("\n");
}
if (end < output.size() && output[end] == '\r')
++end;
if (end < output.size() && output[end] == '\n')
++end;
start = end;
}
return true;
}

52
src/clparser.h Normal file
View File

@ -0,0 +1,52 @@
// Copyright 2015 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_CLPARSER_H_
#define NINJA_CLPARSER_H_
#include <set>
#include <string>
using namespace std;
/// Visual Studio's cl.exe requires some massaging to work with Ninja;
/// for example, it emits include information on stderr in a funny
/// format when building with /showIncludes. This class parses this
/// output.
struct CLParser {
/// Parse a line of cl.exe output and extract /showIncludes info.
/// If a dependency is extracted, returns a nonempty string.
/// Exposed for testing.
static string FilterShowIncludes(const string& line,
const string& deps_prefix);
/// Return true if a mentioned include file is a system path.
/// Filtering these out reduces dependency information considerably.
static bool IsSystemInclude(string path);
/// Parse a line of cl.exe output and return true if it looks like
/// it's printing an input filename. This is a heuristic but it appears
/// to be the best we can do.
/// Exposed for testing.
static bool FilterInputFilename(string line);
/// Parse the full output of cl, filling filtered_output with the text that
/// should be printed (if any). Returns true on success, or false with err
/// filled. output must not be the same object as filtered_object.
bool Parse(const string& output, const string& deps_prefix,
string* filtered_output, string* err);
set<string> includes_;
};
#endif // NINJA_CLPARSER_H_

117
src/clparser_test.cc Normal file
View File

@ -0,0 +1,117 @@
// 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.
#include "clparser.h"
#include "test.h"
#include "util.h"
TEST(CLParserTest, ShowIncludes) {
ASSERT_EQ("", CLParser::FilterShowIncludes("", ""));
ASSERT_EQ("", CLParser::FilterShowIncludes("Sample compiler output", ""));
ASSERT_EQ("c:\\Some Files\\foobar.h",
CLParser::FilterShowIncludes("Note: including file: "
"c:\\Some Files\\foobar.h", ""));
ASSERT_EQ("c:\\initspaces.h",
CLParser::FilterShowIncludes("Note: including file: "
"c:\\initspaces.h", ""));
ASSERT_EQ("c:\\initspaces.h",
CLParser::FilterShowIncludes("Non-default prefix: inc file: "
"c:\\initspaces.h",
"Non-default prefix: inc file:"));
}
TEST(CLParserTest, FilterInputFilename) {
ASSERT_TRUE(CLParser::FilterInputFilename("foobar.cc"));
ASSERT_TRUE(CLParser::FilterInputFilename("foo bar.cc"));
ASSERT_TRUE(CLParser::FilterInputFilename("baz.c"));
ASSERT_TRUE(CLParser::FilterInputFilename("FOOBAR.CC"));
ASSERT_FALSE(CLParser::FilterInputFilename(
"src\\cl_helper.cc(166) : fatal error C1075: end "
"of file found ..."));
}
TEST(CLParserTest, ParseSimple) {
CLParser parser;
string output, err;
ASSERT_TRUE(parser.Parse(
"foo\r\n"
"Note: inc file prefix: foo.h\r\n"
"bar\r\n",
"Note: inc file prefix:", &output, &err));
ASSERT_EQ("foo\nbar\n", output);
ASSERT_EQ(1u, parser.includes_.size());
ASSERT_EQ("foo.h", *parser.includes_.begin());
}
TEST(CLParserTest, ParseFilenameFilter) {
CLParser parser;
string output, err;
ASSERT_TRUE(parser.Parse(
"foo.cc\r\n"
"cl: warning\r\n",
"", &output, &err));
ASSERT_EQ("cl: warning\n", output);
}
TEST(CLParserTest, ParseSystemInclude) {
CLParser parser;
string output, err;
ASSERT_TRUE(parser.Parse(
"Note: including file: c:\\Program Files\\foo.h\r\n"
"Note: including file: d:\\Microsoft Visual Studio\\bar.h\r\n"
"Note: including file: path.h\r\n",
"", &output, &err));
// We should have dropped the first two includes because they look like
// system headers.
ASSERT_EQ("", output);
ASSERT_EQ(1u, parser.includes_.size());
ASSERT_EQ("path.h", *parser.includes_.begin());
}
TEST(CLParserTest, DuplicatedHeader) {
CLParser parser;
string output, err;
ASSERT_TRUE(parser.Parse(
"Note: including file: foo.h\r\n"
"Note: including file: bar.h\r\n"
"Note: including file: foo.h\r\n",
"", &output, &err));
// We should have dropped one copy of foo.h.
ASSERT_EQ("", output);
ASSERT_EQ(2u, parser.includes_.size());
}
TEST(CLParserTest, DuplicatedHeaderPathConverted) {
CLParser parser;
string output, err;
// This isn't inline in the Parse() call below because the #ifdef in
// a macro expansion would confuse MSVC2013's preprocessor.
const char kInput[] =
"Note: including file: sub/./foo.h\r\n"
"Note: including file: bar.h\r\n"
#ifdef _WIN32
"Note: including file: sub\\foo.h\r\n";
#else
"Note: including file: sub/foo.h\r\n";
#endif
ASSERT_TRUE(parser.Parse(kInput, "", &output, &err));
// We should have dropped one copy of foo.h.
ASSERT_EQ("", output);
ASSERT_EQ(2u, parser.includes_.size());
}

View File

@ -14,23 +14,12 @@
#include "msvc_helper.h"
#include <algorithm>
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <windows.h>
#include "includes_normalize.h"
#include "util.h"
namespace {
/// Return true if \a input ends with \a needle.
bool EndsWith(const string& input, const string& needle) {
return (input.size() >= needle.size() &&
input.substr(input.size() - needle.size()) == needle);
}
string Replace(const string& input, const string& find, const string& replace) {
string result = input;
size_t start_pos = 0;
@ -48,78 +37,6 @@ string EscapeForDepfile(const string& path) {
return Replace(path, " ", "\\ ");
}
// static
string CLParser::FilterShowIncludes(const string& line,
const string& deps_prefix) {
const string kDepsPrefixEnglish = "Note: including file: ";
const char* in = line.c_str();
const char* end = in + line.size();
const string& prefix = deps_prefix.empty() ? kDepsPrefixEnglish : deps_prefix;
if (end - in > (int)prefix.size() &&
memcmp(in, prefix.c_str(), (int)prefix.size()) == 0) {
in += prefix.size();
while (*in == ' ')
++in;
return line.substr(in - line.c_str());
}
return "";
}
// static
bool CLParser::IsSystemInclude(string path) {
transform(path.begin(), path.end(), path.begin(), ::tolower);
// TODO: this is a heuristic, perhaps there's a better way?
return (path.find("program files") != string::npos ||
path.find("microsoft visual studio") != string::npos);
}
// static
bool CLParser::FilterInputFilename(string line) {
transform(line.begin(), line.end(), line.begin(), ::tolower);
// TODO: other extensions, like .asm?
return EndsWith(line, ".c") ||
EndsWith(line, ".cc") ||
EndsWith(line, ".cxx") ||
EndsWith(line, ".cpp");
}
bool CLParser::Parse(const string& output, const string& deps_prefix,
string* filtered_output, string* err) {
// Loop over all lines in the output to process them.
assert(&output != filtered_output);
size_t start = 0;
while (start < output.size()) {
size_t end = output.find_first_of("\r\n", start);
if (end == string::npos)
end = output.size();
string line = output.substr(start, end - start);
string include = FilterShowIncludes(line, deps_prefix);
if (!include.empty()) {
string normalized;
if (!IncludesNormalize::Normalize(include, NULL, &normalized, err))
return false;
if (!IsSystemInclude(normalized))
includes_.insert(normalized);
} else if (FilterInputFilename(line)) {
// Drop it.
// TODO: if we support compiling multiple output files in a single
// cl.exe invocation, we should stash the filename.
} else {
filtered_output->append(line);
filtered_output->append("\n");
}
if (end < output.size() && output[end] == '\r')
++end;
if (end < output.size() && output[end] == '\n')
++end;
start = end;
}
return true;
}
int CLWrapper::Run(const string& command, string* output) {
SECURITY_ATTRIBUTES security_attributes = {};
security_attributes.nLength = sizeof(SECURITY_ATTRIBUTES);

View File

@ -13,42 +13,10 @@
// limitations under the License.
#include <string>
#include <set>
#include <vector>
using namespace std;
string EscapeForDepfile(const string& path);
/// Visual Studio's cl.exe requires some massaging to work with Ninja;
/// for example, it emits include information on stderr in a funny
/// format when building with /showIncludes. This class parses this
/// output.
struct CLParser {
/// Parse a line of cl.exe output and extract /showIncludes info.
/// If a dependency is extracted, returns a nonempty string.
/// Exposed for testing.
static string FilterShowIncludes(const string& line,
const string& deps_prefix);
/// Return true if a mentioned include file is a system path.
/// Filtering these out reduces dependency information considerably.
static bool IsSystemInclude(string path);
/// Parse a line of cl.exe output and return true if it looks like
/// it's printing an input filename. This is a heuristic but it appears
/// to be the best we can do.
/// Exposed for testing.
static bool FilterInputFilename(string line);
/// Parse the full output of cl, filling filtered_output with the text that
/// should be printed (if any). Returns true on success, or false with err
/// filled. output must not be the same object as filtered_object.
bool Parse(const string& output, const string& deps_prefix,
string* filtered_output, string* err);
set<string> includes_;
};
/// Wraps a synchronous execution of a CL subprocess.
struct CLWrapper {
CLWrapper() : env_block_(NULL) {}

View File

@ -19,6 +19,7 @@
#include <stdio.h>
#include <windows.h>
#include "clparser.h"
#include "util.h"
#include "getopt.h"

View File

@ -17,99 +17,7 @@
#include "test.h"
#include "util.h"
TEST(CLParserTest, ShowIncludes) {
ASSERT_EQ("", CLParser::FilterShowIncludes("", ""));
ASSERT_EQ("", CLParser::FilterShowIncludes("Sample compiler output", ""));
ASSERT_EQ("c:\\Some Files\\foobar.h",
CLParser::FilterShowIncludes("Note: including file: "
"c:\\Some Files\\foobar.h", ""));
ASSERT_EQ("c:\\initspaces.h",
CLParser::FilterShowIncludes("Note: including file: "
"c:\\initspaces.h", ""));
ASSERT_EQ("c:\\initspaces.h",
CLParser::FilterShowIncludes("Non-default prefix: inc file: "
"c:\\initspaces.h",
"Non-default prefix: inc file:"));
}
TEST(CLParserTest, FilterInputFilename) {
ASSERT_TRUE(CLParser::FilterInputFilename("foobar.cc"));
ASSERT_TRUE(CLParser::FilterInputFilename("foo bar.cc"));
ASSERT_TRUE(CLParser::FilterInputFilename("baz.c"));
ASSERT_TRUE(CLParser::FilterInputFilename("FOOBAR.CC"));
ASSERT_FALSE(CLParser::FilterInputFilename(
"src\\cl_helper.cc(166) : fatal error C1075: end "
"of file found ..."));
}
TEST(CLParserTest, ParseSimple) {
CLParser parser;
string output, err;
ASSERT_TRUE(parser.Parse(
"foo\r\n"
"Note: inc file prefix: foo.h\r\n"
"bar\r\n",
"Note: inc file prefix:", &output, &err));
ASSERT_EQ("foo\nbar\n", output);
ASSERT_EQ(1u, parser.includes_.size());
ASSERT_EQ("foo.h", *parser.includes_.begin());
}
TEST(CLParserTest, ParseFilenameFilter) {
CLParser parser;
string output, err;
ASSERT_TRUE(parser.Parse(
"foo.cc\r\n"
"cl: warning\r\n",
"", &output, &err));
ASSERT_EQ("cl: warning\n", output);
}
TEST(CLParserTest, ParseSystemInclude) {
CLParser parser;
string output, err;
ASSERT_TRUE(parser.Parse(
"Note: including file: c:\\Program Files\\foo.h\r\n"
"Note: including file: d:\\Microsoft Visual Studio\\bar.h\r\n"
"Note: including file: path.h\r\n",
"", &output, &err));
// We should have dropped the first two includes because they look like
// system headers.
ASSERT_EQ("", output);
ASSERT_EQ(1u, parser.includes_.size());
ASSERT_EQ("path.h", *parser.includes_.begin());
}
TEST(CLParserTest, DuplicatedHeader) {
CLParser parser;
string output, err;
ASSERT_TRUE(parser.Parse(
"Note: including file: foo.h\r\n"
"Note: including file: bar.h\r\n"
"Note: including file: foo.h\r\n",
"", &output, &err));
// We should have dropped one copy of foo.h.
ASSERT_EQ("", output);
ASSERT_EQ(2u, parser.includes_.size());
}
TEST(CLParserTest, DuplicatedHeaderPathConverted) {
CLParser parser;
string output, err;
ASSERT_TRUE(parser.Parse(
"Note: including file: sub/foo.h\r\n"
"Note: including file: bar.h\r\n"
"Note: including file: sub\\foo.h\r\n",
"", &output, &err));
// We should have dropped one copy of foo.h.
ASSERT_EQ("", output);
ASSERT_EQ(2u, parser.includes_.size());
}
TEST(CLParserTest, SpacesInFilename) {
TEST(EscapeForDepfileTest, SpacesInFilename) {
ASSERT_EQ("sub\\some\\ sdk\\foo.h",
EscapeForDepfile("sub\\some sdk\\foo.h"));
}