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 //==============================================================================
34 ProjectSaver (Project
& project_
, const File
& projectFile_
)
35 : project (project_
), projectFile (projectFile_
), resourceFile (project_
)
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;
76 hasResources
= (resourceFile
.getNumFiles() > 0);
78 writeMainProjectFile();
80 if (errors
.size() == 0)
81 writeJuceSourceWrappers();
83 if (errors
.size() == 0)
86 if (errors
.size() > 0)
87 project
.setFile (oldFile
);
94 const File
& projectFile
;
95 ResourceFile resourceFile
;
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);
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));
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
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
145 << " Any commented-out settings will fall back to using the default values that" << newLine
146 << " they are given in juce_Config.h" << newLine
148 << "*/" << newLine
<< newLine
;
150 bool notActive
= project
.getJuceLinkageMode() == Project::useLinkedJuce
151 || project
.getJuceLinkageMode() == Project::notLinkedToJuce
;
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
)
172 if (value
== Project::configFlagEnabled
)
174 else if (value
== Project::configFlagDisabled
)
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
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
194 << newLine
<< newLine
195 << CodeHelpers::createIncludeStatement (appConfigFile
, appConfigFile
) << newLine
;
198 writeInclude (out
, project
.isUsingFullyAmalgamatedFile() ? "juce_amalgamated.cpp"
199 : "amalgamation/juce_amalgamated_template.cpp");
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
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"
230 if (binaryDataCpp
.exists())
231 out
<< CodeHelpers::createIncludeStatement (binaryDataCpp
.withFileExtension (".h"), appConfigFile
) << newLine
;
234 << "namespace ProjectInfo" << 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
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
;
268 int i
= paths
.size();
271 for (int j
= i
; --j
>= 0;)
273 if (paths
[i
] == paths
[j
] && guards
[i
] == guards
[j
])
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
)
294 configs
.addTokens (configString
, ", {}", String::empty
);
296 configs
.removeEmptyStrings();
297 jassert ((configs
.size() & 1) == 0); // looks like a syntax error in the configs?
300 for (int i
= (isInput
? 0 : 1); i
< configs
.size(); i
+= 2)
301 maxVal
= jmax (maxVal
, configs
[i
].getIntValue());
306 static const String
createVersionCode (const String
& version
)
309 configs
.addTokens (version
, ",.", String::empty
);
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
331 << "#ifndef " << headerGuard
<< newLine
332 << "#define " << headerGuard
<< 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
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
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());
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());
401 //resourceFile.setJuceHeaderToInclude (juceHeaderFile);
402 resourceFile
.setClassName ("BinaryData");
404 if (! resourceFile
.write (binaryDataCpp
))
405 errors
.add ("Can't create binary resources file: " + binaryDataCpp
.getFullPathName());
409 binaryDataCpp
.deleteFile();
410 binaryDataCpp
.withFileExtension ("h").deleteFile();
413 if (project
.isLibrary())
416 if (! wrapperFolder
.createDirectory())
418 errors
.add ("Couldn't create folder: " + wrapperFolder
.getFullPathName());
422 if (hasAppConfigFile
)
424 MemoryOutputStream mem
;
425 writeAppConfig (mem
);
426 replaceFileIfDifferent (appConfigFile
, mem
);
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
);
455 sourceWrapperMM
.deleteFile();
456 sourceWrapperCpp
.deleteFile();
460 if (hasAppHeaderFile
)
462 MemoryOutputStream mem
;
463 writeAppHeader (mem
);
464 replaceFileIfDifferent (juceHeaderFile
, mem
);
468 juceHeaderFile
.deleteFile();
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
));
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
));
510 exporter
->juceWrapperFiles
.add (RelativePath (sourceWrapperCpp
, targetFolder
, RelativePath::buildTargetFolder
));
515 if (project
.isAudioPlugin())
516 exporter
->juceWrapperFiles
.add (RelativePath (pluginCharacteristicsFile
, targetFolder
, RelativePath::buildTargetFolder
));
522 catch (ProjectExporter::SaveError
& error
)
524 errors
.add (error
.message
);
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
);