Revision control
++ // Copyright (C) 2016 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
++
++ #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.size(); 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.size() > 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.size(); 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.size() != 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.size(); 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();
++ if (!Option::output.open(stdout, QIODevice::WriteOnly | QIODevice::Text)) {
++ fprintf(stderr, "Failure to open stdout\n");
++ return false;
++ }
++ 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.size() < 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>;
++ QList<TargetInfo> targets;
++ const int last = makefiles.size() - 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");
++ 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.size()));
++ }
++
++ //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.size(); 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.size()-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.size(); 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