aboutsummaryrefslogtreecommitdiffstats
path: root/src/plugins
diff options
context:
space:
mode:
authorTobias Hunger <[email protected]>2016-10-17 13:55:54 +0200
committerTobias Hunger <[email protected]>2016-10-31 10:40:24 +0000
commit9a980adf3ce0258d599b7269877f92c498c8e37e (patch)
tree486525182b8943350829f83fedefd6feb387daf4 /src/plugins
parentb75c6444d1d0f3abb796b23d78a24029f9fb5093 (diff)
CMake: Implement different backends to run cmake
Only the original one is implemented so far, but at least in theory backends for retrieving data from cmake can now be switched at runtime. Change-Id: Id73a81c7d40f078be95defd30a38511dca3a3720 Reviewed-by: Tim Jenssen <[email protected]> Reviewed-by: Eike Ziller <[email protected]>
Diffstat (limited to 'src/plugins')
-rw-r--r--src/plugins/cmakeprojectmanager/builddirmanager.cpp676
-rw-r--r--src/plugins/cmakeprojectmanager/builddirmanager.h57
-rw-r--r--src/plugins/cmakeprojectmanager/builddirreader.cpp95
-rw-r--r--src/plugins/cmakeprojectmanager/builddirreader.h115
-rw-r--r--src/plugins/cmakeprojectmanager/cmakebuildconfiguration.cpp10
-rw-r--r--src/plugins/cmakeprojectmanager/cmakebuildconfiguration.h4
-rw-r--r--src/plugins/cmakeprojectmanager/cmakefile.cpp65
-rw-r--r--src/plugins/cmakeprojectmanager/cmakefile.h48
-rw-r--r--src/plugins/cmakeprojectmanager/cmakeprojectmanager.pro10
-rw-r--r--src/plugins/cmakeprojectmanager/cmakeprojectmanager.qbs8
-rw-r--r--src/plugins/cmakeprojectmanager/tealeafreader.cpp678
-rw-r--r--src/plugins/cmakeprojectmanager/tealeafreader.h93
12 files changed, 1068 insertions, 791 deletions
diff --git a/src/plugins/cmakeprojectmanager/builddirmanager.cpp b/src/plugins/cmakeprojectmanager/builddirmanager.cpp
index 6a49163bad9..4f8f2bb8817 100644
--- a/src/plugins/cmakeprojectmanager/builddirmanager.cpp
+++ b/src/plugins/cmakeprojectmanager/builddirmanager.cpp
@@ -63,19 +63,9 @@
using namespace ProjectExplorer;
-// --------------------------------------------------------------------
-// Helper:
-// --------------------------------------------------------------------
-
namespace CMakeProjectManager {
namespace Internal {
-static QStringList toArguments(const CMakeConfig &config, const Kit *k) {
- return Utils::transform(config, [k](const CMakeConfigItem &i) -> QString {
- return i.toArgument(k->macroExpander());
- });
-}
-
// --------------------------------------------------------------------
// BuildDirManager:
// --------------------------------------------------------------------
@@ -84,65 +74,56 @@ BuildDirManager::BuildDirManager(CMakeBuildConfiguration *bc) :
m_buildConfiguration(bc)
{
QTC_ASSERT(bc, return);
- m_projectName = sourceDirectory().fileName();
m_reparseTimer.setSingleShot(true);
connect(&m_reparseTimer, &QTimer::timeout, this, &BuildDirManager::parse);
- connect(Core::EditorManager::instance(), &Core::EditorManager::aboutToSave,
- this, &BuildDirManager::handleDocumentSaves);
-}
-
-BuildDirManager::~BuildDirManager()
-{
- stopProcess();
- resetData();
- qDeleteAll(m_watchedFiles);
- delete m_tempDir;
-}
-
-const Kit *BuildDirManager::kit() const
-{
- return m_buildConfiguration->target()->kit();
-}
-
-const Utils::FileName BuildDirManager::buildDirectory() const
-{
- return m_buildConfiguration->buildDirectory();
}
const Utils::FileName BuildDirManager::workDirectory() const
{
- const Utils::FileName bdir = buildDirectory();
+ const Utils::FileName bdir = m_buildConfiguration->buildDirectory();
if (bdir.exists())
return bdir;
- if (m_tempDir)
- return Utils::FileName::fromString(m_tempDir->path());
- return bdir;
+ if (!m_tempDir)
+ m_tempDir.reset(new QTemporaryDir(QDir::tempPath() + QLatin1String("/qtc-cmake-XXXXXX")));
+ return Utils::FileName::fromString(m_tempDir->path());
}
-const Utils::FileName BuildDirManager::sourceDirectory() const
+void BuildDirManager::updateReader()
{
- return m_buildConfiguration->target()->project()->projectDirectory();
-}
+ BuildDirReader::Parameters p(m_buildConfiguration);
+ p.buildDirectory = workDirectory();
-const CMakeConfig BuildDirManager::intendedConfiguration() const
-{
- return m_buildConfiguration->cmakeConfiguration();
+ if (!m_reader || !m_reader->isCompatible(p)) {
+ m_reader.reset(BuildDirReader::createReader(p));
+ connect(m_reader.get(), &BuildDirReader::configurationStarted,
+ this, &BuildDirManager::configurationStarted);
+ connect(m_reader.get(), &BuildDirReader::dataAvailable,
+ this, &BuildDirManager::dataAvailable);
+ connect(m_reader.get(), &BuildDirReader::errorOccured,
+ this, &BuildDirManager::errorOccured);
+ connect(m_reader.get(), &BuildDirReader::dirty, this, &BuildDirManager::becameDirty);
+ }
+ m_reader->setParameters(p);
}
bool BuildDirManager::isParsing() const
{
- if (m_cmakeProcess)
- return m_cmakeProcess->state() != QProcess::NotRunning;
- return false;
+ return m_reader && m_reader->isParsing();
}
-void BuildDirManager::cmakeFilesChanged()
+void BuildDirManager::becameDirty()
{
if (isParsing())
return;
+ Target *t = m_buildConfiguration->target()->project()->activeTarget();
+ BuildConfiguration *bc = t ? t->activeBuildConfiguration() : nullptr;
+
+ if (bc != m_buildConfiguration)
+ return;
+
const CMakeTool *tool = CMakeKitInformation::cmakeTool(m_buildConfiguration->target()->kit());
if (!tool->isAutoRun())
return;
@@ -155,26 +136,22 @@ void BuildDirManager::forceReparse()
if (m_buildConfiguration->target()->activeBuildConfiguration() != m_buildConfiguration)
return;
- stopProcess();
- CMakeTool *tool = CMakeKitInformation::cmakeTool(kit());
+ CMakeTool *tool = CMakeKitInformation::cmakeTool(m_buildConfiguration->target()->kit());
QTC_ASSERT(tool, return);
- startCMake(tool, CMakeGeneratorKitInformation::generatorArguments(kit()), intendedConfiguration());
+ m_reader->stop();
+ m_reader->parse(true);
}
void BuildDirManager::resetData()
{
- m_hasData = false;
-
- qDeleteAll(m_watchedFiles);
- m_watchedFiles.clear();
+ if (m_reader)
+ m_reader->resetData();
m_cmakeCache.clear();
- m_projectName.clear();
- m_buildTargets.clear();
- qDeleteAll(m_files);
- m_files.clear();
+
+ m_reader.reset(nullptr);
}
bool BuildDirManager::updateCMakeStateBeforeBuild()
@@ -187,116 +164,34 @@ bool BuildDirManager::persistCMakeState()
if (!m_tempDir)
return false;
- QDir dir(buildDirectory().toString());
- dir.mkpath(buildDirectory().toString());
+ const QString buildDir = m_buildConfiguration->buildDirectory().toString();
+ QDir dir(buildDir);
+ dir.mkpath(buildDir);
- delete m_tempDir;
- m_tempDir = nullptr;
+ m_tempDir.reset(nullptr);
- resetData();
QTimer::singleShot(0, this, &BuildDirManager::parse); // make sure signals only happen afterwards!
return true;
}
void BuildDirManager::generateProjectTree(CMakeProjectNode *root)
{
- root->setDisplayName(m_projectName);
-
- // Delete no longer necessary file watcher:
- const QSet<Utils::FileName> currentWatched
- = Utils::transform(m_watchedFiles, [](CMakeFile *cmf) { return cmf->filePath(); });
- const QSet<Utils::FileName> toWatch = m_cmakeFiles;
- QSet<Utils::FileName> toDelete = currentWatched;
- toDelete.subtract(toWatch);
- m_watchedFiles = Utils::filtered(m_watchedFiles, [&toDelete](Internal::CMakeFile *cmf) {
- if (toDelete.contains(cmf->filePath())) {
- delete cmf;
- return false;
- }
- return true;
- });
-
- // Add new file watchers:
- QSet<Utils::FileName> toAdd = toWatch;
- toAdd.subtract(currentWatched);
- foreach (const Utils::FileName &fn, toAdd) {
- CMakeFile *cm = new CMakeFile(this, fn);
- Core::DocumentManager::addDocument(cm);
- m_watchedFiles.insert(cm);
- }
-
- QList<FileNode *> fileNodes = m_files;
- root->buildTree(fileNodes);
- m_files.clear(); // Some of the FileNodes in files() were deleted!
+ QTC_ASSERT(m_reader, return);
+ m_reader->generateProjectTree(root);
}
QSet<Core::Id> BuildDirManager::updateCodeModel(CppTools::ProjectPartBuilder &ppBuilder)
{
- QSet<Core::Id> languages;
- ToolChain *tc = ToolChainKitInformation::toolChain(kit(), ToolChain::Language::Cxx);
- const Utils::FileName sysroot = SysRootKitInformation::sysRoot(kit());
-
- QHash<QString, QStringList> targetDataCache;
- foreach (const CMakeBuildTarget &cbt, buildTargets()) {
- if (cbt.targetType == UtilityType)
- continue;
-
- // CMake shuffles the include paths that it reports via the CodeBlocks generator
- // So remove the toolchain include paths, so that at least those end up in the correct
- // place.
- QStringList cxxflags = getCXXFlagsFor(cbt, targetDataCache);
- QSet<Utils::FileName> tcIncludes;
- foreach (const HeaderPath &hp, tc->systemHeaderPaths(cxxflags, sysroot))
- tcIncludes.insert(Utils::FileName::fromString(hp.path()));
- QStringList includePaths;
- foreach (const Utils::FileName &i, cbt.includeFiles) {
- if (!tcIncludes.contains(i))
- includePaths.append(i.toString());
- }
- includePaths += buildDirectory().toString();
- ppBuilder.setIncludePaths(includePaths);
- ppBuilder.setCFlags(cxxflags);
- ppBuilder.setCxxFlags(cxxflags);
- ppBuilder.setDefines(cbt.defines);
- ppBuilder.setDisplayName(cbt.title);
-
- const QSet<Core::Id> partLanguages
- = QSet<Core::Id>::fromList(ppBuilder.createProjectPartsForFiles(
- Utils::transform(cbt.files, [](const Utils::FileName &fn) { return fn.toString(); })));
-
- languages.unite(partLanguages);
- }
- return languages;
+ QTC_ASSERT(m_reader, return QSet<Core::Id>());
+ return m_reader->updateCodeModel(ppBuilder);
}
void BuildDirManager::parse()
{
+ updateReader();
checkConfiguration();
-
- CMakeTool *tool = CMakeKitInformation::cmakeTool(kit());
- const QStringList generatorArgs = CMakeGeneratorKitInformation::generatorArguments(kit());
-
- QTC_ASSERT(tool, return);
-
- const QString cbpFile = CMakeManager::findCbpFile(QDir(workDirectory().toString()));
- const QFileInfo cbpFileFi = cbpFile.isEmpty() ? QFileInfo() : QFileInfo(cbpFile);
- if (!cbpFileFi.exists()) {
- // Initial create:
- startCMake(tool, generatorArgs, intendedConfiguration());
- return;
- }
-
- const bool mustUpdate = m_cmakeFiles.isEmpty()
- || Utils::anyOf(m_cmakeFiles, [&cbpFileFi](const Utils::FileName &f) {
- return f.toFileInfo().lastModified() > cbpFileFi.lastModified();
- });
- if (mustUpdate) {
- startCMake(tool, generatorArgs, CMakeConfig());
- } else {
- extractData();
- m_hasData = true;
- emit dataAvailable();
- }
+ QTC_ASSERT(m_reader, return);
+ m_reader->parse(false);
}
void BuildDirManager::clearCache()
@@ -316,368 +211,34 @@ void BuildDirManager::clearCache()
QList<CMakeBuildTarget> BuildDirManager::buildTargets() const
{
- return m_buildTargets;
+ QTC_ASSERT(m_reader, return QList<CMakeBuildTarget>());
+ return m_reader->buildTargets();
}
CMakeConfig BuildDirManager::parsedConfiguration() const
{
- if (m_cmakeCache.isEmpty()) {
- Utils::FileName cacheFile = workDirectory();
- cacheFile.appendPath(QLatin1String("CMakeCache.txt"));
- if (!cacheFile.exists())
- return m_cmakeCache;
- QString errorMessage;
- m_cmakeCache = parseConfiguration(cacheFile, &errorMessage);
- if (!errorMessage.isEmpty())
- emit errorOccured(errorMessage);
- const Utils::FileName sourceOfBuildDir
- = Utils::FileName::fromUtf8(CMakeConfigItem::valueOf("CMAKE_HOME_DIRECTORY", m_cmakeCache));
- const Utils::FileName canonicalSourceOfBuildDir = Utils::FileUtils::canonicalPath(sourceOfBuildDir);
- const Utils::FileName canonicalSourceDirectory = Utils::FileUtils::canonicalPath(sourceDirectory());
- if (canonicalSourceOfBuildDir != canonicalSourceDirectory) // Uses case-insensitive compare where appropriate
- emit errorOccured(tr("The build directory is not for %1 but for %2")
- .arg(canonicalSourceOfBuildDir.toUserOutput(),
- canonicalSourceDirectory.toUserOutput()));
- }
+ QTC_ASSERT(m_reader, return m_cmakeCache);
+ if (m_cmakeCache.isEmpty())
+ m_cmakeCache = m_reader->parsedConfiguration();
return m_cmakeCache;
}
-void BuildDirManager::stopProcess()
-{
- if (!m_cmakeProcess)
- return;
-
- m_cmakeProcess->disconnect();
-
- if (m_cmakeProcess->state() == QProcess::Running) {
- m_cmakeProcess->terminate();
- if (!m_cmakeProcess->waitForFinished(500) && m_cmakeProcess->state() == QProcess::Running)
- m_cmakeProcess->kill();
- }
-
- cleanUpProcess();
-
- if (!m_future)
- return;
- m_future->reportCanceled();
- m_future->reportFinished();
- delete m_future;
- m_future = nullptr;
-}
-
-void BuildDirManager::cleanUpProcess()
-{
- if (!m_cmakeProcess)
- return;
-
- QTC_ASSERT(m_cmakeProcess->state() == QProcess::NotRunning, return);
-
- m_cmakeProcess->disconnect();
-
- if (m_cmakeProcess->state() == QProcess::Running) {
- m_cmakeProcess->terminate();
- if (!m_cmakeProcess->waitForFinished(500) && m_cmakeProcess->state() == QProcess::Running)
- m_cmakeProcess->kill();
- }
- m_cmakeProcess->waitForFinished();
- delete m_cmakeProcess;
- m_cmakeProcess = nullptr;
-
- // Delete issue parser:
- m_parser->flush();
- delete m_parser;
- m_parser = nullptr;
-}
-
-void BuildDirManager::extractData()
-{
- const Utils::FileName topCMake
- = Utils::FileName::fromString(sourceDirectory().toString() + QLatin1String("/CMakeLists.txt"));
-
- resetData();
-
- m_projectName = sourceDirectory().fileName();
- m_files.append(new FileNode(topCMake, ProjectFileType, false));
- // Do not insert topCMake into m_cmakeFiles: The project already watches that!
-
- // Find cbp file
- Utils::FileName cbpFile = Utils::FileName::fromString(CMakeManager::findCbpFile(workDirectory().toString()));
- if (cbpFile.isEmpty())
- return;
- m_cmakeFiles.insert(cbpFile);
-
- // Add CMakeCache.txt file:
- Utils::FileName cacheFile = workDirectory();
- cacheFile.appendPath(QLatin1String("CMakeCache.txt"));
- if (cacheFile.toFileInfo().exists())
- m_cmakeFiles.insert(cacheFile);
-
- // setFolderName
- CMakeCbpParser cbpparser;
- CMakeTool *cmake = CMakeKitInformation::cmakeTool(kit());
- // Parsing
- if (!cbpparser.parseCbpFile(cmake->pathMapper(), cbpFile, sourceDirectory()))
- return;
-
- m_projectName = cbpparser.projectName();
-
- m_files = cbpparser.fileList();
- if (cbpparser.hasCMakeFiles()) {
- m_files.append(cbpparser.cmakeFileList());
- foreach (const FileNode *node, cbpparser.cmakeFileList())
- m_cmakeFiles.insert(node->filePath());
- }
-
- // Make sure the top cmakelists.txt file is always listed:
- if (!Utils::contains(m_files, [topCMake](FileNode *fn) { return fn->filePath() == topCMake; })) {
- m_files.append(new FileNode(topCMake, ProjectFileType, false));
- }
-
- m_buildTargets = cbpparser.buildTargets();
-}
-
-void BuildDirManager::startCMake(CMakeTool *tool, const QStringList &generatorArgs,
- const CMakeConfig &config)
-{
- QTC_ASSERT(tool && tool->isValid(), return);
-
- QTC_ASSERT(!m_cmakeProcess, return);
- QTC_ASSERT(!m_parser, return);
- QTC_ASSERT(!m_future, return);
-
- // Find a directory to set up into:
- if (!buildDirectory().exists()) {
- if (!m_tempDir)
- m_tempDir = new QTemporaryDir(QDir::tempPath() + QLatin1String("/qtc-cmake-XXXXXX"));
- QTC_ASSERT(m_tempDir->isValid(), return);
- }
-
- // Make sure work directory exists:
- QTC_ASSERT(workDirectory().exists(), return);
-
- m_parser = new CMakeParser;
- QDir source = QDir(sourceDirectory().toString());
- connect(m_parser, &IOutputParser::addTask, m_parser,
- [source](const Task &task) {
- if (task.file.isEmpty() || task.file.toFileInfo().isAbsolute()) {
- TaskHub::addTask(task);
- } else {
- Task t = task;
- t.file = Utils::FileName::fromString(source.absoluteFilePath(task.file.toString()));
- TaskHub::addTask(t);
- }
- });
-
- // Always use the sourceDir: If we are triggered because the build directory is getting deleted
- // then we are racing against CMakeCache.txt also getting deleted.
- const QString srcDir = sourceDirectory().toString();
-
- m_cmakeProcess = new Utils::QtcProcess(this);
- m_cmakeProcess->setWorkingDirectory(workDirectory().toString());
- m_cmakeProcess->setEnvironment(m_buildConfiguration->environment());
-
- connect(m_cmakeProcess, &QProcess::readyReadStandardOutput,
- this, &BuildDirManager::processCMakeOutput);
- connect(m_cmakeProcess, &QProcess::readyReadStandardError,
- this, &BuildDirManager::processCMakeError);
- connect(m_cmakeProcess, static_cast<void(QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished),
- this, &BuildDirManager::cmakeFinished);
-
- QString args;
- Utils::QtcProcess::addArg(&args, srcDir);
- Utils::QtcProcess::addArgs(&args, generatorArgs);
- Utils::QtcProcess::addArgs(&args, toArguments(config, kit()));
-
- TaskHub::clearTasks(ProjectExplorer::Constants::TASK_CATEGORY_BUILDSYSTEM);
-
- Core::MessageManager::write(tr("Running \"%1 %2\" in %3.")
- .arg(tool->cmakeExecutable().toUserOutput())
- .arg(args)
- .arg(workDirectory().toUserOutput()));
-
- m_future = new QFutureInterface<void>();
- m_future->setProgressRange(0, 1);
- Core::ProgressManager::addTask(m_future->future(),
- tr("Configuring \"%1\"").arg(m_buildConfiguration->target()->project()->displayName()),
- "CMake.Configure");
-
- m_cmakeProcess->setCommand(tool->cmakeExecutable().toString(), args);
- m_cmakeProcess->start();
- emit configurationStarted();
-}
-
-void BuildDirManager::cmakeFinished(int code, QProcess::ExitStatus status)
-{
- QTC_ASSERT(m_cmakeProcess, return);
-
- // process rest of the output:
- processCMakeOutput();
- processCMakeError();
-
- cleanUpProcess();
-
- extractData(); // try even if cmake failed...
-
- QString msg;
- if (status != QProcess::NormalExit)
- msg = tr("*** cmake process crashed!");
- else if (code != 0)
- msg = tr("*** cmake process exited with exit code %1.").arg(code);
-
- if (!msg.isEmpty()) {
- Core::MessageManager::write(msg);
- TaskHub::addTask(Task::Error, msg, ProjectExplorer::Constants::TASK_CATEGORY_BUILDSYSTEM);
- m_future->reportCanceled();
- } else {
- m_future->setProgressValue(1);
- }
-
- m_future->reportFinished();
- delete m_future;
- m_future = nullptr;
-
- m_hasData = true;
- emit dataAvailable();
-}
-
-static QString lineSplit(const QString &rest, const QByteArray &array, std::function<void(const QString &)> f)
-{
- QString tmp = rest + Utils::SynchronousProcess::normalizeNewlines(QString::fromLocal8Bit(array));
- int start = 0;
- int end = tmp.indexOf(QLatin1Char('\n'), start);
- while (end >= 0) {
- f(tmp.mid(start, end - start));
- start = end + 1;
- end = tmp.indexOf(QLatin1Char('\n'), start);
- }
- return tmp.mid(start);
-}
-
-void BuildDirManager::processCMakeOutput()
-{
- static QString rest;
- rest = lineSplit(rest, m_cmakeProcess->readAllStandardOutput(), [this](const QString &s) { Core::MessageManager::write(s); });
-}
-
-void BuildDirManager::processCMakeError()
-{
- static QString rest;
- rest = lineSplit(rest, m_cmakeProcess->readAllStandardError(), [this](const QString &s) {
- m_parser->stdError(s);
- Core::MessageManager::write(s);
- });
-}
-
-QStringList BuildDirManager::getCXXFlagsFor(const CMakeBuildTarget &buildTarget,
- QHash<QString, QStringList> &cache)
-{
- // check cache:
- auto it = cache.constFind(buildTarget.title);
- if (it != cache.constEnd())
- return *it;
-
- if (extractCXXFlagsFromMake(buildTarget, cache))
- return cache.value(buildTarget.title);
-
- if (extractCXXFlagsFromNinja(buildTarget, cache))
- return cache.value(buildTarget.title);
-
- cache.insert(buildTarget.title, QStringList());
- return QStringList();
-}
-
-bool BuildDirManager::extractCXXFlagsFromMake(const CMakeBuildTarget &buildTarget,
- QHash<QString, QStringList> &cache)
-{
- QString makeCommand = buildTarget.makeCommand.toString();
- int startIndex = makeCommand.indexOf('\"');
- int endIndex = makeCommand.indexOf('\"', startIndex + 1);
- if (startIndex != -1 && endIndex != -1) {
- startIndex += 1;
- QString makefile = makeCommand.mid(startIndex, endIndex - startIndex);
- int slashIndex = makefile.lastIndexOf('/');
- makefile.truncate(slashIndex);
- makefile.append("/CMakeFiles/" + buildTarget.title + ".dir/flags.make");
- QFile file(makefile);
- if (file.exists()) {
- file.open(QIODevice::ReadOnly | QIODevice::Text);
- QTextStream stream(&file);
- while (!stream.atEnd()) {
- QString line = stream.readLine().trimmed();
- if (line.startsWith("CXX_FLAGS =")) {
- // Skip past =
- cache.insert(buildTarget.title,
- line.mid(11).trimmed().split(' ', QString::SkipEmptyParts));
- return true;
- }
- }
- }
- }
- return false;
-}
-
-bool BuildDirManager::extractCXXFlagsFromNinja(const CMakeBuildTarget &buildTarget,
- QHash<QString, QStringList> &cache)
-{
- Q_UNUSED(buildTarget)
- if (!cache.isEmpty()) // We fill the cache in one go!
- return false;
-
- // Attempt to find build.ninja file and obtain FLAGS (CXX_FLAGS) from there if no suitable flags.make were
- // found
- // Get "all" target's working directory
- QByteArray ninjaFile;
- QString buildNinjaFile = buildTargets().at(0).workingDirectory.toString();
- buildNinjaFile += "/build.ninja";
- QFile buildNinja(buildNinjaFile);
- if (buildNinja.exists()) {
- buildNinja.open(QIODevice::ReadOnly | QIODevice::Text);
- ninjaFile = buildNinja.readAll();
- buildNinja.close();
- }
-
- if (ninjaFile.isEmpty())
- return false;
-
- QTextStream stream(ninjaFile);
- bool cxxFound = false;
- const QString targetSignature = "# Object build statements for ";
- QString currentTarget;
-
- while (!stream.atEnd()) {
- // 1. Look for a block that refers to the current target
- // 2. Look for a build rule which invokes CXX_COMPILER
- // 3. Return the FLAGS definition
- QString line = stream.readLine().trimmed();
- if (line.startsWith('#')) {
- if (line.startsWith(targetSignature)) {
- int pos = line.lastIndexOf(' ');
- currentTarget = line.mid(pos + 1);
- }
- } else if (!currentTarget.isEmpty() && line.startsWith("build")) {
- cxxFound = line.indexOf("CXX_COMPILER") != -1;
- } else if (cxxFound && line.startsWith("FLAGS =")) {
- // Skip past =
- cache.insert(currentTarget, line.mid(7).trimmed().split(' ', QString::SkipEmptyParts));
- }
- }
- return !cache.isEmpty();
-}
-
void BuildDirManager::checkConfiguration()
{
if (m_tempDir) // always throw away changes in the tmpdir!
return;
+ updateReader();
+
Kit *k = m_buildConfiguration->target()->kit();
- const CMakeConfig cache = parsedConfiguration();
+ const CMakeConfig cache = m_reader->parsedConfiguration();
if (cache.isEmpty())
return; // No cache file yet.
CMakeConfig newConfig;
QSet<QString> changedKeys;
QSet<QString> removedKeys;
- foreach (const CMakeConfigItem &iBc, intendedConfiguration()) {
+ foreach (const CMakeConfigItem &iBc, m_buildConfiguration->cmakeConfiguration()) {
const CMakeConfigItem &iCache
= Utils::findOrDefault(cache, [&iBc](const CMakeConfigItem &i) { return i.key == iBc.key; });
if (iCache.isNull()) {
@@ -715,129 +276,11 @@ void BuildDirManager::checkConfiguration()
box->setDefaultButton(defaultButton);
int ret = box->exec();
- if (ret == QMessageBox::Apply)
+ if (ret == QMessageBox::Apply) {
m_buildConfiguration->setCMakeConfiguration(newConfig);
- }
-}
-
-void BuildDirManager::handleDocumentSaves(Core::IDocument *document)
-{
- Target *t = m_buildConfiguration->target()->project()->activeTarget();
- BuildConfiguration *bc = t ? t->activeBuildConfiguration() : nullptr;
-
- if (!m_cmakeFiles.contains(document->filePath()) ||
- m_buildConfiguration->target() != t ||
- m_buildConfiguration != bc)
- return;
-
- m_reparseTimer.start(100);
-}
-
-static QByteArray trimCMakeCacheLine(const QByteArray &in) {
- int start = 0;
- while (start < in.count() && (in.at(start) == ' ' || in.at(start) == '\t'))
- ++start;
-
- return in.mid(start, in.count() - start - 1);
-}
-
-static QByteArrayList splitCMakeCacheLine(const QByteArray &line) {
- const int colonPos = line.indexOf(':');
- if (colonPos < 0)
- return QByteArrayList();
-
- const int equalPos = line.indexOf('=', colonPos + 1);
- if (equalPos < colonPos)
- return QByteArrayList();
-
- return QByteArrayList() << line.mid(0, colonPos)
- << line.mid(colonPos + 1, equalPos - colonPos - 1)
- << line.mid(equalPos + 1);
-}
-
-static CMakeConfigItem::Type fromByteArray(const QByteArray &type) {
- if (type == "BOOL")
- return CMakeConfigItem::BOOL;
- if (type == "STRING")
- return CMakeConfigItem::STRING;
- if (type == "FILEPATH")
- return CMakeConfigItem::FILEPATH;
- if (type == "PATH")
- return CMakeConfigItem::PATH;
- QTC_CHECK(type == "INTERNAL" || type == "STATIC");
-
- return CMakeConfigItem::INTERNAL;
-}
-
-CMakeConfig BuildDirManager::parseConfiguration(const Utils::FileName &cacheFile,
- QString *errorMessage)
-{
- CMakeConfig result;
- QFile cache(cacheFile.toString());
- if (!cache.open(QIODevice::ReadOnly | QIODevice::Text)) {
- if (errorMessage)
- *errorMessage = tr("Failed to open %1 for reading.").arg(cacheFile.toUserOutput());
- return CMakeConfig();
- }
-
- QSet<QByteArray> advancedSet;
- QMap<QByteArray, QByteArray> valuesMap;
- QByteArray documentation;
- while (!cache.atEnd()) {
- const QByteArray line = trimCMakeCacheLine(cache.readLine());
-
- if (line.isEmpty() || line.startsWith('#'))
- continue;
-
- if (line.startsWith("//")) {
- documentation = line.mid(2);
- continue;
- }
-
- const QByteArrayList pieces = splitCMakeCacheLine(line);
- if (pieces.isEmpty())
- continue;
-
- QTC_ASSERT(pieces.count() == 3, continue);
- const QByteArray key = pieces.at(0);
- const QByteArray type = pieces.at(1);
- const QByteArray value = pieces.at(2);
-
- if (key.endsWith("-ADVANCED") && value == "1") {
- advancedSet.insert(key.left(key.count() - 9 /* "-ADVANCED" */));
- } else if (key.endsWith("-STRINGS") && fromByteArray(type) == CMakeConfigItem::INTERNAL) {
- valuesMap[key.left(key.count() - 8) /* "-STRINGS" */] = value;
- } else {
- CMakeConfigItem::Type t = fromByteArray(type);
- result << CMakeConfigItem(key, t, documentation, value);
+ updateReader(); // Apply changes to reader
}
}
-
- // Set advanced flags:
- for (int i = 0; i < result.count(); ++i) {
- CMakeConfigItem &item = result[i];
- item.isAdvanced = advancedSet.contains(item.key);
-
- if (valuesMap.contains(item.key)) {
- item.values = CMakeConfigItem::cmakeSplitValue(QString::fromUtf8(valuesMap[item.key]));
- } else if (item.key == "CMAKE_BUILD_TYPE") {
- // WA for known options
- item.values << "" << "Debug" << "Release" << "MinSizeRel" << "RelWithDebInfo";
- }
- }
-
- Utils::sort(result, CMakeConfigItem::sortOperator());
-
- return result;
-}
-
-void BuildDirManager::handleCmakeFileChange()
-{
- Target *t = m_buildConfiguration->target()->project()->activeTarget();
- BuildConfiguration *bc = t ? t->activeBuildConfiguration() : nullptr;
-
- if (m_buildConfiguration->target() == t && m_buildConfiguration == bc)
- cmakeFilesChanged();
}
void BuildDirManager::maybeForceReparse()
@@ -851,17 +294,18 @@ void BuildDirManager::maybeForceReparse()
const QByteArrayList criticalKeys
= QByteArrayList() << GENERATOR_KEY << CMAKE_COMMAND_KEY;
- if (!m_hasData) {
+ if (!m_reader->hasData()) {
forceReparse();
return;
}
const CMakeConfig currentConfig = parsedConfiguration();
- const CMakeTool *tool = CMakeKitInformation::cmakeTool(kit());
+ Kit *k = m_buildConfiguration->target()->kit();
+ const CMakeTool *tool = CMakeKitInformation::cmakeTool(k);
QTC_ASSERT(tool, return); // No cmake... we should not have ended up here in the first place
- const QString extraKitGenerator = CMakeGeneratorKitInformation::extraGenerator(kit());
- const QString mainKitGenerator = CMakeGeneratorKitInformation::generator(kit());
+ const QString extraKitGenerator = CMakeGeneratorKitInformation::extraGenerator(k);
+ const QString mainKitGenerator = CMakeGeneratorKitInformation::generator(k);
CMakeConfig targetConfig = m_buildConfiguration->cmakeConfiguration();
targetConfig.append(CMakeConfigItem(GENERATOR_KEY, CMakeConfigItem::INTERNAL,
QByteArray(), mainKitGenerator.toUtf8()));
diff --git a/src/plugins/cmakeprojectmanager/builddirmanager.h b/src/plugins/cmakeprojectmanager/builddirmanager.h
index cc00f69bc3c..0663afeb747 100644
--- a/src/plugins/cmakeprojectmanager/builddirmanager.h
+++ b/src/plugins/cmakeprojectmanager/builddirmanager.h
@@ -25,26 +25,17 @@
#pragma once
-#include "cmakecbpparser.h"
+#include "builddirreader.h"
#include "cmakeconfigitem.h"
-#include "cmakefile.h"
-#include <projectexplorer/task.h>
-
-#include <utils/environment.h>
-#include <utils/qtcprocess.h>
#include <utils/fileutils.h>
-#include <QByteArray>
-#include <QFutureInterface>
#include <QObject>
-#include <QSet>
+#include <QTemporaryDir>
#include <QTimer>
-QT_FORWARD_DECLARE_CLASS(QTemporaryDir);
-QT_FORWARD_DECLARE_CLASS(QFileSystemWatcher);
+#include <memory>
-namespace Core { class IDocument; }
namespace CppTools { class ProjectPartBuilder; }
namespace ProjectExplorer {
@@ -68,7 +59,6 @@ class BuildDirManager : public QObject
public:
BuildDirManager(CMakeBuildConfiguration *bc);
- ~BuildDirManager() override;
bool isParsing() const;
@@ -87,9 +77,6 @@ public:
void checkConfiguration();
- void handleDocumentSaves(Core::IDocument *document);
- void handleCmakeFileChange();
-
signals:
void configurationStarted() const;
void dataAvailable() const;
@@ -99,50 +86,22 @@ protected:
static CMakeConfig parseConfiguration(const Utils::FileName &cacheFile,
QString *errorMessage);
- const ProjectExplorer::Kit *kit() const;
- const Utils::FileName buildDirectory() const;
const Utils::FileName workDirectory() const;
- const Utils::FileName sourceDirectory() const;
- const CMakeConfig intendedConfiguration() const;
private:
- void parse();
-
- void cmakeFilesChanged();
-
- void stopProcess();
- void cleanUpProcess();
- void extractData();
+ void updateReader();
- void startCMake(CMakeTool *tool, const QStringList &generatorArgs, const CMakeConfig &config);
-
- void cmakeFinished(int code, QProcess::ExitStatus status);
- void processCMakeOutput();
- void processCMakeError();
-
- QStringList getCXXFlagsFor(const CMakeBuildTarget &buildTarget, QHash<QString, QStringList> &cache);
- bool extractCXXFlagsFromMake(const CMakeBuildTarget &buildTarget, QHash<QString, QStringList> &cache);
- bool extractCXXFlagsFromNinja(const CMakeBuildTarget &buildTarget, QHash<QString, QStringList> &cache);
+ void parse();
- bool m_hasData = false;
+ void becameDirty();
CMakeBuildConfiguration *m_buildConfiguration = nullptr;
- Utils::QtcProcess *m_cmakeProcess = nullptr;
- QTemporaryDir *m_tempDir = nullptr;
+ mutable std::unique_ptr<QTemporaryDir> m_tempDir = nullptr;
mutable CMakeConfig m_cmakeCache;
- QSet<Utils::FileName> m_cmakeFiles;
- QString m_projectName;
- QList<CMakeBuildTarget> m_buildTargets;
- QList<ProjectExplorer::FileNode *> m_files;
-
- // For error reporting:
- ProjectExplorer::IOutputParser *m_parser = nullptr;
- QFutureInterface<void> *m_future = nullptr;
-
QTimer m_reparseTimer;
- QSet<Internal::CMakeFile *> m_watchedFiles;
+ std::unique_ptr<BuildDirReader> m_reader;
};
} // namespace Internal
diff --git a/src/plugins/cmakeprojectmanager/builddirreader.cpp b/src/plugins/cmakeprojectmanager/builddirreader.cpp
new file mode 100644
index 00000000000..b31eb0702ad
--- /dev/null
+++ b/src/plugins/cmakeprojectmanager/builddirreader.cpp
@@ -0,0 +1,95 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "builddirreader.h"
+
+#include "cmakebuildconfiguration.h"
+#include "cmakekitinformation.h"
+#include "tealeafreader.h"
+
+#include <projectexplorer/kitinformation.h>
+#include <projectexplorer/target.h>
+
+using namespace ProjectExplorer;
+
+namespace CMakeProjectManager {
+namespace Internal {
+
+// --------------------------------------------------------------------
+// BuildDirReader:
+// --------------------------------------------------------------------
+
+BuildDirReader::Parameters::Parameters() = default;
+
+BuildDirReader::Parameters::Parameters(const CMakeBuildConfiguration *bc)
+{
+ const ProjectExplorer::Kit *k = bc->target()->kit();
+
+ projectName = bc->target()->project()->displayName();
+
+ sourceDirectory = bc->target()->project()->projectDirectory();
+ buildDirectory = bc->buildDirectory();
+
+ environment = bc->environment();
+
+ CMakeTool *cmake = CMakeKitInformation::cmakeTool(k);
+ cmakeVersion = cmake->version();
+ cmakeHasServerMode = cmake->hasServerMode();
+ cmakeExecutable = cmake->cmakeExecutable();
+
+ pathMapper = cmake->pathMapper();
+
+ auto tc = ProjectExplorer::ToolChainKitInformation::toolChain(k, ProjectExplorer::ToolChain::Language::Cxx);
+ if (tc)
+ toolChainId = tc->id();
+ sysRoot = ProjectExplorer::SysRootKitInformation::sysRoot(k);
+
+ expander = k->macroExpander();
+
+ configuration = bc->cmakeConfiguration();
+
+ generator = CMakeGeneratorKitInformation::generator(k);
+ extraGenerator = CMakeGeneratorKitInformation::extraGenerator(k);
+ platform = CMakeGeneratorKitInformation::platform(k);
+ toolset = CMakeGeneratorKitInformation::toolset(k);
+ generatorArguments = CMakeGeneratorKitInformation::generatorArguments(k);
+}
+
+BuildDirReader::Parameters::Parameters(const BuildDirReader::Parameters &other) = default;
+
+
+BuildDirReader *BuildDirReader::createReader(const BuildDirReader::Parameters &p)
+{
+ Q_UNUSED(p); // FIXME
+ return new TeaLeafReader;
+}
+
+void BuildDirReader::setParameters(const BuildDirReader::Parameters &p)
+{
+ m_parameters = p;
+}
+
+} // namespace Internal
+} // namespace CMakeProjectManager
diff --git a/src/plugins/cmakeprojectmanager/builddirreader.h b/src/plugins/cmakeprojectmanager/builddirreader.h
new file mode 100644
index 00000000000..ce08966f17b
--- /dev/null
+++ b/src/plugins/cmakeprojectmanager/builddirreader.h
@@ -0,0 +1,115 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include "cmakeconfigitem.h"
+#include "cmakeproject.h"
+#include "cmaketool.h"
+
+#include <utils/environment.h>
+#include <utils/fileutils.h>
+#include <utils/macroexpander.h>
+
+#include <QFutureInterface>
+#include <QObject>
+
+namespace CppTools { class ProjectPartBuilder; }
+
+namespace ProjectExplorer {
+class IOutputParser;
+class Kit;
+} // ProjectExplorer
+
+namespace Utils { class QtcProcess; }
+
+namespace CMakeProjectManager {
+namespace Internal {
+
+class CMakeBuildConfiguration;
+
+class BuildDirReader : public QObject
+{
+ Q_OBJECT
+
+public:
+ struct Parameters {
+ Parameters();
+ Parameters(const CMakeBuildConfiguration *bc);
+ Parameters(const Parameters &other);
+
+ QString projectName;
+
+ Utils::FileName sourceDirectory;
+ Utils::FileName buildDirectory;
+ Utils::Environment environment;
+ Utils::FileName cmakeExecutable;
+ CMakeTool::Version cmakeVersion;
+ bool cmakeHasServerMode;
+ CMakeTool::PathMapper pathMapper;
+
+ QByteArray toolChainId;
+
+ Utils::FileName sysRoot;
+
+ Utils::MacroExpander *expander = nullptr;
+
+ CMakeConfig configuration;
+
+ QString generator;
+ QString extraGenerator;
+ QString platform;
+ QString toolset;
+ QStringList generatorArguments;
+ };
+
+ static BuildDirReader *createReader(const BuildDirReader::Parameters &p);
+ void setParameters(const Parameters &p);
+
+ virtual bool isCompatible(const Parameters &p) = 0;
+ virtual void resetData() = 0;
+ virtual void parse(bool force) = 0;
+ virtual void stop() = 0;
+
+ virtual bool isParsing() const = 0;
+ virtual bool hasData() const = 0;
+
+ virtual CMakeConfig parsedConfiguration() const = 0;
+ virtual QList<CMakeBuildTarget> buildTargets() const = 0;
+ virtual void generateProjectTree(CMakeProjectNode *root) = 0;
+ virtual QSet<Core::Id> updateCodeModel(CppTools::ProjectPartBuilder &ppBuilder) = 0;
+
+signals:
+ void configurationStarted() const;
+ void dataAvailable() const;
+ void dirty() const;
+ void errorOccured(const QString &message) const;
+
+protected:
+ Parameters m_parameters;
+};
+
+} // namespace Internal
+} // namespace CMakeProjectManager
diff --git a/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.cpp b/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.cpp
index c5d6bdafc4b..104e0b5a501 100644
--- a/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.cpp
+++ b/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.cpp
@@ -139,17 +139,17 @@ void CMakeBuildConfiguration::ctor()
target()->kit(),
displayName(), BuildConfiguration::Unknown));
- connect(m_buildDirManager, &BuildDirManager::dataAvailable,
+ connect(m_buildDirManager.get(), &BuildDirManager::dataAvailable,
this, &CMakeBuildConfiguration::dataAvailable);
- connect(m_buildDirManager, &BuildDirManager::errorOccured,
+ connect(m_buildDirManager.get(), &BuildDirManager::errorOccured,
this, &CMakeBuildConfiguration::setError);
- connect(m_buildDirManager, &BuildDirManager::configurationStarted,
+ connect(m_buildDirManager.get(), &BuildDirManager::configurationStarted,
this, [this]() { m_completeConfigurationCache.clear(); emit parsingStarted(); });
connect(this, &CMakeBuildConfiguration::environmentChanged,
- m_buildDirManager, &BuildDirManager::forceReparse);
+ m_buildDirManager.get(), &BuildDirManager::forceReparse);
connect(this, &CMakeBuildConfiguration::buildDirectoryChanged,
- m_buildDirManager, &BuildDirManager::forceReparse);
+ m_buildDirManager.get(), &BuildDirManager::forceReparse);
connect(this, &CMakeBuildConfiguration::parsingStarted, project, &CMakeProject::handleParsingStarted);
connect(this, &CMakeBuildConfiguration::dataAvailable, project, &CMakeProject::updateProjectData);
diff --git a/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.h b/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.h
index 2fa82cb7d53..a4cf71d12e7 100644
--- a/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.h
+++ b/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.h
@@ -32,6 +32,8 @@
#include <projectexplorer/buildconfiguration.h>
#include <projectexplorer/abi.h>
+#include <memory>
+
namespace CppTools { class ProjectPartBuilder; }
namespace ProjectExplorer { class ToolChain; }
@@ -114,7 +116,7 @@ private:
mutable QList<CMakeConfigItem> m_completeConfigurationCache;
- BuildDirManager *const m_buildDirManager = nullptr;
+ std::unique_ptr<BuildDirManager> m_buildDirManager;
friend class CMakeBuildSettingsWidget;
friend class CMakeProjectManager::CMakeProject;
diff --git a/src/plugins/cmakeprojectmanager/cmakefile.cpp b/src/plugins/cmakeprojectmanager/cmakefile.cpp
deleted file mode 100644
index 612e48ec8d1..00000000000
--- a/src/plugins/cmakeprojectmanager/cmakefile.cpp
+++ /dev/null
@@ -1,65 +0,0 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of Qt Creator.
-**
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 3 as published by the Free Software
-** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-3.0.html.
-**
-****************************************************************************/
-
-#include "cmakefile.h"
-
-#include "builddirmanager.h"
-#include "cmakeprojectconstants.h"
-
-#include <projectexplorer/target.h>
-
-#include <utils/fileutils.h>
-
-using namespace Utils;
-
-namespace CMakeProjectManager {
-namespace Internal {
-
-CMakeFile::CMakeFile(BuildDirManager *bdm, const FileName &fileName) : m_buildDirManager(bdm)
-{
- setId("Cmake.ProjectFile");
- setMimeType(QLatin1String(Constants::CMAKEPROJECTMIMETYPE));
- setFilePath(fileName);
-}
-
-Core::IDocument::ReloadBehavior CMakeFile::reloadBehavior(ChangeTrigger state, ChangeType type) const
-{
- Q_UNUSED(state)
- Q_UNUSED(type)
- return BehaviorSilent;
-}
-
-bool CMakeFile::reload(QString *errorString, Core::IDocument::ReloadFlag flag, Core::IDocument::ChangeType type)
-{
- Q_UNUSED(errorString);
- Q_UNUSED(flag);
-
- if (type != TypePermissions)
- m_buildDirManager->handleCmakeFileChange();
- return true;
-}
-
-} // namespace Internal
-} // namespace CMakeProjectManager
diff --git a/src/plugins/cmakeprojectmanager/cmakefile.h b/src/plugins/cmakeprojectmanager/cmakefile.h
deleted file mode 100644
index 4f3376b640c..00000000000
--- a/src/plugins/cmakeprojectmanager/cmakefile.h
+++ /dev/null
@@ -1,48 +0,0 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of Qt Creator.
-**
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 3 as published by the Free Software
-** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-3.0.html.
-**
-****************************************************************************/
-
-#pragma once
-
-#include <coreplugin/idocument.h>
-
-namespace CMakeProjectManager {
-namespace Internal {
-
-class BuildDirManager;
-
-class CMakeFile : public Core::IDocument
-{
-public:
- CMakeFile(BuildDirManager *bdm, const Utils::FileName &fileName);
-
- ReloadBehavior reloadBehavior(ChangeTrigger state, ChangeType type) const override;
- bool reload(QString *errorString, ReloadFlag flag, ChangeType type) override;
-
-private:
- BuildDirManager *m_buildDirManager;
-};
-
-} // namespace Internal
-} // namespace CMakeProjectManager
diff --git a/src/plugins/cmakeprojectmanager/cmakeprojectmanager.pro b/src/plugins/cmakeprojectmanager/cmakeprojectmanager.pro
index 82cd93df82a..263efb955d7 100644
--- a/src/plugins/cmakeprojectmanager/cmakeprojectmanager.pro
+++ b/src/plugins/cmakeprojectmanager/cmakeprojectmanager.pro
@@ -2,6 +2,7 @@ DEFINES += CMAKEPROJECTMANAGER_LIBRARY
include(../../qtcreatorplugin.pri)
HEADERS = builddirmanager.h \
+ builddirreader.h \
cmakebuildinfo.h \
cmakebuildstep.h \
cmakeconfigitem.h \
@@ -24,14 +25,15 @@ HEADERS = builddirmanager.h \
cmakekitinformation.h \
cmakekitconfigwidget.h \
cmakecbpparser.h \
- cmakefile.h \
cmakebuildsettingswidget.h \
cmakeindenter.h \
cmakeautocompleter.h \
configmodel.h \
- configmodelitemdelegate.h
+ configmodelitemdelegate.h \
+ tealeafreader.h
SOURCES = builddirmanager.cpp \
+ builddirreader.cpp \
cmakebuildstep.cpp \
cmakeconfigitem.cpp \
cmakeproject.cpp \
@@ -51,11 +53,11 @@ SOURCES = builddirmanager.cpp \
cmakekitinformation.cpp \
cmakekitconfigwidget.cpp \
cmakecbpparser.cpp \
- cmakefile.cpp \
cmakebuildsettingswidget.cpp \
cmakeindenter.cpp \
cmakeautocompleter.cpp \
configmodel.cpp \
- configmodelitemdelegate.cpp
+ configmodelitemdelegate.cpp \
+ tealeafreader.cpp
RESOURCES += cmakeproject.qrc
diff --git a/src/plugins/cmakeprojectmanager/cmakeprojectmanager.qbs b/src/plugins/cmakeprojectmanager/cmakeprojectmanager.qbs
index 99fe635b277..92a55350c15 100644
--- a/src/plugins/cmakeprojectmanager/cmakeprojectmanager.qbs
+++ b/src/plugins/cmakeprojectmanager/cmakeprojectmanager.qbs
@@ -20,6 +20,8 @@ QtcPlugin {
files: [
"builddirmanager.cpp",
"builddirmanager.h",
+ "builddirreader.cpp",
+ "builddirreader.h",
"cmake_global.h",
"cmakebuildconfiguration.cpp",
"cmakebuildconfiguration.h",
@@ -34,8 +36,6 @@ QtcPlugin {
"cmakeconfigitem.h",
"cmakeeditor.cpp",
"cmakeeditor.h",
- "cmakefile.cpp",
- "cmakefile.h",
"cmakefilecompletionassist.cpp",
"cmakefilecompletionassist.h",
"cmakekitconfigwidget.h",
@@ -73,6 +73,8 @@ QtcPlugin {
"configmodel.cpp",
"configmodel.h",
"configmodelitemdelegate.cpp",
- "configmodelitemdelegate.h"
+ "configmodelitemdelegate.h",
+ "tealeafreader.cpp",
+ "tealeafreader.h"
]
}
diff --git a/src/plugins/cmakeprojectmanager/tealeafreader.cpp b/src/plugins/cmakeprojectmanager/tealeafreader.cpp
new file mode 100644
index 00000000000..76ce5f8f027
--- /dev/null
+++ b/src/plugins/cmakeprojectmanager/tealeafreader.cpp
@@ -0,0 +1,678 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "tealeafreader.h"
+
+#include "cmakebuildconfiguration.h"
+#include "cmakecbpparser.h"
+#include "cmakekitinformation.h"
+#include "cmakeparser.h"
+#include "cmakeprojectconstants.h"
+#include "cmakeprojectmanager.h"
+#include "cmakeprojectnodes.h"
+
+#include <coreplugin/documentmanager.h>
+#include <coreplugin/editormanager/editormanager.h>
+#include <coreplugin/idocument.h>
+#include <coreplugin/messagemanager.h>
+#include <coreplugin/progressmanager/progressmanager.h>
+#include <coreplugin/reaper.h>
+#include <cpptools/projectpartbuilder.h>
+#include <projectexplorer/headerpath.h>
+#include <projectexplorer/ioutputparser.h>
+#include <projectexplorer/kitinformation.h>
+#include <projectexplorer/projectexplorerconstants.h>
+#include <projectexplorer/target.h>
+#include <projectexplorer/task.h>
+#include <projectexplorer/taskhub.h>
+#include <projectexplorer/toolchain.h>
+#include <projectexplorer/toolchainmanager.h>
+
+#include <utils/algorithm.h>
+#include <utils/qtcassert.h>
+#include <utils/qtcprocess.h>
+
+#include <QDateTime>
+#include <QFileInfo>
+
+using namespace ProjectExplorer;
+
+// --------------------------------------------------------------------
+// Helper:
+// --------------------------------------------------------------------
+
+namespace CMakeProjectManager {
+namespace Internal {
+
+class CMakeFile : public Core::IDocument
+{
+public:
+ CMakeFile(TeaLeafReader *r, const Utils::FileName &fileName);
+
+ ReloadBehavior reloadBehavior(ChangeTrigger state, ChangeType type) const override;
+ bool reload(QString *errorString, ReloadFlag flag, ChangeType type) override;
+
+private:
+ TeaLeafReader *m_reader;
+};
+
+CMakeFile::CMakeFile(TeaLeafReader *r, const Utils::FileName &fileName) : m_reader(r)
+{
+ setId("Cmake.ProjectFile");
+ setMimeType(Constants::CMAKEPROJECTMIMETYPE);
+ setFilePath(fileName);
+}
+
+Core::IDocument::ReloadBehavior CMakeFile::reloadBehavior(ChangeTrigger state, ChangeType type) const
+{
+ Q_UNUSED(state)
+ Q_UNUSED(type)
+ return BehaviorSilent;
+}
+
+bool CMakeFile::reload(QString *errorString, Core::IDocument::ReloadFlag flag, Core::IDocument::ChangeType type)
+{
+ Q_UNUSED(errorString);
+ Q_UNUSED(flag);
+
+ if (type != TypePermissions)
+ emit m_reader->dirty();
+ return true;
+}
+
+static QString lineSplit(const QString &rest, const QByteArray &array, std::function<void(const QString &)> f)
+{
+ QString tmp = rest + Utils::SynchronousProcess::normalizeNewlines(QString::fromLocal8Bit(array));
+ int start = 0;
+ int end = tmp.indexOf(QLatin1Char('\n'), start);
+ while (end >= 0) {
+ f(tmp.mid(start, end - start));
+ start = end + 1;
+ end = tmp.indexOf(QLatin1Char('\n'), start);
+ }
+ return tmp.mid(start);
+}
+
+static QStringList toArguments(const CMakeConfig &config, const Utils::MacroExpander *expander) {
+ return Utils::transform(config, [expander](const CMakeConfigItem &i) -> QString {
+ return i.toArgument(expander);
+ });
+}
+
+static QByteArray trimCMakeCacheLine(const QByteArray &in) {
+ int start = 0;
+ while (start < in.count() && (in.at(start) == ' ' || in.at(start) == '\t'))
+ ++start;
+
+ return in.mid(start, in.count() - start - 1);
+}
+
+static QByteArrayList splitCMakeCacheLine(const QByteArray &line) {
+ const int colonPos = line.indexOf(':');
+ if (colonPos < 0)
+ return QByteArrayList();
+
+ const int equalPos = line.indexOf('=', colonPos + 1);
+ if (equalPos < colonPos)
+ return QByteArrayList();
+
+ return QByteArrayList() << line.mid(0, colonPos)
+ << line.mid(colonPos + 1, equalPos - colonPos - 1)
+ << line.mid(equalPos + 1);
+}
+
+static CMakeConfigItem::Type fromByteArray(const QByteArray &type) {
+ if (type == "BOOL")
+ return CMakeConfigItem::BOOL;
+ if (type == "STRING")
+ return CMakeConfigItem::STRING;
+ if (type == "FILEPATH")
+ return CMakeConfigItem::FILEPATH;
+ if (type == "PATH")
+ return CMakeConfigItem::PATH;
+ QTC_CHECK(type == "INTERNAL" || type == "STATIC");
+
+ return CMakeConfigItem::INTERNAL;
+}
+
+// --------------------------------------------------------------------
+// TeaLeafReader:
+// --------------------------------------------------------------------
+
+TeaLeafReader::TeaLeafReader()
+{
+ connect(Core::EditorManager::instance(), &Core::EditorManager::aboutToSave,
+ this, [this](const Core::IDocument *document) {
+ if (m_cmakeFiles.contains(document->filePath()))
+ emit dirty();
+ });
+}
+
+TeaLeafReader::~TeaLeafReader()
+{
+ stop();
+ resetData();
+}
+
+bool TeaLeafReader::isCompatible(const BuildDirReader::Parameters &p)
+{
+ Q_UNUSED(p);
+ return true; // FIXME: Needs to take server mode into account!
+}
+
+void TeaLeafReader::resetData()
+{
+ m_hasData = false;
+
+ qDeleteAll(m_watchedFiles);
+ m_watchedFiles.clear();
+
+ m_cmakeCache.clear();
+ m_projectName.clear();
+ m_buildTargets.clear();
+ qDeleteAll(m_files);
+ m_files.clear();
+}
+
+void TeaLeafReader::parse(bool force)
+{
+ const QString cbpFile = CMakeManager::findCbpFile(QDir(m_parameters.buildDirectory.toString()));
+ const QFileInfo cbpFileFi = cbpFile.isEmpty() ? QFileInfo() : QFileInfo(cbpFile);
+ if (!cbpFileFi.exists()) {
+ // Initial create:
+ startCMake(toArguments(m_parameters.configuration, m_parameters.expander));
+ return;
+ }
+
+ const bool mustUpdate = force
+ || m_cmakeFiles.isEmpty()
+ || Utils::anyOf(m_cmakeFiles, [&cbpFileFi](const Utils::FileName &f) {
+ return f.toFileInfo().lastModified() > cbpFileFi.lastModified();
+ });
+ if (mustUpdate) {
+ startCMake(QStringList());
+ } else {
+ extractData();
+ m_hasData = true;
+ emit dataAvailable();
+ }
+}
+
+void TeaLeafReader::stop()
+{
+ cleanUpProcess();
+
+ if (m_future) {
+ m_future->reportCanceled();
+ m_future->reportFinished();
+ delete m_future;
+ m_future = nullptr;
+ }
+}
+
+bool TeaLeafReader::isParsing() const
+{
+ return m_cmakeProcess && m_cmakeProcess->state() != QProcess::NotRunning;
+}
+
+bool TeaLeafReader::hasData() const
+{
+ return m_hasData;
+}
+
+QList<CMakeBuildTarget> TeaLeafReader::buildTargets() const
+{
+ return m_buildTargets;
+}
+
+CMakeConfig TeaLeafReader::parsedConfiguration() const
+{
+ CMakeConfig result;
+ Utils::FileName cacheFile = m_parameters.buildDirectory;
+ cacheFile.appendPath(QLatin1String("CMakeCache.txt"));
+ if (!cacheFile.exists())
+ return result;
+ QString errorMessage;
+ m_cmakeCache = parseConfiguration(cacheFile, &errorMessage);
+ if (!errorMessage.isEmpty())
+ emit errorOccured(errorMessage);
+ const Utils::FileName sourceOfBuildDir
+ = Utils::FileName::fromUtf8(CMakeConfigItem::valueOf("CMAKE_HOME_DIRECTORY", m_cmakeCache));
+ const Utils::FileName canonicalSourceOfBuildDir = Utils::FileUtils::canonicalPath(sourceOfBuildDir);
+ const Utils::FileName canonicalSourceDirectory = Utils::FileUtils::canonicalPath(m_parameters.sourceDirectory);
+ if (canonicalSourceOfBuildDir != canonicalSourceDirectory) { // Uses case-insensitive compare where appropriate
+ emit errorOccured(tr("The build directory is not for %1 but for %2")
+ .arg(canonicalSourceOfBuildDir.toUserOutput(),
+ canonicalSourceDirectory.toUserOutput()));
+ }
+ return result;
+}
+
+CMakeConfig TeaLeafReader::parseConfiguration(const Utils::FileName &cacheFile, QString *errorMessage) const
+{
+ CMakeConfig result;
+ QFile cache(cacheFile.toString());
+ if (!cache.open(QIODevice::ReadOnly | QIODevice::Text)) {
+ if (errorMessage)
+ *errorMessage = tr("Failed to open %1 for reading.").arg(cacheFile.toUserOutput());
+ return CMakeConfig();
+ }
+
+ QSet<QByteArray> advancedSet;
+ QMap<QByteArray, QByteArray> valuesMap;
+ QByteArray documentation;
+ while (!cache.atEnd()) {
+ const QByteArray line = trimCMakeCacheLine(cache.readLine());
+
+ if (line.isEmpty() || line.startsWith('#'))
+ continue;
+
+ if (line.startsWith("//")) {
+ documentation = line.mid(2);
+ continue;
+ }
+
+ const QByteArrayList pieces = splitCMakeCacheLine(line);
+ if (pieces.isEmpty())
+ continue;
+
+ QTC_ASSERT(pieces.count() == 3, continue);
+ const QByteArray key = pieces.at(0);
+ const QByteArray type = pieces.at(1);
+ const QByteArray value = pieces.at(2);
+
+ if (key.endsWith("-ADVANCED") && value == "1") {
+ advancedSet.insert(key.left(key.count() - 9 /* "-ADVANCED" */));
+ } else if (key.endsWith("-STRINGS") && fromByteArray(type) == CMakeConfigItem::INTERNAL) {
+ valuesMap[key.left(key.count() - 8) /* "-STRINGS" */] = value;
+ } else {
+ CMakeConfigItem::Type t = fromByteArray(type);
+ result << CMakeConfigItem(key, t, documentation, value);
+ }
+ }
+
+ // Set advanced flags:
+ for (int i = 0; i < result.count(); ++i) {
+ CMakeConfigItem &item = result[i];
+ item.isAdvanced = advancedSet.contains(item.key);
+
+ if (valuesMap.contains(item.key)) {
+ item.values = CMakeConfigItem::cmakeSplitValue(QString::fromUtf8(valuesMap[item.key]));
+ } else if (item.key == "CMAKE_BUILD_TYPE") {
+ // WA for known options
+ item.values << "" << "Debug" << "Release" << "MinSizeRel" << "RelWithDebInfo";
+ }
+ }
+
+ Utils::sort(result, CMakeConfigItem::sortOperator());
+
+ return result;
+}
+
+void TeaLeafReader::generateProjectTree(CMakeProjectNode *root)
+{
+ root->setDisplayName(m_projectName);
+
+ // Delete no longer necessary file watcher:
+ const QSet<Utils::FileName> currentWatched
+ = Utils::transform(m_watchedFiles, [](CMakeFile *cmf) { return cmf->filePath(); });
+ const QSet<Utils::FileName> toWatch = m_cmakeFiles;
+ QSet<Utils::FileName> toDelete = currentWatched;
+ toDelete.subtract(toWatch);
+ m_watchedFiles = Utils::filtered(m_watchedFiles, [&toDelete](Internal::CMakeFile *cmf) {
+ if (toDelete.contains(cmf->filePath())) {
+ delete cmf;
+ return false;
+ }
+ return true;
+ });
+
+ // Add new file watchers:
+ QSet<Utils::FileName> toAdd = toWatch;
+ toAdd.subtract(currentWatched);
+ foreach (const Utils::FileName &fn, toAdd) {
+ CMakeFile *cm = new CMakeFile(this, fn);
+ Core::DocumentManager::addDocument(cm);
+ m_watchedFiles.insert(cm);
+ }
+
+ QList<FileNode *> fileNodes = m_files;
+ root->buildTree(fileNodes);
+ m_files.clear(); // Some of the FileNodes in files() were deleted!
+}
+
+QSet<Core::Id> TeaLeafReader::updateCodeModel(CppTools::ProjectPartBuilder &ppBuilder)
+{
+ QSet<Core::Id> languages;
+ ToolChain *tc = ToolChainManager::findToolChain(m_parameters.toolChainId);
+ const Utils::FileName sysroot = m_parameters.sysRoot;
+
+ QHash<QString, QStringList> targetDataCache;
+ foreach (const CMakeBuildTarget &cbt, m_buildTargets) {
+ if (cbt.targetType == UtilityType)
+ continue;
+
+ // CMake shuffles the include paths that it reports via the CodeBlocks generator
+ // So remove the toolchain include paths, so that at least those end up in the correct
+ // place.
+ QStringList cxxflags = getCXXFlagsFor(cbt, targetDataCache);
+ QSet<Utils::FileName> tcIncludes;
+ QStringList includePaths;
+ if (tc) {
+ foreach (const HeaderPath &hp, tc->systemHeaderPaths(cxxflags, sysroot))
+ tcIncludes.insert(Utils::FileName::fromString(hp.path()));
+ foreach (const Utils::FileName &i, cbt.includeFiles) {
+ if (!tcIncludes.contains(i))
+ includePaths.append(i.toString());
+ }
+ } else {
+ includePaths = Utils::transform(cbt.includeFiles, &Utils::FileName::toString);
+ }
+ includePaths += m_parameters.buildDirectory.toString();
+ ppBuilder.setIncludePaths(includePaths);
+ ppBuilder.setCFlags(cxxflags);
+ ppBuilder.setCxxFlags(cxxflags);
+ ppBuilder.setDefines(cbt.defines);
+ ppBuilder.setDisplayName(cbt.title);
+
+ const QSet<Core::Id> partLanguages
+ = QSet<Core::Id>::fromList(ppBuilder.createProjectPartsForFiles(
+ Utils::transform(cbt.files, [](const Utils::FileName &fn) { return fn.toString(); })));
+
+ languages.unite(partLanguages);
+ }
+ return languages;
+
+}
+
+void TeaLeafReader::cleanUpProcess()
+{
+ if (m_cmakeProcess) {
+ m_cmakeProcess->disconnect();
+ Core::Reaper::reap(m_cmakeProcess);
+ m_cmakeProcess = nullptr;
+ }
+
+ // Delete issue parser:
+ if (m_parser)
+ m_parser->flush();
+ delete m_parser;
+ m_parser = nullptr;
+}
+
+void TeaLeafReader::extractData()
+{
+ const Utils::FileName srcDir = m_parameters.sourceDirectory;
+ const Utils::FileName bldDir = m_parameters.buildDirectory;
+ const Utils::FileName topCMake
+ = Utils::FileName::fromString(srcDir.toString() + QLatin1String("/CMakeLists.txt"));
+
+ resetData();
+
+ m_projectName = m_parameters.projectName;
+ m_files.append(new FileNode(topCMake, ProjectFileType, false));
+ // Do not insert topCMake into m_cmakeFiles: The project already watches that!
+
+ // Find cbp file
+ Utils::FileName cbpFile = Utils::FileName::fromString(CMakeManager::findCbpFile(bldDir.toString()));
+ if (cbpFile.isEmpty())
+ return;
+ m_cmakeFiles.insert(cbpFile);
+
+ // Add CMakeCache.txt file:
+ Utils::FileName cacheFile = m_parameters.buildDirectory;
+ cacheFile.appendPath(QLatin1String("CMakeCache.txt"));
+ if (cacheFile.toFileInfo().exists())
+ m_cmakeFiles.insert(cacheFile);
+
+ // setFolderName
+ CMakeCbpParser cbpparser;
+ // Parsing
+ if (!cbpparser.parseCbpFile(m_parameters.pathMapper, cbpFile, srcDir))
+ return;
+
+ m_projectName = cbpparser.projectName();
+
+ m_files = cbpparser.fileList();
+ if (cbpparser.hasCMakeFiles()) {
+ m_files.append(cbpparser.cmakeFileList());
+ foreach (const FileNode *node, cbpparser.cmakeFileList())
+ m_cmakeFiles.insert(node->filePath());
+ }
+
+ // Make sure the top cmakelists.txt file is always listed:
+ if (!Utils::contains(m_files, [topCMake](FileNode *fn) { return fn->filePath() == topCMake; })) {
+ m_files.append(new FileNode(topCMake, ProjectFileType, false));
+ }
+
+ m_buildTargets = cbpparser.buildTargets();
+}
+
+void TeaLeafReader::startCMake(const QStringList &configurationArguments)
+{
+ const Utils::FileName buildDirectory = m_parameters.buildDirectory;
+ QTC_ASSERT(!m_cmakeProcess, return);
+ QTC_ASSERT(!m_parser, return);
+ QTC_ASSERT(!m_future, return);
+ QTC_ASSERT(buildDirectory.exists(), return);
+
+ const QString srcDir = m_parameters.sourceDirectory.toString();
+
+ m_parser = new CMakeParser;
+ QDir source = QDir(srcDir);
+ connect(m_parser, &IOutputParser::addTask, m_parser,
+ [source](const Task &task) {
+ if (task.file.isEmpty() || task.file.toFileInfo().isAbsolute()) {
+ TaskHub::addTask(task);
+ } else {
+ Task t = task;
+ t.file = Utils::FileName::fromString(source.absoluteFilePath(task.file.toString()));
+ TaskHub::addTask(t);
+ }
+ });
+
+ // Always use the sourceDir: If we are triggered because the build directory is getting deleted
+ // then we are racing against CMakeCache.txt also getting deleted.
+
+ m_cmakeProcess = new Utils::QtcProcess;
+ m_cmakeProcess->setWorkingDirectory(buildDirectory.toString());
+ m_cmakeProcess->setEnvironment(m_parameters.environment);
+
+ connect(m_cmakeProcess, &QProcess::readyReadStandardOutput,
+ this, &TeaLeafReader::processCMakeOutput);
+ connect(m_cmakeProcess, &QProcess::readyReadStandardError,
+ this, &TeaLeafReader::processCMakeError);
+ connect(m_cmakeProcess, static_cast<void(QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished),
+ this, &TeaLeafReader::cmakeFinished);
+
+ QString args;
+ Utils::QtcProcess::addArg(&args, srcDir);
+ Utils::QtcProcess::addArgs(&args, m_parameters.generatorArguments);
+ Utils::QtcProcess::addArgs(&args, configurationArguments);
+
+ TaskHub::clearTasks(ProjectExplorer::Constants::TASK_CATEGORY_BUILDSYSTEM);
+
+ Core::MessageManager::write(tr("Running \"%1 %2\" in %3.")
+ .arg(m_parameters.cmakeExecutable.toUserOutput())
+ .arg(args)
+ .arg(buildDirectory.toUserOutput()));
+
+ m_future = new QFutureInterface<void>();
+ m_future->setProgressRange(0, 1);
+ Core::ProgressManager::addTask(m_future->future(),
+ tr("Configuring \"%1\"").arg(m_parameters.projectName),
+ "CMake.Configure");
+
+ m_cmakeProcess->setCommand(m_parameters.cmakeExecutable.toString(), args);
+ m_cmakeProcess->start();
+ emit configurationStarted();
+}
+
+void TeaLeafReader::cmakeFinished(int code, QProcess::ExitStatus status)
+{
+ QTC_ASSERT(m_cmakeProcess, return);
+
+ // process rest of the output:
+ processCMakeOutput();
+ processCMakeError();
+
+ m_cmakeProcess->disconnect();
+ cleanUpProcess();
+
+ extractData(); // try even if cmake failed...
+
+ QString msg;
+ if (status != QProcess::NormalExit)
+ msg = tr("*** cmake process crashed!");
+ else if (code != 0)
+ msg = tr("*** cmake process exited with exit code %1.").arg(code);
+
+ if (!msg.isEmpty()) {
+ Core::MessageManager::write(msg);
+ TaskHub::addTask(Task::Error, msg, ProjectExplorer::Constants::TASK_CATEGORY_BUILDSYSTEM);
+ m_future->reportCanceled();
+ } else {
+ m_future->setProgressValue(1);
+ }
+
+ m_future->reportFinished();
+ delete m_future;
+ m_future = nullptr;
+
+ m_hasData = true;
+ emit dataAvailable();
+}
+
+void TeaLeafReader::processCMakeOutput()
+{
+ static QString rest;
+ rest = lineSplit(rest, m_cmakeProcess->readAllStandardOutput(),
+ [this](const QString &s) { Core::MessageManager::write(s); });
+}
+
+void TeaLeafReader::processCMakeError()
+{
+ static QString rest;
+ rest = lineSplit(rest, m_cmakeProcess->readAllStandardError(), [this](const QString &s) {
+ m_parser->stdError(s);
+ Core::MessageManager::write(s);
+ });
+}
+
+QStringList TeaLeafReader::getCXXFlagsFor(const CMakeBuildTarget &buildTarget, QHash<QString, QStringList> &cache)
+{
+ // check cache:
+ auto it = cache.constFind(buildTarget.title);
+ if (it != cache.constEnd())
+ return *it;
+
+ if (extractCXXFlagsFromMake(buildTarget, cache))
+ return cache.value(buildTarget.title);
+
+ if (extractCXXFlagsFromNinja(buildTarget, cache))
+ return cache.value(buildTarget.title);
+
+ cache.insert(buildTarget.title, QStringList());
+ return QStringList();
+}
+
+bool TeaLeafReader::extractCXXFlagsFromMake(const CMakeBuildTarget &buildTarget, QHash<QString, QStringList> &cache)
+{
+ QString makeCommand = buildTarget.makeCommand.toString();
+ int startIndex = makeCommand.indexOf('\"');
+ int endIndex = makeCommand.indexOf('\"', startIndex + 1);
+ if (startIndex != -1 && endIndex != -1) {
+ startIndex += 1;
+ QString makefile = makeCommand.mid(startIndex, endIndex - startIndex);
+ int slashIndex = makefile.lastIndexOf('/');
+ makefile.truncate(slashIndex);
+ makefile.append("/CMakeFiles/" + buildTarget.title + ".dir/flags.make");
+ QFile file(makefile);
+ if (file.exists()) {
+ file.open(QIODevice::ReadOnly | QIODevice::Text);
+ QTextStream stream(&file);
+ while (!stream.atEnd()) {
+ QString line = stream.readLine().trimmed();
+ if (line.startsWith("CXX_FLAGS =")) {
+ // Skip past =
+ cache.insert(buildTarget.title,
+ line.mid(11).trimmed().split(' ', QString::SkipEmptyParts));
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+}
+
+bool TeaLeafReader::extractCXXFlagsFromNinja(const CMakeBuildTarget &buildTarget, QHash<QString, QStringList> &cache)
+{
+ Q_UNUSED(buildTarget)
+ if (!cache.isEmpty()) // We fill the cache in one go!
+ return false;
+
+ // Attempt to find build.ninja file and obtain FLAGS (CXX_FLAGS) from there if no suitable flags.make were
+ // found
+ // Get "all" target's working directory
+ QByteArray ninjaFile;
+ QString buildNinjaFile = buildTargets().at(0).workingDirectory.toString();
+ buildNinjaFile += "/build.ninja";
+ QFile buildNinja(buildNinjaFile);
+ if (buildNinja.exists()) {
+ buildNinja.open(QIODevice::ReadOnly | QIODevice::Text);
+ ninjaFile = buildNinja.readAll();
+ buildNinja.close();
+ }
+
+ if (ninjaFile.isEmpty())
+ return false;
+
+ QTextStream stream(ninjaFile);
+ bool cxxFound = false;
+ const QString targetSignature = "# Object build statements for ";
+ QString currentTarget;
+
+ while (!stream.atEnd()) {
+ // 1. Look for a block that refers to the current target
+ // 2. Look for a build rule which invokes CXX_COMPILER
+ // 3. Return the FLAGS definition
+ QString line = stream.readLine().trimmed();
+ if (line.startsWith('#')) {
+ if (line.startsWith(targetSignature)) {
+ int pos = line.lastIndexOf(' ');
+ currentTarget = line.mid(pos + 1);
+ }
+ } else if (!currentTarget.isEmpty() && line.startsWith("build")) {
+ cxxFound = line.indexOf("CXX_COMPILER") != -1;
+ } else if (cxxFound && line.startsWith("FLAGS =")) {
+ // Skip past =
+ cache.insert(currentTarget, line.mid(7).trimmed().split(' ', QString::SkipEmptyParts));
+ }
+ }
+ return !cache.isEmpty();
+}
+
+} // namespace Internal
+} // namespace CMakeProjectManager
diff --git a/src/plugins/cmakeprojectmanager/tealeafreader.h b/src/plugins/cmakeprojectmanager/tealeafreader.h
new file mode 100644
index 00000000000..e855716e606
--- /dev/null
+++ b/src/plugins/cmakeprojectmanager/tealeafreader.h
@@ -0,0 +1,93 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include "builddirreader.h"
+
+namespace Utils { class QtcProcess; }
+
+namespace CMakeProjectManager {
+namespace Internal {
+
+class CMakeFile;
+
+class TeaLeafReader : public BuildDirReader
+{
+ Q_OBJECT
+
+public:
+ TeaLeafReader();
+ ~TeaLeafReader() final;
+
+ bool isCompatible(const Parameters &p) final;
+ void resetData() final;
+ void parse(bool force) final;
+ void stop() final;
+
+ bool isParsing() const final;
+ bool hasData() const final;
+
+ QList<CMakeBuildTarget> buildTargets() const final;
+ CMakeConfig parsedConfiguration() const final;
+ void generateProjectTree(CMakeProjectNode *root) final;
+ QSet<Core::Id> updateCodeModel(CppTools::ProjectPartBuilder &ppBuilder) final;
+
+private:
+ void cleanUpProcess();
+ void extractData();
+
+ void startCMake(const QStringList &configurationArguments);
+
+ void cmakeFinished(int code, QProcess::ExitStatus status);
+ void processCMakeOutput();
+ void processCMakeError();
+
+ QStringList getCXXFlagsFor(const CMakeBuildTarget &buildTarget, QHash<QString, QStringList> &cache);
+ bool extractCXXFlagsFromMake(const CMakeBuildTarget &buildTarget, QHash<QString, QStringList> &cache);
+ bool extractCXXFlagsFromNinja(const CMakeBuildTarget &buildTarget, QHash<QString, QStringList> &cache);
+
+ CMakeConfig parseConfiguration(const Utils::FileName &cacheFile, QString *errorMessage) const;
+
+ Utils::QtcProcess *m_cmakeProcess = nullptr;
+
+ // For error reporting:
+ ProjectExplorer::IOutputParser *m_parser = nullptr;
+ QFutureInterface<void> *m_future = nullptr;
+
+ bool m_hasData = false;
+
+ mutable CMakeConfig m_cmakeCache;
+ QSet<Utils::FileName> m_cmakeFiles;
+ QString m_projectName;
+ QList<CMakeBuildTarget> m_buildTargets;
+ QList<ProjectExplorer::FileNode *> m_files;
+ QSet<Internal::CMakeFile *> m_watchedFiles;
+
+ friend class CMakeFile;
+};
+
+} // namespace Internal
+} // namespace CMakeProjectManager