forked from qt-creator/qt-creator
456 lines
14 KiB
C++
456 lines
14 KiB
C++
/**************************************************************************
|
|
**
|
|
** This file is part of Qt Creator
|
|
**
|
|
** Copyright (c) 2010-2011 Openismus GmbH.
|
|
** Authors: Peter Penz (ppenz@openismus.com)
|
|
** Patricia Santana Cruz (patriciasantanacruz@gmail.com)
|
|
**
|
|
** Contact: Nokia Corporation (info@qt.nokia.com)
|
|
**
|
|
**
|
|
** GNU Lesser General Public License Usage
|
|
**
|
|
** This file may be used under the terms of the GNU Lesser General Public
|
|
** License version 2.1 as published by the Free Software Foundation and
|
|
** appearing in the file LICENSE.LGPL included in the packaging of this file.
|
|
** Please review the following information to ensure the GNU Lesser General
|
|
** Public License version 2.1 requirements will be met:
|
|
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
|
**
|
|
** In addition, as a special exception, Nokia gives you certain additional
|
|
** rights. These rights are described in the Nokia Qt LGPL Exception
|
|
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
|
**
|
|
** Other Usage
|
|
**
|
|
** Alternatively, this file may be used in accordance with the terms and
|
|
** conditions contained in a signed written agreement between you and Nokia.
|
|
**
|
|
** If you have questions regarding the use of this file, please contact
|
|
** Nokia at info@qt.nokia.com.
|
|
**
|
|
**************************************************************************/
|
|
|
|
#include "makefileparser.h"
|
|
|
|
#include <utils/qtcassert.h>
|
|
|
|
#include <QFile>
|
|
#include <QFileInfoList>
|
|
#include <QMutexLocker>
|
|
|
|
using namespace AutotoolsProjectManager::Internal;
|
|
|
|
MakefileParser::MakefileParser(const QString &makefile) :
|
|
QObject(),
|
|
m_success(false),
|
|
m_cancel(false),
|
|
m_mutex(),
|
|
m_makefile(makefile),
|
|
m_executable(),
|
|
m_sources(),
|
|
m_makefiles(),
|
|
m_includePaths(),
|
|
m_line(),
|
|
m_textStream()
|
|
{
|
|
}
|
|
|
|
MakefileParser::~MakefileParser()
|
|
{
|
|
delete m_textStream.device();
|
|
}
|
|
|
|
bool MakefileParser::parse()
|
|
{
|
|
m_mutex.lock();
|
|
m_cancel = false;
|
|
m_mutex.unlock(),
|
|
|
|
m_success = true;
|
|
m_executable.clear();
|
|
m_sources.clear();
|
|
m_makefiles.clear();
|
|
|
|
QFile *file = new QFile(m_makefile);
|
|
if (!file->open(QIODevice::ReadOnly | QIODevice::Text))
|
|
return false;
|
|
|
|
QFileInfo info(m_makefile);
|
|
m_makefiles.append(info.fileName());
|
|
|
|
emit status(tr("Parsing %1 in directory %2").arg(info.fileName()).arg(info.absolutePath()));
|
|
|
|
m_textStream.setDevice(file);
|
|
|
|
do {
|
|
m_line = m_textStream.readLine();
|
|
switch (topTarget()) {
|
|
case AmDefaultSourceExt: parseDefaultSourceExtensions(); break;
|
|
case BinPrograms: parseBinPrograms(); break;
|
|
case BuiltSources: break; // TODO: Add to m_sources?
|
|
case Sources: parseSources(); break;
|
|
case SubDirs: parseSubDirs(); break;
|
|
case Undefined:
|
|
default: break;
|
|
}
|
|
} while (!m_line.isNull());
|
|
|
|
parseIncludePaths();
|
|
|
|
return m_success;
|
|
}
|
|
|
|
QStringList MakefileParser::sources() const
|
|
{
|
|
return m_sources;
|
|
}
|
|
|
|
QStringList MakefileParser::makefiles() const
|
|
{
|
|
return m_makefiles;
|
|
}
|
|
|
|
QString MakefileParser::executable() const
|
|
{
|
|
return m_executable;
|
|
}
|
|
|
|
QStringList MakefileParser::includePaths() const
|
|
{
|
|
return m_includePaths;
|
|
}
|
|
|
|
void MakefileParser::cancel()
|
|
{
|
|
QMutexLocker locker(&m_mutex);
|
|
m_cancel = true;
|
|
}
|
|
|
|
bool MakefileParser::isCanceled() const
|
|
{
|
|
QMutexLocker locker(&m_mutex);
|
|
return m_cancel;
|
|
}
|
|
|
|
MakefileParser::TopTarget MakefileParser::topTarget() const
|
|
{
|
|
TopTarget topTarget = Undefined;
|
|
|
|
const QString line = m_line.simplified();
|
|
if (!line.isEmpty() && !line.startsWith(QLatin1Char('#'))) {
|
|
// TODO: Check how many fixed strings like AM_DEFAULT_SOURCE_EXT will
|
|
// be needed vs. variable strings like _SOURCES. Dependent on this a
|
|
// more clever way than this (expensive) if-cascading might be done.
|
|
if (line.startsWith(QLatin1String("AM_DEFAULT_SOURCE_EXT =")))
|
|
topTarget = AmDefaultSourceExt;
|
|
else if (line.startsWith(QLatin1String("bin_PROGRAMS =")))
|
|
topTarget = BinPrograms;
|
|
else if (line.startsWith(QLatin1String("BUILT_SOURCES =")))
|
|
topTarget = BuiltSources;
|
|
else if (line.contains(QLatin1String("SUBDIRS =")))
|
|
topTarget = SubDirs;
|
|
else if (line.contains(QLatin1String("_SOURCES =")))
|
|
topTarget = Sources;
|
|
}
|
|
|
|
return topTarget;
|
|
}
|
|
|
|
void MakefileParser::parseBinPrograms()
|
|
{
|
|
QTC_ASSERT(m_line.contains(QLatin1String("bin_PROGRAMS")), return);
|
|
const QStringList binPrograms = targetValues();
|
|
|
|
// TODO: are multiple values possible?
|
|
if (binPrograms.size() == 1) {
|
|
QFileInfo info(binPrograms.first());
|
|
m_executable = info.fileName();
|
|
}
|
|
}
|
|
|
|
void MakefileParser::parseSources()
|
|
{
|
|
QTC_ASSERT(m_line.contains(QLatin1String("_SOURCES")), return);
|
|
|
|
bool hasVariables = false;
|
|
m_sources.append(targetValues(&hasVariables));
|
|
|
|
// Skip parsing of Makefile.am for getting the sub directories,
|
|
// as variables have been used. As fallback all sources will be added.
|
|
if (hasVariables)
|
|
addAllSources();
|
|
|
|
// Duplicates might be possible in combination with 'AM_DEFAULT_SOURCE_EXT ='
|
|
m_sources.removeDuplicates();
|
|
|
|
// TODO: Definitions like "SOURCES = ../src.cpp" are ignored currently.
|
|
// This case must be handled correctly in MakefileParser::parseSubDirs(),
|
|
// where the current sub directory must be shortened.
|
|
QStringList::iterator it = m_sources.begin();
|
|
while (it != m_sources.end()) {
|
|
if ((*it).startsWith(QLatin1String("..")))
|
|
it = m_sources.erase(it);
|
|
else
|
|
++it;
|
|
}
|
|
}
|
|
|
|
void MakefileParser::parseDefaultSourceExtensions()
|
|
{
|
|
QTC_ASSERT(m_line.contains(QLatin1String("AM_DEFAULT_SOURCE_EXT")), return);
|
|
const QStringList extensions = targetValues();
|
|
if (extensions.isEmpty()) {
|
|
m_success = false;
|
|
return;
|
|
}
|
|
|
|
QFileInfo info(m_makefile);
|
|
const QString dirName = info.absolutePath();
|
|
m_sources.append(directorySources(dirName, extensions));
|
|
|
|
// Duplicates might be possible in combination with '_SOURCES ='
|
|
m_sources.removeDuplicates();
|
|
}
|
|
|
|
void MakefileParser::parseSubDirs()
|
|
{
|
|
QTC_ASSERT(m_line.contains(QLatin1String("SUBDIRS")), return);
|
|
if (isCanceled()) {
|
|
m_success = false;
|
|
return;
|
|
}
|
|
|
|
QFileInfo info(m_makefile);
|
|
const QString path = info.absolutePath();
|
|
const QString makefileName = info.fileName();
|
|
|
|
bool hasVariables = false;
|
|
QStringList subDirs = targetValues(&hasVariables);
|
|
if (hasVariables) {
|
|
// Skip parsing of Makefile.am for getting the sub directories,
|
|
// as variables have been used. As fallback all sources will be added.
|
|
addAllSources();
|
|
return;
|
|
}
|
|
|
|
// If the SUBDIRS values contain a '.' or a variable like $(test),
|
|
// all the sub directories of the current folder must get parsed.
|
|
bool hasDotSubDir = false;
|
|
QStringList::iterator it = subDirs.begin();
|
|
while (it != subDirs.end()) {
|
|
// Erase all entries that represent a '.'
|
|
if ((*it) == QLatin1String(".")) {
|
|
hasDotSubDir = true;
|
|
it = subDirs.erase(it);
|
|
} else {
|
|
++it;
|
|
}
|
|
}
|
|
if (hasDotSubDir) {
|
|
// Add all sub directories of the current folder
|
|
QDir dir(path);
|
|
dir.setFilter(QDir::Dirs | QDir::NoDotAndDotDot);
|
|
foreach (const QFileInfo& info, dir.entryInfoList()) {
|
|
subDirs.append(info.fileName());
|
|
}
|
|
}
|
|
subDirs.removeDuplicates();
|
|
|
|
// Delegate the parsing of all sub directories to a local
|
|
// makefile parser and merge the results
|
|
foreach (const QString& subDir, subDirs) {
|
|
const QChar slash = QLatin1Char('/');
|
|
const QString subDirMakefile = path + slash + subDir
|
|
+ slash + makefileName;
|
|
|
|
// Parse sub directory
|
|
QFile file(subDirMakefile);
|
|
|
|
// Don't try to parse a file, that might not exist (e. g.
|
|
// if SUBDIRS specifies a 'po' directory).
|
|
if (!file.exists())
|
|
continue;
|
|
|
|
MakefileParser parser(subDirMakefile);
|
|
connect(&parser, SIGNAL(status(QString)), this, SIGNAL(status(QString)));
|
|
const bool success = parser.parse();
|
|
|
|
// Don't return, try to parse as many sub directories
|
|
// as possible
|
|
if (!success)
|
|
m_success = false;
|
|
|
|
m_makefiles.append(subDir + slash + makefileName);
|
|
|
|
// Append the sources of the sub directory to the
|
|
// current sources
|
|
foreach (const QString& source, parser.sources())
|
|
m_sources.append(subDir + slash + source);
|
|
|
|
// Duplicates might be possible in combination with several
|
|
// "..._SUBDIRS" targets
|
|
m_makefiles.removeDuplicates();
|
|
m_sources.removeDuplicates();
|
|
}
|
|
|
|
if (subDirs.isEmpty())
|
|
m_success = false;
|
|
}
|
|
|
|
QStringList MakefileParser::directorySources(const QString &directory,
|
|
const QStringList &extensions)
|
|
{
|
|
if (isCanceled()) {
|
|
m_success = false;
|
|
return QStringList();
|
|
}
|
|
|
|
emit status(tr("Parsing directory %1").arg(directory));
|
|
|
|
QStringList list; // return value
|
|
|
|
QDir dir(directory);
|
|
dir.setFilter(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot);
|
|
|
|
const QFileInfoList infos = dir.entryInfoList();
|
|
foreach (const QFileInfo& info, infos) {
|
|
if (info.isDir()) {
|
|
// Append recursively sources from the sub directory
|
|
const QStringList subDirSources = directorySources(info.absoluteFilePath(),
|
|
extensions);
|
|
const QString dirPath = info.fileName();
|
|
foreach (const QString& subDirSource, subDirSources)
|
|
list.append(dirPath + QLatin1Char('/') + subDirSource);
|
|
} else {
|
|
// Check whether the file matches to an extension
|
|
foreach (const QString& extension, extensions) {
|
|
if (info.fileName().endsWith(extension)) {
|
|
list.append(info.fileName());
|
|
appendHeader(list, dir, info.baseName());
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return list;
|
|
}
|
|
|
|
QStringList MakefileParser::targetValues(bool *hasVariables)
|
|
{
|
|
QStringList values;
|
|
if (hasVariables != 0)
|
|
*hasVariables = false;
|
|
|
|
const int index = m_line.indexOf(QLatin1Char('='));
|
|
if (index < 0) {
|
|
m_success = false;
|
|
return QStringList();
|
|
}
|
|
|
|
m_line.remove(0, index + 1); // remove the 'target = ' prefix
|
|
|
|
bool endReached = false;
|
|
do {
|
|
m_line = m_line.simplified();
|
|
|
|
// Get all values of a line separated by spaces.
|
|
// Values representing a variable like $(value) get
|
|
// removed currently.
|
|
QStringList lineValues = m_line.split(QLatin1Char(' '));
|
|
QStringList::iterator it = lineValues.begin();
|
|
while (it != lineValues.end()) {
|
|
if ((*it).startsWith(QLatin1String("$("))) {
|
|
it = lineValues.erase(it);
|
|
if (hasVariables != 0)
|
|
*hasVariables = true;
|
|
} else {
|
|
++it;
|
|
}
|
|
}
|
|
|
|
endReached = lineValues.isEmpty();
|
|
if (!endReached) {
|
|
const QChar backSlash = QLatin1Char('\\');
|
|
QString last = lineValues.last();
|
|
if (last.endsWith(backSlash)) {
|
|
// The last value contains a backslash. Remove the
|
|
// backslash and replace the last value.
|
|
lineValues.pop_back();
|
|
last.remove(backSlash);
|
|
if (!last.isEmpty())
|
|
lineValues.push_back(last);
|
|
|
|
values.append(lineValues);
|
|
m_line = m_textStream.readLine();
|
|
endReached = m_line.isNull();
|
|
} else {
|
|
values.append(lineValues);
|
|
endReached = true;
|
|
}
|
|
}
|
|
} while (!endReached);
|
|
|
|
return values;
|
|
}
|
|
|
|
void MakefileParser::appendHeader(QStringList &list, const QDir &dir, const QString &fileName)
|
|
{
|
|
const char *const headerExtensions[] = { ".h", ".hh", ".hg", ".hxx", ".hpp", 0 };
|
|
int i = 0;
|
|
while (headerExtensions[i] != 0) {
|
|
const QString headerFile = fileName + QLatin1String(headerExtensions[i]);
|
|
QFileInfo fileInfo(dir, headerFile);
|
|
if (fileInfo.exists())
|
|
list.append(headerFile);
|
|
++i;
|
|
}
|
|
}
|
|
|
|
void MakefileParser::addAllSources()
|
|
{
|
|
QStringList extensions;
|
|
extensions << QLatin1String(".c")
|
|
<< QLatin1String(".cpp")
|
|
<< QLatin1String(".cc")
|
|
<< QLatin1String(".cxx")
|
|
<< QLatin1String(".ccg");
|
|
QFileInfo info(m_makefile);
|
|
m_sources.append(directorySources(info.absolutePath(), extensions));
|
|
m_sources.removeDuplicates();
|
|
}
|
|
|
|
void MakefileParser::parseIncludePaths()
|
|
{
|
|
QFileInfo info(m_makefile);
|
|
const QString dirName = info.absolutePath();
|
|
|
|
QFile file(dirName + QLatin1String("/Makefile"));
|
|
if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
|
|
return;
|
|
|
|
// TODO: The parsing is done very poor. Comments are ignored and targets
|
|
// are ignored too. Whether it is worth to improve this, depends on whether
|
|
// we want to parse the generated Makefile at all or whether we want to
|
|
// improve the Makefile.am parsing to be aware of variables.
|
|
QTextStream textStream(&file);
|
|
QString line;
|
|
do {
|
|
line = textStream.readLine();
|
|
QStringList terms = line.split(QLatin1Char(' '), QString::SkipEmptyParts);
|
|
foreach (const QString &term, terms) {
|
|
if (term.startsWith(QLatin1String("-I"))) {
|
|
QString includePath = term.right(term.length() - 2); // remove the "-I"
|
|
if (includePath == QLatin1String("."))
|
|
includePath = dirName;
|
|
if (!includePath.isEmpty())
|
|
m_includePaths += includePath;
|
|
}
|
|
}
|
|
} while (!line.isNull());
|
|
|
|
m_includePaths.removeDuplicates();
|
|
}
|