1 //===--- iwyu_path_util.cc - file-path utilities for include-what-you-use -===//
2 //
3 // The LLVM Compiler Infrastructure
4 //
5 // This file is distributed under the University of Illinois Open Source
6 // License. See LICENSE.TXT for details.
7 //
8 //===----------------------------------------------------------------------===//
9
10 #include "iwyu_path_util.h"
11
12 #include <algorithm> // for std::replace
13 #include <cstddef>
14 #include <cstring> // for strlen
15 #include <system_error>
16
17 #include "iwyu_stl_util.h"
18
19 #include "llvm/ADT/SmallString.h"
20 #include "llvm/ADT/StringRef.h"
21 #include "llvm/ADT/STLExtras.h"
22 #include "llvm/Support/FileSystem.h"
23 #include "llvm/Support/Path.h"
24
25 namespace include_what_you_use {
26
27 namespace {
28
29 vector<HeaderSearchPath>* header_search_paths;
30
31 // Please keep this in sync with _SOURCE_EXTENSIONS in fix_includes.py.
32 const char* source_extensions[] = {
33 ".c",
34 ".C",
35 ".cc",
36 ".CC",
37 ".cxx",
38 ".CXX",
39 ".cpp",
40 ".CPP",
41 ".c++",
42 ".C++",
43 ".cp"
44 };
45
46 } // anonymous namespace
47
SetHeaderSearchPaths(const vector<HeaderSearchPath> & search_paths)48 void SetHeaderSearchPaths(const vector<HeaderSearchPath>& search_paths) {
49 if (header_search_paths != nullptr) {
50 delete header_search_paths;
51 }
52 header_search_paths = new vector<HeaderSearchPath>(search_paths);
53 }
54
HeaderSearchPaths()55 const vector<HeaderSearchPath>& HeaderSearchPaths() {
56 if (header_search_paths == nullptr) {
57 header_search_paths = new vector<HeaderSearchPath>();
58 }
59 return *header_search_paths;
60 }
61
IsHeaderFile(string path)62 bool IsHeaderFile(string path) {
63 if (EndsWith(path, "\"") || EndsWith(path, ">"))
64 path = path.substr(0, path.length() - 1);
65
66 // Some headers don't have an extension (e.g. <string>), or have an
67 // unusual one (the compiler doesn't care), so it's safer to
68 // enumerate non-header extensions instead.
69 // for (size_t i = 0; i < llvm::array_lengthof(source_extensions); ++i) {
70 for (const char* source_extension : source_extensions) {
71 if (EndsWith(path, source_extension))
72 return false;
73 }
74
75 return true;
76 }
77
Basename(const string & path)78 string Basename(const string& path) {
79 string::size_type last_slash = path.rfind('/');
80 if (last_slash != string::npos) {
81 return path.substr(last_slash + 1);
82 }
83 return path;
84 }
85
GetCanonicalName(string file_path)86 string GetCanonicalName(string file_path) {
87 // For this special 'path' we just return it.
88 // Note that we leave the 'quotes' to make it different from regular paths.
89 if (file_path == "<built-in>")
90 return file_path;
91
92 CHECK_(!IsQuotedInclude(file_path));
93
94 file_path = NormalizeFilePath(file_path);
95
96 bool stripped_ext = StripRight(&file_path, ".h")
97 || StripRight(&file_path, ".H")
98 || StripRight(&file_path, ".hpp")
99 || StripRight(&file_path, ".hxx")
100 || StripRight(&file_path, ".hh")
101 || StripRight(&file_path, ".inl");
102 if (!stripped_ext) {
103 for (const char* source_extension : source_extensions) {
104 if (StripRight(&file_path, source_extension))
105 break;
106 }
107 }
108
109 StripRight(&file_path, "_unittest")
110 || StripRight(&file_path, "_regtest")
111 || StripRight(&file_path, "_test")
112 || StripLeft(&file_path, "test_headercompile_");
113 StripRight(&file_path, "-inl");
114 // .h files in /public/ match .cc files in /internal/
115 const string::size_type internal_pos = file_path.find("/internal/");
116 if (internal_pos != string::npos)
117 file_path = (file_path.substr(0, internal_pos) + "/public/" +
118 file_path.substr(internal_pos + strlen("/internal/")));
119
120 // .h files in /include/ match .cc files in /src/
121 const string::size_type include_pos = file_path.find("/include/");
122 if (include_pos != string::npos)
123 file_path = (file_path.substr(0, include_pos) + "/src/" +
124 file_path.substr(include_pos + strlen("/include/")));
125 return file_path;
126 }
127
NormalizeFilePath(const string & path)128 string NormalizeFilePath(const string& path) {
129 llvm::SmallString<128> normalized(path);
130 llvm::sys::path::remove_dots(normalized, /*remove_dot_dot=*/true);
131
132 #ifdef _WIN32
133 // Canonicalize directory separators (forward slashes considered canonical.)
134 std::replace(normalized.begin(), normalized.end(), '\\', '/');
135 #endif
136
137 return normalized.str().str();
138 }
139
NormalizeDirPath(const string & path)140 string NormalizeDirPath(const string& path) {
141 string result = NormalizeFilePath(path);
142 // Ensure trailing slash.
143 if (!result.empty() && result.back() != '/')
144 result += '/';
145 return result;
146 }
147
IsAbsolutePath(const string & path)148 bool IsAbsolutePath(const string& path) {
149 return llvm::sys::path::is_absolute(path);
150 }
151
MakeAbsolutePath(const string & path)152 string MakeAbsolutePath(const string& path) {
153 llvm::SmallString<128> absolute_path(path);
154 std::error_code error = llvm::sys::fs::make_absolute(absolute_path);
155 CHECK_(!error);
156
157 return absolute_path.str().str();
158 }
159
MakeAbsolutePath(const string & base_path,const string & relative_path)160 string MakeAbsolutePath(const string& base_path, const string& relative_path) {
161 llvm::SmallString<128> absolute_path(base_path);
162 llvm::sys::path::append(absolute_path, relative_path);
163
164 return absolute_path.str().str();
165 }
166
GetParentPath(const string & path)167 string GetParentPath(const string& path) {
168 llvm::StringRef parent = llvm::sys::path::parent_path(path);
169 return parent.str();
170 }
171
StripPathPrefix(string * path,const string & prefix_path)172 bool StripPathPrefix(string* path, const string& prefix_path) {
173 // Only makes sense if both are absolute or both are relative (to same dir).
174 CHECK_(IsAbsolutePath(*path) == IsAbsolutePath(prefix_path));
175 return StripLeft(path, prefix_path);
176 }
177
178 // Converts a file-path, such as /usr/include/stdio.h, to a
179 // quoted include, such as <stdio.h>.
ConvertToQuotedInclude(const string & filepath,const string & includer_path)180 string ConvertToQuotedInclude(const string& filepath,
181 const string& includer_path) {
182 // includer_path must be given as an absolute path.
183 CHECK_(includer_path.empty() || IsAbsolutePath(includer_path));
184
185 if (filepath == "<built-in>")
186 return filepath;
187
188 // Get path into same format as header search paths: Absolute and normalized.
189 string path = NormalizeFilePath(MakeAbsolutePath(filepath));
190
191 // Case 1: Uses an explicit entry on the search path (-I) list.
192 const vector<HeaderSearchPath>& search_paths = HeaderSearchPaths();
193 // HeaderSearchPaths is sorted to be longest-first, so this
194 // loop will prefer the longest prefix: /usr/include/c++/4.4/foo
195 // will be mapped to <foo>, not <c++/4.4/foo>.
196 for (const HeaderSearchPath& entry : search_paths) {
197 // All header search paths have a trailing "/", so we'll get a perfect
198 // quoted include by just stripping the prefix.
199
200 if (StripPathPrefix(&path, entry.path)) {
201 if (entry.path_type == HeaderSearchPath::kSystemPath)
202 return "<" + path + ">";
203 else
204 return "\"" + path + "\"";
205 }
206 }
207
208 // Case 2:
209 // Uses the implicit "-I <basename current file>" entry on the search path.
210 if (!includer_path.empty())
211 StripPathPrefix(&path, NormalizeDirPath(includer_path));
212 return "\"" + path + "\"";
213 }
214
IsQuotedInclude(const string & s)215 bool IsQuotedInclude(const string& s) {
216 if (s.size() < 2)
217 return false;
218 return ((StartsWith(s, "<") && EndsWith(s, ">")) ||
219 (StartsWith(s, "\"") && EndsWith(s, "\"")));
220 }
221
222 // Returns whether this is a system (as opposed to user) include file,
223 // based on where it lives.
IsSystemIncludeFile(const string & filepath)224 bool IsSystemIncludeFile(const string& filepath) {
225 return ConvertToQuotedInclude(filepath)[0] == '<';
226 }
227
228 } // namespace include_what_you_use
229