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