summaryrefslogtreecommitdiffstats
path: root/iwyu_path_util.cc
blob: 9987ea47ca2c8b48b83352996898dc59f959b8c2 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
//===--- 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>")
    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