/* 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, const int version, const MSVCVersion &msvc) : MSVCProvider(global_warnings, project_warnings, 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 == "Analysis") { 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 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, "Analysis", *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"; for (std::list::const_iterator arch = _archs.begin(); arch != _archs.end(); ++arch) { project << "\t\t" << getMSVCArchName(*arch) << "-windows\n"; } project << "\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, "Analysis", *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, "Analysis", *arch); outputProperties(setup, project, "LLVM", *arch); outputProperties(setup, project, "Debug", *arch); } project << "\t\n"; // Project-specific settings (analysis 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, "Analysis"); 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, setup.filePrefix + '/' + modulePath); else addFilesToProject(moduleDir, project, includeList, excludeList, 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\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\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\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() << "\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) << "\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, bool runBuildEvents) { std::string warnings; for (StringList::const_iterator i = _globalWarnings.begin(); i != _globalWarnings.end(); ++i) warnings += *i + ';'; std::string definesList; for (StringList::const_iterator i = defines.begin(); i != defines.end(); ++i) definesList += *i + ';'; // Add define to include revision header if (runBuildEvents) definesList += REVISION_DEFINE ";"; std::string includeDirsList; for (StringList::const_iterator i = setup.includeDirs.begin(); i != setup.includeDirs.end(); ++i) includeDirsList += convertPathToWin(*i) + ';'; std::string libraryDirsList; for (StringList::const_iterator i = setup.libraryDirs.begin(); i != setup.libraryDirs.end(); ++i) libraryDirsList += convertPathToWin(*i) + ';'; properties << "\n" << "\n" << "\t\n" << "\t\t<_PropertySheetDisplayName>" << setup.projectDescription << "_Global\n" << "\t\t$(" << LIBS_DEFINE << ")\\bin;$(" << LIBS_DEFINE << ")\\bin\\" << getMSVCArchName(arch) << ";$(" << LIBS_DEFINE << ")\\$(Configuration)\\bin;$(ExecutablePath)\n" << "\t\t" << libraryDirsList << "$(" << LIBS_DEFINE << ")\\lib\\" << getMSVCArchName(arch) << ";$(" << LIBS_DEFINE << ")\\lib\\" << getMSVCArchName(arch) << "\\$(Configuration);$(" << LIBS_DEFINE << ")\\lib;$(" << LIBS_DEFINE << ")\\$(Configuration)\\lib;$(LibraryPath)\n" << "\t\t" << includeDirsList << "$(" << LIBS_DEFINE << ")\\include;$(" << LIBS_DEFINE << ")\\include\\" << (setup.useSDL2 ? "SDL2" : "SDL") << ";$(IncludePath)\n" << "\t\t$(Configuration)" << getMSVCArchName(arch) << "\\\n" << "\t\t$(Configuration)" << getMSVCArchName(arch) << "\\$(ProjectName)\\\n" << "\t\n" << "\t\n" << "\t\t\n" << "\t\t\ttrue\n" << "\t\t\t" << warnings << ";%(DisableSpecificWarnings)\n" << "\t\t\t.;" << prefix << ";" << prefix << "\\engines;" << (setup.tests ? prefix + "\\test\\cxxtest;" : "") << "%(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 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 %(AdditionalOptions)\n" << "\t\t\n" << "\t\t\n" << "\t\t\t%(IgnoreSpecificDefaultLibraries)\n"; if (!setup.featureEnabled("text-console") && !setup.devTools && !setup.tests) { properties << "\t\t\tWindows\n"; } else { properties << "\t\t\tConsole\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; } 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 == "Analysis") ? "false" : "true") << "\n" << "\t\tfalse\n" << "\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\tEnableFastChecks\n" << "\t\t\tMultiThreadedDebugDLL\n" << "\t\t\ttrue\n" << "\t\t\tfalse\n"; // Since MSVC 2015 Edit and Continue is supported for x86 and x86-64, but not for ARM. if (configuration != "Analysis" && (arch == ARCH_X86 || (arch == ARCH_AMD64 && _version >= 14))) { properties << "\t\t\tEditAndContinue\n"; } else { properties << "\t\t\tProgramDatabase\n"; } properties << "\t\t\t" << (configuration == "Analysis" ? "true" : "false") << "\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::writeFileListToProject(const FileNode &dir, std::ostream &projectFile, const int, const std::string &objPrefix, const std::string &filePrefix) { // 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 // Output compile, include, other and resource files outputFiles(projectFile, _compileFiles, "ClCompile"); outputFiles(projectFile, _includeFiles, "ClInclude"); outputFiles(projectFile, _otherFiles, "None"); outputFiles(projectFile, _resourceFiles, "ResourceCompile"); // 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, "Analysis", (*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::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