packaging: Initial packaging
[platform/upstream/cmake.git] / Source / cmTryRunCommand.cxx
1 /*============================================================================
2   CMake - Cross Platform Makefile Generator
3   Copyright 2000-2009 Kitware, Inc., Insight Software Consortium
4
5   Distributed under the OSI-approved BSD License (the "License");
6   see accompanying file Copyright.txt for details.
7
8   This software is distributed WITHOUT ANY WARRANTY; without even the
9   implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
10   See the License for more information.
11 ============================================================================*/
12 #include "cmTryRunCommand.h"
13 #include "cmCacheManager.h"
14 #include "cmTryCompileCommand.h"
15
16 // cmTryRunCommand
17 bool cmTryRunCommand
18 ::InitialPass(std::vector<std::string> const& argv, cmExecutionStatus &)
19 {
20   if(argv.size() < 4)
21     {
22     return false;
23     }
24
25   if(this->Makefile->GetCMakeInstance()->GetWorkingMode() ==
26                                                       cmake::FIND_PACKAGE_MODE)
27     {
28     this->Makefile->IssueMessage(cmake::FATAL_ERROR,
29             "The TRY_RUN() command is not supported in --find-package mode.");
30     return false;
31     }
32
33   // build an arg list for TryCompile and extract the runArgs,
34   std::vector<std::string> tryCompile;
35
36   this->CompileResultVariable = "";
37   this->RunResultVariable = "";
38   this->OutputVariable = "";
39   this->RunOutputVariable = "";
40   this->CompileOutputVariable = "";
41
42   std::string runArgs;
43   unsigned int i;
44   for (i = 1; i < argv.size(); ++i)
45     {
46     if (argv[i] == "ARGS")
47       {
48       ++i;
49       while (i < argv.size() && argv[i] != "COMPILE_DEFINITIONS" &&
50              argv[i] != "CMAKE_FLAGS")
51         {
52         runArgs += " ";
53         runArgs += argv[i];
54         ++i;
55         }
56       if (i < argv.size())
57         {
58         tryCompile.push_back(argv[i]);
59         }
60       }
61     else
62       {
63       if (argv[i] == "OUTPUT_VARIABLE")
64         {
65         if ( argv.size() <= (i+1) )
66           {
67           cmSystemTools::Error(
68             "OUTPUT_VARIABLE specified but there is no variable");
69           return false;
70           }
71         i++;
72         this->OutputVariable = argv[i];
73         }
74       else if (argv[i] == "RUN_OUTPUT_VARIABLE")
75         {
76         if (argv.size() <= (i + 1))
77           {
78           cmSystemTools::Error(
79             "RUN_OUTPUT_VARIABLE specified but there is no variable");
80           return false;
81           }
82         i++;
83         this->RunOutputVariable = argv[i];
84         }
85       else if (argv[i] == "COMPILE_OUTPUT_VARIABLE")
86         {
87         if (argv.size() <= (i + 1))
88           {
89           cmSystemTools::Error(
90             "COMPILE_OUTPUT_VARIABLE specified but there is no variable");
91           return false;
92           }
93         i++;
94         this->CompileOutputVariable = argv[i];
95         }
96       else
97         {
98         tryCompile.push_back(argv[i]);
99         }
100       }
101     }
102
103   // although they could be used together, don't allow it, because
104   // using OUTPUT_VARIABLE makes crosscompiling harder
105   if (this->OutputVariable.size()
106       && ((this->RunOutputVariable.size())
107        || (this->CompileOutputVariable.size())))
108     {
109     cmSystemTools::Error(
110       "You cannot use OUTPUT_VARIABLE together with COMPILE_OUTPUT_VARIABLE "
111       "or RUN_OUTPUT_VARIABLE. Please use only COMPILE_OUTPUT_VARIABLE and/or "
112       "RUN_OUTPUT_VARIABLE.");
113     return false;
114     }
115
116   bool captureRunOutput = false;
117   if (this->OutputVariable.size())
118     {
119     captureRunOutput = true;
120     tryCompile.push_back("OUTPUT_VARIABLE");
121     tryCompile.push_back(this->OutputVariable);
122     }
123   if (this->CompileOutputVariable.size())
124     {
125     tryCompile.push_back("OUTPUT_VARIABLE");
126     tryCompile.push_back(this->CompileOutputVariable);
127     }
128   if (this->RunOutputVariable.size())
129     {
130     captureRunOutput = true;
131     }
132
133   this->RunResultVariable = argv[0];
134   this->CompileResultVariable = argv[1];
135
136   // do the try compile
137   int res = this->TryCompileCode(tryCompile);
138
139   // now try running the command if it compiled
140   if (!res)
141     {
142     if (this->OutputFile.size() == 0)
143       {
144       cmSystemTools::Error(this->FindErrorMessage.c_str());
145       }
146     else
147       {
148       // "run" it and capture the output
149       std::string runOutputContents;
150       if (this->Makefile->IsOn("CMAKE_CROSSCOMPILING"))
151         {
152         this->DoNotRunExecutable(runArgs,
153                                  argv[3],
154                                  captureRunOutput ? &runOutputContents : 0);
155         }
156       else
157         {
158         this->RunExecutable(runArgs, &runOutputContents);
159         }
160
161       // now put the output into the variables
162       if(this->RunOutputVariable.size())
163         {
164         this->Makefile->AddDefinition(this->RunOutputVariable.c_str(),
165                                       runOutputContents.c_str());
166         }
167
168       if(this->OutputVariable.size())
169         {
170         // if the TryCompileCore saved output in this outputVariable then
171         // prepend that output to this output
172         const char* compileOutput
173                  = this->Makefile->GetDefinition(this->OutputVariable.c_str());
174         if (compileOutput)
175           {
176           runOutputContents = std::string(compileOutput) + runOutputContents;
177           }
178         this->Makefile->AddDefinition(this->OutputVariable.c_str(),
179                                       runOutputContents.c_str());
180         }
181       }
182     }
183
184   // if we created a directory etc, then cleanup after ourselves
185   if(!this->Makefile->GetCMakeInstance()->GetDebugTryCompile())
186     {
187     this->CleanupFiles(this->BinaryDirectory.c_str());
188     }
189   return true;
190 }
191
192 void cmTryRunCommand::RunExecutable(const std::string& runArgs,
193                                     std::string* out)
194 {
195   int retVal = -1;
196   std::string finalCommand = cmSystemTools::ConvertToRunCommandPath(
197                                this->OutputFile.c_str());
198   if (runArgs.size())
199     {
200     finalCommand += runArgs;
201     }
202   int timeout = 0;
203   bool worked = cmSystemTools::RunSingleCommand(finalCommand.c_str(),
204                 out, &retVal,
205                 0, cmSystemTools::OUTPUT_NONE, timeout);
206   // set the run var
207   char retChar[1000];
208   if (worked)
209     {
210     sprintf(retChar, "%i", retVal);
211     }
212   else
213     {
214     strcpy(retChar, "FAILED_TO_RUN");
215     }
216   this->Makefile->AddCacheDefinition(this->RunResultVariable.c_str(), retChar,
217                                      "Result of TRY_RUN",
218                                      cmCacheManager::INTERNAL);
219 }
220
221 /* This is only used when cross compiling. Instead of running the
222  executable, two cache variables are created which will hold the results
223  the executable would have produced.
224 */
225 void cmTryRunCommand::DoNotRunExecutable(const std::string& runArgs,
226                                     const std::string& srcFile,
227                                     std::string* out
228                                     )
229 {
230   // copy the executable out of the CMakeFiles/ directory, so it is not
231   // removed at the end of TRY_RUN and the user can run it manually
232   // on the target platform.
233   std::string copyDest =  this->Makefile->GetHomeOutputDirectory();
234   copyDest += cmake::GetCMakeFilesDirectory();
235   copyDest += "/";
236   copyDest += cmSystemTools::GetFilenameWithoutExtension(
237                                                      this->OutputFile.c_str());
238   copyDest += "-";
239   copyDest += this->RunResultVariable;
240   copyDest += cmSystemTools::GetFilenameExtension(this->OutputFile.c_str());
241   cmSystemTools::CopyFileAlways(this->OutputFile.c_str(), copyDest.c_str());
242
243   std::string resultFileName =  this->Makefile->GetHomeOutputDirectory();
244   resultFileName += "/TryRunResults.cmake";
245
246   std::string detailsString = "For details see ";
247   detailsString += resultFileName;
248
249   std::string internalRunOutputName=this->RunResultVariable+"__TRYRUN_OUTPUT";
250   bool error = false;
251
252   if (this->Makefile->GetDefinition(this->RunResultVariable.c_str()) == 0)
253     {
254     // if the variables doesn't exist, create it with a helpful error text
255     // and mark it as advanced
256     std::string comment;
257     comment += "Run result of TRY_RUN(), indicates whether the executable "
258                "would have been able to run on its target platform.\n";
259     comment += detailsString;
260     this->Makefile->AddCacheDefinition(this->RunResultVariable.c_str(),
261                                        "PLEASE_FILL_OUT-FAILED_TO_RUN",
262                                        comment.c_str(),
263                                        cmCacheManager::STRING);
264
265     cmCacheManager::CacheIterator it = this->Makefile->GetCacheManager()->
266                              GetCacheIterator(this->RunResultVariable.c_str());
267     if ( !it.IsAtEnd() )
268       {
269       it.SetProperty("ADVANCED", "1");
270       }
271
272     error = true;
273     }
274
275   // is the output from the executable used ?
276   if (out!=0)
277     {
278     if (this->Makefile->GetDefinition(internalRunOutputName.c_str()) == 0)
279       {
280       // if the variables doesn't exist, create it with a helpful error text
281       // and mark it as advanced
282       std::string comment;
283       comment+="Output of TRY_RUN(), contains the text, which the executable "
284            "would have printed on stdout and stderr on its target platform.\n";
285       comment += detailsString;
286
287       this->Makefile->AddCacheDefinition(internalRunOutputName.c_str(),
288                                          "PLEASE_FILL_OUT-NOTFOUND",
289                                          comment.c_str(),
290                                          cmCacheManager::STRING);
291       cmCacheManager::CacheIterator it = this->Makefile->GetCacheManager()->
292                                GetCacheIterator(internalRunOutputName.c_str());
293       if ( !it.IsAtEnd() )
294         {
295         it.SetProperty("ADVANCED", "1");
296         }
297
298       error = true;
299       }
300     }
301
302   if (error)
303     {
304     static bool firstTryRun = true;
305     std::ofstream file(resultFileName.c_str(),
306                                   firstTryRun ? std::ios::out : std::ios::app);
307     if ( file )
308       {
309       if (firstTryRun)
310         {
311         file << "# This file was generated by CMake because it detected "
312                 "TRY_RUN() commands\n"
313                 "# in crosscompiling mode. It will be overwritten by the next "
314                 "CMake run.\n"
315                 "# Copy it to a safe location, set the variables to "
316                 "appropriate values\n"
317                 "# and use it then to preset the CMake cache (using -C).\n\n";
318         }
319
320       std::string comment ="\n";
321       comment += this->RunResultVariable;
322       comment += "\n   indicates whether the executable would have been able "
323                  "to run on its\n"
324                  "   target platform. If so, set ";
325       comment += this->RunResultVariable;
326       comment += " to\n"
327                  "   the exit code (in many cases 0 for success), otherwise "
328                  "enter \"FAILED_TO_RUN\".\n";
329       if (out!=0)
330         {
331         comment += internalRunOutputName;
332         comment += "\n   contains the text the executable "
333                    "would have printed on stdout and stderr.\n"
334                   "   If the executable would not have been able to run, set ";
335         comment += internalRunOutputName;
336         comment += " empty.\n"
337                    "   Otherwise check if the output is evaluated by the "
338                    "calling CMake code. If so,\n"
339                    "   check what the source file would have printed when "
340                    "called with the given arguments.\n";
341         }
342       comment += "The ";
343       comment += this->CompileResultVariable;
344       comment += " variable holds the build result for this TRY_RUN().\n\n"
345                  "Source file   : ";
346       comment += srcFile + "\n";
347       comment += "Executable    : ";
348       comment += copyDest + "\n";
349       comment += "Run arguments : ";
350       comment += runArgs;
351       comment += "\n";
352       comment += "   Called from: " + this->Makefile->GetListFileStack();
353       cmsys::SystemTools::ReplaceString(comment, "\n", "\n# ");
354       file << comment << "\n\n";
355
356       file << "SET( " << this->RunResultVariable << " \n     \""
357            << this->Makefile->GetDefinition(this->RunResultVariable.c_str())
358            << "\"\n     CACHE STRING \"Result from TRY_RUN\" FORCE)\n\n";
359
360       if (out!=0)
361         {
362         file << "SET( " << internalRunOutputName << " \n     \""
363              << this->Makefile->GetDefinition(internalRunOutputName.c_str())
364              << "\"\n     CACHE STRING \"Output from TRY_RUN\" FORCE)\n\n";
365         }
366       file.close();
367       }
368     firstTryRun = false;
369
370     std::string errorMessage = "TRY_RUN() invoked in cross-compiling mode, "
371                                "please set the following cache variables "
372                                "appropriately:\n";
373     errorMessage += "   " + this->RunResultVariable + " (advanced)\n";
374     if (out!=0)
375       {
376       errorMessage += "   " + internalRunOutputName + " (advanced)\n";
377       }
378     errorMessage += detailsString;
379     cmSystemTools::Error(errorMessage.c_str());
380     return;
381     }
382
383   if (out!=0)
384     {
385     (*out) = this->Makefile->GetDefinition(internalRunOutputName.c_str());
386     }
387 }