/**************************************************************************** ** ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). ** Contact: Qt Software Information (qt-info@nokia.com) ** ** This file is part of the qmake application of the Qt Toolkit. ** ** Copyright (C) 2009 netlabs.org. OS/2 parts. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial Usage ** Licensees holding valid Qt Commercial licenses may use this file in ** accordance with the Qt Commercial License Agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and Nokia. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain ** additional rights. These rights are described in the Nokia Qt LGPL ** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this ** package. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3.0 as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU General Public License version 3.0 requirements will be ** met: http://www.gnu.org/copyleft/gpl.html. ** ** If you are unsure which license is appropriate for your use, please ** contact the sales department at qt-sales@nokia.com. ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "gnumake.h" #include "option.h" #include "meta.h" #include #include #include #include QT_BEGIN_NAMESPACE GNUMakefileGenerator::GNUMakefileGenerator() : Win32MakefileGenerator(), init_flag(false) { if (isDosLikeShell()) quote = "\""; else quote = "'"; } bool GNUMakefileGenerator::isDosLikeShell() const { #ifdef Q_OS_OS2 return Option::shellPath.isEmpty(); #else return Win32MakefileGenerator::isDosLikeShell(); #endif } QString GNUMakefileGenerator::escapeFilePath(const QString &path) const { QString ret = path; if (!isDosLikeShell()) { ret.remove('\"'); ret.replace('\\', "/"); ret.replace(' ', "\\ "); } else { ret.replace(QRegExp("\""), ""); ret.replace(QRegExp("[\\\\/]$"), ""); if (ret.contains(QRegExp("[ +&;%]"))) ret = quote + ret + quote; } return ret; } QString GNUMakefileGenerator::escapeDependencyPath(const QString &path) const { /* dependency escaping is always done Unix-way since this is a requirement * of GNU make which allows " and ' to be part of the file name */ QString ret = path; ret.remove('\"'); ret.replace(' ', "\\ "); return ret; } bool GNUMakefileGenerator::findLibraries() { QStringList &l = project->values("QMAKE_LIBS"); QList dirs; { QStringList &libpaths = project->values("QMAKE_LIBDIR"); for(QStringList::Iterator libpathit = libpaths.begin(); libpathit != libpaths.end(); ++libpathit) dirs.append(QMakeLocalFileName((*libpathit))); } QStringList::Iterator it = l.begin(); while (it != l.end()) { if ((*it).startsWith("-l")) { QString steam = (*it).mid(2), out; QString suffix; if (!project->isEmpty("QMAKE_" + steam.toUpper() + "_SUFFIX")) suffix = project->first("QMAKE_" + steam.toUpper() + "_SUFFIX"); for (QList::Iterator dir_it = dirs.begin(); dir_it != dirs.end(); ++dir_it) { QString extension; int ver = findHighestVersion((*dir_it).local(), steam, "dll.lib|lib"); if (ver != -1) extension += QString::number(ver); extension += suffix; if(QMakeMetaInfo::libExists((*dir_it).local() + Option::dir_sep + steam) || exists((*dir_it).local() + Option::dir_sep + steam + extension + ".lib") || exists((*dir_it).local() + Option::dir_sep + steam + extension + ".dll.lib")) { out = (*it) + extension; break; } } if (!out.isEmpty()) // We assume if it never finds it that its correct (*it) = out; } else if((*it).startsWith("-L")) { dirs.append(QMakeLocalFileName((*it).mid(2))); } ++it; } return true; } bool GNUMakefileGenerator::writeMakefile(QTextStream &t) { writeHeader(t); /* function to convert from DOS-like to Unix-like space escaping in file * names */ t << "q = $(subst %,\\%,$(subst ;,\\;,$(subst &,\\&," "$(subst +,\\+,$(subst $(space),\\ ,$(subst \",,$(1)))))))" << endl << endl; if(!project->values("QMAKE_FAILED_REQUIREMENTS").isEmpty()) { t << "all clean:" << "\n\t" << "@echo \"Some of the required modules (" << var("QMAKE_FAILED_REQUIREMENTS") << ") are not available.\"" << "\n\t" << "@echo \"Skipped.\"" << endl << endl; writeMakeQmake(t); return true; } if(project->first("TEMPLATE") == "app" || project->first("TEMPLATE") == "lib") { if(Option::mkfile::do_stub_makefile) { t << "QMAKE = " << (project->isEmpty("QMAKE_QMAKE") ? QString("qmake") : var("QMAKE_QMAKE")) << endl; QStringList &qut = project->values("QMAKE_EXTRA_TARGETS"); for(QStringList::ConstIterator it = qut.begin(); it != qut.end(); ++it) t << *it << " "; t << "first all clean install distclean uninstall: qmake" << endl << "qmake_all:" << endl; writeMakeQmake(t); if(project->isEmpty("QMAKE_NOFORCE")) t << "FORCE:" << endl << endl; return true; } writeGNUParts(t); return MakefileGenerator::writeMakefile(t); } else if(project->first("TEMPLATE") == "subdirs") { writeSubDirs(t); return true; } return false; } void GNUMakefileGenerator::writeGNUParts(QTextStream &t) { t << "QMAKESPECDIR = " << quote << specdir() << quote << endl << endl; writeStandardParts(t); if (!preCompHeaderOut.isEmpty()) { QString header = project->first("PRECOMPILED_HEADER"); QString cHeader = preCompHeaderOut + Option::dir_sep + "c"; t << escapeDependencyPath(cHeader) << ": " << escapeDependencyPath(header) << " " << escapeDependencyPaths(findDependencies(header)).join(" \\\n\t\t") << "\n\t" << mkdir_p_asstring(preCompHeaderOut) << "\n\t" << "$(CC) -x c-header -c $(CFLAGS) $(INCPATH) -o " << cHeader << " " << header << endl << endl; QString cppHeader = preCompHeaderOut + Option::dir_sep + "c++"; t << escapeDependencyPath(cppHeader) << ": " << escapeDependencyPath(header) << " " << escapeDependencyPaths(findDependencies(header)).join(" \\\n\t\t") << "\n\t" << mkdir_p_asstring(preCompHeaderOut) << "\n\t" << "$(CXX) -x c++-header -c $(CXXFLAGS) $(INCPATH) -o " << cppHeader << " " << header << endl << endl; } } void GNUMakefileGenerator::init() { if(init_flag) return; init_flag = true; if(project->first("TEMPLATE") == "app") { mode = App; project->values("QMAKE_APP_FLAG").append("1"); } else if(project->first("TEMPLATE") == "lib") { mode = project->isActiveConfig("staticlib") ? StaticLib : DLL; project->values("QMAKE_LIB_FLAG").append("1"); } else if(project->first("TEMPLATE") == "subdirs") { MakefileGenerator::init(); if(project->isEmpty("QMAKE_COPY_FILE")) project->values("QMAKE_COPY_FILE").append("$(COPY)"); if(project->isEmpty("QMAKE_COPY_DIR")) project->values("QMAKE_COPY_DIR").append("$(COPY)"); if(project->isEmpty("QMAKE_INSTALL_FILE")) project->values("QMAKE_INSTALL_FILE").append("$(COPY_FILE)"); if(project->isEmpty("QMAKE_INSTALL_PROGRAM")) project->values("QMAKE_INSTALL_PROGRAM").append("$(COPY_FILE)"); if(project->isEmpty("QMAKE_INSTALL_DIR")) project->values("QMAKE_INSTALL_DIR").append("$(COPY_DIR)"); if(project->values("MAKEFILE").isEmpty()) project->values("MAKEFILE").append("Makefile"); if(project->values("QMAKE_QMAKE").isEmpty()) project->values("QMAKE_QMAKE").append("qmake"); return; } project->values("TARGET_PRL").append(project->first("TARGET")); processVars(); // LIBS defined in Profile comes first for gcc project->values("QMAKE_LIBS") += escapeFilePaths(project->values("LIBS")); QString targetfilename = project->values("TARGET").first(); QStringList &configs = project->values("CONFIG"); if(project->isActiveConfig("qt_dll")) if(configs.indexOf("qt") == -1) configs.append("qt"); if(project->isActiveConfig("dll")) { QString destDir = ""; if(!project->first("DESTDIR").isEmpty()) destDir = Option::fixPathToTargetOS(project->first("DESTDIR") + Option::dir_sep, false, false); } MakefileGenerator::init(); // precomp if (!project->first("PRECOMPILED_HEADER").isEmpty() && project->isActiveConfig("precompile_header")) { QString preCompHeader = var("PRECOMPILED_DIR") + QFileInfo(project->first("PRECOMPILED_HEADER")).fileName(); preCompHeaderOut = preCompHeader + ".gch"; project->values("QMAKE_CLEAN").append(preCompHeaderOut + Option::dir_sep + "c"); project->values("QMAKE_CLEAN").append(preCompHeaderOut + Option::dir_sep + "c++"); project->values("QMAKE_RUN_CC").clear(); project->values("QMAKE_RUN_CC").append("$(CC) -c -include " + preCompHeader + " $(CFLAGS) $(INCPATH) -o $obj $src"); project->values("QMAKE_RUN_CC_IMP").clear(); project->values("QMAKE_RUN_CC_IMP").append("$(CC) -c -include " + preCompHeader + " $(CFLAGS) $(INCPATH) -o $@ $<"); project->values("QMAKE_RUN_CXX").clear(); project->values("QMAKE_RUN_CXX").append("$(CXX) -c -include " + preCompHeader + " $(CXXFLAGS) $(INCPATH) -o $obj $src"); project->values("QMAKE_RUN_CXX_IMP").clear(); project->values("QMAKE_RUN_CXX_IMP").append("$(CXX) -c -include " + preCompHeader + " $(CXXFLAGS) $(INCPATH) -o $@ $<"); } } void GNUMakefileGenerator::fixTargetExt() { if (mode == App) project->values("TARGET_EXT").append(".exe"); else if (mode == DLL) project->values("TARGET_EXT").append(project->first("TARGET_VERSION_EXT") + ".dll"); else project->values("TARGET_EXT").append(".lib"); } void GNUMakefileGenerator::writeIncPart(QTextStream &t) { t << "INCPATH ="; QString opt = var("QMAKE_CFLAGS_INCDIR"); QStringList &incs = project->values("INCLUDEPATH"); QString incsSemicolon; for(QStringList::Iterator incit = incs.begin(); incit != incs.end(); ++incit) { QString inc = escapeFilePath(*incit); t << " " << opt << inc; incsSemicolon += inc + Option::dirlist_sep; } t << " " << opt << "$(QMAKESPECDIR)" << endl; incsSemicolon += "$(QMAKESPECDIR)"; t << "INCLUDEPATH = " << incsSemicolon << endl; /* createCompilerResponseFiles() will need QAKESPECDIR expanded) */ project->values("INCLUDEPATH").append(specdir()); } void GNUMakefileGenerator::writeLibsPart(QTextStream &t) { if (mode == StaticLib) { t << "LIB = " << var("QMAKE_LIB") << endl; } else { t << "LINK = " << var("QMAKE_LINK") << endl; t << "LFLAGS = " << var("QMAKE_LFLAGS") << endl; t << "LIBS ="; if(!project->values("QMAKE_LIBDIR").isEmpty()) writeLibDirPart(t); QString opt = var("QMAKE_LFLAGS_LIB"); QStringList libs = project->values("QMAKE_LIBS"); for(QStringList::Iterator it = libs.begin(); it != libs.end(); ++it) { QString lib = escapeFilePath(*it); /* lib may be prefixed with -l which is commonly used in e.g. PRF * (feature) files on all platforms; remove it before prepending * the compiler-specific option */ if (lib.startsWith("-l")) lib = lib.mid(2); t << " " << opt << lib; } t << endl; } } void GNUMakefileGenerator::writeLibDirPart(QTextStream &t) { QString opt = var("QMAKE_LFLAGS_LIBDIR"); QStringList libDirs = project->values("QMAKE_LIBDIR"); for(QStringList::Iterator it = libDirs.begin(); it != libDirs.end(); ++it) { QString libDir = escapeFilePath(*it); /* libDir may be prefixed with -L which is commonly used in e.g. PRF * (feature) files on all platforms; remove it before prepending * the compiler-specific option */ if (libDir.startsWith("-L")) libDir = libDir.mid(2); t << " " << opt << libDir; } } void GNUMakefileGenerator::writeObjectsPart(QTextStream &t) { Win32MakefileGenerator::writeObjectsPart(t); createLinkerResponseFiles(t); /* this function is a nice place to also handle compiler options response * files */ createCompilerResponseFiles(t); } void GNUMakefileGenerator::writeBuildRulesPart(QTextStream &t) { t << "first: all" << endl; t << "all: " << escapeDependencyPath(fileFixify(Option::output.fileName())) << " " << valGlue(escapeDependencyPaths(project->values("ALL_DEPS"))," "," "," ") << escapeFileVars(" $(DESTDIR_TARGET)") << endl << endl; t << escapeFileVars("$(DESTDIR_TARGET): ") << var("PRE_TARGETDEPS") << " $(OBJECTS) " << var("POST_TARGETDEPS"); if (!project->isEmpty("QMAKE_PRE_LINK")) t << "\n\t" <isEmpty("RES_FILE") && !project->isEmpty("QMAKE_RUN_RC2EXE")) { t << "\n\t" << var("QMAKE_RUN_RC2EXE"); } if (mode == DLL && !project->isEmpty("QMAKE_RUN_IMPLIB")) { t << "\n\t" << var("QMAKE_RUN_IMPLIB"); } } if(!project->isEmpty("QMAKE_POST_LINK")) t << "\n\t" <isEmpty("RC_FILE")) { t << "RC_FILE = " << escapeFilePath(var("RC_FILE")) << endl; } if (!project->isEmpty("RES_FILE")) { t << "RES_FILE = " << valList(escapeFilePaths(project->values("RES_FILE"))) << endl; } if (mode == DLL && !project->isEmpty("QMAKE_RUN_IMPLIB")) { t << "TARGET_IMPLIB = $(basename $(DESTDIR_TARGET)).lib" << endl; project->values("QMAKE_CLEAN").append("$(TARGET_IMPLIB)"); } if (project->isEmpty("DEF_FILE")) { /* no DEF file supplied, we will generate one */ if (mode == DLL) { t << "DEF_FILE = $(basename $(DESTDIR_TARGET)).def" << endl; project->values("QMAKE_CLEAN").append("$(DEF_FILE)"); project->values("POST_TARGETDEPS") += escapeFileVars("$(DEF_FILE)"); if (!project->isEmpty("DEF_FILE_TEMPLATE")) { t << "DEF_FILE_TEMPLATE = " << escapeFilePath(var("DEF_FILE_TEMPLATE")) << endl; project->values("QMAKE_GENDEF_DEPS") += escapeFileVars("$(DEF_FILE_TEMPLATE)"); } if (!project->isEmpty("DEF_FILE_MAP")) { t << "DEF_FILE_MAP = " << escapeFilePath(var("DEF_FILE_MAP")) << endl; project->values("QMAKE_GENDEF_DEPS") += escapeFileVars("$(DEF_FILE_MAP)"); } } } else { if (!project->isEmpty("DEF_FILE_TEMPLATE")) { fprintf(stderr, "Both DEF_FILE and DEF_FILE_TEMPLATE are specified.\n"); fprintf(stderr, "Please specify one of them, not both."); exit(1); } t << "DEF_FILE = " << escapeFilePath(var("DEF_FILE")) << endl; project->values("POST_TARGETDEPS") += escapeFileVars("$(DEF_FILE)"); } } void GNUMakefileGenerator::writeRcAndDefPart(QTextStream &t) { if (!project->isEmpty("RC_FILE") && !project->isEmpty("RES_FILE") && !project->isEmpty("QMAKE_RUN_RC2RES")) { t << escapeFileVars("$(RES_FILE): $(RC_FILE)\n\t"); t << var("QMAKE_RUN_RC2RES") << endl; } if (project->isEmpty("DEF_FILE") && mode == DLL) { /* generate a DEF file for the DLL when not supplied */ t << escapeFileVars("$(DEF_FILE): ") << var("QMAKE_GENDEF_DEPS"); t << valGlue(var("QMAKE_RUN_GENDEF").split(";;"), "\n\t", "\n\t", "") << endl; } } void GNUMakefileGenerator::processRcFileVar() { if (Option::qmake_mode == Option::QMAKE_GENERATE_NOTHING) return; if (!project->isEmpty("RC_FILE")) { if (!project->isEmpty("RES_FILE")) { fprintf(stderr, "Both rc and res file specified.\n"); fprintf(stderr, "Please specify one of them, not both."); exit(1); } project->values("RES_FILE").prepend(escapeFilePath(QString("$(OBJECTS_DIR)") + QDir::separator() + QFileInfo(var("RC_FILE")).baseName() + ".res")); project->values("CLEAN_FILES") += "$(RES_FILE)"; } if (!project->isEmpty("RES_FILE")) project->values("POST_TARGETDEPS") += escapeFileVars("$(RES_FILE)"); } void GNUMakefileGenerator::processPrlVariable(const QString &var, const QStringList &l) { if (var == "QMAKE_PRL_LIBS") { QString where = "QMAKE_LIBS"; if (!project->isEmpty("QMAKE_INTERNAL_PRL_LIBS")) where = project->first("QMAKE_INTERNAL_PRL_LIBS"); QStringList &out = project->values(where); for (QStringList::ConstIterator it = l.begin(); it != l.end(); ++it) { out.removeAll((*it)); out.append((*it)); } } else { Win32MakefileGenerator::processPrlVariable(var, l); } } QStringList &GNUMakefileGenerator::findDependencies(const QString &file) { QStringList &aList = MakefileGenerator::findDependencies(file); // Note: The QMAKE_IMAGE_COLLECTION file have all images // as dependency, so don't add precompiled header then if (file == project->first("QMAKE_IMAGE_COLLECTION") || preCompHeaderOut.isEmpty()) return aList; for (QStringList::Iterator it = Option::c_ext.begin(); it != Option::c_ext.end(); ++it) { if (file.endsWith(*it)) { QString cHeader = preCompHeaderOut + Option::dir_sep + "c"; if (!aList.contains(cHeader)) aList += cHeader; break; } } for (QStringList::Iterator it = Option::cpp_ext.begin(); it != Option::cpp_ext.end(); ++it) { if (file.endsWith(*it)) { QString cppHeader = preCompHeaderOut + Option::dir_sep + "c++"; if (!aList.contains(cppHeader)) aList += cppHeader; break; } } return aList; } void GNUMakefileGenerator::writeProjectVarToStream(QTextStream &t, const QString &var) { QStringList &list = project->values(var); for(QStringList::Iterator it = list.begin(); it != list.end(); ++it) { t << (*it) << endl; } } QString GNUMakefileGenerator::makeResponseFileName(const QString &base) { QString fileName = base + "." + var("TARGET"); if (!var("BUILD_NAME").isEmpty()) { fileName += "." + var("BUILD_NAME"); } fileName += ".rsp"; QString filePath = project->first("OBJECTS_DIR"); if (filePath.isEmpty()) filePath = Option::output_dir; filePath = Option::fixPathToTargetOS(filePath + QDir::separator() + fileName); return filePath; } void GNUMakefileGenerator::createCompilerResponseFiles(QTextStream &t) { static const char *vars[] = { "CFLAGS", /*<=*/ "QMAKE_CFLAGS", "CXXFLAGS", /*<=*/ "QMAKE_CXXFLAGS", "INCPATH", /*<=*/ "INCLUDEPATH" }; /* QMAKE_XXX_RSP_VAR is used as a flag whether it is necessary to * generate response files to overcome the 1024 chars CMD.EXE limitation. * When this variable is defined, a response file with the relevant * information will be generated and its full path will be stored in an * environment variable with the given name which can then be referred to in * other places of qmake.conf (e.g. rules) */ for (size_t i = 0; i < sizeof(vars)/sizeof(vars[0]); i+=2) { QString rspVar = project->first(QString().sprintf("QMAKE_%s_RSP_VAR", vars[i])); if (!rspVar.isEmpty()) { QString fileName = makeResponseFileName(vars[i]); t << rspVar.leftJustified(14) << "= " << fileName << endl; QFile file(fileName); if (file.open(QIODevice::WriteOnly | QIODevice::Text)) { QTextStream rt(&file); if (!qstrcmp(vars[i+1], "INCLUDEPATH")) { QString opt = var("QMAKE_CFLAGS_INCDIR"); rt << varGlue(vars[i+1], opt, "\n" + opt, QString::null); } else rt << varGlue(vars[i+1], QString::null, "\n", QString::null); rt.flush(); file.close(); } project->values("QMAKE_DISTCLEAN").append("$(" + rspVar + ")"); } } } void GNUMakefileGenerator::createLinkerResponseFiles(QTextStream &t) { /* see createCompilerResponseFiles() */ QString var = project->first("QMAKE_OBJECTS_RSP_VAR"); if (!var.isEmpty()) { QString fileName = makeResponseFileName("OBJECTS"); t << var.leftJustified(14) << "= " << fileName << endl; QFile file(fileName); if (file.open(QIODevice::WriteOnly | QIODevice::Text)) { QTextStream rt(&file); rt << varGlue("OBJECTS", QString::null, "\n", QString::null); rt.flush(); file.close(); } project->values("QMAKE_DISTCLEAN").append("$(" + var + ")"); } } QString GNUMakefileGenerator::escapeFileVars(const QString &vars) { /* In DOS environment, we escape spaces and other illegal characters in * filenames with double quotes. However, this is not appropriate for make * rule definitions (targets/dependencies) where Unix escaping is * expected. For this reason, we must convert escaping to Unix mode using * the q function that we define in writeMakefile() */ if (isDosLikeShell()) { QString ret = vars; ret.replace(QRegExp("\\$\\((.+)\\)"), "$(call q,$(\\1))"); return ret; } return vars; } QT_END_NAMESPACE