| 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 | |