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 "mingw_make.h" |
5 | #include "option.h" |
6 | |
7 | #include <proitems.h> |
8 | |
9 | #include <qregularexpression.h> |
10 | #include <qdir.h> |
11 | #include <stdlib.h> |
12 | #include <time.h> |
13 | |
14 | QT_BEGIN_NAMESPACE |
15 | |
16 | QString MingwMakefileGenerator::escapeDependencyPath(const QString &path) const |
17 | { |
18 | QString ret = path; |
19 | ret.replace(c: '\\', after: "/" ); // ### this shouldn't be here |
20 | return MakefileGenerator::escapeDependencyPath(path: ret); |
21 | } |
22 | |
23 | ProString MingwMakefileGenerator::fixLibFlag(const ProString &lib) |
24 | { |
25 | if (lib.startsWith(sub: "-l" )) // Fallback for unresolved -l libs. |
26 | return QLatin1String("-l" ) + escapeFilePath(path: lib.mid(off: 2)); |
27 | if (lib.startsWith(sub: "-L" )) // Lib search path. Needed only by -l above. |
28 | return QLatin1String("-L" ) |
29 | + escapeFilePath(path: Option::fixPathToTargetOS(in: lib.mid(off: 2).toQString(), fix_env: false)); |
30 | if (lib.startsWith(sub: "lib" )) // Fallback for unresolved MSVC-style libs. |
31 | return QLatin1String("-l" ) + escapeFilePath(path: lib.mid(off: 3).toQString()); |
32 | return escapeFilePath(path: Option::fixPathToTargetOS(in: lib.toQString(), fix_env: false)); |
33 | } |
34 | |
35 | MakefileGenerator::LibFlagType |
36 | MingwMakefileGenerator::parseLibFlag(const ProString &flag, ProString *arg) |
37 | { |
38 | // Skip MSVC handling from Win32MakefileGenerator |
39 | return MakefileGenerator::parseLibFlag(flag, arg); |
40 | } |
41 | |
42 | bool MingwMakefileGenerator::processPrlFileBase(QString &origFile, QStringView origName, |
43 | QStringView fixedBase, int slashOff) |
44 | { |
45 | if (origName.startsWith(s: u"lib" )) { |
46 | QString newFixedBase = fixedBase.left(n: slashOff) + fixedBase.mid(pos: slashOff + 3); |
47 | if (Win32MakefileGenerator::processPrlFileBase(origFile, origName, |
48 | fixedBase: QStringView(newFixedBase), slashOff)) { |
49 | return true; |
50 | } |
51 | } |
52 | return Win32MakefileGenerator::processPrlFileBase(origFile, origName, fixedBase, slashOff); |
53 | } |
54 | |
55 | bool MingwMakefileGenerator::writeMakefile(QTextStream &t) |
56 | { |
57 | writeHeader(t); |
58 | if (writeDummyMakefile(t)) |
59 | return true; |
60 | |
61 | if(project->first(variableName: "TEMPLATE" ) == "app" || |
62 | project->first(variableName: "TEMPLATE" ) == "lib" || |
63 | project->first(variableName: "TEMPLATE" ) == "aux" ) { |
64 | if(project->isActiveConfig(config: "create_pc" ) && project->first(variableName: "TEMPLATE" ) == "lib" ) |
65 | writePkgConfigFile(); |
66 | writeMingwParts(t); |
67 | return MakefileGenerator::writeMakefile(t); |
68 | } |
69 | else if(project->first(variableName: "TEMPLATE" ) == "subdirs" ) { |
70 | writeSubDirs(t); |
71 | return true; |
72 | } |
73 | return false; |
74 | } |
75 | |
76 | QString MingwMakefileGenerator::installRoot() const |
77 | { |
78 | /* |
79 | We include a magic prefix on the path to bypass mingw-make's "helpful" |
80 | intervention in the environment, recognising variables that look like |
81 | paths and adding the msys system root as prefix, which we don't want. |
82 | Once this hack has smuggled INSTALL_ROOT into make's variable space, we |
83 | can trivially strip the magic prefix back off to get the path we meant. |
84 | */ |
85 | return QStringLiteral("$(INSTALL_ROOT:@msyshack@%=%)" ); |
86 | } |
87 | |
88 | void MingwMakefileGenerator::writeMingwParts(QTextStream &t) |
89 | { |
90 | writeStandardParts(t); |
91 | |
92 | if (!preCompHeaderOut.isEmpty()) { |
93 | QString = project->first(variableName: "PRECOMPILED_HEADER" ).toQString(); |
94 | QString = preCompHeaderOut + Option::dir_sep + "c" ; |
95 | t << escapeDependencyPath(path: cHeader) << ": " << escapeDependencyPath(path: header) << " " |
96 | << finalizeDependencyPaths(paths: findDependencies(file: header)).join(sep: " \\\n\t\t" ) |
97 | << "\n\t" << mkdir_p_asstring(dir: preCompHeaderOut) |
98 | << "\n\t$(CC) -x c-header -c $(CFLAGS) $(INCPATH) -o " << escapeFilePath(path: cHeader) |
99 | << ' ' << escapeFilePath(path: header) << Qt::endl << Qt::endl; |
100 | QString = preCompHeaderOut + Option::dir_sep + "c++" ; |
101 | t << escapeDependencyPath(path: cppHeader) << ": " << escapeDependencyPath(path: header) << " " |
102 | << finalizeDependencyPaths(paths: findDependencies(file: header)).join(sep: " \\\n\t\t" ) |
103 | << "\n\t" << mkdir_p_asstring(dir: preCompHeaderOut) |
104 | << "\n\t$(CXX) -x c++-header -c $(CXXFLAGS) $(INCPATH) -o " << escapeFilePath(path: cppHeader) |
105 | << ' ' << escapeFilePath(path: header) << Qt::endl << Qt::endl; |
106 | } |
107 | } |
108 | |
109 | void MingwMakefileGenerator::init() |
110 | { |
111 | /* this should probably not be here, but I'm using it to wrap the .t files */ |
112 | if(project->first(variableName: "TEMPLATE" ) == "app" ) |
113 | project->values(v: "QMAKE_APP_FLAG" ).append(t: "1" ); |
114 | else if(project->first(variableName: "TEMPLATE" ) == "lib" ) |
115 | project->values(v: "QMAKE_LIB_FLAG" ).append(t: "1" ); |
116 | else if(project->first(variableName: "TEMPLATE" ) == "subdirs" ) { |
117 | MakefileGenerator::init(); |
118 | if(project->values(v: "MAKEFILE" ).isEmpty()) |
119 | project->values(v: "MAKEFILE" ).append(t: "Makefile" ); |
120 | return; |
121 | } |
122 | |
123 | processVars(); |
124 | |
125 | project->values(v: "LIBS" ) += project->values(v: "RES_FILE" ); |
126 | |
127 | if (project->isActiveConfig(config: "dll" )) { |
128 | QString destDir = "" ; |
129 | if(!project->first(variableName: "DESTDIR" ).isEmpty()) |
130 | destDir = Option::fixPathToTargetOS(in: project->first(variableName: "DESTDIR" ) + Option::dir_sep, fix_env: false, canonical: false); |
131 | project->values(v: "MINGW_IMPORT_LIB" ).prepend(t: destDir + project->first(variableName: "LIB_TARGET" )); |
132 | project->values(v: "QMAKE_LFLAGS" ).append(t: QString("-Wl,--out-implib," ) + fileVar(var: "MINGW_IMPORT_LIB" )); |
133 | } |
134 | |
135 | if (!project->values(v: "DEF_FILE" ).isEmpty()) { |
136 | QString defFileName = fileFixify(file: project->first(variableName: "DEF_FILE" ).toQString()); |
137 | project->values(v: "QMAKE_LFLAGS" ).append(t: QString("-Wl," ) + escapeFilePath(path: defFileName)); |
138 | } |
139 | |
140 | if (project->isActiveConfig(config: "staticlib" ) && project->first(variableName: "TEMPLATE" ) == "lib" ) |
141 | project->values(v: "QMAKE_LFLAGS" ).append(t: "-static" ); |
142 | |
143 | MakefileGenerator::init(); |
144 | |
145 | // precomp |
146 | if (!project->first(variableName: "PRECOMPILED_HEADER" ).isEmpty() |
147 | && project->isActiveConfig(config: "precompile_header" )) { |
148 | QString = var(var: "PRECOMPILED_DIR" ) |
149 | + QFileInfo(project->first(variableName: "PRECOMPILED_HEADER" ).toQString()).fileName(); |
150 | preCompHeaderOut = preCompHeader + ".gch" ; |
151 | project->values(v: "QMAKE_CLEAN" ).append(t: preCompHeaderOut + Option::dir_sep + "c" ); |
152 | project->values(v: "QMAKE_CLEAN" ).append(t: preCompHeaderOut + Option::dir_sep + "c++" ); |
153 | |
154 | preCompHeader = escapeFilePath(path: preCompHeader); |
155 | project->values(v: "QMAKE_RUN_CC" ).clear(); |
156 | project->values(v: "QMAKE_RUN_CC" ).append(t: "$(CC) -c -include " + preCompHeader + |
157 | " $(CFLAGS) $(INCPATH) " + var(var: "QMAKE_CC_O_FLAG" ) + "$obj $src" ); |
158 | project->values(v: "QMAKE_RUN_CC_IMP" ).clear(); |
159 | project->values(v: "QMAKE_RUN_CC_IMP" ).append(t: "$(CC) -c -include " + preCompHeader + |
160 | " $(CFLAGS) $(INCPATH) " + var(var: "QMAKE_CC_O_FLAG" ) + "$@ $<" ); |
161 | project->values(v: "QMAKE_RUN_CXX" ).clear(); |
162 | project->values(v: "QMAKE_RUN_CXX" ).append(t: "$(CXX) -c -include " + preCompHeader + |
163 | " $(CXXFLAGS) $(INCPATH) " + var(var: "QMAKE_CC_O_FLAG" ) + "$obj $src" ); |
164 | project->values(v: "QMAKE_RUN_CXX_IMP" ).clear(); |
165 | project->values(v: "QMAKE_RUN_CXX_IMP" ).append(t: "$(CXX) -c -include " + preCompHeader + |
166 | " $(CXXFLAGS) $(INCPATH) " + var(var: "QMAKE_CC_O_FLAG" ) + "$@ $<" ); |
167 | } |
168 | |
169 | if(project->isActiveConfig(config: "dll" )) { |
170 | project->values(v: "QMAKE_DISTCLEAN" ).append(t: project->first(variableName: "MINGW_IMPORT_LIB" )); |
171 | } |
172 | } |
173 | |
174 | void MingwMakefileGenerator::writeIncPart(QTextStream &t) |
175 | { |
176 | t << "INCPATH = " ; |
177 | |
178 | const ProStringList &incs = project->values(v: "INCLUDEPATH" ); |
179 | QFile responseFile; |
180 | QTextStream responseStream; |
181 | QChar sep(' '); |
182 | int totalLength = std::accumulate(first: incs.constBegin(), last: incs.constEnd(), init: 0, |
183 | binary_op: [](int total, const ProString &inc) { |
184 | return total + inc.size() + 2; |
185 | }); |
186 | if (totalLength > project->intValue(v: "QMAKE_RESPONSEFILE_THRESHOLD" , defaultValue: 8000)) { |
187 | const QString fileName = createResponseFile(baseName: "incpath" , objList: incs, prefix: "-I" ); |
188 | if (!fileName.isEmpty()) { |
189 | t << '@' + fileName; |
190 | t << Qt::endl; |
191 | return; |
192 | } |
193 | } |
194 | for (const ProString &incit: std::as_const(t: incs)) { |
195 | QString inc = incit.toQString(); |
196 | inc.replace(re: QRegularExpression("\\\\$" ), after: "" ); |
197 | inc.replace(before: '\\', after: '/'); |
198 | t << "-I" << escapeFilePath(path: inc) << sep; |
199 | } |
200 | t << Qt::endl; |
201 | } |
202 | |
203 | void MingwMakefileGenerator::writeLibsPart(QTextStream &t) |
204 | { |
205 | if(project->isActiveConfig(config: "staticlib" ) && project->first(variableName: "TEMPLATE" ) == "lib" ) { |
206 | t << "LIB = " << var(var: "QMAKE_LIB" ) << Qt::endl; |
207 | } else { |
208 | t << "LINKER = " << var(var: "QMAKE_LINK" ) << Qt::endl; |
209 | t << "LFLAGS = " << var(var: "QMAKE_LFLAGS" ) << Qt::endl; |
210 | t << "LIBS = " |
211 | << fixLibFlags(var: "LIBS" ).join(sep: ' ') << ' ' |
212 | << fixLibFlags(var: "LIBS_PRIVATE" ).join(sep: ' ') << ' ' |
213 | << fixLibFlags(var: "QMAKE_LIBS" ).join(sep: ' ') << ' ' |
214 | << fixLibFlags(var: "QMAKE_LIBS_PRIVATE" ).join(sep: ' ') << Qt::endl; |
215 | } |
216 | } |
217 | |
218 | void MingwMakefileGenerator::writeObjectsPart(QTextStream &t) |
219 | { |
220 | linkerResponseFile = maybeCreateLinkerResponseFile(); |
221 | if (!linkerResponseFile.isValid()) { |
222 | objectsLinkLine = "$(OBJECTS)" ; |
223 | } else if (project->isActiveConfig(config: "staticlib" ) && project->first(variableName: "TEMPLATE" ) == "lib" ) { |
224 | // QMAKE_LIB is used for win32, including mingw, whereas QMAKE_AR is used on Unix. |
225 | QString ar_cmd = var(var: "QMAKE_LIB" ); |
226 | if (ar_cmd.isEmpty()) |
227 | ar_cmd = "ar -rc" ; |
228 | objectsLinkLine = ar_cmd + ' ' + var(var: "DEST_TARGET" ) + " @" |
229 | + escapeFilePath(path: linkerResponseFile.filePath); |
230 | } else { |
231 | objectsLinkLine = "@" + escapeFilePath(path: linkerResponseFile.filePath); |
232 | } |
233 | Win32MakefileGenerator::writeObjectsPart(t); |
234 | } |
235 | |
236 | void MingwMakefileGenerator::writeBuildRulesPart(QTextStream &t) |
237 | { |
238 | t << "first: all\n" ; |
239 | t << "all: " << escapeDependencyPath(path: fileFixify(file: Option::output.fileName())) |
240 | << ' ' << depVar(var: "ALL_DEPS" ) << ' ' << depVar(var: "DEST_TARGET" ) << "\n\n" ; |
241 | t << depVar(var: "DEST_TARGET" ) << ": " |
242 | << depVar(var: "PRE_TARGETDEPS" ) << " $(OBJECTS) " << depVar(var: "POST_TARGETDEPS" ); |
243 | if (project->first(variableName: "TEMPLATE" ) == "aux" ) { |
244 | t << "\n\n" ; |
245 | return; |
246 | } |
247 | |
248 | if(!project->isEmpty(v: "QMAKE_PRE_LINK" )) |
249 | t << "\n\t" <<var(var: "QMAKE_PRE_LINK" ); |
250 | if(project->isActiveConfig(config: "staticlib" ) && project->first(variableName: "TEMPLATE" ) == "lib" ) { |
251 | t << "\n\t-$(DEL_FILE) $(DESTDIR_TARGET) 2>" << var(var: "QMAKE_SHELL_NULL_DEVICE" ); |
252 | const ProString &objmax = project->first(variableName: "QMAKE_LINK_OBJECT_MAX" ); |
253 | if (objmax.isEmpty() || project->values(v: "OBJECTS" ).size() < objmax.toInt()) { |
254 | t << "\n\t$(LIB) $(DESTDIR_TARGET) " << objectsLinkLine << " " ; |
255 | } else { |
256 | t << "\n\t" << objectsLinkLine << " " ; |
257 | } |
258 | } else { |
259 | t << "\n\t$(LINKER) $(LFLAGS) " << var(var: "QMAKE_LINK_O_FLAG" ) << "$(DESTDIR_TARGET) " |
260 | << objectsLinkLine; |
261 | if (!linkerResponseFile.isValid() || linkerResponseFile.onlyObjects) |
262 | t << " $(LIBS)" ; |
263 | } |
264 | if(!project->isEmpty(v: "QMAKE_POST_LINK" )) |
265 | t << "\n\t" <<var(var: "QMAKE_POST_LINK" ); |
266 | t << Qt::endl; |
267 | } |
268 | |
269 | void MingwMakefileGenerator::writeRcFilePart(QTextStream &t) |
270 | { |
271 | const QString rc_file = fileFixify(file: project->first(variableName: "RC_FILE" ).toQString()); |
272 | |
273 | ProStringList rcIncPaths = project->values(v: "RC_INCLUDEPATH" ); |
274 | rcIncPaths.prepend(t: fileInfo(file: rc_file).path()); |
275 | QString incPathStr; |
276 | for (int i = 0; i < rcIncPaths.size(); ++i) { |
277 | const ProString &path = rcIncPaths.at(i); |
278 | if (path.isEmpty()) |
279 | continue; |
280 | incPathStr += QStringLiteral(" --include-dir=" ); |
281 | if (path != "." && QDir::isRelativePath(path: path.toQString())) |
282 | incPathStr += "./" ; |
283 | incPathStr += escapeFilePath(path); |
284 | } |
285 | |
286 | if (!rc_file.isEmpty()) { |
287 | |
288 | ProString defines = varGlue(var: "RC_DEFINES" , before: " -D" , glue: " -D" , after: "" ); |
289 | if (defines.isEmpty()) |
290 | defines = ProString(" $(DEFINES)" ); |
291 | |
292 | addSourceFile(rc_file, seek: QMakeSourceFileInfo::SEEK_DEPS); |
293 | const QStringList rcDeps = QStringList(rc_file) << dependencies(file: rc_file); |
294 | |
295 | t << escapeDependencyPath(path: var(var: "RES_FILE" )) << ": " |
296 | << escapeDependencyPaths(paths: rcDeps).join(sep: ' ') << "\n\t" |
297 | << var(var: "QMAKE_RC" ) << " -i " << escapeFilePath(path: rc_file) << " -o " << fileVar(var: "RES_FILE" ) |
298 | << incPathStr << defines << "\n\n" ; |
299 | } |
300 | } |
301 | |
302 | QStringList &MingwMakefileGenerator::findDependencies(const QString &file) |
303 | { |
304 | QStringList &aList = MakefileGenerator::findDependencies(file); |
305 | if (preCompHeaderOut.isEmpty()) |
306 | return aList; |
307 | for (QStringList::Iterator it = Option::c_ext.begin(); it != Option::c_ext.end(); ++it) { |
308 | if (file.endsWith(s: *it)) { |
309 | QString = preCompHeaderOut + Option::dir_sep + "c" ; |
310 | if (!aList.contains(str: cHeader)) |
311 | aList += cHeader; |
312 | break; |
313 | } |
314 | } |
315 | for (QStringList::Iterator it = Option::cpp_ext.begin(); it != Option::cpp_ext.end(); ++it) { |
316 | if (file.endsWith(s: *it)) { |
317 | QString = preCompHeaderOut + Option::dir_sep + "c++" ; |
318 | if (!aList.contains(str: cppHeader)) |
319 | aList += cppHeader; |
320 | break; |
321 | } |
322 | } |
323 | return aList; |
324 | } |
325 | |
326 | QT_END_NAMESPACE |
327 | |