/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying file Copyright.txt or https://cmake.org/licensing for details. */ #include "QCMake.h" #include #include #include #include #include #include #include "cmExternalMakefileProjectGenerator.h" #include "cmGlobalGenerator.h" #include "cmState.h" #include "cmStringAlgorithms.h" #include "cmSystemTools.h" #ifdef Q_OS_WIN # include "qt_windows.h" // For SetErrorMode #endif QCMake::QCMake(QObject* p) : QObject(p) , StartEnvironment(QProcessEnvironment::systemEnvironment()) , Environment(QProcessEnvironment::systemEnvironment()) { this->WarnUninitializedMode = false; qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType>(); qRegisterMetaType(); cmSystemTools::DisableRunCommandOutput(); cmSystemTools::SetRunCommandHideConsole(true); cmSystemTools::SetMessageCallback( [this](std::string const& msg, const char* title) { this->messageCallback(msg, title); }); cmSystemTools::SetStdoutCallback( [this](std::string const& msg) { this->stdoutCallback(msg); }); cmSystemTools::SetStderrCallback( [this](std::string const& msg) { this->stderrCallback(msg); }); this->CMakeInstance = cm::make_unique(cmake::RoleProject, cmState::Project); this->CMakeInstance->SetCMakeEditCommand( cmSystemTools::GetCMakeGUICommand()); this->CMakeInstance->SetProgressCallback( [this](const std::string& msg, float percent) { this->progressCallback(msg, percent); }); cmSystemTools::SetInterruptCallback( [this] { return this->interruptCallback(); }); std::vector generators; this->CMakeInstance->GetRegisteredGenerators( generators, /*includeNamesWithPlatform=*/false); for (cmake::GeneratorInfo const& gen : generators) { this->AvailableGenerators.push_back(gen); } connect(&this->LoadPresetsTimer, &QTimer::timeout, this, [this]() { this->loadPresets(); if (!this->PresetName.isEmpty() && this->CMakePresetsFile.Presets.find( std::string(this->PresetName.toLocal8Bit())) == this->CMakePresetsFile.Presets.end()) { this->setPreset(QString{}); } }); this->LoadPresetsTimer.start(1000); } QCMake::~QCMake() = default; void QCMake::loadCache(const QString& dir) { this->setBinaryDirectory(dir); } void QCMake::setSourceDirectory(const QString& _dir) { QString dir = QString::fromLocal8Bit( cmSystemTools::GetActualCaseForPath(_dir.toLocal8Bit().data()).c_str()); if (this->SourceDirectory != dir) { this->SourceDirectory = QDir::fromNativeSeparators(dir); emit this->sourceDirChanged(this->SourceDirectory); this->loadPresets(); this->setPreset(QString{}); } } void QCMake::setBinaryDirectory(const QString& _dir) { QString dir = QString::fromLocal8Bit( cmSystemTools::GetActualCaseForPath(_dir.toLocal8Bit().data()).c_str()); if (this->BinaryDirectory != dir) { this->BinaryDirectory = QDir::fromNativeSeparators(dir); emit this->binaryDirChanged(this->BinaryDirectory); cmState* state = this->CMakeInstance->GetState(); this->setGenerator(QString()); this->setToolset(QString()); this->setPlatform(QString()); if (!this->CMakeInstance->LoadCache( this->BinaryDirectory.toLocal8Bit().data())) { QDir testDir(this->BinaryDirectory); if (testDir.exists("CMakeCache.txt")) { cmSystemTools::Error( "There is a CMakeCache.txt file for the current binary " "tree but cmake does not have permission to read it. " "Please check the permissions of the directory you are trying to " "run CMake on."); } } QCMakePropertyList props = this->properties(); emit this->propertiesChanged(props); cmProp homeDir = state->GetCacheEntryValue("CMAKE_HOME_DIRECTORY"); if (homeDir) { setSourceDirectory(QString::fromLocal8Bit(homeDir->c_str())); } cmProp gen = state->GetCacheEntryValue("CMAKE_GENERATOR"); if (gen) { const std::string* extraGen = state->GetInitializedCacheValue("CMAKE_EXTRA_GENERATOR"); std::string curGen = cmExternalMakefileProjectGenerator::CreateFullGeneratorName( *gen, extraGen ? *extraGen : ""); this->setGenerator(QString::fromLocal8Bit(curGen.c_str())); } cmProp platform = state->GetCacheEntryValue("CMAKE_GENERATOR_PLATFORM"); if (platform) { this->setPlatform(QString::fromLocal8Bit(platform->c_str())); } cmProp toolset = state->GetCacheEntryValue("CMAKE_GENERATOR_TOOLSET"); if (toolset) { this->setToolset(QString::fromLocal8Bit(toolset->c_str())); } checkOpenPossible(); } } void QCMake::setPreset(const QString& name, bool setBinary) { if (this->PresetName != name) { this->PresetName = name; emit this->presetChanged(this->PresetName); if (!name.isNull()) { std::string presetName(name.toLocal8Bit()); auto const& expandedPreset = this->CMakePresetsFile.Presets[presetName].Expanded; if (expandedPreset) { if (setBinary) { QString binaryDir = QString::fromLocal8Bit(expandedPreset->BinaryDir.data()); this->setBinaryDirectory(binaryDir); } if (expandedPreset->WarnDev) { this->CMakeInstance->SetSuppressDevWarnings( !*expandedPreset->WarnDev); } if (expandedPreset->ErrorDev) { this->CMakeInstance->SetDevWarningsAsErrors( *expandedPreset->ErrorDev); } if (expandedPreset->WarnDeprecated) { this->CMakeInstance->SetSuppressDeprecatedWarnings( !*expandedPreset->WarnDeprecated); } if (expandedPreset->ErrorDeprecated) { this->CMakeInstance->SetDeprecatedWarningsAsErrors( *expandedPreset->ErrorDeprecated); } if (expandedPreset->WarnUninitialized) { this->WarnUninitializedMode = *expandedPreset->WarnUninitialized; emit this->warnUninitializedModeChanged( *expandedPreset->WarnUninitialized); } this->Environment = this->StartEnvironment; for (auto const& v : expandedPreset->Environment) { if (v.second) { this->Environment.insert(QString::fromLocal8Bit(v.first.data()), QString::fromLocal8Bit(v.second->data())); } } } } emit this->propertiesChanged(this->properties()); } } void QCMake::setGenerator(const QString& gen) { if (this->Generator != gen) { this->Generator = gen; emit this->generatorChanged(this->Generator); } } void QCMake::setPlatform(const QString& platform) { if (this->Platform != platform) { this->Platform = platform; emit this->platformChanged(this->Platform); } } void QCMake::setToolset(const QString& toolset) { if (this->Toolset != toolset) { this->Toolset = toolset; emit this->toolsetChanged(this->Toolset); } } void QCMake::setEnvironment(const QProcessEnvironment& environment) { this->Environment = environment; } void QCMake::configure() { int err; { cmSystemTools::SaveRestoreEnvironment restoreEnv; this->setUpEnvironment(); #ifdef Q_OS_WIN UINT lastErrorMode = SetErrorMode(0); #endif this->CMakeInstance->SetHomeDirectory( this->SourceDirectory.toLocal8Bit().data()); this->CMakeInstance->SetHomeOutputDirectory( this->BinaryDirectory.toLocal8Bit().data()); this->CMakeInstance->SetGlobalGenerator( this->CMakeInstance->CreateGlobalGenerator( this->Generator.toLocal8Bit().data())); this->CMakeInstance->SetGeneratorPlatform( this->Platform.toLocal8Bit().data()); this->CMakeInstance->SetGeneratorToolset( this->Toolset.toLocal8Bit().data()); this->CMakeInstance->LoadCache(); this->CMakeInstance->SetWarnUninitialized(this->WarnUninitializedMode); this->CMakeInstance->PreLoadCMakeFiles(); InterruptFlag = 0; cmSystemTools::ResetErrorOccuredFlag(); err = this->CMakeInstance->Configure(); #ifdef Q_OS_WIN SetErrorMode(lastErrorMode); #endif } emit this->propertiesChanged(this->properties()); emit this->configureDone(err); } void QCMake::generate() { int err; { cmSystemTools::SaveRestoreEnvironment restoreEnv; this->setUpEnvironment(); #ifdef Q_OS_WIN UINT lastErrorMode = SetErrorMode(0); #endif InterruptFlag = 0; cmSystemTools::ResetErrorOccuredFlag(); err = this->CMakeInstance->Generate(); #ifdef Q_OS_WIN SetErrorMode(lastErrorMode); #endif } emit this->generateDone(err); checkOpenPossible(); } void QCMake::open() { #ifdef Q_OS_WIN UINT lastErrorMode = SetErrorMode(0); #endif InterruptFlag = 0; cmSystemTools::ResetErrorOccuredFlag(); auto successful = this->CMakeInstance->Open( this->BinaryDirectory.toLocal8Bit().data(), false); #ifdef Q_OS_WIN SetErrorMode(lastErrorMode); #endif emit this->openDone(successful); } void QCMake::setProperties(const QCMakePropertyList& newProps) { QCMakePropertyList props = newProps; QStringList toremove; // set the value of properties cmState* state = this->CMakeInstance->GetState(); std::vector cacheKeys = state->GetCacheEntryKeys(); for (std::string const& key : cacheKeys) { cmStateEnums::CacheEntryType t = state->GetCacheEntryType(key); if (t == cmStateEnums::INTERNAL || t == cmStateEnums::STATIC) { continue; } QCMakeProperty prop; prop.Key = QString::fromLocal8Bit(key.c_str()); int idx = props.indexOf(prop); if (idx == -1) { toremove.append(QString::fromLocal8Bit(key.c_str())); } else { prop = props[idx]; if (prop.Value.type() == QVariant::Bool) { state->SetCacheEntryValue(key, prop.Value.toBool() ? "ON" : "OFF"); } else { state->SetCacheEntryValue(key, prop.Value.toString().toLocal8Bit().data()); } props.removeAt(idx); } } // remove some properties foreach (QString const& s, toremove) { this->CMakeInstance->UnwatchUnusedCli(s.toLocal8Bit().data()); state->RemoveCacheEntry(s.toLocal8Bit().data()); } // add some new properties foreach (QCMakeProperty const& s, props) { this->CMakeInstance->WatchUnusedCli(s.Key.toLocal8Bit().data()); if (s.Type == QCMakeProperty::BOOL) { this->CMakeInstance->AddCacheEntry( s.Key.toLocal8Bit().data(), s.Value.toBool() ? "ON" : "OFF", s.Help.toLocal8Bit().data(), cmStateEnums::BOOL); } else if (s.Type == QCMakeProperty::STRING) { this->CMakeInstance->AddCacheEntry( s.Key.toLocal8Bit().data(), s.Value.toString().toLocal8Bit().data(), s.Help.toLocal8Bit().data(), cmStateEnums::STRING); } else if (s.Type == QCMakeProperty::PATH) { this->CMakeInstance->AddCacheEntry( s.Key.toLocal8Bit().data(), s.Value.toString().toLocal8Bit().data(), s.Help.toLocal8Bit().data(), cmStateEnums::PATH); } else if (s.Type == QCMakeProperty::FILEPATH) { this->CMakeInstance->AddCacheEntry( s.Key.toLocal8Bit().data(), s.Value.toString().toLocal8Bit().data(), s.Help.toLocal8Bit().data(), cmStateEnums::FILEPATH); } } this->CMakeInstance->SaveCache(this->BinaryDirectory.toLocal8Bit().data()); } QCMakePropertyList QCMake::properties() const { QCMakePropertyList ret; cmState* state = this->CMakeInstance->GetState(); std::vector cacheKeys = state->GetCacheEntryKeys(); for (std::string const& key : cacheKeys) { cmStateEnums::CacheEntryType t = state->GetCacheEntryType(key); if (t == cmStateEnums::INTERNAL || t == cmStateEnums::STATIC || t == cmStateEnums::UNINITIALIZED) { continue; } cmProp cachedValue = state->GetCacheEntryValue(key); QCMakeProperty prop; prop.Key = QString::fromLocal8Bit(key.c_str()); if (cmProp hs = state->GetCacheEntryProperty(key, "HELPSTRING")) { prop.Help = QString::fromLocal8Bit(hs->c_str()); } prop.Value = QString::fromLocal8Bit(cachedValue->c_str()); prop.Advanced = state->GetCacheEntryPropertyAsBool(key, "ADVANCED"); if (t == cmStateEnums::BOOL) { prop.Type = QCMakeProperty::BOOL; prop.Value = cmIsOn(*cachedValue); } else if (t == cmStateEnums::PATH) { prop.Type = QCMakeProperty::PATH; } else if (t == cmStateEnums::FILEPATH) { prop.Type = QCMakeProperty::FILEPATH; } else if (t == cmStateEnums::STRING) { prop.Type = QCMakeProperty::STRING; cmProp stringsProperty = state->GetCacheEntryProperty(key, "STRINGS"); if (stringsProperty) { prop.Strings = QString::fromLocal8Bit(stringsProperty->c_str()).split(";"); } } ret.append(prop); } if (!this->PresetName.isNull()) { std::string presetName(this->PresetName.toLocal8Bit()); auto const& p = this->CMakePresetsFile.Presets.at(presetName).Expanded; if (p) { for (auto const& v : p->CacheVariables) { if (!v.second) { continue; } QCMakeProperty prop; prop.Key = QString::fromLocal8Bit(v.first.data()); prop.Value = QString::fromLocal8Bit(v.second->Value.data()); prop.Type = QCMakeProperty::STRING; if (!v.second->Type.empty()) { auto type = cmState::StringToCacheEntryType(v.second->Type); switch (type) { case cmStateEnums::BOOL: prop.Type = QCMakeProperty::BOOL; prop.Value = cmIsOn(v.second->Value); break; case cmStateEnums::PATH: prop.Type = QCMakeProperty::PATH; break; case cmStateEnums::FILEPATH: prop.Type = QCMakeProperty::FILEPATH; break; default: prop.Type = QCMakeProperty::STRING; break; } } // QCMakeCacheModel prefers variables earlier in the list rather than // later, so overwrite them if they already exist rather than simply // appending bool found = false; for (auto& orig : ret) { if (orig.Key == prop.Key) { orig = prop; found = true; break; } } if (!found) { ret.append(prop); } } } } return ret; } void QCMake::interrupt() { this->InterruptFlag.ref(); } bool QCMake::interruptCallback() { #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) return this->InterruptFlag.load(); #else return this->InterruptFlag.loadRelaxed(); #endif } void QCMake::progressCallback(const std::string& msg, float percent) { if (percent >= 0) { emit this->progressChanged(QString::fromStdString(msg), percent); } else { emit this->outputMessage(QString::fromStdString(msg)); } QCoreApplication::processEvents(); } void QCMake::messageCallback(std::string const& msg, const char* /*title*/) { emit this->errorMessage(QString::fromStdString(msg)); QCoreApplication::processEvents(); } void QCMake::stdoutCallback(std::string const& msg) { emit this->outputMessage(QString::fromStdString(msg)); QCoreApplication::processEvents(); } void QCMake::stderrCallback(std::string const& msg) { emit this->outputMessage(QString::fromStdString(msg)); QCoreApplication::processEvents(); } void QCMake::setUpEnvironment() const { auto env = QProcessEnvironment::systemEnvironment(); for (auto const& key : env.keys()) { cmSystemTools::UnsetEnv(key.toLocal8Bit().data()); } for (auto const& var : this->Environment.toStringList()) { cmSystemTools::PutEnv(var.toLocal8Bit().data()); } } void QCMake::loadPresets() { auto result = this->CMakePresetsFile.ReadProjectPresets( this->SourceDirectory.toLocal8Bit().data(), true); if (result != this->LastLoadPresetsResult && result != cmCMakePresetsFile::ReadFileResult::READ_OK) { emit this->presetLoadError(this->SourceDirectory, result); } this->LastLoadPresetsResult = result; QVector presets; for (auto const& name : this->CMakePresetsFile.PresetOrder) { auto const& it = this->CMakePresetsFile.Presets[name]; auto const& p = it.Unexpanded; if (p.Hidden) { continue; } QCMakePreset preset; preset.name = std::move(QString::fromLocal8Bit(p.Name.data())); preset.displayName = std::move(QString::fromLocal8Bit(p.DisplayName.data())); preset.description = std::move(QString::fromLocal8Bit(p.Description.data())); preset.generator = std::move(QString::fromLocal8Bit(p.Generator.data())); preset.architecture = std::move(QString::fromLocal8Bit(p.Architecture.data())); preset.setArchitecture = !p.ArchitectureStrategy || p.ArchitectureStrategy == cmCMakePresetsFile::ArchToolsetStrategy::Set; preset.toolset = std::move(QString::fromLocal8Bit(p.Toolset.data())); preset.setToolset = !p.ToolsetStrategy || p.ToolsetStrategy == cmCMakePresetsFile::ArchToolsetStrategy::Set; preset.enabled = it.Expanded && std::find_if(this->AvailableGenerators.begin(), this->AvailableGenerators.end(), [&p](const cmake::GeneratorInfo& g) { return g.name == p.Generator; }) != this->AvailableGenerators.end(); presets.push_back(preset); } emit this->presetsChanged(presets); } QString QCMake::binaryDirectory() const { return this->BinaryDirectory; } QString QCMake::sourceDirectory() const { return this->SourceDirectory; } QString QCMake::generator() const { return this->Generator; } QProcessEnvironment QCMake::environment() const { return this->Environment; } std::vector const& QCMake::availableGenerators() const { return AvailableGenerators; } void QCMake::deleteCache() { // delete cache this->CMakeInstance->DeleteCache(this->BinaryDirectory.toLocal8Bit().data()); // reload to make our cache empty this->CMakeInstance->LoadCache(this->BinaryDirectory.toLocal8Bit().data()); // emit no generator and no properties this->setGenerator(QString()); this->setToolset(QString()); QCMakePropertyList props = this->properties(); emit this->propertiesChanged(props); } void QCMake::reloadCache() { // emit that the cache was cleaned out QCMakePropertyList props; emit this->propertiesChanged(props); // reload this->CMakeInstance->LoadCache(this->BinaryDirectory.toLocal8Bit().data()); // emit new cache properties props = this->properties(); emit this->propertiesChanged(props); } void QCMake::setDebugOutput(bool flag) { if (flag != this->CMakeInstance->GetDebugOutput()) { this->CMakeInstance->SetDebugOutputOn(flag); emit this->debugOutputChanged(flag); } } bool QCMake::getDebugOutput() const { return this->CMakeInstance->GetDebugOutput(); } bool QCMake::getSuppressDevWarnings() { return this->CMakeInstance->GetSuppressDevWarnings(); } void QCMake::setSuppressDevWarnings(bool value) { this->CMakeInstance->SetSuppressDevWarnings(value); } bool QCMake::getSuppressDeprecatedWarnings() { return this->CMakeInstance->GetSuppressDeprecatedWarnings(); } void QCMake::setSuppressDeprecatedWarnings(bool value) { this->CMakeInstance->SetSuppressDeprecatedWarnings(value); } bool QCMake::getDevWarningsAsErrors() { return this->CMakeInstance->GetDevWarningsAsErrors(); } void QCMake::setDevWarningsAsErrors(bool value) { this->CMakeInstance->SetDevWarningsAsErrors(value); } bool QCMake::getDeprecatedWarningsAsErrors() { return this->CMakeInstance->GetDeprecatedWarningsAsErrors(); } void QCMake::setDeprecatedWarningsAsErrors(bool value) { this->CMakeInstance->SetDeprecatedWarningsAsErrors(value); } void QCMake::setWarnUninitializedMode(bool value) { this->WarnUninitializedMode = value; } void QCMake::checkOpenPossible() { std::string data = this->BinaryDirectory.toLocal8Bit().data(); auto possible = this->CMakeInstance->Open(data, true); emit openPossible(possible); }