1 /*
2 ==============================================================================
3
4 This file is part of the JUCE library.
5 Copyright (c) 2020 - Raw Material Software Limited
6
7 JUCE is an open source library subject to commercial or open-source
8 licensing.
9
10 By using JUCE, you agree to the terms of both the JUCE 6 End-User License
11 Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
12
13 End User License Agreement: www.juce.com/juce-6-licence
14 Privacy Policy: www.juce.com/juce-privacy-policy
15
16 Or: You may also use this code under the terms of the GPL v3 (see
17 www.gnu.org/licenses).
18
19 JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
20 EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
21 DISCLAIMED.
22
23 ==============================================================================
24 */
25
26 #include "jucer_ProjectSaver.h"
27 #include "jucer_ProjectExport_CLion.h"
28 #include "../Application/jucer_Application.h"
29
30 static constexpr const char* generatedGroupID = "__jucelibfiles";
31 static constexpr const char* generatedGroupUID = "__generatedcode__";
32
33 constexpr int jucerFormatVersion = 1;
34
35 //==============================================================================
ProjectSaver(Project & p)36 ProjectSaver::ProjectSaver (Project& p)
37 : project (p),
38 generatedCodeFolder (project.getGeneratedCodeFolder()),
39 generatedFilesGroup (Project::Item::createGroup (project, getJuceCodeGroupName(), generatedGroupUID, true)),
40 projectLineFeed (project.getProjectLineFeed())
41 {
42 generatedFilesGroup.setID (generatedGroupID);
43 }
44
save(ProjectExporter * exporterToSave)45 Result ProjectSaver::save (ProjectExporter* exporterToSave)
46 {
47 if (! ProjucerApplication::getApp().isRunningCommandLine)
48 {
49 SaveThreadWithProgressWindow thread (*this, exporterToSave);
50 thread.runThread();
51
52 return thread.result;
53 }
54
55 return saveProject (exporterToSave);
56 }
57
saveResourcesOnly()58 Result ProjectSaver::saveResourcesOnly()
59 {
60 writeBinaryDataFiles();
61
62 if (! errors.isEmpty())
63 return Result::fail (errors[0]);
64
65 return Result::ok();
66 }
67
saveBasicProjectItems(const OwnedArray<LibraryModule> & modules,const String & appConfigUserContent)68 void ProjectSaver::saveBasicProjectItems (const OwnedArray<LibraryModule>& modules, const String& appConfigUserContent)
69 {
70 writePluginDefines();
71 writeAppConfigFile (modules, appConfigUserContent);
72 writeBinaryDataFiles();
73 writeAppHeader (modules);
74 writeModuleCppWrappers (modules);
75 }
76
saveContentNeededForLiveBuild()77 Result ProjectSaver::saveContentNeededForLiveBuild()
78 {
79 auto modules = getModules();
80
81 if (errors.isEmpty())
82 {
83 saveBasicProjectItems (modules, loadUserContentFromAppConfig());
84 return Result::ok();
85 }
86
87 return Result::fail (errors[0]);
88 }
89
addFileToGeneratedGroup(const File & file)90 Project::Item ProjectSaver::addFileToGeneratedGroup (const File& file)
91 {
92 auto item = generatedFilesGroup.findItemForFile (file);
93
94 if (item.isValid())
95 return item;
96
97 generatedFilesGroup.addFileAtIndex (file, -1, true);
98 return generatedFilesGroup.findItemForFile (file);
99 }
100
copyFolder(const File & source,const File & dest)101 bool ProjectSaver::copyFolder (const File& source, const File& dest)
102 {
103 if (source.isDirectory() && dest.createDirectory())
104 {
105 for (auto& f : source.findChildFiles (File::findFiles, false))
106 {
107 auto target = dest.getChildFile (f.getFileName());
108 filesCreated.add (target);
109
110 if (! f.copyFileTo (target))
111 return false;
112 }
113
114 for (auto& f : source.findChildFiles (File::findDirectories, false))
115 {
116 auto name = f.getFileName();
117
118 if (name == ".git" || name == ".svn" || name == ".cvs")
119 continue;
120
121 if (! copyFolder (f, dest.getChildFile (f.getFileName())))
122 return false;
123 }
124
125 return true;
126 }
127
128 return false;
129 }
130
131 //==============================================================================
saveGeneratedFile(const String & filePath,const MemoryOutputStream & newData)132 Project::Item ProjectSaver::saveGeneratedFile (const String& filePath, const MemoryOutputStream& newData)
133 {
134 if (! generatedCodeFolder.createDirectory())
135 {
136 addError ("Couldn't create folder: " + generatedCodeFolder.getFullPathName());
137 return Project::Item (project, {}, false);
138 }
139
140 auto file = generatedCodeFolder.getChildFile (filePath);
141
142 if (replaceFileIfDifferent (file, newData))
143 return addFileToGeneratedGroup (file);
144
145 return { project, {}, true };
146 }
147
replaceFileIfDifferent(const File & f,const MemoryOutputStream & newData)148 bool ProjectSaver::replaceFileIfDifferent (const File& f, const MemoryOutputStream& newData)
149 {
150 filesCreated.add (f);
151
152 if (! build_tools::overwriteFileWithNewDataIfDifferent (f, newData))
153 {
154 addError ("Can't write to file: " + f.getFullPathName());
155 return false;
156 }
157
158 return true;
159 }
160
deleteUnwantedFilesIn(const File & parent)161 bool ProjectSaver::deleteUnwantedFilesIn (const File& parent)
162 {
163 // Recursively clears out any files in a folder that we didn't create, but avoids
164 // any folders containing hidden files that might be used by version-control systems.
165 auto shouldFileBeKept = [] (const String& filename)
166 {
167 static StringArray filesToKeep (".svn", ".cvs", "CMakeLists.txt");
168 return filesToKeep.contains (filename);
169 };
170
171 bool folderIsNowEmpty = true;
172 Array<File> filesToDelete;
173
174 for (const auto& i : RangedDirectoryIterator (parent, false, "*", File::findFilesAndDirectories))
175 {
176 auto f = i.getFile();
177
178 if (filesCreated.contains (f) || shouldFileBeKept (f.getFileName()))
179 {
180 folderIsNowEmpty = false;
181 }
182 else if (i.isDirectory())
183 {
184 if (deleteUnwantedFilesIn (f))
185 filesToDelete.add (f);
186 else
187 folderIsNowEmpty = false;
188 }
189 else
190 {
191 filesToDelete.add (f);
192 }
193 }
194
195 for (int j = filesToDelete.size(); --j >= 0;)
196 filesToDelete.getReference (j).deleteRecursively();
197
198 return folderIsNowEmpty;
199 }
200
201 //==============================================================================
addError(const String & message)202 void ProjectSaver::addError (const String& message)
203 {
204 const ScopedLock sl (errorLock);
205 errors.add (message);
206 }
207
208 //==============================================================================
getAppConfigFile() const209 File ProjectSaver::getAppConfigFile() const
210 {
211 return generatedCodeFolder.getChildFile (Project::getAppConfigFilename());
212 }
213
getPluginDefinesFile() const214 File ProjectSaver::getPluginDefinesFile() const
215 {
216 return generatedCodeFolder.getChildFile (Project::getPluginDefinesFilename());
217 }
218
loadUserContentFromAppConfig() const219 String ProjectSaver::loadUserContentFromAppConfig() const
220 {
221 StringArray userContent;
222 bool foundCodeSection = false;
223 auto lines = StringArray::fromLines (getAppConfigFile().loadFileAsString());
224
225 for (int i = 0; i < lines.size(); ++i)
226 {
227 if (lines[i].contains ("[BEGIN_USER_CODE_SECTION]"))
228 {
229 for (int j = i + 1; j < lines.size() && ! lines[j].contains ("[END_USER_CODE_SECTION]"); ++j)
230 userContent.add (lines[j]);
231
232 foundCodeSection = true;
233 break;
234 }
235 }
236
237 if (! foundCodeSection)
238 {
239 userContent.add ({});
240 userContent.add ("// (You can add your own code in this section, and the Projucer will not overwrite it)");
241 userContent.add ({});
242 }
243
244 return userContent.joinIntoString (projectLineFeed) + projectLineFeed;
245 }
246
247 //==============================================================================
getModules()248 OwnedArray<LibraryModule> ProjectSaver::getModules()
249 {
250 OwnedArray<LibraryModule> modules;
251 project.getEnabledModules().createRequiredModules (modules);
252
253 auto isCommandLine = ProjucerApplication::getApp().isRunningCommandLine;
254
255 for (auto* module : modules)
256 {
257 if (! module->isValid())
258 {
259 addError (String ("At least one of your JUCE module paths is invalid!\n")
260 + (isCommandLine ? "Please ensure each module path points to the correct JUCE modules folder."
261 : "Please go to the Modules settings page and ensure each path points to the correct JUCE modules folder."));
262
263 return {};
264 }
265
266 if (project.getEnabledModules().getExtraDependenciesNeeded (module->getID()).size() > 0)
267 {
268 addError (String ("At least one of your modules has missing dependencies!\n")
269 + (isCommandLine ? "Please add the required dependencies, or run the command again with the \"--fix-missing-dependencies\" option."
270 : "Please go to the settings page of the highlighted modules and add the required dependencies."));
271
272 return {};
273 }
274 }
275
276 return modules;
277 }
278
279 //==============================================================================
saveProject(ProjectExporter * specifiedExporterToSave)280 Result ProjectSaver::saveProject (ProjectExporter* specifiedExporterToSave)
281 {
282 if (project.getNumExporters() == 0)
283 {
284 return Result::fail ("No exporters found!\n"
285 "Please add an exporter before saving.");
286 }
287
288 auto oldProjectFile = project.getFile();
289 auto modules = getModules();
290
291 if (errors.isEmpty())
292 {
293 if (project.isAudioPluginProject())
294 {
295 if (project.shouldBuildUnityPlugin())
296 writeUnityScriptFile();
297 }
298
299 saveBasicProjectItems (modules, loadUserContentFromAppConfig());
300 writeProjects (modules, specifiedExporterToSave);
301 writeProjectFile();
302
303 runPostExportScript();
304
305 if (generatedCodeFolder.exists())
306 {
307 writeReadmeFile();
308 deleteUnwantedFilesIn (generatedCodeFolder);
309 }
310
311 if (errors.isEmpty())
312 return Result::ok();
313 }
314
315 project.setFile (oldProjectFile);
316 return Result::fail (errors[0]);
317 }
318
319 //==============================================================================
writePluginDefines(MemoryOutputStream & out) const320 void ProjectSaver::writePluginDefines (MemoryOutputStream& out) const
321 {
322 const auto pluginDefines = getAudioPluginDefines();
323
324 if (pluginDefines.isEmpty())
325 return;
326
327 writeAutoGenWarningComment (out);
328
329 out << "*/" << newLine << newLine
330 << "#pragma once" << newLine << newLine
331 << pluginDefines << newLine;
332 }
333
writeProjectFile()334 void ProjectSaver::writeProjectFile()
335 {
336 auto root = project.getProjectRoot();
337
338 root.removeProperty ("jucerVersion", nullptr);
339
340 if ((int) root.getProperty (Ids::jucerFormatVersion, -1) != jucerFormatVersion)
341 root.setProperty (Ids::jucerFormatVersion, jucerFormatVersion, nullptr);
342
343 project.updateCachedFileState();
344
345 auto newSerialisedXml = project.serialiseProjectXml (root.createXml());
346 jassert (newSerialisedXml.isNotEmpty());
347
348 if (newSerialisedXml != project.getCachedFileStateContent())
349 {
350 project.getFile().replaceWithText (newSerialisedXml);
351 project.updateCachedFileState();
352 }
353 }
354
writeAppConfig(MemoryOutputStream & out,const OwnedArray<LibraryModule> & modules,const String & userContent)355 void ProjectSaver::writeAppConfig (MemoryOutputStream& out, const OwnedArray<LibraryModule>& modules, const String& userContent)
356 {
357 if (! project.shouldUseAppConfig())
358 return;
359
360 writeAutoGenWarningComment (out);
361
362 out << " There's a section below where you can add your own custom code safely, and the" << newLine
363 << " Projucer will preserve the contents of that block, but the best way to change" << newLine
364 << " any of these definitions is by using the Projucer's project settings." << newLine
365 << newLine
366 << " Any commented-out settings will assume their default values." << newLine
367 << newLine
368 << "*/" << newLine
369 << newLine;
370
371 out << "#pragma once" << newLine
372 << newLine
373 << "//==============================================================================" << newLine
374 << "// [BEGIN_USER_CODE_SECTION]" << newLine
375 << userContent
376 << "// [END_USER_CODE_SECTION]" << newLine;
377
378 if (getPluginDefinesFile().existsAsFile() && getAudioPluginDefines().isNotEmpty())
379 out << newLine << CodeHelpers::createIncludeStatement (Project::getPluginDefinesFilename()) << newLine;
380
381 out << newLine
382 << "/*" << newLine
383 << " ==============================================================================" << newLine
384 << newLine
385 << " In accordance with the terms of the JUCE 6 End-Use License Agreement, the" << newLine
386 << " JUCE Code in SECTION A cannot be removed, changed or otherwise rendered" << newLine
387 << " ineffective unless you have a JUCE Indie or Pro license, or are using JUCE" << newLine
388 << " under the GPL v3 license." << newLine
389 << newLine
390 << " End User License Agreement: www.juce.com/juce-6-licence" << newLine
391 << newLine
392 << " ==============================================================================" << newLine
393 << "*/" << newLine
394 << newLine
395 << "// BEGIN SECTION A" << newLine
396 << newLine
397 << "#ifndef JUCE_DISPLAY_SPLASH_SCREEN" << newLine
398 << " #define JUCE_DISPLAY_SPLASH_SCREEN " << (project.shouldDisplaySplashScreen() ? "1" : "0") << newLine
399 << "#endif" << newLine << newLine
400 << "// END SECTION A" << newLine
401 << newLine
402 << "#define JUCE_USE_DARK_SPLASH_SCREEN " << (project.getSplashScreenColourString() == "Dark" ? "1" : "0") << newLine
403 << newLine
404 << "#define JUCE_PROJUCER_VERSION 0x" << String::toHexString (ProjectInfo::versionNumber) << newLine;
405
406 out << newLine
407 << "//==============================================================================" << newLine;
408
409 auto longestModuleName = [&modules]()
410 {
411 int longest = 0;
412
413 for (auto* module : modules)
414 longest = jmax (longest, module->getID().length());
415
416 return longest;
417 }();
418
419 for (auto* module : modules)
420 {
421 out << "#define JUCE_MODULE_AVAILABLE_" << module->getID()
422 << String::repeatedString (" ", longestModuleName + 5 - module->getID().length()) << " 1" << newLine;
423 }
424
425 out << newLine << "#define JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED 1" << newLine;
426
427 for (auto* module : modules)
428 {
429 OwnedArray<Project::ConfigFlag> flags;
430 module->getConfigFlags (project, flags);
431
432 if (flags.size() > 0)
433 {
434 out << newLine
435 << "//==============================================================================" << newLine
436 << "// " << module->getID() << " flags:" << newLine;
437
438 for (auto* flag : flags)
439 {
440 out << newLine
441 << "#ifndef " << flag->symbol
442 << newLine
443 << (flag->value.isUsingDefault() ? " //#define " : " #define ") << flag->symbol << " " << (flag->value.get() ? "1" : "0")
444 << newLine
445 << "#endif"
446 << newLine;
447 }
448 }
449 }
450
451 auto& type = project.getProjectType();
452 auto isStandaloneApplication = (! type.isAudioPlugin() && ! type.isDynamicLibrary());
453
454 out << newLine
455 << "//==============================================================================" << newLine
456 << "#ifndef JUCE_STANDALONE_APPLICATION" << newLine
457 << " #if defined(JucePlugin_Name) && defined(JucePlugin_Build_Standalone)" << newLine
458 << " #define JUCE_STANDALONE_APPLICATION JucePlugin_Build_Standalone" << newLine
459 << " #else" << newLine
460 << " #define JUCE_STANDALONE_APPLICATION " << (isStandaloneApplication ? "1" : "0") << newLine
461 << " #endif" << newLine
462 << "#endif" << newLine;
463 }
464
465 template <typename WriterCallback>
writeOrRemoveGeneratedFile(const String & name,WriterCallback && writerCallback)466 void ProjectSaver::writeOrRemoveGeneratedFile (const String& name, WriterCallback&& writerCallback)
467 {
468 MemoryOutputStream mem;
469 mem.setNewLineString (projectLineFeed);
470
471 writerCallback (mem);
472
473 if (mem.getDataSize() != 0)
474 {
475 saveGeneratedFile (name, mem);
476 return;
477 }
478
479 const auto destFile = generatedCodeFolder.getChildFile (name);
480
481 if (destFile.existsAsFile())
482 {
483 if (! destFile.deleteFile())
484 addError ("Couldn't remove unnecessary file: " + destFile.getFullPathName());
485 }
486 }
487
writePluginDefines()488 void ProjectSaver::writePluginDefines()
489 {
490 writeOrRemoveGeneratedFile (Project::getPluginDefinesFilename(), [&] (MemoryOutputStream& mem)
491 {
492 writePluginDefines (mem);
493 });
494 }
495
writeAppConfigFile(const OwnedArray<LibraryModule> & modules,const String & userContent)496 void ProjectSaver::writeAppConfigFile (const OwnedArray<LibraryModule>& modules, const String& userContent)
497 {
498 writeOrRemoveGeneratedFile (Project::getAppConfigFilename(), [&] (MemoryOutputStream& mem)
499 {
500 writeAppConfig (mem, modules, userContent);
501 });
502 }
503
writeAppHeader(MemoryOutputStream & out,const OwnedArray<LibraryModule> & modules)504 void ProjectSaver::writeAppHeader (MemoryOutputStream& out, const OwnedArray<LibraryModule>& modules)
505 {
506 writeAutoGenWarningComment (out);
507
508 out << " This is the header file that your files should include in order to get all the" << newLine
509 << " JUCE library headers. You should avoid including the JUCE headers directly in" << newLine
510 << " your own source files, because that wouldn't pick up the correct configuration" << newLine
511 << " options for your app." << newLine
512 << newLine
513 << "*/" << newLine << newLine;
514
515 out << "#pragma once" << newLine << newLine;
516
517 if (getAppConfigFile().exists() && project.shouldUseAppConfig())
518 out << CodeHelpers::createIncludeStatement (Project::getAppConfigFilename()) << newLine;
519
520 if (modules.size() > 0)
521 {
522 out << newLine;
523
524 for (auto* module : modules)
525 module->writeIncludes (*this, out);
526
527 out << newLine;
528 }
529
530 if (hasBinaryData && project.shouldIncludeBinaryInJuceHeader())
531 out << CodeHelpers::createIncludeStatement (project.getBinaryDataHeaderFile(), getAppConfigFile()) << newLine;
532
533 out << newLine
534 << "#if defined (JUCE_PROJUCER_VERSION) && JUCE_PROJUCER_VERSION < JUCE_VERSION" << newLine
535 << " /** If you've hit this error then the version of the Projucer that was used to generate this project is" << newLine
536 << " older than the version of the JUCE modules being included. To fix this error, re-save your project" << newLine
537 << " using the latest version of the Projucer or, if you aren't using the Projucer to manage your project," << newLine
538 << " remove the JUCE_PROJUCER_VERSION define." << newLine
539 << " */" << newLine
540 << " #error \"This project was last saved using an outdated version of the Projucer! Re-save this project with the latest version to fix this error.\"" << newLine
541 << "#endif" << newLine
542 << newLine;
543
544 if (project.shouldAddUsingNamespaceToJuceHeader())
545 out << "#if ! DONT_SET_USING_JUCE_NAMESPACE" << newLine
546 << " // If your code uses a lot of JUCE classes, then this will obviously save you" << newLine
547 << " // a lot of typing, but can be disabled by setting DONT_SET_USING_JUCE_NAMESPACE." << newLine
548 << " using namespace juce;" << newLine
549 << "#endif" << newLine;
550
551 out << newLine
552 << "#if ! JUCE_DONT_DECLARE_PROJECTINFO" << newLine
553 << "namespace ProjectInfo" << newLine
554 << "{" << newLine
555 << " const char* const projectName = " << CppTokeniserFunctions::addEscapeChars (project.getProjectNameString()).quoted() << ";" << newLine
556 << " const char* const companyName = " << CppTokeniserFunctions::addEscapeChars (project.getCompanyNameString()).quoted() << ";" << newLine
557 << " const char* const versionString = " << CppTokeniserFunctions::addEscapeChars (project.getVersionString()).quoted() << ";" << newLine
558 << " const int versionNumber = " << project.getVersionAsHex() << ";" << newLine
559 << "}" << newLine
560 << "#endif" << newLine;
561 }
562
writeAppHeader(const OwnedArray<LibraryModule> & modules)563 void ProjectSaver::writeAppHeader (const OwnedArray<LibraryModule>& modules)
564 {
565 MemoryOutputStream mem;
566 mem.setNewLineString (projectLineFeed);
567
568 writeAppHeader (mem, modules);
569 saveGeneratedFile (Project::getJuceSourceHFilename(), mem);
570 }
571
writeModuleCppWrappers(const OwnedArray<LibraryModule> & modules)572 void ProjectSaver::writeModuleCppWrappers (const OwnedArray<LibraryModule>& modules)
573 {
574 for (auto* module : modules)
575 {
576 for (auto& cu : module->getAllCompileUnits())
577 {
578 MemoryOutputStream mem;
579 mem.setNewLineString (projectLineFeed);
580
581 writeAutoGenWarningComment (mem);
582
583 mem << "*/" << newLine << newLine;
584
585 if (project.shouldUseAppConfig())
586 mem << "#include " << Project::getAppConfigFilename().quoted() << newLine;
587
588 mem << "#include <";
589
590 if (cu.file.getFileExtension() != ".r") // .r files are included without the path
591 mem << module->getID() << "/";
592
593 mem << cu.file.getFileName() << ">" << newLine;
594
595 replaceFileIfDifferent (generatedCodeFolder.getChildFile (cu.getFilenameForProxyFile()), mem);
596 }
597 }
598 }
599
writeBinaryDataFiles()600 void ProjectSaver::writeBinaryDataFiles()
601 {
602 auto binaryDataH = project.getBinaryDataHeaderFile();
603
604 JucerResourceFile resourceFile (project);
605
606 if (resourceFile.getNumFiles() > 0)
607 {
608 auto dataNamespace = project.getBinaryDataNamespaceString().trim();
609
610 if (dataNamespace.isEmpty())
611 dataNamespace = "BinaryData";
612
613 resourceFile.setClassName (dataNamespace);
614
615 auto maxSize = project.getMaxBinaryFileSize();
616
617 if (maxSize <= 0)
618 maxSize = 10 * 1024 * 1024;
619
620 Array<File> binaryDataFiles;
621 auto r = resourceFile.write (maxSize);
622
623 if (r.result.wasOk())
624 {
625 hasBinaryData = true;
626
627 for (auto& f : r.filesCreated)
628 {
629 filesCreated.add (f);
630 generatedFilesGroup.addFileRetainingSortOrder (f, ! f.hasFileExtension (".h"));
631 }
632 }
633 else
634 {
635 addError (r.result.getErrorMessage());
636 }
637 }
638 else
639 {
640 for (int i = 20; --i >= 0;)
641 project.getBinaryDataCppFile (i).deleteFile();
642
643 binaryDataH.deleteFile();
644 }
645 }
646
writeReadmeFile()647 void ProjectSaver::writeReadmeFile()
648 {
649 MemoryOutputStream out;
650 out.setNewLineString (projectLineFeed);
651
652 out << newLine
653 << " Important Note!!" << newLine
654 << " ================" << newLine
655 << newLine
656 << "The purpose of this folder is to contain files that are auto-generated by the Projucer," << newLine
657 << "and ALL files in this folder will be mercilessly DELETED and completely re-written whenever" << newLine
658 << "the Projucer saves your project." << newLine
659 << newLine
660 << "Therefore, it's a bad idea to make any manual changes to the files in here, or to" << newLine
661 << "put any of your own files in here if you don't want to lose them. (Of course you may choose" << newLine
662 << "to add the folder's contents to your version-control system so that you can re-merge your own" << newLine
663 << "modifications after the Projucer has saved its changes)." << newLine;
664
665 replaceFileIfDifferent (generatedCodeFolder.getChildFile ("ReadMe.txt"), out);
666 }
667
getAudioPluginDefines() const668 String ProjectSaver::getAudioPluginDefines() const
669 {
670 const auto flags = project.getAudioPluginFlags();
671
672 if (flags.size() == 0)
673 return {};
674
675 MemoryOutputStream mem;
676 mem.setNewLineString (projectLineFeed);
677
678 mem << "//==============================================================================" << newLine
679 << "// Audio plugin settings.." << newLine
680 << newLine;
681
682 for (int i = 0; i < flags.size(); ++i)
683 {
684 mem << "#ifndef " << flags.getAllKeys()[i] << newLine
685 << " #define " << flags.getAllKeys()[i].paddedRight (' ', 32) << " "
686 << flags.getAllValues()[i] << newLine
687 << "#endif" << newLine;
688 }
689
690 return mem.toString().trim();
691 }
692
writeUnityScriptFile()693 void ProjectSaver::writeUnityScriptFile()
694 {
695 auto unityScriptContents = replaceLineFeeds (BinaryData::UnityPluginGUIScript_cs_in,
696 projectLineFeed);
697
698 auto projectName = Project::addUnityPluginPrefixIfNecessary (project.getProjectNameString());
699
700 unityScriptContents = unityScriptContents.replace ("${plugin_class_name}", projectName.replace (" ", "_"))
701 .replace ("${plugin_name}", projectName)
702 .replace ("${plugin_vendor}", project.getPluginManufacturerString())
703 .replace ("${plugin_description}", project.getPluginDescriptionString());
704
705 auto f = generatedCodeFolder.getChildFile (project.getUnityScriptName());
706
707 MemoryOutputStream out;
708 out << unityScriptContents;
709
710 replaceFileIfDifferent (f, out);
711 }
712
writeProjects(const OwnedArray<LibraryModule> & modules,ProjectExporter * specifiedExporterToSave)713 void ProjectSaver::writeProjects (const OwnedArray<LibraryModule>& modules, ProjectExporter* specifiedExporterToSave)
714 {
715 ThreadPool threadPool;
716
717 // keep a copy of the basic generated files group, as each exporter may modify it.
718 auto originalGeneratedGroup = generatedFilesGroup.state.createCopy();
719
720 CLionProjectExporter* clionExporter = nullptr;
721 std::vector<std::unique_ptr<ProjectExporter>> exporters;
722
723 try
724 {
725 for (Project::ExporterIterator exp (project); exp.next();)
726 {
727 if (specifiedExporterToSave != nullptr && exp->getUniqueName() != specifiedExporterToSave->getUniqueName())
728 continue;
729
730 exporters.push_back (std::move (exp.exporter));
731 }
732
733 for (auto& exporter : exporters)
734 {
735 exporter->initialiseDependencyPathValues();
736
737 if (exporter->getTargetFolder().createDirectory())
738 {
739 if (exporter->isCLion())
740 {
741 clionExporter = dynamic_cast<CLionProjectExporter*> (exporter.get());
742 }
743 else
744 {
745 exporter->copyMainGroupFromProject();
746 exporter->settings = exporter->settings.createCopy();
747
748 exporter->addToExtraSearchPaths (build_tools::RelativePath ("JuceLibraryCode", build_tools::RelativePath::projectFolder));
749
750 generatedFilesGroup.state = originalGeneratedGroup.createCopy();
751 exporter->addSettingsForProjectType (project.getProjectType());
752
753 for (auto* module : modules)
754 module->addSettingsForModuleToExporter (*exporter, *this);
755
756 generatedFilesGroup.sortAlphabetically (true, true);
757 exporter->getAllGroups().add (generatedFilesGroup);
758 }
759
760 if (ProjucerApplication::getApp().isRunningCommandLine)
761 saveExporter (*exporter, modules);
762 else
763 threadPool.addJob ([this, &exporter, &modules] { saveExporter (*exporter, modules); });
764 }
765 else
766 {
767 addError ("Can't create folder: " + exporter->getTargetFolder().getFullPathName());
768 }
769 }
770 }
771 catch (build_tools::SaveError& saveError)
772 {
773 addError (saveError.message);
774 }
775
776 while (threadPool.getNumJobs() > 0)
777 Thread::sleep (10);
778
779 if (clionExporter != nullptr)
780 {
781 for (auto& exporter : exporters)
782 clionExporter->writeCMakeListsExporterSection (exporter.get());
783
784 std::cout << "Finished saving: " << clionExporter->getUniqueName() << std::endl;
785 }
786 }
787
runPostExportScript()788 void ProjectSaver::runPostExportScript()
789 {
790 #if JUCE_WINDOWS
791 auto cmdString = project.getPostExportShellCommandWinString();
792 #else
793 auto cmdString = project.getPostExportShellCommandPosixString();
794 #endif
795
796 auto shellCommand = cmdString.replace ("%%1%%", project.getProjectFolder().getFullPathName());
797
798 if (shellCommand.isNotEmpty())
799 {
800 #if JUCE_WINDOWS
801 StringArray argList ("cmd.exe", "/c");
802 #else
803 StringArray argList ("/bin/sh", "-c");
804 #endif
805
806 argList.add (shellCommand);
807 ChildProcess shellProcess;
808
809 if (! shellProcess.start (argList))
810 {
811 addError ("Failed to run shell command: " + argList.joinIntoString (" "));
812 return;
813 }
814
815 if (! shellProcess.waitForProcessToFinish (10000))
816 {
817 addError ("Timeout running shell command: " + argList.joinIntoString (" "));
818 return;
819 }
820
821 auto exitCode = shellProcess.getExitCode();
822
823 if (exitCode != 0)
824 addError ("Shell command: " + argList.joinIntoString (" ") + " failed with exit code: " + String (exitCode));
825 }
826 }
827
saveExporter(ProjectExporter & exporter,const OwnedArray<LibraryModule> & modules)828 void ProjectSaver::saveExporter (ProjectExporter& exporter, const OwnedArray<LibraryModule>& modules)
829 {
830 try
831 {
832 exporter.create (modules);
833
834 if (! exporter.isCLion())
835 {
836 auto outputString = "Finished saving: " + exporter.getUniqueName();
837
838 if (MessageManager::getInstance()->isThisTheMessageThread())
839 std::cout << outputString << std::endl;
840 else
841 MessageManager::callAsync ([outputString] { std::cout << outputString << std::endl; });
842 }
843 }
844 catch (build_tools::SaveError& error)
845 {
846 addError (error.message);
847 }
848 }
849