// Copyright (C) 2020 Alexis Jeandet. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "mesonbuildsystem.h" #include "kitdata.h" #include "mesonbuildconfiguration.h" #include "mesonpluginconstants.h" #include "mesonprojectmanagertr.h" #include "settings.h" #include "toolkitaspectwidget.h" #include #include #include #include #include #include #include #include #include #include #include #include #define LEAVE_IF_BUSY() \ { \ if (m_parseGuard.guardsProject()) \ return false; \ } #define LOCK() \ { \ m_parseGuard = guardParsingRun(); \ } #define UNLOCK(success) \ { \ if (success) \ m_parseGuard.markAsSuccess(); \ m_parseGuard = {}; \ }; using namespace ProjectExplorer; using namespace Utils; namespace MesonProjectManager::Internal { static Q_LOGGING_CATEGORY(mesonBuildSystemLog, "qtc.meson.buildsystem", QtWarningMsg); const char MACHINE_FILE_PREFIX[] = "Meson-MachineFile-"; const char MACHINE_FILE_EXT[] = ".ini"; static KitData createKitData(const Kit *kit) { QTC_ASSERT(kit, return {}); MacroExpander *expander = kit->macroExpander(); KitData data; data.cCompilerPath = expander->expand(QString("%{Compiler:Executable:C}")); data.cxxCompilerPath = expander->expand(QString("%{Compiler:Executable:Cxx}")); data.cmakePath = expander->expand(QString("%{CMake:Executable:FilePath}")); data.qmakePath = expander->expand(QString("%{Qt:qmakeExecutable}")); data.qtPrefixPath = expander->expand(QString("%{Qt:QT_INSTALL_PREFIX}")); data.qtVersionStr = expander->expand(QString("%{Qt:Version}")); data.pythonPath = expander->expand(QString("%{Python:Path}")); data.qtVersion = Utils::QtMajorVersion::None; auto version = QVersionNumber::fromString(data.qtVersionStr); if (!version.isNull()) { switch (version.majorVersion()) { case 4: data.qtVersion = Utils::QtMajorVersion::Qt4; break; case 5: data.qtVersion = Utils::QtMajorVersion::Qt5; break; case 6: data.qtVersion = Utils::QtMajorVersion::Qt6; break; default: data.qtVersion = Utils::QtMajorVersion::Unknown; } } return data; } static FilePath machineFilesDir() { return Core::ICore::userResourcePath("Meson-machine-files"); } static FilePath machineFile(const Kit *kit) { QTC_ASSERT(kit, return {}); auto baseName = QString("%1%2%3").arg(MACHINE_FILE_PREFIX).arg(kit->id().toString()).arg(MACHINE_FILE_EXT); baseName = baseName.remove('{').remove('}'); return machineFilesDir().pathAppended(baseName); } // MachineFileManager class MachineFileManager final : public QObject { public: MachineFileManager(); private: void addMachineFile(const Kit *kit); void removeMachineFile(const Kit *kit); void updateMachineFile(const Kit *kit); void cleanupMachineFiles(); }; MachineFileManager::MachineFileManager() { connect(KitManager::instance(), &KitManager::kitAdded, this, &MachineFileManager::addMachineFile); connect(KitManager::instance(), &KitManager::kitUpdated, this, &MachineFileManager::updateMachineFile); connect(KitManager::instance(), &KitManager::kitRemoved, this, &MachineFileManager::removeMachineFile); connect(KitManager::instance(), &KitManager::kitsLoaded, this, &MachineFileManager::cleanupMachineFiles); } void MachineFileManager::addMachineFile(const Kit *kit) { FilePath filePath = machineFile(kit); QTC_ASSERT(!filePath.isEmpty(), return ); auto kitData = createKitData(kit); auto entry = [](const QString &key, const QString &value) { return QString("%1 = '%2'\n").arg(key).arg(value).toUtf8(); }; QByteArray ba = "[binaries]\n"; ba += entry("c", kitData.cCompilerPath); ba += entry("cpp", kitData.cxxCompilerPath); ba += entry("qmake", kitData.qmakePath); if (!kitData.pythonPath.isEmpty()){ ba += entry("python3", kitData.pythonPath); ba += entry("python", kitData.pythonPath); } if (kitData.qtVersion == QtMajorVersion::Qt4) ba += entry("qmake-qt4", kitData.qmakePath); else if (kitData.qtVersion == QtMajorVersion::Qt5) ba += entry("qmake-qt5", kitData.qmakePath); else if (kitData.qtVersion == QtMajorVersion::Qt6) ba += entry("qmake-qt6", kitData.qmakePath); ba += entry("cmake", kitData.cmakePath); ba += "\n[cmake]\n"; ba += entry("CMAKE_C_COMPILER", kitData.cCompilerPath); ba += entry("CMAKE_CXX_COMPILER", kitData.cxxCompilerPath); ba += entry("CMAKE_PREFIX_PATH", kitData.qtPrefixPath); filePath.writeFileContents(ba); } void MachineFileManager::removeMachineFile(const Kit *kit) { FilePath filePath = machineFile(kit); if (filePath.exists()) filePath.removeFile(); } void MachineFileManager::updateMachineFile(const Kit *kit) { addMachineFile(kit); } void MachineFileManager::cleanupMachineFiles() { FilePath dir = machineFilesDir(); dir.ensureWritableDir(); const FileFilter filter = {{QString("%1*%2").arg(MACHINE_FILE_PREFIX).arg(MACHINE_FILE_EXT)}}; const FilePaths machineFiles = dir.dirEntries(filter); FilePaths expected; for (Kit const *kit : KitManager::kits()) { const FilePath fname = machineFile(kit); expected.push_back(fname); if (!machineFiles.contains(fname)) addMachineFile(kit); } for (const FilePath &file : machineFiles) { if (!expected.contains(file)) file.removeFile(); } } // MesonBuildSystem MesonBuildSystem::MesonBuildSystem(BuildConfiguration *bc) : BuildSystem(bc) , m_parser(MesonToolKitAspect::mesonToolId(bc->kit()), bc->environment(), project()) , m_cppCodeModelUpdater(ProjectUpdaterFactory::createCppProjectUpdater()) { qCDebug(mesonBuildSystemLog) << "Init"; connect(bc, &BuildConfiguration::kitChanged, this, [this] { updateKit(kit()); }); connect(bc, &MesonBuildConfiguration::buildDirectoryChanged, this, [this] { updateKit(kit()); this->triggerParsing(); }); connect(qobject_cast(bc), &MesonBuildConfiguration::parametersChanged, this, [this] { updateKit(kit()); wipe(); }); connect(bc, &MesonBuildConfiguration::environmentChanged, this, [this] { m_parser.setEnvironment(buildConfiguration()->environment()); }); connect(project(), &ProjectExplorer::Project::projectFileIsDirty, this, [this] { if (buildConfiguration()->isActive()) parseProject(); }); connect(&m_parser, &MesonProjectParser::parsingCompleted, this, &MesonBuildSystem::parsingCompleted); connect(&m_IntroWatcher, &Utils::FileSystemWatcher::fileChanged, this, [this] { if (buildConfiguration()->isActive()) parseProject(); }); updateKit(kit()); // as specified here https://mesonbuild.com/IDE-integration.html#ide-integration // meson-info.json is the last written file, which ensure that all others introspection // files are ready when a modification is detected on this one. m_IntroWatcher.addFile(buildConfiguration() ->buildDirectory() .pathAppended(Constants::MESON_INFO_DIR) .pathAppended(Constants::MESON_INFO), Utils::FileSystemWatcher::WatchModifiedDate); } MesonBuildSystem::~MesonBuildSystem() { qCDebug(mesonBuildSystemLog) << "dtor"; } void MesonBuildSystem::triggerParsing() { qCDebug(mesonBuildSystemLog) << "Trigger parsing"; parseProject(); } bool MesonBuildSystem::needsSetup() { const Utils::FilePath &buildDir = buildConfiguration()->buildDirectory(); return (!isSetup(buildDir) || !m_parser.usesSameMesonVersion(buildDir) || !m_parser.matchesKit(m_kitData)); } void MesonBuildSystem::parsingCompleted(bool success) { if (success) { setRootProjectNode(m_parser.takeProjectNode()); if (kit() && buildConfiguration()) { KitInfo kitInfo{kit()}; m_cppCodeModelUpdater->update( {project(), QtSupport::CppKitInfo(kit()), buildConfiguration()->environment(), m_parser.buildProjectParts(kitInfo.cxxToolchain, kitInfo.cToolchain)}); } setApplicationTargets(m_parser.appsTargets()); UNLOCK(true); emitBuildSystemUpdated(); } else { TaskHub::addTask(BuildSystemTask(Task::Error, Tr::tr("Meson build: Parsing failed"))); UNLOCK(false); emitBuildSystemUpdated(); } emitParsingFinished(success); emit buildConfiguration()->enabledChanged(); // HACK. Should not be needed. } QStringList MesonBuildSystem::configArgs(bool isSetup) { MesonBuildConfiguration *bc = static_cast(buildConfiguration()); const QString ¶ms = bc->parameters(); if (!isSetup || params.contains("--cross-file") || params.contains("--native-file")) return m_pendingConfigArgs + bc->mesonConfigArgs(); return QStringList{QString("--native-file=%1").arg(machineFile(kit()).toUrlishString())} + m_pendingConfigArgs + bc->mesonConfigArgs(); } bool MesonBuildSystem::configure() { LEAVE_IF_BUSY(); qCDebug(mesonBuildSystemLog) << "Configure"; if (needsSetup()) return setup(); LOCK(); if (m_parser.configure(projectDirectory(), buildConfiguration()->buildDirectory(), configArgs(false))) { return true; } UNLOCK(false); return false; } bool MesonBuildSystem::setup() { LEAVE_IF_BUSY(); LOCK(); qCDebug(mesonBuildSystemLog) << "Setup"; if (m_parser.setup(projectDirectory(), buildConfiguration()->buildDirectory(), configArgs(true))) return true; UNLOCK(false); return false; } bool MesonBuildSystem::wipe() { LEAVE_IF_BUSY(); LOCK(); qCDebug(mesonBuildSystemLog) << "Wipe"; if (m_parser.wipe(projectDirectory(), buildConfiguration()->buildDirectory(), configArgs(true))) return true; UNLOCK(false); return false; } bool MesonBuildSystem::parseProject() { QTC_ASSERT(buildConfiguration(), return false); if (!isSetup(buildConfiguration()->buildDirectory()) && settings().autorunMeson()) return configure(); LEAVE_IF_BUSY(); LOCK(); qCDebug(mesonBuildSystemLog) << "Starting parser"; if (m_parser.parse(projectDirectory(), buildConfiguration()->buildDirectory())) return true; UNLOCK(false); return false; } void MesonBuildSystem::updateKit(ProjectExplorer::Kit *kit) { QTC_ASSERT(kit, return ); m_kitData = createKitData(kit); m_parser.setQtVersion(m_kitData.qtVersion); } void setupMesonBuildSystem() { static MachineFileManager theMachineFileManager; } } // MesonProjectManager::Internal