qt5base-lts/qmake/generators/metamakefile.cpp

502 lines
17 KiB
C++
Raw Normal View History

// 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.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>;
QList<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");
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