Customwizards: Add a way of wrapping a Generator script.

Add attribute to XML syntax specifying a script to generate files.
The script must provide a --dry-run mode in which it prints the files
it intends to create and their attributes to stdout.
Rework the CustomWizardContext structure to contain target path
and parameter mappings, simplify some code there.
This commit is contained in:
Friedemann Kleint
2010-09-01 13:27:24 +02:00
parent b719bbda42
commit c6132a05f3
12 changed files with 603 additions and 55 deletions

View File

@@ -1,6 +1,7 @@
Qt Creator custom wizard are located in this directory. Qt Creator custom wizards are located in this directory.
The subdirectories 'helloworld' and 'listmodel' are provided as examples. The subdirectories 'helloworld', 'listmodel' and 'scriptgeneratedproject'
are provided as examples.
To see how they work in Qt Creator, rename the 'wizard_sample.xml' files To see how they work in Qt Creator, rename the 'wizard_sample.xml' files
to 'wizard.xml'. to 'wizard.xml'.

View File

@@ -0,0 +1,108 @@
#!/usr/bin/perl -w
# *************************************************************************
#
# This file is part of Qt Creator
#
# Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
#
# Contact: Nokia Corporation (qt-info@nokia.com)
#
# Commercial Usage
#
# Licensees holding valid Qt Commercial licenses may use this file in
# accordance with the Qt Commercial License Agreement provided with the
# Software or, alternatively, in accordance with the terms contained in
# a written agreement between you and Nokia.
#
# 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 as published by the Free Software
# Foundation and appearing in the file LICENSE.LGPL included in the
# packaging of this file. Please review the following information to
# ensure the GNU Lesser General Public License version 2.1 requirements
# will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
#
# If you are unsure which license is appropriate for your use, please
# contact the sales department at http://qt.nokia.com/contact.
#
# *************************************************************************
use strict;
use Getopt::Long;
use IO::File;
my $optDryRun = 0;
my $fieldClassName = 'MyClass';
my $standardFieldProjectName = 'MyProject';
my $standardFieldCppHeaderSuffix = 'h';
my $standardFieldCppSourceSuffix = 'cpp';
my $USAGE=<<EOF;
Usage: generate.pl [--dry-run] <parameter-mappings>
Custom wizard project generation example script.
Known parameters: ClassName=<value>
EOF
if (!GetOptions("dry-run" => \$optDryRun)) {
print $USAGE;
exit (1);
}
if (scalar(@ARGV) == 0) {
print $USAGE;
exit (1);
}
# -- Parse the 'field=value' pairs
foreach my $arg (@ARGV) {
my ($key, $value) = split('=', $arg);
$fieldClassName = $value if ($key eq 'ClassName');
# -- Standard fields defined by the custom project wizard
$standardFieldProjectName = $value if ($key eq 'ProjectName');
$standardFieldCppHeaderSuffix = $value if ($key eq 'CppHeaderSuffix');
$standardFieldCppSourceSuffix = $value if ($key eq 'CppSourceSuffix');
}
# -- Determine file names
my $baseFileName = lc($fieldClassName);
my $sourceFileName = $baseFileName . '.' . $standardFieldCppSourceSuffix;
my $headerFileName = $baseFileName . '.' . $standardFieldCppHeaderSuffix;
my $mainSourceFileName = 'main.' . $standardFieldCppSourceSuffix;
my $projectFileName = lc($standardFieldProjectName) . '.pro';
if ($optDryRun) {
# -- Step 1) Dry run: Print file names along with attributes
print $sourceFileName,",openeditor\n";
print $headerFileName,",openeditor\n";
print $mainSourceFileName,",openeditor\n";
print $projectFileName,",openproject\n";
} else {
# -- Step 2) Actual file creation
print 'Generating ', $headerFileName, ' ', $sourceFileName, ' ',
$mainSourceFileName, ' ', $projectFileName, "\n";
my $headerFile = new IO::File('>' . $headerFileName) or die ('Unable to open ' . $headerFileName . ' :' . $!);
print $headerFile '#ifndef ', uc($fieldClassName), "_H\n#define ", uc($fieldClassName), "_H\n\n",
'class ', $fieldClassName, "{\npublic:\n ", $fieldClassName, "();\n\n};\n\n#endif\n";
$headerFile->close();
my $sourceFile = new IO::File('>' . $sourceFileName) or die ('Unable to open ' . $sourceFileName . ' :' . $!);
print $sourceFile '#include "', $headerFileName ,"\"\n\n",
$fieldClassName,'::', $fieldClassName, "()\n{\n}\n";
$sourceFile->close();
my $mainSourceFile = new IO::File('>' . $mainSourceFileName) or die ('Unable to open ' . $mainSourceFileName . ' :' . $!);
print $mainSourceFile '#include "', $headerFileName ,"\"\n\n",
"int main(int argc, char *argv[])\n{\n ", $fieldClassName,' ', lc($fieldClassName),
";\n return 0;\n}\n";
$mainSourceFile->close();
my $projectFile = new IO::File('>' . $projectFileName) or die ('Unable to open ' . $projectFileName . ' :' . $!);
print $projectFile "TEMPLATE = app\nQT -= core\nCONFIG += console\nTARGET = ", $standardFieldProjectName,
"\nSOURCES += ", $sourceFileName, ' ',$headerFileName, ' ', $mainSourceFileName,
"\nHEADERS += ", $headerFileName,"\n";
$projectFile->close();
}

View File

@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** Commercial Usage
**
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
**
** 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 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** If you are unsure which license is appropriate for your use, please
** contact the sales department at http://qt.nokia.com/contact.
**
**************************************************************************/
Custom class wizard example configuration file. -->
<wizard version="1" class="qt4project" firstpage="10" kind="project" id="A.ScriptGeneratedProject" category="B.CustomProjects">
<description>Creates a simple project using a generator script</description>
<displayname>Simple Script-Generated Project</displayname>;
<displaycategory>Custom Projects</displaycategory>
<!-- Specify the generator script -->
<files generatorscript="generate.pl"/>
<!-- Create parameter wizard page -->
<fieldpagetitle>Simple Script-Generated Project Parameters</fieldpagetitle>
<fields>
<field name="ClassName">
<fieldcontrol class="QLineEdit" validator="^[a-zA-Z0-9_]+$" defaulttext="MyClass" />
<fielddescription>Class name:</fielddescription>
</field>
</fields>
</wizard>

View File

@@ -552,12 +552,11 @@ QStringList BaseFileWizard::runWizard(const QString &path, QWidget *parent)
} }
// Write // Write
foreach (const GeneratedFile &generatedFile, files) { if (!writeFiles(files, &errorMessage)) {
if (!generatedFile.write(&errorMessage)) {
QMessageBox::critical(parent, tr("File Generation Failure"), errorMessage); QMessageBox::critical(parent, tr("File Generation Failure"), errorMessage);
return QStringList(); return QStringList();
} }
}
bool removeOpenProjectAttribute = false; bool removeOpenProjectAttribute = false;
// Run the extensions // Run the extensions
foreach (IFileWizardExtension *ex, extensions) { foreach (IFileWizardExtension *ex, extensions) {
@@ -585,6 +584,17 @@ QStringList BaseFileWizard::runWizard(const QString &path, QWidget *parent)
return result; return result;
} }
// Write
bool BaseFileWizard::writeFiles(const GeneratedFiles &files, QString *errorMessage)
{
foreach (const GeneratedFile &generatedFile, files)
if (!(generatedFile.attributes() & GeneratedFile::CustomGeneratorAttribute))
if (!generatedFile.write(errorMessage))
return false;
return true;
}
void BaseFileWizard::setupWizard(QWizard *w) void BaseFileWizard::setupWizard(QWizard *w)
{ {
w->setOption(QWizard::NoCancelButton, false); w->setOption(QWizard::NoCancelButton, false);

View File

@@ -65,7 +65,14 @@ class GeneratedFilePrivate;
class CORE_EXPORT GeneratedFile class CORE_EXPORT GeneratedFile
{ {
public: public:
enum Attribute { OpenEditorAttribute = 0x01, OpenProjectAttribute = 0x02 }; enum Attribute { // Open this file in editor
OpenEditorAttribute = 0x01,
// Open project
OpenProjectAttribute = 0x02,
/* File is generated by external scripts, do not write out,
* see BaseFileWizard::writeFiles() */
CustomGeneratorAttribute = 0x4
};
Q_DECLARE_FLAGS(Attributes, Attribute) Q_DECLARE_FLAGS(Attributes, Attribute)
GeneratedFile(); GeneratedFile();
@@ -200,6 +207,10 @@ protected:
virtual GeneratedFiles generateFiles(const QWizard *w, virtual GeneratedFiles generateFiles(const QWizard *w,
QString *errorMessage) const = 0; QString *errorMessage) const = 0;
/* Physically write files. Re-implement (calling the base implementation)
* to create files with CustomGeneratorAttribute set. */
virtual bool writeFiles(const GeneratedFiles &files, QString *errorMessage);
// Overwrite for ProjectWizard kind and return the path to the generated project file // Overwrite for ProjectWizard kind and return the path to the generated project file
virtual QString generatedProjectFilePath(const QWizard *wizard) const; virtual QString generatedProjectFilePath(const QWizard *wizard) const;

View File

@@ -32,6 +32,7 @@
#include "customwizardpage.h" #include "customwizardpage.h"
#include "projectexplorer.h" #include "projectexplorer.h"
#include "baseprojectwizarddialog.h" #include "baseprojectwizarddialog.h"
#include "customwizardscriptgenerator.h"
#include <coreplugin/icore.h> #include <coreplugin/icore.h>
#include <coreplugin/messagemanager.h> #include <coreplugin/messagemanager.h>
@@ -186,30 +187,80 @@ Core::GeneratedFiles CustomWizard::generateFiles(const QWizard *dialog, QString
// Look for the Custom field page to find the path // Look for the Custom field page to find the path
const Internal::CustomWizardPage *cwp = findWizardPage<Internal::CustomWizardPage>(dialog); const Internal::CustomWizardPage *cwp = findWizardPage<Internal::CustomWizardPage>(dialog);
QTC_ASSERT(cwp, return Core::GeneratedFiles()) QTC_ASSERT(cwp, return Core::GeneratedFiles())
QString path = cwp->path();
const FieldReplacementMap fieldMap = replacementMap(dialog); CustomWizardContextPtr ctx = context();
ctx->targetPath = cwp->path();
ctx->replacements = replacementMap(dialog);
if (CustomWizardPrivate::verbose) { if (CustomWizardPrivate::verbose) {
QString logText; QString logText;
QTextStream str(&logText); QTextStream str(&logText);
str << "CustomWizard::generateFiles: " << path << '\n'; str << "CustomWizard::generateFiles: " << ctx->targetPath << '\n';
const FieldReplacementMap::const_iterator cend = fieldMap.constEnd(); const FieldReplacementMap::const_iterator cend = context()->replacements.constEnd();
for (FieldReplacementMap::const_iterator it = fieldMap.constBegin(); it != cend; ++it) for (FieldReplacementMap::const_iterator it = context()->replacements.constBegin(); it != cend; ++it)
str << " '" << it.key() << "' -> '" << it.value() << "'\n"; str << " '" << it.key() << "' -> '" << it.value() << "'\n";
qWarning("%s", qPrintable(logText)); qWarning("%s", qPrintable(logText));
} }
return generateWizardFiles(path, fieldMap, errorMessage); return generateWizardFiles(errorMessage);
} }
Core::GeneratedFiles CustomWizard::generateWizardFiles(const QString &targetPath,
const FieldReplacementMap &fieldReplacementMap, bool CustomWizard::writeFiles(const Core::GeneratedFiles &files, QString *errorMessage)
QString *errorMessage) const
{ {
if (!Core::BaseFileWizard::writeFiles(files, errorMessage))
return false;
if (d->m_parameters->filesGeneratorScript.isEmpty())
return true;
// Prepare run of the custom script to generate. In the case of a
// project wizard that is entirely created by a script,
// the target project directory might not exist.
const CustomWizardContextPtr ctx = context();
QDir targetPathDir(ctx->targetPath);
if (!targetPathDir.exists()) {
if (CustomWizardPrivate::verbose) if (CustomWizardPrivate::verbose)
qDebug() << "Replacements" << fieldReplacementMap; qDebug("Creating directory %s", qPrintable(ctx->targetPath));
// Create files if (!targetPathDir.mkpath(ctx->targetPath)) {
*errorMessage = QString::fromLatin1("Unable to create the target directory '%1'").arg(ctx->targetPath);
return false;
}
}
// Run the custom script to actually generate the files.
if (!Internal::runCustomWizardGeneratorScript(ctx->targetPath, d->m_parameters->filesGeneratorScriptFullPath(),
ctx->replacements, errorMessage))
return false;
// Paranoia: Check on the files generated by the script:
foreach (const Core::GeneratedFile &generatedFile, files)
if (generatedFile.attributes() & Core::GeneratedFile::CustomGeneratorAttribute)
if (!QFileInfo(generatedFile.path()).isFile()) {
*errorMessage = QString::fromLatin1("%1 failed to generate %2").
arg(d->m_parameters->filesGeneratorScript, generatedFile.path());
return false;
}
return true;
}
Core::GeneratedFiles CustomWizard::generateWizardFiles(QString *errorMessage) const
{
Core::GeneratedFiles rc; Core::GeneratedFiles rc;
const CustomWizardContextPtr ctx = context();
QTC_ASSERT(!ctx->targetPath.isEmpty(), return rc)
if (CustomWizardPrivate::verbose)
qDebug() << "CustomWizard::generateWizardFiles: in "
<< ctx->targetPath << ", using: " << ctx->replacements;
// If generator script is non-empty, do a dry run to get it's files.
if (!d->m_parameters->filesGeneratorScript.isEmpty()) {
rc += Internal::dryRunCustomWizardGeneratorScript(ctx->targetPath,
d->m_parameters->filesGeneratorScriptFullPath(),
ctx->replacements, errorMessage);
if (rc.isEmpty())
return rc;
}
// Add the template files specified by the <file> elements.
foreach(const Internal::CustomWizardFile &file, d->m_parameters->files) foreach(const Internal::CustomWizardFile &file, d->m_parameters->files)
if (!createFile(file, d->m_parameters->directory, targetPath, fieldReplacementMap, &rc, errorMessage)) if (!createFile(file, d->m_parameters->directory, ctx->targetPath, context()->replacements, &rc, errorMessage))
return Core::GeneratedFiles(); return Core::GeneratedFiles();
return rc; return rc;
} }
@@ -437,13 +488,22 @@ Core::GeneratedFiles CustomProjectWizard::generateFiles(const QWizard *w, QStrin
{ {
const BaseProjectWizardDialog *dialog = qobject_cast<const BaseProjectWizardDialog *>(w); const BaseProjectWizardDialog *dialog = qobject_cast<const BaseProjectWizardDialog *>(w);
QTC_ASSERT(dialog, return Core::GeneratedFiles()) QTC_ASSERT(dialog, return Core::GeneratedFiles())
const QString targetPath = dialog->path() + QLatin1Char('/') + dialog->projectName(); // Add project name as macro. Path is here under project directory
// Add project name as macro. CustomWizardContextPtr ctx = context();
ctx->targetPath = dialog->path() + QLatin1Char('/') + dialog->projectName();
FieldReplacementMap fieldReplacementMap = replacementMap(dialog); FieldReplacementMap fieldReplacementMap = replacementMap(dialog);
fieldReplacementMap.insert(QLatin1String("ProjectName"), dialog->projectName()); fieldReplacementMap.insert(QLatin1String("ProjectName"), dialog->projectName());
ctx->replacements = fieldReplacementMap;
if (CustomWizardPrivate::verbose) if (CustomWizardPrivate::verbose)
qDebug() << "CustomProjectWizard::generateFiles" << dialog << targetPath << fieldReplacementMap; qDebug() << "CustomProjectWizard::generateFiles" << dialog << ctx->targetPath << ctx->replacements;
return generateWizardFiles(targetPath, fieldReplacementMap, errorMessage); const Core::GeneratedFiles generatedFiles = generateWizardFiles(errorMessage);
// Find the project file and store in context
foreach(const Core::GeneratedFile &f, generatedFiles)
if (f.attributes() & Core::GeneratedFile::OpenProjectAttribute) {
ctx->projectFilePath = f.path();
break;
}
return generatedFiles;
} }
bool CustomProjectWizard::postGenerateOpen(const Core::GeneratedFiles &l, QString *errorMessage) bool CustomProjectWizard::postGenerateOpen(const Core::GeneratedFiles &l, QString *errorMessage)
@@ -461,23 +521,11 @@ bool CustomProjectWizard::postGenerateOpen(const Core::GeneratedFiles &l, QStrin
return BaseFileWizard::postGenerateOpenEditors(l, errorMessage); return BaseFileWizard::postGenerateOpenEditors(l, errorMessage);
} }
QString CustomProjectWizard::generatedProjectFilePath(const QWizard *wizard) const QString CustomProjectWizard::generatedProjectFilePath(const QWizard *) const
{ {
const BaseProjectWizardDialog *dialog = qobject_cast<const BaseProjectWizardDialog *>(wizard); if (CustomWizardPrivate::verbose)
QTC_ASSERT(dialog, return QString()) qDebug("CustomProjectWizard::generatedProjectFilePath: '%s'", qPrintable(context()->projectFilePath));
const QString targetPath = dialog->path() + QLatin1Char('/') + dialog->projectName(); return context()->projectFilePath;
const QChar slash = QLatin1Char('/');
// take the first from parameters()->files list which have cwFile.openProject set
foreach(const Internal::CustomWizardFile &file, parameters()->files) {
if (file.openProject) {
FieldReplacementMap fieldReplacementMap = replacementMap(dialog);
fieldReplacementMap.insert(QLatin1String("ProjectName"), dialog->projectName());
QString target = file.target;
Internal::CustomWizardContext::replaceFields(fieldReplacementMap, &target);
return QDir::toNativeSeparators(targetPath + slash + target);
}
}
return QString();
} }
bool CustomProjectWizard::postGenerateFiles(const QWizard *, const Core::GeneratedFiles &l, QString *errorMessage) bool CustomProjectWizard::postGenerateFiles(const QWizard *, const Core::GeneratedFiles &l, QString *errorMessage)

View File

@@ -119,11 +119,10 @@ protected:
const WizardPageList &extensionPages) const; const WizardPageList &extensionPages) const;
// generate files in path // generate files in path
Core::GeneratedFiles generateWizardFiles(const QString &path, Core::GeneratedFiles generateWizardFiles(QString *errorMessage) const;
const FieldReplacementMap &defaultFields,
QString *errorMessage) const;
// Create replacement map as static base fields + QWizard fields // Create replacement map as static base fields + QWizard fields
FieldReplacementMap replacementMap(const QWizard *w) const; FieldReplacementMap replacementMap(const QWizard *w) const;
virtual bool writeFiles(const Core::GeneratedFiles &files, QString *errorMessage);
CustomWizardParametersPtr parameters() const; CustomWizardParametersPtr parameters() const;
CustomWizardContextPtr context() const; CustomWizardContextPtr context() const;

View File

@@ -2,8 +2,10 @@ INCLUDEPATH *= $$PWD
HEADERS += $$PWD/customwizard.h \ HEADERS += $$PWD/customwizard.h \
$$PWD/customwizardparameters.h \ $$PWD/customwizardparameters.h \
$$PWD/customwizardpage.h \ $$PWD/customwizardpage.h \
customwizard/customwizardpreprocessor.h customwizard/customwizardpreprocessor.h \
customwizard/customwizardscriptgenerator.h
SOURCES += $$PWD/customwizard.cpp \ SOURCES += $$PWD/customwizard.cpp \
$$PWD/customwizardparameters.cpp \ $$PWD/customwizardparameters.cpp \
$$PWD/customwizardpage.cpp \ $$PWD/customwizardpage.cpp \
customwizard/customwizardpreprocessor.cpp customwizard/customwizardpreprocessor.cpp \
customwizard/customwizardscriptgenerator.cpp

View File

@@ -70,12 +70,10 @@ static const char fieldMandatoryAttributeC[] = "mandatory";
static const char fieldControlElementC[] = "fieldcontrol"; static const char fieldControlElementC[] = "fieldcontrol";
static const char filesElementC[] = "files"; static const char filesElementC[] = "files";
static const char filesGeneratorScriptAttributeC[] = "generatorscript";
static const char fileElementC[] = "file"; static const char fileElementC[] = "file";
static const char fileNameSourceAttributeC[] = "source"; static const char fileSourceAttributeC[] = "source";
static const char fileNameTargetAttributeC[] = "target"; static const char fileTargetAttributeC[] = "target";
static const char fileNameOpenEditorAttributeC[] = "openeditor";
static const char fileNameOpenProjectAttributeC[] = "openproject";
enum ParseState { enum ParseState {
ParseBeginning, ParseBeginning,
@@ -95,6 +93,9 @@ enum ParseState {
namespace ProjectExplorer { namespace ProjectExplorer {
namespace Internal { namespace Internal {
const char customWizardFileOpenEditorAttributeC[] = "openeditor";
const char customWizardFileOpenProjectAttributeC[] = "openproject";
CustomWizardField::CustomWizardField() : CustomWizardField::CustomWizardField() :
mandatory(false) mandatory(false)
{ {
@@ -134,6 +135,7 @@ void CustomWizardParameters::clear()
directory.clear(); directory.clear();
files.clear(); files.clear();
fields.clear(); fields.clear();
filesGeneratorScript.clear();
firstPageId = -1; firstPageId = -1;
} }
@@ -496,12 +498,15 @@ CustomWizardParameters::ParseResult
state = ParseWithinComboEntry; state = ParseWithinComboEntry;
} }
break; break;
case ParseWithinFiles:
filesGeneratorScript = attributeValue(reader, filesGeneratorScriptAttributeC);
break;
case ParseWithinFile: { // file attribute case ParseWithinFile: { // file attribute
CustomWizardFile file; CustomWizardFile file;
file.source = attributeValue(reader, fileNameSourceAttributeC); file.source = attributeValue(reader, fileSourceAttributeC);
file.target = attributeValue(reader, fileNameTargetAttributeC); file.target = attributeValue(reader, fileTargetAttributeC);
file.openEditor = booleanAttributeValue(reader, fileNameOpenEditorAttributeC, false); file.openEditor = booleanAttributeValue(reader, customWizardFileOpenEditorAttributeC, false);
file.openProject = booleanAttributeValue(reader, fileNameOpenProjectAttributeC, false); file.openProject = booleanAttributeValue(reader, customWizardFileOpenProjectAttributeC, false);
if (file.target.isEmpty()) if (file.target.isEmpty())
file.target = file.source; file.target = file.source;
if (file.source.isEmpty()) { if (file.source.isEmpty()) {
@@ -554,11 +559,23 @@ CustomWizardParameters::ParseResult
return parse(configFile, configFileFullPath, bp, errorMessage); return parse(configFile, configFileFullPath, bp, errorMessage);
} }
QString CustomWizardParameters::filesGeneratorScriptFullPath() const
{
if (filesGeneratorScript.isEmpty())
return QString();
QString rc = directory;
rc += QLatin1Char('/');
rc += filesGeneratorScript;
return rc;
}
QString CustomWizardParameters::toString() const QString CustomWizardParameters::toString() const
{ {
QString rc; QString rc;
QTextStream str(&rc); QTextStream str(&rc);
str << "Directory: " << directory << " Klass: '" << klass << "'\n"; str << "Directory: " << directory << " Klass: '" << klass << "'\n";
if (!filesGeneratorScript.isEmpty())
str << "Script: '" << filesGeneratorScript << "'\n";
foreach(const CustomWizardFile &f, files) { foreach(const CustomWizardFile &f, files) {
str << " File source: " << f.source << " Target: " << f.target; str << " File source: " << f.source << " Target: " << f.target;
if (f.openEditor) if (f.openEditor)
@@ -652,6 +669,9 @@ void CustomWizardContext::reset()
mdb->preferredSuffixByType(QLatin1String(CppTools::Constants::CPP_SOURCE_MIMETYPE))); mdb->preferredSuffixByType(QLatin1String(CppTools::Constants::CPP_SOURCE_MIMETYPE)));
baseReplacements.insert(QLatin1String("CppHeaderSuffix"), baseReplacements.insert(QLatin1String("CppHeaderSuffix"),
mdb->preferredSuffixByType(QLatin1String(CppTools::Constants::CPP_HEADER_MIMETYPE))); mdb->preferredSuffixByType(QLatin1String(CppTools::Constants::CPP_HEADER_MIMETYPE)));
replacements.clear();
targetPath.clear();
projectFilePath.clear();
} }
QString CustomWizardContext::processFile(const FieldReplacementMap &fm, QString in) QString CustomWizardContext::processFile(const FieldReplacementMap &fm, QString in)

View File

@@ -81,9 +81,12 @@ public:
Core::BaseFileWizardParameters *bp, QString *errorMessage); Core::BaseFileWizardParameters *bp, QString *errorMessage);
QString toString() const; QString toString() const;
QString filesGeneratorScriptFullPath() const;
QString directory; QString directory;
QString klass; QString klass;
QList<CustomWizardFile> files; QList<CustomWizardFile> files;
QString filesGeneratorScript;
QString fieldPageTitle; QString fieldPageTitle;
QList<CustomWizardField> fields; QList<CustomWizardField> fields;
int firstPageId; int firstPageId;
@@ -110,8 +113,16 @@ struct CustomWizardContext {
static QString processFile(const FieldReplacementMap &fm, QString in); static QString processFile(const FieldReplacementMap &fm, QString in);
FieldReplacementMap baseReplacements; FieldReplacementMap baseReplacements;
FieldReplacementMap replacements;
// Where files should be created, that is, choosen path for simple wizards
// or "path/project" for project wizards.
QString targetPath;
QString projectFilePath;
}; };
extern const char customWizardFileOpenEditorAttributeC[];
extern const char customWizardFileOpenProjectAttributeC[];
} // namespace Internal } // namespace Internal
} // namespace ProjectExplorer } // namespace ProjectExplorer

View File

@@ -0,0 +1,210 @@
/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** Commercial Usage
**
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
**
** 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 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** If you are unsure which license is appropriate for your use, please
** contact the sales department at http://qt.nokia.com/contact.
**
**************************************************************************/
#include "customwizardscriptgenerator.h"
#include "customwizard.h"
#include "customwizardparameters.h" // XML attributes
#include <QtCore/QProcess>
#include <QtCore/QDir>
#include <QtCore/QFileInfo>
#include <QtCore/QDebug>
#include <QtCore/QTemporaryFile>
#include <QtCore/QSharedPointer>
namespace ProjectExplorer {
namespace Internal {
typedef QSharedPointer<QTemporaryFile> TemporaryFilePtr;
// Format pattern for temporary files
static inline QString tempFilePattern()
{
QString tempPattern = QDir::tempPath();
if (!tempPattern.endsWith(QLatin1Char('/')))
tempPattern += QLatin1Char('/');
tempPattern += QLatin1String("qtcreatorXXXXXX.txt");
return tempPattern;
}
// Create a temporary file with content
static inline TemporaryFilePtr writeTemporaryFile(const QString &content)
{
TemporaryFilePtr temporaryFile(new QTemporaryFile(tempFilePattern()));
if (!temporaryFile->open())
return TemporaryFilePtr();
temporaryFile->write(content.toLocal8Bit());
temporaryFile->close();
return temporaryFile;
}
// Helper for running the optional generation script.
static bool
runGenerationScriptHelper(const QString &workingDirectory,
QString binary, bool dryRun,
const QMap<QString, QString> &fieldMap,
QString *stdOut /* = 0 */, QString *errorMessage)
{
typedef QSharedPointer<QTemporaryFile> TemporaryFilePtr;
typedef QList<TemporaryFilePtr> TemporaryFilePtrList;
typedef QMap<QString, QString>::const_iterator FieldConstIterator;
QProcess process;
QStringList arguments;
// Check on the process
const QFileInfo binaryInfo(binary);
if (!binaryInfo.isFile()) {
*errorMessage = QString::fromLatin1("The Generator script %1 does not exist").arg(binary);
return false;
}
#ifdef Q_OS_WIN // Windows: Cannot run scripts by QProcess, do 'cmd /c'
const QString extension = binaryInfo.suffix();
if (!extension.isEmpty() && extension.compare(QLatin1String("exe"), Qt::CaseInsensitive) != 0) {
arguments.push_back(QLatin1String("/C"));
arguments.push_back(binary);
binary = QString::fromLocal8Bit(qgetenv("COMSPEC"));
if (binary.isEmpty())
binary = QLatin1String("cmd.exe");
}
#endif // Q_OS_WIN
// Arguments
if (dryRun)
arguments << QLatin1String("--dry-run");
// Turn the field replacement map into a list of arguments "key=value".
// Pass on free-format-texts as a temporary files indicated by a colon
// separator "key:filename"
TemporaryFilePtrList temporaryFiles;
const FieldConstIterator cend = fieldMap.constEnd();
for (FieldConstIterator it = fieldMap.constBegin(); it != cend; ++it) {
const QString &value = it.value();
// Is a temporary file required?
const bool passAsTemporaryFile = value.contains(QLatin1Char('\n'));
if (passAsTemporaryFile) {
// Create a file and pass on as "key:filename"
TemporaryFilePtr temporaryFile = writeTemporaryFile(value);
if (temporaryFile.isNull()) {
*errorMessage = QString::fromLatin1("Cannot create temporary file");
return false;
}
temporaryFiles.push_back(temporaryFile);
arguments << (it.key() + QLatin1Char(':') + QDir::toNativeSeparators(temporaryFile->fileName()));
} else {
// Normal value "key=value"
arguments << (it.key() + QLatin1Char('=') + value);
}
}
process.setWorkingDirectory(workingDirectory);
if (CustomWizard::verbose())
qDebug("In %s, running:\n%s\n%s\n", qPrintable(workingDirectory),
qPrintable(binary),
qPrintable(arguments.join(QString(QLatin1Char(' ')))));
process.start(binary, arguments);
if (!process.waitForStarted()) {
*errorMessage = QString::fromLatin1("Unable to start generator script %1: %2").
arg(binary, process.errorString());
return false;
}
if (!process.waitForFinished()) {
*errorMessage = QString::fromLatin1("Generator script %1 timed out").arg(binary);
return false;
}
if (process.exitStatus() != QProcess::NormalExit) {
*errorMessage = QString::fromLatin1("Generator script %1 crashed").arg(binary);
return false;
}
if (process.exitCode() != 0) {
const QString stdErr = QString::fromLocal8Bit(process.readAllStandardError());
*errorMessage = QString::fromLatin1("Generator script %1 returned %2 (%3)").
arg(binary).arg(process.exitCode()).arg(stdErr);
return false;
}
if (stdOut) {
*stdOut = QString::fromLocal8Bit(process.readAllStandardOutput());
stdOut->remove(QLatin1Char('\r'));
if (CustomWizard::verbose())
qDebug("Output: '%s'\n", qPrintable(*stdOut));
}
return true;
}
// Do a dry run of the generation script to get a list of files
Core::GeneratedFiles
dryRunCustomWizardGeneratorScript(const QString &targetPath,
const QString &script,
const QMap<QString, QString> &fieldMap,
QString *errorMessage)
{
// Run in temporary directory as the target path may not exist yet.
QString stdOut;
if (!runGenerationScriptHelper(QDir::tempPath(), script, true,
fieldMap, &stdOut, errorMessage))
return Core::GeneratedFiles();
Core::GeneratedFiles files;
// Parse the output consisting of lines with ',' separated tokens.
// (file name + attributes matching those of the <file> element)
foreach (const QString &line, stdOut.split(QLatin1Char('\n'))) {
const QString trimmed = line.trimmed();
if (!trimmed.isEmpty()) {
Core::GeneratedFile file;
Core::GeneratedFile::Attributes attributes = Core::GeneratedFile::CustomGeneratorAttribute;
const QStringList tokens = line.split(QLatin1Char(','));
const int count = tokens.count();
for (int i = 0; i < count; i++) {
const QString &token = tokens.at(i);
if (i) {
if (token == QLatin1String(customWizardFileOpenEditorAttributeC))
attributes |= Core::GeneratedFile::OpenEditorAttribute;
else if (token == QLatin1String(customWizardFileOpenProjectAttributeC))
attributes |= Core::GeneratedFile::OpenProjectAttribute;
} else {
// Token 0 is file name. Wizard wants native names.
const QString fullPath = targetPath + QLatin1Char('/') + token;
file.setPath(QDir::toNativeSeparators(fullPath));
}
}
file.setAttributes(attributes);
files.push_back(file);
}
}
if (CustomWizard::verbose())
foreach(const Core::GeneratedFile &f, files)
qDebug() << script << " generated: " << f.path() << f.attributes();
return files;
}
bool runCustomWizardGeneratorScript(const QString &targetPath, const QString &script,
const QMap<QString, QString> &fieldMap, QString *errorMessage)
{
return runGenerationScriptHelper(targetPath, script, false, fieldMap,
0, errorMessage);
}
} // namespace Internal
} // namespace ProjectExplorer

View File

@@ -0,0 +1,81 @@
/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** Commercial Usage
**
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
**
** 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 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** If you are unsure which license is appropriate for your use, please
** contact the sales department at http://qt.nokia.com/contact.
**
**************************************************************************/
#ifndef CUSTOMWIZARDSCRIPTGENERATOR_H
#define CUSTOMWIZARDSCRIPTGENERATOR_H
#include <QtCore/QMap>
#include <QtCore/QList>
#include <QtCore/QString>
namespace Core {
class GeneratedFile;
};
namespace ProjectExplorer {
namespace Internal {
/* Custom wizard script generator functions. In addition to the <file> elements
* that define template files in which macros are replaced, it is possible to have
* a custom wizard call a generation script (specified in the "generatorscript"
* attribute of the <files> element) which actually creates files.
* The command line of the script must follow the convention
*
* script [--dry-run] [Field1=Value1 [Field2=Value2] [Field3:Filename3]]]...
*
* Multiline texts will be passed on as temporary files using the colon
* separator.
* The parameters are the field values from the UI.
* As Qt Creator needs to know the file names before actually creates them to
* do overwrite checking etc., this is 2-step process:
* 1) Determine file names and attributes: The script is called with the
* --dry-run option and the field values. It then prints the relative path
* names it intends to create followed by comma-separated attributes
* matching those of the <file> element, for example:
* myclass.cpp,openeditor
* 2) The script is called with the parameters only in the working directory
* and then actually creates the files. If that involves directories, the script
* should create those, too.
*/
// Step 1) Do a dry run of the generation script to get a list of files on stdout
QList<Core::GeneratedFile>
dryRunCustomWizardGeneratorScript(const QString &targetPath, const QString &script,
const QMap<QString, QString> &fieldMap,
QString *errorMessage);
// Step 2) Generate files
bool runCustomWizardGeneratorScript(const QString &targetPath, const QString &script,
const QMap<QString, QString> &fieldMap,
QString *errorMessage);
} // namespace Internal
} // namespace ProjectExplorer
#endif // CUSTOMWIZARDSCRIPTGENERATOR_H