229 lines
7.0 KiB
C++
229 lines
7.0 KiB
C++
//===--- iwyu_path_util.cc - file-path utilities for include-what-you-use -===//
|
|
//
|
|
// The LLVM Compiler Infrastructure
|
|
//
|
|
// This file is distributed under the University of Illinois Open Source
|
|
// License. See LICENSE.TXT for details.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "iwyu_path_util.h"
|
|
|
|
#include <algorithm> // for std::replace
|
|
#include <cstddef>
|
|
#include <cstring> // for strlen
|
|
#include <system_error>
|
|
|
|
#include "iwyu_stl_util.h"
|
|
|
|
#include "llvm/ADT/SmallString.h"
|
|
#include "llvm/ADT/StringRef.h"
|
|
#include "llvm/ADT/STLExtras.h"
|
|
#include "llvm/Support/FileSystem.h"
|
|
#include "llvm/Support/Path.h"
|
|
|
|
namespace include_what_you_use {
|
|
|
|
namespace {
|
|
|
|
vector<HeaderSearchPath>* header_search_paths;
|
|
|
|
// Please keep this in sync with _SOURCE_EXTENSIONS in fix_includes.py.
|
|
const char* source_extensions[] = {
|
|
".c",
|
|
".C",
|
|
".cc",
|
|
".CC",
|
|
".cxx",
|
|
".CXX",
|
|
".cpp",
|
|
".CPP",
|
|
".c++",
|
|
".C++",
|
|
".cp"
|
|
};
|
|
|
|
} // anonymous namespace
|
|
|
|
void SetHeaderSearchPaths(const vector<HeaderSearchPath>& search_paths) {
|
|
if (header_search_paths != nullptr) {
|
|
delete header_search_paths;
|
|
}
|
|
header_search_paths = new vector<HeaderSearchPath>(search_paths);
|
|
}
|
|
|
|
const vector<HeaderSearchPath>& HeaderSearchPaths() {
|
|
if (header_search_paths == nullptr) {
|
|
header_search_paths = new vector<HeaderSearchPath>();
|
|
}
|
|
return *header_search_paths;
|
|
}
|
|
|
|
bool IsHeaderFile(string path) {
|
|
if (EndsWith(path, "\"") || EndsWith(path, ">"))
|
|
path = path.substr(0, path.length() - 1);
|
|
|
|
// Some headers don't have an extension (e.g. <string>), or have an
|
|
// unusual one (the compiler doesn't care), so it's safer to
|
|
// enumerate non-header extensions instead.
|
|
// for (size_t i = 0; i < llvm::array_lengthof(source_extensions); ++i) {
|
|
for (const char* source_extension : source_extensions) {
|
|
if (EndsWith(path, source_extension))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
string Basename(const string& path) {
|
|
string::size_type last_slash = path.rfind('/');
|
|
if (last_slash != string::npos) {
|
|
return path.substr(last_slash + 1);
|
|
}
|
|
return path;
|
|
}
|
|
|
|
string GetCanonicalName(string file_path) {
|
|
// For this special 'path' we just return it.
|
|
// Note that we leave the 'quotes' to make it different from regular paths.
|
|
if (file_path == "<built-in>" || file_path == "<stdin>")
|
|
return file_path;
|
|
|
|
CHECK_(!IsQuotedInclude(file_path));
|
|
|
|
file_path = NormalizeFilePath(file_path);
|
|
|
|
bool stripped_ext = StripRight(&file_path, ".h")
|
|
|| StripRight(&file_path, ".H")
|
|
|| StripRight(&file_path, ".hpp")
|
|
|| StripRight(&file_path, ".hxx")
|
|
|| StripRight(&file_path, ".hh")
|
|
|| StripRight(&file_path, ".inl");
|
|
if (!stripped_ext) {
|
|
for (const char* source_extension : source_extensions) {
|
|
if (StripRight(&file_path, source_extension))
|
|
break;
|
|
}
|
|
}
|
|
|
|
StripRight(&file_path, "_unittest")
|
|
|| StripRight(&file_path, "_regtest")
|
|
|| StripRight(&file_path, "_test")
|
|
|| StripLeft(&file_path, "test_headercompile_");
|
|
StripRight(&file_path, "-inl");
|
|
// .h files in /public/ match .cc files in /internal/
|
|
const string::size_type internal_pos = file_path.find("/internal/");
|
|
if (internal_pos != string::npos)
|
|
file_path = (file_path.substr(0, internal_pos) + "/public/" +
|
|
file_path.substr(internal_pos + strlen("/internal/")));
|
|
|
|
// .h files in /include/ match .cc files in /src/
|
|
const string::size_type include_pos = file_path.find("/include/");
|
|
if (include_pos != string::npos)
|
|
file_path = (file_path.substr(0, include_pos) + "/src/" +
|
|
file_path.substr(include_pos + strlen("/include/")));
|
|
return file_path;
|
|
}
|
|
|
|
string NormalizeFilePath(const string& path) {
|
|
llvm::SmallString<128> normalized(path);
|
|
llvm::sys::path::remove_dots(normalized, /*remove_dot_dot=*/true);
|
|
|
|
#ifdef _WIN32
|
|
// Canonicalize directory separators (forward slashes considered canonical.)
|
|
std::replace(normalized.begin(), normalized.end(), '\\', '/');
|
|
#endif
|
|
|
|
return normalized.str().str();
|
|
}
|
|
|
|
string NormalizeDirPath(const string& path) {
|
|
string result = NormalizeFilePath(path);
|
|
// Ensure trailing slash.
|
|
if (!result.empty() && result.back() != '/')
|
|
result += '/';
|
|
return result;
|
|
}
|
|
|
|
bool IsAbsolutePath(const string& path) {
|
|
return llvm::sys::path::is_absolute(path);
|
|
}
|
|
|
|
string MakeAbsolutePath(const string& path) {
|
|
llvm::SmallString<128> absolute_path(path);
|
|
std::error_code error = llvm::sys::fs::make_absolute(absolute_path);
|
|
CHECK_(!error);
|
|
|
|
return absolute_path.str().str();
|
|
}
|
|
|
|
string MakeAbsolutePath(const string& base_path, const string& relative_path) {
|
|
llvm::SmallString<128> absolute_path(base_path);
|
|
llvm::sys::path::append(absolute_path, relative_path);
|
|
|
|
return absolute_path.str().str();
|
|
}
|
|
|
|
string GetParentPath(const string& path) {
|
|
llvm::StringRef parent = llvm::sys::path::parent_path(path);
|
|
return parent.str();
|
|
}
|
|
|
|
bool StripPathPrefix(string* path, const string& prefix_path) {
|
|
// Only makes sense if both are absolute or both are relative (to same dir).
|
|
CHECK_(IsAbsolutePath(*path) == IsAbsolutePath(prefix_path));
|
|
return StripLeft(path, prefix_path);
|
|
}
|
|
|
|
// Converts a file-path, such as /usr/include/stdio.h, to a
|
|
// quoted include, such as <stdio.h>.
|
|
string ConvertToQuotedInclude(const string& filepath,
|
|
const string& includer_path) {
|
|
// includer_path must be given as an absolute path.
|
|
CHECK_(includer_path.empty() || IsAbsolutePath(includer_path));
|
|
|
|
if (filepath == "<built-in>")
|
|
return filepath;
|
|
|
|
// Get path into same format as header search paths: Absolute and normalized.
|
|
string path = NormalizeFilePath(MakeAbsolutePath(filepath));
|
|
|
|
// Case 1: Uses an explicit entry on the search path (-I) list.
|
|
const vector<HeaderSearchPath>& search_paths = HeaderSearchPaths();
|
|
// HeaderSearchPaths is sorted to be longest-first, so this
|
|
// loop will prefer the longest prefix: /usr/include/c++/4.4/foo
|
|
// will be mapped to <foo>, not <c++/4.4/foo>.
|
|
for (const HeaderSearchPath& entry : search_paths) {
|
|
// All header search paths have a trailing "/", so we'll get a perfect
|
|
// quoted include by just stripping the prefix.
|
|
|
|
if (StripPathPrefix(&path, entry.path)) {
|
|
if (entry.path_type == HeaderSearchPath::kSystemPath)
|
|
return "<" + path + ">";
|
|
else
|
|
return "\"" + path + "\"";
|
|
}
|
|
}
|
|
|
|
// Case 2:
|
|
// Uses the implicit "-I <basename current file>" entry on the search path.
|
|
if (!includer_path.empty())
|
|
StripPathPrefix(&path, NormalizeDirPath(includer_path));
|
|
return "\"" + path + "\"";
|
|
}
|
|
|
|
bool IsQuotedInclude(const string& s) {
|
|
if (s.size() < 2)
|
|
return false;
|
|
return ((StartsWith(s, "<") && EndsWith(s, ">")) ||
|
|
(StartsWith(s, "\"") && EndsWith(s, "\"")));
|
|
}
|
|
|
|
// Returns whether this is a system (as opposed to user) include file,
|
|
// based on where it lives.
|
|
bool IsSystemIncludeFile(const string& filepath) {
|
|
return ConvertToQuotedInclude(filepath)[0] == '<';
|
|
}
|
|
|
|
} // namespace include_what_you_use
|