diff --git a/share/qtcreator/templates/wizards/scriptgeneratedproject/generate.pl b/share/qtcreator/templates/wizards/scriptgeneratedproject/generate.pl index 3f4103c18139e929ef379c38ad4e499c7d882512..580991ea057ba44dd9e979155111419b930dfa41 100755 --- a/share/qtcreator/templates/wizards/scriptgeneratedproject/generate.pl +++ b/share/qtcreator/templates/wizards/scriptgeneratedproject/generate.pl @@ -34,45 +34,59 @@ use Getopt::Long; use IO::File; my $optDryRun = 0; -my $fieldClassName = 'MyClass'; -my $standardFieldProjectName = 'MyProject'; -my $standardFieldCppHeaderSuffix = 'h'; -my $standardFieldCppSourceSuffix = 'cpp'; +my $optHelp = 0; +my $optClassName = 'MyClass'; +my $optProjectName = 'MyProject'; +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; -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. -Known parameters: ClassName=<value> EOF -if (!GetOptions("dry-run" => \$optDryRun)) { - print $USAGE; - exit (1); -} - -if (scalar(@ARGV) == 0) { +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; 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'; +my $baseFileName = lc($optClassName); +my $sourceFileName = $baseFileName . '.' . $optCppSourceSuffix; +my $headerFileName = $baseFileName . '.' . $optCppHeaderSuffix; +my $mainSourceFileName = 'main.' . $optCppSourceSuffix; +my $projectFileName = lc($optProjectName) . '.pro'; if ($optDryRun) { # -- Step 1) Dry run: Print file names along with attributes @@ -85,23 +99,29 @@ if ($optDryRun) { 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"; + print $headerFile '#ifndef ', uc($optClassName), "_H\n#define ", uc($optClassName), "_H\n\n", + 'class ', $optClassName, "{\npublic:\n ", $optClassName, "();\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"; + $optClassName,'::', $optClassName, "()\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), + print $mainSourceFile '#include "', $headerFileName ,"\"\n\n"; +# -- 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"; $mainSourceFile->close(); 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, "\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 index 09d55cd24ee3f272c894e5a31be65730ec917298..35e31897f0d219a0ac928cbf75409c3bd3be6f48 100644 --- a/share/qtcreator/templates/wizards/scriptgeneratedproject/wizard_sample.xml +++ b/share/qtcreator/templates/wizards/scriptgeneratedproject/wizard_sample.xml @@ -34,8 +34,6 @@ Custom class wizard example configuration file. --> <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> @@ -43,5 +41,19 @@ Custom class wizard example configuration file. --> <fieldcontrol class="QLineEdit" validator="^[a-zA-Z0-9_]+$" defaulttext="MyClass" /> <fielddescription>Class name:</fielddescription> </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> + <!-- 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> diff --git a/src/plugins/projectexplorer/customwizard/customwizard.cpp b/src/plugins/projectexplorer/customwizard/customwizard.cpp index 2a84d2ed5721b772671b33e1bd2f70b16b161a4e..7ab2a611be17fb6ecb8a1ea5704ab47414cc25fe 100644 --- a/src/plugins/projectexplorer/customwizard/customwizard.cpp +++ b/src/plugins/projectexplorer/customwizard/customwizard.cpp @@ -225,7 +225,9 @@ bool CustomWizard::writeFiles(const Core::GeneratedFiles &files, QString *errorM } } // 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)) return false; // 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 (!QFileInfo(generatedFile.path()).isFile()) { *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 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 (!d->m_parameters->filesGeneratorScript.isEmpty()) { rc += Internal::dryRunCustomWizardGeneratorScript(ctx->targetPath, - d->m_parameters->filesGeneratorScriptFullPath(), + d->m_parameters->filesGeneratorScript, + d->m_parameters->filesGeneratorScriptArguments, ctx->replacements, errorMessage); if (rc.isEmpty()) return rc; diff --git a/src/plugins/projectexplorer/customwizard/customwizardparameters.cpp b/src/plugins/projectexplorer/customwizard/customwizardparameters.cpp index eab3f8616554b49565d0c570f3df6a5fcb3fb5fd..f3d8fe9591f80a79a28a57721a11a2e6aa6bbf53 100644 --- a/src/plugins/projectexplorer/customwizard/customwizardparameters.cpp +++ b/src/plugins/projectexplorer/customwizard/customwizardparameters.cpp @@ -29,18 +29,24 @@ #include "customwizardparameters.h" #include "customwizardpreprocessor.h" +#include "customwizardscriptgenerator.h" #include <coreplugin/mimedatabase.h> #include <coreplugin/icore.h> #include <cpptools/cpptoolsconstants.h> +#include <utils/qtcassert.h> + #include <QtCore/QDebug> #include <QtCore/QCoreApplication> #include <QtCore/QLocale> #include <QtCore/QFile> +#include <QtCore/QDir> #include <QtCore/QFileInfo> #include <QtCore/QXmlStreamReader> #include <QtCore/QXmlStreamAttribute> +#include <QtCore/QTemporaryFile> + #include <QtGui/QIcon> enum { debug = 0 }; @@ -64,6 +70,13 @@ static const char comboEntriesElementC[] = "comboentries"; static const char comboEntryElementC[] = "comboentry"; 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 fieldNameAttributeC[] = "name"; static const char fieldMandatoryAttributeC[] = "mandatory"; @@ -87,6 +100,8 @@ enum ParseState { ParseWithinComboEntryText, ParseWithinFiles, ParseWithinFile, + ParseWithinScript, + ParseWithinScriptArguments, ParseError }; @@ -136,6 +151,7 @@ void CustomWizardParameters::clear() files.clear(); fields.clear(); filesGeneratorScript.clear(); + filesGeneratorScriptArguments.clear(); firstPageId = -1; } @@ -276,6 +292,8 @@ static ParseState nextOpeningState(ParseState in, const QStringRef &name) return ParseWithinFields; if (name == QLatin1String(filesElementC)) return ParseWithinFiles; + if (name == QLatin1String(generatorScriptElementC)) + return ParseWithinScript; break; case ParseWithinFields: if (name == QLatin1String(fieldElementC)) @@ -303,10 +321,15 @@ static ParseState nextOpeningState(ParseState in, const QStringRef &name) if (name == QLatin1String(fileElementC)) return ParseWithinFile; break; + case ParseWithinScript: + if (name == QLatin1String(generatorScriptArgumentElementC)) + return ParseWithinScriptArguments; + break; case ParseWithinFieldDescription: // No subelements case ParseWithinComboEntryText: case ParseWithinFile: case ParseError: + case ParseWithinScriptArguments: break; } return ParseError; @@ -358,6 +381,14 @@ static ParseState nextClosingState(ParseState in, const QStringRef &name) if (name == QLatin1String(comboEntryTextElementC)) return ParseWithinComboEntry; break; + case ParseWithinScript: + if (name == QLatin1String(generatorScriptElementC)) + return ParseWithinWizard; + break; + case ParseWithinScriptArguments: + if (name == QLatin1String(generatorScriptArgumentElementC)) + return ParseWithinScript; + break; case ParseError: break; } @@ -427,6 +458,11 @@ static inline QString localeLanguage() return name; } +GeneratorScriptArgument::GeneratorScriptArgument(const QString &v) : + value(v), flags(0) +{ +} + // Main parsing routine CustomWizardParameters::ParseResult CustomWizardParameters::parse(QIODevice &device, @@ -499,7 +535,6 @@ CustomWizardParameters::ParseResult } break; case ParseWithinFiles: - filesGeneratorScript = attributeValue(reader, filesGeneratorScriptAttributeC); break; case ParseWithinFile: { // file attribute CustomWizardFile file; @@ -516,6 +551,26 @@ CustomWizardParameters::ParseResult } } 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: break; } @@ -559,23 +614,26 @@ 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"; + if (!filesGeneratorScriptArguments.isEmpty()) { + 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) { str << " File source: " << f.source << " Target: " << f.target; if (f.openEditor) @@ -603,8 +661,16 @@ QString CustomWizardParameters::toString() const // ------------ 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) { qDebug().nospace() << "CustomWizardContext::replaceFields with " << fm << *s; @@ -633,7 +699,7 @@ void CustomWizardContext::replaceFields(const FieldReplacementMap &fm, QString * modifier = fieldSpec.at(fieldSpecSize - 1).toLatin1(); 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()) { pos = nextPos; // Not found, skip continue; @@ -655,9 +721,64 @@ void CustomWizardContext::replaceFields(const FieldReplacementMap &fm, QString * default: 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(); } + 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() diff --git a/src/plugins/projectexplorer/customwizard/customwizardparameters.h b/src/plugins/projectexplorer/customwizard/customwizardparameters.h index 64f601d5f7145a341dd21537ef65f6d3b83aaaf4..5c757e2b835f31ff458620057677c6d008507c4d 100644 --- a/src/plugins/projectexplorer/customwizard/customwizardparameters.h +++ b/src/plugins/projectexplorer/customwizard/customwizardparameters.h @@ -32,12 +32,14 @@ #include <coreplugin/basefilewizard.h> -#include <QtCore/QList> +#include <QtCore/QStringList> #include <QtCore/QMap> +#include <QtCore/QSharedPointer> QT_BEGIN_NAMESPACE class QIODevice; class QDebug; +class QTemporaryFile; QT_END_NAMESPACE namespace ProjectExplorer { @@ -68,6 +70,23 @@ struct CustomWizardFile { 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 { public: @@ -81,12 +100,12 @@ public: Core::BaseFileWizardParameters *bp, QString *errorMessage); QString toString() const; - QString filesGeneratorScriptFullPath() const; - QString directory; QString klass; QList<CustomWizardFile> files; - QString filesGeneratorScript; + QStringList filesGeneratorScript; // Complete binary, such as 'cmd /c myscript.pl'. + QList<GeneratorScriptArgument> filesGeneratorScriptArguments; + QString fieldPageTitle; QList<CustomWizardField> fields; int firstPageId; @@ -102,14 +121,24 @@ public: struct CustomWizardContext { typedef QMap<QString, QString> FieldReplacementMap; + typedef QSharedPointer<QTemporaryFile> TemporaryFilePtr; + typedef QList<TemporaryFilePtr> TemporaryFilePtrList; void reset(); // Replace field values delimited by '%' with special modifiers: // %Field% -> simple replacement // %Field:l% -> lower case replacement, 'u' upper case, - // 'c' capitalize first letter. - static void replaceFields(const FieldReplacementMap &fm, QString *s); + // 'c' capitalize first letter. Return value indicates whether non-empty + // 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); FieldReplacementMap baseReplacements; diff --git a/src/plugins/projectexplorer/customwizard/customwizardscriptgenerator.cpp b/src/plugins/projectexplorer/customwizard/customwizardscriptgenerator.cpp index 4fb95c5d42b07178f2ece505f3c686b33acdc8d4..5b6c2bef19a92740b90408a3c97abf47d8ae50f4 100644 --- a/src/plugins/projectexplorer/customwizard/customwizardscriptgenerator.cpp +++ b/src/plugins/projectexplorer/customwizard/customwizardscriptgenerator.cpp @@ -29,7 +29,9 @@ #include "customwizardscriptgenerator.h" #include "customwizard.h" -#include "customwizardparameters.h" // XML attributes +#include "customwizardparameters.h" + +#include <utils/qtcassert.h> #include <QtCore/QProcess> #include <QtCore/QDir> @@ -41,84 +43,74 @@ 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) +// Parse helper: Determine the correct binary to run: +// Expand to full wizard path if it is relative and located +// in the wizard directory, else assume it can be found in path. +// On Windows, run non-exe files with 'cmd /c'. +QStringList fixGeneratorScript(const QString &configFile, QString binary) { - TemporaryFilePtr temporaryFile(new QTemporaryFile(tempFilePattern())); - if (!temporaryFile->open()) - return TemporaryFilePtr(); - temporaryFile->write(content.toLocal8Bit()); - temporaryFile->close(); - return temporaryFile; + if (binary.isEmpty()) + return QStringList(); + // Expand to full path if it is relative and in the wizard + // directory, else assume it can be found in path. + QFileInfo binaryInfo(binary); + if (!binaryInfo.isAbsolute()) { + QString fullPath = QFileInfo(configFile).absolutePath(); + fullPath += QLatin1Char('/'); + fullPath += binary; + const QFileInfo fullPathInfo(fullPath); + if (fullPathInfo.isFile()) { + binary = fullPathInfo.absoluteFilePath(); + binaryInfo = fullPathInfo; + } + } // not absolute + 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. static bool runGenerationScriptHelper(const QString &workingDirectory, - QString binary, bool dryRun, + const QStringList &script, + const QList<GeneratorScriptArgument> &argumentsIn, + 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; + const QString binary = script.front(); 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 + const int binarySize = script.size(); + for (int i = 1; i < binarySize; i++) + arguments.push_back(script.at(i)); + + // Arguments: Prepend 'dryrun' and do field replacement 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; + arguments.push_back(QLatin1String("--dry-run")); - 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); - } + // Arguments: Prepend 'dryrun'. Do field replacement to actual + // argument value to expand via temporary file if specified + CustomWizardContext::TemporaryFilePtrList temporaryFiles; + foreach (const GeneratorScriptArgument &argument, argumentsIn) { + QString value = argument.value; + const bool nonEmptyReplacements + = argument.flags & GeneratorScriptArgument::WriteFile ? + CustomWizardContext::replaceFields(fieldMap, &value, &temporaryFiles) : + CustomWizardContext::replaceFields(fieldMap, &value); + if (nonEmptyReplacements || !(argument.flags & GeneratorScriptArgument::OmitEmpty)) + arguments.push_back(value); } process.setWorkingDirectory(workingDirectory); if (CustomWizard::verbose()) @@ -157,13 +149,14 @@ static bool // Do a dry run of the generation script to get a list of files Core::GeneratedFiles dryRunCustomWizardGeneratorScript(const QString &targetPath, - const QString &script, + const QStringList &script, + const QList<GeneratorScriptArgument> &arguments, 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, + if (!runGenerationScriptHelper(QDir::tempPath(), script, arguments, true, fieldMap, &stdOut, errorMessage)) return Core::GeneratedFiles(); Core::GeneratedFiles files; @@ -199,10 +192,14 @@ Core::GeneratedFiles return files; } -bool runCustomWizardGeneratorScript(const QString &targetPath, const QString &script, - const QMap<QString, QString> &fieldMap, QString *errorMessage) +bool runCustomWizardGeneratorScript(const QString &targetPath, + 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); } diff --git a/src/plugins/projectexplorer/customwizard/customwizardscriptgenerator.h b/src/plugins/projectexplorer/customwizard/customwizardscriptgenerator.h index d47168b3f934ae8d4364be6733e3a818047939f9..6010a97e1b01a40d7653202a34ba56564f60e850 100644 --- a/src/plugins/projectexplorer/customwizard/customwizardscriptgenerator.h +++ b/src/plugins/projectexplorer/customwizard/customwizardscriptgenerator.h @@ -31,8 +31,7 @@ #define CUSTOMWIZARDSCRIPTGENERATOR_H #include <QtCore/QMap> -#include <QtCore/QList> -#include <QtCore/QString> +#include <QtCore/QStringList> namespace Core { class GeneratedFile; @@ -41,6 +40,8 @@ class GeneratedFile; namespace ProjectExplorer { namespace Internal { +struct GeneratorScriptArgument; + /* 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" @@ -64,14 +65,21 @@ namespace Internal { * 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 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, QString *errorMessage); // 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, QString *errorMessage);