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

source code of qtbase/qmake/generators/win32/msvc_nmake.cpp