1 /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
2 file Copyright.txt or https://cmake.org/licensing for details. */
3 #include "cmLocalVisualStudioGenerator.h"
4
5 #include "windows.h"
6
7 #include "cmCustomCommand.h"
8 #include "cmCustomCommandGenerator.h"
9 #include "cmGeneratorTarget.h"
10 #include "cmGlobalGenerator.h"
11 #include "cmMakefile.h"
12 #include "cmSourceFile.h"
13 #include "cmSystemTools.h"
14
cmLocalVisualStudioGenerator(cmGlobalGenerator * gg,cmMakefile * mf)15 cmLocalVisualStudioGenerator::cmLocalVisualStudioGenerator(
16 cmGlobalGenerator* gg, cmMakefile* mf)
17 : cmLocalGenerator(gg, mf)
18 {
19 }
20
~cmLocalVisualStudioGenerator()21 cmLocalVisualStudioGenerator::~cmLocalVisualStudioGenerator()
22 {
23 }
24
25 cmGlobalVisualStudioGenerator::VSVersion
GetVersion() const26 cmLocalVisualStudioGenerator::GetVersion() const
27 {
28 cmGlobalVisualStudioGenerator* gg =
29 static_cast<cmGlobalVisualStudioGenerator*>(this->GlobalGenerator);
30 return gg->GetVersion();
31 }
32
ComputeObjectFilenames(std::map<cmSourceFile const *,std::string> & mapping,cmGeneratorTarget const * gt)33 void cmLocalVisualStudioGenerator::ComputeObjectFilenames(
34 std::map<cmSourceFile const*, std::string>& mapping,
35 cmGeneratorTarget const* gt)
36 {
37 char const* custom_ext = gt->GetCustomObjectExtension();
38 std::string dir_max = this->ComputeLongestObjectDirectory(gt);
39
40 // Count the number of object files with each name. Note that
41 // windows file names are not case sensitive.
42 std::map<std::string, int> counts;
43
44 for (auto const& si : mapping) {
45 cmSourceFile const* sf = si.first;
46 std::string objectNameLower = cmSystemTools::LowerCase(
47 cmSystemTools::GetFilenameWithoutLastExtension(sf->GetFullPath()));
48 if (custom_ext) {
49 objectNameLower += custom_ext;
50 } else {
51 objectNameLower +=
52 this->GlobalGenerator->GetLanguageOutputExtension(*sf);
53 }
54 counts[objectNameLower] += 1;
55 }
56
57 // For all source files producing duplicate names we need unique
58 // object name computation.
59
60 for (auto& si : mapping) {
61 cmSourceFile const* sf = si.first;
62 std::string objectName =
63 cmSystemTools::GetFilenameWithoutLastExtension(sf->GetFullPath());
64 if (custom_ext) {
65 objectName += custom_ext;
66 } else {
67 objectName += this->GlobalGenerator->GetLanguageOutputExtension(*sf);
68 }
69 if (counts[cmSystemTools::LowerCase(objectName)] > 1) {
70 const_cast<cmGeneratorTarget*>(gt)->AddExplicitObjectName(sf);
71 bool keptSourceExtension;
72 objectName = this->GetObjectFileNameWithoutTarget(
73 *sf, dir_max, &keptSourceExtension, custom_ext);
74 }
75 si.second = objectName;
76 }
77 }
78
79 std::unique_ptr<cmCustomCommand>
MaybeCreateImplibDir(cmGeneratorTarget * target,const std::string & config,bool isFortran)80 cmLocalVisualStudioGenerator::MaybeCreateImplibDir(cmGeneratorTarget* target,
81 const std::string& config,
82 bool isFortran)
83 {
84 std::unique_ptr<cmCustomCommand> pcc;
85
86 // If an executable exports symbols then VS wants to create an
87 // import library but forgets to create the output directory.
88 // The Intel Fortran plugin always forgets to the directory.
89 if (target->GetType() != cmStateEnums::EXECUTABLE &&
90 !(isFortran && target->GetType() == cmStateEnums::SHARED_LIBRARY)) {
91 return pcc;
92 }
93 std::string outDir =
94 target->GetDirectory(config, cmStateEnums::RuntimeBinaryArtifact);
95 std::string impDir =
96 target->GetDirectory(config, cmStateEnums::ImportLibraryArtifact);
97 if (impDir == outDir) {
98 return pcc;
99 }
100
101 // Add a pre-build event to create the directory.
102 std::vector<std::string> no_output;
103 std::vector<std::string> no_byproducts;
104 std::vector<std::string> no_depends;
105 bool stdPipesUTF8 = true;
106 cmCustomCommandLines commands = cmMakeSingleCommandLine(
107 { cmSystemTools::GetCMakeCommand(), "-E", "make_directory", impDir });
108 pcc.reset(new cmCustomCommand(no_output, no_byproducts, no_depends, commands,
109 cmListFileBacktrace(), nullptr, nullptr,
110 stdPipesUTF8));
111 pcc->SetEscapeOldStyle(false);
112 pcc->SetEscapeAllowMakeVars(true);
113 return pcc;
114 }
115
ReportErrorLabel() const116 const char* cmLocalVisualStudioGenerator::ReportErrorLabel() const
117 {
118 return ":VCReportError";
119 }
120
GetReportErrorLabel() const121 const char* cmLocalVisualStudioGenerator::GetReportErrorLabel() const
122 {
123 return this->ReportErrorLabel();
124 }
125
ConstructScript(cmCustomCommandGenerator const & ccg,const std::string & newline_text)126 std::string cmLocalVisualStudioGenerator::ConstructScript(
127 cmCustomCommandGenerator const& ccg, const std::string& newline_text)
128 {
129 bool useLocal = this->CustomCommandUseLocal();
130 std::string workingDirectory = ccg.GetWorkingDirectory();
131
132 // Avoid leading or trailing newlines.
133 std::string newline;
134
135 // Line to check for error between commands.
136 std::string check_error = newline_text;
137 if (useLocal) {
138 check_error += "if %errorlevel% neq 0 goto :cmEnd";
139 } else {
140 check_error += "if errorlevel 1 goto ";
141 check_error += this->GetReportErrorLabel();
142 }
143
144 // Store the script in a string.
145 std::string script;
146
147 // Open a local context.
148 if (useLocal) {
149 script += newline;
150 newline = newline_text;
151 script += "setlocal";
152 }
153
154 if (!workingDirectory.empty()) {
155 // Change the working directory.
156 script += newline;
157 newline = newline_text;
158 script += "cd ";
159 script += this->ConvertToOutputFormat(workingDirectory, SHELL);
160 script += check_error;
161
162 // Change the working drive.
163 if (workingDirectory.size() > 1 && workingDirectory[1] == ':') {
164 script += newline;
165 newline = newline_text;
166 script += workingDirectory[0];
167 script += workingDirectory[1];
168 script += check_error;
169 }
170 }
171
172 // for visual studio IDE add extra stuff to the PATH
173 // if CMAKE_MSVCIDE_RUN_PATH is set.
174 if (this->GetGlobalGenerator()->IsVisualStudio()) {
175 cmValue extraPath =
176 this->Makefile->GetDefinition("CMAKE_MSVCIDE_RUN_PATH");
177 if (extraPath) {
178 script += newline;
179 newline = newline_text;
180 script += "set PATH=";
181 script += *extraPath;
182 script += ";%PATH%";
183 }
184 }
185
186 // Write each command on a single line.
187 for (unsigned int c = 0; c < ccg.GetNumberOfCommands(); ++c) {
188 // Add this command line.
189 std::string cmd = ccg.GetCommand(c);
190
191 if (cmd.empty()) {
192 continue;
193 }
194
195 // Start a new line.
196 script += newline;
197 newline = newline_text;
198
199 // Use "call " before any invocations of .bat or .cmd files
200 // invoked as custom commands.
201 //
202 std::string suffix;
203 if (cmd.size() > 4) {
204 suffix = cmSystemTools::LowerCase(cmd.substr(cmd.size() - 4));
205 if (suffix == ".bat" || suffix == ".cmd") {
206 script += "call ";
207 }
208 }
209
210 if (workingDirectory.empty()) {
211 script += this->ConvertToOutputFormat(
212 this->MaybeRelativeToCurBinDir(cmd), cmOutputConverter::SHELL);
213 } else {
214 script += this->ConvertToOutputFormat(cmd.c_str(), SHELL);
215 }
216 ccg.AppendArguments(c, script);
217
218 // After each custom command, check for an error result.
219 // If there was an error, jump to the VCReportError label,
220 // skipping the run of any subsequent commands in this
221 // sequence.
222 script += check_error;
223 }
224
225 // Close the local context.
226 if (useLocal) {
227 script += newline;
228 script += ":cmEnd";
229 script += newline;
230 script += "endlocal & call :cmErrorLevel %errorlevel% & goto :cmDone";
231 script += newline;
232 script += ":cmErrorLevel";
233 script += newline;
234 script += "exit /b %1";
235 script += newline;
236 script += ":cmDone";
237 script += newline;
238 script += "if %errorlevel% neq 0 goto ";
239 script += this->GetReportErrorLabel();
240 }
241
242 return script;
243 }
244