1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "gn/path_output.h"
6
7 #include "base/strings/string_util.h"
8 #include "gn/filesystem_utils.h"
9 #include "gn/output_file.h"
10 #include "gn/string_utils.h"
11 #include "util/build_config.h"
12
PathOutput(const SourceDir & current_dir,const std::string_view & source_root,EscapingMode escaping)13 PathOutput::PathOutput(const SourceDir& current_dir,
14 const std::string_view& source_root,
15 EscapingMode escaping)
16 : current_dir_(current_dir) {
17 inverse_current_dir_ = RebasePath("//", current_dir, source_root);
18 if (!EndsWithSlash(inverse_current_dir_))
19 inverse_current_dir_.push_back('/');
20 options_.mode = escaping;
21 }
22
23 PathOutput::~PathOutput() = default;
24
WriteFile(std::ostream & out,const SourceFile & file) const25 void PathOutput::WriteFile(std::ostream& out, const SourceFile& file) const {
26 WritePathStr(out, file.value());
27 }
28
WriteDir(std::ostream & out,const SourceDir & dir,DirSlashEnding slash_ending) const29 void PathOutput::WriteDir(std::ostream& out,
30 const SourceDir& dir,
31 DirSlashEnding slash_ending) const {
32 if (dir.value() == "/") {
33 // Writing system root is always a slash (this will normally only come up
34 // on Posix systems).
35 if (slash_ending == DIR_NO_LAST_SLASH)
36 out << "/.";
37 else
38 out << "/";
39 } else if (dir.value() == "//") {
40 // Writing out the source root.
41 if (slash_ending == DIR_NO_LAST_SLASH) {
42 // The inverse_current_dir_ will contain a [back]slash at the end, so we
43 // can't just write it out.
44 if (inverse_current_dir_.empty()) {
45 out << ".";
46 } else {
47 out.write(inverse_current_dir_.c_str(),
48 inverse_current_dir_.size() - 1);
49 }
50 } else {
51 if (inverse_current_dir_.empty())
52 out << "./";
53 else
54 out << inverse_current_dir_;
55 }
56 } else if (dir == current_dir_) {
57 // Writing the same directory. This needs special handling here since
58 // we need to output something else other than the input.
59 if (slash_ending == DIR_INCLUDE_LAST_SLASH)
60 out << "./";
61 else
62 out << ".";
63 } else if (slash_ending == DIR_INCLUDE_LAST_SLASH) {
64 WritePathStr(out, dir.value());
65 } else {
66 // DIR_NO_LAST_SLASH mode, just trim the last char.
67 WritePathStr(out,
68 std::string_view(dir.value().data(), dir.value().size() - 1));
69 }
70 }
71
WriteFile(std::ostream & out,const OutputFile & file) const72 void PathOutput::WriteFile(std::ostream& out, const OutputFile& file) const {
73 // Here we assume that the path is already preprocessed.
74 EscapeStringToStream(out, file.value(), options_);
75 }
76
WriteFiles(std::ostream & out,const std::vector<OutputFile> & files) const77 void PathOutput::WriteFiles(std::ostream& out,
78 const std::vector<OutputFile>& files) const {
79 for (const auto& file : files) {
80 out << " ";
81 WriteFile(out, file);
82 }
83 }
84
WriteFiles(std::ostream & out,const UniqueVector<OutputFile> & files) const85 void PathOutput::WriteFiles(std::ostream& out,
86 const UniqueVector<OutputFile>& files) const {
87 for (const auto& file : files) {
88 out << " ";
89 WriteFile(out, file);
90 }
91 }
92
WriteDir(std::ostream & out,const OutputFile & file,DirSlashEnding slash_ending) const93 void PathOutput::WriteDir(std::ostream& out,
94 const OutputFile& file,
95 DirSlashEnding slash_ending) const {
96 DCHECK(file.value().empty() || file.value()[file.value().size() - 1] == '/');
97
98 switch (slash_ending) {
99 case DIR_INCLUDE_LAST_SLASH:
100 EscapeStringToStream(out, file.value(), options_);
101 break;
102 case DIR_NO_LAST_SLASH:
103 if (!file.value().empty() &&
104 file.value()[file.value().size() - 1] == '/') {
105 // Trim trailing slash.
106 EscapeStringToStream(
107 out, std::string_view(file.value().data(), file.value().size() - 1),
108 options_);
109 } else {
110 // Doesn't end with a slash, write the whole thing.
111 EscapeStringToStream(out, file.value(), options_);
112 }
113 break;
114 }
115 }
116
WriteFile(std::ostream & out,const base::FilePath & file) const117 void PathOutput::WriteFile(std::ostream& out,
118 const base::FilePath& file) const {
119 // Assume native file paths are always absolute.
120 EscapeStringToStream(out, FilePathToUTF8(file), options_);
121 }
122
WriteSourceRelativeString(std::ostream & out,const std::string_view & str) const123 void PathOutput::WriteSourceRelativeString(std::ostream& out,
124 const std::string_view& str) const {
125 if (options_.mode == ESCAPE_NINJA_COMMAND) {
126 // Shell escaping needs an intermediate string since it may end up
127 // quoting the whole thing.
128 std::string intermediate;
129 intermediate.reserve(inverse_current_dir_.size() + str.size());
130 intermediate.assign(inverse_current_dir_.c_str(),
131 inverse_current_dir_.size());
132 intermediate.append(str.data(), str.size());
133
134 EscapeStringToStream(
135 out, std::string_view(intermediate.c_str(), intermediate.size()),
136 options_);
137 } else {
138 // Ninja (and none) escaping can avoid the intermediate string and
139 // reprocessing of the inverse_current_dir_.
140 out << inverse_current_dir_;
141 EscapeStringToStream(out, str, options_);
142 }
143 }
144
WritePathStr(std::ostream & out,const std::string_view & str) const145 void PathOutput::WritePathStr(std::ostream& out,
146 const std::string_view& str) const {
147 DCHECK(str.size() > 0 && str[0] == '/');
148
149 if (str.substr(0, current_dir_.value().size()) ==
150 std::string_view(current_dir_.value())) {
151 // The current dir is a prefix of the output file, so we can strip the
152 // prefix and write out the result.
153 EscapeStringToStream(out, str.substr(current_dir_.value().size()),
154 options_);
155 } else if (str.size() >= 2 && str[1] == '/') {
156 WriteSourceRelativeString(out, str.substr(2));
157 } else {
158 // Input begins with one slash, don't write the current directory since
159 // it's system-absolute.
160 #if defined(OS_WIN)
161 // On Windows, trim the leading slash, since the input for absolute
162 // paths will look like "/C:/foo/bar.txt".
163 EscapeStringToStream(out, str.substr(1), options_);
164 #else
165 EscapeStringToStream(out, str, options_);
166 #endif
167 }
168 }
169