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

2596 lines
88 KiB
C++

/* KDevelop CMake Support
*
* Copyright 2007-2008 Aleix Pol <aleixpol@gmail.com>
*
* 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 "cmakeprojectvisitor.h"
#include "cmakeast.h"
#include "cmakecondition.h"
#include "astfactory.h"
#include "cmakeduchaintypes.h"
#include "cmakeparserutils.h"
#include <language/editor/simplerange.h>
#include <language/duchain/topducontext.h>
#include <language/duchain/duchain.h>
#include <language/duchain/duchainlock.h>
#include <language/duchain/parsingenvironment.h>
#include <language/duchain/declaration.h>
#include <language/duchain/types/functiontype.h>
#include <language/duchain/types/delayedtype.h>
#include <language/duchain/problem.h>
#include <KProcess>
#include <KLocale>
#include <KDebug>
#include <QHash>
#include <QQueue>
#include <QFile>
#include <QDir>
#include <QtCore/qglobal.h>
#include <QByteArray>
#include <QFileInfo>
#include <QScriptEngine>
#include <QScriptValue>
using namespace KDevelop;
static void debugMsgs(const QString& message) { kDebug(9032) << "message:" << message; }
static bool isGenerated(const QString& name)
{
return name.indexOf("#[")>=0;
}
CMakeProjectVisitor::message_callback CMakeProjectVisitor::s_msgcallback=debugMsgs;
CMakeProjectVisitor::CMakeProjectVisitor(const QString& root, ReferencedTopDUContext parent)
: m_root(root), m_vars(0), m_macros(0), m_cache(0)
, m_topctx(0), m_parentCtx(parent), m_hitBreak(false), m_hitReturn(false)
{
}
QStringList CMakeProjectVisitor::envVarDirectories(const QString &varName) const
{
QString env;
QMap<QString, QString>::const_iterator it=m_environmentProfile.constFind(varName);
if(it!=m_environmentProfile.constEnd())
env = *it;
else
env = QString::fromLatin1(qgetenv(varName.toLatin1()));
// kDebug(9042) << ".......resolving env:" << varName << "=" << QProcess::systemEnvironment() << env;
if(!env.isEmpty())
{
QChar separator;
#ifdef Q_OS_WIN
separator = ';';
#else
separator = ':';
#endif
kDebug(9042) << "resolving env:" << varName << "=" << env;
return env.split(separator);
}
else
{
kDebug(9032) << "warning:" << varName << " not found";
return QStringList();
}
}
QList< CMakeProjectVisitor::IntPair > CMakeProjectVisitor::parseArgument(const QString &exp)
{
QString name;
QStack<int> opened;
QList< IntPair > pos;
bool gotDollar=false;
for(int i=exp.indexOf('$'); i<exp.size() && i>=0; i++)
{
switch(exp[i].unicode())
{
case '$':
gotDollar=true;
break;
case '{':
if(gotDollar)
{
opened.push(i);
}
gotDollar=false;
break;
case '}':
if(!opened.isEmpty()) {
// note: don't merge this into the function call below,
// the evaluation order is undefined then!
int start = opened.pop();
pos.append(IntPair(start, i, opened.count() + 1));
}
break;
}
}
for(int i=pos.count()-1; i>=0 && !opened.isEmpty(); i--)
{
if(pos[i].first==opened.top())
opened.pop();
pos[i].level -= opened.size();
}
return pos;
}
QStringList CMakeProjectVisitor::variableValue(const QString& var) const
{
VariableMap::const_iterator it=m_vars->constFind(var);
if(it!=m_vars->constEnd())
return *it;
else {
CacheValues::const_iterator it=m_cache->constFind(var);
if(it!=m_cache->constEnd())
return it->value.split(';');
}
return QStringList();
}
QStringList CMakeProjectVisitor::theValue(const QString& exp, const IntPair& thecase) const
{
int dollar=exp.lastIndexOf('$', thecase.first);
QString type=exp.mid(dollar+1, thecase.first-dollar-1);
QString var=exp.mid(thecase.first+1, thecase.second-thecase.first-1);
QStringList value;
// kDebug() << "lalalallalala" << exp << thecase.print();
if(type.isEmpty())
{
value=variableValue(var);
}
else if(type=="ENV")
{
value=envVarDirectories(var);
}
else
kDebug() << "error: I do not understand the key: " << type;
// kDebug() << "solving: " << var << vars << exp;
return value;
}
static QString replaceOne(const QString& var, const QString& id, const QString& value, int dollar)
{
// kDebug() << "ooo" << var << value << id << var[dollar+id.size()-1] << (dollar+id.size());
// kDebug() << "kkkk" << var.mid(0, dollar) << value << var.mid(dollar+id.size(), var.size()-(dollar+id.size()));
return var.mid(0, dollar)+value+var.mid(dollar+id.size(), var.size()-(dollar+id.size()));
}
QStringList CMakeProjectVisitor::value(const QString& exp, const QList<IntPair>& poss, int& desired) const
{
QString var=exp;
QList<IntPair> invars;
invars += poss[desired];
//kDebug() << ">>>>>" << exp << desired << poss.count();
for(; desired+1<poss.size() && poss[desired].level>1; desired++)
{
invars+=poss[desired+1];
//kDebug() << "poss@"<< desired+1 << "="<< poss[desired+1].print();
}
//kDebug() << ";;;;;" << invars.count();
if(invars.count()>1)
{
QList<IntPair>::const_iterator itConstEnd=invars.constEnd();
QList<IntPair>::iterator itEnd=invars.end();
QList<IntPair>::iterator itBegin=invars.begin();
for(QList<IntPair>::const_iterator it=invars.constBegin(); (it+1)!=itConstEnd; ++it)
{
const IntPair& subvar=*it;
int dollar=var.lastIndexOf('$', subvar.first);
QString id=var.mid(dollar, subvar.second-dollar+1), value=theValue(var, subvar).join(QChar(';'));
int diff=value.size()-id.size();
for(QList<IntPair>::iterator it=itBegin; it!=itEnd; ++it)
{
if(it->first > subvar.first) it->first += diff;
if(it->second> subvar.second) it->second+= diff;
}
var=replaceOne(var, id, value, dollar);
}
}
return theValue(var, invars.last());
}
QStringList CMakeProjectVisitor::resolveVariable(const CMakeFunctionArgument &exp)
{
QStringList ret;
ret += QString();
QList< IntPair > var = parseArgument(exp.value);
int i=0;
IntPair last(-1,-1, 0);
for(QList<IntPair>::const_iterator it=var.constBegin(); it!=var.constEnd(); ++it, ++i)
{
while(it!=var.constEnd() && it->level>1)
++it;
const IntPair& p=*it;
// kDebug () << "reeeeeet" << ret << exp.value << p.print();
int dollar=exp.value.lastIndexOf('$', p.first);
QString pre=exp.value.mid(last.second+1, dollar-last.second-1);
QStringList vars = value(exp.value, var, i);
// kDebug() << "aaaaaaaaaA" << pre << vars;
if(!vars.isEmpty())
{
pre+=vars.takeFirst();
}
ret.last()+=pre;
ret += vars;
last=p;
// kDebug() << "yaaaaaaa" << ret;
// i++;
}
ret.last().append(exp.value.mid(last.second+1, exp.value.count()-last.second));
if(exp.quoted) {
ret=QStringList(ret.join(QChar(';')));
} else if(ret.size()==1 && ret.first().isEmpty()) {
ret.clear();
}
return ret;
}
bool CMakeProjectVisitor::hasMacro(const QString& name) const
{
Q_ASSERT(m_macros);
return m_macros->contains(name);
}
int CMakeProjectVisitor::visit(const CMakeAst *ast)
{
kDebug(9042) << "error! function not implemented" << ast->content()[ast->line()].name;
foreach(const CMakeFunctionArgument& arg, ast->outputArguments())
{
//NOTE: this is a workaround, but fixes some issues.
kDebug(9042) << "reseting: " << arg.value;
m_vars->insert(arg.value, QStringList());
}
return 1;
}
int CMakeProjectVisitor::visit( const AddTestAst * test)
{
Test t;
t.name = test->testName();
t.executable = test->exeName();
t.arguments = test->testArgs();
// Strip the extensions and full path added by kde4_add_unit_test,
//this way it's much more useful, e.g. we can pass it to gdb
if (t.executable.endsWith(".shell"))
{
t.executable.chop(6);
}
else if (t.executable.endsWith(".bat"))
{
t.executable.chop(4);
}
kDebug(9042) << "AddTestAst" << t.executable;
m_testSuites << t;
return 1;
}
int CMakeProjectVisitor::visit(const ProjectAst *project)
{
m_projectName = project->projectName();
m_vars->insertGlobal("CMAKE_PROJECT_NAME", QStringList(project->projectName()));
m_vars->insert("PROJECT_NAME", QStringList(project->projectName()));
m_vars->insertGlobal("PROJECT_SOURCE_DIR", m_vars->value("CMAKE_CURRENT_SOURCE_DIR"));
m_vars->insertGlobal("PROJECT_BINARY_DIR", m_vars->value("CMAKE_CURRENT_BINARY_DIR"));
m_vars->insertGlobal(QString("%1_SOURCE_DIR").arg(m_projectName), m_vars->value("CMAKE_CURRENT_SOURCE_DIR"));
m_vars->insertGlobal(QString("%1_BINARY_DIR").arg(m_projectName), m_vars->value("CMAKE_CURRENT_BINARY_DIR"));
return 1;
}
int CMakeProjectVisitor::visit( const SetTargetPropsAst * targetProps)
{
kDebug(9042) << "setting target props for " << targetProps->targets() << targetProps->properties();
foreach(const QString& _tname, targetProps->targets())
{
QString tname = m_targetAlias.value(_tname, _tname);
foreach(const SetTargetPropsAst::PropPair& t, targetProps->properties())
{
m_props[TargetProperty][tname][t.first] = t.second.split(';');
}
}
return 1;
}
int CMakeProjectVisitor::visit( const SetDirectoryPropsAst * dirProps)
{
QString dir=m_vars->value("CMAKE_CURRENT_SOURCE_DIR").join(QString());
kDebug(9042) << "setting directory props for " << dirProps->properties() << dir;
QMap<QString, QStringList>& dprops = m_props[DirectoryProperty][dir];
foreach(const SetDirectoryPropsAst::PropPair& t, dirProps->properties())
{
dprops[t.first] = t.second.split(';');
}
return 1;
}
int CMakeProjectVisitor::visit( const GetTargetPropAst * prop)
{
QString targetName = prop->target();
kDebug(9042) << "getting target " << targetName << " prop " << prop->property() << prop->variableName();
QStringList value;
CategoryType& category = m_props[TargetProperty];
CategoryType::iterator itTarget = category.find(m_targetAlias.value(targetName, targetName));
if(itTarget!=category.end()) {
QMap<QString, QStringList>& targetProps = itTarget.value();
if(!targetProps.contains(prop->property())) {
if(prop->property().startsWith("LOCATION_") && targetProps.contains("IMPORTED_"+prop->property()))
targetProps[prop->property()] = targetProps["IMPORTED_"+prop->property()];
}
value = targetProps.value(prop->property());
}
if(value.isEmpty())
value += QString(prop->variableName()+"-NOTFOUND");
m_vars->insert(prop->variableName(), value);
// kDebug(9042) << "goooooot" << m_vars->value(prop->variableName());
return 1;
}
int CMakeProjectVisitor::visit(const AddSubdirectoryAst *subd)
{
kDebug(9042) << "adding subdirectory" << subd->sourceDir();
VisitorState p=stackTop();
Subdirectory d;
d.name=subd->sourceDir();
d.build_dir=subd->binaryDir().isEmpty() ? d.name : subd->binaryDir();
d.desc=p.code->at(p.line);
m_subdirectories += d;
return 1;
}
int CMakeProjectVisitor::visit(const SubdirsAst *sdirs)
{
kDebug(9042) << "adding subdirectories" << sdirs->directories() << sdirs->exluceFromAll();
VisitorState p=stackTop();
CMakeFunctionDesc desc=p.code->at(p.line);
foreach(const QString& dir, sdirs->directories() + sdirs->exluceFromAll()) {
Subdirectory d;
d.name=dir;
d.build_dir=dir;
d.desc=desc;
m_subdirectories += d;
}
return 1;
}
void CMakeProjectVisitor::printBacktrace(const QStack<VisitorState> &backtrace)
{
int i=0;
kDebug() << "backtrace" << backtrace.count();
foreach(const VisitorState& v, backtrace)
{
if(v.code->count()>v.line)
kDebug(9042) << i << ": ";// << v.code->at(v.line).name;
else
kDebug(9042) << i << ": ------------------------";
i++;
}
}
CMakeProjectVisitor::VisitorState CMakeProjectVisitor::stackTop() const
{
VisitorState p = {};
QString filename=m_backtrace.front().code->at(m_backtrace.front().line).filePath;
QStack<VisitorState>::const_iterator it=m_backtrace.constBegin();
for(; it!=m_backtrace.constEnd(); ++it)
{
if(filename!=it->code->at(it->line).filePath)
break;
p=*it;
}
return p;
}
void CMakeProjectVisitor::defineTarget(const QString& _id, const QStringList& sources, Target::Type t)
{
QString id = _id.isEmpty() ? "<wrong-target>" : _id;
kDebug(9042) << "Defining target" << id;
if (m_targetForId.contains(id))
kDebug(9032) << "warning! there already was a target called" << id;
VisitorState p=stackTop();
Declaration *d=0;
if(!p.code->at(p.line).arguments.isEmpty()) {
DUChainWriteLocker lock(DUChain::lock());
d= new Declaration(p.code->at(p.line).arguments.first().range(), p.context);
d->setIdentifier( Identifier(id) );
AbstractType::Ptr targetType(new TargetType);
d->setAbstractType(targetType);
}
QMap<QString, QStringList>& targetProps = m_props[TargetProperty][id];
QString exe=id, locationDir;
switch(t) {
case Target::Executable: {
exe += m_vars->value("CMAKE_EXECUTABLE_SUFFIX").join(QString());
locationDir = m_vars->value("CMAKE_RUNTIME_OUTPUT_DIRECTORY").join(QString());
targetProps["RUNTIME_OUTPUT_DIRECTORY"] = QStringList(locationDir);
} break;
case Target::Library: {
exe = QString("%1%2%3").arg(m_vars->value("CMAKE_LIBRARY_PREFIX").join(QString()))
.arg(id)
.arg(m_vars->value("CMAKE_LIBRARY_SUFFIX").join(QString()));
locationDir = m_vars->value("CMAKE_LIBRARY_OUTPUT_DIRECTORY").join(QString());
targetProps["LIBRARY_OUTPUT_DIRECTORY"] = QStringList(locationDir);
} break;
case Target::Custom:
break;
}
if(locationDir.isEmpty()) {
locationDir = m_vars->value("CMAKE_CURRENT_BINARY_DIR").join(QString());
}
Target target;
target.name=id;
target.declaration=IndexedDeclaration(d);
target.files=sources;
target.type=t;
target.desc=p.code->at(p.line);
m_targetForId[target.name]=target;
if(CMakeCondition::textIsTrue(m_vars->value("CMAKE_INCLUDE_CURRENT_DIR_IN_INTERFACE").join(QString())))
targetProps["INTERFACE_INCLUDE_DIRECTORIES"] = (m_vars->value("CMAKE_CURRENT_BINARY_DIR") + m_vars->value("CMAKE_CURRENT_SOURCE_DIR"));
targetProps["OUTPUT_NAME"] = QStringList(exe);
targetProps["LOCATION"] = QStringList(locationDir+'/'+exe);
}
int CMakeProjectVisitor::visit(const AddExecutableAst *exec)
{
if(!exec->isImported())
defineTarget(exec->executable(), exec->sourceLists(), Target::Executable);
else
kDebug(9042) << "imported executable" << exec->executable();
kDebug(9042) << "exec:" << exec->executable() << "->" << m_targetForId.contains(exec->executable())
<< "imported" << exec->isImported();
return 1;
}
int CMakeProjectVisitor::visit(const AddLibraryAst *lib)
{
if(lib->isAlias())
m_targetAlias[lib->libraryName()] = lib->aliasTarget();
else if(!lib->isImported())
defineTarget(lib->libraryName(), lib->sourceLists(), Target::Library);
kDebug(9042) << "lib:" << lib->libraryName();
return 1;
}
int CMakeProjectVisitor::visit(const SetAst *set)
{
//TODO: Must deal with ENV{something} case
if(set->storeInCache()) {
QStringList values;
CacheValues::const_iterator itCache= m_cache->constFind(set->variableName());
if(itCache!=m_cache->constEnd())
values = itCache->value.split(';');
else
values = set->values();
m_vars->insertGlobal(set->variableName(), values);
} else
m_vars->insert(set->variableName(), set->values(), set->parentScope());
// kDebug(9042) << "setting variable:" << set->variableName() << set->parentScope()
// << "to" << m_vars->value(set->variableName()) << set->storeInCache()
// ;
return 1;
}
int CMakeProjectVisitor::visit(const UnsetAst* unset)
{
if(unset->env()) {
kDebug(9032) << "error! can't unset the env var: " << unset->variableName();
} else {
m_vars->remove(unset->variableName());
if(unset->cache()) {
kDebug(9032) << "error! can't unset the cached var: " << unset->variableName();
}
}
kDebug(9042) << "unset variable:" << unset->variableName();
return 1;
}
int CMakeProjectVisitor::visit(const IncludeDirectoriesAst * dirs)
{
kDebug(9042) << "adding include directories" << dirs->includedDirectories();
IncludeDirectoriesAst::IncludeType t = dirs->includeType();
QStringList toInclude = dirs->includedDirectories();
if(t==IncludeDirectoriesAst::Default)
{
if(m_vars->value("CMAKE_INCLUDE_DIRECTORIES_BEFORE")==QStringList("ON"))
t = IncludeDirectoriesAst::Before;
else
t = IncludeDirectoriesAst::After;
}
QString dir = m_vars->value("CMAKE_CURRENT_SOURCE_DIR").join(QString());
QStringList& v = m_props[DirectoryProperty][dir]["INCLUDE_DIRECTORIES"];
if(t==IncludeDirectoriesAst::After)
v += toInclude;
else {
v = toInclude + v;
}
kDebug(9042) << "done." << v;
return 1;
}
QString CMakeProjectVisitor::findFile(const QString &file, const QStringList &folders,
const QStringList& suffixes, bool location)
{
if( file.isEmpty() || QFileInfo(file).isAbsolute() )
return file;
QStringList suffixFolders, useSuffixes(suffixes);
useSuffixes.prepend(QString());
foreach(const QString& apath, folders)
{
foreach(const QString& suffix, useSuffixes)
{
suffixFolders.append(apath+'/'+suffix);
}
}
suffixFolders.removeDuplicates();
KUrl path;
foreach(const QString& mpath, suffixFolders)
{
if(mpath.isEmpty())
continue;
KUrl afile(mpath);
afile.addPath(file);
kDebug(9042) << "Trying:" << mpath << '.' << file;
QFileInfo f(afile.toLocalFile());
if(f.exists() && f.isFile())
{
if(location)
path=mpath;
else
path=afile;
break;
}
}
//kDebug(9042) << "find file" << file << "into:" << folders << "found at:" << path;
return path.toLocalFile(KUrl::RemoveTrailingSlash);
}
int CMakeProjectVisitor::visit(const IncludeAst *inc)
{
Q_ASSERT(m_vars->contains("CMAKE_CURRENT_SOURCE_DIR"));
const QStringList modulePath = m_vars->value("CMAKE_MODULE_PATH") + m_modulePath + m_vars->value("CMAKE_CURRENT_SOURCE_DIR");
kDebug(9042) << "Include:" << inc->includeFile() << "@" << modulePath << " into ";
QString possib=inc->includeFile();
QString path;
if(!KUrl(possib).isRelative() && QFile::exists(possib))
path=possib;
else
{
if(!possib.contains('.'))
possib += ".cmake";
path=findFile(possib, modulePath);
}
if(!path.isEmpty())
{
m_vars->insertMulti("CMAKE_CURRENT_LIST_FILE", QStringList(path));
m_vars->insertMulti("CMAKE_CURRENT_LIST_DIR", QStringList(KUrl(path).directory()));
CMakeFileContent include = CMakeListsParser::readCMakeFile(path);
if ( !include.isEmpty() )
{
kDebug(9042) << "including:" << path;
walk(include, 0, true);
m_hitReturn = false;
}
else
{
//FIXME: Put here the error.
kDebug(9042) << "Include. Parsing error.";
}
Q_ASSERT(m_vars->value("CMAKE_CURRENT_LIST_FILE")==QStringList(path));
m_vars->removeMulti("CMAKE_CURRENT_LIST_FILE");
m_vars->removeMulti("CMAKE_CURRENT_LIST_DIR");
}
else
{
if(!inc->optional())
{
kDebug(9032) << "error!! Could not find" << inc->includeFile() << "=" << possib << "into" << modulePath;
}
}
if(!inc->resultVariable().isEmpty())
{
QString result="NOTFOUND";
if(!path.isEmpty())
result=path;
m_vars->insert(inc->resultVariable(), QStringList(result));
}
kDebug(9042) << "include of" << inc->includeFile() << "done.";
return 1;
}
int CMakeProjectVisitor::visit(const FindPackageAst *pack)
{
m_vars->remove(pack->name()+"-NOTFOUND");
kDebug(9042) << "Find:" << pack->name() << "package." << pack->version() << m_modulePath << "No module: " << pack->noModule();
QStringList possibleModuleNames;
if(!pack->noModule()) //TODO Also implied by a whole slew of additional options.
{
// Look for a Find{package}.cmake
QString possib=pack->name();
if(!possib.endsWith(".cmake"))
possib += ".cmake";
possib.prepend("Find");
possibleModuleNames += possib;
}
const QStringList modulePath = m_vars->value("CMAKE_MODULE_PATH") + m_modulePath + pack->paths();
QString name=pack->name();
QStringList postfix=QStringList() << QString() << "/cmake" << "/CMake";
QStringList configPath;
QStringList lookupPaths = envVarDirectories("CMAKE_PREFIX_PATH") + m_vars->value("CMAKE_PREFIX_PATH")
+ m_vars->value("CMAKE_SYSTEM_PREFIX_PATH");
// note: should note be done if NO_SYSTEM_ENVIRONMENT_PATH is set, see docs:
/* 4. Search the standard system environment variables. This can be skipped
* if NO_SYSTEM_ENVIRONMENT_PATH is passed. Path entries ending in "/bin" or
* "/sbin" are automatically converted to their parent directories.
*/
foreach(const QString& lookup, envVarDirectories("PATH"))
{
if (lookup.endsWith("/bin")) {
lookupPaths << lookup.left(lookup.length() - 4);
} else if (lookup.endsWith("/sbin")) {
lookupPaths << lookup.left(lookup.length() - 5);
} else {
lookupPaths << lookup;
}
}
const bool useLib64 = m_props[GlobalProperty][QString()]["FIND_LIBRARY_USE_LIB64_PATHS"].contains("TRUE");
QSet<QString> handled;
foreach(const QString& lookup, lookupPaths)
{
if(!QFile::exists(lookup) || handled.contains(lookup)) {
continue;
}
foreach(const QString& post, postfix)
{
configPath.prepend(lookup+"/share/"+name.toLower()+post);
configPath.prepend(lookup+"/share/"+name+post);
configPath.prepend(lookup+"/share/cmake/"+name.toLower()+post);
configPath.prepend(lookup+"/share/cmake/"+name+post);
configPath.prepend(lookup+"/lib/"+name.toLower()+post);
configPath.prepend(lookup+"/lib/"+name+post);
configPath.prepend(lookup+"/lib/cmake/"+name.toLower()+post);
configPath.prepend(lookup+"/lib/cmake/"+name+post);
if (useLib64) {
configPath.prepend(lookup+"/lib64/"+name.toLower()+post);
configPath.prepend(lookup+"/lib64/"+name+post);
configPath.prepend(lookup+"/lib64/cmake/"+name.toLower()+post);
configPath.prepend(lookup+"/lib64/cmake/"+name+post);
}
}
handled << lookup;
}
QString varName=pack->name()+"_DIR";
if(m_cache->contains(varName))
configPath.prepend(m_cache->value(varName).value);
QStringList possibleConfigNames;
possibleConfigNames+=QString("%1Config.cmake").arg(pack->name());
possibleConfigNames+=QString("%1-config.cmake").arg(pack->name().toLower());
bool isConfig=false;
QString path;
foreach(const QString& possib, possibleConfigNames) {
path = findFile(possib, configPath);
if (!path.isEmpty()) {
m_vars->insertGlobal(pack->name()+"_DIR", QStringList(KUrl(path).directory()));
isConfig=true;
break;
}
}
if (path.isEmpty()) {
foreach(const QString& possib, possibleModuleNames)
{
path=findFile(possib, modulePath);
if(!path.isEmpty()) {
break;
}
}
}
if(!path.isEmpty())
{
m_vars->insertMulti("CMAKE_CURRENT_LIST_FILE", QStringList(path));
m_vars->insertMulti("CMAKE_CURRENT_LIST_DIR", QStringList(KUrl(path).directory()));
if(pack->isRequired())
m_vars->insert(pack->name()+"_FIND_REQUIRED", QStringList("TRUE"));
if(pack->isQuiet())
m_vars->insert(pack->name()+"_FIND_QUIET", QStringList("TRUE"));
if(!pack->components().isEmpty()) m_vars->insert(pack->name()+"_FIND_COMPONENTS", pack->components());
m_vars->insert(pack->name()+"_FIND_VERSION", QStringList(pack->version()));
QStringList version = pack->version().split('.');
if(version.size()>=1) m_vars->insert(pack->name()+"_FIND_VERSION_MAJOR", QStringList(version[0]));
if(version.size()>=2) m_vars->insert(pack->name()+"_FIND_VERSION_MINOR", QStringList(version[1]));
if(version.size()>=3) m_vars->insert(pack->name()+"_FIND_VERSION_PATCH", QStringList(version[2]));
if(version.size()>=4) m_vars->insert(pack->name()+"_FIND_VERSION_TWEAK", QStringList(version[3]));
m_vars->insert(pack->name()+"_FIND_VERSION_COUNT", QStringList(QString::number(version.size())));
CMakeFileContent package=CMakeListsParser::readCMakeFile( path );
if ( !package.isEmpty() )
{
path=KUrl(path).pathOrUrl();
kDebug(9042) << "================== Found" << path << "===============";
walk(package, 0, true);
m_hitReturn = false;
}
else
{
kDebug(9032) << "error: find_package. Parsing error." << path;
}
if(pack->noModule())
{
m_vars->insertGlobal(QString("%1_CONFIG").arg(pack->name()), QStringList(path));
}
m_vars->removeMulti("CMAKE_CURRENT_LIST_FILE");
m_vars->removeMulti("CMAKE_CURRENT_LIST_DIR");
if(isConfig) {
m_vars->insert(pack->name()+"_FOUND", QStringList("TRUE"));
m_vars->insert(pack->name().toUpper()+"_FOUND", QStringList("TRUE"));
}
}
else
{
if(pack->isRequired()) {
//FIXME: Put here the error.
kDebug(9032) << "error: Could not find" << pack->name() << "into" << modulePath;
}
m_vars->insertGlobal(QString("%1_DIR").arg(pack->name()), QStringList(QString("%1_DIR-NOTFOUND").arg(pack->name())));
}
kDebug(9042) << "Exit. Found:" << pack->name() << m_vars->value(pack->name()+"_FOUND");
return 1;
}
KDevelop::ReferencedTopDUContext CMakeProjectVisitor::createContext(const IndexedString& idxpath, ReferencedTopDUContext aux,
int endl ,int endc, bool isClean)
{
DUChainWriteLocker lock(DUChain::lock());
KDevelop::ReferencedTopDUContext topctx=DUChain::self()->chainForDocument(idxpath);
if(topctx)
{
if(isClean) {
topctx->deleteLocalDeclarations();
topctx->deleteChildContextsRecursively();
topctx->deleteUses();
}
foreach(DUContext* importer, topctx->importers())
importer->removeImportedParentContext(topctx);
topctx->clearImportedParentContexts();
}
else
{
ParsingEnvironmentFile* env = new ParsingEnvironmentFile(idxpath);
env->setLanguage(IndexedString("cmake"));
topctx=new TopDUContext(idxpath, RangeInRevision(0,0, endl, endc), env);
DUChain::self()->addDocumentChain(topctx);
Q_ASSERT(DUChain::self()->chainForDocument(idxpath));
}
//Clean the re-used top-context. This is problematic since it may affect independent projects, but it's better then letting things accumulate.
///@todo This is problematic when the same file is used from within multiple CMakeLists.txts,
/// for example a standard import like FindKDE4.cmake, because it creates a cross-dependency
/// between the topducontext's of independent projects, like for example kdebase and kdevplatform
///@todo Solve that by creating unique versions of all used top-context on a per-project basis using ParsingEnvironmentFile for disambiguation.
topctx->addImportedParentContext(aux);
/// @todo should we check for NULL or assert?
if (aux)
aux->addImportedParentContext(topctx);
return topctx;
}
bool CMakeProjectVisitor::haveToFind(const QString &varName)
{
if(m_vars->contains(varName+"_FOUND"))
return false;
m_vars->remove(varName+"-NOTFOUND");
return true;
}
int CMakeProjectVisitor::visit(const FindProgramAst *fprog)
{
if(!haveToFind(fprog->variableName()))
return 1;
if(m_cache->contains(fprog->variableName()))
{
kDebug(9042) << "FindProgram: cache" << fprog->variableName() << m_cache->value(fprog->variableName()).value;
return 1;
}
QStringList modulePath = fprog->path();
#ifdef Q_OS_WIN
if(!fprog->noSystemEnvironmentPath() && !fprog->noDefaultPath())
modulePath += envVarDirectories("Path");
kDebug() << "added Path env for program finding" << envVarDirectories("Path");
#else
if(!fprog->noSystemEnvironmentPath() && !fprog->noDefaultPath())
modulePath += envVarDirectories("PATH");
#endif
kDebug(9042) << "Find:" << fprog->variableName() << fprog->filenames() << "program into" << modulePath<<":"<< fprog->path();
QString path;
foreach(const QString& filename, fprog->filenames())
{
path=findExecutable(filename, modulePath, fprog->pathSuffixes());
if(!path.isEmpty())
break;
}
if(!path.isEmpty())
m_vars->insertGlobal(fprog->variableName(), QStringList(path));
else
m_vars->insertGlobal(fprog->variableName()+"-NOTFOUND", QStringList());
kDebug(9042) << "FindProgram:" << fprog->variableName() << "=" << m_vars->value(fprog->variableName()) << modulePath;
return 1;
}
QString CMakeProjectVisitor::findExecutable(const QString& file,
const QStringList& directories, const QStringList& pathSuffixes) const
{
QString path;
QStringList suffixes=m_vars->value("CMAKE_EXECUTABLE_SUFFIX");
suffixes.prepend(QString());
kDebug() << "finding executable, using suffixes" << suffixes;
foreach(const QString& suffix, suffixes)
{
path=findFile(file+suffix, directories, pathSuffixes);
if(!path.isEmpty())
break;
}
return path;
}
int CMakeProjectVisitor::visit(const FindPathAst *fpath)
{
if(!haveToFind(fpath->variableName()))
return 1;
if(m_cache->contains(fpath->variableName()))
{
kDebug() << "FindPath: cache" << fpath->variableName();
return 1;
}
QStringList locationOptions = fpath->path()+fpath->hints();
QStringList path, files=fpath->filenames();
QStringList suffixes=fpath->pathSuffixes();
if(!fpath->noDefaultPath())
{
QStringList pp = envVarDirectories("CMAKE_PREFIX_PATH") + m_vars->value("CMAKE_PREFIX_PATH");
foreach(const QString& path, pp) {
locationOptions += path+"/include";
}
locationOptions += pp;
locationOptions += envVarDirectories("CMAKE_INCLUDE_PATH") + m_vars->value("CMAKE_INCLUDE_PATH");
locationOptions += m_vars->value("CMAKE_FRAMEWORK_PATH");
pp=m_vars->value("CMAKE_SYSTEM_PREFIX_PATH");
foreach(const QString& path, pp) {
locationOptions += path+"/include";
}
locationOptions += m_vars->value("CMAKE_SYSTEM_INCLUDE_PATH");
locationOptions += m_vars->value("CMAKE_SYSTEM_FRAMEWORK_PATH");
}
kDebug(9042) << "Find:" << /*locationOptions << "@" <<*/ fpath->variableName() << /*"=" << files <<*/ " path.";
foreach(const QString& p, files)
{
QString p1=findFile(p, locationOptions, suffixes, true);
if(p1.isEmpty())
{
kDebug(9042) << p << "not found";
}
else
{
path += p1;
}
}
if(!path.isEmpty())
{
m_vars->insertGlobal(fpath->variableName(), QStringList(path));
}
else
{
kDebug(9042) << "Path not found";
}
kDebug(9042) << "Find path: " << fpath->variableName() << m_vars->value(fpath->variableName());
// m_vars->insert(fpath->variableName()+"-NOTFOUND", QStringList());
return 1;
}
int CMakeProjectVisitor::visit(const FindLibraryAst *flib)
{
if(!haveToFind(flib->variableName()))
return 1;
if(m_cache->contains(flib->variableName()))
{
kDebug(9042) << "FindLibrary: cache" << flib->variableName();
return 1;
}
QStringList locationOptions = flib->path()+flib->hints();
QStringList files=flib->filenames();
QString path;
if(!flib->noDefaultPath())
{
QStringList opt = envVarDirectories("CMAKE_PREFIX_PATH") + m_vars->value("CMAKE_PREFIX_PATH");
foreach(const QString& s, opt)
locationOptions.append(s+"/lib");
locationOptions += envVarDirectories("CMAKE_LIBRARY_PATH") + m_vars->value("CMAKE_LIBRARY_PATH");
locationOptions += m_vars->value("CMAKE_FRAMEWORK_PATH");
locationOptions += m_vars->value("CMAKE_SYSTEM_LIBRARY_PATH");
locationOptions += m_vars->value("CMAKE_PLATFORM_IMPLICIT_LINK_DIRECTORIES");
opt=m_vars->value("CMAKE_SYSTEM_PREFIX_PATH");
foreach(const QString& s, opt)
locationOptions.append(s+"/lib");
}
foreach(const QString& p, files)
{
foreach(const QString& prefix, m_vars->value("CMAKE_FIND_LIBRARY_PREFIXES"))
{
foreach(const QString& suffix, m_vars->value("CMAKE_FIND_LIBRARY_SUFFIXES"))
{
QString p1=findFile(prefix+p+suffix, locationOptions, flib->pathSuffixes());
if(p1.isEmpty())
{
kDebug(9042) << p << "not found";
}
else
{
path = p1;
break;
}
}
if(!path.isEmpty())
break;
}
if(!path.isEmpty())
break;
}
if(!path.isEmpty())
{
m_vars->insertGlobal(flib->variableName(), QStringList(path));
}
else
kDebug(9032) << "error. Library" << flib->filenames() << "not found";
// m_vars->insert(fpath->variableName()+"-NOTFOUND", QStringList());
kDebug(9042) << "Find Library:" << flib->filenames() << m_vars->value(flib->variableName());
return 1;
}
int CMakeProjectVisitor::visit(const FindFileAst *ffile)
{
if(!haveToFind(ffile->variableName()))
return 1;
if(m_cache->contains(ffile->variableName()))
{
kDebug(9042) << "FindFile: cache" << ffile->variableName();
return 1;
}
QStringList locationOptions = ffile->path()+ffile->hints();
if(!ffile->noDefaultPath())
{
QStringList pp = envVarDirectories("CMAKE_PREFIX_PATH") + m_vars->value("CMAKE_PREFIX_PATH");
foreach(const QString& path, pp) {
locationOptions += path+"/include";
}
locationOptions += pp;
locationOptions += envVarDirectories("CMAKE_INCLUDE_PATH") + m_vars->value("CMAKE_INCLUDE_PATH");
locationOptions += m_vars->value("CMAKE_FRAMEWORK_PATH");
pp=m_vars->value("CMAKE_SYSTEM_PREFIX_PATH");
foreach(const QString& path, pp) {
locationOptions += path+"/include";
}
locationOptions += m_vars->value("CMAKE_SYSTEM_INCLUDE_PATH");
locationOptions += m_vars->value("CMAKE_SYSTEM_FRAMEWORK_PATH");
}
QStringList path, files=ffile->filenames();
kDebug(9042) << "Find File:" << ffile->filenames();
foreach(const QString& p, files)
{
QString p1=findFile(p, locationOptions, ffile->pathSuffixes());
if(p1.isEmpty())
{
kDebug(9042) << p << "not found";
}
else
{
path += p1;
}
}
if(!path.isEmpty())
{
m_vars->insertGlobal(ffile->variableName(), QStringList(path));
}
else
kDebug(9032) << "error. File" << ffile->filenames() << "not found";
// m_vars->insert(fpath->variableName()+"-NOTFOUND", QStringList());
return 1;
}
int CMakeProjectVisitor::visit(const TryCompileAst *tca)
{
kDebug(9042) << "try_compile" << tca->resultName() << tca->binDir() << tca->source()
<< "cmakeflags" << tca->cmakeFlags() << "outputvar" << tca->outputName();
if(m_projectName.isEmpty())
{
kDebug(9042) << "file compile" << tca->compileDefinitions() << tca->copyFile();
}
else
{
kDebug(9042) << "project compile" << tca->projectName() << tca->targetName();
}
QString value;
CacheValues::const_iterator it=m_cache->constFind(tca->resultName());
if(it!=m_cache->constEnd())
value=it->value;
else
value="TRUE";
m_vars->insert(tca->resultName(), QStringList(value));
return 1;
}
int CMakeProjectVisitor::visit(const TargetLinkLibrariesAst *tll)
{
kDebug(9042) << "target_link_libraries";
QHash<QString, Target>::iterator target = m_targetForId.find(tll->target());
//TODO: we can add a problem if the target is not found
if(target != m_targetForId.end()) {
CategoryType& targetProps = m_props[TargetProperty];
CategoryType::iterator it = targetProps.find(m_targetAlias.value(tll->target(), tll->target()));
(*it)["INTERFACE_LINK_LIBRARIES"] += tll->interfaceOnlyDependencies().retrieveTargets()
<< tll->publicDependencies().retrieveTargets();
(*it)["PRIVATE_LINK_LIBRARIES"] += tll->privateDependencies().retrieveTargets();
}
return 1;
}
int CMakeProjectVisitor::visit(const TargetIncludeDirectoriesAst* tid)
{
CategoryType& targetProps = m_props[TargetProperty];
CategoryType::iterator it = targetProps.find(m_targetAlias.value(tid->target(), tid->target()));
//TODO: we can add a problem if the target is not found
if(it != targetProps.end()) {
QStringList interfaceIncludes, includes;
foreach(const TargetIncludeDirectoriesAst::Item& item, tid->items()) {
if(item.visibility == TargetIncludeDirectoriesAst::Public || item.visibility == TargetIncludeDirectoriesAst::Interface)
interfaceIncludes += item.item;
if(item.visibility == TargetIncludeDirectoriesAst::Public || item.visibility == TargetIncludeDirectoriesAst::Private)
includes += item.item;
}
if(!interfaceIncludes.isEmpty())
(*it)["INTERFACE_INCLUDE_DIRECTORIES"] += interfaceIncludes;
if(!includes.isEmpty())
(*it)["INCLUDE_DIRECTORIES"] += includes;
}
return 1;
}
void CMakeProjectVisitor::macroDeclaration(const CMakeFunctionDesc& def, const CMakeFunctionDesc& end, const QStringList& args)
{
if(def.arguments.isEmpty() || end.arguments.isEmpty())
return;
QString id=def.arguments.first().value.toLower();
Identifier identifier(id);
RangeInRevision sr=def.arguments.first().range();
RangeInRevision endsr=end.arguments.first().range();
DUChainWriteLocker lock;
QList<Declaration*> decls=m_topctx->findDeclarations(identifier);
//Only consider declarations in a CMake file
IndexedString cmakeName("cmake");
for(QList<Declaration*>::iterator it=decls.begin(); it!=decls.end(); ) {
if((*it)->topContext()->parsingEnvironmentFile()->language() == cmakeName)
++it;
else
it = decls.erase(it);
}
int idx;
if(!decls.isEmpty())
{
idx=m_topctx->indexForUsedDeclaration(decls.first());
m_topctx->createUse(idx, sr, 0);
}
else
{
Declaration *d = new Declaration(sr, m_topctx);
d->setIdentifier( identifier );
FunctionType* func=new FunctionType();
foreach(const QString& arg, args)
{
DelayedType *delayed=new DelayedType;
delayed->setIdentifier( IndexedTypeIdentifier(arg) );
func->addArgument(AbstractType::Ptr(delayed));
}
d->setAbstractType( AbstractType::Ptr(func) );
idx=m_topctx->indexForUsedDeclaration(d);
}
m_topctx->createUse(idx, endsr, 0);
}
int CMakeProjectVisitor::visit(const MacroAst *macro)
{
kDebug(9042) << "Adding macro:" << macro->macroName();
Macro m;
m.name = macro->macroName();
m.knownArgs=macro->knownArgs();
m.isFunction=false;
return declareFunction(m, macro->content(), macro->line(), "endmacro");
}
int CMakeProjectVisitor::visit(const FunctionAst *func)
{
kDebug(9042) << "Adding function:" << func->name();
Macro m;
m.name = func->name();
m.knownArgs=func->knownArgs();
m.isFunction=true;
return declareFunction(m, func->content(), func->line(), "endfunction");
}
int CMakeProjectVisitor::declareFunction(Macro m, const CMakeFileContent& content,
int initial, const QString& end)
{
CMakeFileContent::const_iterator it=content.constBegin()+initial;
CMakeFileContent::const_iterator itEnd=content.constEnd();
int lines=0;
for(; it!=itEnd; ++it)
{
if(it->name.toLower()==end)
break;
m.code += *it;
++lines;
}
++lines; //We do not want to return to endmacro
if(it!=itEnd)
{
m_macros->insert(m.name, m);
macroDeclaration(content[initial], content[initial+lines-1], m.knownArgs);
}
return lines;
}
int CMakeProjectVisitor::visit(const MacroCallAst *call)
{
if(m_macros->contains(call->name()))
{
const Macro code=m_macros->value(call->name());
kDebug(9042) << "Running macro:" << call->name() << "params:" << call->arguments() << "=" << code.knownArgs << "for" << code.code.count() << "lines";
if(code.knownArgs.count() > call->arguments().count())
{
kDebug(9032) << "error: more parameters needed when calling" << call->name();
}
else
{
{
DUChainWriteLocker lock;
QList<Declaration*> decls=m_topctx->findDeclarations(Identifier(call->name().toLower()));
if(!decls.isEmpty())
{
int idx=m_topctx->indexForUsedDeclaration(decls.first());
m_topctx->createUse(idx, call->content()[call->line()].nameRange(), 0);
}
}
//Giving value to parameters
QStringList::const_iterator mit = code.knownArgs.constBegin();
QStringList::const_iterator cit = call->arguments().constBegin();
QStringList argn;
int i=0;
while(cit != call->arguments().constEnd())
{
m_vars->insertMulti(QString("ARGV%1").arg(i), QStringList(*cit));
if(mit!=code.knownArgs.constEnd())
{
kDebug(9042) << "param:" << *mit << "=" << *cit;
m_vars->insertMulti(*mit, QStringList(*cit));
mit++;
}
else
{
argn += *cit;
}
cit++;
i++;
}
m_vars->insertMulti("ARGN", argn);
m_vars->insertMulti("ARGV", call->arguments());
m_vars->insertMulti("ARGC", QStringList(QString::number(call->arguments().count())));
kDebug(9042) << "argn=" << m_vars->value("ARGN");
bool isfunc = code.isFunction;
if(isfunc)
m_vars->pushScope();
//Executing
int len = walk(code.code, 1);
kDebug(9042) << "visited!" << call->name() <<
m_vars->value("ARGV") << "_" << m_vars->value("ARGN") << "..." << len;
m_hitReturn = false;
if(isfunc)
m_vars->popScope();
//Restoring
i=1;
foreach(const QString& name, code.knownArgs)
{
m_vars->removeMulti(QString("ARGV%1").arg(i));
m_vars->removeMulti(name);
i++;
}
m_vars->removeMulti("ARGV");
m_vars->removeMulti("ARGC");
m_vars->removeMulti("ARGN");
}
}
else
{
kDebug(9032) << "error: Did not find the macro:" << call->name() << call->content()[call->line()].writeBack();
}
return 1;
}
static void usesForArguments(const QStringList& names, const QList<int>& args, const ReferencedTopDUContext& topctx,
const CMakeFunctionDesc& func)
{
//TODO: Should not return here
if(args.size()!=names.size())
return;
//We define the uses for the used variable without ${}
foreach(int use, args)
{
QString var=names[use];
DUChainWriteLocker lock;
QList<Declaration*> decls=topctx->findDeclarations(Identifier(var));
if(!decls.isEmpty() && func.arguments.count() > use)
{
CMakeFunctionArgument arg=func.arguments[use];
int idx=topctx->indexForUsedDeclaration(decls.first());
topctx->createUse(idx, RangeInRevision(arg.line-1, arg.column-1, arg.line-1, arg.column-1+var.size()), 0);
}
}
}
int CMakeProjectVisitor::visit(const IfAst *ifast) //Highly crappy code
{
int lines=ifast->line();
if( ifast->condition().isEmpty() )
{
const CMakeFunctionDesc d = ifast->content().at( ifast->line() );
kDebug() << "error: couldn't parse condition of an IF in file:" << ifast->condition() << d.filePath << d.line;
}
int inside=0;
bool visited=false;
QList<int> ini;
for(; lines < ifast->content().size(); ++lines)
{
const CMakeFunctionDesc funcDesc = ifast->content().at(lines);
QString funcName=funcDesc.name;
// kDebug(9032) << "looking @" << lines << it->writeBack() << ">>" << inside << visited;
if(funcName=="if")
{
inside++;
}
else if(funcName=="endif")
{
inside--;
if(inside<=0) {
// Q_ASSERT(!ini.isEmpty());
if(!funcDesc.arguments.isEmpty())
usesForArguments(ifast->condition(), ini, m_topctx, funcDesc);
break;
}
// kDebug(9042) << "found an endif at:" << lines << "but" << inside;
}
if(inside==1)
{
bool result = false;
if(funcName=="if" || funcName=="elseif")
{
CMakeCondition cond(this);
IfAst myIf;
QStringList condition;
if(funcName=="if")
{
condition=ifast->condition();
}
else
{
if(!myIf.parseFunctionInfo(resolveVariables(funcDesc)))
kDebug(9042) << "uncorrect condition correct" << funcDesc.writeBack();
condition=myIf.condition();
}
result=cond.condition(condition);
int i=0;
foreach(const QString& match, cond.matches()) {
m_vars->insert(QString("CMAKE_MATCH_%1").arg(i), QStringList(match));
i++;
}
if(funcName=="if")
ini=cond.variableArguments();
usesForArguments(condition, cond.variableArguments(), m_topctx, funcDesc);
kDebug(9042) << ">> " << funcName << condition << result;
}
else if(funcName=="else")
{
kDebug(9042) << ">> else";
result=true;
usesForArguments(ifast->condition(), ini, m_topctx, funcDesc);
}
if(!visited && result)
{
kDebug(9042) << "About to visit " << funcName << "?" << result;
lines = walk(ifast->content(), lines+1)-1;
visited=true;
// kDebug(9042) << "Visited. now in" << it->name;
}
}
}
if(lines >= ifast->content().size())
{
kDebug() << "error. found an unfinished endif";
return ifast->content().size()-ifast->line();
}
else
{
// kDebug() << "finish" << "<>" << ifast->condition() << '|' << lines-ifast->line() << " to " << lines << '<' << ifast->content().size();
// kDebug(9042) << "endif==" << ifast->content()[lines].writeBack();
return lines-ifast->line()+1;
}
}
int CMakeProjectVisitor::visit(const ExecProgramAst *exec)
{
QString execName = exec->executableName();
QStringList argsTemp = exec->arguments();
QStringList args;
foreach(const QString& arg, argsTemp)
{
if(arg.contains("#[bin_dir]")) {
if(!exec->outputVariable().isEmpty())
m_vars->insert(exec->outputVariable(), QStringList("OFF"));
return 1;
}
args += arg.split(' ');
}
kDebug(9042) << "Executing:" << execName << "::" << args << "in" << exec->workingDirectory();
KProcess p;
if(!exec->workingDirectory().isEmpty())
p.setWorkingDirectory(exec->workingDirectory());
p.setOutputChannelMode(KProcess::MergedChannels);
p.setProgram(execName, args);
p.start();
if(!p.waitForFinished())
{
kDebug(9032) << "error: failed to execute:" << execName << "error:" << p.error() << p.exitCode();
}
if(!exec->returnValue().isEmpty())
{
kDebug(9042) << "execution returned: " << exec->returnValue() << " = " << p.exitCode();
m_vars->insert(exec->returnValue(), QStringList(QString::number(p.exitCode())));
}
if(!exec->outputVariable().isEmpty())
{
QByteArray b = p.readAllStandardOutput();
QString t;
t.prepend(b.trimmed());
m_vars->insert(exec->outputVariable(), QStringList(t.trimmed()));
kDebug(9042) << "executed" << execName << "<" << t;
}
return 1;
}
int CMakeProjectVisitor::visit(const ExecuteProcessAst *exec)
{
kDebug(9042) << "executing... " << exec->commands();
QList<KProcess*> procs;
foreach(const QStringList& _args, exec->commands())
{
if(_args.isEmpty())
{
kDebug(9032) << "Error: trying to execute empty command";
break;
}
else
{
foreach(const QString& arg, _args) {
if(arg.contains("#[bin_dir]")) {
if(!exec->outputVariable().isEmpty())
m_vars->insert(exec->outputVariable(), QStringList("OFF"));
return 1;
}
}
}
QString workingDir = exec->workingDirectory();
if(!QFile::exists(workingDir))
{
workingDir = m_vars->value("CMAKE_CURRENT_BINARY_DIR").join(QString());
}
QStringList args(_args);
KProcess *p=new KProcess(), *prev=0;
if(!procs.isEmpty())
{
prev=procs.last();
}
p->setWorkingDirectory(workingDir);
p->setOutputChannelMode(KProcess::MergedChannels);
QString execName=args.takeFirst();
p->setProgram(execName, args);
p->start();
procs.append(p);
kDebug(9042) << "Executing:" << execName << "::" << args /*<< "into" << *m_vars*/;
if(prev)
{
prev->setStandardOutputProcess(p);
}
}
foreach(KProcess* p, procs)
{
if(!p->waitForFinished())
{
kDebug(9042) << "error: failed to execute:" << p;
}
}
if(!procs.isEmpty() && !exec->resultVariable().isEmpty())
{
kDebug(9042) << "execution returned: " << exec->resultVariable() << " = " << procs.last()->exitCode();
m_vars->insert(exec->resultVariable(), QStringList(QString::number(procs.last()->exitCode())));
}
//FIXME: remove condition when filtering bad output
if(!procs.isEmpty() && !exec->outputVariable().isEmpty())
{
QByteArray b = procs.last()->readAllStandardOutput();
QString t;
t.prepend(b.trimmed());
m_vars->insert(exec->outputVariable(), QStringList(t.trimmed().replace("\\", "\\\\")));
kDebug(9042) << "executed " << exec->outputVariable() << "=" << t;
}
qDeleteAll(procs);
return 1;
}
int CMakeProjectVisitor::visit(const FileAst *file)
{
Q_ASSERT(m_vars->contains("CMAKE_CURRENT_SOURCE_DIR"));
switch(file->type()) //TODO
{
case FileAst::Write:
kDebug(9042) << "(ni) File write: " << file->path() << file->message();
break;
case FileAst::Append:
kDebug(9042) << "(ni) File append: " << file->path() << file->message();
break;
case FileAst::Read:
{
KUrl filename=file->path();
QFileInfo ifile(filename.toLocalFile());
kDebug(9042) << "FileAst: reading " << file->path() << ifile.isFile();
if(!ifile.isFile())
return 1;
QFile f(filename.toLocalFile());
if (!f.open(QIODevice::ReadOnly | QIODevice::Text))
return 1;
QString output=f.readAll();
m_vars->insert(file->variable(), QStringList(output));
kDebug(9042) << "FileAst: read ";
}
break;
case FileAst::Glob:
case FileAst::GlobRecurse: {
QStringList matches;
foreach(const QString& expr, file->globbingExpressions())
{
if (expr.isEmpty())
continue;
QString pathPrefix;
if (QDir::isRelativePath(expr) && pathPrefix.isEmpty())
pathPrefix = m_vars->value("CMAKE_CURRENT_SOURCE_DIR").first();
matches.append(traverseGlob(pathPrefix, expr, file->type() == FileAst::GlobRecurse, file->isFollowingSymlinks()));
}
if (!file->path().isEmpty())
{
// RELATIVE was specified, so we need to make all paths relative to file->path()
QDir relative(file->path());
QStringList::iterator endIt = matches.end();
for(QStringList::iterator it = matches.begin(); it != endIt; ++it)
{
*it = relative.relativeFilePath(*it);
}
}
m_vars->insert(file->variable(), matches);
kDebug(9042) << "glob. recurse:" << (file->type() == FileAst::GlobRecurse)
<< "RELATIVE: " << file->path()
<< "FOLLOW_SYMLINKS: " << file->isFollowingSymlinks()
<< ", " << file->globbingExpressions() << ": " << matches;
}
break;
case FileAst::Remove:
case FileAst::RemoveRecurse:
kDebug(9042) << "warning. file-remove or remove_recurse. KDevelop won't remove anything.";
break;
case FileAst::MakeDirectory:
kDebug(9042) << "warning. file-make_directory. KDevelop won't create anything.";
break;
case FileAst::RelativePath:
m_vars->insert(file->variable(), QStringList(KUrl::relativePath(file->directory(), file->path())));
kDebug(9042) << "file relative_path" << file->directory() << file->path();
break;
case FileAst::ToCmakePath:
#ifdef Q_OS_WIN
m_vars->insert(file->variable(), file->path().replace("\\", "/").split(';'));
#else
m_vars->insert(file->variable(), file->path().split(':'));
#endif
kDebug(9042) << "file TO_CMAKE_PATH variable:" << file->variable() << "="
<< m_vars->value(file->variable()) << "path:" << file->path();
break;
case FileAst::ToNativePath:
m_vars->insert(file->variable(), QStringList(file->path().replace('/', QDir::separator())));
kDebug(9042) << "file TO_NATIVE_PATH variable:" << file->variable() << "="
<< m_vars->value(file->variable()) << "path:" << file->path();
break;
case FileAst::Strings: {
KUrl filename=file->path();
QFileInfo ifile(filename.toLocalFile());
kDebug(9042) << "FileAst: reading " << file->path() << ifile.isFile();
if(!ifile.isFile())
return 1;
QFile f(filename.toLocalFile());
if (!f.open(QIODevice::ReadOnly | QIODevice::Text))
return 1;
QStringList output=QString(f.readAll()).split('\n');
if(!file->regex().isEmpty()) {
QRegExp rx(file->regex());
for(QStringList::iterator it=output.begin(); it!=output.end(); ) {
if(rx.indexIn(*it)>=0)
++it;
else
it = output.erase(it);
}
}
m_vars->insert(file->variable(), output);
} break;
default:
kDebug(9032) << "error: not implemented. file:" << file->type() <<
"variable:" << file->variable() << "file:" << file->path() << file->content()[file->line()].arguments[0].value;
break;
}
return 1;
}
int CMakeProjectVisitor::visit(const MessageAst *msg)
{
s_msgcallback(msg->message().join(QString()));
return 1;
}
int CMakeProjectVisitor::visit(const MathAst *math)
{
QScriptEngine eng;
QScriptValue result = eng.evaluate(math->expression());
if (result.isError())
{
kDebug(9032) << "error: found an error while calculating" << math->expression();
}
kDebug(9042) << "math. " << math->expression() << "=" << result.toInteger();
m_vars->insert(math->outputVariable(), QStringList(QString::number(result.toInteger())));
return 1;
}
int CMakeProjectVisitor::visit(const GetFilenameComponentAst *filecomp)
{
Q_ASSERT(m_vars->contains("CMAKE_CURRENT_SOURCE_DIR"));
QDir dir=m_vars->value("CMAKE_CURRENT_SOURCE_DIR").first();
QFileInfo fi(dir, filecomp->fileName());
QString val;
switch(filecomp->type())
{
case GetFilenameComponentAst::Path: {
int idx = filecomp->fileName().lastIndexOf(QDir::separator());
if(idx>=0)
val=filecomp->fileName().left(idx);
} break;
case GetFilenameComponentAst::RealPath: {
val = fi.canonicalFilePath();
} break;
case GetFilenameComponentAst::Absolute:
val=fi.absoluteFilePath();
break;
case GetFilenameComponentAst::Name:
val=fi.fileName();
break;
case GetFilenameComponentAst::Ext:
val=fi.suffix();
break;
case GetFilenameComponentAst::NameWe:
val=fi.baseName();
break;
case GetFilenameComponentAst::Program:
kDebug(9042) << "error: filenamecopmonent PROGRAM not implemented"; //TODO: <<
break;
}
m_vars->insert(filecomp->variableName(), QStringList(val));
kDebug(9042) << "filename component" << filecomp->variableName() << "= "
<< filecomp->fileName() << "=" << val << endl;
return 1;
}
int CMakeProjectVisitor::visit(const GetSourceFilePropAst* prop)
{
kDebug(9042) << "not supported yet :::" << prop->variableName();
m_vars->insert(prop->variableName(), QStringList());
return 1;
}
int CMakeProjectVisitor::visit(const OptionAst *opt)
{
kDebug(9042) << "option" << opt->variableName() << "-" << opt->description();
if(!m_vars->contains(opt->variableName()) && !m_cache->contains(opt->variableName()))
{
m_vars->insert(opt->variableName(), QStringList(opt->defaultValue()));
}
return 1;
}
int CMakeProjectVisitor::visit(const ListAst *list)
{
QString output = list->output();
QStringList theList = m_vars->value(list->list());
switch(list->type())
{
case ListAst::Length:
m_vars->insert(output, QStringList(QString::number(theList.count())));
kDebug(9042) << "List length" << m_vars->value(output);
break;
case ListAst::Get: {
bool contains = m_vars->contains(list->list());
QStringList indices;
if(contains) {
foreach(int idx, list->index())
{
if(idx>=theList.count() || (-idx)>theList.count())
kDebug(9032) << "error! trying to GET an element that doesn't exist!" << idx;
else if(idx>=0)
indices += theList[idx];
else
indices += theList[theList.size()+idx];
}
} else
indices += "NOTFOUND";
m_vars->insert(output, indices);
kDebug(9042) << "List: Get" << list->list() << theList << list->output() << list->index();
} break;
case ListAst::Append:
theList += list->elements();
m_vars->insert(list->list(), theList);
break;
case ListAst::Find: {
QString element;
int idx=-1;
if(!list->elements().isEmpty()) {
element = list->elements().first();
idx=theList.indexOf(element);
}
m_vars->insert(list->output(), QStringList(QString::number(idx)));
kDebug(9042) << "List: Find" << theList << list->output() << list->elements() << idx;
} break;
case ListAst::Insert: {
int p=list->index().first();
foreach(const QString& elem, list->elements())
{
theList.insert(p >=0 ? p : (theList.size()+p), elem);
p += p>=0? 1 : 0;
}
m_vars->insert(list->list(), theList);
} break;
case ListAst::RemoveItem:
kDebug(9042) << "list remove item: " << theList << list->elements();
foreach(const QString& elem, list->elements())
{
theList.removeAll(elem);
}
m_vars->insert(list->list(), theList);
break;
case ListAst::RemoveAt: {
QList<int> indices=list->index();
qSort(indices);
QList<int>::const_iterator it=indices.constEnd();
kDebug(9042) << "list remove: " << theList << indices;
do
{
--it;
theList.removeAt(*it >= 0 ? *it : theList.size()+*it);
} while(it!=indices.constBegin());
m_vars->insert(list->list(), theList);
} break;
case ListAst::Sort:
qSort(theList);
m_vars->insert(list->list(), theList);
break;
case ListAst::Reverse: {
QStringList reversed;
foreach(const QString& elem, theList)
reversed.prepend(elem);
m_vars->insert(list->list(), reversed);
}
break;
case ListAst::RemoveDuplicates: {
QStringList noduplicates;
foreach(const QString& elem, theList) {
if(!noduplicates.contains(elem))
noduplicates.append(elem);
}
m_vars->insert(list->list(), noduplicates);
} break;
}
kDebug(9042) << "List!!" << list->output() << '='<< m_vars->value(list->output()) << " -> " << m_vars->value(list->list());
return 1;
}
static int toCommandEnd(const CMakeAst* fea)
{
QString command = fea->content()[fea->line()].name;
QString endCommand = "end"+command;
int lines=fea->line()+1, depth=1;
CMakeFileContent::const_iterator it=fea->content().constBegin()+lines;
CMakeFileContent::const_iterator itEnd=fea->content().constEnd();
for(; depth>0 && it!=itEnd; ++it, lines++)
{
if(it->name==command)
{
depth++;
}
else if(it->name==endCommand)
{
depth--;
}
}
return lines;
}
int CMakeProjectVisitor::visit(const ForeachAst *fea)
{
kDebug(9042) << "foreach>" << fea->loopVar() << "=" << fea->arguments() << "range=" << fea->type();
int end = -1;
switch(fea->type()) {
case ForeachAst::Range:
for( int i = fea->ranges().start; i < fea->ranges().stop && !m_hitBreak; i += fea->ranges().step )
{
m_vars->insertMulti(fea->loopVar(), QStringList(QString::number(i)));
end=walk(fea->content(), fea->line()+1);
m_vars->removeMulti(fea->loopVar());
if(m_hitBreak)
break;
}
break;
case ForeachAst::InItems: {
QStringList args=fea->arguments();
foreach(const QString& s, args)
{
m_vars->insert(fea->loopVar(), QStringList(s));
kDebug(9042) << "looping" << fea->loopVar() << "=" << m_vars->value(fea->loopVar());
end=walk(fea->content(), fea->line()+1);
if(m_hitBreak)
break;
}
} break;
case ForeachAst::InLists: {
QStringList args=fea->arguments();
foreach(const QString& curr, args) {
QStringList list = m_vars->value(curr);
foreach(const QString& s, list)
{
m_vars->insert(fea->loopVar(), QStringList(s));
kDebug(9042) << "looping" << fea->loopVar() << "=" << m_vars->value(fea->loopVar());
end=walk(fea->content(), fea->line()+1);
if(m_hitBreak)
break;
}
}
} break;
}
if(end<0)
end = toCommandEnd(fea);
else
end++;
m_hitBreak=false;
kDebug(9042) << "EndForeach" << fea->loopVar();
return end-fea->line();
}
int CMakeProjectVisitor::visit(const StringAst *sast)
{
kDebug(9042) << "String to" /*<< sast->input()*/ << sast->outputVariable();
switch(sast->type())
{
case StringAst::Regex:
{
QStringList res;
QRegExp rx(sast->regex());
rx.setPatternSyntax(QRegExp::RegExp2);
QString totalInput = sast->input().join(QString());
switch(sast->cmdType())
{
case StringAst::Match:
{
int match=rx.indexIn(totalInput);
if(match>=0) {
res = QStringList(totalInput.mid(match, rx.matchedLength()));
break;
}
break;
}
case StringAst::MatchAll:
{
int pos = 0;
while( (pos = rx.indexIn( totalInput, pos ) ) != -1 )
{
res << rx.cap();
pos += rx.matchedLength();
}
break;
}
case StringAst::RegexReplace:
{
foreach(const QString& in, sast->input())
{
// QString() is required to get rid of the const
res.append(QString(in).replace(rx, sast->replace()));
}
}
break;
default:
kDebug(9032) << "ERROR String: Not a regex. " << sast->cmdType();
break;
}
for(int i=1; i<rx.captureCount()+1 && i<10; i++) {
m_vars->insert(QString("CMAKE_MATCH_%1").arg(i), QStringList(rx.cap(i)));
}
kDebug() << "regex" << sast->outputVariable() << "=" << sast->regex() << res;
m_vars->insert(sast->outputVariable(), res);
}
break;
case StringAst::Replace: {
QStringList out;
foreach(const QString& _in, sast->input())
{
QString in(_in);
QString aux=in.replace(sast->regex(), sast->replace());
out += aux.split(';'); //FIXME: HUGE ugly hack
}
kDebug(9042) << "string REPLACE" << sast->input() << "=>" << out;
m_vars->insert(sast->outputVariable(), out);
} break;
case StringAst::Compare:
{
QString res;
switch(sast->cmdType()){
case StringAst::Equal:
case StringAst::NotEqual:
if(sast->input()[0]==sast->input()[1] && sast->cmdType()==StringAst::Equal)
res = "TRUE";
else
res = "FALSE";
break;
case StringAst::Less:
case StringAst::Greater:
if(sast->input()[0]<sast->input()[1] && sast->cmdType()==StringAst::Less)
res = "TRUE";
else
res = "FALSE";
break;
default:
kDebug(9042) << "String: Not a compare. " << sast->cmdType();
}
m_vars->insert(sast->outputVariable(), QStringList(res));
}
break;
case StringAst::Ascii: {
QString res;
foreach(const QString& ascii, sast->input())
{
bool ok;
res += QChar(ascii.toInt(&ok, 10));
}
m_vars->insert(sast->outputVariable(), QStringList(res));
} break;
case StringAst::Configure:
//This is not up to the cmake support
kDebug(9032) << "warning! String configure is not supported!" << sast->content()[sast->line()].writeBack();
break;
case StringAst::ToUpper:
m_vars->insert(sast->outputVariable(), QStringList(sast->input().join(QChar(';')).toUpper()));
break;
case StringAst::ToLower:
m_vars->insert(sast->outputVariable(), QStringList(sast->input().join(QChar(';')).toLower()));
break;
case StringAst::Length:
m_vars->insert(sast->outputVariable(), QStringList(QString::number(sast->input().join(QChar(';')).count())));
break;
case StringAst::Substring:
{
QString res=sast->input().join(QString());
res=res.mid(sast->begin(), sast->length());
m_vars->insert(sast->outputVariable(), QStringList(res));
} break;
case StringAst::Strip:
m_vars->insert(sast->outputVariable(), QStringList( sast->string().trimmed() ));
break;
case StringAst::Random: {
QString alphabet=sast->string(), result;
for(int i=0; i<sast->length(); i++)
{
int randv=qrand() % alphabet.size();
result += alphabet[randv];
}
m_vars->insert(sast->outputVariable(), QStringList(result));
} break;
}
kDebug(9042) << "String " << m_vars->value(sast->outputVariable());
return 1;
}
int CMakeProjectVisitor::visit(const GetCMakePropertyAst *past)
{
QStringList output;
switch(past->type())
{
case GetCMakePropertyAst::Variables:
kDebug(9042) << "get cmake prop: variables:" << m_vars->size();
output = m_vars->keys();
break;
case GetCMakePropertyAst::CacheVariables:
output = m_cache->keys();
break;
case GetCMakePropertyAst::Components:
case GetCMakePropertyAst::Commands: //FIXME: We do not have commands or components yet
output = QStringList("NOTFOUND");
break;
case GetCMakePropertyAst::Macros:
output = m_macros->keys();
break;
}
m_vars->insert(past->variableName(), output);
return 1;
}
int CMakeProjectVisitor::visit(const CustomCommandAst *ccast)
{
kDebug(9042) << "CustomCommand" << ccast->outputs();
if(ccast->isForTarget())
{
//TODO: implement me
}
else
{
foreach(const QString& out, ccast->outputs())
{
m_generatedFiles[out] = QStringList(ccast->mainDependency())/*+ccast->otherDependencies()*/;
kDebug(9042) << "Have to generate:" << out << "with" << m_generatedFiles[out];
}
}
return 1;
}
int CMakeProjectVisitor::visit(const CustomTargetAst *ctar)
{
kDebug(9042) << "custom_target " << ctar->target() << ctar->dependencies() << ", " << ctar->commandArgs();
kDebug(9042) << ctar->content()[ctar->line()].writeBack();
defineTarget(ctar->target(), ctar->dependencies() + ctar->sourceLists(), Target::Custom);
return 1;
}
int CMakeProjectVisitor::visit(const AddDefinitionsAst *addDef)
{
// kDebug(9042) << "Adding defs: " << addDef->definitions();
CMakeParserUtils::addDefinitions(addDef->definitions(), &m_defs, true);
return 1;
}
int CMakeProjectVisitor::visit(const RemoveDefinitionsAst *remDef)
{
CMakeParserUtils::removeDefinitions(remDef->definitions(), &m_defs, true);
return 1;
}
int CMakeProjectVisitor::visit(const MarkAsAdvancedAst *maa)
{
kDebug(9042) << "Mark As Advanced" << maa->advancedVars();
return 1;
}
/// @todo Add support for platform-specific argument splitting
/// (UNIX_COMMAND and WINDOWS_COMMAND) introduced in CMake 2.8.
int CMakeProjectVisitor::visit( const SeparateArgumentsAst * separgs )
{
QString varName=separgs->variableName();
QStringList res;
foreach(const QString& value, m_vars->value(varName))
{
res += value.split(' ');
}
m_vars->insert(varName, res);
return 1;
}
int CMakeProjectVisitor::visit(const SetPropertyAst* setp)
{
QStringList args = setp->args();
switch(setp->type()) {
case GlobalProperty:
args = QStringList() << QString();
break;
case DirectoryProperty:
args = m_vars->value("CMAKE_CURRENT_SOURCE_DIR");
break;
default:
break;
}
kDebug() << "setprops" << setp->type() << args << setp->name() << setp->values();
CategoryType& cm=m_props[setp->type()];
if(setp->append()) {
foreach(const QString &it, args) {
cm[it][setp->name()].append(setp->values());
}
} else if (setp->appendString()) {
const QString toAppend = setp->values().join(QString());
foreach(const QString &it, args) {
QStringList& values = cm[it][setp->name()];
if (values.isEmpty()) {
values.append(toAppend);
} else {
values.last().append(toAppend);
}
}
} else {
foreach(const QString &it, args)
cm[it].insert(setp->name(), setp->values());
}
return 1;
}
int CMakeProjectVisitor::visit(const GetPropertyAst* getp)
{
QStringList retv;
if(getp->type() == CacheProperty) {
retv = m_cache->value(getp->typeName()).value.split(':');
} else {
QString catn;
switch(getp->type()) {
case GlobalProperty:
break;
case DirectoryProperty:
catn = getp->typeName();
if(catn.isEmpty())
catn = m_vars->value("CMAKE_CURRENT_SOURCE_DIR").join(QString());
break;
default:
catn = getp->typeName();
break;
}
retv = m_props[getp->type()][catn][getp->name()];
}
m_vars->insert(getp->outputVariable(), retv);
kDebug() << "getprops" << getp->type() << getp->name() << getp->typeName() << getp->outputVariable() << "=" << retv;
return 1;
}
int CMakeProjectVisitor::visit(const GetDirPropertyAst* getdp)
{
kDebug() << "getprops";
QStringList retv;
QString dir=getdp->directory();
if(dir.isEmpty()) {
dir=m_vars->value("CMAKE_CURRENT_SOURCE_DIR").join(QString());
} else if(KUrl::isRelativeUrl(dir)) {
KUrl u(m_vars->value("CMAKE_CURRENT_SOURCE_DIR").join(QString()));
u.addPath(dir);
dir=u.path();
}
retv=m_props[DirectoryProperty][dir][getdp->propName()];
m_vars->insert(getdp->outputVariable(), retv);
return 1;
}
int CMakeProjectVisitor::visit(const SetTestsPropsAst* stp)
{
QHash<QString,QString> props;
foreach(const SetTestsPropsAst::PropPair& property, stp->properties()) {
props.insert(property.first, property.second);
}
for(QVector<Test>::iterator it=m_testSuites.begin(), itEnd=m_testSuites.end(); it!=itEnd; ++it) {
it->properties = props;
}
return 1;
}
int CMakeProjectVisitor::visit( const WhileAst * whileast)
{
CMakeCondition cond(this);
bool result=cond.condition(whileast->condition());
usesForArguments(whileast->condition(), cond.variableArguments(), m_topctx, whileast->content()[whileast->line()]);
kDebug(9042) << "Visiting While" << whileast->condition() << "?" << result;
int end = toCommandEnd(whileast);
if(end<whileast->content().size())
{
usesForArguments(whileast->condition(), cond.variableArguments(), m_topctx, whileast->content()[end]);
if(result)
{
walk(whileast->content(), whileast->line()+1);
if(m_hitBreak) {
kDebug() << "break found. leaving loop";
m_hitBreak=false;
} else
walk(whileast->content(), whileast->line());
}
}
kDebug(9042) << "endwhile" << whileast->condition() /*<< whileast->content()[end]*/;
return end-whileast->line();
}
CMakeFunctionDesc CMakeProjectVisitor::resolveVariables(const CMakeFunctionDesc & exp)
{
CMakeFunctionDesc ret=exp;
ret.arguments.clear();
foreach(const CMakeFunctionArgument &arg, exp.arguments)
{
if(arg.value.contains('$'))
ret.addArguments(resolveVariable(arg), arg.quoted);
else
ret.arguments.append(arg);
}
return ret;
}
enum RecursivityType { No, Yes, End, Break, Return };
static RecursivityType recursivity(const QString& functionName)
{
QString upperFunctioName=functionName;
if(upperFunctioName=="if" || upperFunctioName=="while" ||
upperFunctioName=="foreach" || upperFunctioName=="macro")
return Yes;
else if(upperFunctioName=="else" || upperFunctioName=="elseif" || upperFunctioName.startsWith("end"))
return End;
else if(upperFunctioName=="break")
return Break;
else if(upperFunctioName=="return")
return Return;
return No;
}
int CMakeProjectVisitor::walk(const CMakeFileContent & fc, int line, bool isClean)
{
if(fc.isEmpty())
return 0;
ReferencedTopDUContext aux=m_topctx;
IndexedString url(fc.first().filePath);
if(!m_topctx || m_topctx->url()!=url)
{
kDebug(9042) << "Creating a context for" << url;
m_topctx=createContext(url, aux ? aux : m_parentCtx, fc.last().endLine-1, fc.last().endColumn-1, isClean);
if(!aux)
aux=m_topctx;
}
VisitorState p;
p.code = &fc;
p.context = m_topctx;
p.line = line;
m_backtrace.push(p);
CMakeFileContent::const_iterator it=fc.constBegin()+line, itEnd=fc.constEnd();
for(; it!=itEnd; )
{
Q_ASSERT( line<fc.count() );
Q_ASSERT( line>=0 );
Q_ASSERT( *it == fc[line] );
RecursivityType r = recursivity(it->name);
if(r==End || r==Break || r==Return || m_hitBreak || m_hitReturn)
{
m_backtrace.pop();
m_topctx=aux;
if(r==Break)
m_hitBreak=true;
if(r==Return)
m_hitReturn=true;
return line;
}
CMakeAst* element = AstFactory::self()->createAst(it->name);
createUses(*it);
// kDebug(9042) << "resolving:" << it->writeBack();
CMakeFunctionDesc func = resolveVariables(*it); //FIXME not correct in while case
bool correct = element->parseFunctionInfo(func);
// kDebug(9042) << "resolved:" << func.writeBack() << correct;
if(!correct)
{
kDebug(9042) << "error! found an error while processing" << func.writeBack() << "was" << it->writeBack() << endl
<< " at" << url << ":" << func.line << endl;
//FIXME: Should avoid to run?
}
if(element->isDeprecated()) {
kDebug(9032) << "Warning: Using the function: " << func.name << " which is deprecated by cmake.";
DUChainWriteLocker lock(DUChain::lock());
KSharedPtr<Problem> p(new Problem);
p->setDescription(i18n("%1 is a deprecated command and should not be used", func.name));
p->setRange(it->nameRange());
p->setFinalLocation(DocumentRange(url, it->nameRange().castToSimpleRange()));
m_topctx->addProblem(p);
}
element->setContent(fc, line);
createDefinitions(element);
m_vars->insertGlobal("CMAKE_CURRENT_LIST_LINE", QStringList(QString::number(it->line)));
int lines=element->accept(this);
line+=lines;
m_backtrace.top().line = line;
m_backtrace.top().context = m_topctx;
delete element;
if(line>fc.count()) {
KSharedPtr<Problem> p(new Problem);
p->setDescription(i18n("Unfinished function. "));
p->setRange(it->nameRange());
p->setFinalLocation(DocumentRange(url, KDevelop::RangeInRevision(fc.first().range().start, fc.last().range().end).castToSimpleRange()));
DUChainWriteLocker lock(DUChain::lock());
m_topctx->addProblem(p);
break;
}
it+=lines;
}
m_backtrace.pop();
m_topctx=aux;
kDebug(9042) << "Walk stopped @" << line;
return line;
}
void CMakeProjectVisitor::createDefinitions(const CMakeAst *ast)
{
if(!m_topctx)
return;
foreach(const CMakeFunctionArgument &arg, ast->outputArguments())
{
if(!arg.isCorrect())
continue;
Identifier id(arg.value);
DUChainWriteLocker lock;
QList<Declaration*> decls=m_topctx->findDeclarations(id);
if(decls.isEmpty())
{
Declaration *d = new Declaration(arg.range(), m_topctx);
d->setIdentifier(id);
}
else
{
int idx=m_topctx->indexForUsedDeclaration(decls.first());
m_topctx->createUse(idx, arg.range(), 0);
}
}
}
void CMakeProjectVisitor::createUses(const CMakeFunctionDesc& desc)
{
if(!m_topctx)
return;
foreach(const CMakeFunctionArgument &arg, desc.arguments)
{
if(!arg.isCorrect() || !arg.value.contains('$'))
continue;
QList<IntPair> var = parseArgument(arg.value);
QList<IntPair>::const_iterator it, itEnd=var.constEnd();
for(it=var.constBegin(); it!=itEnd; ++it)
{
QString var=arg.value.mid(it->first+1, it->second-it->first-1);
DUChainWriteLocker lock;
QList<Declaration*> decls=m_topctx->findDeclarations(Identifier(var));
if(!decls.isEmpty())
{
int idx=m_topctx->indexForUsedDeclaration(decls.first());
m_topctx->createUse(idx, RangeInRevision(arg.line-1, arg.column+it->first, arg.line-1, arg.column+it->second-1), 0);
}
}
}
}
void CMakeProjectVisitor::setCacheValues( CacheValues* cache)
{
m_cache=cache;
}
void CMakeProjectVisitor::setVariableMap(VariableMap * vars)
{
m_vars=vars;
}
QStringList CMakeProjectVisitor::dependees(const QString& s) const
{
QStringList ret;
if(isGenerated(s))
{
foreach(const QString& f, m_generatedFiles[s])
ret += dependees(f);
}
else
{
ret += s;
}
return ret;
}
QStringList CMakeProjectVisitor::resolveDependencies(const QStringList & files) const
{
QStringList ret;
foreach(const QString& s, files)
{
if(isGenerated(s))
{
kDebug(9042) << "Generated:" << s;
QStringList gen = dependees(s);
foreach(const QString& file, gen)
{
if(!ret.contains(file))
ret.append(file);
}
}
else
{
ret.append(s);
}
}
return ret;
}
QStringList CMakeProjectVisitor::traverseGlob(const QString& startPath, const QString& expression, bool recursive, bool followSymlinks)
{
kDebug(9042) << "Starting from (" << startPath << ", " << expression << "," << recursive << ", " << followSymlinks << ")";
QString expr = expression;
int firstSlash = expr.indexOf('/');
int slashShift = 0;
while (firstSlash == slashShift) //skip trailing slashes
{
slashShift++;
firstSlash = expr.indexOf('/', slashShift);
}
expr = expr.mid(slashShift);
if (firstSlash == -1)
{
//We're in place. Lets match files from startPath dir.
kDebug(9042) << "Matching files in " << startPath << " with glob " << expr;
QStringList dirsToSearch;
if (recursive)
{
QDir::Filters dirFilters = QDir::NoDotAndDotDot | QDir::Dirs;
bool CMP0009IsSetToNew = true; // TODO: Obey CMP0009 policy when policies are implemented.
if (!(CMP0009IsSetToNew && followSymlinks))
dirFilters |= QDir::NoSymLinks;
QQueue<QString> dirsToExpand;
dirsToExpand.enqueue(startPath);
while (!dirsToExpand.empty())
{
QString dir = dirsToExpand.dequeue();
kDebug(9042) << "Enqueueing " << dir;
dirsToSearch << dir;
QDir d(dir);
QStringList dirNames = d.entryList(dirFilters);
foreach(const QString& dirName, dirNames)
{
dirsToExpand << d.filePath(dirName);
}
}
}
else
dirsToSearch << startPath;
QStringList filePaths;
const QStringList nameFilters(expr);
foreach (const QString& dirToSearch, dirsToSearch)
{
QDir dir(dirToSearch);
QStringList fileNames = dir.entryList(nameFilters, QDir::Files|QDir::Dirs|QDir::NoDotAndDotDot);
foreach (const QString& fileName, fileNames)
{
filePaths << dir.filePath(fileName);
}
}
return filePaths;
}
firstSlash -= slashShift;
QString dirGlob = expr.left(firstSlash);
QString rightExpression = expr.mid(firstSlash + 1);
//Now we must find match for a directory specified in dirGlob
QStringList matchedDirs;
if (dirGlob.contains('*') || dirGlob.contains('?') || dirGlob.contains('['))
{
kDebug(9042) << "Got a dir glob " << dirGlob;
if (startPath.isEmpty())
return QStringList();
//it's really a glob, not just dir name
matchedDirs = QDir(startPath).entryList(QStringList(dirGlob), QDir::NoDotAndDotDot | QDir::Dirs);
}
else
{
//just a directory name. Add it as a match.
kDebug(9042) << "Got a simple folder " << dirGlob;
matchedDirs << dirGlob;
}
QStringList matches;
QString path = startPath;
if (!path.endsWith('/'))
path += '/';
foreach(const QString& dirName, matchedDirs)
{
kDebug(9042) << "Going resursive into " << path + dirName << " and glob " << rightExpression;
matches.append(traverseGlob(path + dirName, rightExpression, recursive, followSymlinks));
}
return matches;
}