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 <qdirlisting.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 using F = QDirListing::IteratorFlag;
318 for (const QString &sourceDir : std::as_const(t&: fixifiedSourceDirs)) {
319 for (const auto &dirEntry : QDirListing(sourceDir, sourceFilesFilter, F::FilesOnly | F::ResolveSymlinks)) {
320 QString &duplicate = fileNames[dirEntry.completeBaseName()];
321 if (duplicate.isNull()) {
322 duplicate = dirEntry.filePath();
323 } else {
324 warn_msg(t: WarnLogic, fmt: "%s conflicts with %s", qPrintable(duplicate),
325 qPrintable(dirEntry.filePath()));
326 duplicatesFound = true;
327 }
328 }
329 }
330 if (duplicatesFound) {
331 useInferenceRules = false;
332 warn_msg(t: WarnLogic, fmt: "Automatically turning off nmake's inference rules. (CONFIG += no_batch)");
333 }
334 }
335
336 if (useInferenceRules) {
337 // Batchmode doesn't use the non implicit rules QMAKE_RUN_CXX & QMAKE_RUN_CC
338 project->variables().remove(key: "QMAKE_RUN_CXX");
339 project->variables().remove(key: "QMAKE_RUN_CC");
340
341 for (const QString &sourceDir : std::as_const(t&: source_directories)) {
342 if (sourceDir.isEmpty())
343 continue;
344 QString objDir = var(value: "OBJECTS_DIR");
345 if (objDir == ".\\")
346 objDir = "";
347 for(QStringList::Iterator cppit = Option::cpp_ext.begin(); cppit != Option::cpp_ext.end(); ++cppit)
348 t << '{' << escapeDependencyPath(path: sourceDir) << '}' << (*cppit)
349 << '{' << escapeDependencyPath(path: objDir) << '}' << Option::obj_ext << "::\n\t"
350 << var(value: "QMAKE_RUN_CXX_IMP_BATCH").replace(re: QRegularExpression("\\$@"), after: fileVar(var: "OBJECTS_DIR"))
351 << "\n\t$<\n<<\n\n";
352 for(QStringList::Iterator cit = Option::c_ext.begin(); cit != Option::c_ext.end(); ++cit)
353 t << '{' << escapeDependencyPath(path: sourceDir) << '}' << (*cit)
354 << '{' << escapeDependencyPath(path: objDir) << '}' << Option::obj_ext << "::\n\t"
355 << var(value: "QMAKE_RUN_CC_IMP_BATCH").replace(re: QRegularExpression("\\$@"), after: fileVar(var: "OBJECTS_DIR"))
356 << "\n\t$<\n<<\n\n";
357 }
358 } else {
359 for(QStringList::Iterator cppit = Option::cpp_ext.begin(); cppit != Option::cpp_ext.end(); ++cppit)
360 t << (*cppit) << Option::obj_ext << ":\n\t" << var(value: "QMAKE_RUN_CXX_IMP") << Qt::endl << Qt::endl;
361 for(QStringList::Iterator cit = Option::c_ext.begin(); cit != Option::c_ext.end(); ++cit)
362 t << (*cit) << Option::obj_ext << ":\n\t" << var(value: "QMAKE_RUN_CC_IMP") << Qt::endl << Qt::endl;
363 }
364
365}
366
367void NmakeMakefileGenerator::writeBuildRulesPart(QTextStream &t)
368{
369 const ProString templateName = project->first(variableName: "TEMPLATE");
370
371 t << "first: all\n";
372 t << "all: " << escapeDependencyPath(path: fileFixify(file: Option::output.fileName()))
373 << ' ' << depVar(var: "ALL_DEPS") << ' ' << depVar(var: "DEST_TARGET") << "\n\n";
374 t << depVar(var: "DEST_TARGET") << ": "
375 << depVar(var: "PRE_TARGETDEPS") << " $(OBJECTS) " << depVar(var: "POST_TARGETDEPS");
376 if (templateName == "aux") {
377 t << "\n\n";
378 return;
379 }
380
381 if(!project->isEmpty(v: "QMAKE_PRE_LINK"))
382 t << "\n\t" <<var(value: "QMAKE_PRE_LINK");
383 if(project->isActiveConfig(config: "staticlib")) {
384 t << "\n\t$(LIBAPP) $(LIBFLAGS) " << var(value: "QMAKE_LINK_O_FLAG") << "$(DESTDIR_TARGET) @<<\n\t ";
385 writeResponseFileFiles(t, files: project->values(v: "OBJECTS"));
386 t << "<<";
387 } else {
388 const bool embedManifest = ((templateName == "app" && project->isActiveConfig(config: "embed_manifest_exe"))
389 || (templateName == "lib" && project->isActiveConfig(config: "embed_manifest_dll")
390 && !(project->isActiveConfig(config: "plugin") && project->isActiveConfig(config: "no_plugin_manifest"))
391 ));
392 if (embedManifest) {
393 bool generateManifest = false;
394 const QString target = var(value: "DEST_TARGET");
395 QString manifest = project->first(variableName: "QMAKE_MANIFEST").toQString();
396 QString extraLFlags;
397 const bool linkerSupportsEmbedding = (msvcVersion() >= 1200);
398 if (manifest.isEmpty()) {
399 generateManifest = true;
400 if (linkerSupportsEmbedding) {
401 extraLFlags = "/MANIFEST:embed";
402 } else {
403 manifest = target + ".embed.manifest";
404 extraLFlags += "/MANIFEST /MANIFESTFILE:" + escapeFilePath(path: manifest);
405 project->values(v: "QMAKE_CLEAN") << manifest;
406 }
407 } else {
408 manifest = fileFixify(file: manifest);
409 if (linkerSupportsEmbedding)
410 extraLFlags = "/MANIFEST:embed /MANIFESTINPUT:" + escapeFilePath(path: manifest);
411 }
412
413 const QString resourceId = (templateName == "app") ? "1" : "2";
414 const bool incrementalLinking = project->values(v: "QMAKE_LFLAGS").toQStringList().filter(re: QRegularExpression("(/|-)INCREMENTAL:NO")).isEmpty();
415 if (incrementalLinking && !linkerSupportsEmbedding) {
416 // Link a resource that contains the manifest without modifying the exe/dll after linking.
417
418 QString manifest_rc = target + "_manifest.rc";
419 QString manifest_res = target + "_manifest.res";
420 project->values(v: "QMAKE_CLEAN") << manifest_rc << manifest_res;
421 manifest_rc = escapeFilePath(path: manifest_rc);
422 manifest_res = escapeFilePath(path: manifest_res);
423
424 t << "\n\techo " << resourceId
425 << " /* CREATEPROCESS_MANIFEST_RESOURCE_ID */ 24 /* RT_MANIFEST */ "
426 << cQuoted(str: manifest) << '>' << manifest_rc;
427
428 if (generateManifest) {
429 manifest = escapeFilePath(path: manifest);
430 QString manifest_bak = escapeFilePath(path: target + "_manifest.bak");
431 project->values(v: "QMAKE_CLEAN") << manifest_bak;
432 t << "\n\tif not exist $(DESTDIR_TARGET) if exist " << manifest
433 << " del " << manifest;
434 t << "\n\tif exist " << manifest << " copy /Y " << manifest << ' ' << manifest_bak;
435 const QString extraInlineFileContent = "\n!IF EXIST(" + manifest_res + ")\n" + manifest_res + "\n!ENDIF";
436 t << "\n\t";
437 writeLinkCommand(t, extraFlags: extraLFlags, extraInlineFileContent);
438 t << "\n\tif exist " << manifest_bak << " fc /b " << manifest << ' ' << manifest_bak << " >NUL || del " << manifest_bak;
439 t << "\n\tif not exist " << manifest_bak << " rc.exe /fo" << manifest_res << ' ' << manifest_rc;
440 t << "\n\tif not exist " << manifest_bak << ' ';
441 writeLinkCommand(t, extraFlags: extraLFlags, extraInlineFileContent: manifest_res);
442 t << "\n\tif exist " << manifest_bak << " del " << manifest_bak;
443 } else {
444 t << "\n\trc.exe /fo" << manifest_res << " " << manifest_rc;
445 t << "\n\t";
446 writeLinkCommand(t, extraFlags: extraLFlags, extraInlineFileContent: manifest_res);
447 }
448 } else {
449 // directly embed the manifest in the executable after linking
450 t << "\n\t";
451 writeLinkCommand(t, extraFlags: extraLFlags);
452 if (!linkerSupportsEmbedding) {
453 t << "\n\tmt.exe /nologo /manifest " << escapeFilePath(path: manifest)
454 << " /outputresource:$(DESTDIR_TARGET);" << resourceId;
455 }
456 }
457 } else {
458 t << "\n\t";
459 writeLinkCommand(t);
460 }
461 }
462 if(!project->isEmpty(v: "QMAKE_POST_LINK")) {
463 t << "\n\t" << var(value: "QMAKE_POST_LINK");
464 }
465 t << Qt::endl;
466}
467
468void NmakeMakefileGenerator::writeLinkCommand(QTextStream &t, const QString &extraFlags, const QString &extraInlineFileContent)
469{
470 t << "$(LINKER) $(LFLAGS)";
471 if (!extraFlags.isEmpty())
472 t << ' ' << extraFlags;
473 t << " " << var(value: "QMAKE_LINK_O_FLAG") << "$(DESTDIR_TARGET) @<<\n";
474 writeResponseFileFiles(t, files: project->values(v: "OBJECTS"));
475 t << "$(LIBS)\n";
476 if (!extraInlineFileContent.isEmpty())
477 t << extraInlineFileContent << '\n';
478 t << "<<";
479}
480
481void NmakeMakefileGenerator::writeResponseFileFiles(QTextStream &t, const ProStringList &files)
482{
483 // Add line breaks in file lists in reponse files to work around LNK1170.
484 // The actual line length limit is 131070, but let's use a smaller limit
485 // in case other tools are similarly hampered.
486 const int maxLineLength = 1000;
487 int len = 0;
488 for (const ProString &file : files) {
489 const ProString escapedFilePath = escapeFilePath(path: file);
490 if (len) {
491 if (len + escapedFilePath.length() > maxLineLength) {
492 t << '\n';
493 len = 0;
494 } else {
495 t << ' ';
496 len++;
497 }
498 }
499 t << escapedFilePath;
500 len += escapedFilePath.length();
501 }
502 t << '\n';
503}
504
505int NmakeMakefileGenerator::msvcVersion() const
506{
507 const int fallbackVersion = 800; // Visual Studio 2005
508 const QString ver = project->first(variableName: ProKey("MSVC_VER")).toQString();
509 bool ok;
510 float f = ver.toFloat(ok: &ok);
511 return ok ? int(f * 100) : fallbackVersion;
512}
513
514QT_END_NAMESPACE
515

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

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