/**************************************************************************** ** ** Copyright (C) 2015 The Qt Company Ltd. ** Contact: http://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 http://www.qt.io/terms-conditions. For further information ** use the contact form at http://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 or version 3 as published by the Free ** Software Foundation and appearing in the file LICENSE.LGPLv21 and ** LICENSE.LGPLv3 included in the packaging of this file. Please review the ** following information to ensure the GNU Lesser General Public License ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, The Qt Company gives you certain additional ** rights. These rights are described in The Qt Company LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ****************************************************************************/ #include "genericproject.h" #include "genericbuildconfiguration.h" #include "genericmakestep.h" #include "genericprojectconstants.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Core; using namespace ProjectExplorer; namespace GenericProjectManager { namespace Internal { //////////////////////////////////////////////////////////////////////////////////// // // GenericProject // //////////////////////////////////////////////////////////////////////////////////// GenericProject::GenericProject(Manager *manager, const QString &fileName) : m_manager(manager), m_fileName(fileName) { setId(Constants::GENERICPROJECT_ID); setProjectContext(Context(GenericProjectManager::Constants::PROJECTCONTEXT)); setProjectLanguages(Context(ProjectExplorer::Constants::LANG_CXX)); QFileInfo fileInfo(m_fileName); QDir dir = fileInfo.dir(); m_projectName = fileInfo.completeBaseName(); m_filesFileName = QFileInfo(dir, m_projectName + QLatin1String(".files")).absoluteFilePath(); m_includesFileName = QFileInfo(dir, m_projectName + QLatin1String(".includes")).absoluteFilePath(); m_configFileName = QFileInfo(dir, m_projectName + QLatin1String(".config")).absoluteFilePath(); m_creatorIDocument = new GenericProjectFile(this, m_fileName, GenericProject::Everything); m_filesIDocument = new GenericProjectFile(this, m_filesFileName, GenericProject::Files); m_includesIDocument = new GenericProjectFile(this, m_includesFileName, GenericProject::Configuration); m_configIDocument = new GenericProjectFile(this, m_configFileName, GenericProject::Configuration); DocumentManager::addDocument(m_creatorIDocument); DocumentManager::addDocument(m_filesIDocument); DocumentManager::addDocument(m_includesIDocument); DocumentManager::addDocument(m_configIDocument); m_rootNode = new GenericProjectNode(this, m_creatorIDocument); FileNode *projectFilesNode = new FileNode(m_filesFileName, ProjectFileType, /* generated = */ false); FileNode *projectIncludesNode = new FileNode(m_includesFileName, ProjectFileType, /* generated = */ false); FileNode *projectConfigNode = new FileNode(m_configFileName, ProjectFileType, /* generated = */ false); m_rootNode->addFileNodes(QList() << projectFilesNode << projectIncludesNode << projectConfigNode); m_manager->registerProject(this); } GenericProject::~GenericProject() { m_codeModelFuture.cancel(); m_manager->unregisterProject(this); delete m_rootNode; } QString GenericProject::filesFileName() const { return m_filesFileName; } QString GenericProject::includesFileName() const { return m_includesFileName; } QString GenericProject::configFileName() const { return m_configFileName; } static QStringList readLines(const QString &absoluteFileName) { QStringList lines; QFile file(absoluteFileName); if (file.open(QFile::ReadOnly)) { QTextStream stream(&file); forever { QString line = stream.readLine(); if (line.isNull()) break; lines.append(line); } } return lines; } bool GenericProject::saveRawFileList(const QStringList &rawFileList) { bool result = saveRawList(rawFileList, filesFileName()); refresh(GenericProject::Files); return result; } bool GenericProject::saveRawList(const QStringList &rawList, const QString &fileName) { DocumentManager::expectFileChange(fileName); // Make sure we can open the file for writing Utils::FileSaver saver(fileName, QIODevice::Text); if (!saver.hasError()) { QTextStream stream(saver.file()); foreach (const QString &filePath, rawList) stream << filePath << QLatin1Char('\n'); saver.setResult(&stream); } bool result = saver.finalize(ICore::mainWindow()); DocumentManager::unexpectFileChange(fileName); return result; } bool GenericProject::addFiles(const QStringList &filePaths) { QStringList newList = m_rawFileList; QDir baseDir(QFileInfo(m_fileName).dir()); foreach (const QString &filePath, filePaths) newList.append(baseDir.relativeFilePath(filePath)); QSet includes = projectIncludePaths().toSet(); QSet toAdd; foreach (const QString &filePath, filePaths) { QString directory = QFileInfo(filePath).absolutePath(); if (!includes.contains(directory) && !toAdd.contains(directory)) toAdd << directory; } const QDir dir(projectDirectory().toString()); foreach (const QString &path, toAdd) { QString relative = dir.relativeFilePath(path); if (relative.isEmpty()) relative = QLatin1Char('.'); m_rawProjectIncludePaths.append(relative); } bool result = saveRawList(newList, filesFileName()); result &= saveRawList(m_rawProjectIncludePaths, includesFileName()); refresh(GenericProject::Everything); return result; } bool GenericProject::removeFiles(const QStringList &filePaths) { QStringList newList = m_rawFileList; foreach (const QString &filePath, filePaths) { QHash::iterator i = m_rawListEntries.find(filePath); if (i != m_rawListEntries.end()) newList.removeOne(i.value()); } return saveRawFileList(newList); } bool GenericProject::setFiles(const QStringList &filePaths) { QStringList newList; QDir baseDir(QFileInfo(m_fileName).dir()); foreach (const QString &filePath, filePaths) newList.append(baseDir.relativeFilePath(filePath)); return saveRawFileList(newList); } bool GenericProject::renameFile(const QString &filePath, const QString &newFilePath) { QStringList newList = m_rawFileList; QHash::iterator i = m_rawListEntries.find(filePath); if (i != m_rawListEntries.end()) { int index = newList.indexOf(i.value()); if (index != -1) { QDir baseDir(QFileInfo(m_fileName).dir()); newList.replace(index, baseDir.relativeFilePath(newFilePath)); } } return saveRawFileList(newList); } void GenericProject::parseProject(RefreshOptions options) { if (options & Files) { m_rawListEntries.clear(); m_rawFileList = readLines(filesFileName()); m_files = processEntries(m_rawFileList, &m_rawListEntries); } if (options & Configuration) { m_rawProjectIncludePaths = readLines(includesFileName()); m_projectIncludePaths = processEntries(m_rawProjectIncludePaths); // TODO: Possibly load some configuration from the project file //QSettings projectInfo(m_fileName, QSettings::IniFormat); } if (options & Files) emit fileListChanged(); } void GenericProject::refresh(RefreshOptions options) { QSet oldFileList; if (options & Files) oldFileList = m_files.toSet(); parseProject(options); if (options & Files) m_rootNode->refresh(oldFileList); refreshCppCodeModel(); } /** * Expands environment variables in the given \a string when they are written * like $$(VARIABLE). */ static void expandEnvironmentVariables(const QProcessEnvironment &env, QString &string) { static QRegExp candidate(QLatin1String("\\$\\$\\((.+)\\)")); int index = candidate.indexIn(string); while (index != -1) { const QString value = env.value(candidate.cap(1)); string.replace(index, candidate.matchedLength(), value); index += value.length(); index = candidate.indexIn(string, index); } } /** * Expands environment variables and converts the path from relative to the * project to an absolute path. * * The \a map variable is an optional argument that will map the returned * absolute paths back to their original \a entries. */ QStringList GenericProject::processEntries(const QStringList &paths, QHash *map) const { const QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); const QDir projectDir(QFileInfo(m_fileName).dir()); QFileInfo fileInfo; QStringList absolutePaths; foreach (const QString &path, paths) { QString trimmedPath = path.trimmed(); if (trimmedPath.isEmpty()) continue; expandEnvironmentVariables(env, trimmedPath); trimmedPath = Utils::FileName::fromUserInput(trimmedPath).toString(); fileInfo.setFile(projectDir, trimmedPath); if (fileInfo.exists()) { const QString absPath = fileInfo.absoluteFilePath(); absolutePaths.append(absPath); if (map) map->insert(absPath, trimmedPath); } } absolutePaths.removeDuplicates(); return absolutePaths; } void GenericProject::refreshCppCodeModel() { CppTools::CppModelManager *modelManager = CppTools::CppModelManager::instance(); if (!modelManager) return; m_codeModelFuture.cancel(); CppTools::ProjectInfo pInfo = modelManager->projectInfo(this); pInfo.clearProjectParts(); CppTools::ProjectPartBuilder ppBuilder(pInfo); ppBuilder.setIncludePaths(projectIncludePaths()); ppBuilder.setConfigFileName(configFileName()); ppBuilder.setCxxFlags(QStringList() << QLatin1String("-std=c++11")); const QList languages = ppBuilder.createProjectPartsForFiles(files()); foreach (Core::Id language, languages) setProjectLanguage(language, true); pInfo.finish(); m_codeModelFuture = modelManager->updateProjectInfo(pInfo); } QStringList GenericProject::projectIncludePaths() const { return m_projectIncludePaths; } QStringList GenericProject::files() const { return m_files; } QString GenericProject::displayName() const { return m_projectName; } IDocument *GenericProject::document() const { return m_creatorIDocument; } IProjectManager *GenericProject::projectManager() const { return m_manager; } GenericProjectNode *GenericProject::rootProjectNode() const { return m_rootNode; } QStringList GenericProject::files(FilesMode fileMode) const { Q_UNUSED(fileMode) return m_files; } QStringList GenericProject::buildTargets() const { QStringList targets; targets.append(QLatin1String("all")); targets.append(QLatin1String("clean")); return targets; } bool GenericProject::fromMap(const QVariantMap &map) { if (!Project::fromMap(map)) return false; Kit *defaultKit = KitManager::defaultKit(); if (!activeTarget() && defaultKit) addTarget(createTarget(defaultKit)); // Sanity check: We need both a buildconfiguration and a runconfiguration! QList targetList = targets(); foreach (Target *t, targetList) { if (!t->activeBuildConfiguration()) { removeTarget(t); continue; } if (!t->activeRunConfiguration()) t->addRunConfiguration(new QtSupport::CustomExecutableRunConfiguration(t)); } refresh(Everything); return true; } //////////////////////////////////////////////////////////////////////////////////// // // GenericProjectFile // //////////////////////////////////////////////////////////////////////////////////// GenericProjectFile::GenericProjectFile(GenericProject *parent, QString fileName, GenericProject::RefreshOptions options) : IDocument(parent), m_project(parent), m_options(options) { setId("Generic.ProjectFile"); setMimeType(QLatin1String(Constants::GENERICMIMETYPE)); setFilePath(fileName); } bool GenericProjectFile::save(QString *, const QString &, bool) { return false; } QString GenericProjectFile::defaultPath() const { return QString(); } QString GenericProjectFile::suggestedFileName() const { return QString(); } bool GenericProjectFile::isModified() const { return false; } bool GenericProjectFile::isSaveAsAllowed() const { return false; } IDocument::ReloadBehavior GenericProjectFile::reloadBehavior(ChangeTrigger state, ChangeType type) const { Q_UNUSED(state) Q_UNUSED(type) return BehaviorSilent; } bool GenericProjectFile::reload(QString *errorString, ReloadFlag flag, ChangeType type) { Q_UNUSED(errorString) Q_UNUSED(flag) if (type == TypePermissions) return true; m_project->refresh(m_options); return true; } } // namespace Internal } // namespace GenericProjectManager