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 | |
39 | QT_BEGIN_NAMESPACE |
40 | |
41 | bool |
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 | |
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 | |
70 | ProStringList NmakeMakefileGenerator::() |
71 | { |
72 | return { "$(MAKEFILE)" }; |
73 | } |
74 | |
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 | |
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 | |
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 | |
149 | 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 | |
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 | |
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 | |
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(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 | |
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 ; |
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 = "\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 | |
491 | void NmakeMakefileGenerator::writeLinkCommand(QTextStream &t, const QString &, const QString &) |
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 | |
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 | |
528 | 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 | |