Imported Upstream version 2.8.12.2
[platform/upstream/cmake.git] / Source / cmCoreTryCompile.cxx
1 /*============================================================================
2   CMake - Cross Platform Makefile Generator
3   Copyright 2000-2011 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 "cmCoreTryCompile.h"
13 #include "cmake.h"
14 #include "cmCacheManager.h"
15 #include "cmLocalGenerator.h"
16 #include "cmGlobalGenerator.h"
17 #include "cmExportTryCompileFileGenerator.h"
18 #include <cmsys/Directory.hxx>
19
20 #include <assert.h>
21
22 int cmCoreTryCompile::TryCompileCode(std::vector<std::string> const& argv)
23 {
24   this->BinaryDirectory = argv[1].c_str();
25   this->OutputFile = "";
26   // which signature were we called with ?
27   this->SrcFileSignature = true;
28
29   const char* sourceDirectory = argv[2].c_str();
30   const char* projectName = 0;
31   const char* targetName = 0;
32   std::vector<std::string> cmakeFlags;
33   std::vector<std::string> compileDefs;
34   std::string outputVariable;
35   std::string copyFile;
36   std::string copyFileError;
37   std::vector<cmTarget*> targets;
38   std::string libsToLink = " ";
39   bool useOldLinkLibs = true;
40   char targetNameBuf[64];
41   bool didOutputVariable = false;
42   bool didCopyFile = false;
43   bool didCopyFileError = false;
44   bool useSources = argv[2] == "SOURCES";
45   std::vector<std::string> sources;
46
47   enum Doing { DoingNone, DoingCMakeFlags, DoingCompileDefinitions,
48                DoingLinkLibraries, DoingOutputVariable, DoingCopyFile,
49                DoingCopyFileError, DoingSources };
50   Doing doing = useSources? DoingSources : DoingNone;
51   for(size_t i=3; i < argv.size(); ++i)
52     {
53     if(argv[i] == "CMAKE_FLAGS")
54       {
55       doing = DoingCMakeFlags;
56       // CMAKE_FLAGS is the first argument because we need an argv[0] that
57       // is not used, so it matches regular command line parsing which has
58       // the program name as arg 0
59       cmakeFlags.push_back(argv[i]);
60       }
61     else if(argv[i] == "COMPILE_DEFINITIONS")
62       {
63       doing = DoingCompileDefinitions;
64       }
65     else if(argv[i] == "LINK_LIBRARIES")
66       {
67       doing = DoingLinkLibraries;
68       useOldLinkLibs = false;
69       }
70     else if(argv[i] == "OUTPUT_VARIABLE")
71       {
72       doing = DoingOutputVariable;
73       didOutputVariable = true;
74       }
75     else if(argv[i] == "COPY_FILE")
76       {
77       doing = DoingCopyFile;
78       didCopyFile = true;
79       }
80     else if(argv[i] == "COPY_FILE_ERROR")
81       {
82       doing = DoingCopyFileError;
83       didCopyFileError = true;
84       }
85     else if(doing == DoingCMakeFlags)
86       {
87       cmakeFlags.push_back(argv[i]);
88       }
89     else if(doing == DoingCompileDefinitions)
90       {
91       compileDefs.push_back(argv[i]);
92       }
93     else if(doing == DoingLinkLibraries)
94       {
95       libsToLink += "\"" + cmSystemTools::TrimWhitespace(argv[i]) + "\" ";
96       if(cmTarget *tgt = this->Makefile->FindTargetToUse(argv[i].c_str()))
97         {
98         switch(tgt->GetType())
99           {
100           case cmTarget::SHARED_LIBRARY:
101           case cmTarget::STATIC_LIBRARY:
102           case cmTarget::UNKNOWN_LIBRARY:
103             break;
104           case cmTarget::EXECUTABLE:
105             if (tgt->IsExecutableWithExports())
106               {
107               break;
108               }
109           default:
110             this->Makefile->IssueMessage(cmake::FATAL_ERROR,
111               "Only libraries may be used as try_compile IMPORTED "
112               "LINK_LIBRARIES.  Got " + std::string(tgt->GetName()) + " of "
113               "type " + tgt->GetTargetTypeName(tgt->GetType()) + ".");
114             return -1;
115           }
116         if (tgt->IsImported())
117           {
118           targets.push_back(tgt);
119           }
120         }
121       }
122     else if(doing == DoingOutputVariable)
123       {
124       outputVariable = argv[i].c_str();
125       doing = DoingNone;
126       }
127     else if(doing == DoingCopyFile)
128       {
129       copyFile = argv[i].c_str();
130       doing = DoingNone;
131       }
132     else if(doing == DoingCopyFileError)
133       {
134       copyFileError = argv[i].c_str();
135       doing = DoingNone;
136       }
137     else if(doing == DoingSources)
138       {
139       sources.push_back(argv[i]);
140       }
141     else if(i == 3)
142       {
143       this->SrcFileSignature = false;
144       projectName = argv[i].c_str();
145       }
146     else if(i == 4 && !this->SrcFileSignature)
147       {
148       targetName = argv[i].c_str();
149       }
150     else
151       {
152       cmOStringStream m;
153       m << "try_compile given unknown argument \"" << argv[i] << "\".";
154       this->Makefile->IssueMessage(cmake::AUTHOR_WARNING, m.str());
155       }
156     }
157
158   if(didCopyFile && copyFile.empty())
159     {
160     this->Makefile->IssueMessage(cmake::FATAL_ERROR,
161       "COPY_FILE must be followed by a file path");
162     return -1;
163     }
164
165   if(didCopyFileError && copyFileError.empty())
166     {
167     this->Makefile->IssueMessage(cmake::FATAL_ERROR,
168       "COPY_FILE_ERROR must be followed by a variable name");
169     return -1;
170     }
171
172   if(didCopyFileError && !didCopyFile)
173     {
174     this->Makefile->IssueMessage(cmake::FATAL_ERROR,
175       "COPY_FILE_ERROR may be used only with COPY_FILE");
176     return -1;
177     }
178
179   if(didOutputVariable && outputVariable.empty())
180     {
181     this->Makefile->IssueMessage(cmake::FATAL_ERROR,
182       "OUTPUT_VARIABLE must be followed by a variable name");
183     return -1;
184     }
185
186   if(useSources && sources.empty())
187     {
188     this->Makefile->IssueMessage(cmake::FATAL_ERROR,
189       "SOURCES must be followed by at least one source file");
190     return -1;
191     }
192
193   // compute the binary dir when TRY_COMPILE is called with a src file
194   // signature
195   if (this->SrcFileSignature)
196     {
197     this->BinaryDirectory += cmake::GetCMakeFilesDirectory();
198     this->BinaryDirectory += "/CMakeTmp";
199     }
200   else
201     {
202     // only valid for srcfile signatures
203     if (compileDefs.size())
204       {
205       this->Makefile->IssueMessage(cmake::FATAL_ERROR,
206         "COMPILE_DEFINITIONS specified on a srcdir type TRY_COMPILE");
207       return -1;
208       }
209     if (copyFile.size())
210       {
211       this->Makefile->IssueMessage(cmake::FATAL_ERROR,
212         "COPY_FILE specified on a srcdir type TRY_COMPILE");
213       return -1;
214       }
215     }
216   // make sure the binary directory exists
217   cmSystemTools::MakeDirectory(this->BinaryDirectory.c_str());
218
219   // do not allow recursive try Compiles
220   if (this->BinaryDirectory == this->Makefile->GetHomeOutputDirectory())
221     {
222     cmOStringStream e;
223     e << "Attempt at a recursive or nested TRY_COMPILE in directory\n"
224       << "  " << this->BinaryDirectory << "\n";
225     this->Makefile->IssueMessage(cmake::FATAL_ERROR, e.str());
226     return -1;
227     }
228
229   std::string outFileName = this->BinaryDirectory + "/CMakeLists.txt";
230   // which signature are we using? If we are using var srcfile bindir
231   if (this->SrcFileSignature)
232     {
233     // remove any CMakeCache.txt files so we will have a clean test
234     std::string ccFile = this->BinaryDirectory + "/CMakeCache.txt";
235     cmSystemTools::RemoveFile(ccFile.c_str());
236
237     // Choose sources.
238     if(!useSources)
239       {
240       sources.push_back(argv[2]);
241       }
242
243     // Detect languages to enable.
244     cmLocalGenerator* lg = this->Makefile->GetLocalGenerator();
245     cmGlobalGenerator* gg = lg->GetGlobalGenerator();
246     std::set<std::string> testLangs;
247     for(std::vector<std::string>::iterator si = sources.begin();
248         si != sources.end(); ++si)
249       {
250       std::string ext = cmSystemTools::GetFilenameLastExtension(*si);
251       if(const char* lang = gg->GetLanguageFromExtension(ext.c_str()))
252         {
253         testLangs.insert(lang);
254         }
255       else
256         {
257         cmOStringStream err;
258         err << "Unknown extension \"" << ext << "\" for file\n"
259             << "  " << *si << "\n"
260             << "try_compile() works only for enabled languages.  "
261             << "Currently these are:\n ";
262         std::vector<std::string> langs;
263         gg->GetEnabledLanguages(langs);
264         for(std::vector<std::string>::iterator l = langs.begin();
265             l != langs.end(); ++l)
266           {
267           err << " " << *l;
268           }
269         err << "\nSee project() command to enable other languages.";
270         this->Makefile->IssueMessage(cmake::FATAL_ERROR, err.str());
271         return -1;
272         }
273       }
274
275     // we need to create a directory and CMakeLists file etc...
276     // first create the directories
277     sourceDirectory = this->BinaryDirectory.c_str();
278
279     // now create a CMakeLists.txt file in that directory
280     FILE *fout = fopen(outFileName.c_str(),"w");
281     if (!fout)
282       {
283       cmOStringStream e;
284       e << "Failed to open\n"
285         << "  " << outFileName.c_str() << "\n"
286         << cmSystemTools::GetLastSystemError();
287       this->Makefile->IssueMessage(cmake::FATAL_ERROR, e.str());
288       return -1;
289       }
290
291     const char* def = this->Makefile->GetDefinition("CMAKE_MODULE_PATH");
292     fprintf(fout, "cmake_minimum_required(VERSION %u.%u.%u.%u)\n",
293             cmVersion::GetMajorVersion(), cmVersion::GetMinorVersion(),
294             cmVersion::GetPatchVersion(), cmVersion::GetTweakVersion());
295     if(def)
296       {
297       fprintf(fout, "SET(CMAKE_MODULE_PATH %s)\n", def);
298       }
299
300     std::string projectLangs;
301     for(std::set<std::string>::iterator li = testLangs.begin();
302         li != testLangs.end(); ++li)
303       {
304       projectLangs += " " + *li;
305       std::string rulesOverrideBase = "CMAKE_USER_MAKE_RULES_OVERRIDE";
306       std::string rulesOverrideLang = rulesOverrideBase + "_" + *li;
307       if(const char* rulesOverridePath =
308          this->Makefile->GetDefinition(rulesOverrideLang.c_str()))
309         {
310         fprintf(fout, "SET(%s \"%s\")\n",
311                 rulesOverrideLang.c_str(), rulesOverridePath);
312         }
313       else if(const char* rulesOverridePath2 =
314               this->Makefile->GetDefinition(rulesOverrideBase.c_str()))
315         {
316         fprintf(fout, "SET(%s \"%s\")\n",
317                 rulesOverrideBase.c_str(), rulesOverridePath2);
318         }
319       }
320     fprintf(fout, "PROJECT(CMAKE_TRY_COMPILE%s)\n", projectLangs.c_str());
321     fprintf(fout, "SET(CMAKE_VERBOSE_MAKEFILE 1)\n");
322     for(std::set<std::string>::iterator li = testLangs.begin();
323         li != testLangs.end(); ++li)
324       {
325       std::string langFlags = "CMAKE_" + *li + "_FLAGS";
326       const char* flags = this->Makefile->GetDefinition(langFlags.c_str());
327       fprintf(fout, "SET(CMAKE_%s_FLAGS %s)\n", li->c_str(),
328               lg->EscapeForCMake(flags?flags:"").c_str());
329       fprintf(fout, "SET(CMAKE_%s_FLAGS \"${CMAKE_%s_FLAGS}"
330               " ${COMPILE_DEFINITIONS}\")\n", li->c_str(), li->c_str());
331       }
332     fprintf(fout, "INCLUDE_DIRECTORIES(${INCLUDE_DIRECTORIES})\n");
333     fprintf(fout, "SET(CMAKE_SUPPRESS_REGENERATION 1)\n");
334     fprintf(fout, "LINK_DIRECTORIES(${LINK_DIRECTORIES})\n");
335     // handle any compile flags we need to pass on
336     if (compileDefs.size())
337       {
338       fprintf(fout, "ADD_DEFINITIONS( ");
339       for (size_t i = 0; i < compileDefs.size(); ++i)
340         {
341         fprintf(fout,"%s ",compileDefs[i].c_str());
342         }
343       fprintf(fout, ")\n");
344       }
345
346     /* Use a random file name to avoid rapid creation and deletion
347        of the same executable name (some filesystems fail on that).  */
348     sprintf(targetNameBuf, "cmTryCompileExec%u",
349             cmSystemTools::RandomSeed());
350     targetName = targetNameBuf;
351
352     if (!targets.empty())
353       {
354       std::string fname = "/" + std::string(targetName) + "Targets.cmake";
355       cmExportTryCompileFileGenerator tcfg;
356       tcfg.SetExportFile((this->BinaryDirectory + fname).c_str());
357       tcfg.SetExports(targets);
358       tcfg.SetConfig(this->Makefile->GetDefinition(
359                                           "CMAKE_TRY_COMPILE_CONFIGURATION"));
360
361       if(!tcfg.GenerateImportFile())
362         {
363         this->Makefile->IssueMessage(cmake::FATAL_ERROR,
364                                      "could not write export file.");
365         fclose(fout);
366         return -1;
367         }
368       fprintf(fout,
369               "\ninclude(\"${CMAKE_CURRENT_LIST_DIR}/%s\")\n\n",
370               fname.c_str());
371       }
372
373     /* for the TRY_COMPILEs we want to be able to specify the architecture.
374       So the user can set CMAKE_OSX_ARCHITECTURE to i386;ppc and then set
375       CMAKE_TRY_COMPILE_OSX_ARCHITECTURE first to i386 and then to ppc to
376       have the tests run for each specific architecture. Since
377       cmLocalGenerator doesn't allow building for "the other"
378       architecture only via CMAKE_OSX_ARCHITECTURES.
379       */
380     if(this->Makefile->GetDefinition("CMAKE_TRY_COMPILE_OSX_ARCHITECTURES")!=0)
381       {
382       std::string flag="-DCMAKE_OSX_ARCHITECTURES=";
383       flag += this->Makefile->GetSafeDefinition(
384                                         "CMAKE_TRY_COMPILE_OSX_ARCHITECTURES");
385       cmakeFlags.push_back(flag);
386       }
387     else if (this->Makefile->GetDefinition("CMAKE_OSX_ARCHITECTURES")!=0)
388       {
389       std::string flag="-DCMAKE_OSX_ARCHITECTURES=";
390       flag += this->Makefile->GetSafeDefinition("CMAKE_OSX_ARCHITECTURES");
391       cmakeFlags.push_back(flag);
392       }
393     /* on APPLE also pass CMAKE_OSX_SYSROOT to the try_compile */
394     if(this->Makefile->GetDefinition("CMAKE_OSX_SYSROOT")!=0)
395       {
396       std::string flag="-DCMAKE_OSX_SYSROOT=";
397       flag += this->Makefile->GetSafeDefinition("CMAKE_OSX_SYSROOT");
398       cmakeFlags.push_back(flag);
399       }
400     /* on APPLE also pass CMAKE_OSX_DEPLOYMENT_TARGET to the try_compile */
401     if(this->Makefile->GetDefinition("CMAKE_OSX_DEPLOYMENT_TARGET")!=0)
402       {
403       std::string flag="-DCMAKE_OSX_DEPLOYMENT_TARGET=";
404       flag += this->Makefile->GetSafeDefinition("CMAKE_OSX_DEPLOYMENT_TARGET");
405       cmakeFlags.push_back(flag);
406       }
407     if(this->Makefile->GetDefinition("CMAKE_POSITION_INDEPENDENT_CODE")!=0)
408       {
409       fprintf(fout, "SET(CMAKE_POSITION_INDEPENDENT_CODE \"ON\")\n");
410       }
411
412     /* Put the executable at a known location (for COPY_FILE).  */
413     fprintf(fout, "SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY \"%s\")\n",
414             this->BinaryDirectory.c_str());
415     /* Create the actual executable.  */
416     fprintf(fout, "ADD_EXECUTABLE(%s", targetName);
417     for(std::vector<std::string>::iterator si = sources.begin();
418         si != sources.end(); ++si)
419       {
420       fprintf(fout, " \"%s\"", si->c_str());
421
422       // Add dependencies on any non-temporary sources.
423       if(si->find("CMakeTmp") == si->npos)
424         {
425         this->Makefile->AddCMakeDependFile(*si);
426         }
427       }
428     fprintf(fout, ")\n");
429     if (useOldLinkLibs)
430       {
431       fprintf(fout,
432               "TARGET_LINK_LIBRARIES(%s ${LINK_LIBRARIES})\n",targetName);
433       }
434     else
435       {
436       fprintf(fout, "TARGET_LINK_LIBRARIES(%s %s)\n",
437               targetName,
438               libsToLink.c_str());
439       }
440     fclose(fout);
441     projectName = "CMAKE_TRY_COMPILE";
442     }
443
444   bool erroroc = cmSystemTools::GetErrorOccuredFlag();
445   cmSystemTools::ResetErrorOccuredFlag();
446   std::string output;
447   // actually do the try compile now that everything is setup
448   int res = this->Makefile->TryCompile(sourceDirectory,
449                                        this->BinaryDirectory.c_str(),
450                                        projectName,
451                                        targetName,
452                                        this->SrcFileSignature,
453                                        &cmakeFlags,
454                                        &output);
455   if ( erroroc )
456     {
457     cmSystemTools::SetErrorOccured();
458     }
459
460   // set the result var to the return value to indicate success or failure
461   this->Makefile->AddCacheDefinition(argv[0].c_str(),
462                                      (res == 0 ? "TRUE" : "FALSE"),
463                                      "Result of TRY_COMPILE",
464                                      cmCacheManager::INTERNAL);
465
466   if ( outputVariable.size() > 0 )
467     {
468     this->Makefile->AddDefinition(outputVariable.c_str(), output.c_str());
469     }
470
471   if (this->SrcFileSignature)
472     {
473     std::string copyFileErrorMessage;
474     this->FindOutputFile(targetName);
475
476     if ((res==0) && (copyFile.size()))
477       {
478       if(this->OutputFile.empty() ||
479          !cmSystemTools::CopyFileAlways(this->OutputFile.c_str(),
480                                         copyFile.c_str()))
481         {
482         cmOStringStream emsg;
483         emsg << "Cannot copy output executable\n"
484              << "  '" << this->OutputFile.c_str() << "'\n"
485              << "to destination specified by COPY_FILE:\n"
486              << "  '" << copyFile.c_str() << "'\n";
487         if(!this->FindErrorMessage.empty())
488           {
489           emsg << this->FindErrorMessage.c_str();
490           }
491         if(copyFileError.empty())
492           {
493           this->Makefile->IssueMessage(cmake::FATAL_ERROR, emsg.str());
494           return -1;
495           }
496         else
497           {
498           copyFileErrorMessage = emsg.str();
499           }
500         }
501       }
502
503     if(!copyFileError.empty())
504       {
505       this->Makefile->AddDefinition(copyFileError.c_str(),
506                                     copyFileErrorMessage.c_str());
507       }
508     }
509   return res;
510 }
511
512 void cmCoreTryCompile::CleanupFiles(const char* binDir)
513 {
514   if ( !binDir )
515     {
516     return;
517     }
518
519   std::string bdir = binDir;
520   if(bdir.find("CMakeTmp") == std::string::npos)
521     {
522     cmSystemTools::Error(
523       "TRY_COMPILE attempt to remove -rf directory that does not contain "
524       "CMakeTmp:", binDir);
525     return;
526     }
527
528   cmsys::Directory dir;
529   dir.Load(binDir);
530   size_t fileNum;
531   std::set<cmStdString> deletedFiles;
532   for (fileNum = 0; fileNum <  dir.GetNumberOfFiles(); ++fileNum)
533     {
534     if (strcmp(dir.GetFile(static_cast<unsigned long>(fileNum)),".") &&
535         strcmp(dir.GetFile(static_cast<unsigned long>(fileNum)),".."))
536       {
537
538       if(deletedFiles.find( dir.GetFile(static_cast<unsigned long>(fileNum)))
539          == deletedFiles.end())
540         {
541         deletedFiles.insert(dir.GetFile(static_cast<unsigned long>(fileNum)));
542         std::string fullPath = binDir;
543         fullPath += "/";
544         fullPath += dir.GetFile(static_cast<unsigned long>(fileNum));
545         if(cmSystemTools::FileIsDirectory(fullPath.c_str()))
546           {
547           this->CleanupFiles(fullPath.c_str());
548           cmSystemTools::RemoveADirectory(fullPath.c_str());
549           }
550         else
551           {
552           // Sometimes anti-virus software hangs on to new files so we
553           // cannot delete them immediately.  Try a few times.
554           int tries = 5;
555           while(!cmSystemTools::RemoveFile(fullPath.c_str()) &&
556                 --tries && cmSystemTools::FileExists(fullPath.c_str()))
557             {
558             cmSystemTools::Delay(500);
559             }
560           if(tries == 0)
561             {
562             std::string m = "Remove failed on file: " + fullPath;
563             cmSystemTools::ReportLastSystemError(m.c_str());
564             }
565           }
566         }
567       }
568     }
569 }
570
571 void cmCoreTryCompile::FindOutputFile(const char* targetName)
572 {
573   this->FindErrorMessage = "";
574   this->OutputFile = "";
575   std::string tmpOutputFile = "/";
576   tmpOutputFile += targetName;
577   tmpOutputFile +=this->Makefile->GetSafeDefinition("CMAKE_EXECUTABLE_SUFFIX");
578
579   // a list of directories where to search for the compilation result
580   // at first directly in the binary dir
581   std::vector<std::string> searchDirs;
582   searchDirs.push_back("");
583
584   const char* config = this->Makefile->GetDefinition(
585                                             "CMAKE_TRY_COMPILE_CONFIGURATION");
586   // if a config was specified try that first
587   if (config && config[0])
588     {
589     std::string tmp = "/";
590     tmp += config;
591     searchDirs.push_back(tmp);
592     }
593   searchDirs.push_back("/Debug");
594   searchDirs.push_back("/Development");
595
596   for(std::vector<std::string>::const_iterator it = searchDirs.begin();
597       it != searchDirs.end();
598       ++it)
599     {
600     std::string command = this->BinaryDirectory;
601     command += *it;
602     command += tmpOutputFile;
603     if(cmSystemTools::FileExists(command.c_str()))
604       {
605       tmpOutputFile = cmSystemTools::CollapseFullPath(command.c_str());
606       this->OutputFile = tmpOutputFile;
607       return;
608       }
609     }
610
611   cmOStringStream emsg;
612   emsg << "Unable to find the executable at any of:\n";
613   for (unsigned int i = 0; i < searchDirs.size(); ++i)
614     {
615     emsg << "  " << this->BinaryDirectory << searchDirs[i]
616          << tmpOutputFile << "\n";
617     }
618   this->FindErrorMessage = emsg.str();
619   return;
620 }