/* ============================================================================== This file is part of the JUCE library - "Jules' Utility Class Extensions" Copyright 2004-10 by Raw Material Software Ltd. ------------------------------------------------------------------------------ JUCE can be redistributed and/or modified under the terms of the GNU General Public License (Version 2), as published by the Free Software Foundation. A copy of the license is included in the JUCE distribution, or can be found online at www.gnu.org/licenses. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. ------------------------------------------------------------------------------ To release a closed-source product which uses JUCE, commercial licenses are available: visit www.rawmaterialsoftware.com/juce for more information. ============================================================================== */ #ifndef __JUCER_PROJECTSAVER_JUCEHEADER__ #define __JUCER_PROJECTSAVER_JUCEHEADER__ //============================================================================== class ProjectSaver { public: ProjectSaver (Project& project_, const File& projectFile_) : project (project_), projectFile (projectFile_), resourceFile (project_) { } const String save() { const File oldFile (project.getFile()); project.setFile (projectFile); const String linkageMode (project.getJuceLinkageMode()); if (linkageMode == Project::notLinkedToJuce) { hasAppHeaderFile = ! project.isLibrary(); hasAppConfigFile = false; numJuceSourceFiles = 0; } else if (linkageMode == Project::useAmalgamatedJuce || linkageMode == Project::useAmalgamatedJuceViaSingleTemplate) { hasAppHeaderFile = true; hasAppConfigFile = true; numJuceSourceFiles = 1; } else if (linkageMode == Project::useAmalgamatedJuceViaMultipleTemplates) { hasAppHeaderFile = true; hasAppConfigFile = true; numJuceSourceFiles = project.getNumSeparateAmalgamatedFiles(); } else if (linkageMode == Project::useLinkedJuce) { hasAppHeaderFile = true; hasAppConfigFile = true; numJuceSourceFiles = 0; } else { jassertfalse; } hasResources = (resourceFile.getNumFiles() > 0); writeMainProjectFile(); if (errors.size() == 0) writeJuceSourceWrappers(); if (errors.size() == 0) writeProjects(); if (errors.size() > 0) project.setFile (oldFile); return errors[0]; } private: Project& project; const File& projectFile; ResourceFile resourceFile; StringArray errors; File appConfigFile, juceHeaderFile, binaryDataCpp, pluginCharacteristicsFile; bool hasAppHeaderFile, hasAppConfigFile, hasResources; int numJuceSourceFiles; void writeMainProjectFile() { ScopedPointer xml (project.getProjectRoot().createXml()); jassert (xml != 0); if (xml != 0) { #if JUCE_DEBUG { MemoryOutputStream mo; project.getProjectRoot().writeToStream (mo); MemoryInputStream mi (mo.getData(), mo.getDataSize(), false); ValueTree v = ValueTree::readFromStream (mi); ScopedPointer xml2 (v.createXml()); // This bit just tests that ValueTree save/load works reliably.. Let me know if this asserts for you! jassert (xml->isEquivalentTo (xml2, true)); } #endif MemoryOutputStream mo; xml->writeToStream (mo, String::empty); if (! FileHelpers::overwriteFileWithNewDataIfDifferent (projectFile, mo)) errors.add ("Couldn't write to the target file!"); } } void writeJucerComment (OutputStream& out) { out << "/*" << newLine << newLine << " IMPORTANT! This file is auto-generated by the Jucer each time you save your" << newLine << " project - if you alter its contents, your changes may be overwritten!" << newLine << newLine; } void writeAppConfig (OutputStream& out) { writeJucerComment (out); out << " If you want to change any of these values, use the Jucer to do so, rather than" << newLine << " editing this file directly!" << newLine << newLine << " Any commented-out settings will fall back to using the default values that" << newLine << " they are given in juce_Config.h" << newLine << newLine << "*/" << newLine << newLine; bool notActive = project.getJuceLinkageMode() == Project::useLinkedJuce || project.getJuceLinkageMode() == Project::notLinkedToJuce; if (notActive) out << "/* NOTE: These configs aren't available when you're linking to the juce library statically!" << newLine << " If you need to set a configuration that differs from the default, you'll need" << newLine << " to include the amalgamated Juce files." << newLine << newLine; OwnedArray flags; project.getJuceConfigFlags (flags); for (int i = 0; i < flags.size(); ++i) { const Project::JuceConfigFlag* const f = flags[i]; const String value (f->value.toString()); if (value != Project::configFlagEnabled && value != Project::configFlagDisabled) out << "//#define "; else out << "#define "; out << f->symbol; if (value == Project::configFlagEnabled) out << " 1"; else if (value == Project::configFlagDisabled) out << " 0"; out << newLine; } if (notActive) out << newLine << "*/" << newLine; } void writeSourceWrapper (OutputStream& out, int fileNumber) { writeJucerComment (out); out << " This file pulls in all the Juce source code, and builds it using the settings" << newLine << " defined in " << appConfigFile.getFileName() << "." << newLine << newLine << " If you want to change the method by which Juce is linked into your app, use the" << newLine << " Jucer to change it, rather than trying to edit this file directly." << newLine << newLine << "*/" << newLine << newLine << CodeHelpers::createIncludeStatement (appConfigFile, appConfigFile) << newLine; if (fileNumber == 0) writeInclude (out, project.isUsingFullyAmalgamatedFile() ? "juce_amalgamated.cpp" : "amalgamation/juce_amalgamated_template.cpp"); else writeInclude (out, "amalgamation/juce_amalgamated" + String (fileNumber) + ".cpp"); } void writeAppHeader (OutputStream& out) { writeJucerComment (out); out << " This is the header file that your files should include in order to get all the" << newLine << " Juce library headers. You should NOT include juce.h or juce_amalgamated.h directly in" << newLine << " your own source files, because that wouldn't pick up the correct Juce configuration" << newLine << " options for your app." << newLine << newLine << "*/" << newLine << newLine; String headerGuard ("__APPHEADERFILE_" + String::toHexString (juceHeaderFile.hashCode()).toUpperCase() + "__"); out << "#ifndef " << headerGuard << newLine << "#define " << headerGuard << newLine << newLine; if (hasAppConfigFile) out << CodeHelpers::createIncludeStatement (appConfigFile, appConfigFile) << newLine; if (project.getJuceLinkageMode() != Project::notLinkedToJuce) { writeInclude (out, (project.isUsingSingleTemplateFile() || project.isUsingMultipleTemplateFiles()) ? "juce_amalgamated.h" // could use "amalgamation/juce_amalgamated_template.h", but it's slower.. : (project.isUsingFullyAmalgamatedFile() ? "juce_amalgamated.h" : "juce.h")); } if (binaryDataCpp.exists()) out << CodeHelpers::createIncludeStatement (binaryDataCpp.withFileExtension (".h"), appConfigFile) << newLine; out << newLine << "namespace ProjectInfo" << newLine << "{" << newLine << " const char* const projectName = " << CodeHelpers::addEscapeChars (project.getProjectName().toString()).quoted() << ";" << newLine << " const char* const versionString = " << CodeHelpers::addEscapeChars (project.getVersion().toString()).quoted() << ";" << newLine << " const int versionNumber = " << createVersionCode (project.getVersion().toString()) << ";" << newLine << "}" << newLine << newLine << "#endif // " << headerGuard << newLine; } void writeInclude (OutputStream& out, const String& pathFromJuceFolder) { StringArray paths, guards; for (int i = project.getNumExporters(); --i >= 0;) { ScopedPointer exporter (project.createExporter (i)); if (exporter != 0) { paths.add (exporter->getIncludePathForFileInJuceFolder (pathFromJuceFolder, juceHeaderFile)); guards.add ("defined (" + exporter->getExporterIdentifierMacro() + ")"); } } StringArray uniquePaths (paths); uniquePaths.removeDuplicates (false); if (uniquePaths.size() == 1) { out << "#include " << paths[0] << newLine; } else { int i = paths.size(); for (; --i >= 0;) { for (int j = i; --j >= 0;) { if (paths[i] == paths[j] && guards[i] == guards[j]) { paths.remove (i); guards.remove (i); } } } for (i = 0; i < paths.size(); ++i) { out << (i == 0 ? "#if " : "#elif ") << guards[i] << newLine << " #include " << paths[i] << newLine; } out << "#endif" << newLine; } } static int countMaxPluginChannels (const String& configString, bool isInput) { StringArray configs; configs.addTokens (configString, ", {}", String::empty); configs.trim(); configs.removeEmptyStrings(); jassert ((configs.size() & 1) == 0); // looks like a syntax error in the configs? int maxVal = 0; for (int i = (isInput ? 0 : 1); i < configs.size(); i += 2) maxVal = jmax (maxVal, configs[i].getIntValue()); return maxVal; } static const String createVersionCode (const String& version) { StringArray configs; configs.addTokens (version, ",.", String::empty); configs.trim(); configs.removeEmptyStrings(); int value = (configs[0].getIntValue() << 16) + (configs[1].getIntValue() << 8) + configs[2].getIntValue(); if (configs.size() >= 4) value = (value << 8) + configs[3].getIntValue(); return "0x" + String::toHexString (value); } void writePluginCharacteristics (OutputStream& out) { String headerGuard ("__PLUGINCHARACTERISTICS_" + String::toHexString (pluginCharacteristicsFile.hashCode()).toUpperCase() + "__"); writeJucerComment (out); out << " This header file contains configuration options for the plug-in. If you need to change any of" << newLine << " these, it'd be wise to do so using the Jucer, rather than editing this file directly..." << newLine << newLine << "*/" << newLine << newLine << "#ifndef " << headerGuard << newLine << "#define " << headerGuard << newLine << newLine << "#define JucePlugin_Build_VST " << ((bool) project.shouldBuildVST().getValue() ? 1 : 0) << " // (If you change this value, you'll also need to re-export the projects using the Jucer)" << newLine << "#define JucePlugin_Build_AU " << ((bool) project.shouldBuildAU().getValue() ? 1 : 0) << " // (If you change this value, you'll also need to re-export the projects using the Jucer)" << newLine << "#define JucePlugin_Build_RTAS " << ((bool) project.shouldBuildRTAS().getValue() ? 1 : 0) << " // (If you change this value, you'll also need to re-export the projects using the Jucer)" << newLine << newLine << "#define JucePlugin_Name " << project.getPluginName().toString().quoted() << newLine << "#define JucePlugin_Desc " << project.getPluginDesc().toString().quoted() << newLine << "#define JucePlugin_Manufacturer " << project.getPluginManufacturer().toString().quoted() << newLine << "#define JucePlugin_ManufacturerCode '" << project.getPluginManufacturerCode().toString().trim().substring (0, 4) << "'" << newLine << "#define JucePlugin_PluginCode '" << project.getPluginCode().toString().trim().substring (0, 4) << "'" << newLine << "#define JucePlugin_MaxNumInputChannels " << countMaxPluginChannels (project.getPluginChannelConfigs().toString(), true) << newLine << "#define JucePlugin_MaxNumOutputChannels " << countMaxPluginChannels (project.getPluginChannelConfigs().toString(), false) << newLine << "#define JucePlugin_PreferredChannelConfigurations " << project.getPluginChannelConfigs().toString() << newLine << "#define JucePlugin_IsSynth " << ((bool) project.getPluginIsSynth().getValue() ? 1 : 0) << newLine << "#define JucePlugin_WantsMidiInput " << ((bool) project.getPluginWantsMidiInput().getValue() ? 1 : 0) << newLine << "#define JucePlugin_ProducesMidiOutput " << ((bool) project.getPluginProducesMidiOut().getValue() ? 1 : 0) << newLine << "#define JucePlugin_SilenceInProducesSilenceOut " << ((bool) project.getPluginSilenceInProducesSilenceOut().getValue() ? 1 : 0) << newLine << "#define JucePlugin_TailLengthSeconds " << (double) project.getPluginTailLengthSeconds().getValue() << newLine << "#define JucePlugin_EditorRequiresKeyboardFocus " << ((bool) project.getPluginEditorNeedsKeyFocus().getValue() ? 1 : 0) << newLine << "#define JucePlugin_VersionCode " << createVersionCode (project.getVersion().toString()) << newLine << "#define JucePlugin_VersionString " << project.getVersion().toString().quoted() << newLine << "#define JucePlugin_VSTUniqueID JucePlugin_PluginCode" << newLine << "#define JucePlugin_VSTCategory " << ((bool) project.getPluginIsSynth().getValue() ? "kPlugCategSynth" : "kPlugCategEffect") << newLine << "#define JucePlugin_AUMainType " << ((bool) project.getPluginIsSynth().getValue() ? "kAudioUnitType_MusicDevice" : "kAudioUnitType_Effect") << newLine << "#define JucePlugin_AUSubType JucePlugin_PluginCode" << newLine << "#define JucePlugin_AUExportPrefix " << project.getPluginAUExportPrefix().toString() << newLine << "#define JucePlugin_AUExportPrefixQuoted " << project.getPluginAUExportPrefix().toString().quoted() << newLine << "#define JucePlugin_AUManufacturerCode JucePlugin_ManufacturerCode" << newLine << "#define JucePlugin_CFBundleIdentifier " << project.getBundleIdentifier().toString() << newLine << "#define JucePlugin_AUCocoaViewClassName " << project.getPluginAUCocoaViewClassName().toString() << newLine << "#define JucePlugin_RTASCategory " << ((bool) project.getPluginIsSynth().getValue() ? "ePlugInCategory_SWGenerators" : "ePlugInCategory_None") << newLine << "#define JucePlugin_RTASManufacturerCode JucePlugin_ManufacturerCode" << newLine << "#define JucePlugin_RTASProductId JucePlugin_PluginCode" << newLine; out << "#define JUCE_USE_VSTSDK_2_4 1" << newLine << newLine << "#endif // " << headerGuard << newLine; } bool replaceFileIfDifferent (const File& f, const MemoryOutputStream& newData) { if (! FileHelpers::overwriteFileWithNewDataIfDifferent (f, newData)) { errors.add ("Can't write to file: " + f.getFullPathName()); return false; } return true; } void writeJuceSourceWrappers() { const File wrapperFolder (project.getWrapperFolder()); appConfigFile = wrapperFolder.getChildFile (project.getAppConfigFilename()); pluginCharacteristicsFile = wrapperFolder.getChildFile (project.getPluginCharacteristicsFilename()); juceHeaderFile = project.getAppIncludeFile(); binaryDataCpp = wrapperFolder.getChildFile ("BinaryData.cpp"); if (resourceFile.getNumFiles() > 0) { if (! wrapperFolder.createDirectory()) { errors.add ("Couldn't create folder: " + wrapperFolder.getFullPathName()); return; } //resourceFile.setJuceHeaderToInclude (juceHeaderFile); resourceFile.setClassName ("BinaryData"); if (! resourceFile.write (binaryDataCpp)) errors.add ("Can't create binary resources file: " + binaryDataCpp.getFullPathName()); } else { binaryDataCpp.deleteFile(); binaryDataCpp.withFileExtension ("h").deleteFile(); } if (project.isLibrary()) return; if (! wrapperFolder.createDirectory()) { errors.add ("Couldn't create folder: " + wrapperFolder.getFullPathName()); return; } if (hasAppConfigFile) { MemoryOutputStream mem; writeAppConfig (mem); replaceFileIfDifferent (appConfigFile, mem); } else { appConfigFile.deleteFile(); } if (project.isAudioPlugin()) { MemoryOutputStream mem; writePluginCharacteristics (mem); replaceFileIfDifferent (pluginCharacteristicsFile, mem); } for (int i = 0; i <= project.getNumSeparateAmalgamatedFiles(); ++i) { const File sourceWrapperCpp (getSourceWrapperCpp (i)); const File sourceWrapperMM (sourceWrapperCpp.withFileExtension (".mm")); if (numJuceSourceFiles > 0 && ((i == 0 && numJuceSourceFiles == 1) || (i != 0 && numJuceSourceFiles > 1))) { MemoryOutputStream mem; writeSourceWrapper (mem, i); replaceFileIfDifferent (sourceWrapperCpp, mem); replaceFileIfDifferent (sourceWrapperMM, mem); } else { sourceWrapperMM.deleteFile(); sourceWrapperCpp.deleteFile(); } } if (hasAppHeaderFile) { MemoryOutputStream mem; writeAppHeader (mem); replaceFileIfDifferent (juceHeaderFile, mem); } else { juceHeaderFile.deleteFile(); } } void writeProjects() { for (int i = project.getNumExporters(); --i >= 0;) { ScopedPointer exporter (project.createExporter (i)); std::cout << "Writing files for: " << exporter->getName() << std::endl; const File targetFolder (exporter->getTargetFolder()); if (targetFolder.createDirectory()) { exporter->juceWrapperFolder = RelativePath (project.getWrapperFolder(), targetFolder, RelativePath::buildTargetFolder); if (hasAppConfigFile) exporter->juceWrapperFiles.add (RelativePath (appConfigFile, targetFolder, RelativePath::buildTargetFolder)); if (hasAppHeaderFile) exporter->juceWrapperFiles.add (RelativePath (juceHeaderFile, targetFolder, RelativePath::buildTargetFolder)); if (hasResources) { exporter->juceWrapperFiles.add (RelativePath (binaryDataCpp, targetFolder, RelativePath::buildTargetFolder)); exporter->juceWrapperFiles.add (RelativePath (binaryDataCpp, targetFolder, RelativePath::buildTargetFolder) .withFileExtension (".h")); } if (numJuceSourceFiles > 0) { for (int j = 0; j <= project.getNumSeparateAmalgamatedFiles(); ++j) { const File sourceWrapperCpp (getSourceWrapperCpp (j)); const File sourceWrapperMM (sourceWrapperCpp.withFileExtension (".mm")); if ((j == 0 && numJuceSourceFiles == 1) || (j != 0 && numJuceSourceFiles > 1)) { if (exporter->usesMMFiles()) exporter->juceWrapperFiles.add (RelativePath (sourceWrapperMM, targetFolder, RelativePath::buildTargetFolder)); else exporter->juceWrapperFiles.add (RelativePath (sourceWrapperCpp, targetFolder, RelativePath::buildTargetFolder)); } } } if (project.isAudioPlugin()) exporter->juceWrapperFiles.add (RelativePath (pluginCharacteristicsFile, targetFolder, RelativePath::buildTargetFolder)); try { exporter->create(); } catch (ProjectExporter::SaveError& error) { errors.add (error.message); } } else { errors.add ("Can't create folder: " + exporter->getTargetFolder().getFullPathName()); } } } const File getSourceWrapperCpp (int fileIndex) const { return project.getWrapperFolder().getChildFile (project.getJuceSourceFilenameRoot() + (fileIndex != 0 ? String (fileIndex) : String::empty)) .withFileExtension (".cpp"); } JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ProjectSaver); }; #endif