blob: 9ec2fe6411643a5ace887ee68e92893888f968c7 [file] [log] [blame]
/****************************************************************************
**
** 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 "option.h"
#include "cachekeys.h"
#include <ioutils.h>
#include <qdir.h>
#include <qregexp.h>
#include <qhash.h>
#include <qdebug.h>
#include <qlibraryinfo.h>
#include <stdlib.h>
#include <stdarg.h>
QT_BEGIN_NAMESPACE
using namespace QMakeInternal;
EvalHandler Option::evalHandler;
QMakeGlobals *Option::globals;
ProFileCache *Option::proFileCache;
QMakeVfs *Option::vfs;
QMakeParser *Option::parser;
//convenience
QString Option::prf_ext;
QString Option::prl_ext;
QString Option::libtool_ext;
QString Option::pkgcfg_ext;
QString Option::ui_ext;
QStringList Option::h_ext;
QString Option::cpp_moc_ext;
QStringList Option::cpp_ext;
QStringList Option::c_ext;
QString Option::objc_ext;
QString Option::objcpp_ext;
QString Option::obj_ext;
QString Option::lex_ext;
QString Option::yacc_ext;
QString Option::pro_ext;
QString Option::dir_sep;
QString Option::h_moc_mod;
QString Option::yacc_mod;
QString Option::lex_mod;
QString Option::res_ext;
char Option::field_sep;
//mode
Option::QMAKE_MODE Option::qmake_mode = Option::QMAKE_GENERATE_NOTHING;
//all modes
int Option::warn_level = WarnLogic | WarnDeprecated;
int Option::debug_level = 0;
QFile Option::output;
QString Option::output_dir;
bool Option::recursive = false;
//QMAKE_*_PROPERTY stuff
QStringList Option::prop::properties;
//QMAKE_GENERATE_PROJECT stuff
bool Option::projfile::do_pwd = true;
QStringList Option::projfile::project_dirs;
//QMAKE_GENERATE_MAKEFILE stuff
int Option::mkfile::cachefile_depth = -1;
bool Option::mkfile::do_deps = true;
bool Option::mkfile::do_mocs = true;
bool Option::mkfile::do_dep_heuristics = true;
bool Option::mkfile::do_preprocess = false;
QStringList Option::mkfile::project_files;
static Option::QMAKE_MODE default_mode(QString progname)
{
int s = progname.lastIndexOf(QDir::separator());
if(s != -1)
progname = progname.right(progname.length() - (s + 1));
if(progname == "qmakegen")
return Option::QMAKE_GENERATE_PROJECT;
else if(progname == "qt-config")
return Option::QMAKE_QUERY_PROPERTY;
return Option::QMAKE_GENERATE_MAKEFILE;
}
static QString detectProjectFile(const QString &path)
{
QString ret;
QDir dir(path);
if(dir.exists(dir.dirName() + Option::pro_ext)) {
ret = dir.filePath(dir.dirName()) + Option::pro_ext;
} else { //last try..
QStringList profiles = dir.entryList(QStringList("*" + Option::pro_ext));
if(profiles.count() == 1)
ret = dir.filePath(profiles.at(0));
}
return ret;
}
bool usage(const char *a0)
{
fprintf(stdout, "Usage: %s [mode] [options] [files]\n"
"\n"
"QMake has two modes, one mode for generating project files based on\n"
"some heuristics, and the other for generating makefiles. Normally you\n"
"shouldn't need to specify a mode, as makefile generation is the default\n"
"mode for qmake, but you may use this to test qmake on an existing project\n"
"\n"
"Mode:\n"
" -project Put qmake into project file generation mode%s\n"
" In this mode qmake interprets [files] as files to\n"
" be added to the .pro file. By default, all files with\n"
" known source extensions are added.\n"
" Note: The created .pro file probably will \n"
" need to be edited. For example add the QT variable to \n"
" specify what modules are required.\n"
" -makefile Put qmake into makefile generation mode%s\n"
" In this mode qmake interprets files as project files to\n"
" be processed, if skipped qmake will try to find a project\n"
" file in your current working directory\n"
"\n"
"Warnings Options:\n"
" -Wnone Turn off all warnings; specific ones may be re-enabled by\n"
" later -W options\n"
" -Wall Turn on all warnings\n"
" -Wparser Turn on parser warnings\n"
" -Wlogic Turn on logic warnings (on by default)\n"
" -Wdeprecated Turn on deprecation warnings (on by default)\n"
"\n"
"Options:\n"
" * You can place any variable assignment in options and it will be *\n"
" * processed as if it was in [files]. These assignments will be *\n"
" * processed before [files] by default. *\n"
" -o file Write output to file\n"
" -d Increase debug level\n"
" -t templ Overrides TEMPLATE as templ\n"
" -tp prefix Overrides TEMPLATE so that prefix is prefixed into the value\n"
" -help This help\n"
" -v Version information\n"
" -early All subsequent variable assignments will be\n"
" parsed right before default_pre.prf\n"
" -before All subsequent variable assignments will be\n"
" parsed right before [files] (the default)\n"
" -after All subsequent variable assignments will be\n"
" parsed after [files]\n"
" -late All subsequent variable assignments will be\n"
" parsed right after default_post.prf\n"
" -norecursive Don't do a recursive search\n"
" -recursive Do a recursive search\n"
" -set <prop> <value> Set persistent property\n"
" -unset <prop> Unset persistent property\n"
" -query <prop> Query persistent property. Show all if <prop> is empty.\n"
" -qtconf file Use file instead of looking for qt.conf\n"
" -cache file Use file as cache [makefile mode only]\n"
" -spec spec Use spec as QMAKESPEC [makefile mode only]\n"
" -nocache Don't use a cache file [makefile mode only]\n"
" -nodepend Don't generate dependencies [makefile mode only]\n"
" -nomoc Don't generate moc targets [makefile mode only]\n"
" -nopwd Don't look for files in pwd [project mode only]\n"
,a0,
default_mode(a0) == Option::QMAKE_GENERATE_PROJECT ? " (default)" : "",
default_mode(a0) == Option::QMAKE_GENERATE_MAKEFILE ? " (default)" : ""
);
return false;
}
int
Option::parseCommandLine(QStringList &args, QMakeCmdLineParserState &state)
{
enum { ArgNone, ArgOutput } argState = ArgNone;
int x = 0;
while (x < args.count()) {
switch (argState) {
case ArgOutput:
Option::output.setFileName(args.at(x--));
args.erase(args.begin() + x, args.begin() + x + 2);
argState = ArgNone;
continue;
default:
QMakeGlobals::ArgumentReturn cmdRet = globals->addCommandLineArguments(state, args, &x);
if (cmdRet == QMakeGlobals::ArgumentsOk)
break;
if (cmdRet == QMakeGlobals::ArgumentMalformed) {
fprintf(stderr, "***Option %s requires a parameter\n", qPrintable(args.at(x - 1)));
return Option::QMAKE_CMDLINE_SHOW_USAGE | Option::QMAKE_CMDLINE_ERROR;
}
Q_ASSERT(cmdRet == QMakeGlobals::ArgumentUnknown);
QString arg = args.at(x);
if (arg.startsWith(QLatin1Char('-'))) {
if (arg == "-d") {
Option::debug_level++;
} else if (arg == "-v" || arg == "-version" || arg == "--version") {
fprintf(stdout,
"QMake version %s\n"
"Using Qt version %s in %s\n",
QMAKE_VERSION_STR, QT_VERSION_STR,
QLibraryInfo::location(QLibraryInfo::LibrariesPath).toLatin1().constData());
#ifdef QMAKE_OPENSOURCE_VERSION
fprintf(stdout, "QMake is Open Source software from The Qt Company Ltd and/or its subsidiary(-ies).\n");
#endif
return Option::QMAKE_CMDLINE_BAIL;
} else if (arg == "-h" || arg == "-help" || arg == "--help") {
return Option::QMAKE_CMDLINE_SHOW_USAGE;
} else if (arg == "-Wall") {
Option::warn_level |= WarnAll;
} else if (arg == "-Wparser") {
Option::warn_level |= WarnParser;
} else if (arg == "-Wlogic") {
Option::warn_level |= WarnLogic;
} else if (arg == "-Wdeprecated") {
Option::warn_level |= WarnDeprecated;
} else if (arg == "-Wnone") {
Option::warn_level = WarnNone;
} else if (arg == "-r" || arg == "-recursive") {
Option::recursive = true;
args.removeAt(x);
continue;
} else if (arg == "-nr" || arg == "-norecursive") {
Option::recursive = false;
args.removeAt(x);
continue;
} else if (arg == "-o" || arg == "-output") {
argState = ArgOutput;
} else {
if (Option::qmake_mode == Option::QMAKE_GENERATE_MAKEFILE ||
Option::qmake_mode == Option::QMAKE_GENERATE_PRL) {
if (arg == "-nodepend" || arg == "-nodepends") {
Option::mkfile::do_deps = false;
} else if (arg == "-nomoc") {
Option::mkfile::do_mocs = false;
} else if (arg == "-nodependheuristics") {
Option::mkfile::do_dep_heuristics = false;
} else if (arg == "-E") {
Option::mkfile::do_preprocess = true;
} else {
fprintf(stderr, "***Unknown option %s\n", arg.toLatin1().constData());
return Option::QMAKE_CMDLINE_SHOW_USAGE | Option::QMAKE_CMDLINE_ERROR;
}
} else if (Option::qmake_mode == Option::QMAKE_GENERATE_PROJECT) {
if (arg == "-nopwd") {
Option::projfile::do_pwd = false;
} else {
fprintf(stderr, "***Unknown option %s\n", arg.toLatin1().constData());
return Option::QMAKE_CMDLINE_SHOW_USAGE | Option::QMAKE_CMDLINE_ERROR;
}
}
}
} else {
bool handled = true;
if(Option::qmake_mode == Option::QMAKE_QUERY_PROPERTY ||
Option::qmake_mode == Option::QMAKE_SET_PROPERTY ||
Option::qmake_mode == Option::QMAKE_UNSET_PROPERTY) {
Option::prop::properties.append(arg);
} else {
QFileInfo fi(arg);
if(!fi.makeAbsolute()) //strange
arg = fi.filePath();
if(Option::qmake_mode == Option::QMAKE_GENERATE_MAKEFILE ||
Option::qmake_mode == Option::QMAKE_GENERATE_PRL) {
if(fi.isDir()) {
QString proj = detectProjectFile(arg);
if (!proj.isNull())
arg = proj;
}
Option::mkfile::project_files.append(arg);
} else if(Option::qmake_mode == Option::QMAKE_GENERATE_PROJECT) {
Option::projfile::project_dirs.append(arg);
} else {
handled = false;
}
}
if(!handled) {
return Option::QMAKE_CMDLINE_SHOW_USAGE | Option::QMAKE_CMDLINE_ERROR;
}
args.removeAt(x);
continue;
}
}
x++;
}
if (argState != ArgNone) {
fprintf(stderr, "***Option %s requires a parameter\n", qPrintable(args.at(x - 1)));
return Option::QMAKE_CMDLINE_SHOW_USAGE | Option::QMAKE_CMDLINE_ERROR;
}
return Option::QMAKE_CMDLINE_SUCCESS;
}
int
Option::init(int argc, char **argv)
{
Option::prf_ext = ".prf";
Option::pro_ext = ".pro";
Option::field_sep = ' ';
if(argc && argv) {
QString argv0 = argv[0];
#ifdef Q_OS_WIN
if (!argv0.endsWith(QLatin1String(".exe"), Qt::CaseInsensitive))
argv0 += QLatin1String(".exe");
#endif
if(Option::qmake_mode == Option::QMAKE_GENERATE_NOTHING)
Option::qmake_mode = default_mode(argv0);
if (!argv0.isEmpty() && IoUtils::isAbsolutePath(argv0)) {
globals->qmake_abslocation = argv0;
} else if (argv0.contains(QLatin1Char('/'))
#ifdef Q_OS_WIN
|| argv0.contains(QLatin1Char('\\'))
#endif
) { //relative PWD
globals->qmake_abslocation = QDir::current().absoluteFilePath(argv0);
} else { //in the PATH
QByteArray pEnv = qgetenv("PATH");
QDir currentDir = QDir::current();
#ifdef Q_OS_WIN
QStringList paths = QString::fromLocal8Bit(pEnv).split(QLatin1String(";"));
paths.prepend(QLatin1String("."));
#else
QStringList paths = QString::fromLocal8Bit(pEnv).split(QLatin1String(":"));
#endif
for (QStringList::const_iterator p = paths.constBegin(); p != paths.constEnd(); ++p) {
if ((*p).isEmpty())
continue;
QString candidate = currentDir.absoluteFilePath(*p + QLatin1Char('/') + argv0);
if (QFile::exists(candidate)) {
globals->qmake_abslocation = candidate;
break;
}
}
}
if (Q_UNLIKELY(globals->qmake_abslocation.isNull())) {
// This is rather unlikely to ever happen on a modern system ...
globals->qmake_abslocation = QLibraryInfo::rawLocation(
QLibraryInfo::HostBinariesPath,
QLibraryInfo::EffectivePaths)
#ifdef Q_OS_WIN
+ "/qmake.exe";
#else
+ "/qmake";
#endif
} else {
globals->qmake_abslocation = QDir::cleanPath(globals->qmake_abslocation);
}
} else {
Option::qmake_mode = Option::QMAKE_GENERATE_MAKEFILE;
}
QMakeCmdLineParserState cmdstate(QDir::currentPath());
const QByteArray envflags = qgetenv("QMAKEFLAGS");
if (!envflags.isNull()) {
QStringList args;
QByteArray buf = "";
char quote = 0;
bool hasWord = false;
for (int i = 0; i < envflags.size(); ++i) {
char c = envflags.at(i);
if (!quote && (c == '\'' || c == '"')) {
quote = c;
} else if (c == quote) {
quote = 0;
} else if (!quote && c == ' ') {
if (hasWord) {
args << QString::fromLocal8Bit(buf);
hasWord = false;
buf = "";
}
} else {
buf += c;
hasWord = true;
}
}
if (hasWord)
args << QString::fromLocal8Bit(buf);
parseCommandLine(args, cmdstate);
cmdstate.flush();
}
if(argc && argv) {
QStringList args;
args.reserve(argc - 1);
for (int i = 1; i < argc; i++)
args << QString::fromLocal8Bit(argv[i]);
while (!args.isEmpty()) {
QString opt = args.at(0);
if (opt == "-project") {
Option::recursive = true;
Option::qmake_mode = Option::QMAKE_GENERATE_PROJECT;
} else if (opt == "-prl") {
Option::mkfile::do_deps = false;
Option::mkfile::do_mocs = false;
Option::qmake_mode = Option::QMAKE_GENERATE_PRL;
} else if (opt == "-set") {
Option::qmake_mode = Option::QMAKE_SET_PROPERTY;
} else if (opt == "-unset") {
Option::qmake_mode = Option::QMAKE_UNSET_PROPERTY;
} else if (opt == "-query") {
Option::qmake_mode = Option::QMAKE_QUERY_PROPERTY;
} else if (opt == "-makefile") {
Option::qmake_mode = Option::QMAKE_GENERATE_MAKEFILE;
} else {
break;
}
args.takeFirst();
break;
}
int ret = parseCommandLine(args, cmdstate);
if(ret != Option::QMAKE_CMDLINE_SUCCESS) {
if ((ret & Option::QMAKE_CMDLINE_SHOW_USAGE) != 0)
usage(argv[0]);
return ret;
//return ret == QMAKE_CMDLINE_SHOW_USAGE ? usage(argv[0]) : false;
}
globals->qmake_args = args;
globals->qmake_extra_args = cmdstate.extraargs;
}
globals->commitCommandLineArguments(cmdstate);
globals->debugLevel = Option::debug_level;
//last chance for defaults
if(Option::qmake_mode == Option::QMAKE_GENERATE_MAKEFILE ||
Option::qmake_mode == Option::QMAKE_GENERATE_PRL) {
globals->useEnvironment();
//try REALLY hard to do it for them, lazy..
if(Option::mkfile::project_files.isEmpty()) {
QString proj = detectProjectFile(qmake_getpwd());
if(!proj.isNull())
Option::mkfile::project_files.append(proj);
#ifndef QT_BUILD_QMAKE_LIBRARY
if(Option::mkfile::project_files.isEmpty()) {
usage(argv[0]);
return Option::QMAKE_CMDLINE_ERROR;
}
#endif
}
}
return QMAKE_CMDLINE_SUCCESS;
}
void Option::prepareProject(const QString &pfile)
{
// Canonicalize only the directory, otherwise things will go haywire
// if the file itself is a symbolic link.
const QString srcpath = QFileInfo(QFileInfo(pfile).absolutePath()).canonicalFilePath();
globals->setDirectories(srcpath, output_dir);
}
bool Option::postProcessProject(QMakeProject *project)
{
Option::cpp_ext = project->values("QMAKE_EXT_CPP").toQStringList();
Option::h_ext = project->values("QMAKE_EXT_H").toQStringList();
Option::c_ext = project->values("QMAKE_EXT_C").toQStringList();
Option::objc_ext = project->first("QMAKE_EXT_OBJC").toQString();
Option::objcpp_ext = project->first("QMAKE_EXT_OBJCXX").toQString();
Option::res_ext = project->first("QMAKE_EXT_RES").toQString();
Option::pkgcfg_ext = project->first("QMAKE_EXT_PKGCONFIG").toQString();
Option::libtool_ext = project->first("QMAKE_EXT_LIBTOOL").toQString();
Option::prl_ext = project->first("QMAKE_EXT_PRL").toQString();
Option::ui_ext = project->first("QMAKE_EXT_UI").toQString();
Option::cpp_moc_ext = project->first("QMAKE_EXT_CPP_MOC").toQString();
Option::lex_ext = project->first("QMAKE_EXT_LEX").toQString();
Option::yacc_ext = project->first("QMAKE_EXT_YACC").toQString();
Option::obj_ext = project->first("QMAKE_EXT_OBJ").toQString();
Option::h_moc_mod = project->first("QMAKE_H_MOD_MOC").toQString();
Option::lex_mod = project->first("QMAKE_MOD_LEX").toQString();
Option::yacc_mod = project->first("QMAKE_MOD_YACC").toQString();
Option::dir_sep = project->dirSep().toQString();
if (!project->buildRoot().isEmpty() && Option::output_dir.startsWith(project->buildRoot()))
Option::mkfile::cachefile_depth =
Option::output_dir.mid(project->buildRoot().length()).count('/');
return true;
}
QString
Option::fixString(QString string, uchar flags)
{
//const QString orig_string = string;
static QHash<FixStringCacheKey, QString> *cache = nullptr;
if(!cache) {
cache = new QHash<FixStringCacheKey, QString>;
qmakeAddCacheClear(qmakeDeleteCacheClear<QHash<FixStringCacheKey, QString> >, (void**)&cache);
}
FixStringCacheKey cacheKey(string, flags);
QHash<FixStringCacheKey, QString>::const_iterator it = cache->constFind(cacheKey);
if (it != cache->constEnd()) {
//qDebug() << "Fix (cached) " << orig_string << "->" << it.value();
return it.value();
}
//fix the environment variables
if(flags & Option::FixEnvVars) {
int rep;
static QRegExp reg_var("\\$\\(.*\\)");
reg_var.setMinimal(true);
while((rep = reg_var.indexIn(string)) != -1)
string.replace(rep, reg_var.matchedLength(),
QString::fromLocal8Bit(qgetenv(string.mid(rep + 2, reg_var.matchedLength() - 3).toLatin1().constData()).constData()));
}
//canonicalize it (and treat as a path)
if(flags & Option::FixPathCanonicalize) {
#if 0
string = QFileInfo(string).canonicalFilePath();
#endif
string = QDir::cleanPath(string);
}
// either none or only one active flag
Q_ASSERT(((flags & Option::FixPathToLocalSeparators) != 0) +
((flags & Option::FixPathToTargetSeparators) != 0) +
((flags & Option::FixPathToNormalSeparators) != 0) <= 1);
//fix separators
if (flags & Option::FixPathToNormalSeparators) {
string.replace('\\', '/');
} else if (flags & Option::FixPathToLocalSeparators) {
#if defined(Q_OS_WIN32)
string.replace('/', '\\');
#else
string.replace('\\', '/');
#endif
} else if(flags & Option::FixPathToTargetSeparators) {
string.replace('/', Option::dir_sep).replace('\\', Option::dir_sep);
}
if ((string.startsWith("\"") && string.endsWith("\"")) ||
(string.startsWith("\'") && string.endsWith("\'")))
string = string.mid(1, string.length()-2);
//cache
//qDebug() << "Fix" << orig_string << "->" << string;
cache->insert(cacheKey, string);
return string;
}
void debug_msg_internal(int level, const char *fmt, ...)
{
if(Option::debug_level < level)
return;
fprintf(stderr, "DEBUG %d: ", level);
{
va_list ap;
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
}
fprintf(stderr, "\n");
}
void warn_msg(QMakeWarn type, const char *fmt, ...)
{
if(!(Option::warn_level & type))
return;
fprintf(stderr, "WARNING: ");
{
va_list ap;
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
}
fprintf(stderr, "\n");
}
void EvalHandler::message(int type, const QString &msg, const QString &fileName, int lineNo)
{
QString pfx;
if ((type & QMakeHandler::CategoryMask) == QMakeHandler::WarningMessage) {
int code = (type & QMakeHandler::CodeMask);
if ((code == QMakeHandler::WarnLanguage && !(Option::warn_level & WarnParser))
|| (code == QMakeHandler::WarnDeprecated && !(Option::warn_level & WarnDeprecated)))
return;
pfx = QString::fromLatin1("WARNING: ");
}
if (lineNo > 0)
fprintf(stderr, "%s%s:%d: %s\n", qPrintable(pfx), qPrintable(fileName), lineNo, qPrintable(msg));
else if (lineNo)
fprintf(stderr, "%s%s: %s\n", qPrintable(pfx), qPrintable(fileName), qPrintable(msg));
else
fprintf(stderr, "%s%s\n", qPrintable(pfx), qPrintable(msg));
}
void EvalHandler::fileMessage(int type, const QString &msg)
{
Q_UNUSED(type)
fprintf(stderr, "%s\n", qPrintable(msg));
}
void EvalHandler::aboutToEval(ProFile *, ProFile *, EvalFileType)
{
}
void EvalHandler::doneWithEval(ProFile *)
{
}
class QMakeCacheClearItem {
private:
qmakeCacheClearFunc func;
void **data;
public:
QMakeCacheClearItem(qmakeCacheClearFunc f, void **d) : func(f), data(d) { }
~QMakeCacheClearItem() {
(*func)(*data);
*data = nullptr;
}
};
static QList<QMakeCacheClearItem*> cache_items;
void
qmakeClearCaches()
{
qDeleteAll(cache_items);
cache_items.clear();
}
void
qmakeAddCacheClear(qmakeCacheClearFunc func, void **data)
{
cache_items.append(new QMakeCacheClearItem(func, data));
}
QString qmake_libraryInfoFile()
{
if (!Option::globals->qtconf.isEmpty())
return Option::globals->qtconf;
if (!Option::globals->qmake_abslocation.isEmpty())
return QDir(QFileInfo(Option::globals->qmake_abslocation).absolutePath()).filePath("qt.conf");
return QString();
}
QString qmake_abslocation()
{
return Option::globals->qmake_abslocation;
}
QT_END_NAMESPACE