1 | // Copyright (C) 2016 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 |
3 | |
4 | #include "msvc_vcproj.h" |
5 | #include "option.h" |
6 | #include "xmloutput.h" |
7 | |
8 | #include <ioutils.h> |
9 | |
10 | #include <qdir.h> |
11 | #include <qdiriterator.h> |
12 | #include <qcryptographichash.h> |
13 | #include <qhash.h> |
14 | #include <quuid.h> |
15 | #include <qregularexpression.h> |
16 | |
17 | #include <stdlib.h> |
18 | #include <tuple> |
19 | #include <utility> |
20 | |
21 | //#define DEBUG_SOLUTION_GEN |
22 | |
23 | using namespace QMakeInternal; |
24 | using namespace Qt::StringLiterals; |
25 | |
26 | QT_BEGIN_NAMESPACE |
27 | // Filter GUIDs (Do NOT change these!) ------------------------------ |
28 | const char _GUIDSourceFiles[] = "{4FC737F1-C7A5-4376-A066-2A32D752A2FF}" ; |
29 | const char [] = "{93995380-89BD-4b04-88EB-625FBE52EBFB}" ; |
30 | const char _GUIDGeneratedFiles[] = "{71ED8ED8-ACB9-4CE9-BBE1-E00B30144E11}" ; |
31 | const char _GUIDResourceFiles[] = "{D9D6E242-F8AF-46E4-B9FD-80ECBC20BA3E}" ; |
32 | const char _GUIDLexYaccFiles[] = "{E12AE0D2-192F-4d59-BD23-7D3FA58D3183}" ; |
33 | const char _GUIDTranslationFiles[] = "{639EADAA-A684-42e4-A9AD-28FC9BCB8F7C}" ; |
34 | const char _GUIDFormFiles[] = "{99349809-55BA-4b9d-BF79-8FDBB0286EB3}" ; |
35 | const char [] = "{E0D8C965-CC5F-43d7-AD63-FAEF0BBC0F85}" ; |
36 | const char _GUIDDistributionFiles[] = "{B83CAF91-C7BF-462F-B76C-EA11631F866C}" ; |
37 | |
38 | // Flatfile Tags ---------------------------------------------------- |
39 | const char [] = "Microsoft Visual Studio Solution File, Format Version 7.00" ; |
40 | const char [] = "Microsoft Visual Studio Solution File, Format Version 8.00" ; |
41 | const char [] = "Microsoft Visual Studio Solution File, Format Version 9.00" |
42 | "\n# Visual Studio 2005" ; |
43 | const char [] = "Microsoft Visual Studio Solution File, Format Version 10.00" |
44 | "\n# Visual Studio 2008" ; |
45 | const char [] = "Microsoft Visual Studio Solution File, Format Version 11.00" |
46 | "\n# Visual Studio 2010" ; |
47 | const char [] = "Microsoft Visual Studio Solution File, Format Version 12.00" |
48 | "\n# Visual Studio 2012" ; |
49 | const char [] = "Microsoft Visual Studio Solution File, Format Version 12.00" |
50 | "\n# Visual Studio 2013" ; |
51 | const char [] = "Microsoft Visual Studio Solution File, Format Version 12.00" |
52 | "\n# Visual Studio 2015" ; |
53 | const char [] = "Microsoft Visual Studio Solution File, Format Version 12.00" |
54 | "\n# Visual Studio 15" ; |
55 | const char [] = "Microsoft Visual Studio Solution File, Format Version 12.00" |
56 | "\n# Visual Studio Version 16" ; |
57 | const char [] = "Microsoft Visual Studio Solution File, Format Version 12.00" |
58 | "\n# Visual Studio Version 17" ; |
59 | // The following UUID _may_ change for later servicepacks... |
60 | // If so we need to search through the registry at |
61 | // HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\7.0\Projects |
62 | // to find the subkey that contains a "PossibleProjectExtension" |
63 | // containing "vcproj"... |
64 | // Use the hardcoded value for now so projects generated on other |
65 | // platforms are actually usable. |
66 | const char _slnMSVCvcprojGUID[] = "{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}" ; |
67 | const char _slnProjectBeg[] = "\nProject(\"" ; |
68 | const char _slnProjectMid[] = "\") = " ; |
69 | const char _slnProjectEnd[] = "\nEndProject" ; |
70 | const char _slnGlobalBeg[] = "\nGlobal" ; |
71 | const char _slnGlobalEnd[] = "\nEndGlobal" ; |
72 | const char _slnSolutionConf[] = "\n\tGlobalSection(SolutionConfigurationPlatforms) = preSolution" |
73 | "\n\t\tDebug|Win32 = Debug|Win32" |
74 | "\n\t\tRelease|Win32 = Release|Win32" |
75 | "\n\tEndGlobalSection" ; |
76 | |
77 | const char _slnProjDepBeg[] = "\n\tProjectSection(ProjectDependencies) = postProject" ; |
78 | const char _slnProjDepEnd[] = "\n\tEndProjectSection" ; |
79 | const char _slnProjConfBeg[] = "\n\tGlobalSection(ProjectConfigurationPlatforms) = postSolution" ; |
80 | const char _slnProjRelConfTag1[]= ".Release|%1.ActiveCfg = Release|" ; |
81 | const char _slnProjRelConfTag2[]= ".Release|%1.Build.0 = Release|" ; |
82 | const char _slnProjDbgConfTag1[]= ".Debug|%1.ActiveCfg = Debug|" ; |
83 | const char _slnProjDbgConfTag2[]= ".Debug|%1.Build.0 = Debug|" ; |
84 | const char _slnProjConfEnd[] = "\n\tEndGlobalSection" ; |
85 | const char _slnExtSections[] = "\n\tGlobalSection(ExtensibilityGlobals) = postSolution" |
86 | "\n\tEndGlobalSection" |
87 | "\n\tGlobalSection(ExtensibilityAddIns) = postSolution" |
88 | "\n\tEndGlobalSection" ; |
89 | // ------------------------------------------------------------------ |
90 | |
91 | VcprojGenerator::VcprojGenerator() |
92 | : Win32MakefileGenerator(), |
93 | is64Bit(false), |
94 | customBuildToolFilterFileSuffix(QStringLiteral(".cbt" )), |
95 | projectWriter(nullptr) |
96 | { |
97 | } |
98 | |
99 | VcprojGenerator::~VcprojGenerator() |
100 | { |
101 | delete projectWriter; |
102 | } |
103 | |
104 | bool VcprojGenerator::writeMakefile(QTextStream &t) |
105 | { |
106 | initProject(); // Fills the whole project with proper data |
107 | |
108 | // Generate solution file |
109 | if(project->first(variableName: "TEMPLATE" ) == "vcsubdirs" ) { |
110 | if (!project->isActiveConfig(config: "build_pass" )) { |
111 | debug_msg(level: 1, fmt: "Generator: MSVC.NET: Writing solution file" ); |
112 | writeSubDirs(t); |
113 | } else { |
114 | debug_msg(level: 1, fmt: "Generator: MSVC.NET: Not writing solution file for build_pass configs" ); |
115 | } |
116 | return true; |
117 | } else |
118 | // Generate single configuration project file |
119 | if (project->first(variableName: "TEMPLATE" ) == "vcapp" || |
120 | project->first(variableName: "TEMPLATE" ) == "vclib" ) { |
121 | if(!project->isActiveConfig(config: "build_pass" )) { |
122 | debug_msg(level: 1, fmt: "Generator: MSVC.NET: Writing single configuration project file" ); |
123 | XmlOutput xmlOut(t); |
124 | projectWriter->write(xmlOut, vcProject); |
125 | } |
126 | return true; |
127 | } |
128 | return project->isActiveConfig(config: "build_pass" ); |
129 | } |
130 | |
131 | bool VcprojGenerator::writeProjectMakefile() |
132 | { |
133 | QTextStream t(&Option::output); |
134 | |
135 | // Check if all requirements are fulfilled |
136 | if(!project->values(v: "QMAKE_FAILED_REQUIREMENTS" ).isEmpty()) { |
137 | fprintf(stderr, format: "Project file not generated because all requirements not met:\n\t%s\n" , |
138 | var(var: "QMAKE_FAILED_REQUIREMENTS" ).toLatin1().constData()); |
139 | return true; |
140 | } |
141 | |
142 | // Generate project file |
143 | if(project->first(variableName: "TEMPLATE" ) == "vcapp" || |
144 | project->first(variableName: "TEMPLATE" ) == "vclib" ) { |
145 | if (!mergedProjects.size()) { |
146 | warn_msg(t: WarnLogic, fmt: "Generator: MSVC.NET: no single configuration created, cannot output project!" ); |
147 | return false; |
148 | } |
149 | |
150 | debug_msg(level: 1, fmt: "Generator: MSVC.NET: Writing project file" ); |
151 | VCProject mergedProject; |
152 | for (int i = 0; i < mergedProjects.size(); ++i) { |
153 | VCProjectSingleConfig *singleProject = &(mergedProjects.at(i)->vcProject); |
154 | mergedProject.SingleProjects += *singleProject; |
155 | for (int j = 0; j < singleProject->ExtraCompilersFiles.size(); ++j) { |
156 | const QString &compilerName = singleProject->ExtraCompilersFiles.at(i: j).Name; |
157 | if (!mergedProject.ExtraCompilers.contains(str: compilerName)) |
158 | mergedProject.ExtraCompilers += compilerName; |
159 | } |
160 | } |
161 | |
162 | if(mergedProjects.size() > 1 && |
163 | mergedProjects.at(i: 0)->vcProject.Name == |
164 | mergedProjects.at(i: 1)->vcProject.Name) |
165 | mergedProjects.at(i: 0)->writePrlFile(); |
166 | mergedProject.Name = project->first(variableName: "QMAKE_PROJECT_NAME" ).toQString(); |
167 | mergedProject.Version = mergedProjects.at(i: 0)->vcProject.Version; |
168 | mergedProject.SdkVersion = mergedProjects.at(i: 0)->vcProject.SdkVersion; |
169 | mergedProject.ProjectGUID = project->isEmpty(v: "QMAKE_UUID" ) ? getProjectUUID().toString().toUpper() : project->first(variableName: "QMAKE_UUID" ).toQString(); |
170 | mergedProject.Keyword = project->first(variableName: "VCPROJ_KEYWORD" ).toQString(); |
171 | mergedProject.SccProjectName = mergedProjects.at(i: 0)->vcProject.SccProjectName; |
172 | mergedProject.SccLocalPath = mergedProjects.at(i: 0)->vcProject.SccLocalPath; |
173 | mergedProject.PlatformName = mergedProjects.at(i: 0)->vcProject.PlatformName; |
174 | mergedProject.WindowsTargetPlatformVersion = |
175 | project->first(variableName: "WINDOWS_TARGET_PLATFORM_VERSION" ).toQString(); |
176 | mergedProject.WindowsTargetPlatformMinVersion = |
177 | project->first(variableName: "WINDOWS_TARGET_PLATFORM_MIN_VERSION" ).toQString(); |
178 | |
179 | XmlOutput xmlOut(t); |
180 | projectWriter->write(xmlOut, mergedProject); |
181 | return true; |
182 | } else if(project->first(variableName: "TEMPLATE" ) == "vcsubdirs" ) { |
183 | return writeMakefile(t); |
184 | } |
185 | return false; |
186 | } |
187 | |
188 | struct VcsolutionDepend { |
189 | QString uuid; |
190 | QString vcprojFile; |
191 | QString projectName; |
192 | QString target; |
193 | Target targetType; |
194 | QStringList dependencies; |
195 | }; |
196 | |
197 | /* Disable optimization in getProjectUUID() due to a compiler |
198 | * bug in MSVC 2015 that causes ASSERT: "&other != this" in the QString |
199 | * copy constructor for non-empty file names at: |
200 | * filename.isEmpty()?project->first("QMAKE_MAKEFILE"):filename */ |
201 | |
202 | #if defined(Q_CC_MSVC) && !defined(Q_CC_CLANG) |
203 | # pragma optimize( "g", off ) |
204 | # pragma warning ( disable : 4748 ) |
205 | #endif |
206 | |
207 | QUuid VcprojGenerator::getProjectUUID(const QString &filename) |
208 | { |
209 | bool validUUID = true; |
210 | |
211 | // Read GUID from variable-space |
212 | auto uuid = QUuid::fromString(string: project->first(variableName: "GUID" ).toQStringView()); |
213 | |
214 | // If none, create one based on the MD5 of absolute project path |
215 | if(uuid.isNull() || !filename.isEmpty()) { |
216 | QString abspath = Option::fixPathToTargetOS( |
217 | in: filename.isEmpty() ? project->first(variableName: "QMAKE_MAKEFILE" ).toQString() : filename); |
218 | QByteArray digest = QCryptographicHash::hash(data: abspath.toUtf8(), method: QCryptographicHash::Sha1); |
219 | memcpy(dest: (unsigned char*)(&uuid), src: digest.constData(), n: sizeof(QUuid)); |
220 | validUUID = !uuid.isNull(); |
221 | uuid.data4[0] = (uuid.data4[0] & 0x3F) | 0x80; // UV_DCE variant |
222 | uuid.data3 = (uuid.data3 & 0x0FFF) | (QUuid::Name<<12); |
223 | } |
224 | |
225 | // If still not valid, generate new one, and suggest adding to .pro |
226 | if(uuid.isNull() || !validUUID) { |
227 | uuid = QUuid::createUuid(); |
228 | fprintf(stderr, |
229 | format: "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" , |
230 | uuid.toString().toUpper().toLatin1().constData()); |
231 | } |
232 | |
233 | // Store GUID in variable-space |
234 | project->values(v: "GUID" ) = ProStringList(uuid.toString().toUpper()); |
235 | return uuid; |
236 | } |
237 | |
238 | #if defined(Q_CC_MSVC) && !defined(Q_CC_CLANG) |
239 | # pragma optimize( "g", on ) |
240 | #endif |
241 | |
242 | QUuid VcprojGenerator::increaseUUID(const QUuid &id) |
243 | { |
244 | QUuid result(id); |
245 | qint64 dataFirst = (result.data4[0] << 24) + |
246 | (result.data4[1] << 16) + |
247 | (result.data4[2] << 8) + |
248 | result.data4[3]; |
249 | qint64 dataLast = (result.data4[4] << 24) + |
250 | (result.data4[5] << 16) + |
251 | (result.data4[6] << 8) + |
252 | result.data4[7]; |
253 | |
254 | if(!(dataLast++)) |
255 | dataFirst++; |
256 | |
257 | result.data4[0] = uchar((dataFirst >> 24) & 0xff); |
258 | result.data4[1] = uchar((dataFirst >> 16) & 0xff); |
259 | result.data4[2] = uchar((dataFirst >> 8) & 0xff); |
260 | result.data4[3] = uchar(dataFirst & 0xff); |
261 | result.data4[4] = uchar((dataLast >> 24) & 0xff); |
262 | result.data4[5] = uchar((dataLast >> 16) & 0xff); |
263 | result.data4[6] = uchar((dataLast >> 8) & 0xff); |
264 | result.data4[7] = uchar(dataLast & 0xff); |
265 | return result; |
266 | } |
267 | |
268 | QString VcprojGenerator::retrievePlatformToolSet() const |
269 | { |
270 | // The PlatformToolset string corresponds to the name of a directory in |
271 | // $(VCTargetsPath)\Platforms\{Win32,x64,...}\PlatformToolsets |
272 | // e.g. v90, v100, v110, v110_xp, v120_CTP_Nov, v120, or WindowsSDK7.1 |
273 | |
274 | // This environment variable may be set by a commandline build |
275 | // environment such as the Windows SDK command prompt |
276 | QByteArray envVar = qgetenv(varName: "PlatformToolset" ); |
277 | if (!envVar.isEmpty()) |
278 | return envVar; |
279 | |
280 | return u"v"_s + project->first(variableName: "MSVC_TOOLSET_VER" ); |
281 | } |
282 | |
283 | bool VcprojGenerator::isStandardSuffix(const QString &suffix) const |
284 | { |
285 | if (!project->values(v: "QMAKE_APP_FLAG" ).isEmpty()) { |
286 | if (suffix.compare(s: "exe" , cs: Qt::CaseInsensitive) == 0) |
287 | return true; |
288 | } else if (project->isActiveConfig(config: "shared" )) { |
289 | if (suffix.compare(s: "dll" , cs: Qt::CaseInsensitive) == 0) |
290 | return true; |
291 | } else { |
292 | if (suffix.compare(s: "lib" , cs: Qt::CaseInsensitive) == 0) |
293 | return true; |
294 | } |
295 | return false; |
296 | } |
297 | |
298 | ProString VcprojGenerator::firstInputFileName(const ProString &) const |
299 | { |
300 | for (const ProString &var : project->values(v: ProKey(extraCompilerName + ".input" ))) { |
301 | const ProStringList &files = project->values(v: var.toKey()); |
302 | if (!files.isEmpty()) |
303 | return files.first(); |
304 | } |
305 | return ProString(); |
306 | } |
307 | |
308 | QString VcprojGenerator::firstExpandedOutputFileName(const ProString &) |
309 | { |
310 | const ProString firstOutput = project->first(variableName: ProKey(extraCompilerName + ".output" )); |
311 | return replaceExtraCompilerVariables(val: firstOutput.toQString(), |
312 | in: firstInputFileName(extraCompilerName).toQString(), |
313 | out: QString(), forShell: NoShell); |
314 | } |
315 | |
316 | ProStringList VcprojGenerator::collectDependencies(QMakeProject *proj, QHash<QString, QString> &projLookup, |
317 | QHash<QString, QString> &projGuids, |
318 | QHash<VcsolutionDepend *, QStringList> &, |
319 | QHash<QString, VcsolutionDepend*> &solution_depends, |
320 | QList<VcsolutionDepend*> &solution_cleanup, |
321 | QTextStream &t, |
322 | QHash<QString, ProStringList> &subdirProjectLookup, |
323 | const ProStringList &allDependencies) |
324 | { |
325 | QList<QPair<QString, ProStringList>> collectedSubdirs; |
326 | ProStringList tmp_proj_subdirs = proj->values(v: "SUBDIRS" ); |
327 | ProStringList projectsInProject; |
328 | const int numSubdirs = tmp_proj_subdirs.size(); |
329 | collectedSubdirs.reserve(asize: numSubdirs); |
330 | for (int x = 0; x < numSubdirs; ++x) { |
331 | ProString tmpdir = tmp_proj_subdirs.at(i: x); |
332 | const ProKey tmpdirConfig(tmpdir + ".CONFIG" ); |
333 | if (!proj->isEmpty(v: tmpdirConfig)) { |
334 | const ProStringList config = proj->values(v: tmpdirConfig); |
335 | if (config.contains(QStringLiteral("no_default_target" ))) |
336 | continue; // Ignore this sub-dir |
337 | } |
338 | const ProKey fkey(tmpdir + ".file" ); |
339 | const ProKey skey(tmpdir + ".subdir" ); |
340 | if (!proj->isEmpty(v: fkey)) { |
341 | if (!proj->isEmpty(v: skey)) |
342 | warn_msg(t: WarnLogic, fmt: "Cannot assign both file and subdir for subdir %s" , |
343 | tmpdir.toLatin1().constData()); |
344 | tmpdir = proj->first(variableName: fkey); |
345 | } else if (!proj->isEmpty(v: skey)) { |
346 | tmpdir = proj->first(variableName: skey); |
347 | } |
348 | projectsInProject.append(t: tmpdir); |
349 | collectedSubdirs.append(t: qMakePair(value1: tmpdir.toQString(), value2&: proj->values(v: ProKey(tmp_proj_subdirs.at(i: x) + ".depends" )))); |
350 | projLookup.insert(key: tmp_proj_subdirs.at(i: x).toQString(), value: tmpdir.toQString()); |
351 | } |
352 | for (const auto &subdir : std::as_const(t&: collectedSubdirs)) { |
353 | QString profile = subdir.first; |
354 | QFileInfo fi(fileInfo(file: Option::normalizePath(in: profile))); |
355 | if (fi.exists()) { |
356 | if (fi.isDir()) { |
357 | if (!profile.endsWith(s: Option::dir_sep)) |
358 | profile += Option::dir_sep; |
359 | profile += fi.baseName() + Option::pro_ext; |
360 | QString profileKey = fi.absoluteFilePath(); |
361 | fi = QFileInfo(fileInfo(file: Option::normalizePath(in: profile))); |
362 | if (!fi.exists()) |
363 | continue; |
364 | projLookup.insert(key: profileKey, value: fi.absoluteFilePath()); |
365 | } |
366 | QString oldpwd = qmake_getpwd(); |
367 | QString oldoutpwd = Option::output_dir; |
368 | QMakeProject tmp_proj; |
369 | QString dir = fi.absolutePath(), fn = fi.fileName(); |
370 | if (!dir.isEmpty()) { |
371 | if (!qmake_setpwd(p: dir)) |
372 | fprintf(stderr, format: "Cannot find directory: %s" , dir.toLatin1().constData()); |
373 | } |
374 | Option::output_dir = Option::globals->shadowedPath(fileName: QDir::cleanPath(path: dir)); |
375 | if (tmp_proj.read(project: fn)) { |
376 | // Check if all requirements are fulfilled |
377 | if (!tmp_proj.isEmpty(v: "QMAKE_FAILED_REQUIREMENTS" )) { |
378 | fprintf(stderr, format: "Project file(%s) not added to Solution because all requirements not met:\n\t%s\n" , |
379 | fn.toLatin1().constData(), |
380 | tmp_proj.values(v: "QMAKE_FAILED_REQUIREMENTS" ).join(sep: ' ').toLatin1().constData()); |
381 | qmake_setpwd(p: oldpwd); |
382 | Option::output_dir = oldoutpwd; |
383 | continue; |
384 | } |
385 | if (tmp_proj.first(variableName: "TEMPLATE" ) == "vcsubdirs" ) { |
386 | ProStringList tmpList = collectDependencies(proj: &tmp_proj, projLookup, projGuids, extraSubdirs, solution_depends, solution_cleanup, t, subdirProjectLookup, allDependencies: subdir.second); |
387 | subdirProjectLookup.insert(key: subdir.first, value: tmpList); |
388 | } else { |
389 | ProStringList tmpList; |
390 | tmpList += subdir.second; |
391 | tmpList += allDependencies; |
392 | // Initialize a 'fake' project to get the correct variables |
393 | // and to be able to extract all the dependencies |
394 | Option::QMAKE_MODE old_mode = Option::qmake_mode; |
395 | Option::qmake_mode = Option::QMAKE_GENERATE_NOTHING; |
396 | VcprojGenerator tmp_vcproj; |
397 | tmp_vcproj.setNoIO(true); |
398 | tmp_vcproj.setProjectFile(&tmp_proj); |
399 | Option::qmake_mode = old_mode; |
400 | |
401 | // We assume project filename is [QMAKE_PROJECT_NAME].vcproj |
402 | const ProString projectName = tmp_vcproj.project->first(variableName: "QMAKE_PROJECT_NAME" ); |
403 | const QString vcproj = projectName + project->first(variableName: "VCPROJ_EXTENSION" ); |
404 | QString vcprojDir = Option::output_dir; |
405 | |
406 | // If file doesn't exsist, then maybe the users configuration |
407 | // doesn't allow it to be created. Skip to next... |
408 | if (!exists(file: vcprojDir + Option::dir_sep + vcproj)) { |
409 | warn_msg(t: WarnLogic, fmt: "Ignored (not found) '%s'" , QString(vcprojDir + Option::dir_sep + vcproj).toLatin1().constData()); |
410 | goto nextfile; // # Dirty! |
411 | } |
412 | |
413 | VcsolutionDepend *newDep = new VcsolutionDepend; |
414 | newDep->vcprojFile = vcprojDir + Option::dir_sep + vcproj; |
415 | newDep->projectName = projectName.toQString(); |
416 | newDep->target = tmp_proj.first(variableName: "MSVCPROJ_TARGET" ).toQString().section(in_sep: Option::dir_sep, start: -1); |
417 | newDep->targetType = tmp_vcproj.projectTarget; |
418 | newDep->uuid = tmp_proj.isEmpty(v: "QMAKE_UUID" ) ? getProjectUUID(filename: Option::fixPathToLocalOS(in: vcprojDir + QDir::separator() + vcproj)).toString().toUpper(): tmp_proj.first(variableName: "QMAKE_UUID" ).toQString(); |
419 | // We want to store it as the .lib name. |
420 | if (newDep->target.endsWith(s: ".dll" )) |
421 | newDep->target = newDep->target.left(n: newDep->target.size()-3) + "lib" ; |
422 | projGuids.insert(key: newDep->projectName, value: newDep->target); |
423 | |
424 | if (tmpList.size()) { |
425 | const ProStringList depends = tmpList; |
426 | for (const ProString &dep : depends) { |
427 | QString depend = dep.toQString(); |
428 | if (!projGuids[depend].isEmpty()) { |
429 | newDep->dependencies << projGuids[depend]; |
430 | } else if (subdirProjectLookup[projLookup[depend]].size() > 0) { |
431 | const ProStringList tmpLst = subdirProjectLookup[projLookup[depend]]; |
432 | for (const ProString &tDep : tmpLst) { |
433 | QString tmpDep = tDep.toQString(); |
434 | newDep->dependencies << projGuids[projLookup[tmpDep]]; |
435 | } |
436 | } else { |
437 | extraSubdirs.insert(key: newDep, value: tmpList.toQStringList()); |
438 | newDep->dependencies.clear(); |
439 | break; |
440 | } |
441 | } |
442 | } |
443 | |
444 | // All ActiveQt Server projects are dependent on idc.exe |
445 | if (tmp_proj.values(v: "CONFIG" ).contains(str: "qaxserver" )) |
446 | newDep->dependencies << "idc.exe" ; |
447 | |
448 | // Add all unknown libs to the deps |
449 | QStringList where = QStringList() << "LIBS" << "LIBS_PRIVATE" |
450 | << "QMAKE_LIBS" << "QMAKE_LIBS_PRIVATE" ; |
451 | for (QStringList::ConstIterator wit = where.cbegin(); |
452 | wit != where.cend(); ++wit) { |
453 | const ProStringList &l = tmp_proj.values(v: ProKey(*wit)); |
454 | for (ProStringList::ConstIterator it = l.begin(); it != l.end(); ++it) { |
455 | const QString opt = fixLibFlag(lib: *it).toQString(); |
456 | if (!opt.startsWith(s: "/" ) && // Not a switch |
457 | opt != newDep->target && // Not self |
458 | opt != "opengl32.lib" && // We don't care about these libs |
459 | opt != "glu32.lib" && // to make depgen alittle faster |
460 | opt != "kernel32.lib" && |
461 | opt != "user32.lib" && |
462 | opt != "gdi32.lib" && |
463 | opt != "comdlg32.lib" && |
464 | opt != "advapi32.lib" && |
465 | opt != "shell32.lib" && |
466 | opt != "ole32.lib" && |
467 | opt != "oleaut32.lib" && |
468 | opt != "uuid.lib" && |
469 | opt != "imm32.lib" && |
470 | opt != "winmm.lib" && |
471 | opt != "wsock32.lib" && |
472 | opt != "ws2_32.lib" && |
473 | opt != "winspool.lib" && |
474 | opt != "delayimp.lib" ) |
475 | { |
476 | newDep->dependencies << opt.section(in_sep: Option::dir_sep, start: -1); |
477 | } |
478 | } |
479 | } |
480 | #ifdef DEBUG_SOLUTION_GEN |
481 | qDebug("Deps for %20s: [%s]" , newDep->target.toLatin1().constData(), newDep->dependencies.join(" :: " ).toLatin1().constData()); |
482 | #endif |
483 | solution_cleanup.append(t: newDep); |
484 | solution_depends.insert(key: newDep->target, value: newDep); |
485 | } |
486 | nextfile: |
487 | qmake_setpwd(p: oldpwd); |
488 | Option::output_dir = oldoutpwd; |
489 | } |
490 | } |
491 | } |
492 | return projectsInProject; |
493 | } |
494 | |
495 | void VcprojGenerator::writeSubDirs(QTextStream &t) |
496 | { |
497 | // Check if all requirements are fulfilled |
498 | if(!project->values(v: "QMAKE_FAILED_REQUIREMENTS" ).isEmpty()) { |
499 | fprintf(stderr, format: "Project file not generated because all requirements not met:\n\t%s\n" , |
500 | var(var: "QMAKE_FAILED_REQUIREMENTS" ).toLatin1().constData()); |
501 | return; |
502 | } |
503 | |
504 | switch (vcProject.Configuration.CompilerVersion) { |
505 | case NET2022: |
506 | t << _slnHeader143; |
507 | break; |
508 | case NET2019: |
509 | t << _slnHeader142; |
510 | break; |
511 | case NET2017: |
512 | t << _slnHeader141; |
513 | break; |
514 | case NET2015: |
515 | t << _slnHeader140; |
516 | break; |
517 | case NET2013: |
518 | t << _slnHeader120; |
519 | break; |
520 | case NET2012: |
521 | t << _slnHeader110; |
522 | break; |
523 | case NET2010: |
524 | t << _slnHeader100; |
525 | break; |
526 | case NET2008: |
527 | t << _slnHeader90; |
528 | break; |
529 | case NET2005: |
530 | t << _slnHeader80; |
531 | break; |
532 | case NET2003: |
533 | t << _slnHeader71; |
534 | break; |
535 | case NET2002: |
536 | t << _slnHeader70; |
537 | break; |
538 | default: |
539 | t << _slnHeader70; |
540 | warn_msg(t: WarnLogic, fmt: "Generator: MSVC.NET: Unknown version (%d) of MSVC detected for .sln" , |
541 | vcProject.Configuration.CompilerVersion); |
542 | break; |
543 | } |
544 | |
545 | QHash<QString, VcsolutionDepend*> solution_depends; |
546 | QList<VcsolutionDepend*> solution_cleanup; |
547 | |
548 | // Make sure that all temp projects are configured |
549 | // for release so that the depends are created |
550 | // without the debug <lib>dxxx.lib name mangling |
551 | QString old_after_vars = Option::globals->extra_cmds[QMakeEvalAfter]; |
552 | Option::globals->extra_cmds[QMakeEvalAfter].append(s: "\nCONFIG+=release" ); |
553 | |
554 | QHash<QString, QString> profileLookup; |
555 | QHash<QString, QString> projGuids; |
556 | QHash<VcsolutionDepend *, QStringList> ; |
557 | QHash<QString, ProStringList> subdirProjectLookup; |
558 | collectDependencies(proj: project, projLookup&: profileLookup, projGuids, extraSubdirs, solution_depends, solution_cleanup, t, subdirProjectLookup); |
559 | |
560 | // write out projects |
561 | for (QList<VcsolutionDepend*>::Iterator it = solution_cleanup.begin(); it != solution_cleanup.end(); ++it) { |
562 | // ### quoting rules? |
563 | t << _slnProjectBeg << _slnMSVCvcprojGUID << _slnProjectMid |
564 | << "\"" << (*it)->projectName << "\", \"" << (*it)->vcprojFile |
565 | << "\", \"" << (*it)->uuid << "\"" ; |
566 | |
567 | debug_msg(level: 1, fmt: "Project %s has dependencies: %s" , (*it)->target.toLatin1().constData(), (*it)->dependencies.join(sep: " " ).toLatin1().constData()); |
568 | |
569 | bool hasDependency = false; |
570 | for (QStringList::iterator dit = (*it)->dependencies.begin(); dit != (*it)->dependencies.end(); ++dit) { |
571 | if (VcsolutionDepend *vc = solution_depends[*dit]) { |
572 | if (!hasDependency) { |
573 | hasDependency = true; |
574 | t << _slnProjDepBeg; |
575 | } |
576 | t << "\n\t\t" << vc->uuid << " = " << vc->uuid; |
577 | } |
578 | } |
579 | if (hasDependency) |
580 | t << _slnProjDepEnd; |
581 | |
582 | t << _slnProjectEnd; |
583 | } |
584 | |
585 | t << _slnGlobalBeg; |
586 | |
587 | for (auto = extraSubdirs.cbegin(), end = extraSubdirs.cend(); extraIt != end; ++extraIt) { |
588 | for (const QString &depend : extraIt.value()) { |
589 | if (!projGuids[depend].isEmpty()) { |
590 | extraIt.key()->dependencies << projGuids[depend]; |
591 | } else if (!profileLookup[depend].isEmpty()) { |
592 | if (!projGuids[profileLookup[depend]].isEmpty()) |
593 | extraIt.key()->dependencies << projGuids[profileLookup[depend]]; |
594 | } |
595 | } |
596 | } |
597 | QString slnConf = _slnSolutionConf; |
598 | if (!project->isEmpty(v: "VCPROJ_ARCH" )) { |
599 | slnConf.replace(before: QLatin1String("|Win32" ), after: "|" + project->first(variableName: "VCPROJ_ARCH" )); |
600 | } else if (is64Bit) { |
601 | slnConf.replace(before: QLatin1String("|Win32" ), after: QLatin1String("|x64" )); |
602 | } |
603 | t << slnConf; |
604 | |
605 | // Restore previous after_user_var options |
606 | Option::globals->extra_cmds[QMakeEvalAfter] = old_after_vars; |
607 | |
608 | t << _slnProjConfBeg; |
609 | for(QList<VcsolutionDepend*>::Iterator it = solution_cleanup.begin(); it != solution_cleanup.end(); ++it) { |
610 | QString platform = is64Bit ? "x64" : "Win32" ; |
611 | QString xplatform = platform; |
612 | if (!project->isEmpty(v: "VCPROJ_ARCH" )) { |
613 | xplatform = project->first(variableName: "VCPROJ_ARCH" ).toQString(); |
614 | } |
615 | if (!project->isHostBuild()) |
616 | platform = xplatform; |
617 | t << "\n\t\t" << (*it)->uuid << QString(_slnProjDbgConfTag1).arg(a: xplatform) << platform; |
618 | t << "\n\t\t" << (*it)->uuid << QString(_slnProjDbgConfTag2).arg(a: xplatform) << platform; |
619 | t << "\n\t\t" << (*it)->uuid << QString(_slnProjRelConfTag1).arg(a: xplatform) << platform; |
620 | t << "\n\t\t" << (*it)->uuid << QString(_slnProjRelConfTag2).arg(a: xplatform) << platform; |
621 | } |
622 | t << _slnProjConfEnd; |
623 | t << _slnExtSections; |
624 | t << _slnGlobalEnd; |
625 | |
626 | |
627 | while (!solution_cleanup.isEmpty()) |
628 | delete solution_cleanup.takeFirst(); |
629 | } |
630 | |
631 | // ------------------------------------------------------------------------------------------------ |
632 | // ------------------------------------------------------------------------------------------------ |
633 | |
634 | bool VcprojGenerator::hasBuiltinCompiler(const QString &file) |
635 | { |
636 | // Source files |
637 | for (int i = 0; i < Option::cpp_ext.size(); ++i) |
638 | if (file.endsWith(s: Option::cpp_ext.at(i))) |
639 | return true; |
640 | for (int i = 0; i < Option::c_ext.size(); ++i) |
641 | if (file.endsWith(s: Option::c_ext.at(i))) |
642 | return true; |
643 | if (file.endsWith(s: ".rc" ) |
644 | || file.endsWith(s: ".idl" )) |
645 | return true; |
646 | return false; |
647 | } |
648 | |
649 | void VcprojGenerator::createCustomBuildToolFakeFile(const QString &cbtFilePath, |
650 | const QString &realOutFilePath) |
651 | { |
652 | QFile file(fileFixify(file: cbtFilePath, fix: FileFixifyFromOutdir | FileFixifyAbsolute)); |
653 | if (file.exists()) |
654 | return; |
655 | if (!file.open(flags: QIODevice::WriteOnly | QIODevice::Text)) { |
656 | warn_msg(t: WarnLogic, fmt: "Cannot create '%s'." , qPrintable(file.fileName())); |
657 | return; |
658 | } |
659 | file.write(data: "This is a dummy file needed to create " ); |
660 | file.write(qPrintable(realOutFilePath)); |
661 | file.write(data: "\n" ); |
662 | } |
663 | |
664 | void VcprojGenerator::init() |
665 | { |
666 | is64Bit = (project->first(variableName: "QMAKE_TARGET.arch" ) == "x86_64" ); |
667 | projectWriter = createProjectWriter(); |
668 | |
669 | if(project->first(variableName: "TEMPLATE" ) == "vcsubdirs" ) //too much work for subdirs |
670 | return; |
671 | |
672 | debug_msg(level: 1, fmt: "Generator: MSVC.NET: Initializing variables" ); |
673 | |
674 | // this should probably not be here, but I'm using it to wrap the .t files |
675 | if (project->first(variableName: "TEMPLATE" ) == "vcapp" ) |
676 | project->values(v: "QMAKE_APP_FLAG" ).append(t: "1" ); |
677 | else if (project->first(variableName: "TEMPLATE" ) == "vclib" ) |
678 | project->values(v: "QMAKE_LIB_FLAG" ).append(t: "1" ); |
679 | |
680 | processVars(); |
681 | |
682 | // set /VERSION for EXE/DLL header |
683 | ProString major_minor = project->first(variableName: "VERSION_PE_HEADER" ); |
684 | if (major_minor.isEmpty()) { |
685 | ProString version = project->first(variableName: "VERSION" ); |
686 | if (!version.isEmpty()) { |
687 | int firstDot = version.indexOf(s: "." ); |
688 | int secondDot = version.indexOf(s: "." , from: firstDot + 1); |
689 | major_minor = version.left(len: secondDot); |
690 | } |
691 | } |
692 | if (!major_minor.isEmpty()) |
693 | project->values(v: "QMAKE_LFLAGS" ).append(t: "/VERSION:" + major_minor); |
694 | |
695 | MakefileGenerator::init(); |
696 | |
697 | // $$QMAKE.. -> $$MSVCPROJ.. ------------------------------------- |
698 | const ProStringList &incs = project->values(v: "INCLUDEPATH" ); |
699 | for (ProStringList::ConstIterator incit = incs.begin(); incit != incs.end(); ++incit) { |
700 | QString inc = (*incit).toQString(); |
701 | project->values(v: "MSVCPROJ_INCPATH" ).append(t: "-I" + escapeFilePath(path: inc)); |
702 | } |
703 | |
704 | QString dest = Option::fixPathToTargetOS(in: project->first(variableName: "TARGET" ).toQString()) + project->first(variableName: "TARGET_EXT" ); |
705 | project->values(v: "MSVCPROJ_TARGET" ) = ProStringList(dest); |
706 | |
707 | // DLL COPY ------------------------------------------------------ |
708 | if (project->isActiveConfig(config: "dll" ) && !project->values(v: "DLLDESTDIR" ).isEmpty()) { |
709 | const ProStringList &dlldirs = project->values(v: "DLLDESTDIR" ); |
710 | QString copydll("" ); |
711 | ProStringList::ConstIterator dlldir; |
712 | for (dlldir = dlldirs.begin(); dlldir != dlldirs.end(); ++dlldir) { |
713 | if (!copydll.isEmpty()) |
714 | copydll += " && " ; |
715 | copydll += "copy \"$(TargetPath)\" " + escapeFilePath(path: *dlldir); |
716 | } |
717 | |
718 | QString deststr("Copy " + dest + " to " ); |
719 | for (dlldir = dlldirs.begin(); dlldir != dlldirs.end();) { |
720 | deststr += *dlldir; |
721 | ++dlldir; |
722 | if (dlldir != dlldirs.end()) |
723 | deststr += ", " ; |
724 | } |
725 | |
726 | project->values(v: "MSVCPROJ_COPY_DLL" ).append(t: copydll); |
727 | project->values(v: "MSVCPROJ_COPY_DLL_DESC" ).append(t: deststr); |
728 | } |
729 | |
730 | #if 0 |
731 | // Verbose output if "-d -d"... |
732 | qDebug("Generator: MSVC.NET: List of current variables:" ); |
733 | for (ProValueMap::ConstIterator it = project->variables().begin(); it != project->variables().end(); ++it) |
734 | qDebug("Generator: MSVC.NET: %s => %s" , qPrintable(it.key().toQString()), qPrintable(it.value().join(" | " ))); |
735 | #endif |
736 | |
737 | // Figure out what we're trying to build |
738 | if(project->first(variableName: "TEMPLATE" ) == "vcapp" ) { |
739 | projectTarget = Application; |
740 | } else if(project->first(variableName: "TEMPLATE" ) == "vclib" ) { |
741 | if(project->isActiveConfig(config: "staticlib" )) { |
742 | project->values(v: "LIBS" ) += project->values(v: "RES_FILE" ); |
743 | projectTarget = StaticLib; |
744 | } else |
745 | projectTarget = SharedLib; |
746 | } |
747 | |
748 | // Setup PCH variables |
749 | precompH = project->first(variableName: "PRECOMPILED_HEADER" ).toQString(); |
750 | precompSource = project->first(variableName: "PRECOMPILED_SOURCE" ).toQString(); |
751 | pchIsCFile = project->isActiveConfig(config: "precompile_header_c" ); |
752 | usePCH = !precompH.isEmpty() && (pchIsCFile || project->isActiveConfig(config: "precompile_header" )); |
753 | if (usePCH) { |
754 | precompHFilename = fileInfo(file: precompH).fileName(); |
755 | // Created files |
756 | QString origTarget = project->first(variableName: "QMAKE_ORIG_TARGET" ).toQString(); |
757 | precompObj = origTarget + Option::obj_ext; |
758 | precompPch = origTarget + ".pch" ; |
759 | // Add PRECOMPILED_HEADER to HEADERS |
760 | if (!project->values(v: "HEADERS" ).contains(str: precompH)) |
761 | project->values(v: "HEADERS" ) += precompH; |
762 | // Return to variable pool |
763 | project->values(v: "PRECOMPILED_OBJECT" ) = ProStringList(precompObj); |
764 | project->values(v: "PRECOMPILED_PCH" ) = ProStringList(precompPch); |
765 | |
766 | autogenPrecompSource = precompSource.isEmpty() && project->isActiveConfig(config: "autogen_precompile_source" ); |
767 | if (autogenPrecompSource) { |
768 | precompSource = precompH |
769 | + (pchIsCFile |
770 | ? (Option::c_ext.size() ? Option::c_ext.at(i: 0) : QLatin1String(".c" )) |
771 | : (Option::cpp_ext.size() ? Option::cpp_ext.at(i: 0) : QLatin1String(".cpp" ))); |
772 | project->values(v: "GENERATED_SOURCES" ) += precompSource; |
773 | } else if (!precompSource.isEmpty()) { |
774 | project->values(v: "SOURCES" ) += precompSource; |
775 | } |
776 | } |
777 | |
778 | // Helper function to create a fake file foo.cbt for the project view. |
779 | // |
780 | // This prevents VS from complaining about a circular dependency from "foo -> foo". |
781 | // |
782 | // The .cbt file is added as "source" of the Custom Build Tool. This means, in the project |
783 | // view, this is the file the Custom Build Tool property page is attached to. |
784 | // |
785 | // This function returns a pair with |
786 | // - the fully resolved output file path |
787 | // - the file path of the .cbt file |
788 | auto |
789 | = [this](const QString &compilerOutput, const ProString &, |
790 | const QStringList &inputs) -> std::pair<QString, QString> |
791 | { |
792 | QString realOut = replaceExtraCompilerVariables(compilerOutput, inputs, {}, NoShell); |
793 | QString out = realOut + customBuildToolFilterFileSuffix; |
794 | createCustomBuildToolFakeFile(cbtFilePath: out, realOutFilePath: realOut); |
795 | out = Option::fixPathToTargetOS(in: out, fix_env: false); |
796 | extraCompilerSources[out] += extraCompiler.toQString(); |
797 | return { realOut, out }; |
798 | }; |
799 | |
800 | // Add all input files for a custom compiler into a map for uniqueness. |
801 | // |
802 | // Use .cbt files for the following cases: |
803 | // - CONFIG += combine |
804 | // - the input has a built-in compiler (e.g. C++ source file) |
805 | for (const ProString &quc : project->values(v: "QMAKE_EXTRA_COMPILERS" )) { |
806 | const ProStringList &invar = project->values(v: ProKey(quc + ".input" )); |
807 | const QString compiler_out = project->first(variableName: ProKey(quc + ".output" )).toQString(); |
808 | |
809 | QStringList inputFiles; |
810 | for (auto it = invar.begin(); it != invar.end(); ++it) |
811 | inputFiles += project->values(v: it->toKey()).toQStringList(); |
812 | |
813 | if (project->values(v: ProKey(quc + ".CONFIG" )).contains(str: "combine" )) { |
814 | // Handle "CONFIG += combine" extra compilers. |
815 | QString realOut; |
816 | QString out; |
817 | std::tie(args&: realOut, args&: out) |
818 | = addExtraCompilerSourceWithCustomBuildToolFakeFile(compiler_out, quc, inputFiles); |
819 | if (hasBuiltinCompiler(file: realOut)) |
820 | extraCompilerOutputs[out] = realOut; |
821 | } else { |
822 | // Handle regular 1-to-1 extra compilers. |
823 | for (const QString &file : inputFiles) { |
824 | if (verifyExtraCompiler(c: quc, f: file)) { |
825 | if (!hasBuiltinCompiler(file)) { |
826 | extraCompilerSources[file] += quc.toQString(); |
827 | } else { |
828 | QString out; |
829 | std::tie(args: std::ignore, args&: out) |
830 | = addExtraCompilerSourceWithCustomBuildToolFakeFile(compiler_out, |
831 | quc, |
832 | QStringList(file)); |
833 | extraCompilerOutputs[out] = file; |
834 | } |
835 | } |
836 | } |
837 | } |
838 | } |
839 | |
840 | #if 0 // Debugging |
841 | for (auto it = extraCompilerSources.cbegin(), end = extraCompilerSources.cend(); it != end; ++it) |
842 | qDebug("Extracompilers for %s are (%s)" , it.key().toLatin1().constData(), it.value().join(", " ).toLatin1().constData()); |
843 | for (auto it = extraCompilerOutputs.cbegin(), end = extraCompilerOutputs.cend(); it != end; ++it) |
844 | qDebug("Object mapping for %s is (%s)" , qPrintable(it.key()), qPrintable(it.value())); |
845 | qDebug("" ); |
846 | #endif |
847 | } |
848 | |
849 | bool VcprojGenerator::mergeBuildProject(MakefileGenerator *other) |
850 | { |
851 | if (!other || !other->projectFile()) { |
852 | warn_msg(t: WarnLogic, fmt: "VcprojGenerator: Cannot merge null project." ); |
853 | return false; |
854 | } |
855 | if (other->projectFile()->first(variableName: "MAKEFILE_GENERATOR" ) != project->first(variableName: "MAKEFILE_GENERATOR" )) { |
856 | warn_msg(t: WarnLogic, fmt: "VcprojGenerator: Cannot merge other types of projects! (ignored)" ); |
857 | return false; |
858 | } |
859 | |
860 | VcprojGenerator *otherVC = static_cast<VcprojGenerator*>(other); |
861 | mergedProjects += otherVC; |
862 | return true; |
863 | } |
864 | |
865 | void VcprojGenerator::initProject() |
866 | { |
867 | // Initialize XML sub elements |
868 | // - Do this first since project elements may need |
869 | // - to know of certain configuration options |
870 | initConfiguration(); |
871 | initRootFiles(); |
872 | initSourceFiles(); |
873 | initHeaderFiles(); |
874 | initGeneratedFiles(); |
875 | initLexYaccFiles(); |
876 | initTranslationFiles(); |
877 | initFormFiles(); |
878 | initResourceFiles(); |
879 | initDistributionFiles(); |
880 | initExtraCompilerOutputs(); |
881 | |
882 | // Own elements ----------------------------- |
883 | vcProject.Name = project->first(variableName: "QMAKE_ORIG_TARGET" ).toQString(); |
884 | switch (vcProject.Configuration.CompilerVersion) { |
885 | case NET2022: |
886 | vcProject.Version = "17.00" ; |
887 | break; |
888 | case NET2019: |
889 | vcProject.Version = "16.00" ; |
890 | break; |
891 | case NET2017: |
892 | vcProject.Version = "15.00" ; |
893 | break; |
894 | case NET2015: |
895 | vcProject.Version = "14.00" ; |
896 | break; |
897 | case NET2013: |
898 | vcProject.Version = "12.00" ; |
899 | break; |
900 | case NET2012: |
901 | vcProject.Version = "11.00" ; |
902 | break; |
903 | case NET2010: |
904 | vcProject.Version = "10.00" ; |
905 | break; |
906 | case NET2008: |
907 | vcProject.Version = "9,00" ; |
908 | break; |
909 | case NET2005: |
910 | //### using ',' because of a bug in 2005 B2 |
911 | //### VS uses '.' or ',' depending on the regional settings! Using ',' always works. |
912 | vcProject.Version = "8,00" ; |
913 | break; |
914 | case NET2003: |
915 | vcProject.Version = "7.10" ; |
916 | break; |
917 | case NET2002: |
918 | vcProject.Version = "7.00" ; |
919 | break; |
920 | default: |
921 | vcProject.Version = "7.00" ; |
922 | warn_msg(t: WarnLogic, fmt: "Generator: MSVC.NET: Unknown version (%d) of MSVC detected for .vcproj" , vcProject.Configuration.CompilerVersion); |
923 | break; |
924 | } |
925 | |
926 | vcProject.Keyword = project->first(variableName: "VCPROJ_KEYWORD" ).toQString(); |
927 | if (!project->isEmpty(v: "VCPROJ_ARCH" )) { |
928 | vcProject.PlatformName = project->first(variableName: "VCPROJ_ARCH" ).toQString(); |
929 | } else { |
930 | vcProject.PlatformName = (is64Bit ? "x64" : "Win32" ); |
931 | } |
932 | vcProject.SdkVersion = project->first(variableName: "WINSDK_VER" ).toQString(); |
933 | // These are not used by Qt, but may be used by customers |
934 | vcProject.SccProjectName = project->first(variableName: "SCCPROJECTNAME" ).toQString(); |
935 | vcProject.SccLocalPath = project->first(variableName: "SCCLOCALPATH" ).toQString(); |
936 | vcProject.flat_files = project->isActiveConfig(config: "flat" ); |
937 | |
938 | // Set up the full target path for target conflict checking. |
939 | const QChar slash = QLatin1Char('/'); |
940 | QString destdir = QDir::fromNativeSeparators(pathName: var(var: "DESTDIR" )); |
941 | if (!destdir.endsWith(c: slash)) |
942 | destdir.append(c: slash); |
943 | project->values(v: "DEST_TARGET" ) = ProStringList(destdir |
944 | + project->first(variableName: "TARGET" ) |
945 | + project->first(variableName: "TARGET_EXT" )); |
946 | } |
947 | |
948 | void VcprojGenerator::initConfiguration() |
949 | { |
950 | // Initialize XML sub elements |
951 | // - Do this first since main configuration elements may need |
952 | // - to know of certain compiler/linker options |
953 | VCConfiguration &conf = vcProject.Configuration; |
954 | conf.suppressUnknownOptionWarnings = project->isActiveConfig(config: "suppress_vcproj_warnings" ); |
955 | conf.CompilerVersion = vsVersionFromString(versionString: project->first(variableName: "MSVC_VER" )); |
956 | |
957 | initCompilerTool(); |
958 | |
959 | // Only on configuration per build |
960 | bool isDebug = project->isActiveConfig(config: "debug" ); |
961 | |
962 | if(projectTarget == StaticLib) |
963 | initLibrarianTool(); |
964 | else { |
965 | conf.linker.GenerateDebugInformation = project->isActiveConfig(config: "debug_info" ) ? _True : _False; |
966 | initLinkerTool(); |
967 | } |
968 | initManifestTool(); |
969 | initResourceTool(); |
970 | initIDLTool(); |
971 | |
972 | // Own elements ----------------------------- |
973 | ProString temp = project->first(variableName: "BuildBrowserInformation" ); |
974 | switch (projectTarget) { |
975 | case SharedLib: |
976 | conf.ConfigurationType = typeDynamicLibrary; |
977 | break; |
978 | case StaticLib: |
979 | conf.ConfigurationType = typeStaticLibrary; |
980 | break; |
981 | case Application: |
982 | default: |
983 | conf.ConfigurationType = typeApplication; |
984 | break; |
985 | } |
986 | |
987 | conf.OutputDirectory = project->first(variableName: "DESTDIR" ).toQString(); |
988 | if (conf.OutputDirectory.isEmpty()) |
989 | conf.OutputDirectory = ".\\" ; |
990 | if (!conf.OutputDirectory.endsWith(s: "\\" )) |
991 | conf.OutputDirectory += '\\'; |
992 | if (conf.CompilerVersion >= NET2010) { |
993 | conf.PlatformToolSet = retrievePlatformToolSet(); |
994 | |
995 | const QFileInfo targetInfo = fileInfo(file: project->first(variableName: "MSVCPROJ_TARGET" ).toQString()); |
996 | conf.PrimaryOutput = targetInfo.completeBaseName(); |
997 | |
998 | const QString targetSuffix = targetInfo.suffix(); |
999 | if (!isStandardSuffix(suffix: targetSuffix)) |
1000 | conf.PrimaryOutputExtension = '.' + targetSuffix; |
1001 | } |
1002 | |
1003 | conf.Name = project->values(v: "BUILD_NAME" ).join(sep: ' '); |
1004 | if (conf.Name.isEmpty()) |
1005 | conf.Name = isDebug ? "Debug" : "Release" ; |
1006 | conf.ConfigurationName = conf.Name; |
1007 | if (!project->isEmpty(v: "VCPROJ_ARCH" )) { |
1008 | conf.Name += "|" + project->first(variableName: "VCPROJ_ARCH" ); |
1009 | } else { |
1010 | conf.Name += (is64Bit ? "|x64" : "|Win32" ); |
1011 | } |
1012 | conf.ATLMinimizesCRunTimeLibraryUsage = (project->first(variableName: "ATLMinimizesCRunTimeLibraryUsage" ).isEmpty() ? _False : _True); |
1013 | conf.BuildBrowserInformation = triState(temp.isEmpty() ? (short)unset : temp.toShort()); |
1014 | temp = project->first(variableName: "CharacterSet" ); |
1015 | conf.CharacterSet = charSet(temp.isEmpty() ? short(charSetNotSet) : temp.toShort()); |
1016 | conf.DeleteExtensionsOnClean = project->first(variableName: "DeleteExtensionsOnClean" ).toQString(); |
1017 | conf.ImportLibrary = conf.linker.ImportLibrary; |
1018 | conf.IntermediateDirectory = project->first(variableName: "OBJECTS_DIR" ).toQString(); |
1019 | conf.WholeProgramOptimization = conf.compiler.WholeProgramOptimization; |
1020 | temp = project->first(variableName: "UseOfATL" ); |
1021 | if(!temp.isEmpty()) |
1022 | conf.UseOfATL = useOfATL(temp.toShort()); |
1023 | temp = project->first(variableName: "UseOfMfc" ); |
1024 | if(!temp.isEmpty()) |
1025 | conf.UseOfMfc = useOfMfc(temp.toShort()); |
1026 | |
1027 | // Configuration does not need parameters from |
1028 | // these sub XML items; |
1029 | initCustomBuildTool(); |
1030 | initPreBuildEventTools(); |
1031 | initPostBuildEventTools(); |
1032 | // Only deploy for crosscompiled projects |
1033 | if (!project->isHostBuild()) |
1034 | initDeploymentTool(); |
1035 | initWinDeployQtTool(); |
1036 | initPreLinkEventTools(); |
1037 | } |
1038 | |
1039 | // Filter from the given QMAKE_CFLAGS the options that are relevant |
1040 | // for the vcxproj-global VCCLCompilerTool. |
1041 | static ProStringList relevantCFlags(const ProStringList &flags) |
1042 | { |
1043 | ProStringList result; |
1044 | static const QRegularExpression rex("^[/-]std:" ); |
1045 | for (const ProString &flag : flags) { |
1046 | if (rex.match(subject: flag.toQString()).hasMatch()) { |
1047 | result.append(t: flag); |
1048 | } |
1049 | } |
1050 | return result; |
1051 | } |
1052 | |
1053 | void VcprojGenerator::initCompilerTool() |
1054 | { |
1055 | QString placement = project->first(variableName: "OBJECTS_DIR" ).toQString(); |
1056 | if(placement.isEmpty()) |
1057 | placement = ".\\" ; |
1058 | |
1059 | VCConfiguration &conf = vcProject.Configuration; |
1060 | if (conf.CompilerVersion >= NET2010) { |
1061 | // adjust compiler tool defaults for VS 2010 and above |
1062 | conf.compiler.Optimization = optimizeDisabled; |
1063 | } |
1064 | conf.compiler.AssemblerListingLocation = placement ; |
1065 | conf.compiler.ObjectFile = placement ; |
1066 | conf.compiler.ExceptionHandling = ehNone; |
1067 | // PCH |
1068 | if (usePCH) { |
1069 | conf.compiler.UsePrecompiledHeader = pchUseUsingSpecific; |
1070 | conf.compiler.PrecompiledHeaderFile = "$(IntDir)\\" + precompPch; |
1071 | conf.compiler.PrecompiledHeaderThrough = project->first(variableName: "PRECOMPILED_HEADER" ).toQString(); |
1072 | conf.compiler.ForcedIncludeFiles = project->values(v: "PRECOMPILED_HEADER" ).toQStringList(); |
1073 | } |
1074 | |
1075 | conf.compiler.parseOptions(options: relevantCFlags(flags: project->values(v: "QMAKE_CFLAGS" ))); |
1076 | conf.compiler.parseOptions(options: project->values(v: "QMAKE_CXXFLAGS" )); |
1077 | |
1078 | if (project->isActiveConfig(config: "windows" )) |
1079 | conf.compiler.PreprocessorDefinitions += "_WINDOWS" ; |
1080 | else if (project->isActiveConfig(config: "console" )) |
1081 | conf.compiler.PreprocessorDefinitions += "_CONSOLE" ; |
1082 | |
1083 | conf.compiler.PreprocessorDefinitions += project->values(v: "DEFINES" ).toQStringList(); |
1084 | conf.compiler.PreprocessorDefinitions += project->values(v: "PRL_EXPORT_DEFINES" ).toQStringList(); |
1085 | conf.compiler.parseOptions(options: project->values(v: "MSVCPROJ_INCPATH" )); |
1086 | } |
1087 | |
1088 | void VcprojGenerator::initLibrarianTool() |
1089 | { |
1090 | VCConfiguration &conf = vcProject.Configuration; |
1091 | conf.librarian.OutputFile = "$(OutDir)\\" ; |
1092 | conf.librarian.OutputFile += project->first(variableName: "MSVCPROJ_TARGET" ).toQString(); |
1093 | conf.librarian.AdditionalOptions += project->values(v: "QMAKE_LIBFLAGS" ).toQStringList(); |
1094 | } |
1095 | |
1096 | void VcprojGenerator::initManifestTool() |
1097 | { |
1098 | VCManifestTool &tool = vcProject.Configuration.manifestTool; |
1099 | const ProString tmplt = project->first(variableName: "TEMPLATE" ); |
1100 | if ((tmplt == "vclib" |
1101 | && !project->isActiveConfig(config: "embed_manifest_dll" ) |
1102 | && !project->isActiveConfig(config: "static" )) |
1103 | || (tmplt == "vcapp" |
1104 | && !project->isActiveConfig(config: "embed_manifest_exe" ))) { |
1105 | tool.EmbedManifest = _False; |
1106 | } |
1107 | } |
1108 | |
1109 | void VcprojGenerator::initLinkerTool() |
1110 | { |
1111 | VCConfiguration &conf = vcProject.Configuration; |
1112 | conf.linker.parseOptions(options: project->values(v: "QMAKE_LFLAGS" )); |
1113 | |
1114 | if (!project->values(v: "DEF_FILE" ).isEmpty()) |
1115 | conf.linker.ModuleDefinitionFile = project->first(variableName: "DEF_FILE" ).toQString(); |
1116 | |
1117 | static const char * const lflags[] = { "LIBS" , "LIBS_PRIVATE" , |
1118 | "QMAKE_LIBS" , "QMAKE_LIBS_PRIVATE" , nullptr }; |
1119 | for (int i = 0; lflags[i]; i++) { |
1120 | const auto libs = fixLibFlags(var: lflags[i]); |
1121 | for (const ProString &lib : libs) { |
1122 | if (lib.startsWith(sub: "/LIBPATH:" )) |
1123 | conf.linker.AdditionalLibraryDirectories << lib.mid(off: 9).toQString(); |
1124 | else |
1125 | conf.linker.AdditionalDependencies << lib.toQString(); |
1126 | } |
1127 | } |
1128 | |
1129 | conf.linker.OutputFile = "$(OutDir)\\" ; |
1130 | conf.linker.OutputFile += project->first(variableName: "MSVCPROJ_TARGET" ).toQString(); |
1131 | } |
1132 | |
1133 | void VcprojGenerator::initResourceTool() |
1134 | { |
1135 | VCConfiguration &conf = vcProject.Configuration; |
1136 | |
1137 | ProStringList rcDefines = project->values(v: "RC_DEFINES" ); |
1138 | if (rcDefines.size() > 0) |
1139 | conf.resource.PreprocessorDefinitions = rcDefines.toQStringList(); |
1140 | else |
1141 | conf.resource.PreprocessorDefinitions = conf.compiler.PreprocessorDefinitions; |
1142 | |
1143 | for (const ProString &path : project->values(v: "RC_INCLUDEPATH" )) { |
1144 | QString fixedPath = fileFixify(file: path.toQString()); |
1145 | if (fileInfo(file: fixedPath).isRelative()) { |
1146 | if (fixedPath == QLatin1String("." )) |
1147 | fixedPath = QStringLiteral("$(ProjectDir)" ); |
1148 | else |
1149 | fixedPath.prepend(QStringLiteral("$(ProjectDir)\\" )); |
1150 | } |
1151 | conf.resource.AdditionalIncludeDirectories << escapeFilePath(path: fixedPath); |
1152 | } |
1153 | |
1154 | // We need to add _DEBUG for the debug version of the project, since the normal compiler defines |
1155 | // do not contain it. (The compiler defines this symbol automatically, which is wy we don't need |
1156 | // to add it for the compiler) However, the resource tool does not do this. |
1157 | if(project->isActiveConfig(config: "debug" )) |
1158 | conf.resource.PreprocessorDefinitions += "_DEBUG" ; |
1159 | if (conf.CompilerVersion < NET2010 && project->isActiveConfig(config: "staticlib" )) |
1160 | conf.resource.ResourceOutputFileName = "$(OutDir)\\$(InputName).res" ; |
1161 | } |
1162 | |
1163 | void VcprojGenerator::initIDLTool() |
1164 | { |
1165 | } |
1166 | |
1167 | void VcprojGenerator::initCustomBuildTool() |
1168 | { |
1169 | } |
1170 | |
1171 | void VcprojGenerator::initPreBuildEventTools() |
1172 | { |
1173 | } |
1174 | |
1175 | void VcprojGenerator::initPostBuildEventTools() |
1176 | { |
1177 | VCConfiguration &conf = vcProject.Configuration; |
1178 | if (!project->values(v: "QMAKE_POST_LINK" ).isEmpty()) { |
1179 | QStringList cmdline = VCToolBase::fixCommandLine(input: var(var: "QMAKE_POST_LINK" )); |
1180 | conf.postBuild.CommandLine = cmdline; |
1181 | conf.postBuild.Description = cmdline.join(sep: QLatin1String("\r\n" )); |
1182 | conf.postBuild.ExcludedFromBuild = _False; |
1183 | } |
1184 | if (!project->values(v: "MSVCPROJ_COPY_DLL" ).isEmpty()) { |
1185 | conf.postBuild.Description += var(var: "MSVCPROJ_COPY_DLL_DESC" ); |
1186 | conf.postBuild.CommandLine += var(var: "MSVCPROJ_COPY_DLL" ); |
1187 | conf.postBuild.ExcludedFromBuild = _False; |
1188 | } |
1189 | } |
1190 | |
1191 | void VcprojGenerator::initDeploymentTool() |
1192 | { |
1193 | VCConfiguration &conf = vcProject.Configuration; |
1194 | QString targetPath; |
1195 | targetPath = project->values(v: "deploy.path" ).join(sep: ' '); |
1196 | if (targetPath.isEmpty()) |
1197 | targetPath = QString("%CSIDL_PROGRAM_FILES%\\" ) + project->first(variableName: "TARGET" ); |
1198 | if (targetPath.endsWith(s: "/" ) || targetPath.endsWith(s: "\\" )) |
1199 | targetPath.chop(n: 1); |
1200 | conf.deployment.RemoteDirectory = targetPath; |
1201 | const ProStringList dllPaths = project->values(v: "QMAKE_DLL_PATHS" ); |
1202 | // Only deploy Qt libs for shared build |
1203 | if (!dllPaths.isEmpty()) { |
1204 | // FIXME: This code should actually resolve the libraries from all Qt modules. |
1205 | ProStringList arg = project->values(v: "LIBS" ) + project->values(v: "LIBS_PRIVATE" ) |
1206 | + project->values(v: "QMAKE_LIBS" ) + project->values(v: "QMAKE_LIBS_PRIVATE" ); |
1207 | bool qpaPluginDeployed = false; |
1208 | for (ProStringList::ConstIterator it = arg.constBegin(); it != arg.constEnd(); ++it) { |
1209 | QString dllName = (*it).toQString(); |
1210 | dllName.replace(before: QLatin1Char('\\'), after: QLatin1Char('/')); |
1211 | // LIBPATH isn't relevant for deployment |
1212 | if (dllName.startsWith(s: QLatin1String("/LIBPATH:" ))) |
1213 | continue; |
1214 | // We want to deploy .dlls not .libs |
1215 | if (dllName.endsWith(s: QLatin1String(".lib" ))) |
1216 | dllName.replace(i: dllName.size() - 3, len: 3, after: QLatin1String("dll" )); |
1217 | // Use only the file name and check in Qt's install path and LIBPATHs to check for existence |
1218 | dllName.remove(i: 0, len: dllName.lastIndexOf(c: QLatin1Char('/')) + 1); |
1219 | QFileInfo info; |
1220 | for (const ProString &dllPath : dllPaths) { |
1221 | QString absoluteDllFilePath = dllPath.toQString(); |
1222 | if (!absoluteDllFilePath.endsWith(c: QLatin1Char('/'))) |
1223 | absoluteDllFilePath += QLatin1Char('/'); |
1224 | absoluteDllFilePath += dllName; |
1225 | info = QFileInfo(absoluteDllFilePath); |
1226 | if (info.exists()) |
1227 | break; |
1228 | } |
1229 | |
1230 | if (!info.exists()) |
1231 | continue; |
1232 | |
1233 | conf.deployment.AdditionalFiles += info.fileName() |
1234 | + "|" + QDir::toNativeSeparators(pathName: info.absolutePath()) |
1235 | + "|" + targetPath |
1236 | + "|0;" ; |
1237 | if (!qpaPluginDeployed) { |
1238 | QString debugInfix; |
1239 | bool foundGuid = dllName.contains(s: QLatin1String("Guid" )); |
1240 | if (foundGuid) |
1241 | debugInfix = QLatin1Char('d'); |
1242 | |
1243 | if (foundGuid || dllName.contains(s: QLatin1String("Gui" ))) { |
1244 | QFileInfo info2; |
1245 | for (const ProString &dllPath : dllPaths) { |
1246 | QString absoluteDllFilePath = dllPath.toQString(); |
1247 | if (!absoluteDllFilePath.endsWith(c: QLatin1Char('/'))) |
1248 | absoluteDllFilePath += QLatin1Char('/'); |
1249 | absoluteDllFilePath += QLatin1String("../plugins/platforms/qwindows" ) |
1250 | + debugInfix + QLatin1String(".dll" ); |
1251 | info2 = QFileInfo(absoluteDllFilePath); |
1252 | if (info2.exists()) |
1253 | break; |
1254 | } |
1255 | if (info2.exists()) { |
1256 | conf.deployment.AdditionalFiles += QLatin1String("qwindows" ) + debugInfix + QLatin1String(".dll" ) |
1257 | + QLatin1Char('|') + QDir::toNativeSeparators(pathName: info2.absolutePath()) |
1258 | + QLatin1Char('|') + targetPath + QLatin1String("\\platforms" ) |
1259 | + QLatin1String("|0;" ); |
1260 | qpaPluginDeployed = true; |
1261 | } |
1262 | } |
1263 | } |
1264 | } |
1265 | } |
1266 | |
1267 | for (const ProString &item : project->values(v: "INSTALLS" )) { |
1268 | // get item.path |
1269 | QString devicePath = project->first(variableName: ProKey(item + ".path" )).toQString(); |
1270 | if (devicePath.isEmpty()) |
1271 | devicePath = targetPath; |
1272 | // check if item.path is relative (! either /,\ or %) |
1273 | if (!(devicePath.at(i: 0) == QLatin1Char('/') |
1274 | || devicePath.at(i: 0) == QLatin1Char('\\') |
1275 | || devicePath.at(i: 0) == QLatin1Char('%'))) { |
1276 | // create output path |
1277 | devicePath = Option::fixPathToTargetOS(in: targetPath + QLatin1Char('\\') + devicePath); |
1278 | } |
1279 | // foreach d in item.files |
1280 | for (const ProString &src : project->values(v: ProKey(item + ".files" ))) { |
1281 | QString itemDevicePath = devicePath; |
1282 | QString source = Option::normalizePath(in: src.toQString()); |
1283 | QString nameFilter; |
1284 | QFileInfo info(source); |
1285 | QString searchPath; |
1286 | if (info.isDir()) { |
1287 | nameFilter = QLatin1String("*" ); |
1288 | itemDevicePath += "\\" + info.fileName(); |
1289 | searchPath = info.absoluteFilePath(); |
1290 | } else { |
1291 | nameFilter = info.fileName(); |
1292 | searchPath = info.absolutePath(); |
1293 | } |
1294 | |
1295 | int pathSize = searchPath.size(); |
1296 | QDirIterator iterator(searchPath, QStringList() << nameFilter |
1297 | , QDir::Files | QDir::NoDotAndDotDot | QDir::NoSymLinks |
1298 | , QDirIterator::Subdirectories); |
1299 | // foreach dirIterator-entry in d |
1300 | while(iterator.hasNext()) { |
1301 | iterator.next(); |
1302 | |
1303 | QString absoluteItemPath = Option::fixPathToTargetOS(in: QFileInfo(iterator.filePath()).absolutePath()); |
1304 | // Identify if it is just another subdir |
1305 | int diffSize = absoluteItemPath.size() - pathSize; |
1306 | // write out rules |
1307 | conf.deployment.AdditionalFiles += iterator.fileName() |
1308 | + "|" + absoluteItemPath |
1309 | + "|" + itemDevicePath + (diffSize ? (absoluteItemPath.right(n: diffSize)) : QLatin1String("" )) |
1310 | + "|0;" ; |
1311 | } |
1312 | } |
1313 | } |
1314 | } |
1315 | |
1316 | void VcprojGenerator::initWinDeployQtTool() |
1317 | { |
1318 | VCConfiguration &conf = vcProject.Configuration; |
1319 | conf.windeployqt.ExcludedFromBuild = true; |
1320 | if (project->isActiveConfig(config: "windeployqt" )) { |
1321 | conf.windeployqt.Record = QStringLiteral("$(TargetName).windeployqt.$(Platform).$(Configuration)" ); |
1322 | const QString commandLine = MakefileGenerator::shellQuote(str: QDir::toNativeSeparators(pathName: project->first(variableName: "QMAKE_WINDEPLOYQT" ).toQString())) |
1323 | + QLatin1Char(' ') + project->values(v: "WINDEPLOYQT_OPTIONS" ).join(sep: QLatin1Char(' ')); |
1324 | |
1325 | // Visual Studio copies all files to be deployed into the MSIL directory |
1326 | // and then invokes MDILXapCompile on it, which checks for managed code and |
1327 | // translates it into native code. The problem is that all entries of the |
1328 | // package will be copied into the MSIL directly, losing the subdirectory |
1329 | // structure (for instance for plugins). However, the MDILXapCompile call |
1330 | // itself contains the original subdirectories as parameters and hence the |
1331 | // call fails. |
1332 | // Hence the only way to get a build done is to recreate the directory |
1333 | // structure manually by invoking windeployqt a second time, so that |
1334 | // the MDILXapCompile call succeeds and deployment continues. |
1335 | conf.windeployqt.CommandLine += commandLine |
1336 | + QStringLiteral(" -list relative -dir \"$(MSBuildProjectDirectory)\" \"$(OutDir)\\$(TargetFileName)\" > " ) |
1337 | + MakefileGenerator::shellQuote(str: conf.windeployqt.Record); |
1338 | conf.windeployqt.config = &vcProject.Configuration; |
1339 | conf.windeployqt.ExcludedFromBuild = false; |
1340 | } |
1341 | } |
1342 | |
1343 | void VcprojGenerator::initPreLinkEventTools() |
1344 | { |
1345 | VCConfiguration &conf = vcProject.Configuration; |
1346 | if(!project->values(v: "QMAKE_PRE_LINK" ).isEmpty()) { |
1347 | QStringList cmdline = VCToolBase::fixCommandLine(input: var(var: "QMAKE_PRE_LINK" )); |
1348 | conf.preLink.CommandLine = cmdline; |
1349 | conf.preLink.Description = cmdline.join(sep: QLatin1String("\r\n" )); |
1350 | conf.preLink.ExcludedFromBuild = _False; |
1351 | } |
1352 | } |
1353 | |
1354 | void VcprojGenerator::initRootFiles() |
1355 | { |
1356 | // Note: Root files do _not_ have any filter name, filter nor GUID! |
1357 | vcProject.RootFiles.addFiles(fileList: project->values(v: "RC_FILE" )); |
1358 | |
1359 | vcProject.RootFiles.Project = this; |
1360 | vcProject.RootFiles.Config = &(vcProject.Configuration); |
1361 | } |
1362 | |
1363 | void VcprojGenerator::initSourceFiles() |
1364 | { |
1365 | vcProject.SourceFiles.Name = "Source Files" ; |
1366 | vcProject.SourceFiles.Filter = "cpp;c;cxx;def;odl;idl;hpj;bat;asm;asmx" ; |
1367 | vcProject.SourceFiles.Guid = _GUIDSourceFiles; |
1368 | |
1369 | vcProject.SourceFiles.addFiles(fileList: project->values(v: "SOURCES" )); |
1370 | |
1371 | vcProject.SourceFiles.Project = this; |
1372 | vcProject.SourceFiles.Config = &(vcProject.Configuration); |
1373 | } |
1374 | |
1375 | void VcprojGenerator::() |
1376 | { |
1377 | vcProject.HeaderFiles.Name = "Header Files" ; |
1378 | vcProject.HeaderFiles.Filter = "h;hpp;hxx;hm;inl;inc;xsd" ; |
1379 | vcProject.HeaderFiles.Guid = _GUIDHeaderFiles; |
1380 | |
1381 | vcProject.HeaderFiles.addFiles(fileList: project->values(v: "HEADERS" )); |
1382 | if (usePCH) // Generated PCH cpp file |
1383 | vcProject.HeaderFiles.addFile(filename: precompH); |
1384 | |
1385 | vcProject.HeaderFiles.Project = this; |
1386 | vcProject.HeaderFiles.Config = &(vcProject.Configuration); |
1387 | // vcProject.HeaderFiles.CustomBuild = mocHdr; |
1388 | // addMocArguments(vcProject.HeaderFiles); |
1389 | } |
1390 | |
1391 | void VcprojGenerator::initGeneratedFiles() |
1392 | { |
1393 | vcProject.GeneratedFiles.Name = "Generated Files" ; |
1394 | vcProject.GeneratedFiles.Filter = "cpp;c;cxx;moc;h;def;odl;idl;res;" ; |
1395 | vcProject.GeneratedFiles.Guid = _GUIDGeneratedFiles; |
1396 | |
1397 | // ### These cannot have CustomBuild (mocSrc)!! |
1398 | vcProject.GeneratedFiles.addFiles(fileList: project->values(v: "GENERATED_SOURCES" )); |
1399 | vcProject.GeneratedFiles.addFiles(fileList: project->values(v: "GENERATED_FILES" )); |
1400 | vcProject.GeneratedFiles.addFiles(fileList: project->values(v: "IDLSOURCES" )); |
1401 | if (project->values(v: "RC_FILE" ).isEmpty()) |
1402 | vcProject.GeneratedFiles.addFiles(fileList: project->values(v: "RES_FILE" )); |
1403 | if(!extraCompilerOutputs.isEmpty()) |
1404 | vcProject.GeneratedFiles.addFiles(fileList: extraCompilerOutputs.keys()); |
1405 | |
1406 | vcProject.GeneratedFiles.Project = this; |
1407 | vcProject.GeneratedFiles.Config = &(vcProject.Configuration); |
1408 | // vcProject.GeneratedFiles.CustomBuild = mocSrc; |
1409 | } |
1410 | |
1411 | void VcprojGenerator::initLexYaccFiles() |
1412 | { |
1413 | vcProject.LexYaccFiles.Name = "Lex / Yacc Files" ; |
1414 | vcProject.LexYaccFiles.ParseFiles = _False; |
1415 | vcProject.LexYaccFiles.Filter = "l;y" ; |
1416 | vcProject.LexYaccFiles.Guid = _GUIDLexYaccFiles; |
1417 | |
1418 | vcProject.LexYaccFiles.addFiles(fileList: project->values(v: "LEXSOURCES" )); |
1419 | vcProject.LexYaccFiles.addFiles(fileList: project->values(v: "YACCSOURCES" )); |
1420 | |
1421 | vcProject.LexYaccFiles.Project = this; |
1422 | vcProject.LexYaccFiles.Config = &(vcProject.Configuration); |
1423 | } |
1424 | |
1425 | void VcprojGenerator::initTranslationFiles() |
1426 | { |
1427 | vcProject.TranslationFiles.Name = "Translation Files" ; |
1428 | vcProject.TranslationFiles.ParseFiles = _False; |
1429 | vcProject.TranslationFiles.Filter = "ts;xlf" ; |
1430 | vcProject.TranslationFiles.Guid = _GUIDTranslationFiles; |
1431 | |
1432 | vcProject.TranslationFiles.addFiles(fileList: project->values(v: "TRANSLATIONS" )); |
1433 | vcProject.TranslationFiles.addFiles(fileList: project->values(v: "EXTRA_TRANSLATIONS" )); |
1434 | |
1435 | vcProject.TranslationFiles.Project = this; |
1436 | vcProject.TranslationFiles.Config = &(vcProject.Configuration); |
1437 | } |
1438 | |
1439 | void VcprojGenerator::initFormFiles() |
1440 | { |
1441 | vcProject.FormFiles.Name = "Form Files" ; |
1442 | vcProject.FormFiles.ParseFiles = _False; |
1443 | vcProject.FormFiles.Filter = "ui" ; |
1444 | vcProject.FormFiles.Guid = _GUIDFormFiles; |
1445 | vcProject.FormFiles.addFiles(fileList: project->values(v: "FORMS" )); |
1446 | vcProject.FormFiles.Project = this; |
1447 | vcProject.FormFiles.Config = &(vcProject.Configuration); |
1448 | } |
1449 | |
1450 | void VcprojGenerator::initResourceFiles() |
1451 | { |
1452 | vcProject.ResourceFiles.Name = "Resource Files" ; |
1453 | vcProject.ResourceFiles.ParseFiles = _False; |
1454 | vcProject.ResourceFiles.Filter = "qrc;*" ; //"rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;ts;xlf;qrc"; |
1455 | vcProject.ResourceFiles.Guid = _GUIDResourceFiles; |
1456 | |
1457 | // Bad hack, please look away ------------------------------------- |
1458 | QString rcc_dep_cmd = project->values(v: "rcc.depend_command" ).join(sep: ' '); |
1459 | if(!rcc_dep_cmd.isEmpty()) { |
1460 | const QStringList qrc_files = project->values(v: "RESOURCES" ).toQStringList(); |
1461 | QStringList deps; |
1462 | for (const QString &qrc_file : qrc_files) { |
1463 | callExtraCompilerDependCommand(extraCompiler: "rcc" , |
1464 | tmp_dep_cmd: rcc_dep_cmd, |
1465 | inpf: qrc_file, |
1466 | tmp_out: QString(), |
1467 | dep_lines: true, // dep_lines |
1468 | deps: &deps, |
1469 | existingDepsOnly: false, // existingDepsOnly |
1470 | checkCommandAvailability: true // checkCommandavailability |
1471 | ); |
1472 | } |
1473 | vcProject.ResourceFiles.addFiles(fileList: deps); |
1474 | } |
1475 | // You may look again -------------------------------------------- |
1476 | |
1477 | vcProject.ResourceFiles.addFiles(fileList: project->values(v: "RESOURCES" )); |
1478 | |
1479 | vcProject.ResourceFiles.Project = this; |
1480 | vcProject.ResourceFiles.Config = &(vcProject.Configuration); |
1481 | } |
1482 | |
1483 | void VcprojGenerator::initDistributionFiles() |
1484 | { |
1485 | vcProject.DistributionFiles.Name = "Distribution Files" ; |
1486 | vcProject.DistributionFiles.ParseFiles = _False; |
1487 | vcProject.DistributionFiles.Filter = "*" ; |
1488 | vcProject.DistributionFiles.Guid = _GUIDDistributionFiles; |
1489 | vcProject.DistributionFiles.addFiles(fileList: project->values(v: "DISTFILES" )); |
1490 | vcProject.DistributionFiles.Project = this; |
1491 | vcProject.DistributionFiles.Config = &(vcProject.Configuration); |
1492 | } |
1493 | |
1494 | QString VcprojGenerator::(const ProString &, |
1495 | const QStringList &inputs, |
1496 | const QStringList &outputs) |
1497 | { |
1498 | QString name = project->values(v: ProKey(extraCompiler + ".name" )).join(sep: ' '); |
1499 | if (name.isEmpty()) |
1500 | name = extraCompiler.toQString(); |
1501 | else |
1502 | name = replaceExtraCompilerVariables(name, inputs, outputs, NoShell); |
1503 | return name; |
1504 | } |
1505 | |
1506 | void VcprojGenerator::() |
1507 | { |
1508 | ProStringList otherFilters; |
1509 | otherFilters << "FORMS" |
1510 | << "GENERATED_FILES" |
1511 | << "GENERATED_SOURCES" |
1512 | << "HEADERS" |
1513 | << "IDLSOURCES" |
1514 | << "LEXSOURCES" |
1515 | << "RC_FILE" |
1516 | << "RESOURCES" |
1517 | << "RES_FILE" |
1518 | << "SOURCES" |
1519 | << "TRANSLATIONS" |
1520 | << "YACCSOURCES" ; |
1521 | const ProStringList &quc = project->values(v: "QMAKE_EXTRA_COMPILERS" ); |
1522 | for (ProStringList::ConstIterator it = quc.begin(); it != quc.end(); ++it) { |
1523 | const ProStringList &inputVars = project->values(v: ProKey(*it + ".input" )); |
1524 | ProStringList inputFiles; |
1525 | for (auto var : inputVars) |
1526 | inputFiles.append(l: project->values(v: var.toKey())); |
1527 | const ProStringList &outputs = project->values(v: ProKey(*it + ".output" )); |
1528 | |
1529 | // Create an extra compiler filter and add the files |
1530 | VCFilter ; |
1531 | extraCompile.Name = extraCompilerName(extraCompiler: it->toQString(), inputs: inputFiles.toQStringList(), |
1532 | outputs: outputs.toQStringList()); |
1533 | extraCompile.ParseFiles = _False; |
1534 | extraCompile.Filter = "" ; |
1535 | extraCompile.Guid = QString(_GUIDExtraCompilerFiles) + "-" + (*it); |
1536 | |
1537 | bool addOnInput = hasBuiltinCompiler(file: firstExpandedOutputFileName(extraCompilerName: *it)); |
1538 | if (!addOnInput) { |
1539 | // If the extra compiler has a variable_out set that is already handled |
1540 | // some other place, ignore it. |
1541 | const ProString &outputVar = project->first(variableName: ProKey(*it + ".variable_out" )); |
1542 | if (!outputVar.isEmpty() && otherFilters.contains(str: outputVar)) |
1543 | continue; |
1544 | |
1545 | QString tmp_out; |
1546 | if (!outputs.isEmpty()) |
1547 | tmp_out = outputs.first().toQString(); |
1548 | if (project->values(v: ProKey(*it + ".CONFIG" )).indexOf(t: "combine" ) != -1) { |
1549 | // Combined output, only one file result. Use .cbt file. |
1550 | extraCompile.addFile(filename: Option::fixPathToTargetOS( |
1551 | in: replaceExtraCompilerVariables(val: tmp_out + customBuildToolFilterFileSuffix, |
1552 | in: QString(), out: QString(), forShell: NoShell), fix_env: false)); |
1553 | } else if (!inputVars.isEmpty()) { |
1554 | // One output file per input |
1555 | const ProStringList &tmp_in = project->values(v: inputVars.first().toKey()); |
1556 | for (int i = 0; i < tmp_in.size(); ++i) { |
1557 | const QString &filename = tmp_in.at(i).toQString(); |
1558 | if (extraCompilerSources.contains(key: filename) && !otherFiltersContain(fileName: filename)) |
1559 | extraCompile.addFile(filename: Option::fixPathToTargetOS( |
1560 | in: replaceExtraCompilerVariables(val: filename, in: tmp_out, out: QString(), forShell: NoShell), fix_env: false)); |
1561 | } |
1562 | } |
1563 | } else { |
1564 | // In this case we the outputs have a built-in compiler, so we cannot add the custom |
1565 | // build steps there. So, we turn it around and add it to the input files instead, |
1566 | // provided that the input file variable is not handled already (those in otherFilters |
1567 | // are handled, so we avoid them). |
1568 | for (const ProString &inputVar : inputVars) { |
1569 | if (!otherFilters.contains(str: inputVar)) { |
1570 | const ProStringList &tmp_in = project->values(v: inputVar.toKey()); |
1571 | for (int i = 0; i < tmp_in.size(); ++i) { |
1572 | const QString &filename = tmp_in.at(i).toQString(); |
1573 | if (extraCompilerSources.contains(key: filename) && !otherFiltersContain(fileName: filename)) |
1574 | extraCompile.addFile(filename: Option::fixPathToTargetOS( |
1575 | in: replaceExtraCompilerVariables(val: filename, in: QString(), out: QString(), forShell: NoShell), fix_env: false)); |
1576 | } |
1577 | } |
1578 | } |
1579 | } |
1580 | extraCompile.Project = this; |
1581 | extraCompile.Config = &(vcProject.Configuration); |
1582 | |
1583 | vcProject.ExtraCompilersFiles.append(t: extraCompile); |
1584 | } |
1585 | } |
1586 | |
1587 | bool VcprojGenerator::otherFiltersContain(const QString &fileName) const |
1588 | { |
1589 | auto filterFileMatches = [&fileName] (const VCFilterFile &ff) |
1590 | { |
1591 | return ff.file == fileName; |
1592 | }; |
1593 | for (const VCFilter *filter : { &vcProject.RootFiles, |
1594 | &vcProject.SourceFiles, |
1595 | &vcProject.HeaderFiles, |
1596 | &vcProject.GeneratedFiles, |
1597 | &vcProject.LexYaccFiles, |
1598 | &vcProject.TranslationFiles, |
1599 | &vcProject.FormFiles, |
1600 | &vcProject.ResourceFiles, |
1601 | &vcProject.DeploymentFiles, |
1602 | &vcProject.DistributionFiles}) { |
1603 | if (std::any_of(first: filter->Files.cbegin(), last: filter->Files.cend(), pred: filterFileMatches)) |
1604 | return true; |
1605 | } |
1606 | return false; |
1607 | } |
1608 | |
1609 | // ------------------------------------------------------------------------------------------------ |
1610 | // ------------------------------------------------------------------------------------------------ |
1611 | |
1612 | VCProjectWriter *VcprojGenerator::createProjectWriter() |
1613 | { |
1614 | return new VCProjectWriter; |
1615 | } |
1616 | |
1617 | QString VcprojGenerator::( |
1618 | const QString &var, const QStringList &in, const QStringList &out, ReplaceFor forShell) |
1619 | { |
1620 | QString ret = MakefileGenerator::replaceExtraCompilerVariables(var, in, out, forShell); |
1621 | |
1622 | ProStringList &defines = project->values(v: "VCPROJ_MAKEFILE_DEFINES" ); |
1623 | if(defines.isEmpty()) |
1624 | defines.append(t: varGlue(var: "PRL_EXPORT_DEFINES" ,before: " -D" ,glue: " -D" ,after: "" ) + |
1625 | varGlue(var: "DEFINES" ,before: " -D" ,glue: " -D" ,after: "" )); |
1626 | ret.replace(before: QLatin1String("$(DEFINES)" ), after: defines.first().toQString()); |
1627 | |
1628 | ProStringList &incpath = project->values(v: "VCPROJ_MAKEFILE_INCPATH" ); |
1629 | if(incpath.isEmpty() && !this->var(var: "MSVCPROJ_INCPATH" ).isEmpty()) |
1630 | incpath.append(t: this->var(var: "MSVCPROJ_INCPATH" )); |
1631 | ret.replace(before: QLatin1String("$(INCPATH)" ), after: incpath.join(sep: ' ')); |
1632 | |
1633 | return ret; |
1634 | } |
1635 | |
1636 | bool VcprojGenerator::openOutput(QFile &file, const QString &/*build*/) const |
1637 | { |
1638 | ProString fileName = file.fileName(); |
1639 | ProString extension = project->first(variableName: "TEMPLATE" ) == "vcsubdirs" |
1640 | ? project->first(variableName: "VCSOLUTION_EXTENSION" ) : project->first(variableName: "VCPROJ_EXTENSION" ); |
1641 | if (!fileName.endsWith(sub: extension)) { |
1642 | if (fileName.isEmpty()) { |
1643 | fileName = !project->first(variableName: "MAKEFILE" ).isEmpty() |
1644 | ? project->first(variableName: "MAKEFILE" ) : project->first(variableName: "TARGET" ); |
1645 | } |
1646 | file.setFileName(fileName + extension); |
1647 | } |
1648 | return Win32MakefileGenerator::openOutput(file, build: QString()); |
1649 | } |
1650 | |
1651 | QT_END_NAMESPACE |
1652 | |