From c6132a05f311fedf7e14f5dc990aa60ba6a683f3 Mon Sep 17 00:00:00 2001
From: Friedemann Kleint <Friedemann.Kleint@nokia.com>
Date: Wed, 1 Sep 2010 13:27:24 +0200
Subject: [PATCH] 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.
---
 share/qtcreator/templates/wizards/README.txt  |   5 +-
 .../scriptgeneratedproject/generate.pl        | 108 +++++++++
 .../scriptgeneratedproject/wizard_sample.xml  |  47 ++++
 src/plugins/coreplugin/basefilewizard.cpp     |  20 +-
 src/plugins/coreplugin/basefilewizard.h       |  13 +-
 .../customwizard/customwizard.cpp             | 114 +++++++---
 .../customwizard/customwizard.h               |   5 +-
 .../customwizard/customwizard.pri             |   6 +-
 .../customwizard/customwizardparameters.cpp   |  38 +++-
 .../customwizard/customwizardparameters.h     |  11 +
 .../customwizardscriptgenerator.cpp           | 210 ++++++++++++++++++
 .../customwizardscriptgenerator.h             |  81 +++++++
 12 files changed, 603 insertions(+), 55 deletions(-)
 create mode 100755 share/qtcreator/templates/wizards/scriptgeneratedproject/generate.pl
 create mode 100644 share/qtcreator/templates/wizards/scriptgeneratedproject/wizard_sample.xml
 create mode 100644 src/plugins/projectexplorer/customwizard/customwizardscriptgenerator.cpp
 create mode 100644 src/plugins/projectexplorer/customwizard/customwizardscriptgenerator.h

diff --git a/share/qtcreator/templates/wizards/README.txt b/share/qtcreator/templates/wizards/README.txt
index 695c1ef7e96..3a2e9486224 100644
--- a/share/qtcreator/templates/wizards/README.txt
+++ b/share/qtcreator/templates/wizards/README.txt
@@ -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 'wizard.xml'.
 
diff --git a/share/qtcreator/templates/wizards/scriptgeneratedproject/generate.pl b/share/qtcreator/templates/wizards/scriptgeneratedproject/generate.pl
new file mode 100755
index 00000000000..3f4103c1813
--- /dev/null
+++ b/share/qtcreator/templates/wizards/scriptgeneratedproject/generate.pl
@@ -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();
+}
diff --git a/share/qtcreator/templates/wizards/scriptgeneratedproject/wizard_sample.xml b/share/qtcreator/templates/wizards/scriptgeneratedproject/wizard_sample.xml
new file mode 100644
index 00000000000..09d55cd24ee
--- /dev/null
+++ b/share/qtcreator/templates/wizards/scriptgeneratedproject/wizard_sample.xml
@@ -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>
diff --git a/src/plugins/coreplugin/basefilewizard.cpp b/src/plugins/coreplugin/basefilewizard.cpp
index 9ab88b19118..789bdf48032 100644
--- a/src/plugins/coreplugin/basefilewizard.cpp
+++ b/src/plugins/coreplugin/basefilewizard.cpp
@@ -552,12 +552,11 @@ QStringList BaseFileWizard::runWizard(const QString &path, QWidget *parent)
     }
 
     // Write
-    foreach (const GeneratedFile &generatedFile, files) {
-        if (!generatedFile.write(&errorMessage)) {
-            QMessageBox::critical(parent, tr("File Generation Failure"), errorMessage);
-            return QStringList();
-        }
+    if (!writeFiles(files, &errorMessage)) {
+        QMessageBox::critical(parent, tr("File Generation Failure"), errorMessage);
+        return QStringList();
     }
+
     bool removeOpenProjectAttribute = false;
     // Run the extensions
     foreach (IFileWizardExtension *ex, extensions) {
@@ -585,6 +584,17 @@ QStringList BaseFileWizard::runWizard(const QString &path, QWidget *parent)
     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)
 {
     w->setOption(QWizard::NoCancelButton, false);
diff --git a/src/plugins/coreplugin/basefilewizard.h b/src/plugins/coreplugin/basefilewizard.h
index ac094bed7b8..aa130b073ac 100644
--- a/src/plugins/coreplugin/basefilewizard.h
+++ b/src/plugins/coreplugin/basefilewizard.h
@@ -65,7 +65,14 @@ class GeneratedFilePrivate;
 class CORE_EXPORT GeneratedFile
 {
 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)
 
     GeneratedFile();
@@ -200,6 +207,10 @@ protected:
     virtual GeneratedFiles generateFiles(const QWizard *w,
                                          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
     virtual QString generatedProjectFilePath(const QWizard *wizard) const;
 
diff --git a/src/plugins/projectexplorer/customwizard/customwizard.cpp b/src/plugins/projectexplorer/customwizard/customwizard.cpp
index 33345c6db07..cb8eab04fcc 100644
--- a/src/plugins/projectexplorer/customwizard/customwizard.cpp
+++ b/src/plugins/projectexplorer/customwizard/customwizard.cpp
@@ -32,6 +32,7 @@
 #include "customwizardpage.h"
 #include "projectexplorer.h"
 #include "baseprojectwizarddialog.h"
+#include "customwizardscriptgenerator.h"
 
 #include <coreplugin/icore.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
     const Internal::CustomWizardPage *cwp = findWizardPage<Internal::CustomWizardPage>(dialog);
     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) {
         QString logText;
         QTextStream str(&logText);
-        str << "CustomWizard::generateFiles: " << path << '\n';
-        const FieldReplacementMap::const_iterator cend = fieldMap.constEnd();
-        for (FieldReplacementMap::const_iterator it = fieldMap.constBegin(); it != cend; ++it)
+        str << "CustomWizard::generateFiles: " << ctx->targetPath << '\n';
+        const FieldReplacementMap::const_iterator cend = context()->replacements.constEnd();
+        for (FieldReplacementMap::const_iterator it = context()->replacements.constBegin(); it != cend; ++it)
             str << "  '" << it.key() << "' -> '" << it.value() << "'\n";
         qWarning("%s", qPrintable(logText));
     }
-    return generateWizardFiles(path, fieldMap, errorMessage);
+    return generateWizardFiles(errorMessage);
 }
 
-Core::GeneratedFiles CustomWizard::generateWizardFiles(const QString &targetPath,
-                                                       const FieldReplacementMap &fieldReplacementMap,
-                                                       QString *errorMessage) const
+
+bool CustomWizard::writeFiles(const Core::GeneratedFiles &files, QString *errorMessage)
+{
+    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)
+            qDebug("Creating directory %s", qPrintable(ctx->targetPath));
+        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
 {
-    if (CustomWizardPrivate::verbose)
-        qDebug() << "Replacements" << fieldReplacementMap;
-    // Create files
     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)
-        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 rc;
 }
@@ -437,13 +488,22 @@ Core::GeneratedFiles CustomProjectWizard::generateFiles(const QWizard *w, QStrin
 {
     const BaseProjectWizardDialog *dialog = qobject_cast<const BaseProjectWizardDialog *>(w);
     QTC_ASSERT(dialog, return Core::GeneratedFiles())
-    const QString targetPath = dialog->path() + QLatin1Char('/') + dialog->projectName();
-    // Add project name as macro.
+    // Add project name as macro. Path is here under project directory
+    CustomWizardContextPtr ctx = context();
+    ctx->targetPath = dialog->path() + QLatin1Char('/') + dialog->projectName();
     FieldReplacementMap fieldReplacementMap = replacementMap(dialog);
     fieldReplacementMap.insert(QLatin1String("ProjectName"), dialog->projectName());
+    ctx->replacements = fieldReplacementMap;
     if (CustomWizardPrivate::verbose)
-        qDebug() << "CustomProjectWizard::generateFiles" << dialog << targetPath << fieldReplacementMap;
-    return generateWizardFiles(targetPath, fieldReplacementMap, errorMessage);
+        qDebug() << "CustomProjectWizard::generateFiles" << dialog << ctx->targetPath << ctx->replacements;
+    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)
@@ -461,23 +521,11 @@ bool CustomProjectWizard::postGenerateOpen(const Core::GeneratedFiles &l, QStrin
     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);
-    QTC_ASSERT(dialog, return QString())
-    const QString targetPath = dialog->path() + QLatin1Char('/') + dialog->projectName();
-    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();
+    if (CustomWizardPrivate::verbose)
+        qDebug("CustomProjectWizard::generatedProjectFilePath: '%s'", qPrintable(context()->projectFilePath));
+    return context()->projectFilePath;
 }
 
 bool CustomProjectWizard::postGenerateFiles(const QWizard *, const Core::GeneratedFiles &l, QString *errorMessage)
diff --git a/src/plugins/projectexplorer/customwizard/customwizard.h b/src/plugins/projectexplorer/customwizard/customwizard.h
index 5eb6d37e4be..fd864458dd8 100644
--- a/src/plugins/projectexplorer/customwizard/customwizard.h
+++ b/src/plugins/projectexplorer/customwizard/customwizard.h
@@ -119,11 +119,10 @@ protected:
                           const WizardPageList &extensionPages) const;
 
     // generate files in path
-    Core::GeneratedFiles generateWizardFiles(const QString &path,
-                                             const FieldReplacementMap &defaultFields,
-                                             QString *errorMessage) const;
+    Core::GeneratedFiles generateWizardFiles(QString *errorMessage) const;
     // Create replacement map as static base fields + QWizard fields
     FieldReplacementMap replacementMap(const QWizard *w) const;
+    virtual bool writeFiles(const Core::GeneratedFiles &files, QString *errorMessage);
 
     CustomWizardParametersPtr parameters() const;
     CustomWizardContextPtr context() const;
diff --git a/src/plugins/projectexplorer/customwizard/customwizard.pri b/src/plugins/projectexplorer/customwizard/customwizard.pri
index ac8bf2ea260..6b0066cbc65 100644
--- a/src/plugins/projectexplorer/customwizard/customwizard.pri
+++ b/src/plugins/projectexplorer/customwizard/customwizard.pri
@@ -2,8 +2,10 @@ INCLUDEPATH *= $$PWD
 HEADERS += $$PWD/customwizard.h \
     $$PWD/customwizardparameters.h \
     $$PWD/customwizardpage.h \
-    customwizard/customwizardpreprocessor.h
+    customwizard/customwizardpreprocessor.h \
+    customwizard/customwizardscriptgenerator.h
 SOURCES += $$PWD/customwizard.cpp \
     $$PWD/customwizardparameters.cpp \
     $$PWD/customwizardpage.cpp \
-    customwizard/customwizardpreprocessor.cpp
+    customwizard/customwizardpreprocessor.cpp \
+    customwizard/customwizardscriptgenerator.cpp
diff --git a/src/plugins/projectexplorer/customwizard/customwizardparameters.cpp b/src/plugins/projectexplorer/customwizard/customwizardparameters.cpp
index 848944b4dbd..eab3f861655 100644
--- a/src/plugins/projectexplorer/customwizard/customwizardparameters.cpp
+++ b/src/plugins/projectexplorer/customwizard/customwizardparameters.cpp
@@ -70,12 +70,10 @@ static const char fieldMandatoryAttributeC[] = "mandatory";
 static const char fieldControlElementC[] = "fieldcontrol";
 
 static const char filesElementC[] = "files";
+static const char filesGeneratorScriptAttributeC[] = "generatorscript";
 static const char fileElementC[] = "file";
-static const char fileNameSourceAttributeC[] = "source";
-static const char fileNameTargetAttributeC[] = "target";
-static const char fileNameOpenEditorAttributeC[] = "openeditor";
-static const char fileNameOpenProjectAttributeC[] = "openproject";
-
+static const char fileSourceAttributeC[] = "source";
+static const char fileTargetAttributeC[] = "target";
 
 enum ParseState {
     ParseBeginning,
@@ -95,6 +93,9 @@ enum ParseState {
 namespace ProjectExplorer {
 namespace Internal {
 
+const char customWizardFileOpenEditorAttributeC[] = "openeditor";
+const char customWizardFileOpenProjectAttributeC[] = "openproject";
+
 CustomWizardField::CustomWizardField() :
     mandatory(false)
 {
@@ -134,6 +135,7 @@ void CustomWizardParameters::clear()
     directory.clear();
     files.clear();
     fields.clear();
+    filesGeneratorScript.clear();
     firstPageId = -1;
 }
 
@@ -496,12 +498,15 @@ CustomWizardParameters::ParseResult
                         state = ParseWithinComboEntry;
                 }
                      break;
+                case ParseWithinFiles:
+                    filesGeneratorScript = attributeValue(reader, filesGeneratorScriptAttributeC);
+                    break;
                 case ParseWithinFile: { // file attribute
                         CustomWizardFile file;
-                        file.source = attributeValue(reader, fileNameSourceAttributeC);
-                        file.target = attributeValue(reader, fileNameTargetAttributeC);
-                        file.openEditor = booleanAttributeValue(reader, fileNameOpenEditorAttributeC, false);
-                        file.openProject = booleanAttributeValue(reader, fileNameOpenProjectAttributeC, false);
+                        file.source = attributeValue(reader, fileSourceAttributeC);
+                        file.target = attributeValue(reader, fileTargetAttributeC);
+                        file.openEditor = booleanAttributeValue(reader, customWizardFileOpenEditorAttributeC, false);
+                        file.openProject = booleanAttributeValue(reader, customWizardFileOpenProjectAttributeC, false);
                         if (file.target.isEmpty())
                             file.target = file.source;
                         if (file.source.isEmpty()) {
@@ -554,11 +559,23 @@ CustomWizardParameters::ParseResult
     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 rc;
     QTextStream str(&rc);
     str << "Directory: " << directory << " Klass: '" << klass << "'\n";
+    if (!filesGeneratorScript.isEmpty())
+        str << "Script: '" <<  filesGeneratorScript << "'\n";
     foreach(const CustomWizardFile &f, files) {
         str << "  File source: " << f.source << " Target: " << f.target;
         if (f.openEditor)
@@ -652,6 +669,9 @@ void CustomWizardContext::reset()
                             mdb->preferredSuffixByType(QLatin1String(CppTools::Constants::CPP_SOURCE_MIMETYPE)));
     baseReplacements.insert(QLatin1String("CppHeaderSuffix"),
                             mdb->preferredSuffixByType(QLatin1String(CppTools::Constants::CPP_HEADER_MIMETYPE)));
+    replacements.clear();
+    targetPath.clear();
+    projectFilePath.clear();
 }
 
 QString CustomWizardContext::processFile(const FieldReplacementMap &fm, QString in)
diff --git a/src/plugins/projectexplorer/customwizard/customwizardparameters.h b/src/plugins/projectexplorer/customwizard/customwizardparameters.h
index 3f5b75196a8..64f601d5f71 100644
--- a/src/plugins/projectexplorer/customwizard/customwizardparameters.h
+++ b/src/plugins/projectexplorer/customwizard/customwizardparameters.h
@@ -81,9 +81,12 @@ public:
                       Core::BaseFileWizardParameters *bp, QString *errorMessage);
     QString toString() const;
 
+    QString filesGeneratorScriptFullPath() const;
+
     QString directory;
     QString klass;
     QList<CustomWizardFile> files;
+    QString filesGeneratorScript;
     QString fieldPageTitle;
     QList<CustomWizardField> fields;
     int firstPageId;
@@ -110,8 +113,16 @@ struct CustomWizardContext {
     static QString processFile(const FieldReplacementMap &fm, QString in);
 
     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 ProjectExplorer
 
diff --git a/src/plugins/projectexplorer/customwizard/customwizardscriptgenerator.cpp b/src/plugins/projectexplorer/customwizard/customwizardscriptgenerator.cpp
new file mode 100644
index 00000000000..4fb95c5d42b
--- /dev/null
+++ b/src/plugins/projectexplorer/customwizard/customwizardscriptgenerator.cpp
@@ -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
diff --git a/src/plugins/projectexplorer/customwizard/customwizardscriptgenerator.h b/src/plugins/projectexplorer/customwizard/customwizardscriptgenerator.h
new file mode 100644
index 00000000000..d47168b3f93
--- /dev/null
+++ b/src/plugins/projectexplorer/customwizard/customwizardscriptgenerator.h
@@ -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
-- 
GitLab