1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the qmake application of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU
19** General Public License version 3 as published by the Free Software
20** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21** included in the packaging of this file. Please review the following
22** information to ensure the GNU General Public License requirements will
23** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24**
25** $QT_END_LICENSE$
26**
27****************************************************************************/
28
29#include "msvc_nmake.h"
30#include "option.h"
31
32#include <qregularexpression.h>
33#include <qdir.h>
34#include <qdiriterator.h>
35#include <qset.h>
36
37#include <time.h>
38
39QT_BEGIN_NAMESPACE
40
41bool
42NmakeMakefileGenerator::writeMakefile(QTextStream &t)
43{
44 writeHeader(t);
45 if (writeDummyMakefile(t))
46 return true;
47
48 if(project->first("TEMPLATE") == "app" ||
49 project->first("TEMPLATE") == "lib" ||
50 project->first("TEMPLATE") == "aux") {
51 writeNmakeParts(t);
52 return MakefileGenerator::writeMakefile(t);
53 }
54 else if(project->first("TEMPLATE") == "subdirs") {
55 writeSubDirs(t);
56 return true;
57 }
58 return false;
59}
60
61void NmakeMakefileGenerator::writeSubMakeCall(QTextStream &t, const QString &callPrefix,
62 const QString &makeArguments)
63{
64 // Pass MAKEFLAGS as environment variable to sub-make calls.
65 // Unlike other make tools nmake doesn't do this automatically.
66 t << "\n\t@set MAKEFLAGS=$(MAKEFLAGS)";
67 Win32MakefileGenerator::writeSubMakeCall(t, callPrefix, makeArguments);
68}
69
70ProStringList NmakeMakefileGenerator::extraSubTargetDependencies()
71{
72 return { "$(MAKEFILE)" };
73}
74
75QString NmakeMakefileGenerator::defaultInstall(const QString &t)
76{
77 QString ret = Win32MakefileGenerator::defaultInstall(t);
78 if (ret.isEmpty())
79 return ret;
80
81 const QString root = installRoot();
82 ProStringList &uninst = project->values(ProKey(t + ".uninstall"));
83 QString targetdir = fileFixify(project->first(ProKey(t + ".path")).toQString(), FileFixifyAbsolute);
84 if(targetdir.right(1) != Option::dir_sep)
85 targetdir += Option::dir_sep;
86
87 if (project->isActiveConfig("debug_info")) {
88 if (t == "dlltarget" || project->values(ProKey(t + ".CONFIG")).indexOf("no_dll") == -1) {
89 const QFileInfo targetFileInfo(project->first("DESTDIR") + project->first("TARGET")
90 + project->first("TARGET_EXT"));
91 const QString pdb_target = targetFileInfo.completeBaseName() + ".pdb";
92 QString src_targ = (project->isEmpty("DESTDIR") ? QString("$(DESTDIR)") : project->first("DESTDIR")) + pdb_target;
93 QString dst_targ = filePrefixRoot(root, fileFixify(targetdir + pdb_target, FileFixifyAbsolute));
94 if(!ret.isEmpty())
95 ret += "\n\t";
96 ret += QString("-$(INSTALL_FILE) ") + escapeFilePath(src_targ) + ' ' + escapeFilePath(dst_targ);
97 if(!uninst.isEmpty())
98 uninst.append("\n\t");
99 uninst.append("-$(DEL_FILE) " + escapeFilePath(dst_targ));
100 }
101 }
102
103 return ret;
104}
105
106QStringList &NmakeMakefileGenerator::findDependencies(const QString &file)
107{
108 QStringList &aList = MakefileGenerator::findDependencies(file);
109 for(QStringList::Iterator it = Option::cpp_ext.begin(); it != Option::cpp_ext.end(); ++it) {
110 if(file.endsWith(*it)) {
111 if(!precompObj.isEmpty() && !aList.contains(precompObj))
112 aList += precompObj;
113 break;
114 }
115 }
116 for (QStringList::Iterator it = Option::c_ext.begin(); it != Option::c_ext.end(); ++it) {
117 if (file.endsWith(*it)) {
118 if (!precompObjC.isEmpty() && !aList.contains(precompObjC))
119 aList += precompObjC;
120 break;
121 }
122 }
123 return aList;
124}
125
126void NmakeMakefileGenerator::writeNmakeParts(QTextStream &t)
127{
128 writeStandardParts(t);
129
130 // precompiled header
131 if(usePCH) {
132 QString precompRule = QString("-c -Yc -Fp%1 -Fo%2")
133 .arg(escapeFilePath(precompPch), escapeFilePath(precompObj));
134 t << escapeDependencyPath(precompObj) << ": " << escapeDependencyPath(precompH) << ' '
135 << finalizeDependencyPaths(findDependencies(precompH)).join(" \\\n\t\t")
136 << "\n\t$(CXX) " + precompRule +" $(CXXFLAGS) $(INCPATH) -TP "
137 << escapeFilePath(precompH) << Qt::endl << Qt::endl;
138 }
139 if (usePCHC) {
140 QString precompRuleC = QString("-c -Yc -Fp%1 -Fo%2")
141 .arg(escapeFilePath(precompPchC), escapeFilePath(precompObjC));
142 t << escapeDependencyPath(precompObjC) << ": " << escapeDependencyPath(precompH) << ' '
143 << finalizeDependencyPaths(findDependencies(precompH)).join(" \\\n\t\t")
144 << "\n\t$(CC) " + precompRuleC +" $(CFLAGS) $(INCPATH) -TC "
145 << escapeFilePath(precompH) << Qt::endl << Qt::endl;
146 }
147}
148
149QString NmakeMakefileGenerator::var(const ProKey &value) const
150{
151 if (usePCH || usePCHC) {
152 const bool isRunC = (value == "QMAKE_RUN_CC_IMP_BATCH"
153 || value == "QMAKE_RUN_CC_IMP"
154 || value == "QMAKE_RUN_CC");
155 const bool isRunCpp = (value == "QMAKE_RUN_CXX_IMP_BATCH"
156 || value == "QMAKE_RUN_CXX_IMP"
157 || value == "QMAKE_RUN_CXX");
158 if ((isRunCpp && usePCH) || (isRunC && usePCHC)) {
159 QString precompH_f = escapeFilePath(fileFixify(precompH, FileFixifyBackwards));
160 QString precompRule = QString("-c -FI%1 -Yu%2 -Fp%3")
161 .arg(precompH_f, precompH_f, escapeFilePath(isRunC ? precompPchC : precompPch));
162 // ### For clang_cl 8 we force inline methods to be compiled here instead
163 // linking them from a pch.o file. We do this by pretending we are also doing
164 // the pch.o generation step.
165 if (project->isActiveConfig("clang_cl"))
166 precompRule += QString(" -Xclang -building-pch-with-obj");
167 QString p = MakefileGenerator::var(value);
168 p.replace(QLatin1String("-c"), precompRule);
169 return p;
170 }
171 }
172
173 // Normal val
174 return MakefileGenerator::var(value);
175}
176
177void NmakeMakefileGenerator::init()
178{
179 /* this should probably not be here, but I'm using it to wrap the .t files */
180 if(project->first("TEMPLATE") == "app")
181 project->values("QMAKE_APP_FLAG").append("1");
182 else if(project->first("TEMPLATE") == "lib")
183 project->values("QMAKE_LIB_FLAG").append("1");
184 else if(project->first("TEMPLATE") == "subdirs") {
185 MakefileGenerator::init();
186 if(project->values("MAKEFILE").isEmpty())
187 project->values("MAKEFILE").append("Makefile");
188 return;
189 }
190
191 processVars();
192
193 project->values("LIBS") += project->values("RES_FILE");
194
195 if (!project->values("DEF_FILE").isEmpty()) {
196 QString defFileName = fileFixify(project->first("DEF_FILE").toQString());
197 project->values("QMAKE_LFLAGS").append(QString("/DEF:") + escapeFilePath(defFileName));
198 }
199
200 // set /VERSION for EXE/DLL header
201 ProString major_minor = project->first("VERSION_PE_HEADER");
202 if (major_minor.isEmpty()) {
203 ProString version = project->first("VERSION");
204 if (!version.isEmpty()) {
205 int firstDot = version.indexOf(".");
206 int secondDot = version.indexOf(".", firstDot + 1);
207 major_minor = version.left(secondDot);
208 }
209 }
210 if (!major_minor.isEmpty())
211 project->values("QMAKE_LFLAGS").append("/VERSION:" + major_minor);
212
213 if (project->isEmpty("QMAKE_LINK_O_FLAG"))
214 project->values("QMAKE_LINK_O_FLAG").append("/OUT:");
215
216 // Base class init!
217 MakefileGenerator::init();
218
219 // Setup PCH variables
220 precompH = project->first("PRECOMPILED_HEADER").toQString();
221 usePCH = !precompH.isEmpty() && project->isActiveConfig("precompile_header");
222 usePCHC = !precompH.isEmpty() && project->isActiveConfig("precompile_header_c");
223 if (usePCH) {
224 // Created files
225 precompObj = var("PRECOMPILED_DIR") + project->first("TARGET") + "_pch" + Option::obj_ext;
226 precompPch = var("PRECOMPILED_DIR") + project->first("TARGET") + "_pch.pch";
227 // Add linking of precompObj (required for whole precompiled classes)
228 // ### For clang_cl we currently let inline methods be generated in the normal objects,
229 // since the PCH object is buggy (as of clang 8.0.0)
230 if (!project->isActiveConfig("clang_cl"))
231 project->values("OBJECTS") += precompObj;
232 // Add pch file to cleanup
233 project->values("QMAKE_CLEAN") += precompPch;
234 // Return to variable pool
235 project->values("PRECOMPILED_OBJECT") = ProStringList(precompObj);
236 project->values("PRECOMPILED_PCH") = ProStringList(precompPch);
237 }
238 if (usePCHC) {
239 precompObjC = var("PRECOMPILED_DIR") + project->first("TARGET") + "_pch_c" + Option::obj_ext;
240 precompPchC = var("PRECOMPILED_DIR") + project->first("TARGET") + "_pch_c.pch";
241 if (!project->isActiveConfig("clang_cl"))
242 project->values("OBJECTS") += precompObjC;
243 project->values("QMAKE_CLEAN") += precompPchC;
244 project->values("PRECOMPILED_OBJECT_C") = ProStringList(precompObjC);
245 project->values("PRECOMPILED_PCH_C") = ProStringList(precompPchC);
246 }
247
248 const QFileInfo targetFileInfo(project->first("DESTDIR") + project->first("TARGET")
249 + project->first("TARGET_EXT"));
250 const ProString targetBase = targetFileInfo.path() + '/' + targetFileInfo.completeBaseName();
251 if (project->first("TEMPLATE") == "lib" && project->isActiveConfig("shared")) {
252 project->values("QMAKE_CLEAN").append(targetBase + ".exp");
253 project->values("QMAKE_DISTCLEAN").append(targetBase + ".lib");
254 }
255 if (project->isActiveConfig("debug_info")) {
256 QString pdbfile;
257 QString distPdbFile = targetBase + ".pdb";
258 if (project->isActiveConfig("staticlib")) {
259 // For static libraries, the compiler's pdb file and the dist pdb file are the same.
260 pdbfile = distPdbFile;
261 } else {
262 // Use $${TARGET}.vc.pdb in the OBJECTS_DIR for the compiler and
263 // $${TARGET}.pdb (the default) for the linker.
264 pdbfile = var("OBJECTS_DIR") + project->first("TARGET") + ".vc.pdb";
265 }
266 QString escapedPdbFile = escapeFilePath(pdbfile);
267 project->values("QMAKE_CFLAGS").append("/Fd" + escapedPdbFile);
268 project->values("QMAKE_CXXFLAGS").append("/Fd" + escapedPdbFile);
269 project->values("QMAKE_CLEAN").append(pdbfile);
270 project->values("QMAKE_DISTCLEAN").append(distPdbFile);
271 }
272 if (project->isActiveConfig("debug")) {
273 project->values("QMAKE_CLEAN").append(targetBase + ".ilk");
274 project->values("QMAKE_CLEAN").append(targetBase + ".idb");
275 }
276
277 if (project->values("QMAKE_APP_FLAG").isEmpty() && project->isActiveConfig("dll")) {
278 ProStringList &defines = project->values("DEFINES");
279 if (!defines.contains("_WINDLL"))
280 defines.append("_WINDLL");
281 }
282}
283
284QStringList NmakeMakefileGenerator::sourceFilesForImplicitRulesFilter()
285{
286 QStringList filter;
287 const QChar wildcard = QLatin1Char('*');
288 for (const QString &ext : qAsConst(Option::c_ext))
289 filter << wildcard + ext;
290 for (const QString &ext : qAsConst(Option::cpp_ext))
291 filter << wildcard + ext;
292 return filter;
293}
294
295void NmakeMakefileGenerator::writeImplicitRulesPart(QTextStream &t)
296{
297 t << "####### Implicit rules\n\n";
298
299 t << ".SUFFIXES:";
300 for(QStringList::Iterator cit = Option::c_ext.begin(); cit != Option::c_ext.end(); ++cit)
301 t << " " << (*cit);
302 for(QStringList::Iterator cppit = Option::cpp_ext.begin(); cppit != Option::cpp_ext.end(); ++cppit)
303 t << " " << (*cppit);
304 t << Qt::endl << Qt::endl;
305
306 bool useInferenceRules = !project->isActiveConfig("no_batch");
307 QSet<QString> source_directories;
308 if (useInferenceRules) {
309 source_directories.insert(".");
310 static const char * const directories[] = { "UI_SOURCES_DIR", "UI_DIR", nullptr };
311 for (int y = 0; directories[y]; y++) {
312 QString dirTemp = project->first(directories[y]).toQString();
313 if (dirTemp.endsWith("\\"))
314 dirTemp.truncate(dirTemp.length()-1);
315 if(!dirTemp.isEmpty())
316 source_directories.insert(dirTemp);
317 }
318 static const char * const srcs[] = { "SOURCES", "GENERATED_SOURCES", nullptr };
319 for (int x = 0; srcs[x]; x++) {
320 const ProStringList &l = project->values(srcs[x]);
321 for (ProStringList::ConstIterator sit = l.begin(); sit != l.end(); ++sit) {
322 QString sep = "\\";
323 if((*sit).indexOf(sep) == -1)
324 sep = "/";
325 QString dir = (*sit).toQString().section(sep, 0, -2);
326 if (!dir.isEmpty())
327 source_directories.insert(dir);
328 }
329 }
330
331 // nmake's inference rules might pick up the wrong files when encountering source files with
332 // the same name in different directories. In this situation, turn inference rules off.
333 QHash<QString, QString> fileNames;
334 bool duplicatesFound = false;
335 const QStringList sourceFilesFilter = sourceFilesForImplicitRulesFilter();
336 QStringList fixifiedSourceDirs = fileFixify(QList<QString>(source_directories.constBegin(), source_directories.constEnd()), FileFixifyAbsolute);
337 fixifiedSourceDirs.removeDuplicates();
338 for (const QString &sourceDir : qAsConst(fixifiedSourceDirs)) {
339 QDirIterator dit(sourceDir, sourceFilesFilter, QDir::Files | QDir::NoDotAndDotDot);
340 while (dit.hasNext()) {
341 dit.next();
342 const QFileInfo fi = dit.fileInfo();
343 QString &duplicate = fileNames[fi.completeBaseName()];
344 if (duplicate.isNull()) {
345 duplicate = fi.filePath();
346 } else {
347 warn_msg(WarnLogic, "%s conflicts with %s", qPrintable(duplicate),
348 qPrintable(fi.filePath()));
349 duplicatesFound = true;
350 }
351 }
352 }
353 if (duplicatesFound) {
354 useInferenceRules = false;
355 warn_msg(WarnLogic, "Automatically turning off nmake's inference rules. (CONFIG += no_batch)");
356 }
357 }
358
359 if (useInferenceRules) {
360 // Batchmode doesn't use the non implicit rules QMAKE_RUN_CXX & QMAKE_RUN_CC
361 project->variables().remove("QMAKE_RUN_CXX");
362 project->variables().remove("QMAKE_RUN_CC");
363
364 for (const QString &sourceDir : qAsConst(source_directories)) {
365 if (sourceDir.isEmpty())
366 continue;
367 QString objDir = var("OBJECTS_DIR");
368 if (objDir == ".\\")
369 objDir = "";
370 for(QStringList::Iterator cppit = Option::cpp_ext.begin(); cppit != Option::cpp_ext.end(); ++cppit)
371 t << '{' << escapeDependencyPath(sourceDir) << '}' << (*cppit)
372 << '{' << escapeDependencyPath(objDir) << '}' << Option::obj_ext << "::\n\t"
373 << var("QMAKE_RUN_CXX_IMP_BATCH").replace(QRegularExpression("\\$@"), fileVar("OBJECTS_DIR"))
374 << "\n\t$<\n<<\n\n";
375 for(QStringList::Iterator cit = Option::c_ext.begin(); cit != Option::c_ext.end(); ++cit)
376 t << '{' << escapeDependencyPath(sourceDir) << '}' << (*cit)
377 << '{' << escapeDependencyPath(objDir) << '}' << Option::obj_ext << "::\n\t"
378 << var("QMAKE_RUN_CC_IMP_BATCH").replace(QRegularExpression("\\$@"), fileVar("OBJECTS_DIR"))
379 << "\n\t$<\n<<\n\n";
380 }
381 } else {
382 for(QStringList::Iterator cppit = Option::cpp_ext.begin(); cppit != Option::cpp_ext.end(); ++cppit)
383 t << (*cppit) << Option::obj_ext << ":\n\t" << var("QMAKE_RUN_CXX_IMP") << Qt::endl << Qt::endl;
384 for(QStringList::Iterator cit = Option::c_ext.begin(); cit != Option::c_ext.end(); ++cit)
385 t << (*cit) << Option::obj_ext << ":\n\t" << var("QMAKE_RUN_CC_IMP") << Qt::endl << Qt::endl;
386 }
387
388}
389
390void NmakeMakefileGenerator::writeBuildRulesPart(QTextStream &t)
391{
392 const ProString templateName = project->first("TEMPLATE");
393
394 t << "first: all\n";
395 t << "all: " << escapeDependencyPath(fileFixify(Option::output.fileName()))
396 << ' ' << depVar("ALL_DEPS") << ' ' << depVar("DEST_TARGET") << "\n\n";
397 t << depVar("DEST_TARGET") << ": "
398 << depVar("PRE_TARGETDEPS") << " $(OBJECTS) " << depVar("POST_TARGETDEPS");
399 if (templateName == "aux") {
400 t << "\n\n";
401 return;
402 }
403
404 if(!project->isEmpty("QMAKE_PRE_LINK"))
405 t << "\n\t" <<var("QMAKE_PRE_LINK");
406 if(project->isActiveConfig("staticlib")) {
407 t << "\n\t$(LIBAPP) $(LIBFLAGS) " << var("QMAKE_LINK_O_FLAG") << "$(DESTDIR_TARGET) @<<\n\t ";
408 writeResponseFileFiles(t, project->values("OBJECTS"));
409 t << "<<";
410 } else {
411 const bool embedManifest = ((templateName == "app" && project->isActiveConfig("embed_manifest_exe"))
412 || (templateName == "lib" && project->isActiveConfig("embed_manifest_dll")
413 && !(project->isActiveConfig("plugin") && project->isActiveConfig("no_plugin_manifest"))
414 ));
415 if (embedManifest) {
416 bool generateManifest = false;
417 const QString target = var("DEST_TARGET");
418 QString manifest = project->first("QMAKE_MANIFEST").toQString();
419 QString extraLFlags;
420 const bool linkerSupportsEmbedding = (msvcVersion() >= 1200);
421 if (manifest.isEmpty()) {
422 generateManifest = true;
423 if (linkerSupportsEmbedding) {
424 extraLFlags = "/MANIFEST:embed";
425 } else {
426 manifest = target + ".embed.manifest";
427 extraLFlags += "/MANIFEST /MANIFESTFILE:" + escapeFilePath(manifest);
428 project->values("QMAKE_CLEAN") << manifest;
429 }
430 } else {
431 manifest = fileFixify(manifest);
432 if (linkerSupportsEmbedding)
433 extraLFlags = "/MANIFEST:embed /MANIFESTINPUT:" + escapeFilePath(manifest);
434 }
435
436 const QString resourceId = (templateName == "app") ? "1" : "2";
437 const bool incrementalLinking = project->values("QMAKE_LFLAGS").toQStringList().filter(QRegularExpression("(/|-)INCREMENTAL:NO")).isEmpty();
438 if (incrementalLinking && !linkerSupportsEmbedding) {
439 // Link a resource that contains the manifest without modifying the exe/dll after linking.
440
441 QString manifest_rc = target + "_manifest.rc";
442 QString manifest_res = target + "_manifest.res";
443 project->values("QMAKE_CLEAN") << manifest_rc << manifest_res;
444 manifest_rc = escapeFilePath(manifest_rc);
445 manifest_res = escapeFilePath(manifest_res);
446
447 t << "\n\techo " << resourceId
448 << " /* CREATEPROCESS_MANIFEST_RESOURCE_ID */ 24 /* RT_MANIFEST */ "
449 << cQuoted(manifest) << '>' << manifest_rc;
450
451 if (generateManifest) {
452 manifest = escapeFilePath(manifest);
453 QString manifest_bak = escapeFilePath(target + "_manifest.bak");
454 project->values("QMAKE_CLEAN") << manifest_bak;
455 t << "\n\tif not exist $(DESTDIR_TARGET) if exist " << manifest
456 << " del " << manifest;
457 t << "\n\tif exist " << manifest << " copy /Y " << manifest << ' ' << manifest_bak;
458 const QString extraInlineFileContent = "\n!IF EXIST(" + manifest_res + ")\n" + manifest_res + "\n!ENDIF";
459 t << "\n\t";
460 writeLinkCommand(t, extraLFlags, extraInlineFileContent);
461 t << "\n\tif exist " << manifest_bak << " fc /b " << manifest << ' ' << manifest_bak << " >NUL || del " << manifest_bak;
462 t << "\n\tif not exist " << manifest_bak << " rc.exe /fo" << manifest_res << ' ' << manifest_rc;
463 t << "\n\tif not exist " << manifest_bak << ' ';
464 writeLinkCommand(t, extraLFlags, manifest_res);
465 t << "\n\tif exist " << manifest_bak << " del " << manifest_bak;
466 } else {
467 t << "\n\trc.exe /fo" << manifest_res << " " << manifest_rc;
468 t << "\n\t";
469 writeLinkCommand(t, extraLFlags, manifest_res);
470 }
471 } else {
472 // directly embed the manifest in the executable after linking
473 t << "\n\t";
474 writeLinkCommand(t, extraLFlags);
475 if (!linkerSupportsEmbedding) {
476 t << "\n\tmt.exe /nologo /manifest " << escapeFilePath(manifest)
477 << " /outputresource:$(DESTDIR_TARGET);" << resourceId;
478 }
479 }
480 } else {
481 t << "\n\t";
482 writeLinkCommand(t);
483 }
484 }
485 if(!project->isEmpty("QMAKE_POST_LINK")) {
486 t << "\n\t" << var("QMAKE_POST_LINK");
487 }
488 t << Qt::endl;
489}
490
491void NmakeMakefileGenerator::writeLinkCommand(QTextStream &t, const QString &extraFlags, const QString &extraInlineFileContent)
492{
493 t << "$(LINKER) $(LFLAGS)";
494 if (!extraFlags.isEmpty())
495 t << ' ' << extraFlags;
496 t << " " << var("QMAKE_LINK_O_FLAG") << "$(DESTDIR_TARGET) @<<\n";
497 writeResponseFileFiles(t, project->values("OBJECTS"));
498 t << "$(LIBS)\n";
499 if (!extraInlineFileContent.isEmpty())
500 t << extraInlineFileContent << '\n';
501 t << "<<";
502}
503
504void NmakeMakefileGenerator::writeResponseFileFiles(QTextStream &t, const ProStringList &files)
505{
506 // Add line breaks in file lists in reponse files to work around LNK1170.
507 // The actual line length limit is 131070, but let's use a smaller limit
508 // in case other tools are similarly hampered.
509 const int maxLineLength = 1000;
510 int len = 0;
511 for (const ProString &file : files) {
512 const ProString escapedFilePath = escapeFilePath(file);
513 if (len) {
514 if (len + escapedFilePath.length() > maxLineLength) {
515 t << '\n';
516 len = 0;
517 } else {
518 t << ' ';
519 len++;
520 }
521 }
522 t << escapedFilePath;
523 len += escapedFilePath.length();
524 }
525 t << '\n';
526}
527
528int NmakeMakefileGenerator::msvcVersion() const
529{
530 const int fallbackVersion = 800; // Visual Studio 2005
531 const QString ver = project->first(ProKey("MSVC_VER")).toQString();
532 bool ok;
533 float f = ver.toFloat(&ok);
534 return ok ? int(f * 100) : fallbackVersion;
535}
536
537QT_END_NAMESPACE
538