|  | /**************************************************************************** | 
|  | ** | 
|  | ** Copyright (C) 2016 The Qt Company Ltd. | 
|  | ** Contact: https://www.qt.io/licensing/ | 
|  | ** | 
|  | ** This file is part of the qmake application of the Qt Toolkit. | 
|  | ** | 
|  | ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ | 
|  | ** Commercial License Usage | 
|  | ** Licensees holding valid commercial Qt licenses may use this file in | 
|  | ** accordance with the commercial license agreement provided with the | 
|  | ** Software or, alternatively, in accordance with the terms contained in | 
|  | ** a written agreement between you and The Qt Company. For licensing terms | 
|  | ** and conditions see https://www.qt.io/terms-conditions. For further | 
|  | ** information use the contact form at https://www.qt.io/contact-us. | 
|  | ** | 
|  | ** GNU General Public License Usage | 
|  | ** Alternatively, this file may be used under the terms of the GNU | 
|  | ** General Public License version 3 as published by the Free Software | 
|  | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT | 
|  | ** included in the packaging of this file. Please review the following | 
|  | ** information to ensure the GNU General Public License requirements will | 
|  | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. | 
|  | ** | 
|  | ** $QT_END_LICENSE$ | 
|  | ** | 
|  | ****************************************************************************/ | 
|  |  | 
|  | #include "metamakefile.h" | 
|  | #include "qdir.h" | 
|  | #include "qdebug.h" | 
|  | #include "makefile.h" | 
|  | #include "project.h" | 
|  | #include "cachekeys.h" | 
|  |  | 
|  | #include <algorithm> | 
|  | #include <iterator> | 
|  | #include <utility> | 
|  |  | 
|  | #define BUILDSMETATYPE 1 | 
|  | #define SUBDIRSMETATYPE 2 | 
|  |  | 
|  | QT_BEGIN_NAMESPACE | 
|  |  | 
|  | MetaMakefileGenerator::~MetaMakefileGenerator() | 
|  | { | 
|  | if(own_project) | 
|  | delete project; | 
|  | } | 
|  |  | 
|  | #ifndef QT_QMAKE_PARSER_ONLY | 
|  |  | 
|  | class BuildsMetaMakefileGenerator : public MetaMakefileGenerator | 
|  | { | 
|  | private: | 
|  | bool init_flag; | 
|  | struct Build { | 
|  | QString name, build; | 
|  | MakefileGenerator *makefile; | 
|  | }; | 
|  | QList<Build *> makefiles; | 
|  | void clearBuilds(); | 
|  | MakefileGenerator *processBuild(const ProString &); | 
|  | void accumulateVariableFromBuilds(const ProKey &name, Build *build) const; | 
|  | void checkForConflictingTargets() const; | 
|  |  | 
|  | public: | 
|  |  | 
|  | BuildsMetaMakefileGenerator(QMakeProject *p, const QString &n, bool op) : MetaMakefileGenerator(p, n, op), init_flag(false) { } | 
|  | ~BuildsMetaMakefileGenerator() { clearBuilds(); } | 
|  |  | 
|  | bool init() override; | 
|  | int type() const override { return BUILDSMETATYPE; } | 
|  | bool write() override; | 
|  | }; | 
|  |  | 
|  | void | 
|  | BuildsMetaMakefileGenerator::clearBuilds() | 
|  | { | 
|  | for(int i = 0; i < makefiles.count(); i++) { | 
|  | Build *build = makefiles[i]; | 
|  | if(QMakeProject *p = build->makefile->projectFile()) { | 
|  | if(p != project) | 
|  | delete p; | 
|  | } | 
|  | delete build->makefile; | 
|  | delete build; | 
|  | } | 
|  | makefiles.clear(); | 
|  | } | 
|  |  | 
|  | bool | 
|  | BuildsMetaMakefileGenerator::init() | 
|  | { | 
|  | if(init_flag) | 
|  | return false; | 
|  | init_flag = true; | 
|  |  | 
|  | const ProStringList &builds = project->values("BUILDS"); | 
|  | bool use_single_build = builds.isEmpty(); | 
|  | if(builds.count() > 1 && Option::output.fileName() == "-") { | 
|  | use_single_build = true; | 
|  | warn_msg(WarnLogic, "Cannot direct to stdout when using multiple BUILDS."); | 
|  | } | 
|  | if(!use_single_build) { | 
|  | for(int i = 0; i < builds.count(); i++) { | 
|  | ProString build = builds[i]; | 
|  | MakefileGenerator *makefile = processBuild(build); | 
|  | if(!makefile) | 
|  | return false; | 
|  | if(!makefile->supportsMetaBuild()) { | 
|  | warn_msg(WarnLogic, "QMAKESPEC does not support multiple BUILDS."); | 
|  | clearBuilds(); | 
|  | use_single_build = true; | 
|  | break; | 
|  | } else { | 
|  | Build *b = new Build; | 
|  | b->name = name; | 
|  | if(builds.count() != 1) | 
|  | b->build = build.toQString(); | 
|  | b->makefile = makefile; | 
|  | makefiles += b; | 
|  | } | 
|  | } | 
|  | } | 
|  | if(use_single_build) { | 
|  | Build *build = new Build; | 
|  | build->name = name; | 
|  | build->makefile = createMakefileGenerator(project, false); | 
|  | if (build->makefile){ | 
|  | makefiles += build; | 
|  | }else { | 
|  | delete build; | 
|  | return false; | 
|  | } | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool | 
|  | BuildsMetaMakefileGenerator::write() | 
|  | { | 
|  | Build *glue = nullptr; | 
|  | if(!makefiles.isEmpty() && !makefiles.first()->build.isNull() | 
|  | && Option::qmake_mode != Option::QMAKE_GENERATE_PRL) { | 
|  | glue = new Build; | 
|  | glue->name = name; | 
|  | glue->makefile = createMakefileGenerator(project, true); | 
|  | makefiles += glue; | 
|  | } | 
|  |  | 
|  | bool ret = true; | 
|  | const QString &output_name = Option::output.fileName(); | 
|  | for(int i = 0; ret && i < makefiles.count(); i++) { | 
|  | Option::output.setFileName(output_name); | 
|  | Build *build = makefiles[i]; | 
|  |  | 
|  | bool using_stdout = false; | 
|  | if(build->makefile && (Option::qmake_mode == Option::QMAKE_GENERATE_MAKEFILE || | 
|  | Option::qmake_mode == Option::QMAKE_GENERATE_PROJECT) | 
|  | && (!build->makefile->supportsMergedBuilds() | 
|  | || (build->makefile->supportsMergedBuilds() && (!glue || build == glue)))) { | 
|  | //open output | 
|  | if(!(Option::output.isOpen())) { | 
|  | if(Option::output.fileName() == "-") { | 
|  | Option::output.setFileName(""); | 
|  | Option::output_dir = qmake_getpwd(); | 
|  | Option::output.open(stdout, QIODevice::WriteOnly | QIODevice::Text); | 
|  | using_stdout = true; | 
|  | } else { | 
|  | if(Option::output.fileName().isEmpty() && | 
|  | Option::qmake_mode == Option::QMAKE_GENERATE_MAKEFILE) | 
|  | Option::output.setFileName(project->first("QMAKE_MAKEFILE").toQString()); | 
|  | QString build_name = build->name; | 
|  | if(!build->build.isEmpty()) { | 
|  | if(!build_name.isEmpty()) | 
|  | build_name += "."; | 
|  | build_name += build->build; | 
|  | } | 
|  | if(!build->makefile->openOutput(Option::output, build_name)) { | 
|  | fprintf(stderr, "Failure to open file: %s\n", | 
|  | Option::output.fileName().isEmpty() ? "(stdout)" : | 
|  | Option::output.fileName().toLatin1().constData()); | 
|  | return false; | 
|  | } | 
|  | } | 
|  | } | 
|  | } else { | 
|  | using_stdout = true; //kind of.. | 
|  | } | 
|  |  | 
|  | if(!build->makefile) { | 
|  | ret = false; | 
|  | } else if(build == glue) { | 
|  | checkForConflictingTargets(); | 
|  | accumulateVariableFromBuilds("QMAKE_INTERNAL_INCLUDED_FILES", build); | 
|  | ret = build->makefile->writeProjectMakefile(); | 
|  | } else { | 
|  | ret = build->makefile->write(); | 
|  | if (glue && glue->makefile->supportsMergedBuilds()) | 
|  | ret = glue->makefile->mergeBuildProject(build->makefile); | 
|  | } | 
|  | if(!using_stdout) { | 
|  | Option::output.close(); | 
|  | if(!ret) | 
|  | Option::output.remove(); | 
|  | } | 
|  | } | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | MakefileGenerator | 
|  | *BuildsMetaMakefileGenerator::processBuild(const ProString &build) | 
|  | { | 
|  | if(project) { | 
|  | debug_msg(1, "Meta Generator: Parsing '%s' for build [%s].", | 
|  | project->projectFile().toLatin1().constData(),build.toLatin1().constData()); | 
|  |  | 
|  | //initialize the base | 
|  | ProValueMap basevars; | 
|  | ProStringList basecfgs = project->values(ProKey(build + ".CONFIG")); | 
|  | basecfgs += build; | 
|  | basecfgs += "build_pass"; | 
|  | basevars["BUILD_PASS"] = ProStringList(build); | 
|  | ProStringList buildname = project->values(ProKey(build + ".name")); | 
|  | basevars["BUILD_NAME"] = (buildname.isEmpty() ? ProStringList(build) : buildname); | 
|  |  | 
|  | //create project | 
|  | QMakeProject *build_proj = new QMakeProject; | 
|  | build_proj->setExtraVars(basevars); | 
|  | build_proj->setExtraConfigs(basecfgs); | 
|  |  | 
|  | if (build_proj->read(project->projectFile())) | 
|  | return createMakefileGenerator(build_proj); | 
|  | } | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | void BuildsMetaMakefileGenerator::accumulateVariableFromBuilds(const ProKey &name, Build *dst) const | 
|  | { | 
|  | ProStringList &values = dst->makefile->projectFile()->values(name); | 
|  | for (auto build : makefiles) { | 
|  | if (build != dst) | 
|  | values += build->makefile->projectFile()->values(name); | 
|  | } | 
|  | values.removeDuplicates(); | 
|  | } | 
|  |  | 
|  | void BuildsMetaMakefileGenerator::checkForConflictingTargets() const | 
|  | { | 
|  | if (makefiles.count() < 3) { | 
|  | // Checking for conflicts only makes sense if we have more than one BUILD, | 
|  | // and the last entry in makefiles is the "glue" Build. | 
|  | return; | 
|  | } | 
|  | if (!project->isActiveConfig("build_all")) { | 
|  | // Only complain if we're about to build all configurations. | 
|  | return; | 
|  | } | 
|  | using TargetInfo = std::pair<Build *, ProString>; | 
|  | QVector<TargetInfo> targets; | 
|  | const int last = makefiles.count() - 1; | 
|  | targets.resize(last); | 
|  | for (int i = 0; i < last; ++i) { | 
|  | Build *b = makefiles.at(i); | 
|  | auto mkf = b->makefile; | 
|  | auto prj = mkf->projectFile(); | 
|  | targets[i] = std::make_pair(b, prj->first(mkf->fullTargetVariable())); | 
|  | } | 
|  | std::stable_sort(targets.begin(), targets.end(), | 
|  | [](const TargetInfo &lhs, const TargetInfo &rhs) | 
|  | { | 
|  | return lhs.second < rhs.second; | 
|  | }); | 
|  | for (auto prev = targets.begin(), it = std::next(prev); it != targets.end(); ++prev, ++it) { | 
|  | if (prev->second == it->second) { | 
|  | warn_msg(WarnLogic, "Targets of builds '%s' and '%s' conflict: %s.", | 
|  | qPrintable(prev->first->build), | 
|  | qPrintable(it->first->build), | 
|  | qPrintable(prev->second.toQString())); | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | class SubdirsMetaMakefileGenerator : public MetaMakefileGenerator | 
|  | { | 
|  | protected: | 
|  | bool init_flag; | 
|  | struct Subdir { | 
|  | Subdir() : makefile(nullptr), indent(0) { } | 
|  | ~Subdir() { delete makefile; } | 
|  | QString input_dir; | 
|  | QString output_dir, output_file; | 
|  | MetaMakefileGenerator *makefile; | 
|  | int indent; | 
|  | }; | 
|  | QList<Subdir *> subs; | 
|  | MakefileGenerator *processBuild(const QString &); | 
|  |  | 
|  | public: | 
|  | SubdirsMetaMakefileGenerator(QMakeProject *p, const QString &n, bool op) : MetaMakefileGenerator(p, n, op), init_flag(false) { } | 
|  | ~SubdirsMetaMakefileGenerator(); | 
|  |  | 
|  | bool init() override; | 
|  | int type() const override { return SUBDIRSMETATYPE; } | 
|  | bool write() override; | 
|  | }; | 
|  |  | 
|  | bool | 
|  | SubdirsMetaMakefileGenerator::init() | 
|  | { | 
|  | if(init_flag) | 
|  | return false; | 
|  | init_flag = true; | 
|  | bool hasError = false; | 
|  |  | 
|  | bool recurse = Option::recursive; | 
|  | if (recurse && project->isActiveConfig("dont_recurse")) | 
|  | recurse = false; | 
|  | if(recurse) { | 
|  | QString old_output_dir = Option::output_dir; | 
|  | QString old_output = Option::output.fileName(); | 
|  | QString oldpwd = qmake_getpwd(); | 
|  | QString thispwd = oldpwd; | 
|  | if(!thispwd.endsWith('/')) | 
|  | thispwd += '/'; | 
|  | const ProStringList &subdirs = project->values("SUBDIRS"); | 
|  | static int recurseDepth = -1; | 
|  | ++recurseDepth; | 
|  | for(int i = 0; i < subdirs.size(); ++i) { | 
|  | Subdir *sub = new Subdir; | 
|  | sub->indent = recurseDepth; | 
|  |  | 
|  | QFileInfo subdir(subdirs.at(i).toQString()); | 
|  | const ProKey fkey(subdirs.at(i) + ".file"); | 
|  | if (!project->isEmpty(fkey)) { | 
|  | subdir = project->first(fkey).toQString(); | 
|  | } else { | 
|  | const ProKey skey(subdirs.at(i) + ".subdir"); | 
|  | if (!project->isEmpty(skey)) | 
|  | subdir = project->first(skey).toQString(); | 
|  | } | 
|  | QString sub_name; | 
|  | if(subdir.isDir()) | 
|  | subdir = QFileInfo(subdir.filePath() + "/" + subdir.fileName() + Option::pro_ext); | 
|  | else | 
|  | sub_name = subdir.baseName(); | 
|  | if(!subdir.isRelative()) { //we can try to make it relative | 
|  | QString subdir_path = subdir.filePath(); | 
|  | if(subdir_path.startsWith(thispwd)) | 
|  | subdir = QFileInfo(subdir_path.mid(thispwd.length())); | 
|  | } | 
|  |  | 
|  | //handle sub project | 
|  | QMakeProject *sub_proj = new QMakeProject; | 
|  | for (int ind = 0; ind < sub->indent; ++ind) | 
|  | printf(" "); | 
|  | sub->input_dir = subdir.absolutePath(); | 
|  | if(subdir.isRelative() && old_output_dir != oldpwd) { | 
|  | sub->output_dir = old_output_dir + (subdir.path() != "." ? "/" + subdir.path() : QString()); | 
|  | printf("Reading %s [%s]\n", subdir.absoluteFilePath().toLatin1().constData(), sub->output_dir.toLatin1().constData()); | 
|  | } else { //what about shadow builds? | 
|  | sub->output_dir = sub->input_dir; | 
|  | printf("Reading %s\n", subdir.absoluteFilePath().toLatin1().constData()); | 
|  | } | 
|  | qmake_setpwd(sub->input_dir); | 
|  | Option::output_dir = sub->output_dir; | 
|  | bool tmpError = !sub_proj->read(subdir.fileName()); | 
|  | if (!sub_proj->isEmpty("QMAKE_FAILED_REQUIREMENTS")) { | 
|  | fprintf(stderr, "Project file(%s) not recursed because all requirements not met:\n\t%s\n", | 
|  | subdir.fileName().toLatin1().constData(), | 
|  | sub_proj->values("QMAKE_FAILED_REQUIREMENTS").join(' ').toLatin1().constData()); | 
|  | delete sub; | 
|  | delete sub_proj; | 
|  | Option::output_dir = old_output_dir; | 
|  | qmake_setpwd(oldpwd); | 
|  | continue; | 
|  | } else { | 
|  | hasError |= tmpError; | 
|  | } | 
|  | sub->makefile = MetaMakefileGenerator::createMetaGenerator(sub_proj, sub_name); | 
|  | const QString output_name = Option::output.fileName(); | 
|  | Option::output.setFileName(sub->output_file); | 
|  | hasError |= !sub->makefile->write(); | 
|  | delete sub; | 
|  | qmakeClearCaches(); | 
|  | sub = nullptr; | 
|  | Option::output.setFileName(output_name); | 
|  | Option::output_dir = old_output_dir; | 
|  | qmake_setpwd(oldpwd); | 
|  |  | 
|  | } | 
|  | --recurseDepth; | 
|  | Option::output.setFileName(old_output); | 
|  | Option::output_dir = old_output_dir; | 
|  | qmake_setpwd(oldpwd); | 
|  | } | 
|  |  | 
|  | Subdir *self = new Subdir; | 
|  | self->input_dir = qmake_getpwd(); | 
|  | self->output_dir = Option::output_dir; | 
|  | if(!recurse || (!Option::output.fileName().endsWith(Option::dir_sep) && !QFileInfo(Option::output).isDir())) | 
|  | self->output_file = Option::output.fileName(); | 
|  | self->makefile = new BuildsMetaMakefileGenerator(project, name, false); | 
|  | self->makefile->init(); | 
|  | subs.append(self); | 
|  |  | 
|  | return !hasError; | 
|  | } | 
|  |  | 
|  | bool | 
|  | SubdirsMetaMakefileGenerator::write() | 
|  | { | 
|  | bool ret = true; | 
|  | const QString &pwd = qmake_getpwd(); | 
|  | const QString &output_dir = Option::output_dir; | 
|  | const QString &output_name = Option::output.fileName(); | 
|  | for(int i = 0; ret && i < subs.count(); i++) { | 
|  | const Subdir *sub = subs.at(i); | 
|  | qmake_setpwd(sub->input_dir); | 
|  | Option::output_dir = QFileInfo(sub->output_dir).absoluteFilePath(); | 
|  | Option::output.setFileName(sub->output_file); | 
|  | if(i != subs.count()-1) { | 
|  | for (int ind = 0; ind < sub->indent; ++ind) | 
|  | printf(" "); | 
|  | printf("Writing %s\n", QDir::cleanPath(Option::output_dir+"/"+ | 
|  | Option::output.fileName()).toLatin1().constData()); | 
|  | } | 
|  | if (!(ret = sub->makefile->write())) | 
|  | break; | 
|  | //restore because I'm paranoid | 
|  | qmake_setpwd(pwd); | 
|  | Option::output.setFileName(output_name); | 
|  | Option::output_dir = output_dir; | 
|  | } | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | SubdirsMetaMakefileGenerator::~SubdirsMetaMakefileGenerator() | 
|  | { | 
|  | for(int i = 0; i < subs.count(); i++) | 
|  | delete subs[i]; | 
|  | subs.clear(); | 
|  | } | 
|  |  | 
|  | //Factory things | 
|  | QT_BEGIN_INCLUDE_NAMESPACE | 
|  | #include "unixmake.h" | 
|  | #include "mingw_make.h" | 
|  | #include "projectgenerator.h" | 
|  | #include "pbuilder_pbx.h" | 
|  | #include "msvc_nmake.h" | 
|  | #include "msvc_vcproj.h" | 
|  | #include "msvc_vcxproj.h" | 
|  | QT_END_INCLUDE_NAMESPACE | 
|  |  | 
|  | MakefileGenerator * | 
|  | MetaMakefileGenerator::createMakefileGenerator(QMakeProject *proj, bool noIO) | 
|  | { | 
|  | Option::postProcessProject(proj); | 
|  |  | 
|  | MakefileGenerator *mkfile = nullptr; | 
|  | if(Option::qmake_mode == Option::QMAKE_GENERATE_PROJECT) { | 
|  | mkfile = new ProjectGenerator; | 
|  | mkfile->setProjectFile(proj); | 
|  | return mkfile; | 
|  | } | 
|  |  | 
|  | ProString gen = proj->first("MAKEFILE_GENERATOR"); | 
|  | if(gen.isEmpty()) { | 
|  | fprintf(stderr, "MAKEFILE_GENERATOR variable not set as a result of parsing : %s. Possibly qmake was not able to find files included using \"include(..)\" - enable qmake debugging to investigate more.\n", | 
|  | proj->projectFile().toLatin1().constData()); | 
|  | } else if(gen == "UNIX") { | 
|  | mkfile = new UnixMakefileGenerator; | 
|  | } else if(gen == "MINGW") { | 
|  | mkfile = new MingwMakefileGenerator; | 
|  | } else if(gen == "PROJECTBUILDER" || gen == "XCODE") { | 
|  | #ifdef Q_CC_MSVC | 
|  | fprintf(stderr, "Generating Xcode projects is not supported with an MSVC build of Qt.\n"); | 
|  | #else | 
|  | mkfile = new ProjectBuilderMakefileGenerator; | 
|  | #endif | 
|  | } else if(gen == "MSVC.NET") { | 
|  | if (proj->first("TEMPLATE").startsWith("vc")) | 
|  | mkfile = new VcprojGenerator; | 
|  | else | 
|  | mkfile = new NmakeMakefileGenerator; | 
|  | } else if(gen == "MSBUILD") { | 
|  | // Visual Studio >= v11.0 | 
|  | if (proj->first("TEMPLATE").startsWith("vc")) | 
|  | mkfile = new VcxprojGenerator; | 
|  | else | 
|  | mkfile = new NmakeMakefileGenerator; | 
|  | } else { | 
|  | fprintf(stderr, "Unknown generator specified: %s\n", gen.toLatin1().constData()); | 
|  | } | 
|  | if (mkfile) { | 
|  | mkfile->setNoIO(noIO); | 
|  | mkfile->setProjectFile(proj); | 
|  | } | 
|  | return mkfile; | 
|  | } | 
|  |  | 
|  | MetaMakefileGenerator * | 
|  | MetaMakefileGenerator::createMetaGenerator(QMakeProject *proj, const QString &name, bool op, bool *success) | 
|  | { | 
|  | Option::postProcessProject(proj); | 
|  |  | 
|  | MetaMakefileGenerator *ret = nullptr; | 
|  | if ((Option::qmake_mode == Option::QMAKE_GENERATE_MAKEFILE || | 
|  | Option::qmake_mode == Option::QMAKE_GENERATE_PRL)) { | 
|  | if (proj->first("TEMPLATE").endsWith("subdirs")) | 
|  | ret = new SubdirsMetaMakefileGenerator(proj, name, op); | 
|  | } | 
|  | if (!ret) | 
|  | ret = new BuildsMetaMakefileGenerator(proj, name, op); | 
|  | bool res = ret->init(); | 
|  | if (success) | 
|  | *success = res; | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | #endif // QT_QMAKE_PARSER_ONLY | 
|  |  | 
|  | QT_END_NAMESPACE |