Custom wizards: Enhance generator script

following suggestions. Introduce argument syntax in XML allowing for
fine-grained control of handling (omitting empty values, use
temporary files).
This commit is contained in:
Friedemann Kleint
2010-09-03 14:27:13 +02:00
parent 8d169786f4
commit 14f009da05
7 changed files with 323 additions and 133 deletions

View File

@@ -34,45 +34,59 @@ use Getopt::Long;
use IO::File; use IO::File;
my $optDryRun = 0; my $optDryRun = 0;
my $fieldClassName = 'MyClass'; my $optHelp = 0;
my $standardFieldProjectName = 'MyProject'; my $optClassName = 'MyClass';
my $standardFieldCppHeaderSuffix = 'h'; my $optProjectName = 'MyProject';
my $standardFieldCppSourceSuffix = 'cpp'; my $optCppHeaderSuffix = 'h';
my $optCppSourceSuffix = 'cpp';
my $optDescription = '';
# -- Read in a file and return its lines
sub readFile
{
my ($fileName) = @_;
my @rc = ();
my $fh = new IO::File('<' . $fileName) or die ('Unable to open for reading ' . $fileName . ' :' . $!);
while (my $line = <$fh>) {
chomp($line);
push (@rc, $line);
}
$fh->close();
return @rc;
}
my $USAGE=<<EOF; my $USAGE=<<EOF;
Usage: generate.pl [--dry-run] <parameter-mappings> Usage: generate.pl [--help] | [--dry-run]
[--class-name=<class name>]
[--project-name=<project name>]
[--header-suffix=<header suffix>]
[--source-suffix=<source suffix>]
[--description=<description-file>]
Custom wizard project generation example script. Custom wizard project generation example script.
Known parameters: ClassName=<value>
EOF EOF
if (!GetOptions("dry-run" => \$optDryRun)) { my $argCount = scalar(@ARGV);
if ($argCount == 0
|| !GetOptions("help" => \$optHelp,
"dry-run" => \$optDryRun,
"class-name:s" => \$optClassName,
"project-name:s" => \$optProjectName,
"header-suffix:s" => \$optCppHeaderSuffix,
"source-suffix:s" => \$optCppSourceSuffix,
"description:s" => \$optDescription)
|| $optHelp != 0) {
print $USAGE; print $USAGE;
exit (1); 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 # -- Determine file names
my $baseFileName = lc($fieldClassName); my $baseFileName = lc($optClassName);
my $sourceFileName = $baseFileName . '.' . $standardFieldCppSourceSuffix; my $sourceFileName = $baseFileName . '.' . $optCppSourceSuffix;
my $headerFileName = $baseFileName . '.' . $standardFieldCppHeaderSuffix; my $headerFileName = $baseFileName . '.' . $optCppHeaderSuffix;
my $mainSourceFileName = 'main.' . $standardFieldCppSourceSuffix; my $mainSourceFileName = 'main.' . $optCppSourceSuffix;
my $projectFileName = lc($standardFieldProjectName) . '.pro'; my $projectFileName = lc($optProjectName) . '.pro';
if ($optDryRun) { if ($optDryRun) {
# -- Step 1) Dry run: Print file names along with attributes # -- Step 1) Dry run: Print file names along with attributes
@@ -85,23 +99,29 @@ if ($optDryRun) {
print 'Generating ', $headerFileName, ' ', $sourceFileName, ' ', print 'Generating ', $headerFileName, ' ', $sourceFileName, ' ',
$mainSourceFileName, ' ', $projectFileName, "\n"; $mainSourceFileName, ' ', $projectFileName, "\n";
my $headerFile = new IO::File('>' . $headerFileName) or die ('Unable to open ' . $headerFileName . ' :' . $!); 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", print $headerFile '#ifndef ', uc($optClassName), "_H\n#define ", uc($optClassName), "_H\n\n",
'class ', $fieldClassName, "{\npublic:\n ", $fieldClassName, "();\n\n};\n\n#endif\n"; 'class ', $optClassName, "{\npublic:\n ", $optClassName, "();\n\n};\n\n#endif\n";
$headerFile->close(); $headerFile->close();
my $sourceFile = new IO::File('>' . $sourceFileName) or die ('Unable to open ' . $sourceFileName . ' :' . $!); my $sourceFile = new IO::File('>' . $sourceFileName) or die ('Unable to open ' . $sourceFileName . ' :' . $!);
print $sourceFile '#include "', $headerFileName ,"\"\n\n", print $sourceFile '#include "', $headerFileName ,"\"\n\n",
$fieldClassName,'::', $fieldClassName, "()\n{\n}\n"; $optClassName,'::', $optClassName, "()\n{\n}\n";
$sourceFile->close(); $sourceFile->close();
my $mainSourceFile = new IO::File('>' . $mainSourceFileName) or die ('Unable to open ' . $mainSourceFileName . ' :' . $!); my $mainSourceFile = new IO::File('>' . $mainSourceFileName) or die ('Unable to open ' . $mainSourceFileName . ' :' . $!);
print $mainSourceFile '#include "', $headerFileName ,"\"\n\n", print $mainSourceFile '#include "', $headerFileName ,"\"\n\n";
"int main(int argc, char *argv[])\n{\n ", $fieldClassName,' ', lc($fieldClassName), # -- Write out description comments
if ($optDescription ne '') {
foreach my $description (readFile($optDescription)) {
print $mainSourceFile '// ', $description, "\n";
}
}
print $mainSourceFile "int main(int argc, char *argv[])\n{\n ", $optClassName,' ', lc($optClassName),
";\n return 0;\n}\n"; ";\n return 0;\n}\n";
$mainSourceFile->close(); $mainSourceFile->close();
my $projectFile = new IO::File('>' . $projectFileName) or die ('Unable to open ' . $projectFileName . ' :' . $!); my $projectFile = new IO::File('>' . $projectFileName) or die ('Unable to open ' . $projectFileName . ' :' . $!);
print $projectFile "TEMPLATE = app\nQT -= core\nCONFIG += console\nTARGET = ", $standardFieldProjectName, print $projectFile "TEMPLATE = app\nQT -= core\nCONFIG += console\nTARGET = ", $optProjectName,
"\nSOURCES += ", $sourceFileName, ' ',$headerFileName, ' ', $mainSourceFileName, "\nSOURCES += ", $sourceFileName, ' ',$headerFileName, ' ', $mainSourceFileName,
"\nHEADERS += ", $headerFileName,"\n"; "\nHEADERS += ", $headerFileName,"\n";
$projectFile->close(); $projectFile->close();

View File

@@ -34,8 +34,6 @@ Custom class wizard example configuration file. -->
<description>Creates a simple project using a generator script</description> <description>Creates a simple project using a generator script</description>
<displayname>Simple Script-Generated Project</displayname>; <displayname>Simple Script-Generated Project</displayname>;
<displaycategory>Custom Projects</displaycategory> <displaycategory>Custom Projects</displaycategory>
<!-- Specify the generator script -->
<files generatorscript="generate.pl"/>
<!-- Create parameter wizard page --> <!-- Create parameter wizard page -->
<fieldpagetitle>Simple Script-Generated Project Parameters</fieldpagetitle> <fieldpagetitle>Simple Script-Generated Project Parameters</fieldpagetitle>
<fields> <fields>
@@ -43,5 +41,19 @@ Custom class wizard example configuration file. -->
<fieldcontrol class="QLineEdit" validator="^[a-zA-Z0-9_]+$" defaulttext="MyClass" /> <fieldcontrol class="QLineEdit" validator="^[a-zA-Z0-9_]+$" defaulttext="MyClass" />
<fielddescription>Class name:</fielddescription> <fielddescription>Class name:</fielddescription>
</field> </field>
<!-- Description will be inserted as a multi-line C++-comment -->
<field name="Description">
<fieldcontrol class="QTextEdit" defaulttext="Enter description" />
<fielddescription>Description:</fielddescription>
</field>
</fields> </fields>
<!-- Specify the generator script -->
<generatorscript binary="generate.pl">
<argument value="--class-name=%ClassName%"/>
<argument value="--project-name=%ProjectName%"/>
<argument value="--header-suffix=%CppHeaderSuffix%" omit-empty="true"/>
<argument value="--source-suffix=%CppSourceSuffix%" omit-empty="true"/>
<!-- Multi-line description passed as temporary file unless empty -->
<argument value="--description=%Description%" omit-empty="true" write-file="true"/>
</generatorscript>
</wizard> </wizard>

View File

@@ -225,7 +225,9 @@ bool CustomWizard::writeFiles(const Core::GeneratedFiles &files, QString *errorM
} }
} }
// Run the custom script to actually generate the files. // Run the custom script to actually generate the files.
if (!Internal::runCustomWizardGeneratorScript(ctx->targetPath, d->m_parameters->filesGeneratorScriptFullPath(), if (!Internal::runCustomWizardGeneratorScript(ctx->targetPath,
d->m_parameters->filesGeneratorScript,
d->m_parameters->filesGeneratorScriptArguments,
ctx->replacements, errorMessage)) ctx->replacements, errorMessage))
return false; return false;
// Paranoia: Check on the files generated by the script: // Paranoia: Check on the files generated by the script:
@@ -233,7 +235,7 @@ bool CustomWizard::writeFiles(const Core::GeneratedFiles &files, QString *errorM
if (generatedFile.attributes() & Core::GeneratedFile::CustomGeneratorAttribute) if (generatedFile.attributes() & Core::GeneratedFile::CustomGeneratorAttribute)
if (!QFileInfo(generatedFile.path()).isFile()) { if (!QFileInfo(generatedFile.path()).isFile()) {
*errorMessage = QString::fromLatin1("%1 failed to generate %2"). *errorMessage = QString::fromLatin1("%1 failed to generate %2").
arg(d->m_parameters->filesGeneratorScript, generatedFile.path()); arg(d->m_parameters->filesGeneratorScript.back(), generatedFile.path());
return false; return false;
} }
return true; return true;
@@ -253,7 +255,8 @@ Core::GeneratedFiles CustomWizard::generateWizardFiles(QString *errorMessage) co
// If generator script is non-empty, do a dry run to get it's files. // If generator script is non-empty, do a dry run to get it's files.
if (!d->m_parameters->filesGeneratorScript.isEmpty()) { if (!d->m_parameters->filesGeneratorScript.isEmpty()) {
rc += Internal::dryRunCustomWizardGeneratorScript(ctx->targetPath, rc += Internal::dryRunCustomWizardGeneratorScript(ctx->targetPath,
d->m_parameters->filesGeneratorScriptFullPath(), d->m_parameters->filesGeneratorScript,
d->m_parameters->filesGeneratorScriptArguments,
ctx->replacements, errorMessage); ctx->replacements, errorMessage);
if (rc.isEmpty()) if (rc.isEmpty())
return rc; return rc;

View File

@@ -29,18 +29,24 @@
#include "customwizardparameters.h" #include "customwizardparameters.h"
#include "customwizardpreprocessor.h" #include "customwizardpreprocessor.h"
#include "customwizardscriptgenerator.h"
#include <coreplugin/mimedatabase.h> #include <coreplugin/mimedatabase.h>
#include <coreplugin/icore.h> #include <coreplugin/icore.h>
#include <cpptools/cpptoolsconstants.h> #include <cpptools/cpptoolsconstants.h>
#include <utils/qtcassert.h>
#include <QtCore/QDebug> #include <QtCore/QDebug>
#include <QtCore/QCoreApplication> #include <QtCore/QCoreApplication>
#include <QtCore/QLocale> #include <QtCore/QLocale>
#include <QtCore/QFile> #include <QtCore/QFile>
#include <QtCore/QDir>
#include <QtCore/QFileInfo> #include <QtCore/QFileInfo>
#include <QtCore/QXmlStreamReader> #include <QtCore/QXmlStreamReader>
#include <QtCore/QXmlStreamAttribute> #include <QtCore/QXmlStreamAttribute>
#include <QtCore/QTemporaryFile>
#include <QtGui/QIcon> #include <QtGui/QIcon>
enum { debug = 0 }; enum { debug = 0 };
@@ -64,6 +70,13 @@ static const char comboEntriesElementC[] = "comboentries";
static const char comboEntryElementC[] = "comboentry"; static const char comboEntryElementC[] = "comboentry";
static const char comboEntryTextElementC[] = "comboentrytext"; static const char comboEntryTextElementC[] = "comboentrytext";
static const char generatorScriptElementC[] = "generatorscript";
static const char generatorScriptBinaryAttributeC[] = "binary";
static const char generatorScriptArgumentElementC[] = "argument";
static const char generatorScriptArgumentValueAttributeC[] = "value";
static const char generatorScriptArgumentOmitEmptyAttributeC[] = "omit-empty";
static const char generatorScriptArgumentWriteFileAttributeC[] = "write-file";
static const char fieldDescriptionElementC[] = "fielddescription"; static const char fieldDescriptionElementC[] = "fielddescription";
static const char fieldNameAttributeC[] = "name"; static const char fieldNameAttributeC[] = "name";
static const char fieldMandatoryAttributeC[] = "mandatory"; static const char fieldMandatoryAttributeC[] = "mandatory";
@@ -87,6 +100,8 @@ enum ParseState {
ParseWithinComboEntryText, ParseWithinComboEntryText,
ParseWithinFiles, ParseWithinFiles,
ParseWithinFile, ParseWithinFile,
ParseWithinScript,
ParseWithinScriptArguments,
ParseError ParseError
}; };
@@ -136,6 +151,7 @@ void CustomWizardParameters::clear()
files.clear(); files.clear();
fields.clear(); fields.clear();
filesGeneratorScript.clear(); filesGeneratorScript.clear();
filesGeneratorScriptArguments.clear();
firstPageId = -1; firstPageId = -1;
} }
@@ -276,6 +292,8 @@ static ParseState nextOpeningState(ParseState in, const QStringRef &name)
return ParseWithinFields; return ParseWithinFields;
if (name == QLatin1String(filesElementC)) if (name == QLatin1String(filesElementC))
return ParseWithinFiles; return ParseWithinFiles;
if (name == QLatin1String(generatorScriptElementC))
return ParseWithinScript;
break; break;
case ParseWithinFields: case ParseWithinFields:
if (name == QLatin1String(fieldElementC)) if (name == QLatin1String(fieldElementC))
@@ -303,10 +321,15 @@ static ParseState nextOpeningState(ParseState in, const QStringRef &name)
if (name == QLatin1String(fileElementC)) if (name == QLatin1String(fileElementC))
return ParseWithinFile; return ParseWithinFile;
break; break;
case ParseWithinScript:
if (name == QLatin1String(generatorScriptArgumentElementC))
return ParseWithinScriptArguments;
break;
case ParseWithinFieldDescription: // No subelements case ParseWithinFieldDescription: // No subelements
case ParseWithinComboEntryText: case ParseWithinComboEntryText:
case ParseWithinFile: case ParseWithinFile:
case ParseError: case ParseError:
case ParseWithinScriptArguments:
break; break;
} }
return ParseError; return ParseError;
@@ -358,6 +381,14 @@ static ParseState nextClosingState(ParseState in, const QStringRef &name)
if (name == QLatin1String(comboEntryTextElementC)) if (name == QLatin1String(comboEntryTextElementC))
return ParseWithinComboEntry; return ParseWithinComboEntry;
break; break;
case ParseWithinScript:
if (name == QLatin1String(generatorScriptElementC))
return ParseWithinWizard;
break;
case ParseWithinScriptArguments:
if (name == QLatin1String(generatorScriptArgumentElementC))
return ParseWithinScript;
break;
case ParseError: case ParseError:
break; break;
} }
@@ -427,6 +458,11 @@ static inline QString localeLanguage()
return name; return name;
} }
GeneratorScriptArgument::GeneratorScriptArgument(const QString &v) :
value(v), flags(0)
{
}
// Main parsing routine // Main parsing routine
CustomWizardParameters::ParseResult CustomWizardParameters::ParseResult
CustomWizardParameters::parse(QIODevice &device, CustomWizardParameters::parse(QIODevice &device,
@@ -499,7 +535,6 @@ CustomWizardParameters::ParseResult
} }
break; break;
case ParseWithinFiles: case ParseWithinFiles:
filesGeneratorScript = attributeValue(reader, filesGeneratorScriptAttributeC);
break; break;
case ParseWithinFile: { // file attribute case ParseWithinFile: { // file attribute
CustomWizardFile file; CustomWizardFile file;
@@ -516,6 +551,26 @@ CustomWizardParameters::ParseResult
} }
} }
break; break;
case ParseWithinScript:
filesGeneratorScript = fixGeneratorScript(configFileFullPath, attributeValue(reader, generatorScriptBinaryAttributeC));
if (filesGeneratorScript.isEmpty()) {
*errorMessage = QString::fromLatin1("No binary specified for generator script.");
return ParseFailed;
}
break;
case ParseWithinScriptArguments: {
GeneratorScriptArgument argument(attributeValue(reader, generatorScriptArgumentValueAttributeC));
if (argument.value.isEmpty()) {
*errorMessage = QString::fromLatin1("No value specified for generator script argument.");
return ParseFailed;
}
if (booleanAttributeValue(reader, generatorScriptArgumentOmitEmptyAttributeC, false))
argument.flags |= GeneratorScriptArgument::OmitEmpty;
if (booleanAttributeValue(reader, generatorScriptArgumentWriteFileAttributeC, false))
argument.flags |= GeneratorScriptArgument::WriteFile;
filesGeneratorScriptArguments.push_back(argument);
}
break;
default: default:
break; break;
} }
@@ -559,23 +614,26 @@ 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()) if (!filesGeneratorScriptArguments.isEmpty()) {
str << "Script: '" << filesGeneratorScript << "'\n"; str << "Script:";
foreach(const QString &a, filesGeneratorScript)
str << " '" << a << '\'';
str << "\nArguments: ";
foreach(const GeneratorScriptArgument &a, filesGeneratorScriptArguments) {
str << " '" << a.value << '\'';
if (a.flags & GeneratorScriptArgument::OmitEmpty)
str << " [omit empty]";
if (a.flags & GeneratorScriptArgument::WriteFile)
str << " [write file]";
str << ',';
}
str << '\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)
@@ -603,8 +661,16 @@ QString CustomWizardParameters::toString() const
// ------------ CustomWizardContext // ------------ CustomWizardContext
void CustomWizardContext::replaceFields(const FieldReplacementMap &fm, QString *s) static inline QString passThrough(const QString &in) { return in; }
// Do field replacements applying modifiers and string transformation
// for the value
template <class ValueStringTransformation>
bool replaceFieldHelper(ValueStringTransformation transform,
const CustomWizardContext::FieldReplacementMap &fm,
QString *s)
{ {
bool nonEmptyReplacements = false;
if (debug) { if (debug) {
qDebug().nospace() << "CustomWizardContext::replaceFields with " << qDebug().nospace() << "CustomWizardContext::replaceFields with " <<
fm << *s; fm << *s;
@@ -633,7 +699,7 @@ void CustomWizardContext::replaceFields(const FieldReplacementMap &fm, QString *
modifier = fieldSpec.at(fieldSpecSize - 1).toLatin1(); modifier = fieldSpec.at(fieldSpecSize - 1).toLatin1();
fieldSpec.truncate(fieldSpecSize - 2); fieldSpec.truncate(fieldSpecSize - 2);
} }
const FieldReplacementMap::const_iterator it = fm.constFind(fieldSpec); const CustomWizardContext::FieldReplacementMap::const_iterator it = fm.constFind(fieldSpec);
if (it == fm.constEnd()) { if (it == fm.constEnd()) {
pos = nextPos; // Not found, skip pos = nextPos; // Not found, skip
continue; continue;
@@ -655,9 +721,64 @@ void CustomWizardContext::replaceFields(const FieldReplacementMap &fm, QString *
default: default:
break; break;
} }
s->replace(pos, nextPos - pos, replacement); if (!replacement.isEmpty())
nonEmptyReplacements = true;
// Apply transformation to empty values as well.
s->replace(pos, nextPos - pos, transform(replacement));
nonEmptyReplacements = true;
pos += replacement.size(); pos += replacement.size();
} }
return nonEmptyReplacements;
}
bool CustomWizardContext::replaceFields(const FieldReplacementMap &fm, QString *s)
{
return replaceFieldHelper(passThrough, fm, s);
}
// Transformation to be passed to replaceFieldHelper(). Writes the
// value to a text file and returns the file name to be inserted
// instead of the expanded field in the parsed template,
// used for the arguments of a generator script.
class TemporaryFileTransform {
public:
typedef CustomWizardContext::TemporaryFilePtr TemporaryFilePtr;
typedef CustomWizardContext::TemporaryFilePtrList TemporaryFilePtrList;
explicit TemporaryFileTransform(TemporaryFilePtrList *f);
QString operator()(const QString &) const;
private:
TemporaryFilePtrList *m_files;
QString m_pattern;
};
TemporaryFileTransform::TemporaryFileTransform(TemporaryFilePtrList *f) :
m_files(f), m_pattern(QDir::tempPath())
{
if (!m_pattern.endsWith(QLatin1Char('/')))
m_pattern += QLatin1Char('/');
m_pattern += QLatin1String("qtcreatorXXXXXX.txt");
}
QString TemporaryFileTransform::operator()(const QString &value) const
{
TemporaryFilePtr temporaryFile(new QTemporaryFile(m_pattern));
QTC_ASSERT(temporaryFile->open(), return QString(); )
temporaryFile->write(value.toLocal8Bit());
const QString name = temporaryFile->fileName();
temporaryFile->flush();
temporaryFile->close();
m_files->push_back(temporaryFile);
return name;
}
bool CustomWizardContext::replaceFields(const FieldReplacementMap &fm, QString *s,
TemporaryFilePtrList *files)
{
return replaceFieldHelper(TemporaryFileTransform(files), fm, s);
} }
void CustomWizardContext::reset() void CustomWizardContext::reset()

View File

@@ -32,12 +32,14 @@
#include <coreplugin/basefilewizard.h> #include <coreplugin/basefilewizard.h>
#include <QtCore/QList> #include <QtCore/QStringList>
#include <QtCore/QMap> #include <QtCore/QMap>
#include <QtCore/QSharedPointer>
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
class QIODevice; class QIODevice;
class QDebug; class QDebug;
class QTemporaryFile;
QT_END_NAMESPACE QT_END_NAMESPACE
namespace ProjectExplorer { namespace ProjectExplorer {
@@ -68,6 +70,23 @@ struct CustomWizardFile {
bool openProject; bool openProject;
}; };
// Argument to the generator script containing placeholders to
// be replaced by field values or file names
// as in '--class-name=%ClassName%' or '--description=%Description%'.
struct GeneratorScriptArgument {
enum Flags {
// Omit this arguments if all field placeholders expanded to empty strings.
OmitEmpty = 0x1,
// Do use the actual field value, but write it to a temporary
// text file and inserts its file name (suitable for multiline texts).
WriteFile = 0x2 };
explicit GeneratorScriptArgument(const QString &value = QString());
QString value;
unsigned flags;
};
struct CustomWizardParameters struct CustomWizardParameters
{ {
public: public:
@@ -81,12 +100,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; QStringList filesGeneratorScript; // Complete binary, such as 'cmd /c myscript.pl'.
QList<GeneratorScriptArgument> filesGeneratorScriptArguments;
QString fieldPageTitle; QString fieldPageTitle;
QList<CustomWizardField> fields; QList<CustomWizardField> fields;
int firstPageId; int firstPageId;
@@ -102,14 +121,24 @@ public:
struct CustomWizardContext { struct CustomWizardContext {
typedef QMap<QString, QString> FieldReplacementMap; typedef QMap<QString, QString> FieldReplacementMap;
typedef QSharedPointer<QTemporaryFile> TemporaryFilePtr;
typedef QList<TemporaryFilePtr> TemporaryFilePtrList;
void reset(); void reset();
// Replace field values delimited by '%' with special modifiers: // Replace field values delimited by '%' with special modifiers:
// %Field% -> simple replacement // %Field% -> simple replacement
// %Field:l% -> lower case replacement, 'u' upper case, // %Field:l% -> lower case replacement, 'u' upper case,
// 'c' capitalize first letter. // 'c' capitalize first letter. Return value indicates whether non-empty
static void replaceFields(const FieldReplacementMap &fm, QString *s); // replacements where encountered
static bool replaceFields(const FieldReplacementMap &fm, QString *s);
// Special replaceFields() overload used for the arguments of a generator
// script: Write the expanded field values out to temporary files and
// inserts file names instead of the expanded fields in string 's'.
static bool replaceFields(const FieldReplacementMap &fm, QString *s,
TemporaryFilePtrList *files);
static QString processFile(const FieldReplacementMap &fm, QString in); static QString processFile(const FieldReplacementMap &fm, QString in);
FieldReplacementMap baseReplacements; FieldReplacementMap baseReplacements;

View File

@@ -29,7 +29,9 @@
#include "customwizardscriptgenerator.h" #include "customwizardscriptgenerator.h"
#include "customwizard.h" #include "customwizard.h"
#include "customwizardparameters.h" // XML attributes #include "customwizardparameters.h"
#include <utils/qtcassert.h>
#include <QtCore/QProcess> #include <QtCore/QProcess>
#include <QtCore/QDir> #include <QtCore/QDir>
@@ -41,84 +43,74 @@
namespace ProjectExplorer { namespace ProjectExplorer {
namespace Internal { namespace Internal {
typedef QSharedPointer<QTemporaryFile> TemporaryFilePtr; // Parse helper: Determine the correct binary to run:
// Expand to full wizard path if it is relative and located
// Format pattern for temporary files // in the wizard directory, else assume it can be found in path.
static inline QString tempFilePattern() // On Windows, run non-exe files with 'cmd /c'.
QStringList fixGeneratorScript(const QString &configFile, QString binary)
{ {
QString tempPattern = QDir::tempPath(); if (binary.isEmpty())
if (!tempPattern.endsWith(QLatin1Char('/'))) return QStringList();
tempPattern += QLatin1Char('/'); // Expand to full path if it is relative and in the wizard
tempPattern += QLatin1String("qtcreatorXXXXXX.txt"); // directory, else assume it can be found in path.
return tempPattern; QFileInfo binaryInfo(binary);
} if (!binaryInfo.isAbsolute()) {
QString fullPath = QFileInfo(configFile).absolutePath();
// Create a temporary file with content fullPath += QLatin1Char('/');
static inline TemporaryFilePtr writeTemporaryFile(const QString &content) fullPath += binary;
{ const QFileInfo fullPathInfo(fullPath);
TemporaryFilePtr temporaryFile(new QTemporaryFile(tempFilePattern())); if (fullPathInfo.isFile()) {
if (!temporaryFile->open()) binary = fullPathInfo.absoluteFilePath();
return TemporaryFilePtr(); binaryInfo = fullPathInfo;
temporaryFile->write(content.toLocal8Bit()); }
temporaryFile->close(); } // not absolute
return temporaryFile; QStringList rc(binary);
#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) {
rc.push_front(QLatin1String("/C"));
rc.push_front(QString::fromLocal8Bit(qgetenv("COMSPEC")));
if (rc.front().isEmpty())
rc.front() = QLatin1String("cmd.exe");
}
#endif
return rc;
} }
// Helper for running the optional generation script. // Helper for running the optional generation script.
static bool static bool
runGenerationScriptHelper(const QString &workingDirectory, runGenerationScriptHelper(const QString &workingDirectory,
QString binary, bool dryRun, const QStringList &script,
const QList<GeneratorScriptArgument> &argumentsIn,
bool dryRun,
const QMap<QString, QString> &fieldMap, const QMap<QString, QString> &fieldMap,
QString *stdOut /* = 0 */, QString *errorMessage) QString *stdOut /* = 0 */, QString *errorMessage)
{ {
typedef QSharedPointer<QTemporaryFile> TemporaryFilePtr; typedef QSharedPointer<QTemporaryFile> TemporaryFilePtr;
typedef QList<TemporaryFilePtr> TemporaryFilePtrList; typedef QList<TemporaryFilePtr> TemporaryFilePtrList;
typedef QMap<QString, QString>::const_iterator FieldConstIterator;
QProcess process; QProcess process;
const QString binary = script.front();
QStringList arguments; QStringList arguments;
// Check on the process const int binarySize = script.size();
const QFileInfo binaryInfo(binary); for (int i = 1; i < binarySize; i++)
if (!binaryInfo.isFile()) { arguments.push_back(script.at(i));
*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(); // Arguments: Prepend 'dryrun' and do field replacement
for (FieldConstIterator it = fieldMap.constBegin(); it != cend; ++it) { if (dryRun)
const QString &value = it.value(); arguments.push_back(QLatin1String("--dry-run"));
// Is a temporary file required?
const bool passAsTemporaryFile = value.contains(QLatin1Char('\n')); // Arguments: Prepend 'dryrun'. Do field replacement to actual
if (passAsTemporaryFile) { // argument value to expand via temporary file if specified
// Create a file and pass on as "key:filename" CustomWizardContext::TemporaryFilePtrList temporaryFiles;
TemporaryFilePtr temporaryFile = writeTemporaryFile(value); foreach (const GeneratorScriptArgument &argument, argumentsIn) {
if (temporaryFile.isNull()) { QString value = argument.value;
*errorMessage = QString::fromLatin1("Cannot create temporary file"); const bool nonEmptyReplacements
return false; = argument.flags & GeneratorScriptArgument::WriteFile ?
} CustomWizardContext::replaceFields(fieldMap, &value, &temporaryFiles) :
temporaryFiles.push_back(temporaryFile); CustomWizardContext::replaceFields(fieldMap, &value);
arguments << (it.key() + QLatin1Char(':') + QDir::toNativeSeparators(temporaryFile->fileName())); if (nonEmptyReplacements || !(argument.flags & GeneratorScriptArgument::OmitEmpty))
} else { arguments.push_back(value);
// Normal value "key=value"
arguments << (it.key() + QLatin1Char('=') + value);
}
} }
process.setWorkingDirectory(workingDirectory); process.setWorkingDirectory(workingDirectory);
if (CustomWizard::verbose()) if (CustomWizard::verbose())
@@ -157,13 +149,14 @@ static bool
// Do a dry run of the generation script to get a list of files // Do a dry run of the generation script to get a list of files
Core::GeneratedFiles Core::GeneratedFiles
dryRunCustomWizardGeneratorScript(const QString &targetPath, dryRunCustomWizardGeneratorScript(const QString &targetPath,
const QString &script, const QStringList &script,
const QList<GeneratorScriptArgument> &arguments,
const QMap<QString, QString> &fieldMap, const QMap<QString, QString> &fieldMap,
QString *errorMessage) QString *errorMessage)
{ {
// Run in temporary directory as the target path may not exist yet. // Run in temporary directory as the target path may not exist yet.
QString stdOut; QString stdOut;
if (!runGenerationScriptHelper(QDir::tempPath(), script, true, if (!runGenerationScriptHelper(QDir::tempPath(), script, arguments, true,
fieldMap, &stdOut, errorMessage)) fieldMap, &stdOut, errorMessage))
return Core::GeneratedFiles(); return Core::GeneratedFiles();
Core::GeneratedFiles files; Core::GeneratedFiles files;
@@ -199,10 +192,14 @@ Core::GeneratedFiles
return files; return files;
} }
bool runCustomWizardGeneratorScript(const QString &targetPath, const QString &script, bool runCustomWizardGeneratorScript(const QString &targetPath,
const QMap<QString, QString> &fieldMap, QString *errorMessage) const QStringList &script,
const QList<GeneratorScriptArgument> &arguments,
const QMap<QString, QString> &fieldMap,
QString *errorMessage)
{ {
return runGenerationScriptHelper(targetPath, script, false, fieldMap, return runGenerationScriptHelper(targetPath, script, arguments,
false, fieldMap,
0, errorMessage); 0, errorMessage);
} }

View File

@@ -31,8 +31,7 @@
#define CUSTOMWIZARDSCRIPTGENERATOR_H #define CUSTOMWIZARDSCRIPTGENERATOR_H
#include <QtCore/QMap> #include <QtCore/QMap>
#include <QtCore/QList> #include <QtCore/QStringList>
#include <QtCore/QString>
namespace Core { namespace Core {
class GeneratedFile; class GeneratedFile;
@@ -41,6 +40,8 @@ class GeneratedFile;
namespace ProjectExplorer { namespace ProjectExplorer {
namespace Internal { namespace Internal {
struct GeneratorScriptArgument;
/* Custom wizard script generator functions. In addition to the <file> elements /* 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 * 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" * a custom wizard call a generation script (specified in the "generatorscript"
@@ -64,14 +65,21 @@ namespace Internal {
* should create those, too. * should create those, too.
*/ */
// Parse the script arguments apart and expand the binary.
QStringList fixGeneratorScript(const QString &configFile, QString attributeIn);
// Step 1) Do a dry run of the generation script to get a list of files on stdout // Step 1) Do a dry run of the generation script to get a list of files on stdout
QList<Core::GeneratedFile> QList<Core::GeneratedFile>
dryRunCustomWizardGeneratorScript(const QString &targetPath, const QString &script, dryRunCustomWizardGeneratorScript(const QString &targetPath,
const QStringList &script,
const QList<GeneratorScriptArgument> &arguments,
const QMap<QString, QString> &fieldMap, const QMap<QString, QString> &fieldMap,
QString *errorMessage); QString *errorMessage);
// Step 2) Generate files // Step 2) Generate files
bool runCustomWizardGeneratorScript(const QString &targetPath, const QString &script, bool runCustomWizardGeneratorScript(const QString &targetPath,
const QStringList &script,
const QList<GeneratorScriptArgument> &arguments,
const QMap<QString, QString> &fieldMap, const QMap<QString, QString> &fieldMap,
QString *errorMessage); QString *errorMessage);