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 #pragma once
27 
28 #include "../Project/jucer_Project.h"
29 #include "../Utility/UI/PropertyComponents/jucer_PropertyComponentsWithEnablement.h"
30 #include "../Utility/Helpers/jucer_ValueWithDefaultWrapper.h"
31 #include "../Project/Modules/jucer_Modules.h"
32 
33 class ProjectSaver;
34 
35 //==============================================================================
36 class ProjectExporter  : private Value::Listener
37 {
38 public:
39     ProjectExporter (Project&, const ValueTree& settings);
40     virtual ~ProjectExporter() override = default;
41 
42     //==============================================================================
43     struct ExporterTypeInfo
44     {
45         Identifier identifier;
46         String displayName;
47         String targetFolder;
48 
49         Image icon;
50     };
51 
52     static std::vector<ExporterTypeInfo> getExporterTypeInfos();
53     static ExporterTypeInfo getTypeInfoForExporter (const Identifier& exporterIdentifier);
54     static ExporterTypeInfo getCurrentPlatformExporterTypeInfo();
55 
56     static std::unique_ptr<ProjectExporter> createNewExporter (Project&, const Identifier& exporterIdentifier);
57     static std::unique_ptr<ProjectExporter> createExporterFromSettings (Project&, const ValueTree& settings);
58 
59     static bool canProjectBeLaunched (Project*);
60 
61     //==============================================================================
62     // capabilities of exporter
63     virtual bool usesMMFiles() const = 0;
64     virtual void createExporterProperties (PropertyListBuilder&) = 0;
65     virtual bool canLaunchProject() = 0;
66     virtual bool launchProject() = 0;
67     virtual void create (const OwnedArray<LibraryModule>&) const = 0; // may throw a SaveError
68     virtual bool shouldFileBeCompiledByDefault (const File& path) const;
69     virtual bool canCopeWithDuplicateFiles() = 0;
70     virtual bool supportsUserDefinedConfigurations() const = 0; // false if exporter only supports two configs Debug and Release
updateDeprecatedSettings()71     virtual void updateDeprecatedSettings()               {}
updateDeprecatedSettingsInteractively()72     virtual void updateDeprecatedSettingsInteractively()  {}
initialiseDependencyPathValues()73     virtual void initialiseDependencyPathValues()         {}
74 
75     // IDE targeted by exporter
76     virtual bool isXcode() const         = 0;
77     virtual bool isVisualStudio() const  = 0;
78     virtual bool isCodeBlocks() const    = 0;
79     virtual bool isMakefile() const      = 0;
80     virtual bool isAndroidStudio() const = 0;
81     virtual bool isCLion() const         = 0;
82 
83     // operating system targeted by exporter
84     virtual bool isAndroid() const = 0;
85     virtual bool isWindows() const = 0;
86     virtual bool isLinux() const   = 0;
87     virtual bool isOSX() const     = 0;
88     virtual bool isiOS() const     = 0;
89 
90     virtual String getNewLineString() const = 0;
getDescription()91     virtual String getDescription()  { return {}; }
92 
supportsPrecompiledHeaders()93     virtual bool supportsPrecompiledHeaders() const  { return false; }
94 
95     //==============================================================================
96     // cross-platform audio plug-ins supported by exporter
97     virtual bool supportsTargetType (build_tools::ProjectType::Target::Type type) const = 0;
98 
shouldBuildTargetType(build_tools::ProjectType::Target::Type type)99     inline bool shouldBuildTargetType (build_tools::ProjectType::Target::Type type) const
100     {
101         return project.shouldBuildTargetType (type) && supportsTargetType (type);
102     }
103 
callForAllSupportedTargets(std::function<void (build_tools::ProjectType::Target::Type)> callback)104     inline void callForAllSupportedTargets (std::function<void (build_tools::ProjectType::Target::Type)> callback)
105     {
106         for (int i = 0; i < build_tools::ProjectType::Target::unspecified; ++i)
107             if (shouldBuildTargetType (static_cast<build_tools::ProjectType::Target::Type> (i)))
108                 callback (static_cast<build_tools::ProjectType::Target::Type> (i));
109     }
110 
111     //==============================================================================
mayCompileOnCurrentOS()112     bool mayCompileOnCurrentOS() const
113     {
114        #if JUCE_MAC
115         return isOSX() || isAndroid() || isiOS();
116        #elif JUCE_WINDOWS
117         return isWindows() || isAndroid();
118        #elif JUCE_LINUX || JUCE_BSD
119         return isLinux() || isAndroid();
120        #else
121         #error
122        #endif
123     }
124 
125     //==============================================================================
126     String getUniqueName() const;
127     File getTargetFolder() const;
128 
getProject()129     Project& getProject() noexcept                        { return project; }
getProject()130     const Project& getProject() const noexcept            { return project; }
131 
getUndoManager()132     UndoManager* getUndoManager() const                   { return project.getUndoManagerFor (settings); }
133 
getSetting(const Identifier & nm)134     Value getSetting (const Identifier& nm)               { return settings.getPropertyAsValue (nm, project.getUndoManagerFor (settings)); }
getSettingString(const Identifier & nm)135     String getSettingString (const Identifier& nm) const  { return settings [nm]; }
136 
getTargetLocationValue()137     Value getTargetLocationValue()                        { return targetLocationValue.getPropertyAsValue(); }
getTargetLocationString()138     String getTargetLocationString() const                { return targetLocationValue.get(); }
139 
getExtraCompilerFlagsString()140     String getExtraCompilerFlagsString() const            { return extraCompilerFlagsValue.get().toString().replaceCharacters ("\r\n", "  "); }
getExtraLinkerFlagsString()141     String getExtraLinkerFlagsString() const              { return extraLinkerFlagsValue.get().toString().replaceCharacters ("\r\n", "  "); }
142 
getExternalLibrariesString()143     String getExternalLibrariesString() const             { return getSearchPathsFromString (externalLibrariesValue.get().toString()).joinIntoString (";"); }
144 
shouldUseGNUExtensions()145     bool shouldUseGNUExtensions() const                   { return gnuExtensionsValue.get(); }
146 
getVSTLegacyPathString()147     String getVSTLegacyPathString() const                 { return vstLegacyPathValueWrapper.getCurrentValue(); }
getAAXPathString()148     String getAAXPathString() const                       { return aaxPathValueWrapper.getCurrentValue(); }
getRTASPathString()149     String getRTASPathString() const                      { return rtasPathValueWrapper.getCurrentValue(); }
150 
151     // NB: this is the path to the parent "modules" folder that contains the named module, not the
152     // module folder itself.
153     ValueWithDefault getPathForModuleValue (const String& moduleID);
154     String getPathForModuleString (const String& moduleID) const;
155     void removePathForModule (const String& moduleID);
156 
157     TargetOS::OS getTargetOSForExporter() const;
158 
159     build_tools::RelativePath getLegacyModulePath (const String& moduleID) const;
160     String getLegacyModulePath() const;
161 
162     // Returns a path to the actual module folder itself
163     build_tools::RelativePath getModuleFolderRelativeToProject (const String& moduleID) const;
164     void updateOldModulePaths();
165 
166     build_tools::RelativePath rebaseFromProjectFolderToBuildTarget (const build_tools::RelativePath& path) const;
167     void addToExtraSearchPaths (const build_tools::RelativePath& pathFromProjectFolder, int index = -1);
168     void addToModuleLibPaths   (const build_tools::RelativePath& pathFromProjectFolder);
169 
170     void addProjectPathToBuildPathList (StringArray&, const build_tools::RelativePath&, int index = -1) const;
171 
172     std::unique_ptr<Drawable> getBigIcon() const;
173     std::unique_ptr<Drawable> getSmallIcon() const;
getIcons()174     build_tools::Icons getIcons() const { return { getSmallIcon(), getBigIcon() }; }
175 
getExporterIdentifierMacro()176     String getExporterIdentifierMacro() const
177     {
178         return "JUCER_" + settings.getType().toString() + "_"
179                 + String::toHexString (getTargetLocationString().hashCode()).toUpperCase();
180     }
181 
182     // An exception that can be thrown by the create() method.
183     void createPropertyEditors (PropertyListBuilder&);
184     void addSettingsForProjectType (const build_tools::ProjectType&);
185 
186     //==============================================================================
187     void copyMainGroupFromProject();
getAllGroups()188     Array<Project::Item>& getAllGroups() noexcept               { jassert (itemGroups.size() > 0); return itemGroups; }
getAllGroups()189     const Array<Project::Item>& getAllGroups() const noexcept   { jassert (itemGroups.size() > 0); return itemGroups; }
190     Project::Item& getModulesGroup();
191 
192     //==============================================================================
193     StringArray linuxLibs, linuxPackages, makefileExtraLinkerFlags;
194 
195     enum class PackageDependencyType
196     {
197         compile,
198         link
199     };
200 
201     StringArray getLinuxPackages (PackageDependencyType type) const;
202 
203     //==============================================================================
204     StringPairArray msvcExtraPreprocessorDefs;
205     String msvcDelayLoadedDLLs;
206     StringArray mingwLibs, windowsLibs;
207 
208     //==============================================================================
209     StringArray androidLibs;
210 
211     //==============================================================================
212     StringArray extraSearchPaths;
213     StringArray moduleLibSearchPaths;
214 
215     //==============================================================================
216     class BuildConfiguration  : public ReferenceCountedObject
217     {
218     public:
219         BuildConfiguration (Project& project, const ValueTree& configNode, const ProjectExporter&);
220         ~BuildConfiguration();
221 
222         using Ptr = ReferenceCountedObjectPtr<BuildConfiguration>;
223 
224         //==============================================================================
225         virtual void createConfigProperties (PropertyListBuilder&) = 0;
226         virtual String getModuleLibraryArchName() const = 0;
227 
228         //==============================================================================
getName()229         String getName() const                                 { return configNameValue.get(); }
isDebug()230         bool isDebug() const                                   { return isDebugValue.get(); }
231 
getTargetBinaryRelativePathString()232         String getTargetBinaryRelativePathString() const       { return targetBinaryPathValue.get(); }
233         String getTargetBinaryNameString (bool isUnityPlugin = false) const
234         {
235             return (isUnityPlugin ? Project::addUnityPluginPrefixIfNecessary (targetNameValue.get().toString())
236                                   : targetNameValue.get().toString());
237         }
238 
getOptimisationLevelInt()239         int getOptimisationLevelInt() const                    { return optimisationLevelValue.get(); }
240         String getGCCOptimisationFlag() const;
isLinkTimeOptimisationEnabled()241         bool isLinkTimeOptimisationEnabled() const             { return linkTimeOptimisationValue.get(); }
242 
getBuildConfigPreprocessorDefsString()243         String getBuildConfigPreprocessorDefsString() const    { return ppDefinesValue.get(); }
244         StringPairArray getAllPreprocessorDefs() const;        // includes inherited definitions
245         StringPairArray getUniquePreprocessorDefs() const;     // returns pre-processor definitions that are not already in the project pre-processor defs
246 
getHeaderSearchPathString()247         String getHeaderSearchPathString() const               { return headerSearchPathValue.get(); }
248         StringArray getHeaderSearchPaths() const;
249 
getLibrarySearchPathString()250         String getLibrarySearchPathString() const              { return librarySearchPathValue.get(); }
251         StringArray getLibrarySearchPaths() const;
252 
getPrecompiledHeaderFilename()253         String getPrecompiledHeaderFilename() const            { return "JucePrecompiledHeader_" + getName(); }
getSkipPrecompiledHeaderDefine()254         static String getSkipPrecompiledHeaderDefine()         { return "JUCE_SKIP_PRECOMPILED_HEADER"; }
255 
shouldUsePrecompiledHeaderFile()256         bool shouldUsePrecompiledHeaderFile() const            { return usePrecompiledHeaderFileValue.get(); }
257         String getPrecompiledHeaderFileContent() const;
258 
259         //==============================================================================
getValue(const Identifier & nm)260         Value getValue (const Identifier& nm)                  { return config.getPropertyAsValue (nm, getUndoManager()); }
getUndoManager()261         UndoManager* getUndoManager() const                    { return project.getUndoManagerFor (config); }
262 
263         //==============================================================================
264         void createPropertyEditors (PropertyListBuilder&);
265         void addRecommendedLinuxCompilerWarningsProperty (PropertyListBuilder&);
266         void addRecommendedLLVMCompilerWarningsProperty (PropertyListBuilder&);
267         StringArray getRecommendedCompilerWarningFlags() const;
268         void addGCCOptimisationProperty (PropertyListBuilder&);
269         void removeFromExporter();
270 
271         //==============================================================================
272         ValueTree config;
273         Project& project;
274         const ProjectExporter& exporter;
275 
276     protected:
277         ValueWithDefault isDebugValue, configNameValue, targetNameValue, targetBinaryPathValue, recommendedWarningsValue, optimisationLevelValue,
278                          linkTimeOptimisationValue, ppDefinesValue, headerSearchPathValue, librarySearchPathValue, userNotesValue,
279                          usePrecompiledHeaderFileValue, precompiledHeaderFileValue;
280 
281     private:
282         std::map<String, StringArray> recommendedCompilerWarningFlags;
283 
284         JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BuildConfiguration)
285     };
286 
287     void addNewConfigurationFromExisting (const BuildConfiguration& configToCopy);
288     void addNewConfiguration (bool isDebugConfig);
289     bool hasConfigurationNamed (const String& name) const;
290     String getUniqueConfigName (String name) const;
291 
292     String getExternalLibraryFlags (const BuildConfiguration& config) const;
293 
294     //==============================================================================
295     struct ConfigIterator
296     {
297         ConfigIterator (ProjectExporter& exporter);
298 
299         bool next();
300 
301         BuildConfiguration& operator*() const       { return *config; }
302         BuildConfiguration* operator->() const      { return config.get(); }
303 
304         BuildConfiguration::Ptr config;
305         int index;
306 
307     private:
308         ProjectExporter& exporter;
309         JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ConfigIterator)
310     };
311 
312     struct ConstConfigIterator
313     {
314         ConstConfigIterator (const ProjectExporter& exporter);
315 
316         bool next();
317 
318         const BuildConfiguration& operator*() const       { return *config; }
319         const BuildConfiguration* operator->() const      { return config.get(); }
320 
321         BuildConfiguration::Ptr config;
322         int index;
323 
324     private:
325         const ProjectExporter& exporter;
326         JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ConstConfigIterator)
327     };
328 
329     int getNumConfigurations() const;
330     BuildConfiguration::Ptr getConfiguration (int index) const;
331 
332     ValueTree getConfigurations() const;
333     virtual void createDefaultConfigs();
334     void createDefaultModulePaths();
335 
336     //==============================================================================
getExporterPreprocessorDefsValue()337     Value getExporterPreprocessorDefsValue()            { return extraPPDefsValue.getPropertyAsValue(); }
getExporterPreprocessorDefsString()338     String getExporterPreprocessorDefsString() const    { return extraPPDefsValue.get(); }
339 
340     // includes exporter, project + config defs
341     StringPairArray getAllPreprocessorDefs (const BuildConfiguration& config, const build_tools::ProjectType::Target::Type targetType) const;
342     // includes exporter + project defs
343     StringPairArray getAllPreprocessorDefs() const;
344 
345     void addTargetSpecificPreprocessorDefs (StringPairArray& defs, const build_tools::ProjectType::Target::Type targetType) const;
346 
347     String replacePreprocessorTokens (const BuildConfiguration&, const String& sourceString) const;
348 
349     ValueTree settings;
350 
351     enum GCCOptimisationLevel
352     {
353         gccO0     = 1,
354         gccO1     = 4,
355         gccO2     = 5,
356         gccO3     = 3,
357         gccOs     = 2,
358         gccOfast  = 6
359     };
360 
isPCHEnabledForAnyConfigurations()361     bool isPCHEnabledForAnyConfigurations() const
362     {
363         if (supportsPrecompiledHeaders())
364             for (ConstConfigIterator config (*this); config.next();)
365                 if (config->shouldUsePrecompiledHeaderFile())
366                     return true;
367 
368         return false;
369     }
370 
371 protected:
372     //==============================================================================
373     String name;
374     Project& project;
375     const build_tools::ProjectType& projectType;
376     const String projectName;
377     const File projectFolder;
378 
379     //==============================================================================
380     ValueWithDefaultWrapper vstLegacyPathValueWrapper, rtasPathValueWrapper, aaxPathValueWrapper;
381 
382     ValueWithDefault targetLocationValue, extraCompilerFlagsValue, extraLinkerFlagsValue, externalLibrariesValue,
383                      userNotesValue, gnuExtensionsValue, bigIconValue, smallIconValue, extraPPDefsValue;
384 
385     Value projectCompilerFlagSchemesValue;
386     HashMap<String, ValueWithDefault> compilerFlagSchemesMap;
387 
388     mutable Array<Project::Item> itemGroups;
389     Project::Item* modulesGroup = nullptr;
390 
391     virtual BuildConfiguration::Ptr createBuildConfig (const ValueTree&) const = 0;
392 
393     void addDefaultPreprocessorDefs (StringPairArray&) const;
394 
getDefaultBuildsRootFolder()395     static String getDefaultBuildsRootFolder()            { return "Builds/"; }
396 
getStaticLibbedFilename(String name)397     static String getStaticLibbedFilename (String name)   { return addSuffix (addLibPrefix (name), ".a"); }
getDynamicLibbedFilename(String name)398     static String getDynamicLibbedFilename (String name)  { return addSuffix (addLibPrefix (name), ".so"); }
399 
400     virtual void addPlatformSpecificSettingsForProjectType (const build_tools::ProjectType&) = 0;
401 
402     //==============================================================================
createDirectoryOrThrow(const File & dirToCreate)403     static void createDirectoryOrThrow (const File& dirToCreate)
404     {
405         if (! dirToCreate.createDirectory())
406             throw build_tools::SaveError ("Can't create folder: " + dirToCreate.getFullPathName());
407     }
408 
409     static void writeXmlOrThrow (const XmlElement& xml, const File& file, const String& encoding,
410                                  int maxCharsPerLine, bool useUnixNewLines = false)
411     {
412         XmlElement::TextFormat format;
413         format.customEncoding = encoding;
414         format.lineWrapLength = maxCharsPerLine;
415         format.newLineChars = useUnixNewLines ? "\n" : "\r\n";
416 
417         MemoryOutputStream mo (8192);
418         xml.writeTo (mo, format);
419         build_tools::overwriteFileIfDifferentOrThrow (file, mo);
420     }
421 
422 private:
423     //==============================================================================
valueChanged(Value &)424     void valueChanged (Value&) override   { updateCompilerFlagValues(); }
425     void updateCompilerFlagValues();
426 
427     //==============================================================================
addLibPrefix(const String name)428     static String addLibPrefix (const String name)
429     {
430         return name.startsWith ("lib") ? name
431                                        : "lib" + name;
432     }
433 
addSuffix(const String name,const String suffix)434     static String addSuffix (const String name, const String suffix)
435     {
436         return name.endsWithIgnoreCase (suffix) ? name
437                                                 : name + suffix;
438     }
439 
440     void createDependencyPathProperties (PropertyListBuilder&);
441     void createIconProperties (PropertyListBuilder&);
442     void addVSTPathsIfPluginOrHost();
443     void addCommonAudioPluginSettings();
444     void addLegacyVSTFolderToPathIfSpecified();
445     build_tools::RelativePath getInternalVST3SDKPath();
446     void addAAXFoldersToPath();
447 
448     JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ProjectExporter)
449 };
450