diff --git a/share/qtcreator/templates/wizards/README.txt b/share/qtcreator/templates/wizards/README.txt index 695c1ef7e969a5f02300a9e01adbd30e84703675..3a2e94862246b9a0a233ac46b27f182fa54a9f44 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 0000000000000000000000000000000000000000..3f4103c18139e929ef379c38ad4e499c7d882512 --- /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 0000000000000000000000000000000000000000..09d55cd24ee3f272c894e5a31be65730ec917298 --- /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 9ab88b19118eb8f2b08b0b258853d49616f09dd4..789bdf48032238244f38c405abed83bb2c7e5094 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 ac094bed7b8855711f431b1f38998cd8865fc626..aa130b073ac5b3284ed314a370594024e13efa87 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 33345c6db07bedd0a9a0847a6e0233a4931e2cf5..cb8eab04fcc215437eecf0b2f9809daa2c63cf23 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 5eb6d37e4be2587a73d2d0333e369cb89512e39d..fd864458dd8c75f8dc8cf41411ecc613afe9167a 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 ac8bf2ea2601015aae5bd8eb4ee0a90556c2a35c..6b0066cbc650f2b0cb637f83ee3f5d9c468565b7 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 848944b4dbdc5acf96393990c3ca50f080d1e707..eab3f8616554b49565d0c570f3df6a5fcb3fb5fd 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 3f5b75196a894f117a14c078d058415921d82108..64f601d5f7145a341dd21537ef65f6d3b83aaaf4 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 0000000000000000000000000000000000000000..4fb95c5d42b07178f2ece505f3c686b33acdc8d4 --- /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 0000000000000000000000000000000000000000..d47168b3f934ae8d4364be6733e3a818047939f9 --- /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