Add remaining files
[juce-lv2.git] / juce / source / extras / Introjucer / Source / Project / jucer_ProjectSaver.h
blobd483b4de33d6862f1f374d96c0767575fddf8788
1 /*
2 ==============================================================================
4 This file is part of the JUCE library - "Jules' Utility Class Extensions"
5 Copyright 2004-10 by Raw Material Software Ltd.
7 ------------------------------------------------------------------------------
9 JUCE can be redistributed and/or modified under the terms of the GNU General
10 Public License (Version 2), as published by the Free Software Foundation.
11 A copy of the license is included in the JUCE distribution, or can be found
12 online at www.gnu.org/licenses.
14 JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
15 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
16 A PARTICULAR PURPOSE. See the GNU General Public License for more details.
18 ------------------------------------------------------------------------------
20 To release a closed-source product which uses JUCE, commercial licenses are
21 available: visit www.rawmaterialsoftware.com/juce for more information.
23 ==============================================================================
26 #ifndef __JUCER_PROJECTSAVER_JUCEHEADER__
27 #define __JUCER_PROJECTSAVER_JUCEHEADER__
30 //==============================================================================
31 class ProjectSaver
33 public:
34 ProjectSaver (Project& project_, const File& projectFile_)
35 : project (project_), projectFile (projectFile_), resourceFile (project_)
39 const String save()
41 const File oldFile (project.getFile());
42 project.setFile (projectFile);
44 const String linkageMode (project.getJuceLinkageMode());
46 if (linkageMode == Project::notLinkedToJuce)
48 hasAppHeaderFile = ! project.isLibrary();
49 hasAppConfigFile = false;
50 numJuceSourceFiles = 0;
52 else if (linkageMode == Project::useAmalgamatedJuce
53 || linkageMode == Project::useAmalgamatedJuceViaSingleTemplate)
55 hasAppHeaderFile = true;
56 hasAppConfigFile = true;
57 numJuceSourceFiles = 1;
59 else if (linkageMode == Project::useAmalgamatedJuceViaMultipleTemplates)
61 hasAppHeaderFile = true;
62 hasAppConfigFile = true;
63 numJuceSourceFiles = project.getNumSeparateAmalgamatedFiles();
65 else if (linkageMode == Project::useLinkedJuce)
67 hasAppHeaderFile = true;
68 hasAppConfigFile = true;
69 numJuceSourceFiles = 0;
71 else
73 jassertfalse;
76 hasResources = (resourceFile.getNumFiles() > 0);
78 writeMainProjectFile();
80 if (errors.size() == 0)
81 writeJuceSourceWrappers();
83 if (errors.size() == 0)
84 writeProjects();
86 if (errors.size() > 0)
87 project.setFile (oldFile);
89 return errors[0];
92 private:
93 Project& project;
94 const File& projectFile;
95 ResourceFile resourceFile;
96 StringArray errors;
98 File appConfigFile, juceHeaderFile, binaryDataCpp, pluginCharacteristicsFile;
99 bool hasAppHeaderFile, hasAppConfigFile, hasResources;
100 int numJuceSourceFiles;
102 void writeMainProjectFile()
104 ScopedPointer <XmlElement> xml (project.getProjectRoot().createXml());
105 jassert (xml != nullptr);
107 if (xml != nullptr)
109 #if JUCE_DEBUG
111 MemoryOutputStream mo;
112 project.getProjectRoot().writeToStream (mo);
114 MemoryInputStream mi (mo.getData(), mo.getDataSize(), false);
115 ValueTree v = ValueTree::readFromStream (mi);
116 ScopedPointer <XmlElement> xml2 (v.createXml());
118 // This bit just tests that ValueTree save/load works reliably.. Let me know if this asserts for you!
119 jassert (xml->isEquivalentTo (xml2, true));
121 #endif
123 MemoryOutputStream mo;
124 xml->writeToStream (mo, String::empty);
126 if (! FileHelpers::overwriteFileWithNewDataIfDifferent (projectFile, mo))
127 errors.add ("Couldn't write to the target file!");
131 void writeJucerComment (OutputStream& out)
133 out << "/*" << newLine << newLine
134 << " IMPORTANT! This file is auto-generated by the Jucer each time you save your" << newLine
135 << " project - if you alter its contents, your changes may be overwritten!" << newLine
136 << newLine;
139 void writeAppConfig (OutputStream& out)
141 writeJucerComment (out);
142 out << " If you want to change any of these values, use the Jucer to do so, rather than" << newLine
143 << " editing this file directly!" << newLine
144 << newLine
145 << " Any commented-out settings will fall back to using the default values that" << newLine
146 << " they are given in juce_Config.h" << newLine
147 << newLine
148 << "*/" << newLine << newLine;
150 bool notActive = project.getJuceLinkageMode() == Project::useLinkedJuce
151 || project.getJuceLinkageMode() == Project::notLinkedToJuce;
152 if (notActive)
153 out << "/* NOTE: These configs aren't available when you're linking to the juce library statically!" << newLine
154 << " If you need to set a configuration that differs from the default, you'll need" << newLine
155 << " to include the amalgamated Juce files." << newLine << newLine;
157 OwnedArray <Project::JuceConfigFlag> flags;
158 project.getJuceConfigFlags (flags);
160 for (int i = 0; i < flags.size(); ++i)
162 const Project::JuceConfigFlag* const f = flags[i];
163 const String value (f->value.toString());
165 if (value != Project::configFlagEnabled && value != Project::configFlagDisabled)
166 out << "//#define ";
167 else
168 out << "#define ";
170 out << f->symbol;
172 if (value == Project::configFlagEnabled)
173 out << " 1";
174 else if (value == Project::configFlagDisabled)
175 out << " 0";
177 out << newLine;
180 if (notActive)
181 out << newLine << "*/" << newLine;
184 void writeSourceWrapper (OutputStream& out, int fileNumber)
186 writeJucerComment (out);
187 out << " This file pulls in all the Juce source code, and builds it using the settings" << newLine
188 << " defined in " << appConfigFile.getFileName() << "." << newLine
189 << newLine
190 << " If you want to change the method by which Juce is linked into your app, use the" << newLine
191 << " Jucer to change it, rather than trying to edit this file directly." << newLine
192 << newLine
193 << "*/"
194 << newLine << newLine
195 << CodeHelpers::createIncludeStatement (appConfigFile, appConfigFile) << newLine;
197 if (fileNumber == 0)
198 writeInclude (out, project.isUsingFullyAmalgamatedFile() ? "juce_amalgamated.cpp"
199 : "amalgamation/juce_amalgamated_template.cpp");
200 else
201 writeInclude (out, "amalgamation/juce_amalgamated" + String (fileNumber) + ".cpp");
204 void writeAppHeader (OutputStream& out)
206 writeJucerComment (out);
207 out << " This is the header file that your files should include in order to get all the" << newLine
208 << " Juce library headers. You should NOT include juce.h or juce_amalgamated.h directly in" << newLine
209 << " your own source files, because that wouldn't pick up the correct Juce configuration" << newLine
210 << " options for your app." << newLine
211 << newLine
212 << "*/" << newLine << newLine;
214 String headerGuard ("__APPHEADERFILE_" + String::toHexString (juceHeaderFile.hashCode()).toUpperCase() + "__");
215 out << "#ifndef " << headerGuard << newLine
216 << "#define " << headerGuard << newLine << newLine;
218 if (hasAppConfigFile)
219 out << CodeHelpers::createIncludeStatement (appConfigFile, appConfigFile) << newLine;
221 if (project.getJuceLinkageMode() != Project::notLinkedToJuce)
223 writeInclude (out, (project.isUsingSingleTemplateFile() || project.isUsingMultipleTemplateFiles())
224 ? "juce_amalgamated.h" // could use "amalgamation/juce_amalgamated_template.h", but it's slower..
225 : (project.isUsingFullyAmalgamatedFile()
226 ? "juce_amalgamated.h"
227 : "juce.h"));
230 if (binaryDataCpp.exists())
231 out << CodeHelpers::createIncludeStatement (binaryDataCpp.withFileExtension (".h"), appConfigFile) << newLine;
233 out << newLine
234 << "namespace ProjectInfo" << newLine
235 << "{" << newLine
236 << " const char* const projectName = " << CodeHelpers::addEscapeChars (project.getProjectName().toString()).quoted() << ";" << newLine
237 << " const char* const versionString = " << CodeHelpers::addEscapeChars (project.getVersion().toString()).quoted() << ";" << newLine
238 << " const int versionNumber = " << createVersionCode (project.getVersion().toString()) << ";" << newLine
239 << "}" << newLine
240 << newLine
241 << "#endif // " << headerGuard << newLine;
244 void writeInclude (OutputStream& out, const String& pathFromJuceFolder)
246 StringArray paths, guards;
248 for (int i = project.getNumExporters(); --i >= 0;)
250 ScopedPointer <ProjectExporter> exporter (project.createExporter (i));
252 if (exporter != nullptr)
254 paths.add (exporter->getIncludePathForFileInJuceFolder (pathFromJuceFolder, juceHeaderFile));
255 guards.add ("defined (" + exporter->getExporterIdentifierMacro() + ")");
259 StringArray uniquePaths (paths);
260 uniquePaths.removeDuplicates (false);
262 if (uniquePaths.size() == 1)
264 out << "#include " << paths[0] << newLine;
266 else
268 int i = paths.size();
269 for (; --i >= 0;)
271 for (int j = i; --j >= 0;)
273 if (paths[i] == paths[j] && guards[i] == guards[j])
275 paths.remove (i);
276 guards.remove (i);
281 for (i = 0; i < paths.size(); ++i)
283 out << (i == 0 ? "#if " : "#elif ") << guards[i] << newLine
284 << " #include " << paths[i] << newLine;
287 out << "#endif" << newLine;
291 static int countMaxPluginChannels (const String& configString, bool isInput)
293 StringArray configs;
294 configs.addTokens (configString, ", {}", String::empty);
295 configs.trim();
296 configs.removeEmptyStrings();
297 jassert ((configs.size() & 1) == 0); // looks like a syntax error in the configs?
299 int maxVal = 0;
300 for (int i = (isInput ? 0 : 1); i < configs.size(); i += 2)
301 maxVal = jmax (maxVal, configs[i].getIntValue());
303 return maxVal;
306 static const String createVersionCode (const String& version)
308 StringArray configs;
309 configs.addTokens (version, ",.", String::empty);
310 configs.trim();
311 configs.removeEmptyStrings();
313 int value = (configs[0].getIntValue() << 16) + (configs[1].getIntValue() << 8) + configs[2].getIntValue();
315 if (configs.size() >= 4)
316 value = (value << 8) + configs[3].getIntValue();
318 return "0x" + String::toHexString (value);
321 void writePluginCharacteristics (OutputStream& out)
323 String headerGuard ("__PLUGINCHARACTERISTICS_" + String::toHexString (pluginCharacteristicsFile.hashCode()).toUpperCase() + "__");
325 writeJucerComment (out);
326 out << " This header file contains configuration options for the plug-in. If you need to change any of" << newLine
327 << " these, it'd be wise to do so using the Jucer, rather than editing this file directly..." << newLine
328 << newLine
329 << "*/" << newLine
330 << newLine
331 << "#ifndef " << headerGuard << newLine
332 << "#define " << headerGuard << newLine
333 << newLine
334 << "#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
335 << "#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
336 << "#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
337 << newLine
338 << "#define JucePlugin_Name " << project.getPluginName().toString().quoted() << newLine
339 << "#define JucePlugin_Desc " << project.getPluginDesc().toString().quoted() << newLine
340 << "#define JucePlugin_Manufacturer " << project.getPluginManufacturer().toString().quoted() << newLine
341 << "#define JucePlugin_ManufacturerCode '" << project.getPluginManufacturerCode().toString().trim().substring (0, 4) << "'" << newLine
342 << "#define JucePlugin_PluginCode '" << project.getPluginCode().toString().trim().substring (0, 4) << "'" << newLine
343 << "#define JucePlugin_MaxNumInputChannels " << countMaxPluginChannels (project.getPluginChannelConfigs().toString(), true) << newLine
344 << "#define JucePlugin_MaxNumOutputChannels " << countMaxPluginChannels (project.getPluginChannelConfigs().toString(), false) << newLine
345 << "#define JucePlugin_PreferredChannelConfigurations " << project.getPluginChannelConfigs().toString() << newLine
346 << "#define JucePlugin_IsSynth " << ((bool) project.getPluginIsSynth().getValue() ? 1 : 0) << newLine
347 << "#define JucePlugin_WantsMidiInput " << ((bool) project.getPluginWantsMidiInput().getValue() ? 1 : 0) << newLine
348 << "#define JucePlugin_ProducesMidiOutput " << ((bool) project.getPluginProducesMidiOut().getValue() ? 1 : 0) << newLine
349 << "#define JucePlugin_SilenceInProducesSilenceOut " << ((bool) project.getPluginSilenceInProducesSilenceOut().getValue() ? 1 : 0) << newLine
350 << "#define JucePlugin_TailLengthSeconds " << (double) project.getPluginTailLengthSeconds().getValue() << newLine
351 << "#define JucePlugin_EditorRequiresKeyboardFocus " << ((bool) project.getPluginEditorNeedsKeyFocus().getValue() ? 1 : 0) << newLine
352 << "#define JucePlugin_VersionCode " << createVersionCode (project.getVersion().toString()) << newLine
353 << "#define JucePlugin_VersionString " << project.getVersion().toString().quoted() << newLine
354 << "#define JucePlugin_VSTUniqueID JucePlugin_PluginCode" << newLine
355 << "#define JucePlugin_VSTCategory " << ((bool) project.getPluginIsSynth().getValue() ? "kPlugCategSynth" : "kPlugCategEffect") << newLine
356 << "#define JucePlugin_AUMainType " << ((bool) project.getPluginIsSynth().getValue() ? "kAudioUnitType_MusicDevice" : "kAudioUnitType_Effect") << newLine
357 << "#define JucePlugin_AUSubType JucePlugin_PluginCode" << newLine
358 << "#define JucePlugin_AUExportPrefix " << project.getPluginAUExportPrefix().toString() << newLine
359 << "#define JucePlugin_AUExportPrefixQuoted " << project.getPluginAUExportPrefix().toString().quoted() << newLine
360 << "#define JucePlugin_AUManufacturerCode JucePlugin_ManufacturerCode" << newLine
361 << "#define JucePlugin_CFBundleIdentifier " << project.getBundleIdentifier().toString() << newLine
362 << "#define JucePlugin_AUCocoaViewClassName " << project.getPluginAUCocoaViewClassName().toString() << newLine
363 << "#define JucePlugin_RTASCategory " << ((bool) project.getPluginIsSynth().getValue() ? "ePlugInCategory_SWGenerators" : "ePlugInCategory_None") << newLine
364 << "#define JucePlugin_RTASManufacturerCode JucePlugin_ManufacturerCode" << newLine
365 << "#define JucePlugin_RTASProductId JucePlugin_PluginCode" << newLine;
367 out << "#define JUCE_USE_VSTSDK_2_4 1" << newLine
368 << newLine
369 << "#endif // " << headerGuard << newLine;
372 bool replaceFileIfDifferent (const File& f, const MemoryOutputStream& newData)
374 if (! FileHelpers::overwriteFileWithNewDataIfDifferent (f, newData))
376 errors.add ("Can't write to file: " + f.getFullPathName());
377 return false;
380 return true;
383 void writeJuceSourceWrappers()
385 const File wrapperFolder (project.getWrapperFolder());
387 appConfigFile = wrapperFolder.getChildFile (project.getAppConfigFilename());
388 pluginCharacteristicsFile = wrapperFolder.getChildFile (project.getPluginCharacteristicsFilename());
390 juceHeaderFile = project.getAppIncludeFile();
391 binaryDataCpp = wrapperFolder.getChildFile ("BinaryData.cpp");
393 if (resourceFile.getNumFiles() > 0)
395 if (! wrapperFolder.createDirectory())
397 errors.add ("Couldn't create folder: " + wrapperFolder.getFullPathName());
398 return;
401 //resourceFile.setJuceHeaderToInclude (juceHeaderFile);
402 resourceFile.setClassName ("BinaryData");
404 if (! resourceFile.write (binaryDataCpp))
405 errors.add ("Can't create binary resources file: " + binaryDataCpp.getFullPathName());
407 else
409 binaryDataCpp.deleteFile();
410 binaryDataCpp.withFileExtension ("h").deleteFile();
413 if (project.isLibrary())
414 return;
416 if (! wrapperFolder.createDirectory())
418 errors.add ("Couldn't create folder: " + wrapperFolder.getFullPathName());
419 return;
422 if (hasAppConfigFile)
424 MemoryOutputStream mem;
425 writeAppConfig (mem);
426 replaceFileIfDifferent (appConfigFile, mem);
428 else
430 appConfigFile.deleteFile();
433 if (project.isAudioPlugin())
435 MemoryOutputStream mem;
436 writePluginCharacteristics (mem);
437 replaceFileIfDifferent (pluginCharacteristicsFile, mem);
440 for (int i = 0; i <= project.getNumSeparateAmalgamatedFiles(); ++i)
442 const File sourceWrapperCpp (getSourceWrapperCpp (i));
443 const File sourceWrapperMM (sourceWrapperCpp.withFileExtension (".mm"));
445 if (numJuceSourceFiles > 0
446 && ((i == 0 && numJuceSourceFiles == 1) || (i != 0 && numJuceSourceFiles > 1)))
448 MemoryOutputStream mem;
449 writeSourceWrapper (mem, i);
450 replaceFileIfDifferent (sourceWrapperCpp, mem);
451 replaceFileIfDifferent (sourceWrapperMM, mem);
453 else
455 sourceWrapperMM.deleteFile();
456 sourceWrapperCpp.deleteFile();
460 if (hasAppHeaderFile)
462 MemoryOutputStream mem;
463 writeAppHeader (mem);
464 replaceFileIfDifferent (juceHeaderFile, mem);
466 else
468 juceHeaderFile.deleteFile();
472 void writeProjects()
474 for (int i = project.getNumExporters(); --i >= 0;)
476 ScopedPointer <ProjectExporter> exporter (project.createExporter (i));
477 std::cout << "Writing files for: " << exporter->getName() << std::endl;
479 const File targetFolder (exporter->getTargetFolder());
481 if (targetFolder.createDirectory())
483 exporter->juceWrapperFolder = RelativePath (project.getWrapperFolder(), targetFolder, RelativePath::buildTargetFolder);
485 if (hasAppConfigFile)
486 exporter->juceWrapperFiles.add (RelativePath (appConfigFile, targetFolder, RelativePath::buildTargetFolder));
488 if (hasAppHeaderFile)
489 exporter->juceWrapperFiles.add (RelativePath (juceHeaderFile, targetFolder, RelativePath::buildTargetFolder));
491 if (hasResources)
493 exporter->juceWrapperFiles.add (RelativePath (binaryDataCpp, targetFolder, RelativePath::buildTargetFolder));
494 exporter->juceWrapperFiles.add (RelativePath (binaryDataCpp, targetFolder, RelativePath::buildTargetFolder)
495 .withFileExtension (".h"));
498 if (numJuceSourceFiles > 0)
500 for (int j = 0; j <= project.getNumSeparateAmalgamatedFiles(); ++j)
502 const File sourceWrapperCpp (getSourceWrapperCpp (j));
503 const File sourceWrapperMM (sourceWrapperCpp.withFileExtension (".mm"));
505 if ((j == 0 && numJuceSourceFiles == 1) || (j != 0 && numJuceSourceFiles > 1))
507 if (exporter->usesMMFiles())
508 exporter->juceWrapperFiles.add (RelativePath (sourceWrapperMM, targetFolder, RelativePath::buildTargetFolder));
509 else
510 exporter->juceWrapperFiles.add (RelativePath (sourceWrapperCpp, targetFolder, RelativePath::buildTargetFolder));
515 if (project.isAudioPlugin())
516 exporter->juceWrapperFiles.add (RelativePath (pluginCharacteristicsFile, targetFolder, RelativePath::buildTargetFolder));
520 exporter->create();
522 catch (ProjectExporter::SaveError& error)
524 errors.add (error.message);
527 else
529 errors.add ("Can't create folder: " + exporter->getTargetFolder().getFullPathName());
534 const File getSourceWrapperCpp (int fileIndex) const
536 return project.getWrapperFolder().getChildFile (project.getJuceSourceFilenameRoot() + (fileIndex != 0 ? String (fileIndex) : String::empty))
537 .withFileExtension (".cpp");
540 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ProjectSaver);
544 #endif