3582116a60
...which represents the version of the MSVC platform toolset. This variable is used to set the platform toolset version in .vcxproj files. Before, the platform toolset version was determined in qmake's C++ code. Now, it's set next to where MSVC_VER is set in common mkspecs .conf files. This will simplify supporting new Visual Studio versions in the future. Change-Id: If78c921f93c6378829746d617c7e7d312174257e Reviewed-by: Alexandru Croitor <alexandru.croitor@qt.io>
1676 lines
72 KiB
C++
1676 lines
72 KiB
C++
/****************************************************************************
|
|
**
|
|
** Copyright (C) 2016 The Qt Company Ltd.
|
|
** Contact: https://www.qt.io/licensing/
|
|
**
|
|
** This file is part of the qmake application of the Qt Toolkit.
|
|
**
|
|
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
|
|
** Commercial License Usage
|
|
** Licensees holding valid commercial Qt licenses may use this file in
|
|
** accordance with the commercial license agreement provided with the
|
|
** Software or, alternatively, in accordance with the terms contained in
|
|
** a written agreement between you and The Qt Company. For licensing terms
|
|
** and conditions see https://www.qt.io/terms-conditions. For further
|
|
** information use the contact form at https://www.qt.io/contact-us.
|
|
**
|
|
** GNU General Public License Usage
|
|
** Alternatively, this file may be used under the terms of the GNU
|
|
** General Public License version 3 as published by the Free Software
|
|
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
|
** included in the packaging of this file. Please review the following
|
|
** information to ensure the GNU General Public License requirements will
|
|
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
|
**
|
|
** $QT_END_LICENSE$
|
|
**
|
|
****************************************************************************/
|
|
|
|
#include "msvc_vcproj.h"
|
|
#include "option.h"
|
|
#include "xmloutput.h"
|
|
|
|
#include <ioutils.h>
|
|
|
|
#include <qdir.h>
|
|
#include <qdiriterator.h>
|
|
#include <qcryptographichash.h>
|
|
#include <qhash.h>
|
|
#include <quuid.h>
|
|
#include <qregularexpression.h>
|
|
|
|
#include <stdlib.h>
|
|
#include <tuple>
|
|
#include <utility>
|
|
|
|
//#define DEBUG_SOLUTION_GEN
|
|
|
|
using namespace QMakeInternal;
|
|
|
|
QT_BEGIN_NAMESPACE
|
|
// Filter GUIDs (Do NOT change these!) ------------------------------
|
|
const char _GUIDSourceFiles[] = "{4FC737F1-C7A5-4376-A066-2A32D752A2FF}";
|
|
const char _GUIDHeaderFiles[] = "{93995380-89BD-4b04-88EB-625FBE52EBFB}";
|
|
const char _GUIDGeneratedFiles[] = "{71ED8ED8-ACB9-4CE9-BBE1-E00B30144E11}";
|
|
const char _GUIDResourceFiles[] = "{D9D6E242-F8AF-46E4-B9FD-80ECBC20BA3E}";
|
|
const char _GUIDLexYaccFiles[] = "{E12AE0D2-192F-4d59-BD23-7D3FA58D3183}";
|
|
const char _GUIDTranslationFiles[] = "{639EADAA-A684-42e4-A9AD-28FC9BCB8F7C}";
|
|
const char _GUIDFormFiles[] = "{99349809-55BA-4b9d-BF79-8FDBB0286EB3}";
|
|
const char _GUIDExtraCompilerFiles[] = "{E0D8C965-CC5F-43d7-AD63-FAEF0BBC0F85}";
|
|
const char _GUIDDistributionFiles[] = "{B83CAF91-C7BF-462F-B76C-EA11631F866C}";
|
|
|
|
// Flatfile Tags ----------------------------------------------------
|
|
const char _slnHeader70[] = "Microsoft Visual Studio Solution File, Format Version 7.00";
|
|
const char _slnHeader71[] = "Microsoft Visual Studio Solution File, Format Version 8.00";
|
|
const char _slnHeader80[] = "Microsoft Visual Studio Solution File, Format Version 9.00"
|
|
"\n# Visual Studio 2005";
|
|
const char _slnHeader90[] = "Microsoft Visual Studio Solution File, Format Version 10.00"
|
|
"\n# Visual Studio 2008";
|
|
const char _slnHeader100[] = "Microsoft Visual Studio Solution File, Format Version 11.00"
|
|
"\n# Visual Studio 2010";
|
|
const char _slnHeader110[] = "Microsoft Visual Studio Solution File, Format Version 12.00"
|
|
"\n# Visual Studio 2012";
|
|
const char _slnHeader120[] = "Microsoft Visual Studio Solution File, Format Version 12.00"
|
|
"\n# Visual Studio 2013";
|
|
const char _slnHeader140[] = "Microsoft Visual Studio Solution File, Format Version 12.00"
|
|
"\n# Visual Studio 2015";
|
|
const char _slnHeader141[] = "Microsoft Visual Studio Solution File, Format Version 12.00"
|
|
"\n# Visual Studio 15";
|
|
const char _slnHeader142[] = "Microsoft Visual Studio Solution File, Format Version 12.00"
|
|
"\n# Visual Studio Version 16";
|
|
const char _slnHeader143[] = "Microsoft Visual Studio Solution File, Format Version 12.00"
|
|
"\n# Visual Studio Version 17";
|
|
// The following UUID _may_ change for later servicepacks...
|
|
// If so we need to search through the registry at
|
|
// HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\7.0\Projects
|
|
// to find the subkey that contains a "PossibleProjectExtension"
|
|
// containing "vcproj"...
|
|
// Use the hardcoded value for now so projects generated on other
|
|
// platforms are actually usable.
|
|
const char _slnMSVCvcprojGUID[] = "{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}";
|
|
const char _slnProjectBeg[] = "\nProject(\"";
|
|
const char _slnProjectMid[] = "\") = ";
|
|
const char _slnProjectEnd[] = "\nEndProject";
|
|
const char _slnGlobalBeg[] = "\nGlobal";
|
|
const char _slnGlobalEnd[] = "\nEndGlobal";
|
|
const char _slnSolutionConf[] = "\n\tGlobalSection(SolutionConfigurationPlatforms) = preSolution"
|
|
"\n\t\tDebug|Win32 = Debug|Win32"
|
|
"\n\t\tRelease|Win32 = Release|Win32"
|
|
"\n\tEndGlobalSection";
|
|
|
|
const char _slnProjDepBeg[] = "\n\tProjectSection(ProjectDependencies) = postProject";
|
|
const char _slnProjDepEnd[] = "\n\tEndProjectSection";
|
|
const char _slnProjConfBeg[] = "\n\tGlobalSection(ProjectConfigurationPlatforms) = postSolution";
|
|
const char _slnProjRelConfTag1[]= ".Release|%1.ActiveCfg = Release|";
|
|
const char _slnProjRelConfTag2[]= ".Release|%1.Build.0 = Release|";
|
|
const char _slnProjDbgConfTag1[]= ".Debug|%1.ActiveCfg = Debug|";
|
|
const char _slnProjDbgConfTag2[]= ".Debug|%1.Build.0 = Debug|";
|
|
const char _slnProjConfEnd[] = "\n\tEndGlobalSection";
|
|
const char _slnExtSections[] = "\n\tGlobalSection(ExtensibilityGlobals) = postSolution"
|
|
"\n\tEndGlobalSection"
|
|
"\n\tGlobalSection(ExtensibilityAddIns) = postSolution"
|
|
"\n\tEndGlobalSection";
|
|
// ------------------------------------------------------------------
|
|
|
|
VcprojGenerator::VcprojGenerator()
|
|
: Win32MakefileGenerator(),
|
|
is64Bit(false),
|
|
customBuildToolFilterFileSuffix(QStringLiteral(".cbt")),
|
|
projectWriter(nullptr)
|
|
{
|
|
}
|
|
|
|
VcprojGenerator::~VcprojGenerator()
|
|
{
|
|
delete projectWriter;
|
|
}
|
|
|
|
bool VcprojGenerator::writeMakefile(QTextStream &t)
|
|
{
|
|
initProject(); // Fills the whole project with proper data
|
|
|
|
// Generate solution file
|
|
if(project->first("TEMPLATE") == "vcsubdirs") {
|
|
if (!project->isActiveConfig("build_pass")) {
|
|
debug_msg(1, "Generator: MSVC.NET: Writing solution file");
|
|
writeSubDirs(t);
|
|
} else {
|
|
debug_msg(1, "Generator: MSVC.NET: Not writing solution file for build_pass configs");
|
|
}
|
|
return true;
|
|
} else
|
|
// Generate single configuration project file
|
|
if (project->first("TEMPLATE") == "vcapp" ||
|
|
project->first("TEMPLATE") == "vclib") {
|
|
if(!project->isActiveConfig("build_pass")) {
|
|
debug_msg(1, "Generator: MSVC.NET: Writing single configuration project file");
|
|
XmlOutput xmlOut(t);
|
|
projectWriter->write(xmlOut, vcProject);
|
|
}
|
|
return true;
|
|
}
|
|
return project->isActiveConfig("build_pass");
|
|
}
|
|
|
|
bool VcprojGenerator::writeProjectMakefile()
|
|
{
|
|
QTextStream t(&Option::output);
|
|
|
|
// Check if all requirements are fulfilled
|
|
if(!project->values("QMAKE_FAILED_REQUIREMENTS").isEmpty()) {
|
|
fprintf(stderr, "Project file not generated because all requirements not met:\n\t%s\n",
|
|
var("QMAKE_FAILED_REQUIREMENTS").toLatin1().constData());
|
|
return true;
|
|
}
|
|
|
|
// Generate project file
|
|
if(project->first("TEMPLATE") == "vcapp" ||
|
|
project->first("TEMPLATE") == "vclib") {
|
|
if (!mergedProjects.count()) {
|
|
warn_msg(WarnLogic, "Generator: MSVC.NET: no single configuration created, cannot output project!");
|
|
return false;
|
|
}
|
|
|
|
debug_msg(1, "Generator: MSVC.NET: Writing project file");
|
|
VCProject mergedProject;
|
|
for (int i = 0; i < mergedProjects.count(); ++i) {
|
|
VCProjectSingleConfig *singleProject = &(mergedProjects.at(i)->vcProject);
|
|
mergedProject.SingleProjects += *singleProject;
|
|
for (int j = 0; j < singleProject->ExtraCompilersFiles.count(); ++j) {
|
|
const QString &compilerName = singleProject->ExtraCompilersFiles.at(j).Name;
|
|
if (!mergedProject.ExtraCompilers.contains(compilerName))
|
|
mergedProject.ExtraCompilers += compilerName;
|
|
}
|
|
}
|
|
|
|
if(mergedProjects.count() > 1 &&
|
|
mergedProjects.at(0)->vcProject.Name ==
|
|
mergedProjects.at(1)->vcProject.Name)
|
|
mergedProjects.at(0)->writePrlFile();
|
|
mergedProject.Name = project->first("QMAKE_PROJECT_NAME").toQString();
|
|
mergedProject.Version = mergedProjects.at(0)->vcProject.Version;
|
|
mergedProject.SdkVersion = mergedProjects.at(0)->vcProject.SdkVersion;
|
|
mergedProject.ProjectGUID = project->isEmpty("QMAKE_UUID") ? getProjectUUID().toString().toUpper() : project->first("QMAKE_UUID").toQString();
|
|
mergedProject.Keyword = project->first("VCPROJ_KEYWORD").toQString();
|
|
mergedProject.SccProjectName = mergedProjects.at(0)->vcProject.SccProjectName;
|
|
mergedProject.SccLocalPath = mergedProjects.at(0)->vcProject.SccLocalPath;
|
|
mergedProject.PlatformName = mergedProjects.at(0)->vcProject.PlatformName;
|
|
mergedProject.WindowsTargetPlatformVersion =
|
|
project->first("WINDOWS_TARGET_PLATFORM_VERSION").toQString();
|
|
mergedProject.WindowsTargetPlatformMinVersion =
|
|
project->first("WINDOWS_TARGET_PLATFORM_MIN_VERSION").toQString();
|
|
|
|
XmlOutput xmlOut(t);
|
|
projectWriter->write(xmlOut, mergedProject);
|
|
return true;
|
|
} else if(project->first("TEMPLATE") == "vcsubdirs") {
|
|
return writeMakefile(t);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
struct VcsolutionDepend {
|
|
QString uuid;
|
|
QString vcprojFile;
|
|
QString projectName;
|
|
QString target;
|
|
Target targetType;
|
|
QStringList dependencies;
|
|
};
|
|
|
|
/* Disable optimization in getProjectUUID() due to a compiler
|
|
* bug in MSVC 2015 that causes ASSERT: "&other != this" in the QString
|
|
* copy constructor for non-empty file names at:
|
|
* filename.isEmpty()?project->first("QMAKE_MAKEFILE"):filename */
|
|
|
|
#if defined(Q_CC_MSVC) && !defined(Q_CC_CLANG)
|
|
# pragma optimize( "g", off )
|
|
# pragma warning ( disable : 4748 )
|
|
#endif
|
|
|
|
QUuid VcprojGenerator::getProjectUUID(const QString &filename)
|
|
{
|
|
bool validUUID = true;
|
|
|
|
// Read GUID from variable-space
|
|
auto uuid = QUuid::fromString(project->first("GUID").toQStringView());
|
|
|
|
// If none, create one based on the MD5 of absolute project path
|
|
if(uuid.isNull() || !filename.isEmpty()) {
|
|
QString abspath = Option::fixPathToTargetOS(
|
|
filename.isEmpty() ? project->first("QMAKE_MAKEFILE").toQString() : filename);
|
|
QByteArray digest = QCryptographicHash::hash(abspath.toUtf8(), QCryptographicHash::Sha1);
|
|
memcpy((unsigned char*)(&uuid), digest.constData(), sizeof(QUuid));
|
|
validUUID = !uuid.isNull();
|
|
uuid.data4[0] = (uuid.data4[0] & 0x3F) | 0x80; // UV_DCE variant
|
|
uuid.data3 = (uuid.data3 & 0x0FFF) | (QUuid::Name<<12);
|
|
}
|
|
|
|
// If still not valid, generate new one, and suggest adding to .pro
|
|
if(uuid.isNull() || !validUUID) {
|
|
uuid = QUuid::createUuid();
|
|
fprintf(stderr,
|
|
"qmake couldn't create a GUID based on filepath, and we couldn't\nfind a valid GUID in the .pro file (Consider adding\n'GUID = %s' to the .pro file)\n",
|
|
uuid.toString().toUpper().toLatin1().constData());
|
|
}
|
|
|
|
// Store GUID in variable-space
|
|
project->values("GUID") = ProStringList(uuid.toString().toUpper());
|
|
return uuid;
|
|
}
|
|
|
|
#if defined(Q_CC_MSVC) && !defined(Q_CC_CLANG)
|
|
# pragma optimize( "g", on )
|
|
#endif
|
|
|
|
QUuid VcprojGenerator::increaseUUID(const QUuid &id)
|
|
{
|
|
QUuid result(id);
|
|
qint64 dataFirst = (result.data4[0] << 24) +
|
|
(result.data4[1] << 16) +
|
|
(result.data4[2] << 8) +
|
|
result.data4[3];
|
|
qint64 dataLast = (result.data4[4] << 24) +
|
|
(result.data4[5] << 16) +
|
|
(result.data4[6] << 8) +
|
|
result.data4[7];
|
|
|
|
if(!(dataLast++))
|
|
dataFirst++;
|
|
|
|
result.data4[0] = uchar((dataFirst >> 24) & 0xff);
|
|
result.data4[1] = uchar((dataFirst >> 16) & 0xff);
|
|
result.data4[2] = uchar((dataFirst >> 8) & 0xff);
|
|
result.data4[3] = uchar(dataFirst & 0xff);
|
|
result.data4[4] = uchar((dataLast >> 24) & 0xff);
|
|
result.data4[5] = uchar((dataLast >> 16) & 0xff);
|
|
result.data4[6] = uchar((dataLast >> 8) & 0xff);
|
|
result.data4[7] = uchar(dataLast & 0xff);
|
|
return result;
|
|
}
|
|
|
|
QString VcprojGenerator::retrievePlatformToolSet() const
|
|
{
|
|
// The PlatformToolset string corresponds to the name of a directory in
|
|
// $(VCTargetsPath)\Platforms\{Win32,x64,...}\PlatformToolsets
|
|
// e.g. v90, v100, v110, v110_xp, v120_CTP_Nov, v120, or WindowsSDK7.1
|
|
|
|
// This environment variable may be set by a commandline build
|
|
// environment such as the Windows SDK command prompt
|
|
QByteArray envVar = qgetenv("PlatformToolset");
|
|
if (!envVar.isEmpty())
|
|
return envVar;
|
|
|
|
return u"v"_qs + project->first("MSVC_TOOLSET_VER");
|
|
}
|
|
|
|
bool VcprojGenerator::isStandardSuffix(const QString &suffix) const
|
|
{
|
|
if (!project->values("QMAKE_APP_FLAG").isEmpty()) {
|
|
if (suffix.compare("exe", Qt::CaseInsensitive) == 0)
|
|
return true;
|
|
} else if (project->isActiveConfig("shared")) {
|
|
if (suffix.compare("dll", Qt::CaseInsensitive) == 0)
|
|
return true;
|
|
} else {
|
|
if (suffix.compare("lib", Qt::CaseInsensitive) == 0)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
ProString VcprojGenerator::firstInputFileName(const ProString &extraCompilerName) const
|
|
{
|
|
for (const ProString &var : project->values(ProKey(extraCompilerName + ".input"))) {
|
|
const ProStringList &files = project->values(var.toKey());
|
|
if (!files.isEmpty())
|
|
return files.first();
|
|
}
|
|
return ProString();
|
|
}
|
|
|
|
QString VcprojGenerator::firstExpandedOutputFileName(const ProString &extraCompilerName)
|
|
{
|
|
const ProString firstOutput = project->first(ProKey(extraCompilerName + ".output"));
|
|
return replaceExtraCompilerVariables(firstOutput.toQString(),
|
|
firstInputFileName(extraCompilerName).toQString(),
|
|
QString(), NoShell);
|
|
}
|
|
|
|
ProStringList VcprojGenerator::collectDependencies(QMakeProject *proj, QHash<QString, QString> &projLookup,
|
|
QHash<QString, QString> &projGuids,
|
|
QHash<VcsolutionDepend *, QStringList> &extraSubdirs,
|
|
QHash<QString, VcsolutionDepend*> &solution_depends,
|
|
QList<VcsolutionDepend*> &solution_cleanup,
|
|
QTextStream &t,
|
|
QHash<QString, ProStringList> &subdirProjectLookup,
|
|
const ProStringList &allDependencies)
|
|
{
|
|
QList<QPair<QString, ProStringList>> collectedSubdirs;
|
|
ProStringList tmp_proj_subdirs = proj->values("SUBDIRS");
|
|
ProStringList projectsInProject;
|
|
const int numSubdirs = tmp_proj_subdirs.size();
|
|
collectedSubdirs.reserve(numSubdirs);
|
|
for (int x = 0; x < numSubdirs; ++x) {
|
|
ProString tmpdir = tmp_proj_subdirs.at(x);
|
|
const ProKey tmpdirConfig(tmpdir + ".CONFIG");
|
|
if (!proj->isEmpty(tmpdirConfig)) {
|
|
const ProStringList config = proj->values(tmpdirConfig);
|
|
if (config.contains(QStringLiteral("no_default_target")))
|
|
continue; // Ignore this sub-dir
|
|
}
|
|
const ProKey fkey(tmpdir + ".file");
|
|
const ProKey skey(tmpdir + ".subdir");
|
|
if (!proj->isEmpty(fkey)) {
|
|
if (!proj->isEmpty(skey))
|
|
warn_msg(WarnLogic, "Cannot assign both file and subdir for subdir %s",
|
|
tmpdir.toLatin1().constData());
|
|
tmpdir = proj->first(fkey);
|
|
} else if (!proj->isEmpty(skey)) {
|
|
tmpdir = proj->first(skey);
|
|
}
|
|
projectsInProject.append(tmpdir);
|
|
collectedSubdirs.append(qMakePair(tmpdir.toQString(), proj->values(ProKey(tmp_proj_subdirs.at(x) + ".depends"))));
|
|
projLookup.insert(tmp_proj_subdirs.at(x).toQString(), tmpdir.toQString());
|
|
}
|
|
for (const auto &subdir : qAsConst(collectedSubdirs)) {
|
|
QString profile = subdir.first;
|
|
QFileInfo fi(fileInfo(Option::normalizePath(profile)));
|
|
if (fi.exists()) {
|
|
if (fi.isDir()) {
|
|
if (!profile.endsWith(Option::dir_sep))
|
|
profile += Option::dir_sep;
|
|
profile += fi.baseName() + Option::pro_ext;
|
|
QString profileKey = fi.absoluteFilePath();
|
|
fi = QFileInfo(fileInfo(Option::normalizePath(profile)));
|
|
if (!fi.exists())
|
|
continue;
|
|
projLookup.insert(profileKey, fi.absoluteFilePath());
|
|
}
|
|
QString oldpwd = qmake_getpwd();
|
|
QString oldoutpwd = Option::output_dir;
|
|
QMakeProject tmp_proj;
|
|
QString dir = fi.absolutePath(), fn = fi.fileName();
|
|
if (!dir.isEmpty()) {
|
|
if (!qmake_setpwd(dir))
|
|
fprintf(stderr, "Cannot find directory: %s", dir.toLatin1().constData());
|
|
}
|
|
Option::output_dir = Option::globals->shadowedPath(QDir::cleanPath(dir));
|
|
if (tmp_proj.read(fn)) {
|
|
// Check if all requirements are fulfilled
|
|
if (!tmp_proj.isEmpty("QMAKE_FAILED_REQUIREMENTS")) {
|
|
fprintf(stderr, "Project file(%s) not added to Solution because all requirements not met:\n\t%s\n",
|
|
fn.toLatin1().constData(),
|
|
tmp_proj.values("QMAKE_FAILED_REQUIREMENTS").join(' ').toLatin1().constData());
|
|
qmake_setpwd(oldpwd);
|
|
Option::output_dir = oldoutpwd;
|
|
continue;
|
|
}
|
|
if (tmp_proj.first("TEMPLATE") == "vcsubdirs") {
|
|
ProStringList tmpList = collectDependencies(&tmp_proj, projLookup, projGuids, extraSubdirs, solution_depends, solution_cleanup, t, subdirProjectLookup, subdir.second);
|
|
subdirProjectLookup.insert(subdir.first, tmpList);
|
|
} else {
|
|
ProStringList tmpList;
|
|
tmpList += subdir.second;
|
|
tmpList += allDependencies;
|
|
// Initialize a 'fake' project to get the correct variables
|
|
// and to be able to extract all the dependencies
|
|
Option::QMAKE_MODE old_mode = Option::qmake_mode;
|
|
Option::qmake_mode = Option::QMAKE_GENERATE_NOTHING;
|
|
VcprojGenerator tmp_vcproj;
|
|
tmp_vcproj.setNoIO(true);
|
|
tmp_vcproj.setProjectFile(&tmp_proj);
|
|
Option::qmake_mode = old_mode;
|
|
|
|
// We assume project filename is [QMAKE_PROJECT_NAME].vcproj
|
|
const ProString projectName = tmp_vcproj.project->first("QMAKE_PROJECT_NAME");
|
|
const QString vcproj = projectName + project->first("VCPROJ_EXTENSION");
|
|
QString vcprojDir = Option::output_dir;
|
|
|
|
// If file doesn't exsist, then maybe the users configuration
|
|
// doesn't allow it to be created. Skip to next...
|
|
if (!exists(vcprojDir + Option::dir_sep + vcproj)) {
|
|
warn_msg(WarnLogic, "Ignored (not found) '%s'", QString(vcprojDir + Option::dir_sep + vcproj).toLatin1().constData());
|
|
goto nextfile; // # Dirty!
|
|
}
|
|
|
|
VcsolutionDepend *newDep = new VcsolutionDepend;
|
|
newDep->vcprojFile = vcprojDir + Option::dir_sep + vcproj;
|
|
newDep->projectName = projectName.toQString();
|
|
newDep->target = tmp_proj.first("MSVCPROJ_TARGET").toQString().section(Option::dir_sep, -1);
|
|
newDep->targetType = tmp_vcproj.projectTarget;
|
|
newDep->uuid = tmp_proj.isEmpty("QMAKE_UUID") ? getProjectUUID(Option::fixPathToLocalOS(vcprojDir + QDir::separator() + vcproj)).toString().toUpper(): tmp_proj.first("QMAKE_UUID").toQString();
|
|
// We want to store it as the .lib name.
|
|
if (newDep->target.endsWith(".dll"))
|
|
newDep->target = newDep->target.left(newDep->target.length()-3) + "lib";
|
|
projGuids.insert(newDep->projectName, newDep->target);
|
|
|
|
if (tmpList.size()) {
|
|
const ProStringList depends = tmpList;
|
|
for (const ProString &dep : depends) {
|
|
QString depend = dep.toQString();
|
|
if (!projGuids[depend].isEmpty()) {
|
|
newDep->dependencies << projGuids[depend];
|
|
} else if (subdirProjectLookup[projLookup[depend]].size() > 0) {
|
|
const ProStringList tmpLst = subdirProjectLookup[projLookup[depend]];
|
|
for (const ProString &tDep : tmpLst) {
|
|
QString tmpDep = tDep.toQString();
|
|
newDep->dependencies << projGuids[projLookup[tmpDep]];
|
|
}
|
|
} else {
|
|
extraSubdirs.insert(newDep, tmpList.toQStringList());
|
|
newDep->dependencies.clear();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// All ActiveQt Server projects are dependent on idc.exe
|
|
if (tmp_proj.values("CONFIG").contains("qaxserver"))
|
|
newDep->dependencies << "idc.exe";
|
|
|
|
// Add all unknown libs to the deps
|
|
QStringList where = QStringList() << "LIBS" << "LIBS_PRIVATE"
|
|
<< "QMAKE_LIBS" << "QMAKE_LIBS_PRIVATE";
|
|
for (QStringList::ConstIterator wit = where.cbegin();
|
|
wit != where.cend(); ++wit) {
|
|
const ProStringList &l = tmp_proj.values(ProKey(*wit));
|
|
for (ProStringList::ConstIterator it = l.begin(); it != l.end(); ++it) {
|
|
const QString opt = fixLibFlag(*it).toQString();
|
|
if (!opt.startsWith("/") && // Not a switch
|
|
opt != newDep->target && // Not self
|
|
opt != "opengl32.lib" && // We don't care about these libs
|
|
opt != "glu32.lib" && // to make depgen alittle faster
|
|
opt != "kernel32.lib" &&
|
|
opt != "user32.lib" &&
|
|
opt != "gdi32.lib" &&
|
|
opt != "comdlg32.lib" &&
|
|
opt != "advapi32.lib" &&
|
|
opt != "shell32.lib" &&
|
|
opt != "ole32.lib" &&
|
|
opt != "oleaut32.lib" &&
|
|
opt != "uuid.lib" &&
|
|
opt != "imm32.lib" &&
|
|
opt != "winmm.lib" &&
|
|
opt != "wsock32.lib" &&
|
|
opt != "ws2_32.lib" &&
|
|
opt != "winspool.lib" &&
|
|
opt != "delayimp.lib")
|
|
{
|
|
newDep->dependencies << opt.section(Option::dir_sep, -1);
|
|
}
|
|
}
|
|
}
|
|
#ifdef DEBUG_SOLUTION_GEN
|
|
qDebug("Deps for %20s: [%s]", newDep->target.toLatin1().constData(), newDep->dependencies.join(" :: ").toLatin1().constData());
|
|
#endif
|
|
solution_cleanup.append(newDep);
|
|
solution_depends.insert(newDep->target, newDep);
|
|
}
|
|
nextfile:
|
|
qmake_setpwd(oldpwd);
|
|
Option::output_dir = oldoutpwd;
|
|
}
|
|
}
|
|
}
|
|
return projectsInProject;
|
|
}
|
|
|
|
void VcprojGenerator::writeSubDirs(QTextStream &t)
|
|
{
|
|
// Check if all requirements are fulfilled
|
|
if(!project->values("QMAKE_FAILED_REQUIREMENTS").isEmpty()) {
|
|
fprintf(stderr, "Project file not generated because all requirements not met:\n\t%s\n",
|
|
var("QMAKE_FAILED_REQUIREMENTS").toLatin1().constData());
|
|
return;
|
|
}
|
|
|
|
switch (vcProject.Configuration.CompilerVersion) {
|
|
case NET2022:
|
|
t << _slnHeader143;
|
|
break;
|
|
case NET2019:
|
|
t << _slnHeader142;
|
|
break;
|
|
case NET2017:
|
|
t << _slnHeader141;
|
|
break;
|
|
case NET2015:
|
|
t << _slnHeader140;
|
|
break;
|
|
case NET2013:
|
|
t << _slnHeader120;
|
|
break;
|
|
case NET2012:
|
|
t << _slnHeader110;
|
|
break;
|
|
case NET2010:
|
|
t << _slnHeader100;
|
|
break;
|
|
case NET2008:
|
|
t << _slnHeader90;
|
|
break;
|
|
case NET2005:
|
|
t << _slnHeader80;
|
|
break;
|
|
case NET2003:
|
|
t << _slnHeader71;
|
|
break;
|
|
case NET2002:
|
|
t << _slnHeader70;
|
|
break;
|
|
default:
|
|
t << _slnHeader70;
|
|
warn_msg(WarnLogic, "Generator: MSVC.NET: Unknown version (%d) of MSVC detected for .sln",
|
|
vcProject.Configuration.CompilerVersion);
|
|
break;
|
|
}
|
|
|
|
QHash<QString, VcsolutionDepend*> solution_depends;
|
|
QList<VcsolutionDepend*> solution_cleanup;
|
|
|
|
// Make sure that all temp projects are configured
|
|
// for release so that the depends are created
|
|
// without the debug <lib>dxxx.lib name mangling
|
|
QString old_after_vars = Option::globals->extra_cmds[QMakeEvalAfter];
|
|
Option::globals->extra_cmds[QMakeEvalAfter].append("\nCONFIG+=release");
|
|
|
|
QHash<QString, QString> profileLookup;
|
|
QHash<QString, QString> projGuids;
|
|
QHash<VcsolutionDepend *, QStringList> extraSubdirs;
|
|
QHash<QString, ProStringList> subdirProjectLookup;
|
|
collectDependencies(project, profileLookup, projGuids, extraSubdirs, solution_depends, solution_cleanup, t, subdirProjectLookup);
|
|
|
|
// write out projects
|
|
for (QList<VcsolutionDepend*>::Iterator it = solution_cleanup.begin(); it != solution_cleanup.end(); ++it) {
|
|
// ### quoting rules?
|
|
t << _slnProjectBeg << _slnMSVCvcprojGUID << _slnProjectMid
|
|
<< "\"" << (*it)->projectName << "\", \"" << (*it)->vcprojFile
|
|
<< "\", \"" << (*it)->uuid << "\"";
|
|
|
|
debug_msg(1, "Project %s has dependencies: %s", (*it)->target.toLatin1().constData(), (*it)->dependencies.join(" ").toLatin1().constData());
|
|
|
|
bool hasDependency = false;
|
|
for (QStringList::iterator dit = (*it)->dependencies.begin(); dit != (*it)->dependencies.end(); ++dit) {
|
|
if (VcsolutionDepend *vc = solution_depends[*dit]) {
|
|
if (!hasDependency) {
|
|
hasDependency = true;
|
|
t << _slnProjDepBeg;
|
|
}
|
|
t << "\n\t\t" << vc->uuid << " = " << vc->uuid;
|
|
}
|
|
}
|
|
if (hasDependency)
|
|
t << _slnProjDepEnd;
|
|
|
|
t << _slnProjectEnd;
|
|
}
|
|
|
|
t << _slnGlobalBeg;
|
|
|
|
for (auto extraIt = extraSubdirs.cbegin(), end = extraSubdirs.cend(); extraIt != end; ++extraIt) {
|
|
for (const QString &depend : extraIt.value()) {
|
|
if (!projGuids[depend].isEmpty()) {
|
|
extraIt.key()->dependencies << projGuids[depend];
|
|
} else if (!profileLookup[depend].isEmpty()) {
|
|
if (!projGuids[profileLookup[depend]].isEmpty())
|
|
extraIt.key()->dependencies << projGuids[profileLookup[depend]];
|
|
}
|
|
}
|
|
}
|
|
QString slnConf = _slnSolutionConf;
|
|
if (!project->isEmpty("VCPROJ_ARCH")) {
|
|
slnConf.replace(QLatin1String("|Win32"), "|" + project->first("VCPROJ_ARCH"));
|
|
} else if (is64Bit) {
|
|
slnConf.replace(QLatin1String("|Win32"), QLatin1String("|x64"));
|
|
}
|
|
t << slnConf;
|
|
|
|
// Restore previous after_user_var options
|
|
Option::globals->extra_cmds[QMakeEvalAfter] = old_after_vars;
|
|
|
|
t << _slnProjConfBeg;
|
|
for(QList<VcsolutionDepend*>::Iterator it = solution_cleanup.begin(); it != solution_cleanup.end(); ++it) {
|
|
QString platform = is64Bit ? "x64" : "Win32";
|
|
QString xplatform = platform;
|
|
if (!project->isEmpty("VCPROJ_ARCH")) {
|
|
xplatform = project->first("VCPROJ_ARCH").toQString();
|
|
}
|
|
if (!project->isHostBuild())
|
|
platform = xplatform;
|
|
t << "\n\t\t" << (*it)->uuid << QString(_slnProjDbgConfTag1).arg(xplatform) << platform;
|
|
t << "\n\t\t" << (*it)->uuid << QString(_slnProjDbgConfTag2).arg(xplatform) << platform;
|
|
t << "\n\t\t" << (*it)->uuid << QString(_slnProjRelConfTag1).arg(xplatform) << platform;
|
|
t << "\n\t\t" << (*it)->uuid << QString(_slnProjRelConfTag2).arg(xplatform) << platform;
|
|
}
|
|
t << _slnProjConfEnd;
|
|
t << _slnExtSections;
|
|
t << _slnGlobalEnd;
|
|
|
|
|
|
while (!solution_cleanup.isEmpty())
|
|
delete solution_cleanup.takeFirst();
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
// ------------------------------------------------------------------------------------------------
|
|
|
|
bool VcprojGenerator::hasBuiltinCompiler(const QString &file)
|
|
{
|
|
// Source files
|
|
for (int i = 0; i < Option::cpp_ext.count(); ++i)
|
|
if (file.endsWith(Option::cpp_ext.at(i)))
|
|
return true;
|
|
for (int i = 0; i < Option::c_ext.count(); ++i)
|
|
if (file.endsWith(Option::c_ext.at(i)))
|
|
return true;
|
|
if (file.endsWith(".rc")
|
|
|| file.endsWith(".idl"))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
void VcprojGenerator::createCustomBuildToolFakeFile(const QString &cbtFilePath,
|
|
const QString &realOutFilePath)
|
|
{
|
|
QFile file(fileFixify(cbtFilePath, FileFixifyFromOutdir | FileFixifyAbsolute));
|
|
if (file.exists())
|
|
return;
|
|
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
|
warn_msg(WarnLogic, "Cannot create '%s'.", qPrintable(file.fileName()));
|
|
return;
|
|
}
|
|
file.write("This is a dummy file needed to create ");
|
|
file.write(qPrintable(realOutFilePath));
|
|
file.write("\n");
|
|
}
|
|
|
|
void VcprojGenerator::init()
|
|
{
|
|
is64Bit = (project->first("QMAKE_TARGET.arch") == "x86_64");
|
|
projectWriter = createProjectWriter();
|
|
|
|
if(project->first("TEMPLATE") == "vcsubdirs") //too much work for subdirs
|
|
return;
|
|
|
|
debug_msg(1, "Generator: MSVC.NET: Initializing variables");
|
|
|
|
// this should probably not be here, but I'm using it to wrap the .t files
|
|
if (project->first("TEMPLATE") == "vcapp")
|
|
project->values("QMAKE_APP_FLAG").append("1");
|
|
else if (project->first("TEMPLATE") == "vclib")
|
|
project->values("QMAKE_LIB_FLAG").append("1");
|
|
|
|
processVars();
|
|
|
|
// set /VERSION for EXE/DLL header
|
|
ProString major_minor = project->first("VERSION_PE_HEADER");
|
|
if (major_minor.isEmpty()) {
|
|
ProString version = project->first("VERSION");
|
|
if (!version.isEmpty()) {
|
|
int firstDot = version.indexOf(".");
|
|
int secondDot = version.indexOf(".", firstDot + 1);
|
|
major_minor = version.left(secondDot);
|
|
}
|
|
}
|
|
if (!major_minor.isEmpty())
|
|
project->values("QMAKE_LFLAGS").append("/VERSION:" + major_minor);
|
|
|
|
MakefileGenerator::init();
|
|
|
|
// $$QMAKE.. -> $$MSVCPROJ.. -------------------------------------
|
|
const ProStringList &incs = project->values("INCLUDEPATH");
|
|
for (ProStringList::ConstIterator incit = incs.begin(); incit != incs.end(); ++incit) {
|
|
QString inc = (*incit).toQString();
|
|
project->values("MSVCPROJ_INCPATH").append("-I" + escapeFilePath(inc));
|
|
}
|
|
|
|
QString dest = Option::fixPathToTargetOS(project->first("TARGET").toQString()) + project->first("TARGET_EXT");
|
|
project->values("MSVCPROJ_TARGET") = ProStringList(dest);
|
|
|
|
// DLL COPY ------------------------------------------------------
|
|
if (project->isActiveConfig("dll") && !project->values("DLLDESTDIR").isEmpty()) {
|
|
const ProStringList &dlldirs = project->values("DLLDESTDIR");
|
|
QString copydll("");
|
|
ProStringList::ConstIterator dlldir;
|
|
for (dlldir = dlldirs.begin(); dlldir != dlldirs.end(); ++dlldir) {
|
|
if (!copydll.isEmpty())
|
|
copydll += " && ";
|
|
copydll += "copy \"$(TargetPath)\" " + escapeFilePath(*dlldir);
|
|
}
|
|
|
|
QString deststr("Copy " + dest + " to ");
|
|
for (dlldir = dlldirs.begin(); dlldir != dlldirs.end();) {
|
|
deststr += *dlldir;
|
|
++dlldir;
|
|
if (dlldir != dlldirs.end())
|
|
deststr += ", ";
|
|
}
|
|
|
|
project->values("MSVCPROJ_COPY_DLL").append(copydll);
|
|
project->values("MSVCPROJ_COPY_DLL_DESC").append(deststr);
|
|
}
|
|
|
|
#if 0
|
|
// Verbose output if "-d -d"...
|
|
qDebug("Generator: MSVC.NET: List of current variables:");
|
|
for (ProValueMap::ConstIterator it = project->variables().begin(); it != project->variables().end(); ++it)
|
|
qDebug("Generator: MSVC.NET: %s => %s", qPrintable(it.key().toQString()), qPrintable(it.value().join(" | ")));
|
|
#endif
|
|
|
|
// Figure out what we're trying to build
|
|
if(project->first("TEMPLATE") == "vcapp") {
|
|
projectTarget = Application;
|
|
} else if(project->first("TEMPLATE") == "vclib") {
|
|
if(project->isActiveConfig("staticlib")) {
|
|
project->values("LIBS") += project->values("RES_FILE");
|
|
projectTarget = StaticLib;
|
|
} else
|
|
projectTarget = SharedLib;
|
|
}
|
|
|
|
// Setup PCH variables
|
|
precompH = project->first("PRECOMPILED_HEADER").toQString();
|
|
precompSource = project->first("PRECOMPILED_SOURCE").toQString();
|
|
pchIsCFile = project->isActiveConfig("precompile_header_c");
|
|
usePCH = !precompH.isEmpty() && (pchIsCFile || project->isActiveConfig("precompile_header"));
|
|
if (usePCH) {
|
|
precompHFilename = fileInfo(precompH).fileName();
|
|
// Created files
|
|
QString origTarget = project->first("QMAKE_ORIG_TARGET").toQString();
|
|
precompObj = origTarget + Option::obj_ext;
|
|
precompPch = origTarget + ".pch";
|
|
// Add PRECOMPILED_HEADER to HEADERS
|
|
if (!project->values("HEADERS").contains(precompH))
|
|
project->values("HEADERS") += precompH;
|
|
// Return to variable pool
|
|
project->values("PRECOMPILED_OBJECT") = ProStringList(precompObj);
|
|
project->values("PRECOMPILED_PCH") = ProStringList(precompPch);
|
|
|
|
autogenPrecompSource = precompSource.isEmpty() && project->isActiveConfig("autogen_precompile_source");
|
|
if (autogenPrecompSource) {
|
|
precompSource = precompH
|
|
+ (pchIsCFile
|
|
? (Option::c_ext.count() ? Option::c_ext.at(0) : QLatin1String(".c"))
|
|
: (Option::cpp_ext.count() ? Option::cpp_ext.at(0) : QLatin1String(".cpp")));
|
|
project->values("GENERATED_SOURCES") += precompSource;
|
|
} else if (!precompSource.isEmpty()) {
|
|
project->values("SOURCES") += precompSource;
|
|
}
|
|
}
|
|
|
|
// Helper function to create a fake file foo.cbt for the project view.
|
|
//
|
|
// This prevents VS from complaining about a circular dependency from "foo -> foo".
|
|
//
|
|
// The .cbt file is added as "source" of the Custom Build Tool. This means, in the project
|
|
// view, this is the file the Custom Build Tool property page is attached to.
|
|
//
|
|
// This function returns a pair with
|
|
// - the fully resolved output file path
|
|
// - the file path of the .cbt file
|
|
auto addExtraCompilerSourceWithCustomBuildToolFakeFile
|
|
= [this](const QString &compilerOutput, const ProString &extraCompiler,
|
|
const QStringList &inputs) -> std::pair<QString, QString>
|
|
{
|
|
QString realOut = replaceExtraCompilerVariables(compilerOutput, inputs, {}, NoShell);
|
|
QString out = realOut + customBuildToolFilterFileSuffix;
|
|
createCustomBuildToolFakeFile(out, realOut);
|
|
out = Option::fixPathToTargetOS(out, false);
|
|
extraCompilerSources[out] += extraCompiler.toQString();
|
|
return { realOut, out };
|
|
};
|
|
|
|
// Add all input files for a custom compiler into a map for uniqueness.
|
|
//
|
|
// Use .cbt files for the following cases:
|
|
// - CONFIG += combine
|
|
// - the input has a built-in compiler (e.g. C++ source file)
|
|
for (const ProString &quc : project->values("QMAKE_EXTRA_COMPILERS")) {
|
|
const ProStringList &invar = project->values(ProKey(quc + ".input"));
|
|
const QString compiler_out = project->first(ProKey(quc + ".output")).toQString();
|
|
|
|
QStringList inputFiles;
|
|
for (auto it = invar.begin(); it != invar.end(); ++it)
|
|
inputFiles += project->values(it->toKey()).toQStringList();
|
|
|
|
if (project->values(ProKey(quc + ".CONFIG")).contains("combine")) {
|
|
// Handle "CONFIG += combine" extra compilers.
|
|
QString realOut;
|
|
QString out;
|
|
std::tie(realOut, out)
|
|
= addExtraCompilerSourceWithCustomBuildToolFakeFile(compiler_out, quc, inputFiles);
|
|
if (hasBuiltinCompiler(realOut))
|
|
extraCompilerOutputs[out] = realOut;
|
|
} else {
|
|
// Handle regular 1-to-1 extra compilers.
|
|
for (const QString &file : inputFiles) {
|
|
if (verifyExtraCompiler(quc, file)) {
|
|
if (!hasBuiltinCompiler(file)) {
|
|
extraCompilerSources[file] += quc.toQString();
|
|
} else {
|
|
QString out;
|
|
std::tie(std::ignore, out)
|
|
= addExtraCompilerSourceWithCustomBuildToolFakeFile(compiler_out,
|
|
quc,
|
|
QStringList(file));
|
|
extraCompilerOutputs[out] = file;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#if 0 // Debugging
|
|
for (auto it = extraCompilerSources.cbegin(), end = extraCompilerSources.cend(); it != end; ++it)
|
|
qDebug("Extracompilers for %s are (%s)", it.key().toLatin1().constData(), it.value().join(", ").toLatin1().constData());
|
|
for (auto it = extraCompilerOutputs.cbegin(), end = extraCompilerOutputs.cend(); it != end; ++it)
|
|
qDebug("Object mapping for %s is (%s)", qPrintable(it.key()), qPrintable(it.value()));
|
|
qDebug("");
|
|
#endif
|
|
}
|
|
|
|
bool VcprojGenerator::mergeBuildProject(MakefileGenerator *other)
|
|
{
|
|
if (!other || !other->projectFile()) {
|
|
warn_msg(WarnLogic, "VcprojGenerator: Cannot merge null project.");
|
|
return false;
|
|
}
|
|
if (other->projectFile()->first("MAKEFILE_GENERATOR") != project->first("MAKEFILE_GENERATOR")) {
|
|
warn_msg(WarnLogic, "VcprojGenerator: Cannot merge other types of projects! (ignored)");
|
|
return false;
|
|
}
|
|
|
|
VcprojGenerator *otherVC = static_cast<VcprojGenerator*>(other);
|
|
mergedProjects += otherVC;
|
|
return true;
|
|
}
|
|
|
|
void VcprojGenerator::initProject()
|
|
{
|
|
// Initialize XML sub elements
|
|
// - Do this first since project elements may need
|
|
// - to know of certain configuration options
|
|
initConfiguration();
|
|
initRootFiles();
|
|
initSourceFiles();
|
|
initHeaderFiles();
|
|
initGeneratedFiles();
|
|
initLexYaccFiles();
|
|
initTranslationFiles();
|
|
initFormFiles();
|
|
initResourceFiles();
|
|
initDistributionFiles();
|
|
initExtraCompilerOutputs();
|
|
|
|
// Own elements -----------------------------
|
|
vcProject.Name = project->first("QMAKE_ORIG_TARGET").toQString();
|
|
switch (vcProject.Configuration.CompilerVersion) {
|
|
case NET2022:
|
|
vcProject.Version = "17.00";
|
|
break;
|
|
case NET2019:
|
|
vcProject.Version = "16.00";
|
|
break;
|
|
case NET2017:
|
|
vcProject.Version = "15.00";
|
|
break;
|
|
case NET2015:
|
|
vcProject.Version = "14.00";
|
|
break;
|
|
case NET2013:
|
|
vcProject.Version = "12.00";
|
|
break;
|
|
case NET2012:
|
|
vcProject.Version = "11.00";
|
|
break;
|
|
case NET2010:
|
|
vcProject.Version = "10.00";
|
|
break;
|
|
case NET2008:
|
|
vcProject.Version = "9,00";
|
|
break;
|
|
case NET2005:
|
|
//### using ',' because of a bug in 2005 B2
|
|
//### VS uses '.' or ',' depending on the regional settings! Using ',' always works.
|
|
vcProject.Version = "8,00";
|
|
break;
|
|
case NET2003:
|
|
vcProject.Version = "7.10";
|
|
break;
|
|
case NET2002:
|
|
vcProject.Version = "7.00";
|
|
break;
|
|
default:
|
|
vcProject.Version = "7.00";
|
|
warn_msg(WarnLogic, "Generator: MSVC.NET: Unknown version (%d) of MSVC detected for .vcproj", vcProject.Configuration.CompilerVersion);
|
|
break;
|
|
}
|
|
|
|
vcProject.Keyword = project->first("VCPROJ_KEYWORD").toQString();
|
|
if (!project->isEmpty("VCPROJ_ARCH")) {
|
|
vcProject.PlatformName = project->first("VCPROJ_ARCH").toQString();
|
|
} else {
|
|
vcProject.PlatformName = (is64Bit ? "x64" : "Win32");
|
|
}
|
|
vcProject.SdkVersion = project->first("WINSDK_VER").toQString();
|
|
// These are not used by Qt, but may be used by customers
|
|
vcProject.SccProjectName = project->first("SCCPROJECTNAME").toQString();
|
|
vcProject.SccLocalPath = project->first("SCCLOCALPATH").toQString();
|
|
vcProject.flat_files = project->isActiveConfig("flat");
|
|
|
|
// Set up the full target path for target conflict checking.
|
|
const QChar slash = QLatin1Char('/');
|
|
QString destdir = QDir::fromNativeSeparators(var("DESTDIR"));
|
|
if (!destdir.endsWith(slash))
|
|
destdir.append(slash);
|
|
project->values("DEST_TARGET") = ProStringList(destdir
|
|
+ project->first("TARGET")
|
|
+ project->first("TARGET_EXT"));
|
|
}
|
|
|
|
void VcprojGenerator::initConfiguration()
|
|
{
|
|
// Initialize XML sub elements
|
|
// - Do this first since main configuration elements may need
|
|
// - to know of certain compiler/linker options
|
|
VCConfiguration &conf = vcProject.Configuration;
|
|
conf.suppressUnknownOptionWarnings = project->isActiveConfig("suppress_vcproj_warnings");
|
|
conf.CompilerVersion = vsVersionFromString(project->first("MSVC_VER"));
|
|
|
|
initCompilerTool();
|
|
|
|
// Only on configuration per build
|
|
bool isDebug = project->isActiveConfig("debug");
|
|
|
|
if(projectTarget == StaticLib)
|
|
initLibrarianTool();
|
|
else {
|
|
conf.linker.GenerateDebugInformation = project->isActiveConfig("debug_info") ? _True : _False;
|
|
initLinkerTool();
|
|
}
|
|
initManifestTool();
|
|
initResourceTool();
|
|
initIDLTool();
|
|
|
|
// Own elements -----------------------------
|
|
ProString temp = project->first("BuildBrowserInformation");
|
|
switch (projectTarget) {
|
|
case SharedLib:
|
|
conf.ConfigurationType = typeDynamicLibrary;
|
|
break;
|
|
case StaticLib:
|
|
conf.ConfigurationType = typeStaticLibrary;
|
|
break;
|
|
case Application:
|
|
default:
|
|
conf.ConfigurationType = typeApplication;
|
|
break;
|
|
}
|
|
|
|
conf.OutputDirectory = project->first("DESTDIR").toQString();
|
|
if (conf.OutputDirectory.isEmpty())
|
|
conf.OutputDirectory = ".\\";
|
|
if (!conf.OutputDirectory.endsWith("\\"))
|
|
conf.OutputDirectory += '\\';
|
|
if (conf.CompilerVersion >= NET2010) {
|
|
conf.PlatformToolSet = retrievePlatformToolSet();
|
|
|
|
const QFileInfo targetInfo = fileInfo(project->first("MSVCPROJ_TARGET").toQString());
|
|
conf.PrimaryOutput = targetInfo.completeBaseName();
|
|
|
|
const QString targetSuffix = targetInfo.suffix();
|
|
if (!isStandardSuffix(targetSuffix))
|
|
conf.PrimaryOutputExtension = '.' + targetSuffix;
|
|
}
|
|
|
|
conf.Name = project->values("BUILD_NAME").join(' ');
|
|
if (conf.Name.isEmpty())
|
|
conf.Name = isDebug ? "Debug" : "Release";
|
|
conf.ConfigurationName = conf.Name;
|
|
if (!project->isEmpty("VCPROJ_ARCH")) {
|
|
conf.Name += "|" + project->first("VCPROJ_ARCH");
|
|
} else {
|
|
conf.Name += (is64Bit ? "|x64" : "|Win32");
|
|
}
|
|
conf.ATLMinimizesCRunTimeLibraryUsage = (project->first("ATLMinimizesCRunTimeLibraryUsage").isEmpty() ? _False : _True);
|
|
conf.BuildBrowserInformation = triState(temp.isEmpty() ? (short)unset : temp.toShort());
|
|
temp = project->first("CharacterSet");
|
|
conf.CharacterSet = charSet(temp.isEmpty() ? short(charSetNotSet) : temp.toShort());
|
|
conf.DeleteExtensionsOnClean = project->first("DeleteExtensionsOnClean").toQString();
|
|
conf.ImportLibrary = conf.linker.ImportLibrary;
|
|
conf.IntermediateDirectory = project->first("OBJECTS_DIR").toQString();
|
|
conf.WholeProgramOptimization = conf.compiler.WholeProgramOptimization;
|
|
temp = project->first("UseOfATL");
|
|
if(!temp.isEmpty())
|
|
conf.UseOfATL = useOfATL(temp.toShort());
|
|
temp = project->first("UseOfMfc");
|
|
if(!temp.isEmpty())
|
|
conf.UseOfMfc = useOfMfc(temp.toShort());
|
|
|
|
// Configuration does not need parameters from
|
|
// these sub XML items;
|
|
initCustomBuildTool();
|
|
initPreBuildEventTools();
|
|
initPostBuildEventTools();
|
|
// Only deploy for crosscompiled projects
|
|
if (!project->isHostBuild())
|
|
initDeploymentTool();
|
|
initWinDeployQtTool();
|
|
initPreLinkEventTools();
|
|
}
|
|
|
|
// Filter from the given QMAKE_CFLAGS the options that are relevant
|
|
// for the vcxproj-global VCCLCompilerTool.
|
|
static ProStringList relevantCFlags(const ProStringList &flags)
|
|
{
|
|
ProStringList result;
|
|
static const QRegularExpression rex("^[/-]std:");
|
|
for (const ProString &flag : flags) {
|
|
if (rex.match(flag.toQString()).hasMatch()) {
|
|
result.append(flag);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void VcprojGenerator::initCompilerTool()
|
|
{
|
|
QString placement = project->first("OBJECTS_DIR").toQString();
|
|
if(placement.isEmpty())
|
|
placement = ".\\";
|
|
|
|
VCConfiguration &conf = vcProject.Configuration;
|
|
if (conf.CompilerVersion >= NET2010) {
|
|
// adjust compiler tool defaults for VS 2010 and above
|
|
conf.compiler.Optimization = optimizeDisabled;
|
|
}
|
|
conf.compiler.AssemblerListingLocation = placement ;
|
|
conf.compiler.ObjectFile = placement ;
|
|
conf.compiler.ExceptionHandling = ehNone;
|
|
// PCH
|
|
if (usePCH) {
|
|
conf.compiler.UsePrecompiledHeader = pchUseUsingSpecific;
|
|
conf.compiler.PrecompiledHeaderFile = "$(IntDir)\\" + precompPch;
|
|
conf.compiler.PrecompiledHeaderThrough = project->first("PRECOMPILED_HEADER").toQString();
|
|
conf.compiler.ForcedIncludeFiles = project->values("PRECOMPILED_HEADER").toQStringList();
|
|
}
|
|
|
|
conf.compiler.parseOptions(relevantCFlags(project->values("QMAKE_CFLAGS")));
|
|
conf.compiler.parseOptions(project->values("QMAKE_CXXFLAGS"));
|
|
|
|
if (project->isActiveConfig("windows"))
|
|
conf.compiler.PreprocessorDefinitions += "_WINDOWS";
|
|
else if (project->isActiveConfig("console"))
|
|
conf.compiler.PreprocessorDefinitions += "_CONSOLE";
|
|
|
|
conf.compiler.PreprocessorDefinitions += project->values("DEFINES").toQStringList();
|
|
conf.compiler.PreprocessorDefinitions += project->values("PRL_EXPORT_DEFINES").toQStringList();
|
|
conf.compiler.parseOptions(project->values("MSVCPROJ_INCPATH"));
|
|
}
|
|
|
|
void VcprojGenerator::initLibrarianTool()
|
|
{
|
|
VCConfiguration &conf = vcProject.Configuration;
|
|
conf.librarian.OutputFile = "$(OutDir)\\";
|
|
conf.librarian.OutputFile += project->first("MSVCPROJ_TARGET").toQString();
|
|
conf.librarian.AdditionalOptions += project->values("QMAKE_LIBFLAGS").toQStringList();
|
|
}
|
|
|
|
void VcprojGenerator::initManifestTool()
|
|
{
|
|
VCManifestTool &tool = vcProject.Configuration.manifestTool;
|
|
const ProString tmplt = project->first("TEMPLATE");
|
|
if ((tmplt == "vclib"
|
|
&& !project->isActiveConfig("embed_manifest_dll")
|
|
&& !project->isActiveConfig("static"))
|
|
|| (tmplt == "vcapp"
|
|
&& !project->isActiveConfig("embed_manifest_exe"))) {
|
|
tool.EmbedManifest = _False;
|
|
}
|
|
}
|
|
|
|
void VcprojGenerator::initLinkerTool()
|
|
{
|
|
VCConfiguration &conf = vcProject.Configuration;
|
|
conf.linker.parseOptions(project->values("QMAKE_LFLAGS"));
|
|
|
|
if (!project->values("DEF_FILE").isEmpty())
|
|
conf.linker.ModuleDefinitionFile = project->first("DEF_FILE").toQString();
|
|
|
|
static const char * const lflags[] = { "LIBS", "LIBS_PRIVATE",
|
|
"QMAKE_LIBS", "QMAKE_LIBS_PRIVATE", nullptr };
|
|
for (int i = 0; lflags[i]; i++) {
|
|
const auto libs = fixLibFlags(lflags[i]);
|
|
for (const ProString &lib : libs) {
|
|
if (lib.startsWith("/LIBPATH:"))
|
|
conf.linker.AdditionalLibraryDirectories << lib.mid(9).toQString();
|
|
else
|
|
conf.linker.AdditionalDependencies << lib.toQString();
|
|
}
|
|
}
|
|
|
|
conf.linker.OutputFile = "$(OutDir)\\";
|
|
conf.linker.OutputFile += project->first("MSVCPROJ_TARGET").toQString();
|
|
}
|
|
|
|
void VcprojGenerator::initResourceTool()
|
|
{
|
|
VCConfiguration &conf = vcProject.Configuration;
|
|
|
|
ProStringList rcDefines = project->values("RC_DEFINES");
|
|
if (rcDefines.size() > 0)
|
|
conf.resource.PreprocessorDefinitions = rcDefines.toQStringList();
|
|
else
|
|
conf.resource.PreprocessorDefinitions = conf.compiler.PreprocessorDefinitions;
|
|
|
|
for (const ProString &path : project->values("RC_INCLUDEPATH")) {
|
|
QString fixedPath = fileFixify(path.toQString());
|
|
if (fileInfo(fixedPath).isRelative()) {
|
|
if (fixedPath == QLatin1String("."))
|
|
fixedPath = QStringLiteral("$(ProjectDir)");
|
|
else
|
|
fixedPath.prepend(QStringLiteral("$(ProjectDir)\\"));
|
|
}
|
|
conf.resource.AdditionalIncludeDirectories << escapeFilePath(fixedPath);
|
|
}
|
|
|
|
// We need to add _DEBUG for the debug version of the project, since the normal compiler defines
|
|
// do not contain it. (The compiler defines this symbol automatically, which is wy we don't need
|
|
// to add it for the compiler) However, the resource tool does not do this.
|
|
if(project->isActiveConfig("debug"))
|
|
conf.resource.PreprocessorDefinitions += "_DEBUG";
|
|
if (conf.CompilerVersion < NET2010 && project->isActiveConfig("staticlib"))
|
|
conf.resource.ResourceOutputFileName = "$(OutDir)\\$(InputName).res";
|
|
}
|
|
|
|
void VcprojGenerator::initIDLTool()
|
|
{
|
|
}
|
|
|
|
void VcprojGenerator::initCustomBuildTool()
|
|
{
|
|
}
|
|
|
|
void VcprojGenerator::initPreBuildEventTools()
|
|
{
|
|
}
|
|
|
|
void VcprojGenerator::initPostBuildEventTools()
|
|
{
|
|
VCConfiguration &conf = vcProject.Configuration;
|
|
if (!project->values("QMAKE_POST_LINK").isEmpty()) {
|
|
QStringList cmdline = VCToolBase::fixCommandLine(var("QMAKE_POST_LINK"));
|
|
conf.postBuild.CommandLine = cmdline;
|
|
conf.postBuild.Description = cmdline.join(QLatin1String("\r\n"));
|
|
conf.postBuild.ExcludedFromBuild = _False;
|
|
}
|
|
if (!project->values("MSVCPROJ_COPY_DLL").isEmpty()) {
|
|
conf.postBuild.Description += var("MSVCPROJ_COPY_DLL_DESC");
|
|
conf.postBuild.CommandLine += var("MSVCPROJ_COPY_DLL");
|
|
conf.postBuild.ExcludedFromBuild = _False;
|
|
}
|
|
}
|
|
|
|
void VcprojGenerator::initDeploymentTool()
|
|
{
|
|
VCConfiguration &conf = vcProject.Configuration;
|
|
QString targetPath;
|
|
targetPath = project->values("deploy.path").join(' ');
|
|
if (targetPath.isEmpty())
|
|
targetPath = QString("%CSIDL_PROGRAM_FILES%\\") + project->first("TARGET");
|
|
if (targetPath.endsWith("/") || targetPath.endsWith("\\"))
|
|
targetPath.chop(1);
|
|
conf.deployment.RemoteDirectory = targetPath;
|
|
const ProStringList dllPaths = project->values("QMAKE_DLL_PATHS");
|
|
// Only deploy Qt libs for shared build
|
|
if (!dllPaths.isEmpty()) {
|
|
// FIXME: This code should actually resolve the libraries from all Qt modules.
|
|
ProStringList arg = project->values("LIBS") + project->values("LIBS_PRIVATE")
|
|
+ project->values("QMAKE_LIBS") + project->values("QMAKE_LIBS_PRIVATE");
|
|
bool qpaPluginDeployed = false;
|
|
for (ProStringList::ConstIterator it = arg.constBegin(); it != arg.constEnd(); ++it) {
|
|
QString dllName = (*it).toQString();
|
|
dllName.replace(QLatin1Char('\\'), QLatin1Char('/'));
|
|
// LIBPATH isn't relevant for deployment
|
|
if (dllName.startsWith(QLatin1String("/LIBPATH:")))
|
|
continue;
|
|
// We want to deploy .dlls not .libs
|
|
if (dllName.endsWith(QLatin1String(".lib")))
|
|
dllName.replace(dllName.length() - 3, 3, QLatin1String("dll"));
|
|
// Use only the file name and check in Qt's install path and LIBPATHs to check for existence
|
|
dllName.remove(0, dllName.lastIndexOf(QLatin1Char('/')) + 1);
|
|
QFileInfo info;
|
|
for (const ProString &dllPath : dllPaths) {
|
|
QString absoluteDllFilePath = dllPath.toQString();
|
|
if (!absoluteDllFilePath.endsWith(QLatin1Char('/')))
|
|
absoluteDllFilePath += QLatin1Char('/');
|
|
absoluteDllFilePath += dllName;
|
|
info = QFileInfo(absoluteDllFilePath);
|
|
if (info.exists())
|
|
break;
|
|
}
|
|
|
|
if (!info.exists())
|
|
continue;
|
|
|
|
conf.deployment.AdditionalFiles += info.fileName()
|
|
+ "|" + QDir::toNativeSeparators(info.absolutePath())
|
|
+ "|" + targetPath
|
|
+ "|0;";
|
|
if (!qpaPluginDeployed) {
|
|
QString debugInfix;
|
|
bool foundGuid = dllName.contains(QLatin1String("Guid"));
|
|
if (foundGuid)
|
|
debugInfix = QLatin1Char('d');
|
|
|
|
if (foundGuid || dllName.contains(QLatin1String("Gui"))) {
|
|
QFileInfo info2;
|
|
for (const ProString &dllPath : dllPaths) {
|
|
QString absoluteDllFilePath = dllPath.toQString();
|
|
if (!absoluteDllFilePath.endsWith(QLatin1Char('/')))
|
|
absoluteDllFilePath += QLatin1Char('/');
|
|
absoluteDllFilePath += QLatin1String("../plugins/platforms/qwindows")
|
|
+ debugInfix + QLatin1String(".dll");
|
|
info2 = QFileInfo(absoluteDllFilePath);
|
|
if (info2.exists())
|
|
break;
|
|
}
|
|
if (info2.exists()) {
|
|
conf.deployment.AdditionalFiles += QLatin1String("qwindows") + debugInfix + QLatin1String(".dll")
|
|
+ QLatin1Char('|') + QDir::toNativeSeparators(info2.absolutePath())
|
|
+ QLatin1Char('|') + targetPath + QLatin1String("\\platforms")
|
|
+ QLatin1String("|0;");
|
|
qpaPluginDeployed = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (const ProString &item : project->values("INSTALLS")) {
|
|
// get item.path
|
|
QString devicePath = project->first(ProKey(item + ".path")).toQString();
|
|
if (devicePath.isEmpty())
|
|
devicePath = targetPath;
|
|
// check if item.path is relative (! either /,\ or %)
|
|
if (!(devicePath.at(0) == QLatin1Char('/')
|
|
|| devicePath.at(0) == QLatin1Char('\\')
|
|
|| devicePath.at(0) == QLatin1Char('%'))) {
|
|
// create output path
|
|
devicePath = Option::fixPathToTargetOS(targetPath + QLatin1Char('\\') + devicePath);
|
|
}
|
|
// foreach d in item.files
|
|
for (const ProString &src : project->values(ProKey(item + ".files"))) {
|
|
QString itemDevicePath = devicePath;
|
|
QString source = Option::normalizePath(src.toQString());
|
|
QString nameFilter;
|
|
QFileInfo info(source);
|
|
QString searchPath;
|
|
if (info.isDir()) {
|
|
nameFilter = QLatin1String("*");
|
|
itemDevicePath += "\\" + info.fileName();
|
|
searchPath = info.absoluteFilePath();
|
|
} else {
|
|
nameFilter = info.fileName();
|
|
searchPath = info.absolutePath();
|
|
}
|
|
|
|
int pathSize = searchPath.size();
|
|
QDirIterator iterator(searchPath, QStringList() << nameFilter
|
|
, QDir::Files | QDir::NoDotAndDotDot | QDir::NoSymLinks
|
|
, QDirIterator::Subdirectories);
|
|
// foreach dirIterator-entry in d
|
|
while(iterator.hasNext()) {
|
|
iterator.next();
|
|
|
|
QString absoluteItemPath = Option::fixPathToTargetOS(QFileInfo(iterator.filePath()).absolutePath());
|
|
// Identify if it is just another subdir
|
|
int diffSize = absoluteItemPath.size() - pathSize;
|
|
// write out rules
|
|
conf.deployment.AdditionalFiles += iterator.fileName()
|
|
+ "|" + absoluteItemPath
|
|
+ "|" + itemDevicePath + (diffSize ? (absoluteItemPath.right(diffSize)) : QLatin1String(""))
|
|
+ "|0;";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void VcprojGenerator::initWinDeployQtTool()
|
|
{
|
|
VCConfiguration &conf = vcProject.Configuration;
|
|
conf.windeployqt.ExcludedFromBuild = true;
|
|
if (project->isActiveConfig("windeployqt")) {
|
|
conf.windeployqt.Record = QStringLiteral("$(TargetName).windeployqt.$(Platform).$(Configuration)");
|
|
const QString commandLine = MakefileGenerator::shellQuote(QDir::toNativeSeparators(project->first("QMAKE_WINDEPLOYQT").toQString()))
|
|
+ QLatin1Char(' ') + project->values("WINDEPLOYQT_OPTIONS").join(QLatin1Char(' '));
|
|
|
|
// Visual Studio copies all files to be deployed into the MSIL directory
|
|
// and then invokes MDILXapCompile on it, which checks for managed code and
|
|
// translates it into native code. The problem is that all entries of the
|
|
// package will be copied into the MSIL directly, losing the subdirectory
|
|
// structure (for instance for plugins). However, the MDILXapCompile call
|
|
// itself contains the original subdirectories as parameters and hence the
|
|
// call fails.
|
|
// Hence the only way to get a build done is to recreate the directory
|
|
// structure manually by invoking windeployqt a second time, so that
|
|
// the MDILXapCompile call succeeds and deployment continues.
|
|
conf.windeployqt.CommandLine += commandLine
|
|
+ QStringLiteral(" -list relative -dir \"$(MSBuildProjectDirectory)\" \"$(OutDir)\\$(TargetFileName)\" > ")
|
|
+ MakefileGenerator::shellQuote(conf.windeployqt.Record);
|
|
conf.windeployqt.config = &vcProject.Configuration;
|
|
conf.windeployqt.ExcludedFromBuild = false;
|
|
}
|
|
}
|
|
|
|
void VcprojGenerator::initPreLinkEventTools()
|
|
{
|
|
VCConfiguration &conf = vcProject.Configuration;
|
|
if(!project->values("QMAKE_PRE_LINK").isEmpty()) {
|
|
QStringList cmdline = VCToolBase::fixCommandLine(var("QMAKE_PRE_LINK"));
|
|
conf.preLink.CommandLine = cmdline;
|
|
conf.preLink.Description = cmdline.join(QLatin1String("\r\n"));
|
|
conf.preLink.ExcludedFromBuild = _False;
|
|
}
|
|
}
|
|
|
|
void VcprojGenerator::initRootFiles()
|
|
{
|
|
// Note: Root files do _not_ have any filter name, filter nor GUID!
|
|
vcProject.RootFiles.addFiles(project->values("RC_FILE"));
|
|
|
|
vcProject.RootFiles.Project = this;
|
|
vcProject.RootFiles.Config = &(vcProject.Configuration);
|
|
}
|
|
|
|
void VcprojGenerator::initSourceFiles()
|
|
{
|
|
vcProject.SourceFiles.Name = "Source Files";
|
|
vcProject.SourceFiles.Filter = "cpp;c;cxx;def;odl;idl;hpj;bat;asm;asmx";
|
|
vcProject.SourceFiles.Guid = _GUIDSourceFiles;
|
|
|
|
vcProject.SourceFiles.addFiles(project->values("SOURCES"));
|
|
|
|
vcProject.SourceFiles.Project = this;
|
|
vcProject.SourceFiles.Config = &(vcProject.Configuration);
|
|
}
|
|
|
|
void VcprojGenerator::initHeaderFiles()
|
|
{
|
|
vcProject.HeaderFiles.Name = "Header Files";
|
|
vcProject.HeaderFiles.Filter = "h;hpp;hxx;hm;inl;inc;xsd";
|
|
vcProject.HeaderFiles.Guid = _GUIDHeaderFiles;
|
|
|
|
vcProject.HeaderFiles.addFiles(project->values("HEADERS"));
|
|
if (usePCH) // Generated PCH cpp file
|
|
vcProject.HeaderFiles.addFile(precompH);
|
|
|
|
vcProject.HeaderFiles.Project = this;
|
|
vcProject.HeaderFiles.Config = &(vcProject.Configuration);
|
|
// vcProject.HeaderFiles.CustomBuild = mocHdr;
|
|
// addMocArguments(vcProject.HeaderFiles);
|
|
}
|
|
|
|
void VcprojGenerator::initGeneratedFiles()
|
|
{
|
|
vcProject.GeneratedFiles.Name = "Generated Files";
|
|
vcProject.GeneratedFiles.Filter = "cpp;c;cxx;moc;h;def;odl;idl;res;";
|
|
vcProject.GeneratedFiles.Guid = _GUIDGeneratedFiles;
|
|
|
|
// ### These cannot have CustomBuild (mocSrc)!!
|
|
vcProject.GeneratedFiles.addFiles(project->values("GENERATED_SOURCES"));
|
|
vcProject.GeneratedFiles.addFiles(project->values("GENERATED_FILES"));
|
|
vcProject.GeneratedFiles.addFiles(project->values("IDLSOURCES"));
|
|
if (project->values("RC_FILE").isEmpty())
|
|
vcProject.GeneratedFiles.addFiles(project->values("RES_FILE"));
|
|
if(!extraCompilerOutputs.isEmpty())
|
|
vcProject.GeneratedFiles.addFiles(extraCompilerOutputs.keys());
|
|
|
|
vcProject.GeneratedFiles.Project = this;
|
|
vcProject.GeneratedFiles.Config = &(vcProject.Configuration);
|
|
// vcProject.GeneratedFiles.CustomBuild = mocSrc;
|
|
}
|
|
|
|
void VcprojGenerator::initLexYaccFiles()
|
|
{
|
|
vcProject.LexYaccFiles.Name = "Lex / Yacc Files";
|
|
vcProject.LexYaccFiles.ParseFiles = _False;
|
|
vcProject.LexYaccFiles.Filter = "l;y";
|
|
vcProject.LexYaccFiles.Guid = _GUIDLexYaccFiles;
|
|
|
|
vcProject.LexYaccFiles.addFiles(project->values("LEXSOURCES"));
|
|
vcProject.LexYaccFiles.addFiles(project->values("YACCSOURCES"));
|
|
|
|
vcProject.LexYaccFiles.Project = this;
|
|
vcProject.LexYaccFiles.Config = &(vcProject.Configuration);
|
|
}
|
|
|
|
void VcprojGenerator::initTranslationFiles()
|
|
{
|
|
vcProject.TranslationFiles.Name = "Translation Files";
|
|
vcProject.TranslationFiles.ParseFiles = _False;
|
|
vcProject.TranslationFiles.Filter = "ts;xlf";
|
|
vcProject.TranslationFiles.Guid = _GUIDTranslationFiles;
|
|
|
|
vcProject.TranslationFiles.addFiles(project->values("TRANSLATIONS"));
|
|
vcProject.TranslationFiles.addFiles(project->values("EXTRA_TRANSLATIONS"));
|
|
|
|
vcProject.TranslationFiles.Project = this;
|
|
vcProject.TranslationFiles.Config = &(vcProject.Configuration);
|
|
}
|
|
|
|
void VcprojGenerator::initFormFiles()
|
|
{
|
|
vcProject.FormFiles.Name = "Form Files";
|
|
vcProject.FormFiles.ParseFiles = _False;
|
|
vcProject.FormFiles.Filter = "ui";
|
|
vcProject.FormFiles.Guid = _GUIDFormFiles;
|
|
vcProject.FormFiles.addFiles(project->values("FORMS"));
|
|
vcProject.FormFiles.Project = this;
|
|
vcProject.FormFiles.Config = &(vcProject.Configuration);
|
|
}
|
|
|
|
void VcprojGenerator::initResourceFiles()
|
|
{
|
|
vcProject.ResourceFiles.Name = "Resource Files";
|
|
vcProject.ResourceFiles.ParseFiles = _False;
|
|
vcProject.ResourceFiles.Filter = "qrc;*"; //"rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;ts;xlf;qrc";
|
|
vcProject.ResourceFiles.Guid = _GUIDResourceFiles;
|
|
|
|
// Bad hack, please look away -------------------------------------
|
|
QString rcc_dep_cmd = project->values("rcc.depend_command").join(' ');
|
|
if(!rcc_dep_cmd.isEmpty()) {
|
|
const QStringList qrc_files = project->values("RESOURCES").toQStringList();
|
|
QStringList deps;
|
|
for (const QString &qrc_file : qrc_files) {
|
|
callExtraCompilerDependCommand("rcc",
|
|
rcc_dep_cmd,
|
|
qrc_file,
|
|
QString(),
|
|
true, // dep_lines
|
|
&deps,
|
|
false, // existingDepsOnly
|
|
true // checkCommandavailability
|
|
);
|
|
}
|
|
vcProject.ResourceFiles.addFiles(deps);
|
|
}
|
|
// You may look again --------------------------------------------
|
|
|
|
vcProject.ResourceFiles.addFiles(project->values("RESOURCES"));
|
|
|
|
vcProject.ResourceFiles.Project = this;
|
|
vcProject.ResourceFiles.Config = &(vcProject.Configuration);
|
|
}
|
|
|
|
void VcprojGenerator::initDistributionFiles()
|
|
{
|
|
vcProject.DistributionFiles.Name = "Distribution Files";
|
|
vcProject.DistributionFiles.ParseFiles = _False;
|
|
vcProject.DistributionFiles.Filter = "*";
|
|
vcProject.DistributionFiles.Guid = _GUIDDistributionFiles;
|
|
vcProject.DistributionFiles.addFiles(project->values("DISTFILES"));
|
|
vcProject.DistributionFiles.Project = this;
|
|
vcProject.DistributionFiles.Config = &(vcProject.Configuration);
|
|
}
|
|
|
|
QString VcprojGenerator::extraCompilerName(const ProString &extraCompiler,
|
|
const QStringList &inputs,
|
|
const QStringList &outputs)
|
|
{
|
|
QString name = project->values(ProKey(extraCompiler + ".name")).join(' ');
|
|
if (name.isEmpty())
|
|
name = extraCompiler.toQString();
|
|
else
|
|
name = replaceExtraCompilerVariables(name, inputs, outputs, NoShell);
|
|
return name;
|
|
}
|
|
|
|
void VcprojGenerator::initExtraCompilerOutputs()
|
|
{
|
|
ProStringList otherFilters;
|
|
otherFilters << "FORMS"
|
|
<< "GENERATED_FILES"
|
|
<< "GENERATED_SOURCES"
|
|
<< "HEADERS"
|
|
<< "IDLSOURCES"
|
|
<< "LEXSOURCES"
|
|
<< "RC_FILE"
|
|
<< "RESOURCES"
|
|
<< "RES_FILE"
|
|
<< "SOURCES"
|
|
<< "TRANSLATIONS"
|
|
<< "YACCSOURCES";
|
|
const ProStringList &quc = project->values("QMAKE_EXTRA_COMPILERS");
|
|
for (ProStringList::ConstIterator it = quc.begin(); it != quc.end(); ++it) {
|
|
const ProStringList &inputVars = project->values(ProKey(*it + ".input"));
|
|
ProStringList inputFiles;
|
|
for (auto var : inputVars)
|
|
inputFiles.append(project->values(var.toKey()));
|
|
const ProStringList &outputs = project->values(ProKey(*it + ".output"));
|
|
|
|
// Create an extra compiler filter and add the files
|
|
VCFilter extraCompile;
|
|
extraCompile.Name = extraCompilerName(it->toQString(), inputFiles.toQStringList(),
|
|
outputs.toQStringList());
|
|
extraCompile.ParseFiles = _False;
|
|
extraCompile.Filter = "";
|
|
extraCompile.Guid = QString(_GUIDExtraCompilerFiles) + "-" + (*it);
|
|
|
|
bool addOnInput = hasBuiltinCompiler(firstExpandedOutputFileName(*it));
|
|
if (!addOnInput) {
|
|
// If the extra compiler has a variable_out set that is already handled
|
|
// some other place, ignore it.
|
|
const ProString &outputVar = project->first(ProKey(*it + ".variable_out"));
|
|
if (!outputVar.isEmpty() && otherFilters.contains(outputVar))
|
|
continue;
|
|
|
|
QString tmp_out;
|
|
if (!outputs.isEmpty())
|
|
tmp_out = outputs.first().toQString();
|
|
if (project->values(ProKey(*it + ".CONFIG")).indexOf("combine") != -1) {
|
|
// Combined output, only one file result. Use .cbt file.
|
|
extraCompile.addFile(Option::fixPathToTargetOS(
|
|
replaceExtraCompilerVariables(tmp_out + customBuildToolFilterFileSuffix,
|
|
QString(), QString(), NoShell), false));
|
|
} else if (!inputVars.isEmpty()) {
|
|
// One output file per input
|
|
const ProStringList &tmp_in = project->values(inputVars.first().toKey());
|
|
for (int i = 0; i < tmp_in.count(); ++i) {
|
|
const QString &filename = tmp_in.at(i).toQString();
|
|
if (extraCompilerSources.contains(filename) && !otherFiltersContain(filename))
|
|
extraCompile.addFile(Option::fixPathToTargetOS(
|
|
replaceExtraCompilerVariables(filename, tmp_out, QString(), NoShell), false));
|
|
}
|
|
}
|
|
} else {
|
|
// In this case we the outputs have a built-in compiler, so we cannot add the custom
|
|
// build steps there. So, we turn it around and add it to the input files instead,
|
|
// provided that the input file variable is not handled already (those in otherFilters
|
|
// are handled, so we avoid them).
|
|
for (const ProString &inputVar : inputVars) {
|
|
if (!otherFilters.contains(inputVar)) {
|
|
const ProStringList &tmp_in = project->values(inputVar.toKey());
|
|
for (int i = 0; i < tmp_in.count(); ++i) {
|
|
const QString &filename = tmp_in.at(i).toQString();
|
|
if (extraCompilerSources.contains(filename) && !otherFiltersContain(filename))
|
|
extraCompile.addFile(Option::fixPathToTargetOS(
|
|
replaceExtraCompilerVariables(filename, QString(), QString(), NoShell), false));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
extraCompile.Project = this;
|
|
extraCompile.Config = &(vcProject.Configuration);
|
|
|
|
vcProject.ExtraCompilersFiles.append(extraCompile);
|
|
}
|
|
}
|
|
|
|
bool VcprojGenerator::otherFiltersContain(const QString &fileName) const
|
|
{
|
|
auto filterFileMatches = [&fileName] (const VCFilterFile &ff)
|
|
{
|
|
return ff.file == fileName;
|
|
};
|
|
for (const VCFilter *filter : { &vcProject.RootFiles,
|
|
&vcProject.SourceFiles,
|
|
&vcProject.HeaderFiles,
|
|
&vcProject.GeneratedFiles,
|
|
&vcProject.LexYaccFiles,
|
|
&vcProject.TranslationFiles,
|
|
&vcProject.FormFiles,
|
|
&vcProject.ResourceFiles,
|
|
&vcProject.DeploymentFiles,
|
|
&vcProject.DistributionFiles}) {
|
|
if (std::any_of(filter->Files.cbegin(), filter->Files.cend(), filterFileMatches))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
// ------------------------------------------------------------------------------------------------
|
|
|
|
VCProjectWriter *VcprojGenerator::createProjectWriter()
|
|
{
|
|
return new VCProjectWriter;
|
|
}
|
|
|
|
QString VcprojGenerator::replaceExtraCompilerVariables(
|
|
const QString &var, const QStringList &in, const QStringList &out, ReplaceFor forShell)
|
|
{
|
|
QString ret = MakefileGenerator::replaceExtraCompilerVariables(var, in, out, forShell);
|
|
|
|
ProStringList &defines = project->values("VCPROJ_MAKEFILE_DEFINES");
|
|
if(defines.isEmpty())
|
|
defines.append(varGlue("PRL_EXPORT_DEFINES"," -D"," -D","") +
|
|
varGlue("DEFINES"," -D"," -D",""));
|
|
ret.replace(QLatin1String("$(DEFINES)"), defines.first().toQString());
|
|
|
|
ProStringList &incpath = project->values("VCPROJ_MAKEFILE_INCPATH");
|
|
if(incpath.isEmpty() && !this->var("MSVCPROJ_INCPATH").isEmpty())
|
|
incpath.append(this->var("MSVCPROJ_INCPATH"));
|
|
ret.replace(QLatin1String("$(INCPATH)"), incpath.join(' '));
|
|
|
|
return ret;
|
|
}
|
|
|
|
bool VcprojGenerator::openOutput(QFile &file, const QString &/*build*/) const
|
|
{
|
|
ProString fileName = file.fileName();
|
|
ProString extension = project->first("TEMPLATE") == "vcsubdirs"
|
|
? project->first("VCSOLUTION_EXTENSION") : project->first("VCPROJ_EXTENSION");
|
|
if (!fileName.endsWith(extension)) {
|
|
if (fileName.isEmpty()) {
|
|
fileName = !project->first("MAKEFILE").isEmpty()
|
|
? project->first("MAKEFILE") : project->first("TARGET");
|
|
}
|
|
file.setFileName(fileName + extension);
|
|
}
|
|
return Win32MakefileGenerator::openOutput(file, QString());
|
|
}
|
|
|
|
QT_END_NAMESPACE
|