/**************************************************************************** ** ** 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 #include #include #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 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; QList 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 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"); Q_CONSTINIT 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 = QFileInfo(project->first(fkey).toQString()); } else { const ProKey skey(subdirs.at(i) + ".subdir"); if (!project->isEmpty(skey)) subdir = QFileInfo(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