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 | |
14 | QT_BEGIN_NAMESPACE |
15 | |
16 | bool |
17 | NmakeMakefileGenerator::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 | |
36 | void 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 | |
45 | ProStringList NmakeMakefileGenerator::() |
46 | { |
47 | return { "$(MAKEFILE)" }; |
48 | } |
49 | |
50 | QString 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 | |
81 | QStringList &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 | |
101 | void 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 | |
124 | QString 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 | |
152 | void NmakeMakefileGenerator::suppressBuiltinRules(QTextStream &) const |
153 | { |
154 | } |
155 | |
156 | void 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 | |
263 | QStringList 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 | |
274 | void 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 | |
368 | void 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 ; |
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 = "\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 | |
469 | void NmakeMakefileGenerator::writeLinkCommand(QTextStream &t, const QString &, const QString &) |
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 | |
482 | void 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 | |
506 | int 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 | |
515 | QT_END_NAMESPACE |
516 | |