/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
*/
#include "msbuild.h"
#include "config.h"
#include
#include
namespace CreateProjectTool {
//////////////////////////////////////////////////////////////////////////
// MSBuild Provider (Visual Studio 2010 and later)
//////////////////////////////////////////////////////////////////////////
MSBuildProvider::MSBuildProvider(StringList &global_warnings, std::map &project_warnings, StringList &global_errors, const int version, const MSVCVersion &msvc)
: MSVCProvider(global_warnings, project_warnings, global_errors, version, msvc) {
_archs.push_back(ARCH_X86);
_archs.push_back(ARCH_AMD64);
_archs.push_back(ARCH_ARM64);
}
const char *MSBuildProvider::getProjectExtension() {
return ".vcxproj";
}
const char *MSBuildProvider::getPropertiesExtension() {
return ".props";
}
namespace {
inline void outputConfiguration(std::ostream &project, const std::string &config, MSVC_Architecture arch) {
project << "\t\t\n"
<< "\t\t\t" << config << "\n"
<< "\t\t\t" << getMSVCConfigName(arch) << "\n"
<< "\t\t\n";
}
inline void outputConfigurationType(const BuildSetup &setup, std::ostream &project, const std::string &name, const std::string &config, MSVC_Architecture arch, const MSVCVersion &msvc) {
project << "\t\n";
if (name == setup.projectName || setup.devTools || setup.tests) {
project << "\t\tApplication\n";
} else {
project << "\t\tStaticLibrary\n";
}
project << "\t\t" << (config == "LLVM" ? msvc.toolsetLLVM : msvc.toolsetMSVC ) << "\n";
project << "\t\t" << (setup.useWindowsUnicode ? "Unicode" : "NotSet") << "\n";
if (msvc.version >= 16 && config == "ASan") {
project << "\t\ttrue\n";
}
project << "\t\n";
}
inline void outputProperties(const BuildSetup &setup, std::ostream &project, const std::string &config, MSVC_Architecture arch) {
project << "\t\n"
<< "\t\t\n"
<< "\t\t\n"
<< "\t\n";
}
} // End of anonymous namespace
void MSBuildProvider::createProjectFile(const std::string &name, const std::string &uuid, const BuildSetup &setup, const std::string &moduleDir,
const StringList &includeList, const StringList &excludeList, const std::string &pchIncludeRoot, const StringList &pchDirs, const StringList &pchExclude) {
const std::string projectFile = setup.outputDir + '/' + name + getProjectExtension();
std::ofstream project(projectFile.c_str());
if (!project || !project.is_open()) {
error("Could not open \"" + projectFile + "\" for writing");
return;
}
project << "\n"
<< "\n"
<< "\t\n";
for (std::list::const_iterator arch = _archs.begin(); arch != _archs.end(); ++arch) {
outputConfiguration(project, "Debug", *arch);
outputConfiguration(project, "ASan", *arch);
outputConfiguration(project, "LLVM", *arch);
outputConfiguration(project, "Release", *arch);
}
project << "\t\n";
// Project name & Guid
project << "\t\n"
<< "\t\t{" << uuid << "}\n"
<< "\t\t" << name << "\n"
<< "\t\tWin32Proj\n"
<< "\t\t$(VCTargetsPath" << _version << ")\n"
<< "\t\n";
// Shared configuration
project << "\t\n";
for (std::list::const_iterator arch = _archs.begin(); arch != _archs.end(); ++arch) {
outputConfigurationType(setup, project, name, "Release", *arch, _msvcVersion);
outputConfigurationType(setup, project, name, "ASan", *arch, _msvcVersion);
outputConfigurationType(setup, project, name, "LLVM", *arch, _msvcVersion);
outputConfigurationType(setup, project, name, "Debug", *arch, _msvcVersion);
}
project << "\t\n"
<< "\t\n"
<< "\t\n";
for (std::list::const_iterator arch = _archs.begin(); arch != _archs.end(); ++arch) {
outputProperties(setup, project, "Release", *arch);
outputProperties(setup, project, "ASan", *arch);
outputProperties(setup, project, "LLVM", *arch);
outputProperties(setup, project, "Debug", *arch);
}
project << "\t\n";
// Project-specific settings (asan uses debug properties)
for (std::list::const_iterator arch = _archs.begin(); arch != _archs.end(); ++arch) {
BuildSetup archsetup = setup;
std::map::const_iterator disabled_features_it = _arch_disabled_features.find(*arch);
if (disabled_features_it != _arch_disabled_features.end()) {
for (StringList::const_iterator j = disabled_features_it->second.begin(); j != disabled_features_it->second.end(); ++j) {
archsetup = removeFeatureFromSetup(archsetup, *j);
}
}
outputProjectSettings(project, name, archsetup, false, *arch, "Debug");
outputProjectSettings(project, name, archsetup, false, *arch, "ASan");
outputProjectSettings(project, name, archsetup, false, *arch, "LLVM");
outputProjectSettings(project, name, archsetup, true, *arch, "Release");
}
// Files
std::string modulePath;
if (!moduleDir.compare(0, setup.srcDir.size(), setup.srcDir)) {
modulePath = moduleDir.substr(setup.srcDir.size());
if (!modulePath.empty() && modulePath.at(0) == '/')
modulePath.erase(0, 1);
}
if (!modulePath.empty())
addFilesToProject(moduleDir, project, includeList, excludeList, pchIncludeRoot, pchDirs, pchExclude, setup.filePrefix + '/' + modulePath);
else
addFilesToProject(moduleDir, project, includeList, excludeList, pchIncludeRoot, pchDirs, pchExclude, setup.filePrefix);
// Output references for the main project
if (name == setup.projectName)
writeReferences(setup, project);
// Output auto-generated test runner
if (setup.tests) {
project << "\t\n";
project << "\t\t\n";
project << "\t\t\n";
project << "\t\t\n"; // symbols are already defined in null_osystem.cpp
project << "\t\n";
}
// Visual Studio 2015 and up automatically import natvis files that are part of the project
if (name == PROJECT_NAME && _version >= 14) {
project << "\t\n";
project << "\t\t\n";
project << "\t\n";
}
project << "\t\n"
"\t\n"
"\t\n";
if (setup.tests) {
// We override the normal target to ignore the exit code (this allows us to have a clean output and not message about the command exit code)
project << "\t\t\n"
<< "\t\t\t\n"
<< "\t\t\t\n"
<< "\t\t\t\n"
<< "\t\t\n";
}
project << "\n";
// Output filter file if necessary
createFiltersFile(setup, name);
}
void MSBuildProvider::createFiltersFile(const BuildSetup &setup, const std::string &name) {
// No filters => no need to create a filter file
if (_filters.empty())
return;
// Sort all list alphabetically
_filters.sort();
_compileFiles.sort();
_includeFiles.sort();
_otherFiles.sort();
_resourceFiles.sort();
_asmFiles.sort();
const std::string filtersFile = setup.outputDir + '/' + name + getProjectExtension() + ".filters";
std::ofstream filters(filtersFile.c_str());
if (!filters || !filters.is_open()) {
error("Could not open \"" + filtersFile + "\" for writing");
return;
}
filters << "\n"
<< "\n";
// Output the list of filters
filters << "\t\n";
for (std::list::iterator filter = _filters.begin(); filter != _filters.end(); ++filter) {
filters << "\t\t\n"
<< "\t\t\t" << createUUID() << "\n"
<< "\t\t\n";
}
filters << "\t\n";
// Output files
outputFilter(filters, _compileFiles, "ClCompile");
outputFilter(filters, _includeFiles, "ClInclude");
outputFilter(filters, _otherFiles, "None");
outputFilter(filters, _resourceFiles, "ResourceCompile");
outputFilter(filters, _asmFiles, "CustomBuild");
filters << "";
}
void MSBuildProvider::outputFilter(std::ostream &filters, const FileEntries &files, const std::string &action) {
if (!files.empty()) {
filters << "\t\n";
for (FileEntries::const_iterator entry = files.begin(), end = files.end(); entry != end; ++entry) {
if ((*entry).filter != "") {
filters << "\t\t<" << action << " Include=\"" << (*entry).path << "\">\n"
<< "\t\t\t" << (*entry).filter << "\n"
<< "\t\t" << action << ">\n";
} else {
filters << "\t\t<" << action << " Include=\"" << (*entry).path << "\" />\n";
}
}
filters << "\t\n";
}
}
void MSBuildProvider::writeReferences(const BuildSetup &setup, std::ofstream &output) {
output << "\t\n";
for (UUIDMap::const_iterator i = _engineUuidMap.begin(); i != _engineUuidMap.end(); ++i) {
output << "\tfirst << ".vcxproj\">\n"
<< "\t\t{" << i->second << "}\n"
<< "\t\n";
}
output << "\t\n";
}
void MSBuildProvider::outputProjectSettings(std::ofstream &project, const std::string &name, const BuildSetup &setup, bool isRelease, MSVC_Architecture arch, const std::string &configuration) {
// Check for project-specific warnings:
std::map::iterator warningsIterator = _projectWarnings.find(name);
bool enableLanguageExtensions = find(_enableLanguageExtensions.begin(), _enableLanguageExtensions.end(), name) != _enableLanguageExtensions.end();
bool disableEditAndContinue = find(_disableEditAndContinue.begin(), _disableEditAndContinue.end(), name) != _disableEditAndContinue.end();
// Nothing to add here, move along!
if (!setup.devTools && !setup.tests && name != setup.projectName && !enableLanguageExtensions && !disableEditAndContinue && warningsIterator == _projectWarnings.end())
return;
std::string warnings = "";
if (warningsIterator != _projectWarnings.end())
for (StringList::const_iterator i = warningsIterator->second.begin(); i != warningsIterator->second.end(); ++i)
warnings += *i + ';';
project << "\t\n"
<< "\t\t\n";
// Language Extensions
if (setup.devTools || setup.tests || name == setup.projectName || enableLanguageExtensions) {
project << "\t\t\tfalse\n";
project << "\t\t\tfalse\n"; // Required for Windows SDK 8.1
}
// Edit and Continue
if ((name == setup.projectName || disableEditAndContinue) && !isRelease)
project << "\t\t\tProgramDatabase\n";
// Warnings
if (warningsIterator != _projectWarnings.end())
project << "\t\t\t" << warnings << ";%(DisableSpecificWarnings)\n";
project << "\t\t\n";
// Link configuration for main project
if (name == setup.projectName || setup.devTools || setup.tests) {
std::string libraries = outputLibraryDependencies(setup, isRelease);
// MSBuild uses ; for separators instead of spaces
for (std::string::iterator i = libraries.begin(); i != libraries.end(); ++i) {
if (*i == ' ') {
*i = ';';
}
}
project << "\t\t\n"
<< "\t\t\t$(OutDir)" << ((setup.devTools || setup.tests) ? name : setup.projectName) << ".exe\n"
<< "\t\t\t" << libraries << "%(AdditionalDependencies)\n"
<< "\t\t\n";
if (!setup.devTools && !setup.tests && setup.runBuildEvents) {
project << "\t\t\n"
<< "\t\t\tGenerate revision\n"
<< "\t\t\t" << getPreBuildEvent(setup) << "\n"
<< "\t\t\n";
// Copy data files to the build folder
project << "\t\t\n"
<< "\t\t\tCopy data files to the build folder\n"
<< "\t\t\t" << getPostBuildEvent(arch, setup, isRelease) << "\n"
<< "\t\t\n";
} else if (setup.tests) {
project << "\t\t\n"
<< "\t\t\tGenerate runner.cpp\n"
<< "\t\t\t" << getTestPreBuildEvent(setup) << "\n"
<< "\t\t\n";
}
}
project << "\t\n";
}
void MSBuildProvider::outputGlobalPropFile(const BuildSetup &setup, std::ofstream &properties, MSVC_Architecture arch, const StringList &defines, const std::string &prefix) {
std::string warnings;
for (StringList::const_iterator i = _globalWarnings.begin(); i != _globalWarnings.end(); ++i)
warnings += *i + ';';
std::string warningsAsErrors;
for (StringList::const_iterator i = _globalErrors.begin(); i != _globalErrors.end(); ++i)
warningsAsErrors += "/we\"" + (*i) + "\" ";
std::string definesList;
for (StringList::const_iterator i = defines.begin(); i != defines.end(); ++i)
definesList += *i + ';';
// Add define to include revision header
if (setup.runBuildEvents)
definesList += REVISION_DEFINE ";";
std::string includeSDL = setup.getSDLName();
properties << "\n"
<< "\n"
<< "\t\n"
<< "\t\t<_PropertySheetDisplayName>" << setup.projectDescription << "_Global\n"
<< "\t\t$(Configuration)" << getMSVCArchName(arch) << "\\\n"
<< "\t\t$(Configuration)" << getMSVCArchName(arch) << "\\$(ProjectName)\\\n";
if (_msvcVersion.version >= 17 && setup.useVcpkg)
properties << "\t\ttrue\n";
properties << "\t\n"
<< "\t\n"
<< "\t\t\n"
<< "\t\t\ttrue\n"
<< "\t\t\t" << warnings << ";%(DisableSpecificWarnings)\n"
<< "\t\t\t.;" << prefix << ";" << prefix << "\\engines;";
if (setup.tests) {
properties << prefix << "\\test\\cxxtest;";
}
// HACK to workaround SDL/SDL.h includes
if (setup.useVcpkg) {
properties << "$(_ZVcpkgCurrentInstalledDir)include\\" << includeSDL;
}
properties << "%(AdditionalIncludeDirectories)\n"
<< "\t\t\t" << definesList << "%(PreprocessorDefinitions)\n"
<< "\t\t\t" << ((setup.devTools || setup.tests) ? "Sync" : "") << "\n";
#if NEEDS_RTTI
properties << "\t\t\ttrue\n";
#else
properties << "\t\t\tfalse\n";
#endif
// Print a bit more context for warnings, like modern GCC/Clang do
if (_msvcVersion.version >= 15)
properties << "\t\t\tCaret\n";
properties << "\t\t\tLevel4\n"
<< "\t\t\tfalse\n"
<< "\t\t\tDefault\n"
<< "\t\t\ttrue\n"
<< "\t\t\ttrue\n"
<< "\t\t\t$(IntDir)dists\\msvc\\%(RelativeDir)\n"
<< "\t\t\t/utf-8 " << (_msvcVersion.version >= 15 ? "/Zc:__cplusplus " : "") << warningsAsErrors << "%(AdditionalOptions)\n"
<< "\t\t\n"
<< "\t\t\n"
<< "\t\t\t%(IgnoreSpecificDefaultLibraries)\n";
// console subsystem is required for text-console, tools, and tests
if (!setup.useWindowsSubsystem || setup.featureEnabled("text-console") || setup.devTools || setup.tests) {
properties << "\t\t\tConsole\n";
} else {
properties << "\t\t\tWindows\n";
}
if (!setup.devTools && !setup.tests)
properties << "\t\t\tWinMainCRTStartup\n";
properties << "\t\t\n"
<< "\t\t\n"
<< "\t\t\t.;" << prefix << ";%(AdditionalIncludeDirectories)\n"
<< "\t\t\t" << definesList << "%(PreprocessorDefinitions)\n"
<< "\t\t\n"
<< "\t\n"
<< "\n";
properties.flush();
}
void MSBuildProvider::createBuildProp(const BuildSetup &setup, bool isRelease, MSVC_Architecture arch, const std::string &configuration) {
std::ofstream properties((setup.outputDir + '/' + setup.projectDescription + "_" + configuration + getMSVCArchName(arch) + getPropertiesExtension()).c_str());
if (!properties || !properties.is_open()) {
error("Could not open \"" + setup.outputDir + '/' + setup.projectDescription + "_" + configuration + getMSVCArchName(arch) + getPropertiesExtension() + "\" for writing");
return;
}
std::string includeDirsList;
for (StringList::const_iterator i = setup.includeDirs.begin(); i != setup.includeDirs.end(); ++i)
includeDirsList += convertPathToWin(*i) + ';';
std::string includeSDL = setup.getSDLName();
std::string libraryDirsList;
for (StringList::const_iterator i = setup.libraryDirs.begin(); i != setup.libraryDirs.end(); ++i)
libraryDirsList += convertPathToWin(*i) + ';';
std::string libsPath;
if (setup.libsDir.empty())
libsPath = "$(" LIBS_DEFINE ")";
else
libsPath = convertPathToWin(setup.libsDir);
std::string cfgPath = (isRelease ? "Release" : "Debug");
properties << "\n"
<< "\n"
<< "\t\n"
<< "\t\t\n"
<< "\t\n"
<< "\t\n"
<< "\t\t<_PropertySheetDisplayName>" << setup.projectDescription << "_" << configuration << getMSVCArchName(arch) << "\n"
<< "\t\t" << ((isRelease || configuration == "ASan") ? "false" : "true") << "\n"
<< "\t\tfalse\n";
if (!setup.useVcpkg) {
properties << "\t\t" << libsPath << "\\bin;" << libsPath << "\\bin\\" << getMSVCArchName(arch) << ";" << libsPath << "\\" << cfgPath << "\\bin;$(ExecutablePath)\n"
<< "\t\t" << libraryDirsList << libsPath << "\\lib\\" << getMSVCArchName(arch) << ";" << libsPath << "\\lib\\" << getMSVCArchName(arch) << "\\" << cfgPath << ";" << libsPath << "\\lib;" << libsPath << "\\" << cfgPath << "\\lib;$(LibraryPath)\n"
<< "\t\t" << includeDirsList << libsPath << "\\include;" << libsPath << "\\include\\" << includeSDL << ";$(IncludePath)\n";
} else {
properties << "\t\t" << getMSVCArchName(arch) << "-windows\n";
properties << "\t\t" << cfgPath << "\n";
}
properties << "\t\n"
<< "\t\n"
<< "\t\t\n";
if (isRelease) {
properties << "\t\t\ttrue\n"
<< "\t\t\ttrue\n"
<< "\t\t\tWIN32;RELEASE_BUILD;%(PreprocessorDefinitions)\n"
<< "\t\t\ttrue\n"
<< "\t\t\tfalse\n"
<< "\t\t\t\n"
<< "\t\t\tMultiThreadedDLL\n"
<< "\t\t\tfalse\n"
<< "\t\t\n"
<< "\t\t\n"
<< "\t\t\ttrue\n"
<< "\t\t\n"
<< "\t\t\n"
<< "\t\t\tUseLinkTimeCodeGeneration\n"
<< "\t\t\t%(IgnoreSpecificDefaultLibraries)\n"
<< "\t\t\ttrue\n";
} else {
properties << "\t\t\tDisabled\n"
<< "\t\t\tWIN32;" << (configuration == "LLVM" ? "_CRT_SECURE_NO_WARNINGS;" : "") << "%(PreprocessorDefinitions)\n"
<< "\t\t\tMultiThreadedDebugDLL\n"
<< "\t\t\ttrue\n"
<< "\t\t\tfalse\n";
if (configuration != "ASan") {
properties << "\t\t\tEnableFastChecks\n";
}
// Since MSVC 2015 Edit and Continue is supported for x86 and x86-64, but not for ARM.
if (configuration != "ASan" && (arch == ARCH_X86 || (arch == ARCH_AMD64 && _version >= 14))) {
properties << "\t\t\tEditAndContinue\n";
} else {
properties << "\t\t\tProgramDatabase\n";
}
if (configuration == "LLVM") {
properties << "\t\t\t-Wno-microsoft -Wno-long-long -Wno-multichar -Wno-unknown-pragmas -Wno-reorder -Wpointer-arith -Wcast-qual -Wshadow -Wnon-virtual-dtor -Wwrite-strings -Wno-conversion -Wno-shorten-64-to-32 -Wno-sign-compare -Wno-four-char-constants -Wno-nested-anon-types -Qunused-arguments %(AdditionalOptions)\n";
}
properties << "\t\t\n"
<< "\t\t\n"
<< "\t\t\ttrue\n"
<< "\t\t\tfalse\n";
}
properties << "\t\t\n"
<< "\t\n"
<< "\n";
properties.flush();
properties.close();
}
bool hasEnding(std::string const &fullString, std::string const &ending) {
if (fullString.length() > ending.length()) {
return (0 == fullString.compare(fullString.length() - ending.length(), ending.length(), ending));
} else {
return false;
}
}
void MSBuildProvider::outputNasmCommand(std::ostream &projectFile, const std::string &config, const std::string &prefix) {
projectFile << "\t\t\tnasm.exe -f win32 -g -o \"$(IntDir)" << prefix << "%(Filename).obj\" \"%(FullPath)\"\n"
<< "\t\t\t$(IntDir)" << prefix << "%(Filename).obj;%(Outputs)\n";
if (_version >= 15) {
projectFile << "\t\t\tObject\n"
<< "\t\t\ttrue\n";
}
}
void MSBuildProvider::insertPathIntoDirectory(FileNode &dir, const std::string &path) {
size_t separatorLoc = path.find('\\');
if (separatorLoc != std::string::npos) {
// Inside of a subdirectory
std::string subdirName = path.substr(0, separatorLoc);
FileNode::NodeList::iterator dirIt = dir.children.begin();
FileNode::NodeList::iterator dirItEnd = dir.children.end();
while (dirIt != dirItEnd) {
if ((*dirIt)->name == subdirName)
break;
++dirIt;
}
FileNode *dirNode = nullptr;
if (dirIt == dirItEnd) {
dirNode = new FileNode(subdirName);
dir.children.push_back(dirNode);
} else {
dirNode = *dirIt;
}
insertPathIntoDirectory(*dirNode, path.substr(separatorLoc + 1));
} else {
FileNode *fileNode = new FileNode(path);
dir.children.push_back(fileNode);
}
}
void MSBuildProvider::createFileNodesFromPCHList(FileNode &dir, const std::string &pathBase, const StringList &pchCompileFiles) {
for (StringList::const_iterator it = pchCompileFiles.begin(), itEnd = pchCompileFiles.end(); it != itEnd; ++it) {
const std::string &pchPath = *it;
if (pchPath.size() > pathBase.size() && pchPath.substr(0, pathBase.size()) == pathBase) {
std::string internalPath = pchPath.substr(pathBase.size());
insertPathIntoDirectory(dir, internalPath);
}
}
}
void MSBuildProvider::writeFileListToProject(const FileNode &dir, std::ostream &projectFile, const int,
const std::string &objPrefix, const std::string &filePrefix,
const std::string &pchIncludeRoot, const StringList &pchDirs, const StringList &pchExclude) {
// Reset lists
_filters.clear();
_compileFiles.clear();
_includeFiles.clear();
_otherFiles.clear();
_resourceFiles.clear();
_asmFiles.clear();
// Compute the list of files
_filters.push_back(""); // init filters
computeFileList(dir, objPrefix, filePrefix);
_filters.pop_back(); // remove last empty filter
StringList pchCompileFiles;
// Output compile, include, other and resource files
outputCompileFiles(projectFile, pchIncludeRoot, pchDirs, pchExclude, pchCompileFiles);
outputFiles(projectFile, _includeFiles, "ClInclude");
outputFiles(projectFile, _otherFiles, "None");
outputFiles(projectFile, _resourceFiles, "ResourceCompile");
if (pchCompileFiles.size() > 0) {
// Generate filters and additional compile files for PCH files
FileNode pchDir(dir.name);
createFileNodesFromPCHList(pchDir, convertPathToWin(dir.name) + '\\', pchCompileFiles);
StringList backupFilters = _filters;
_filters.clear();
_filters.push_back(""); // init filters
computeFileList(pchDir, objPrefix, filePrefix);
_filters.pop_back(); // remove last empty filter
// Combine lists, removing duplicates
for (StringList::const_iterator it = backupFilters.begin(), itEnd = backupFilters.end(); it != itEnd; ++it) {
if (std::find(_filters.begin(), _filters.end(), *it) != _filters.end())
_filters.push_back(*it);
}
}
// Output asm files
if (!_asmFiles.empty()) {
projectFile << "\t\n";
for (std::list::const_iterator entry = _asmFiles.begin(); entry != _asmFiles.end(); ++entry) {
projectFile << "\t\t\n"
<< "\t\t\tDocument\n";
outputNasmCommand(projectFile, "Debug", (*entry).prefix);
outputNasmCommand(projectFile, "ASan", (*entry).prefix);
outputNasmCommand(projectFile, "Release", (*entry).prefix);
outputNasmCommand(projectFile, "LLVM", (*entry).prefix);
projectFile << "\t\t\n";
}
projectFile << "\t\n";
}
}
void MSBuildProvider::outputFiles(std::ostream &projectFile, const FileEntries &files, const std::string &action) {
if (!files.empty()) {
projectFile << "\t\n";
for (FileEntries::const_iterator entry = files.begin(), end = files.end(); entry != end; ++entry) {
projectFile << "\t\t<" << action << " Include=\"" << (*entry).path << "\" />\n";
}
projectFile << "\t\n";
}
}
void MSBuildProvider::outputCompileFiles(std::ostream &projectFile, const std::string &pchIncludeRoot, const StringList &pchDirs, const StringList &pchExclude, StringList &outPCHFiles) {
const FileEntries &files = _compileFiles;
const bool hasPCH = (pchDirs.size() > 0);
std::string pchIncludeRootWin;
StringList pchDirsWin;
StringList pchExcludeWin;
if (hasPCH) {
pchIncludeRootWin = convertPathToWin(pchIncludeRoot);
// Convert PCH paths to Win
for (StringList::const_iterator entry = pchDirs.begin(), end = pchDirs.end(); entry != end; ++entry) {
std::string convertedPath = convertPathToWin(*entry);
if (convertedPath.size() < pchIncludeRootWin.size() || convertedPath.substr(0, pchIncludeRootWin.size()) != pchIncludeRootWin) {
error("PCH path '" + convertedPath + "' wasn't located under PCH include root '" + pchIncludeRootWin + "'");
}
pchDirsWin.push_back(convertPathToWin(*entry));
}
for (StringList::const_iterator entry = pchExclude.begin(), end = pchExclude.end(); entry != end; ++entry) {
const std::string path = *entry;
if (path.size() >= 2 && path[path.size() - 1] == 'o' && path[path.size() - 2] == '.')
pchExcludeWin.push_back(convertPathToWin(path.substr(0, path.size() - 2)));
}
}
std::map pchMap;
if (!files.empty()) {
projectFile << "\t\n";
for (FileEntries::const_iterator entry = files.begin(), end = files.end(); entry != end; ++entry) {
std::string pchIncludePath, pchFilePath, pchFileName;
bool fileHasPCH = false;
if (hasPCH)
fileHasPCH = calculatePchPaths(entry->path, pchIncludeRootWin, pchDirsWin, pchExcludeWin, '\\', pchIncludePath, pchFilePath, pchFileName);
if (fileHasPCH) {
std::string pchOutputFileName = "$(IntDir)dists\\msvc\\%(RelativeDir)" + pchFileName.substr(0, pchFileName.size() - 2) + ".pch";
PCHInfo &pchInfo = pchMap[pchFilePath];
pchInfo.file = pchIncludePath;
pchInfo.outputFile = pchOutputFileName;
projectFile << "\t\t\n";
projectFile << "\t\t\tUse\n";
projectFile << "\t\t\t" << pchIncludePath << "\n";
projectFile << "\t\t\t" << pchOutputFileName << "\n";
projectFile << "\t\t\n";
} else {
projectFile << "\t\t\n";
}
}
// Flush PCH files
for (std::map::const_iterator pchIt = pchMap.begin(), pchItEnd = pchMap.end(); pchIt != pchItEnd; ++pchIt) {
const PCHInfo &pchInfo = pchIt->second;
const std::string &filePath = pchIt->first;
assert(filePath.size() >= 2 && filePath.substr(filePath.size() - 2) == ".h");
std::string cppFilePath = filePath.substr(0, filePath.size() - 2) + ".cpp";
std::string expectedContents = "/* This file is automatically generated by create_project */\n"
"/* DO NOT EDIT MANUALLY */\n"
"#include \"" + pchInfo.file + "\"\n";
// Try to avoid touching the generated .cpp if it's identical to the expected output.
// If we touch the file, then every file that includes PCH needs to be recompiled.
std::ifstream pchInputFile(cppFilePath.c_str());
bool needToEmit = true;
if (pchInputFile && pchInputFile.is_open()) {
std::string fileContents;
for (;;) {
char buffer[1024];
size_t numRead = sizeof(buffer) - 1;
pchInputFile.read(buffer, numRead);
buffer[pchInputFile.gcount()] = '\0';
fileContents += buffer;
if (pchInputFile.eof() || pchInputFile.fail())
break;
if (fileContents.size() > expectedContents.size())
break;
}
needToEmit = (fileContents != expectedContents);
pchInputFile.close();
}
if (needToEmit) {
std::ofstream pchOutputFile(cppFilePath.c_str());
if (!pchOutputFile || !pchOutputFile.is_open()) {
error("Could not open \"" + cppFilePath + "\" for writing");
return;
}
pchOutputFile << expectedContents;
pchOutputFile.close();
}
projectFile << "\t\t\n";
projectFile << "\t\t\tCreate\n";
projectFile << "\t\t\t" << pchInfo.file << "\n";
projectFile << "\t\t\t" << pchInfo.outputFile << "\n";
projectFile << "\t\t\n";
outPCHFiles.push_back(cppFilePath);
}
projectFile << "\t\n";
}
}
void MSBuildProvider::computeFileList(const FileNode &dir, const std::string &objPrefix, const std::string &filePrefix) {
for (FileNode::NodeList::const_iterator i = dir.children.begin(); i != dir.children.end(); ++i) {
const FileNode *node = *i;
if (!node->children.empty()) {
// Update filter
std::string _currentFilter = _filters.back();
_filters.back().append((_filters.back() == "" ? "" : "\\") + node->name);
computeFileList(*node, objPrefix + node->name + '_', filePrefix + node->name + '/');
// Reset filter
_filters.push_back(_currentFilter);
} else {
// Filter files by extension
std::string name, ext;
splitFilename(node->name, name, ext);
FileEntry entry;
entry.name = name;
entry.path = convertPathToWin(filePrefix + node->name);
entry.filter = _filters.back();
entry.prefix = objPrefix;
if (ext == "cpp" || ext == "c")
_compileFiles.push_back(entry);
else if (ext == "h")
_includeFiles.push_back(entry);
else if (ext == "rc")
_resourceFiles.push_back(entry);
else if (ext == "asm")
_asmFiles.push_back(entry);
else
_otherFiles.push_back(entry);
}
}
}
} // namespace CreateProjectTool