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 <qregexp.h>
33 #include <qdir.h>
34 #include <qdiriterator.h>
35 #include <qset.h>
36 
37 #include <time.h>
38 
39 QT_BEGIN_NAMESPACE
40 
41 bool
writeMakefile(QTextStream & t)42 NmakeMakefileGenerator::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 
writeSubMakeCall(QTextStream & t,const QString & callPrefix,const QString & makeArguments)61 void 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 
extraSubTargetDependencies()70 ProStringList NmakeMakefileGenerator::extraSubTargetDependencies()
71 {
72     return { "$(MAKEFILE)" };
73 }
74 
defaultInstall(const QString & t)75 QString 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 
findDependencies(const QString & file)106 QStringList &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 
writeNmakeParts(QTextStream & t)126 void 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 
var(const ProKey & value) const149 QString 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 
init()177 void 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 
sourceFilesForImplicitRulesFilter()284 QStringList 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 
writeImplicitRulesPart(QTextStream & t)295 void 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(QRegExp("\\$@"), 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(QRegExp("\\$@"), 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 
writeBuildRulesPart(QTextStream & t)390 void 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(QRegExp("(/|-)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 
writeLinkCommand(QTextStream & t,const QString & extraFlags,const QString & extraInlineFileContent)491 void 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 
writeResponseFileFiles(QTextStream & t,const ProStringList & files)504 void 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 
msvcVersion() const528 int 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 
537 QT_END_NAMESPACE
538