kde-extraapps/kdevelop/projectmanagers/cmake/parser/cmakeparserutils.cpp
2015-07-26 14:23:17 +03:00

306 lines
11 KiB
C++

/* KDevelop CMake Support
*
* Copyright 2008 Matt Rogers <mattr@kde.org>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
#include "cmakeparserutils.h"
#include <QStringList>
#include <qfileinfo.h>
#include <qdir.h>
#include <kdebug.h>
#include "variablemap.h"
#include <kprocess.h>
#include <kstandarddirs.h>
#include "cmakeprojectvisitor.h"
#include "cmakeprojectdata.h"
#include "cmakecachereader.h"
#include <util/path.h>
#include <ktempdir.h>
namespace CMakeParserUtils
{
QList<int> parseVersion(const QString& version, bool* ok)
{
QList<int> versionNumList;
*ok = false;
QStringList versionStringList = version.split('.', QString::SkipEmptyParts);
foreach(const QString& part, versionStringList)
{
int i = part.toInt(ok);
if (!*ok)
{
versionNumList.clear();
return versionNumList;
}
else
versionNumList.append(i);
}
return versionNumList;
}
QString valueFromSystemInfo(const QString& variable, const QString& systeminfo)
{
int idx = systeminfo.indexOf( variable );
if( idx != -1 ) {
// 2 == ' "'
idx += variable.count() + 2;
int end = systeminfo.indexOf( "\"", idx );
if( end != -1 ) {
return systeminfo.mid( idx, end-idx );
}
}
return QString();
}
QPair<VariableMap,QStringList> initialVariables()
{
static QPair<VariableMap, QStringList> ret;
if (!ret.first.isEmpty()) {
return ret;
}
QString cmakeCmd=KStandardDirs::findExe("cmake");
QString systeminfo=executeProcess(cmakeCmd, QStringList("--system-information"));
VariableMap varsDef;
QStringList modulePathDef=QStringList(CMakeParserUtils::valueFromSystemInfo( "CMAKE_ROOT", systeminfo ) + "/Modules");
kDebug(9042) << "found module path is" << modulePathDef;
varsDef.insertGlobal("CMAKE_BINARY_DIR", QStringList("#[bin_dir]"));
varsDef.insertGlobal("CMAKE_INSTALL_PREFIX", QStringList("#[install_dir]"));
varsDef.insertGlobal("CMAKE_COMMAND", QStringList(cmakeCmd));
varsDef.insertGlobal("CMAKE_MAJOR_VERSION", QStringList(CMakeParserUtils::valueFromSystemInfo("CMAKE_MAJOR_VERSION", systeminfo)));
varsDef.insertGlobal("CMAKE_MINOR_VERSION", QStringList(CMakeParserUtils::valueFromSystemInfo("CMAKE_MINOR_VERSION", systeminfo)));
varsDef.insertGlobal("CMAKE_PATCH_VERSION", QStringList(CMakeParserUtils::valueFromSystemInfo("CMAKE_PATCH_VERSION", systeminfo)));
varsDef.insertGlobal("CMAKE_VERSION", QStringList(CMakeParserUtils::valueFromSystemInfo("CMAKE_VERSION", systeminfo)));
varsDef.insertGlobal("CMAKE_INCLUDE_CURRENT_DIR", QStringList("OFF"));
QStringList cmakeInitScripts;
#ifdef Q_OS_WIN
cmakeInitScripts << "CMakeMinGWFindMake.cmake";
cmakeInitScripts << "CMakeMSYSFindMake.cmake";
cmakeInitScripts << "CMakeNMakeFindMake.cmake";
cmakeInitScripts << "CMakeVS8FindMake.cmake";
#else
cmakeInitScripts << "CMakeUnixFindMake.cmake";
#endif
cmakeInitScripts << "CMakeDetermineSystem.cmake";
cmakeInitScripts << "CMakeSystemSpecificInformation.cmake";
cmakeInitScripts << "CMakeDetermineCCompiler.cmake";
cmakeInitScripts << "CMakeDetermineCXXCompiler.cmake";
varsDef.insertGlobal("CMAKE_MODULE_PATH", modulePathDef);
varsDef.insertGlobal("CMAKE_ROOT", QStringList(CMakeParserUtils::valueFromSystemInfo("CMAKE_ROOT", systeminfo)));
//Defines the behaviour that can't be identified on initialization scripts
#ifdef Q_OS_WIN32
varsDef.insertGlobal("WIN32", QStringList("1"));
varsDef.insertGlobal("CMAKE_HOST_WIN32", QStringList("1"));
#else
varsDef.insertGlobal("UNIX", QStringList("1"));
varsDef.insertGlobal("CMAKE_HOST_UNIX", QStringList("1"));
#endif
#ifdef Q_OS_MAC
varsDef.insertGlobal("APPLE", QStringList("1"));
varsDef.insertGlobal("CMAKE_HOST_APPLE", QStringList("1"));
#endif
ret = qMakePair(varsDef, cmakeInitScripts);
return ret;
}
QString executeProcess(const QString& execName, const QStringList& args)
{
Q_ASSERT(!execName.isEmpty());
kDebug(9042) << "Executing:" << execName << "::" << args /*<< "into" << *m_vars*/;
KProcess p;
p.setOutputChannelMode(KProcess::MergedChannels);
p.setProgram(execName, args);
KTempDir tmp(KStandardDirs::locateLocal("tmp", "kdevcmakemanager"));
p.setWorkingDirectory( tmp.name() );
p.start();
if(!p.waitForFinished())
{
kDebug() << "failed to execute:" << execName;
}
QByteArray b = p.readAllStandardOutput();
QString t;
t.prepend(b.trimmed());
tmp.unlink();
return t;
}
void printSubdirectories(const QList<Subdirectory>& subs)
{
Q_FOREACH(const Subdirectory& s, subs) {
qDebug() << "lala " << s.name;
}
}
QString binaryPath(const QString& sourcedir, const QString& projectSourceDir, const QString projectBinDir)
{
QString binDir = projectBinDir;
// CURRENT_BINARY_DIR must point to the subfolder if any"
if (sourcedir.startsWith(projectSourceDir)) {
Q_ASSERT(projectSourceDir.size()==sourcedir.size() || sourcedir.at(projectSourceDir.size()) == '/');
binDir += sourcedir.mid(projectSourceDir.size());
}
return binDir;
}
KDevelop::ReferencedTopDUContext includeScript(const QString& file, const KDevelop::ReferencedTopDUContext& parent, CMakeProjectData* data, const QString& sourcedir, const QMap<QString, QString>& env)
{
kDebug(9042) << "Running cmake script: " << file;
CMakeFileContent f = CMakeListsParser::readCMakeFile(file);
data->vm.insertGlobal("CMAKE_CURRENT_LIST_FILE", QStringList(file));
data->vm.insertGlobal("CMAKE_CURRENT_LIST_DIR", QStringList(QFileInfo(file).dir().absolutePath()));
const QString projectSourceDir = data->vm.value("CMAKE_SOURCE_DIR").first();
const QString projectBinDir = data->vm.value("CMAKE_BINARY_DIR").join(QString());
data->vm.insertGlobal("CMAKE_CURRENT_BINARY_DIR", QStringList(binaryPath(sourcedir, projectSourceDir, projectBinDir)));
data->vm.insertGlobal("CMAKE_CURRENT_SOURCE_DIR", QStringList(sourcedir));
CMakeProjectVisitor v(file, parent);
v.setCacheValues(&data->cache);
v.setVariableMap(&data->vm);
v.setMacroMap(&data->mm);
v.setModulePath(data->modulePath);
v.setEnvironmentProfile(env);
v.setProperties(data->properties);
v.setDefinitions(data->definitions);
v.walk(f, 0, true);
data->projectName=v.projectName();
data->subdirectories=v.subdirectories();
data->targets=v.targets();
data->properties=v.properties();
data->testSuites=v.testSuites();
data->targetAlias=v.targetAlias();
data->definitions=v.definitions();
//printSubdirectories(data->subdirectories);
data->vm.remove("CMAKE_CURRENT_LIST_FILE");
data->vm.remove("CMAKE_CURRENT_LIST_DIR");
data->vm.remove("CMAKE_CURRENT_SOURCE_DIR");
data->vm.remove("CMAKE_CURRENT_BINARY_DIR");
return v.context();
}
CacheValues readCache(const KDevelop::Path &path)
{
QFile file(path.toLocalFile());
if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
{
kDebug() << "error. Could not find the file" << path;
return CacheValues();
}
CacheValues ret;
QTextStream in(&file);
kDebug(9042) << "Reading cache:" << path;
QStringList currentComment;
while (!in.atEnd())
{
QString line = in.readLine().trimmed();
if(!line.isEmpty() && line[0].isLetter()) //it is a variable
{
CacheLine c;
c.readLine(line);
if(c.flag().isEmpty()) {
ret[c.name()]=CacheEntry(c.value(), currentComment.join("\n"));
currentComment.clear();
}
// kDebug(9042) << "Cache line" << line << c.name();
}
else if(line.startsWith("//"))
currentComment += line.right(line.count()-2);
}
return ret;
}
/**
* Parse a string which contains definition(s), the thing is that in CMake you can do funky things:
*
* add_definitions(-DFOO -DBAR)
* add_definitions("-DLA=LU -DA=B")
* set_property( DIRECTORY PROPERTY COMPILE_DEFINITIONS A AV=1 )
* ...
*
* TODO: this probably fails with defines containing spaces...
*/
static void parseDefinition(const QString& param, const bool expectDashD, const bool remove, CMakeDefinitions* defs)
{
int pos = 0;
while (pos != -1 && pos < param.size()) {
if (param.at(pos).isSpace()) {
++pos;
continue;
} else if (expectDashD) {
if (param.midRef(pos, 2) != QLatin1String("-D")) {
pos = param.indexOf(' ', pos);
continue;
}
pos += 2;
}
const int eq = param.indexOf('=', pos);
const int space = param.indexOf(' ', pos);
QString key;
QString value;
if (eq != -1 && (eq < space || space == -1)) {
key = param.mid(pos, eq - pos);
if (!remove) {
value = param.mid(eq + 1, space - (eq + 1));
}
} else {
key = param.mid(pos, space - pos);
}
if (remove) {
defs->remove(key);
} else {
defs->insert(key, value);
}
pos = space;
}
}
void addDefinitions(const QStringList& definitions, CMakeDefinitions* to, const bool expectDashD)
{
Q_ASSERT(to);
foreach(const QString& v, definitions) {
parseDefinition(v, expectDashD, false, to);
}
}
void removeDefinitions(const QStringList& definitions, CMakeDefinitions* from, const bool expectDashD)
{
Q_ASSERT(from);
foreach(const QString& v, definitions) {
parseDefinition(v, expectDashD, true, from);
}
}
}