Merge branch 'master' of git@scm.dev.nokia.troll.no:creator/mainline

This commit is contained in:
hjk
2009-01-23 17:21:36 +01:00
15 changed files with 510 additions and 62 deletions

View File

@@ -0,0 +1,90 @@
#include "cmakeconfigurewidget.h"
#include "cmakeprojectmanager.h"
#include <projectexplorer/environment.h>
#include <QtGui/QVBoxLayout>
#include <QtGui/QLineEdit>
#include <QtGui/QSpacerItem>
using namespace CMakeProjectManager;
using namespace CMakeProjectManager::Internal;
CMakeConfigureWidget::CMakeConfigureWidget(QWidget *parent, CMakeManager *manager, const QString &sourceDirectory)
: QWidget(parent), m_configureSucceded(false), m_cmakeManager(manager), m_sourceDirectory(sourceDirectory)
{
m_ui.setupUi(this);
m_ui.buildDirectoryLineEdit->setPath(sourceDirectory + "/qtcreator-build");
connect(m_ui.configureButton, SIGNAL(clicked()), this, SLOT(runCMake()));
// TODO make the configure button do stuff
// TODO set initial settings
// TODO note if there's already a build in that directory
// detect which generators we have
// let the user select generator
}
QString CMakeConfigureWidget::buildDirectory()
{
return m_ui.buildDirectoryLineEdit->path();
}
QStringList CMakeConfigureWidget::arguments()
{
return ProjectExplorer::Environment::parseCombinedArgString(m_ui.cmakeArgumentsLineEdit->text());
}
bool CMakeConfigureWidget::configureSucceded()
{
return m_configureSucceded;
}
void CMakeConfigureWidget::runCMake()
{
// TODO run project createCbp()
// get output and display it
// TODO analyse wheter this worked out
m_ui.cmakeOutput->setPlainText(tr("Waiting for cmake..."));
QString string = m_cmakeManager->createXmlFile(arguments(), m_sourceDirectory, buildDirectory());
m_ui.cmakeOutput->setPlainText(string);
}
//////
// CMakeConfigureDialog
/////
CMakeConfigureDialog::CMakeConfigureDialog(QWidget *parent, CMakeManager *manager, const QString &sourceDirectory)
: QDialog(parent)
{
QVBoxLayout *vbox = new QVBoxLayout(this);
setLayout(vbox);
m_cmakeConfigureWidget = new CMakeConfigureWidget(this, manager, sourceDirectory);
vbox->addWidget(m_cmakeConfigureWidget);
QHBoxLayout *hboxlayout = new QHBoxLayout(this);
hboxlayout->addSpacerItem(new QSpacerItem(20, 20, QSizePolicy::Expanding, QSizePolicy::Fixed));
QPushButton *okButton = new QPushButton(this);
okButton->setText(tr("Ok"));
okButton->setDefault(true);
connect(okButton, SIGNAL(clicked()), this, SLOT(accept()));
hboxlayout->addWidget(okButton);
vbox->addLayout(hboxlayout);
}
QString CMakeConfigureDialog::buildDirectory()
{
return m_cmakeConfigureWidget->buildDirectory();
}
QStringList CMakeConfigureDialog::arguments()
{
return m_cmakeConfigureWidget->arguments();
}
bool CMakeConfigureDialog::configureSucceded()
{
return m_cmakeConfigureWidget->configureSucceded();
}

View File

@@ -0,0 +1,48 @@
#ifndef CMAKECONFIGUREWIDGET_H
#define CMAKECONFIGUREWIDGET_H
#include "ui_cmakeconfigurewidget.h"
#include <QtGui/QWidget>
#include <QtGui/QDialog>
namespace CMakeProjectManager {
namespace Internal {
class CMakeManager;
class CMakeConfigureWidget : public QWidget
{
Q_OBJECT
public:
CMakeConfigureWidget(QWidget *parent, CMakeManager *manager, const QString &sourceDirectory);
Ui::CMakeConfigureWidget m_ui;
QString buildDirectory();
QStringList arguments();
bool configureSucceded();
private slots:
void runCMake();
private:
bool m_configureSucceded;
CMakeManager *m_cmakeManager;
QString m_sourceDirectory;
};
class CMakeConfigureDialog : public QDialog
{
public:
CMakeConfigureDialog(QWidget *parent, CMakeManager *manager, const QString &sourceDirectory);
QString buildDirectory();
QStringList arguments();
bool configureSucceded();
private:
CMakeConfigureWidget *m_cmakeConfigureWidget;
};
}
}
#endif // CMAKECONFIGUREWIDGET_H

View File

@@ -32,21 +32,24 @@
***************************************************************************/
#include "cmakeproject.h"
#include "ui_cmakeconfigurewidget.h"
#include "cmakeconfigurewidget.h"
#include "cmakeprojectconstants.h"
#include "cmakeprojectnodes.h"
#include "cmakerunconfiguration.h"
#include "cmakestep.h"
#include "makestep.h"
#include <extensionsystem/pluginmanager.h>
#include <cpptools/cppmodelmanagerinterface.h>
#include <extensionsystem/pluginmanager.h>
#include <utils/qtcassert.h>
#include <coreplugin/icore.h>
#include <QtCore/QDebug>
#include <QtCore/QDir>
#include <QtCore/QProcess>
#include <QtGui/QFormLayout>
#include <QtGui/QMainWindow>
using namespace CMakeProjectManager;
using namespace CMakeProjectManager::Internal;
@@ -67,8 +70,6 @@ CMakeProject::CMakeProject(CMakeManager *manager, const QString &fileName)
: m_manager(manager), m_fileName(fileName), m_rootNode(new CMakeProjectNode(m_fileName))
{
m_file = new CMakeFile(this, fileName);
QDir dir = QFileInfo(m_fileName).absoluteDir();
parseCMakeLists(dir);
}
CMakeProject::~CMakeProject()
@@ -78,12 +79,12 @@ CMakeProject::~CMakeProject()
// TODO also call this method if the CMakeLists.txt file changed, which is also called if the CMakeList.txt is updated
// TODO make this function work even if it is reparsing
void CMakeProject::parseCMakeLists(const QDir &directory)
void CMakeProject::parseCMakeLists()
{
createCbpFile(buildDirectory(QString()));
QString cbpFile = findCbpFile(buildDirectory(QString()));
QString sourceDirectory = QFileInfo(m_fileName).absolutePath();
m_manager->createXmlFile(cmakeStep()->userArguments(activeBuildConfiguration()), sourceDirectory, buildDirectory(activeBuildConfiguration()));
QString cbpFile = findCbpFile(buildDirectory(activeBuildConfiguration()));
CMakeCbpParser cbpparser;
qDebug()<<"Parsing file "<<cbpFile;
if (cbpparser.parseCbpFile(cbpFile)) {
@@ -142,24 +143,6 @@ QString CMakeProject::findCbpFile(const QDir &directory)
return QString::null;
}
void CMakeProject::createCbpFile(const QDir &directory)
{
// We create a cbp file, only if we didn't find a cbp file in the base directory
// Yet that can still override cbp files in subdirectories
// And we are creating tons of files in the source directories
// All of that is not really nice.
// The mid term plan is to move away from the CodeBlocks Generator and use our own
// QtCreator generator, which actually can be very similar to the CodeBlock Generator
// TODO we need to pass on the same paremeters as the cmakestep
qDebug()<<"Creating cbp file";
directory.mkpath(directory.absolutePath());
QProcess cmake;
cmake.setWorkingDirectory(directory.absolutePath());
cmake.start("cmake", QStringList() << ".." << "-GCodeBlocks - Unix Makefiles");
cmake.waitForFinished(-1);
qDebug()<<"cmake output: \n"<<cmake.readAll();
}
void CMakeProject::buildTree(CMakeProjectNode *rootNode, QList<ProjectExplorer::FileNode *> list)
{
@@ -300,8 +283,20 @@ void CMakeProject::restoreSettingsImpl(ProjectExplorer::PersistentSettingsReader
{
// TODO
Project::restoreSettingsImpl(reader);
if (buildConfigurations().isEmpty()) {
// No build configuration, adding those
bool hasUserFile = !buildConfigurations().isEmpty();
if (!hasUserFile) {
// Ask the user for where he wants to build it
// and the cmake command line
// TODO check wheter there's already a CMakeCache.txt in the src directory,
// then we don't need to ask, we simply need to build in the src directory
CMakeConfigureDialog ccd(Core::ICore::instance()->mainWindow(), m_manager, QFileInfo(m_fileName).absolutePath());
ccd.exec();
qDebug()<<"ccd.buildDirectory()"<<ccd.buildDirectory();
// Now create a standard build configuration
CMakeStep *cmakeStep = new CMakeStep(this);
MakeStep *makeStep = new MakeStep(this);
@@ -311,7 +306,14 @@ void CMakeProject::restoreSettingsImpl(ProjectExplorer::PersistentSettingsReader
addBuildConfiguration("all");
setActiveBuildConfiguration("all");
makeStep->setBuildTarget("all", "all", true);
if (!ccd.buildDirectory().isEmpty())
setValue("all", "buildDirectory", ccd.buildDirectory());
cmakeStep->setUserArguments("all", ccd.arguments());
}
parseCMakeLists(); // Gets the directory from the active buildconfiguration
if (!hasUserFile) {
// Create run configurations for m_targets
qDebug()<<"Create run configurations of m_targets";
bool setActive = false;
@@ -328,7 +330,6 @@ void CMakeProject::restoreSettingsImpl(ProjectExplorer::PersistentSettingsReader
}
}
// Restoring is fine
}

View File

@@ -106,9 +106,8 @@ public:
QStringList targets() const;
private:
void parseCMakeLists(const QDir &directory);
void parseCMakeLists();
QString findCbpFile(const QDir &);
void createCbpFile(const QDir &);
void buildTree(CMakeProjectNode *rootNode, QList<ProjectExplorer::FileNode *> list);
ProjectExplorer::FolderNode *findOrCreateFolder(CMakeProjectNode *rootNode, QString directory);

View File

@@ -36,18 +36,30 @@
#include "cmakeproject.h"
#include "cmakeprojectconstants.h"
#include <coreplugin/icore.h>
#include <coreplugin/uniqueidmanager.h>
#include <projectexplorer/projectexplorerconstants.h>
#include <projectexplorer/environment.h>
#include <qtconcurrent/QtConcurrentTools>
#include <QtCore/QtConcurrentRun>
#include <QtCore/QSettings>
#include <QtGui/QFormLayout>
using namespace CMakeProjectManager::Internal;
CMakeManager::CMakeManager()
CMakeManager::CMakeManager(CMakeSettingsPage *cmakeSettingsPage)
: m_settingsPage(cmakeSettingsPage)
{
Core::UniqueIDManager *uidm = Core::UniqueIDManager::instance();
m_projectContext = uidm->uniqueIdentifier(CMakeProjectManager::Constants::PROJECTCONTEXT);
m_projectLanguage = uidm->uniqueIdentifier(ProjectExplorer::Constants::LANG_CXX);
}
CMakeSettingsPage::~CMakeSettingsPage()
{
}
int CMakeManager::projectContext() const
{
return m_projectContext;
@@ -61,6 +73,14 @@ int CMakeManager::projectLanguage() const
ProjectExplorer::Project *CMakeManager::openProject(const QString &fileName)
{
// TODO check wheter this project is already opened
// Check that we have a cmake executable first
// Look at the settings first
QString cmakeExecutable = m_settingsPage->cmakeExecutable();
if (cmakeExecutable.isNull())
m_settingsPage->askUserForCMakeExecutable();
cmakeExecutable = m_settingsPage->cmakeExecutable();
if (cmakeExecutable.isNull())
return 0;
return new CMakeProject(this, fileName);
}
@@ -68,3 +88,204 @@ QString CMakeManager::mimeType() const
{
return Constants::CMAKEMIMETYPE;
}
QString CMakeManager::cmakeExecutable() const
{
return m_settingsPage->cmakeExecutable();
}
// TODO need to refactor this out
// we probably want the process instead of this function
// cmakeproject then could even run the cmake process in the background, adding the files afterwards
// sounds like a plan
QString CMakeManager::createXmlFile(const QStringList &arguments, const QString &sourceDirectory, const QDir &buildDirectory)
{
// We create a cbp file, only if we didn't find a cbp file in the base directory
// Yet that can still override cbp files in subdirectories
// And we are creating tons of files in the source directories
// All of that is not really nice.
// The mid term plan is to move away from the CodeBlocks Generator and use our own
// QtCreator generator, which actually can be very similar to the CodeBlock Generator
// TODO we need to pass on the same paremeters as the cmakestep
QString buildDirectoryPath = buildDirectory.absolutePath();
qDebug()<<"Creating cbp file in"<<buildDirectoryPath;
buildDirectory.mkpath(buildDirectoryPath);
QProcess cmake;
cmake.setWorkingDirectory(buildDirectoryPath);
cmake.start(cmakeExecutable(), QStringList() << sourceDirectory << arguments << "-GCodeBlocks - Unix Makefiles");
qDebug()<<cmakeExecutable()<<sourceDirectory << arguments;
cmake.waitForFinished(-1);
cmake.setProcessChannelMode(QProcess::MergedChannels);
QString output = cmake.readAll();
qDebug()<<"cmake output: \n"<<output;
return output;
}
/////
// CMakeRunner
////
// TODO give a better name, what this class is to update cached information
// about a cmake executable, with qtconcurrent
// The nifty feature of this class is that it does so in a seperate thread,
// not blocking the main thread
CMakeRunner::CMakeRunner()
: m_cacheUpToDate(false)
{
}
void CMakeRunner::run(QFutureInterface<void> &fi)
{
m_mutex.lock();
QString executable = m_executable;
m_mutex.unlock();
QProcess cmake;
cmake.start(executable, QStringList()<<"--help");
cmake.waitForFinished();
QString response = cmake.readAll();
QRegExp versionRegexp("^cmake version ([*\\d\\.]*)-(|patch (\\d*))(|\\r)\\n");
versionRegexp.indexIn(response);
m_mutex.lock();
m_supportsQtCreator = response.contains("QtCreator");
m_version = versionRegexp.cap(1);
if (!versionRegexp.capturedTexts().size()>3)
m_version += "." + versionRegexp.cap(3);
m_cacheUpToDate = true;
m_mutex.unlock();
fi.reportFinished();
}
void CMakeRunner::setExecutable(const QString &executable)
{
waitForUpToDate();
m_mutex.lock();
m_executable = executable;
m_cacheUpToDate = false;
m_mutex.unlock();
m_future = QtConcurrent::run(&CMakeRunner::run, this);
}
QString CMakeRunner::executable() const
{
waitForUpToDate();
m_mutex.lock();
QString result = m_executable;
m_mutex.unlock();
return result;
}
QString CMakeRunner::version() const
{
waitForUpToDate();
m_mutex.lock();
QString result = m_version;
m_mutex.unlock();
return result;
}
bool CMakeRunner::supportsQtCreator() const
{
waitForUpToDate();
m_mutex.lock();
bool result = m_supportsQtCreator;
m_mutex.unlock();
return result;
}
void CMakeRunner::waitForUpToDate() const
{
m_future.waitForFinished();
}
/////
// CMakeSettingsPage
////
CMakeSettingsPage::CMakeSettingsPage()
{
Core::ICore *core = Core::ICore::instance();
QSettings * settings = core->settings();
settings->beginGroup("CMakeSettings");
m_cmakeRunner.setExecutable(settings->value("cmakeExecutable").toString());
settings->endGroup();
}
QString CMakeSettingsPage::findCmakeExecutable() const
{
ProjectExplorer::Environment env = ProjectExplorer::Environment::systemEnvironment();
return env.searchInPath("cmake");
}
QString CMakeSettingsPage::name() const
{
return "CMake";
}
QString CMakeSettingsPage::category() const
{
return "CMake";
}
QString CMakeSettingsPage::trCategory() const
{
return tr("CMake");
}
QWidget *CMakeSettingsPage::createPage(QWidget *parent)
{
QWidget *w = new QWidget(parent);
QFormLayout *fl = new QFormLayout(w);
m_pathchooser = new Core::Utils::PathChooser(w);
m_pathchooser->setExpectedKind(Core::Utils::PathChooser::Command);
fl->addRow("CMake executable", m_pathchooser);
m_pathchooser->setPath(cmakeExecutable());
return w;
}
void CMakeSettingsPage::saveSettings() const
{
QSettings *settings = Core::ICore::instance()->settings();
settings->beginGroup("CMakeSettings");
settings->setValue("cmakeExecutable", m_cmakeRunner.executable());
settings->endGroup();
}
void CMakeSettingsPage::apply()
{
m_cmakeRunner.setExecutable(m_pathchooser->path());
saveSettings();
}
void CMakeSettingsPage::finish()
{
}
QString CMakeSettingsPage::cmakeExecutable() const
{
if (m_cmakeRunner.executable().isEmpty()) {
QString cmakeExecutable = findCmakeExecutable();
if (!cmakeExecutable.isEmpty()) {
m_cmakeRunner.setExecutable(cmakeExecutable);
saveSettings();
}
}
return m_cmakeRunner.executable();
}
void CMakeSettingsPage::askUserForCMakeExecutable()
{
// TODO implement
// That is ideally add a label to the settings page, which says something
// to the effect: please configure the cmake executable
// and show the settings page
// ensure that we rehide the label in the finish() function
// But to test that i need an environment without cmake, e.g. windows
}

View File

@@ -34,27 +34,82 @@
#ifndef CMAKEPROJECTMANAGER_H
#define CMAKEPROJECTMANAGER_H
#include <coreplugin/dialogs/ioptionspage.h>
#include <projectexplorer/iprojectmanager.h>
#include <utils/pathchooser.h>
#include <QtCore/QFuture>
#include <QtCore/QStringList>
#include <QtCore/QDir>
namespace CMakeProjectManager {
namespace Internal {
class CMakeSettingsPage;
class CMakeRunner;
class CMakeManager : public ProjectExplorer::IProjectManager
{
Q_OBJECT
public:
CMakeManager();
CMakeManager(CMakeSettingsPage *cmakeSettingsPage);
virtual int projectContext() const;
virtual int projectLanguage() const;
//virtual bool canOpenProject(const QString &fileName);
virtual ProjectExplorer::Project *openProject(const QString &fileName);
virtual QString mimeType() const;
//virtual QString fileFilter() const;
QString cmakeExecutable() const;
QString createXmlFile(const QStringList &arguments, const QString &sourceDirectory, const QDir &buildDirectory);
private:
int m_projectContext;
int m_projectLanguage;
CMakeSettingsPage *m_settingsPage;
};
class CMakeRunner
{
public:
CMakeRunner();
void run(QFutureInterface<void> &fi);
void setExecutable(const QString &executable);
QString executable() const;
QString version() const;
bool supportsQtCreator() const;
void waitForUpToDate() const;
private:
QString m_executable;
QString m_version;
bool m_supportsQtCreator;
bool m_cacheUpToDate;
mutable QFuture<void> m_future;
mutable QMutex m_mutex;
};
class CMakeSettingsPage : public Core::IOptionsPage
{
Q_OBJECT
public:
CMakeSettingsPage();
virtual ~CMakeSettingsPage();
virtual QString name() const;
virtual QString category() const;
virtual QString trCategory() const;
virtual QWidget *createPage(QWidget *parent);
virtual void apply();
virtual void finish();
QString cmakeExecutable() const;
void askUserForCMakeExecutable();
private:
void updateCachedInformation() const;
void saveSettings() const;
QString findCmakeExecutable() const;
mutable CMakeRunner m_cmakeRunner;
Core::Utils::PathChooser *m_pathchooser;
};
} // namespace Internal

View File

@@ -9,12 +9,15 @@ HEADERS = cmakeproject.h \
cmakeprojectnodes.h \
cmakestep.h \
makestep.h \
cmakerunconfiguration.h
cmakerunconfiguration.h \
cmakeconfigurewidget.h
SOURCES = cmakeproject.cpp \
cmakeprojectplugin.cpp \
cmakeprojectmanager.cpp \
cmakeprojectnodes.cpp \
cmakestep.cpp \
makestep.cpp \
cmakerunconfiguration.cpp
cmakerunconfiguration.cpp \
cmakeconfigurewidget.cpp
RESOURCES += cmakeproject.qrc
FORMS += cmakeconfigurewidget.ui

View File

@@ -59,7 +59,9 @@ bool CMakeProjectPlugin::initialize(const QStringList & /*arguments*/, QString *
Core::ICore *core = Core::ICore::instance();
if (!core->mimeDatabase()->addMimeTypes(QLatin1String(":cmakeproject/CMakeProject.mimetypes.xml"), errorMessage))
return false;
addAutoReleasedObject(new CMakeManager());
CMakeSettingsPage *cmp = new CMakeSettingsPage();
addAutoReleasedObject(cmp);
addAutoReleasedObject(new CMakeManager(cmp));
addAutoReleasedObject(new CMakeBuildStepFactory());
addAutoReleasedObject(new MakeBuildStepFactory());
addAutoReleasedObject(new CMakeRunConfigurationFactory());

View File

@@ -36,6 +36,7 @@
#include "cmakeproject.h"
#include "cmakeprojectconstants.h"
#include <projectexplorer/environment.h>
#include <utils/qtcassert.h>
#include <QtGui/QFormLayout>
#include <QtGui/QLineEdit>
@@ -56,7 +57,10 @@ bool CMakeStep::init(const QString &buildConfiguration)
{
setEnabled(buildConfiguration, true);
setWorkingDirectory(buildConfiguration, m_pro->buildDirectory(buildConfiguration));
setCommand(buildConfiguration, "cmake"); // TODO give full path here?
CMakeManager *cmakeProjectManager = static_cast<CMakeManager *>(m_pro->projectManager());
setCommand(buildConfiguration, cmakeProjectManager->cmakeExecutable());
QString sourceDir = QFileInfo(m_pro->file()->fileName()).absolutePath();
setArguments(buildConfiguration,
@@ -99,14 +103,14 @@ bool CMakeStep::immutable() const
return true;
}
QString CMakeStep::userArguments(const QString &buildConfiguration) const
QStringList CMakeStep::userArguments(const QString &buildConfiguration) const
{
return ProjectExplorer::Environment::joinArgumentList(value(buildConfiguration, "userArguments").toStringList());
return value(buildConfiguration, "userArguments").toStringList();
}
void CMakeStep::setUserArguments(const QString &buildConfiguration, const QString &arguments)
void CMakeStep::setUserArguments(const QString &buildConfiguration, const QStringList &arguments)
{
setValue(buildConfiguration, "userArguments", ProjectExplorer::Environment::parseCombinedArgString(arguments));
setValue(buildConfiguration, "userArguments", arguments);
}
//
@@ -132,13 +136,13 @@ void CMakeBuildStepConfigWidget::init(const QString &buildConfiguration)
{
m_buildConfiguration = buildConfiguration;
disconnect(m_arguments, SIGNAL(textChanged(QString)), this, SLOT(argumentsLineEditChanged()));
m_arguments->setText(m_cmakeStep->userArguments(buildConfiguration));
m_arguments->setText(ProjectExplorer::Environment::joinArgumentList(m_cmakeStep->userArguments(buildConfiguration)));
connect(m_arguments, SIGNAL(textChanged(QString)), this, SLOT(argumentsLineEditChanged()));
}
void CMakeBuildStepConfigWidget::argumentsLineEditChanged()
{
m_cmakeStep->setUserArguments(m_buildConfiguration, m_arguments->text());
m_cmakeStep->setUserArguments(m_buildConfiguration, ProjectExplorer::Environment::parseCombinedArgString(m_arguments->text()));
}
//

View File

@@ -63,8 +63,8 @@ public:
virtual ProjectExplorer::BuildStepConfigWidget *createConfigWidget();
virtual bool immutable() const;
void setUserArguments(const QString &buildConfiguration, const QString &arguments);
QString userArguments(const QString &buildConfiguration) const;
void setUserArguments(const QString &buildConfiguration, const QStringList &arguments);
QStringList userArguments(const QString &buildConfiguration) const;
private:
CMakeProject *m_pro;
};

View File

@@ -183,10 +183,6 @@ void Environment::clear()
m_values.clear();
}
// currently it returns the string that was passed in, except
// under windows and if the executable does not end in .exe
// then it returns executable appended with .exe
// that is clearly wrong
QString Environment::searchInPath(QString executable)
{
// qDebug()<<"looking for "<<executable<< "in PATH: "<<m_values.value("PATH");

View File

@@ -6,7 +6,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>521</width>
<width>551</width>
<height>300</height>
</rect>
</property>
@@ -35,7 +35,7 @@
<item>
<widget class="QComboBox" name="runConfigurationCombo">
<property name="sizeAdjustPolicy">
<enum>QComboBox::AdjustToMinimumContentsLength</enum>
<enum>QComboBox::AdjustToContents</enum>
</property>
<property name="minimumContentsLength">
<number>30</number>