1 /*============================================================================
2 CMake - Cross Platform Makefile Generator
3 Copyright 2000-2011 Kitware, Inc., Insight Software Consortium
5 Distributed under the OSI-approved BSD License (the "License");
6 see accompanying file Copyright.txt for details.
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"
14 #include "cmCacheManager.h"
15 #include "cmLocalGenerator.h"
16 #include "cmGlobalGenerator.h"
17 #include "cmExportTryCompileFileGenerator.h"
18 #include <cmsys/Directory.hxx>
22 int cmCoreTryCompile::TryCompileCode(std::vector<std::string> const& argv)
24 this->BinaryDirectory = argv[1].c_str();
25 this->OutputFile = "";
26 // which signature were we called with ?
27 this->SrcFileSignature = true;
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;
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;
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)
53 if(argv[i] == "CMAKE_FLAGS")
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]);
61 else if(argv[i] == "COMPILE_DEFINITIONS")
63 doing = DoingCompileDefinitions;
65 else if(argv[i] == "LINK_LIBRARIES")
67 doing = DoingLinkLibraries;
68 useOldLinkLibs = false;
70 else if(argv[i] == "OUTPUT_VARIABLE")
72 doing = DoingOutputVariable;
73 didOutputVariable = true;
75 else if(argv[i] == "COPY_FILE")
77 doing = DoingCopyFile;
80 else if(argv[i] == "COPY_FILE_ERROR")
82 doing = DoingCopyFileError;
83 didCopyFileError = true;
85 else if(doing == DoingCMakeFlags)
87 cmakeFlags.push_back(argv[i]);
89 else if(doing == DoingCompileDefinitions)
91 compileDefs.push_back(argv[i]);
93 else if(doing == DoingLinkLibraries)
95 libsToLink += "\"" + cmSystemTools::TrimWhitespace(argv[i]) + "\" ";
96 if(cmTarget *tgt = this->Makefile->FindTargetToUse(argv[i].c_str()))
98 switch(tgt->GetType())
100 case cmTarget::SHARED_LIBRARY:
101 case cmTarget::STATIC_LIBRARY:
102 case cmTarget::UNKNOWN_LIBRARY:
104 case cmTarget::EXECUTABLE:
105 if (tgt->IsExecutableWithExports())
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()) + ".");
116 if (tgt->IsImported())
118 targets.push_back(tgt);
122 else if(doing == DoingOutputVariable)
124 outputVariable = argv[i].c_str();
127 else if(doing == DoingCopyFile)
129 copyFile = argv[i].c_str();
132 else if(doing == DoingCopyFileError)
134 copyFileError = argv[i].c_str();
137 else if(doing == DoingSources)
139 sources.push_back(argv[i]);
143 this->SrcFileSignature = false;
144 projectName = argv[i].c_str();
146 else if(i == 4 && !this->SrcFileSignature)
148 targetName = argv[i].c_str();
153 m << "try_compile given unknown argument \"" << argv[i] << "\".";
154 this->Makefile->IssueMessage(cmake::AUTHOR_WARNING, m.str());
158 if(didCopyFile && copyFile.empty())
160 this->Makefile->IssueMessage(cmake::FATAL_ERROR,
161 "COPY_FILE must be followed by a file path");
165 if(didCopyFileError && copyFileError.empty())
167 this->Makefile->IssueMessage(cmake::FATAL_ERROR,
168 "COPY_FILE_ERROR must be followed by a variable name");
172 if(didCopyFileError && !didCopyFile)
174 this->Makefile->IssueMessage(cmake::FATAL_ERROR,
175 "COPY_FILE_ERROR may be used only with COPY_FILE");
179 if(didOutputVariable && outputVariable.empty())
181 this->Makefile->IssueMessage(cmake::FATAL_ERROR,
182 "OUTPUT_VARIABLE must be followed by a variable name");
186 if(useSources && sources.empty())
188 this->Makefile->IssueMessage(cmake::FATAL_ERROR,
189 "SOURCES must be followed by at least one source file");
193 // compute the binary dir when TRY_COMPILE is called with a src file
195 if (this->SrcFileSignature)
197 this->BinaryDirectory += cmake::GetCMakeFilesDirectory();
198 this->BinaryDirectory += "/CMakeTmp";
202 // only valid for srcfile signatures
203 if (compileDefs.size())
205 this->Makefile->IssueMessage(cmake::FATAL_ERROR,
206 "COMPILE_DEFINITIONS specified on a srcdir type TRY_COMPILE");
211 this->Makefile->IssueMessage(cmake::FATAL_ERROR,
212 "COPY_FILE specified on a srcdir type TRY_COMPILE");
216 // make sure the binary directory exists
217 cmSystemTools::MakeDirectory(this->BinaryDirectory.c_str());
219 // do not allow recursive try Compiles
220 if (this->BinaryDirectory == this->Makefile->GetHomeOutputDirectory())
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());
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)
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());
240 sources.push_back(argv[2]);
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)
250 std::string ext = cmSystemTools::GetFilenameLastExtension(*si);
251 if(const char* lang = gg->GetLanguageFromExtension(ext.c_str()))
253 testLangs.insert(lang);
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)
269 err << "\nSee project() command to enable other languages.";
270 this->Makefile->IssueMessage(cmake::FATAL_ERROR, err.str());
275 // we need to create a directory and CMakeLists file etc...
276 // first create the directories
277 sourceDirectory = this->BinaryDirectory.c_str();
279 // now create a CMakeLists.txt file in that directory
280 FILE *fout = fopen(outFileName.c_str(),"w");
284 e << "Failed to open\n"
285 << " " << outFileName.c_str() << "\n"
286 << cmSystemTools::GetLastSystemError();
287 this->Makefile->IssueMessage(cmake::FATAL_ERROR, e.str());
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());
297 fprintf(fout, "SET(CMAKE_MODULE_PATH %s)\n", def);
300 std::string projectLangs;
301 for(std::set<std::string>::iterator li = testLangs.begin();
302 li != testLangs.end(); ++li)
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()))
310 fprintf(fout, "SET(%s \"%s\")\n",
311 rulesOverrideLang.c_str(), rulesOverridePath);
313 else if(const char* rulesOverridePath2 =
314 this->Makefile->GetDefinition(rulesOverrideBase.c_str()))
316 fprintf(fout, "SET(%s \"%s\")\n",
317 rulesOverrideBase.c_str(), rulesOverridePath2);
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)
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());
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())
338 fprintf(fout, "ADD_DEFINITIONS( ");
339 for (size_t i = 0; i < compileDefs.size(); ++i)
341 fprintf(fout,"%s ",compileDefs[i].c_str());
343 fprintf(fout, ")\n");
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;
352 if (!targets.empty())
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"));
361 if(!tcfg.GenerateImportFile())
363 this->Makefile->IssueMessage(cmake::FATAL_ERROR,
364 "could not write export file.");
369 "\ninclude(\"${CMAKE_CURRENT_LIST_DIR}/%s\")\n\n",
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.
380 if(this->Makefile->GetDefinition("CMAKE_TRY_COMPILE_OSX_ARCHITECTURES")!=0)
382 std::string flag="-DCMAKE_OSX_ARCHITECTURES=";
383 flag += this->Makefile->GetSafeDefinition(
384 "CMAKE_TRY_COMPILE_OSX_ARCHITECTURES");
385 cmakeFlags.push_back(flag);
387 else if (this->Makefile->GetDefinition("CMAKE_OSX_ARCHITECTURES")!=0)
389 std::string flag="-DCMAKE_OSX_ARCHITECTURES=";
390 flag += this->Makefile->GetSafeDefinition("CMAKE_OSX_ARCHITECTURES");
391 cmakeFlags.push_back(flag);
393 /* on APPLE also pass CMAKE_OSX_SYSROOT to the try_compile */
394 if(this->Makefile->GetDefinition("CMAKE_OSX_SYSROOT")!=0)
396 std::string flag="-DCMAKE_OSX_SYSROOT=";
397 flag += this->Makefile->GetSafeDefinition("CMAKE_OSX_SYSROOT");
398 cmakeFlags.push_back(flag);
400 /* on APPLE also pass CMAKE_OSX_DEPLOYMENT_TARGET to the try_compile */
401 if(this->Makefile->GetDefinition("CMAKE_OSX_DEPLOYMENT_TARGET")!=0)
403 std::string flag="-DCMAKE_OSX_DEPLOYMENT_TARGET=";
404 flag += this->Makefile->GetSafeDefinition("CMAKE_OSX_DEPLOYMENT_TARGET");
405 cmakeFlags.push_back(flag);
407 if(this->Makefile->GetDefinition("CMAKE_POSITION_INDEPENDENT_CODE")!=0)
409 fprintf(fout, "SET(CMAKE_POSITION_INDEPENDENT_CODE \"ON\")\n");
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)
420 fprintf(fout, " \"%s\"", si->c_str());
422 // Add dependencies on any non-temporary sources.
423 if(si->find("CMakeTmp") == si->npos)
425 this->Makefile->AddCMakeDependFile(*si);
428 fprintf(fout, ")\n");
432 "TARGET_LINK_LIBRARIES(%s ${LINK_LIBRARIES})\n",targetName);
436 fprintf(fout, "TARGET_LINK_LIBRARIES(%s %s)\n",
441 projectName = "CMAKE_TRY_COMPILE";
444 bool erroroc = cmSystemTools::GetErrorOccuredFlag();
445 cmSystemTools::ResetErrorOccuredFlag();
447 // actually do the try compile now that everything is setup
448 int res = this->Makefile->TryCompile(sourceDirectory,
449 this->BinaryDirectory.c_str(),
452 this->SrcFileSignature,
457 cmSystemTools::SetErrorOccured();
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);
466 if ( outputVariable.size() > 0 )
468 this->Makefile->AddDefinition(outputVariable.c_str(), output.c_str());
471 if (this->SrcFileSignature)
473 std::string copyFileErrorMessage;
474 this->FindOutputFile(targetName);
476 if ((res==0) && (copyFile.size()))
478 if(this->OutputFile.empty() ||
479 !cmSystemTools::CopyFileAlways(this->OutputFile.c_str(),
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())
489 emsg << this->FindErrorMessage.c_str();
491 if(copyFileError.empty())
493 this->Makefile->IssueMessage(cmake::FATAL_ERROR, emsg.str());
498 copyFileErrorMessage = emsg.str();
503 if(!copyFileError.empty())
505 this->Makefile->AddDefinition(copyFileError.c_str(),
506 copyFileErrorMessage.c_str());
512 void cmCoreTryCompile::CleanupFiles(const char* binDir)
519 std::string bdir = binDir;
520 if(bdir.find("CMakeTmp") == std::string::npos)
522 cmSystemTools::Error(
523 "TRY_COMPILE attempt to remove -rf directory that does not contain "
524 "CMakeTmp:", binDir);
528 cmsys::Directory dir;
531 std::set<cmStdString> deletedFiles;
532 for (fileNum = 0; fileNum < dir.GetNumberOfFiles(); ++fileNum)
534 if (strcmp(dir.GetFile(static_cast<unsigned long>(fileNum)),".") &&
535 strcmp(dir.GetFile(static_cast<unsigned long>(fileNum)),".."))
538 if(deletedFiles.find( dir.GetFile(static_cast<unsigned long>(fileNum)))
539 == deletedFiles.end())
541 deletedFiles.insert(dir.GetFile(static_cast<unsigned long>(fileNum)));
542 std::string fullPath = binDir;
544 fullPath += dir.GetFile(static_cast<unsigned long>(fileNum));
545 if(cmSystemTools::FileIsDirectory(fullPath.c_str()))
547 this->CleanupFiles(fullPath.c_str());
548 cmSystemTools::RemoveADirectory(fullPath.c_str());
552 // Sometimes anti-virus software hangs on to new files so we
553 // cannot delete them immediately. Try a few times.
555 while(!cmSystemTools::RemoveFile(fullPath.c_str()) &&
556 --tries && cmSystemTools::FileExists(fullPath.c_str()))
558 cmSystemTools::Delay(500);
562 std::string m = "Remove failed on file: " + fullPath;
563 cmSystemTools::ReportLastSystemError(m.c_str());
571 void cmCoreTryCompile::FindOutputFile(const char* targetName)
573 this->FindErrorMessage = "";
574 this->OutputFile = "";
575 std::string tmpOutputFile = "/";
576 tmpOutputFile += targetName;
577 tmpOutputFile +=this->Makefile->GetSafeDefinition("CMAKE_EXECUTABLE_SUFFIX");
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("");
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])
589 std::string tmp = "/";
591 searchDirs.push_back(tmp);
593 searchDirs.push_back("/Debug");
594 searchDirs.push_back("/Development");
596 for(std::vector<std::string>::const_iterator it = searchDirs.begin();
597 it != searchDirs.end();
600 std::string command = this->BinaryDirectory;
602 command += tmpOutputFile;
603 if(cmSystemTools::FileExists(command.c_str()))
605 tmpOutputFile = cmSystemTools::CollapseFullPath(command.c_str());
606 this->OutputFile = tmpOutputFile;
611 cmOStringStream emsg;
612 emsg << "Unable to find the executable at any of:\n";
613 for (unsigned int i = 0; i < searchDirs.size(); ++i)
615 emsg << " " << this->BinaryDirectory << searchDirs[i]
616 << tmpOutputFile << "\n";
618 this->FindErrorMessage = emsg.str();