- clean working copy for leaving SVN

This commit is contained in:
2022-06-28 19:04:46 +02:00
parent 62a2d47cb3
commit 762b8f0e7d
1966 changed files with 699872 additions and 126 deletions
@@ -0,0 +1,695 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found 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.juce.com for more information.
==============================================================================
*/
class AndroidProjectExporter : public ProjectExporter
{
public:
//==============================================================================
static const char* getNameAndroid() { return "Android Project"; }
static const char* getValueTreeTypeName() { return "ANDROID"; }
static AndroidProjectExporter* createForSettings (Project& project, const ValueTree& settings)
{
if (settings.hasType (getValueTreeTypeName()))
return new AndroidProjectExporter (project, settings);
return nullptr;
}
//==============================================================================
AndroidProjectExporter (Project& p, const ValueTree& t) : ProjectExporter (p, t)
{
name = getNameAndroid();
if (getTargetLocationString().isEmpty())
getTargetLocationValue() = getDefaultBuildsRootFolder() + "Android";
if (getActivityClassPath().isEmpty())
getActivityClassPathValue() = createDefaultClassName();
if (getSDKPathString().isEmpty()) getSDKPathValue() = "${user.home}/SDKs/android-sdk";
if (getNDKPathString().isEmpty()) getNDKPathValue() = "${user.home}/SDKs/android-ndk";
if (getMinimumSDKVersionString().isEmpty())
getMinimumSDKVersionValue() = 8;
if (getInternetNeededValue().toString().isEmpty())
getInternetNeededValue() = true;
if (getKeyStoreValue().getValue().isVoid()) getKeyStoreValue() = "${user.home}/.android/debug.keystore";
if (getKeyStorePassValue().getValue().isVoid()) getKeyStorePassValue() = "android";
if (getKeyAliasValue().getValue().isVoid()) getKeyAliasValue() = "androiddebugkey";
if (getKeyAliasPassValue().getValue().isVoid()) getKeyAliasPassValue() = "android";
if (getCPP11EnabledValue().getValue().isVoid()) getCPP11EnabledValue() = true;
}
//==============================================================================
bool canLaunchProject() override { return false; }
bool launchProject() override { return false; }
bool isAndroid() const override { return true; }
bool usesMMFiles() const override { return false; }
bool canCopeWithDuplicateFiles() override { return false; }
void createExporterProperties (PropertyListBuilder& props) override
{
props.add (new TextPropertyComponent (getActivityClassPathValue(), "Android Activity class name", 256, false),
"The full java class name to use for the app's Activity class.");
props.add (new TextPropertyComponent (getSDKPathValue(), "Android SDK Path", 1024, false),
"The path to the Android SDK folder on the target build machine");
props.add (new TextPropertyComponent (getNDKPathValue(), "Android NDK Path", 1024, false),
"The path to the Android NDK folder on the target build machine");
props.add (new TextPropertyComponent (getMinimumSDKVersionValue(), "Minimum SDK version", 32, false),
"The number of the minimum version of the Android SDK that the app requires");
props.add (new TextPropertyComponent (getNDKToolchainVersionValue(), "NDK Toolchain version", 32, false),
"The variable NDK_TOOLCHAIN_VERSION in Application.mk - leave blank for a default value");
props.add (new BooleanPropertyComponent (getCPP11EnabledValue(), "Enable C++11 features", "Enable the -std=c++11 flag"),
"If enabled, this will set the -std=c++11 flag for the build.");
props.add (new BooleanPropertyComponent (getInternetNeededValue(), "Internet Access", "Specify internet access permission in the manifest"),
"If enabled, this will set the android.permission.INTERNET flag in the manifest.");
props.add (new BooleanPropertyComponent (getAudioRecordNeededValue(), "Audio Input Required", "Specify audio record permission in the manifest"),
"If enabled, this will set the android.permission.RECORD_AUDIO flag in the manifest.");
props.add (new TextPropertyComponent (getOtherPermissionsValue(), "Custom permissions", 2048, false),
"A space-separated list of other permission flags that should be added to the manifest.");
props.add (new TextPropertyComponent (getKeyStoreValue(), "Key Signing: key.store", 2048, false),
"The key.store value, used when signing the package.");
props.add (new TextPropertyComponent (getKeyStorePassValue(), "Key Signing: key.store.password", 2048, false),
"The key.store password, used when signing the package.");
props.add (new TextPropertyComponent (getKeyAliasValue(), "Key Signing: key.alias", 2048, false),
"The key.alias value, used when signing the package.");
props.add (new TextPropertyComponent (getKeyAliasPassValue(), "Key Signing: key.alias.password", 2048, false),
"The key.alias password, used when signing the package.");
}
Value getActivityClassPathValue() { return getSetting (Ids::androidActivityClass); }
String getActivityClassPath() const { return settings [Ids::androidActivityClass]; }
Value getSDKPathValue() { return getSetting (Ids::androidSDKPath); }
String getSDKPathString() const { return settings [Ids::androidSDKPath]; }
Value getNDKPathValue() { return getSetting (Ids::androidNDKPath); }
String getNDKPathString() const { return settings [Ids::androidNDKPath]; }
Value getNDKToolchainVersionValue() { return getSetting (Ids::toolset); }
String getNDKToolchainVersionString() const { return settings [Ids::toolset]; }
Value getKeyStoreValue() { return getSetting (Ids::androidKeyStore); }
String getKeyStoreString() const { return settings [Ids::androidKeyStore]; }
Value getKeyStorePassValue() { return getSetting (Ids::androidKeyStorePass); }
String getKeyStorePassString() const { return settings [Ids::androidKeyStorePass]; }
Value getKeyAliasValue() { return getSetting (Ids::androidKeyAlias); }
String getKeyAliasString() const { return settings [Ids::androidKeyAlias]; }
Value getKeyAliasPassValue() { return getSetting (Ids::androidKeyAliasPass); }
String getKeyAliasPassString() const { return settings [Ids::androidKeyAliasPass]; }
Value getInternetNeededValue() { return getSetting (Ids::androidInternetNeeded); }
bool getInternetNeeded() const { return settings [Ids::androidInternetNeeded]; }
Value getAudioRecordNeededValue() { return getSetting (Ids::androidMicNeeded); }
bool getAudioRecordNeeded() const { return settings [Ids::androidMicNeeded]; }
Value getMinimumSDKVersionValue() { return getSetting (Ids::androidMinimumSDK); }
String getMinimumSDKVersionString() const { return settings [Ids::androidMinimumSDK]; }
Value getOtherPermissionsValue() { return getSetting (Ids::androidOtherPermissions); }
String getOtherPermissions() const { return settings [Ids::androidOtherPermissions]; }
Value getCPP11EnabledValue() { return getSetting (Ids::androidCpp11); }
bool isCPP11Enabled() const { return settings [Ids::androidCpp11]; }
String createDefaultClassName() const
{
String s (project.getBundleIdentifier().toString().toLowerCase());
if (s.length() > 5
&& s.containsChar ('.')
&& s.containsOnly ("abcdefghijklmnopqrstuvwxyz_.")
&& ! s.startsWithChar ('.'))
{
if (! s.endsWithChar ('.'))
s << ".";
}
else
{
s = "com.yourcompany.";
}
return s + CodeHelpers::makeValidIdentifier (project.getProjectFilenameRoot(), false, true, false);
}
//==============================================================================
void create (const OwnedArray<LibraryModule>& modules) const override
{
const File target (getTargetFolder());
const File jniFolder (target.getChildFile ("jni"));
copyActivityJavaFiles (modules);
createDirectoryOrThrow (jniFolder);
createDirectoryOrThrow (target.getChildFile ("res").getChildFile ("values"));
createDirectoryOrThrow (target.getChildFile ("libs"));
createDirectoryOrThrow (target.getChildFile ("bin"));
{
ScopedPointer<XmlElement> manifest (createManifestXML());
writeXmlOrThrow (*manifest, target.getChildFile ("AndroidManifest.xml"), "utf-8", 100, true);
}
writeApplicationMk (jniFolder.getChildFile ("Application.mk"));
writeAndroidMk (jniFolder.getChildFile ("Android.mk"));
{
ScopedPointer<XmlElement> antBuildXml (createAntBuildXML());
writeXmlOrThrow (*antBuildXml, target.getChildFile ("build.xml"), "UTF-8", 100);
}
writeProjectPropertiesFile (target.getChildFile ("project.properties"));
writeLocalPropertiesFile (target.getChildFile ("local.properties"));
writeStringsFile (target.getChildFile ("res/values/strings.xml"));
ScopedPointer<Drawable> bigIcon (getBigIcon());
ScopedPointer<Drawable> smallIcon (getSmallIcon());
if (bigIcon != nullptr && smallIcon != nullptr)
{
const int step = jmax (bigIcon->getWidth(), bigIcon->getHeight()) / 8;
writeIcon (target.getChildFile ("res/drawable-xhdpi/icon.png"), getBestIconForSize (step * 8, false));
writeIcon (target.getChildFile ("res/drawable-hdpi/icon.png"), getBestIconForSize (step * 6, false));
writeIcon (target.getChildFile ("res/drawable-mdpi/icon.png"), getBestIconForSize (step * 4, false));
writeIcon (target.getChildFile ("res/drawable-ldpi/icon.png"), getBestIconForSize (step * 3, false));
}
else if (Drawable* icon = bigIcon != nullptr ? bigIcon : smallIcon)
{
writeIcon (target.getChildFile ("res/drawable-mdpi/icon.png"), rescaleImageForIcon (*icon, icon->getWidth()));
}
}
protected:
//==============================================================================
class AndroidBuildConfiguration : public BuildConfiguration
{
public:
AndroidBuildConfiguration (Project& p, const ValueTree& settings)
: BuildConfiguration (p, settings)
{
if (getArchitectures().isEmpty())
getArchitecturesValue() = "armeabi armeabi-v7a";
}
Value getArchitecturesValue() { return getValue (Ids::androidArchitectures); }
String getArchitectures() const { return config [Ids::androidArchitectures]; }
void createConfigProperties (PropertyListBuilder& props)
{
props.add (new TextPropertyComponent (getArchitecturesValue(), "Architectures", 256, false),
"A list of the ARM architectures to build (for a fat binary).");
}
};
BuildConfiguration::Ptr createBuildConfig (const ValueTree& v) const
{
return new AndroidBuildConfiguration (project, v);
}
private:
//==============================================================================
XmlElement* createManifestXML() const
{
XmlElement* manifest = new XmlElement ("manifest");
manifest->setAttribute ("xmlns:android", "http://schemas.android.com/apk/res/android");
manifest->setAttribute ("android:versionCode", "1");
manifest->setAttribute ("android:versionName", "1.0");
manifest->setAttribute ("package", getActivityClassPackage());
XmlElement* screens = manifest->createNewChildElement ("supports-screens");
screens->setAttribute ("android:smallScreens", "true");
screens->setAttribute ("android:normalScreens", "true");
screens->setAttribute ("android:largeScreens", "true");
//screens->setAttribute ("android:xlargeScreens", "true");
screens->setAttribute ("android:anyDensity", "true");
XmlElement* sdk = manifest->createNewChildElement ("uses-sdk");
sdk->setAttribute ("android:minSdkVersion", getMinimumSDKVersionString());
sdk->setAttribute ("android:targetSdkVersion", "11");
{
const StringArray permissions (getPermissionsRequired());
for (int i = permissions.size(); --i >= 0;)
manifest->createNewChildElement ("uses-permission")->setAttribute ("android:name", permissions[i]);
}
if (project.getModules().isModuleEnabled ("juce_opengl"))
{
XmlElement* feature = manifest->createNewChildElement ("uses-feature");
feature->setAttribute ("android:glEsVersion", "0x00020000");
feature->setAttribute ("android:required", "true");
}
XmlElement* app = manifest->createNewChildElement ("application");
app->setAttribute ("android:label", "@string/app_name");
{
ScopedPointer<Drawable> bigIcon (getBigIcon()), smallIcon (getSmallIcon());
if (bigIcon != nullptr || smallIcon != nullptr)
app->setAttribute ("android:icon", "@drawable/icon");
}
if (getMinimumSDKVersionString().getIntValue() >= 11)
app->setAttribute ("android:hardwareAccelerated", "false"); // (using the 2D acceleration slows down openGL)
XmlElement* act = app->createNewChildElement ("activity");
act->setAttribute ("android:name", getActivityName());
act->setAttribute ("android:label", "@string/app_name");
act->setAttribute ("android:configChanges", "keyboardHidden|orientation");
XmlElement* intent = act->createNewChildElement ("intent-filter");
intent->createNewChildElement ("action")->setAttribute ("android:name", "android.intent.action.MAIN");
intent->createNewChildElement ("category")->setAttribute ("android:name", "android.intent.category.LAUNCHER");
return manifest;
}
StringArray getPermissionsRequired() const
{
StringArray s;
s.addTokens (getOtherPermissions(), ", ", "");
if (getInternetNeeded()) s.add ("android.permission.INTERNET");
if (getAudioRecordNeeded()) s.add ("android.permission.RECORD_AUDIO");
s.trim();
s.removeDuplicates (false);
return s;
}
//==============================================================================
void findAllFilesToCompile (const Project::Item& projectItem, Array<RelativePath>& results) const
{
if (projectItem.isGroup())
{
for (int i = 0; i < projectItem.getNumChildren(); ++i)
findAllFilesToCompile (projectItem.getChild(i), results);
}
else
{
if (projectItem.shouldBeCompiled())
results.add (RelativePath (projectItem.getFile(), getTargetFolder(), RelativePath::buildTargetFolder));
}
}
//==============================================================================
String getActivityName() const
{
return getActivityClassPath().fromLastOccurrenceOf (".", false, false);
}
String getActivityClassPackage() const
{
return getActivityClassPath().upToLastOccurrenceOf (".", false, false);
}
String getJNIActivityClassName() const
{
return getActivityClassPath().replaceCharacter ('.', '/');
}
static LibraryModule* getCoreModule (const OwnedArray<LibraryModule>& modules)
{
for (int i = modules.size(); --i >= 0;)
if (modules.getUnchecked(i)->getID() == "juce_core")
return modules.getUnchecked(i);
return nullptr;
}
void copyActivityJavaFiles (const OwnedArray<LibraryModule>& modules) const
{
const String className (getActivityName());
const String package (getActivityClassPackage());
String path (package.replaceCharacter ('.', File::separator));
if (path.isEmpty() || className.isEmpty())
throw SaveError ("Invalid Android Activity class name: " + getActivityClassPath());
const File classFolder (getTargetFolder().getChildFile ("src")
.getChildFile (path));
createDirectoryOrThrow (classFolder);
LibraryModule* const coreModule = getCoreModule (modules);
if (coreModule != nullptr)
{
File javaDestFile (classFolder.getChildFile (className + ".java"));
File javaSourceFile (coreModule->getFolder().getChildFile ("native")
.getChildFile ("java")
.getChildFile ("JuceAppActivity.java"));
MemoryOutputStream newFile;
newFile << javaSourceFile.loadFileAsString()
.replace ("JuceAppActivity", className)
.replace ("package com.juce;", "package " + package + ";");
overwriteFileIfDifferentOrThrow (javaDestFile, newFile);
}
}
String getABIs (bool forDebug) const
{
for (ConstConfigIterator config (*this); config.next();)
{
const AndroidBuildConfiguration& androidConfig = dynamic_cast<const AndroidBuildConfiguration&> (*config);
if (config->isDebug() == forDebug)
return androidConfig.getArchitectures();
}
return String();
}
String getCppFlags() const
{
String flags ("-fsigned-char -fexceptions -frtti");
if (! getNDKToolchainVersionString().startsWithIgnoreCase ("clang"))
flags << " -Wno-psabi";
return flags;
}
String getToolchainVersion() const
{
String v (getNDKToolchainVersionString());
return v.isNotEmpty() ? v : "4.8";
}
void writeApplicationMk (const File& file) const
{
MemoryOutputStream mo;
mo << "# Automatically generated makefile, created by the Introjucer" << newLine
<< "# Don't edit this file! Your changes will be overwritten when you re-save the Introjucer project!" << newLine
<< newLine
<< "APP_STL := gnustl_static" << newLine
<< "APP_CPPFLAGS += " << getCppFlags() << newLine
<< "APP_PLATFORM := " << getAppPlatform() << newLine
<< "NDK_TOOLCHAIN_VERSION := " << getToolchainVersion() << newLine
<< newLine
<< "ifeq ($(NDK_DEBUG),1)" << newLine
<< " APP_ABI := " << getABIs (true) << newLine
<< "else" << newLine
<< " APP_ABI := " << getABIs (false) << newLine
<< "endif" << newLine;
overwriteFileIfDifferentOrThrow (file, mo);
}
void writeAndroidMk (const File& file) const
{
Array<RelativePath> files;
for (int i = 0; i < getAllGroups().size(); ++i)
findAllFilesToCompile (getAllGroups().getReference(i), files);
MemoryOutputStream mo;
writeAndroidMk (mo, files);
overwriteFileIfDifferentOrThrow (file, mo);
}
void writeAndroidMk (OutputStream& out, const Array<RelativePath>& files) const
{
out << "# Automatically generated makefile, created by the Introjucer" << newLine
<< "# Don't edit this file! Your changes will be overwritten when you re-save the Introjucer project!" << newLine
<< newLine
<< "LOCAL_PATH := $(call my-dir)" << newLine
<< newLine
<< "include $(CLEAR_VARS)" << newLine
<< newLine
<< "ifeq ($(TARGET_ARCH_ABI), armeabi-v7a)" << newLine
<< " LOCAL_ARM_MODE := arm" << newLine
<< "endif" << newLine
<< newLine
<< "LOCAL_MODULE := juce_jni" << newLine
<< "LOCAL_SRC_FILES := \\" << newLine;
for (int i = 0; i < files.size(); ++i)
out << " " << (files.getReference(i).isAbsolute() ? "" : "../")
<< escapeSpaces (files.getReference(i).toUnixStyle()) << "\\" << newLine;
String debugSettings, releaseSettings;
out << newLine
<< "ifeq ($(NDK_DEBUG),1)" << newLine;
writeConfigSettings (out, true);
out << "else" << newLine;
writeConfigSettings (out, false);
out << "endif" << newLine
<< newLine
<< "include $(BUILD_SHARED_LIBRARY)" << newLine;
}
void writeConfigSettings (OutputStream& out, bool forDebug) const
{
for (ConstConfigIterator config (*this); config.next();)
{
if (config->isDebug() == forDebug)
{
const AndroidBuildConfiguration& androidConfig = dynamic_cast<const AndroidBuildConfiguration&> (*config);
String cppFlags;
cppFlags << createCPPFlags (androidConfig)
<< (" " + replacePreprocessorTokens (androidConfig, getExtraCompilerFlagsString()).trim()).trimEnd()
<< newLine
<< getLDLIBS (androidConfig).trimEnd()
<< newLine;
out << " LOCAL_CPPFLAGS += " << cppFlags;
out << " LOCAL_CFLAGS += " << cppFlags;
break;
}
}
}
String getLDLIBS (const AndroidBuildConfiguration& config) const
{
return " LOCAL_LDLIBS :=" + config.getGCCLibraryPathFlags()
+ " -llog -lGLESv2 " + getExternalLibraryFlags (config)
+ " " + replacePreprocessorTokens (config, getExtraLinkerFlagsString());
}
String createIncludePathFlags (const BuildConfiguration& config) const
{
String flags;
StringArray searchPaths (extraSearchPaths);
searchPaths.addArray (config.getHeaderSearchPaths());
searchPaths.removeDuplicates (false);
for (int i = 0; i < searchPaths.size(); ++i)
flags << " -I " << FileHelpers::unixStylePath (replacePreprocessorTokens (config, searchPaths[i])).quoted();
return flags;
}
String createCPPFlags (const BuildConfiguration& config) const
{
StringPairArray defines;
defines.set ("JUCE_ANDROID", "1");
defines.set ("JUCE_ANDROID_API_VERSION", getMinimumSDKVersionString());
defines.set ("JUCE_ANDROID_ACTIVITY_CLASSNAME", getJNIActivityClassName().replaceCharacter ('/', '_'));
defines.set ("JUCE_ANDROID_ACTIVITY_CLASSPATH", "\\\"" + getJNIActivityClassName() + "\\\"");
String flags ("-fsigned-char -fexceptions -frtti");
if (config.isDebug())
{
flags << " -g";
defines.set ("DEBUG", "1");
defines.set ("_DEBUG", "1");
}
else
{
defines.set ("NDEBUG", "1");
}
flags << createIncludePathFlags (config)
<< " -O" << config.getGCCOptimisationFlag();
if (isCPP11Enabled())
flags << " -std=c++11 -std=gnu++11"; // these flags seem to enable slightly different things on gcc, and both seem to be needed
defines = mergePreprocessorDefs (defines, getAllPreprocessorDefs (config));
return flags + createGCCPreprocessorFlags (defines);
}
//==============================================================================
XmlElement* createAntBuildXML() const
{
XmlElement* proj = new XmlElement ("project");
proj->setAttribute ("name", projectName);
proj->setAttribute ("default", "debug");
proj->createNewChildElement ("loadproperties")->setAttribute ("srcFile", "local.properties");
proj->createNewChildElement ("loadproperties")->setAttribute ("srcFile", "project.properties");
{
XmlElement* target = proj->createNewChildElement ("target");
target->setAttribute ("name", "clean");
target->setAttribute ("depends", "android_rules.clean");
target->createNewChildElement ("delete")->setAttribute ("dir", "libs");
target->createNewChildElement ("delete")->setAttribute ("dir", "obj");
XmlElement* executable = target->createNewChildElement ("exec");
executable->setAttribute ("executable", "${ndk.dir}/ndk-build");
executable->setAttribute ("dir", "${basedir}");
executable->setAttribute ("failonerror", "true");
executable->createNewChildElement ("arg")->setAttribute ("value", "clean");
}
{
XmlElement* target = proj->createNewChildElement ("target");
target->setAttribute ("name", "-pre-build");
addDebugConditionClause (target, "makefileConfig", "Debug", "Release");
addDebugConditionClause (target, "ndkDebugValue", "NDK_DEBUG=1", "NDK_DEBUG=0");
String debugABIs, releaseABIs;
for (ConstConfigIterator config (*this); config.next();)
{
const AndroidBuildConfiguration& androidConfig = dynamic_cast<const AndroidBuildConfiguration&> (*config);
if (config->isDebug())
debugABIs = androidConfig.getArchitectures();
else
releaseABIs = androidConfig.getArchitectures();
}
addDebugConditionClause (target, "app_abis", debugABIs, releaseABIs);
XmlElement* executable = target->createNewChildElement ("exec");
executable->setAttribute ("executable", "${ndk.dir}/ndk-build");
executable->setAttribute ("dir", "${basedir}");
executable->setAttribute ("failonerror", "true");
executable->createNewChildElement ("arg")->setAttribute ("value", "--jobs=2");
executable->createNewChildElement ("arg")->setAttribute ("value", "CONFIG=${makefileConfig}");
executable->createNewChildElement ("arg")->setAttribute ("value", "${ndkDebugValue}");
executable->createNewChildElement ("arg")->setAttribute ("value", "APP_ABI=${app_abis}");
target->createNewChildElement ("delete")->setAttribute ("file", "${out.final.file}");
target->createNewChildElement ("delete")->setAttribute ("file", "${out.packaged.file}");
}
proj->createNewChildElement ("import")->setAttribute ("file", "${sdk.dir}/tools/ant/build.xml");
return proj;
}
void addDebugConditionClause (XmlElement* target, const String& property,
const String& debugValue, const String& releaseValue) const
{
XmlElement* condition = target->createNewChildElement ("condition");
condition->setAttribute ("property", property);
condition->setAttribute ("value", debugValue);
condition->setAttribute ("else", releaseValue);
XmlElement* equals = condition->createNewChildElement ("equals");
equals->setAttribute ("arg1", "${ant.project.invoked-targets}");
equals->setAttribute ("arg2", "debug");
}
String getAppPlatform() const
{
int ndkVersion = getMinimumSDKVersionString().getIntValue();
if (ndkVersion == 9)
ndkVersion = 10; // (doesn't seem to be a version '9')
return "android-" + String (ndkVersion);
}
void writeProjectPropertiesFile (const File& file) const
{
MemoryOutputStream mo;
mo << "# This file is used to override default values used by the Ant build system." << newLine
<< "# It is automatically generated - DO NOT EDIT IT or your changes will be lost!." << newLine
<< newLine
<< "target=" << getAppPlatform() << newLine
<< newLine;
overwriteFileIfDifferentOrThrow (file, mo);
}
void writeLocalPropertiesFile (const File& file) const
{
MemoryOutputStream mo;
mo << "# This file is used to override default values used by the Ant build system." << newLine
<< "# It is automatically generated by the Introjucer - DO NOT EDIT IT or your changes will be lost!." << newLine
<< newLine
<< "sdk.dir=" << escapeSpaces (replacePreprocessorDefs (getAllPreprocessorDefs(), getSDKPathString())) << newLine
<< "ndk.dir=" << escapeSpaces (replacePreprocessorDefs (getAllPreprocessorDefs(), getNDKPathString())) << newLine
<< "key.store=" << getKeyStoreString() << newLine
<< "key.alias=" << getKeyAliasString() << newLine
<< "key.store.password=" << getKeyStorePassString() << newLine
<< "key.alias.password=" << getKeyAliasPassString() << newLine
<< newLine;
overwriteFileIfDifferentOrThrow (file, mo);
}
void writeIcon (const File& file, const Image& im) const
{
if (im.isValid())
{
createDirectoryOrThrow (file.getParentDirectory());
PNGImageFormat png;
MemoryOutputStream mo;
if (! png.writeImageToStream (im, mo))
throw SaveError ("Can't generate Android icon file");
overwriteFileIfDifferentOrThrow (file, mo);
}
}
void writeStringsFile (const File& file) const
{
XmlElement strings ("resources");
XmlElement* resourceName = strings.createNewChildElement ("string");
resourceName->setAttribute ("name", "app_name");
resourceName->addTextElement (projectName);
writeXmlOrThrow (strings, file, "utf-8", 100);
}
//==============================================================================
JUCE_DECLARE_NON_COPYABLE (AndroidProjectExporter)
};
@@ -0,0 +1,344 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found 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.juce.com for more information.
==============================================================================
*/
class CodeBlocksProjectExporter : public ProjectExporter
{
public:
//==============================================================================
static const char* getNameCodeBlocks() { return "Code::Blocks project"; }
static const char* getValueTreeTypeName() { return "CODEBLOCKS"; }
static CodeBlocksProjectExporter* createForSettings (Project& project, const ValueTree& settings)
{
if (settings.hasType (getValueTreeTypeName()))
return new CodeBlocksProjectExporter (project, settings);
return nullptr;
}
//==============================================================================
CodeBlocksProjectExporter (Project& p, const ValueTree& t) : ProjectExporter (p, t)
{
name = getNameCodeBlocks();
if (getTargetLocationString().isEmpty())
getTargetLocationValue() = getDefaultBuildsRootFolder() + "CodeBlocks";
}
//==============================================================================
bool canLaunchProject() override { return false; }
bool launchProject() override { return false; }
bool isCodeBlocks() const override { return true; }
bool isWindows() const override { return true; }
bool usesMMFiles() const override { return false; }
bool canCopeWithDuplicateFiles() override { return false; }
void createExporterProperties (PropertyListBuilder&) override
{
}
//==============================================================================
void create (const OwnedArray<LibraryModule>&) const override
{
const File cbpFile (getTargetFolder().getChildFile (project.getProjectFilenameRoot())
.withFileExtension (".cbp"));
XmlElement xml ("CodeBlocks_project_file");
addVersion (xml);
createProject (*xml.createNewChildElement ("Project"));
writeXmlOrThrow (xml, cbpFile, "UTF-8", 10);
}
private:
//==============================================================================
class CodeBlocksBuildConfiguration : public BuildConfiguration
{
public:
CodeBlocksBuildConfiguration (Project& p, const ValueTree& settings)
: BuildConfiguration (p, settings)
{
}
void createConfigProperties (PropertyListBuilder&)
{
}
};
BuildConfiguration::Ptr createBuildConfig (const ValueTree& tree) const override
{
return new CodeBlocksBuildConfiguration (project, tree);
}
//==============================================================================
void addVersion (XmlElement& xml) const
{
XmlElement* fileVersion = xml.createNewChildElement ("FileVersion");
fileVersion->setAttribute ("major", 1);
fileVersion->setAttribute ("minor", 6);
}
void addOptions (XmlElement& xml) const
{
xml.createNewChildElement ("Option")->setAttribute ("title", project.getTitle());
xml.createNewChildElement ("Option")->setAttribute ("pch_mode", 2);
xml.createNewChildElement ("Option")->setAttribute ("compiler", "gcc");
}
static StringArray cleanArray (StringArray s)
{
s.trim();
s.removeDuplicates (false);
s.removeEmptyStrings (true);
return s;
}
StringArray getDefines (const BuildConfiguration& config) const
{
StringPairArray defines;
defines.set ("__MINGW__", "1");
defines.set ("__MINGW_EXTENSION", String::empty);
if (config.isDebug())
{
defines.set ("DEBUG", "1");
defines.set ("_DEBUG", "1");
}
else
{
defines.set ("NDEBUG", "1");
}
defines = mergePreprocessorDefs (defines, getAllPreprocessorDefs (config));
StringArray defs;
for (int i = 0; i < defines.size(); ++i)
defs.add (defines.getAllKeys()[i] + "=" + defines.getAllValues()[i]);
return cleanArray (defs);
}
StringArray getCompilerFlags (const BuildConfiguration& config) const
{
StringArray flags;
flags.add ("-O" + config.getGCCOptimisationFlag());
flags.add ("-std=c++11");
flags.add ("-mstackrealign");
if (config.isDebug())
flags.add ("-g");
flags.addTokens (replacePreprocessorTokens (config, getExtraCompilerFlagsString()).trim(),
" \n", "\"'");
{
const StringArray defines (getDefines (config));
for (int i = 0; i < defines.size(); ++i)
{
String def (defines[i]);
if (! def.containsChar ('='))
def << '=';
flags.add ("-D" + def);
}
}
return cleanArray (flags);
}
StringArray getLinkerFlags (const BuildConfiguration& config) const
{
StringArray flags;
if (! config.isDebug())
flags.add ("-s");
flags.addTokens (replacePreprocessorTokens (config, getExtraLinkerFlagsString()).trim(),
" \n", "\"'");
return cleanArray (flags);
}
StringArray getIncludePaths (const BuildConfiguration& config) const
{
StringArray paths;
paths.add (".");
paths.add (RelativePath (project.getGeneratedCodeFolder(),
getTargetFolder(), RelativePath::buildTargetFolder).toWindowsStyle());
paths.addArray (config.getHeaderSearchPaths());
return cleanArray (paths);
}
static int getTypeIndex (const ProjectType& type)
{
if (type.isGUIApplication()) return 0;
if (type.isCommandLineApp()) return 1;
if (type.isStaticLibrary()) return 2;
if (type.isDynamicLibrary()) return 3;
if (type.isAudioPlugin()) return 3;
return 0;
}
void createBuildTarget (XmlElement& xml, const BuildConfiguration& config) const
{
xml.setAttribute ("title", config.getName());
{
XmlElement* output = xml.createNewChildElement ("Option");
String outputPath;
if (config.getTargetBinaryRelativePathString().isNotEmpty())
{
RelativePath binaryPath (config.getTargetBinaryRelativePathString(), RelativePath::projectFolder);
binaryPath = binaryPath.rebased (projectFolder, getTargetFolder(), RelativePath::buildTargetFolder);
outputPath = config.getTargetBinaryRelativePathString();
}
else
{
outputPath ="bin/" + File::createLegalFileName (config.getName().trim());
}
output->setAttribute ("output", outputPath + "/" + replacePreprocessorTokens (config, config.getTargetBinaryNameString()));
output->setAttribute ("prefix_auto", 1);
output->setAttribute ("extension_auto", 1);
}
xml.createNewChildElement ("Option")
->setAttribute ("object_output", "obj/" + File::createLegalFileName (config.getName().trim()));
xml.createNewChildElement ("Option")->setAttribute ("type", getTypeIndex (project.getProjectType()));
xml.createNewChildElement ("Option")->setAttribute ("compiler", "gcc");
{
XmlElement* const compiler = xml.createNewChildElement ("Compiler");
{
const StringArray compilerFlags (getCompilerFlags (config));
for (int i = 0; i < compilerFlags.size(); ++i)
setAddOption (*compiler, "option", compilerFlags[i]);
}
{
const StringArray includePaths (getIncludePaths (config));
for (int i = 0; i < includePaths.size(); ++i)
setAddOption (*compiler, "directory", includePaths[i]);
}
}
{
XmlElement* const linker = xml.createNewChildElement ("Linker");
const StringArray linkerFlags (getLinkerFlags (config));
for (int i = 0; i < linkerFlags.size(); ++i)
setAddOption (*linker, "option", linkerFlags[i]);
for (int i = 0; i < mingwLibs.size(); ++i)
setAddOption (*linker, "library", mingwLibs[i]);
const StringArray librarySearchPaths (config.getLibrarySearchPaths());
for (int i = 0; i < librarySearchPaths.size(); ++i)
setAddOption (*linker, "directory", replacePreprocessorDefs (getAllPreprocessorDefs(), librarySearchPaths[i]));
}
}
void addBuild (XmlElement& xml) const
{
XmlElement* const build = xml.createNewChildElement ("Build");
for (ConstConfigIterator config (*this); config.next();)
createBuildTarget (*build->createNewChildElement ("Target"), *config);
}
void addProjectCompilerOptions (XmlElement& xml) const
{
XmlElement* const compiler = xml.createNewChildElement ("Compiler");
setAddOption (*compiler, "option", "-Wall");
setAddOption (*compiler, "option", "-Wno-strict-aliasing");
setAddOption (*compiler, "option", "-Wno-strict-overflow");
}
void addProjectLinkerOptions (XmlElement& xml) const
{
XmlElement* const linker = xml.createNewChildElement ("Linker");
static const char* defaultLibs[] = { "gdi32", "user32", "kernel32", "comctl32" };
StringArray libs (defaultLibs, numElementsInArray (defaultLibs));
libs.addTokens (getExternalLibrariesString(), ";\n", "\"'");
libs = cleanArray (libs);
for (int i = 0; i < libs.size(); ++i)
setAddOption (*linker, "library", replacePreprocessorDefs (getAllPreprocessorDefs(), libs[i]));
}
void addCompileUnits (const Project::Item& projectItem, XmlElement& xml) const
{
if (projectItem.isGroup())
{
for (int i = 0; i < projectItem.getNumChildren(); ++i)
addCompileUnits (projectItem.getChild(i), xml);
}
else if (projectItem.shouldBeAddedToTargetProject())
{
const RelativePath file (projectItem.getFile(), getTargetFolder(), RelativePath::buildTargetFolder);
XmlElement* unit = xml.createNewChildElement ("Unit");
unit->setAttribute ("filename", file.toUnixStyle());
if (! projectItem.shouldBeCompiled())
{
unit->createNewChildElement("Option")->setAttribute ("compile", 0);
unit->createNewChildElement("Option")->setAttribute ("link", 0);
}
}
}
void addCompileUnits (XmlElement& xml) const
{
for (int i = 0; i < getAllGroups().size(); ++i)
addCompileUnits (getAllGroups().getReference(i), xml);
}
void createProject (XmlElement& xml) const
{
addOptions (xml);
addBuild (xml);
addProjectCompilerOptions (xml);
addProjectLinkerOptions (xml);
addCompileUnits (xml);
}
void setAddOption (XmlElement& xml, const String& nm, const String& value) const
{
xml.createNewChildElement ("Add")->setAttribute (nm, value);
}
JUCE_DECLARE_NON_COPYABLE (CodeBlocksProjectExporter)
};
@@ -0,0 +1,339 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found 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.juce.com for more information.
==============================================================================
*/
class MakefileProjectExporter : public ProjectExporter
{
public:
//==============================================================================
static const char* getNameLinux() { return "Linux Makefile"; }
static const char* getValueTreeTypeName() { return "LINUX_MAKE"; }
static MakefileProjectExporter* createForSettings (Project& project, const ValueTree& settings)
{
if (settings.hasType (getValueTreeTypeName()))
return new MakefileProjectExporter (project, settings);
return nullptr;
}
//==============================================================================
MakefileProjectExporter (Project& p, const ValueTree& t) : ProjectExporter (p, t)
{
name = getNameLinux();
if (getTargetLocationString().isEmpty())
getTargetLocationValue() = getDefaultBuildsRootFolder() + "Linux";
}
//==============================================================================
bool canLaunchProject() override { return false; }
bool launchProject() override { return false; }
bool usesMMFiles() const override { return false; }
bool isLinux() const override { return true; }
bool canCopeWithDuplicateFiles() override { return false; }
void createExporterProperties (PropertyListBuilder&) override
{
}
//==============================================================================
void create (const OwnedArray<LibraryModule>&) const override
{
Array<RelativePath> files;
for (int i = 0; i < getAllGroups().size(); ++i)
findAllFilesToCompile (getAllGroups().getReference(i), files);
MemoryOutputStream mo;
writeMakefile (mo, files);
overwriteFileIfDifferentOrThrow (getTargetFolder().getChildFile ("Makefile"), mo);
}
protected:
//==============================================================================
class MakeBuildConfiguration : public BuildConfiguration
{
public:
MakeBuildConfiguration (Project& p, const ValueTree& settings)
: BuildConfiguration (p, settings)
{
setValueIfVoid (getLibrarySearchPathValue(), "/usr/X11R6/lib/");
}
Value getArchitectureType() { return getValue (Ids::linuxArchitecture); }
String getArchitectureTypeString() const { return config [Ids::linuxArchitecture]; }
void createConfigProperties (PropertyListBuilder& props) override
{
static const char* const archNames[] = { "(Default)", "32-bit (-m32)", "64-bit (-m64)", "ARM v6", "ARM v7" };
const var archFlags[] = { var(), "-m32", "-m64", "-march=armv6", "-march=armv7" };
props.add (new ChoicePropertyComponent (getArchitectureType(), "Architecture",
StringArray (archNames, numElementsInArray (archNames)),
Array<var> (archFlags, numElementsInArray (archFlags))));
}
};
BuildConfiguration::Ptr createBuildConfig (const ValueTree& tree) const override
{
return new MakeBuildConfiguration (project, tree);
}
private:
//==============================================================================
void findAllFilesToCompile (const Project::Item& projectItem, Array<RelativePath>& results) const
{
if (projectItem.isGroup())
{
for (int i = 0; i < projectItem.getNumChildren(); ++i)
findAllFilesToCompile (projectItem.getChild(i), results);
}
else
{
if (projectItem.shouldBeCompiled())
results.add (RelativePath (projectItem.getFile(), getTargetFolder(), RelativePath::buildTargetFolder));
}
}
void writeDefineFlags (OutputStream& out, const BuildConfiguration& config) const
{
StringPairArray defines;
defines.set ("LINUX", "1");
if (config.isDebug())
{
defines.set ("DEBUG", "1");
defines.set ("_DEBUG", "1");
}
else
{
defines.set ("NDEBUG", "1");
}
out << createGCCPreprocessorFlags (mergePreprocessorDefs (defines, getAllPreprocessorDefs (config)));
}
void writeHeaderPathFlags (OutputStream& out, const BuildConfiguration& config) const
{
StringArray searchPaths (extraSearchPaths);
searchPaths.addArray (config.getHeaderSearchPaths());
searchPaths.insert (0, "/usr/include/freetype2");
searchPaths.insert (0, "/usr/include");
searchPaths.removeDuplicates (false);
for (int i = 0; i < searchPaths.size(); ++i)
out << " -I " << addQuotesIfContainsSpaces (FileHelpers::unixStylePath (replacePreprocessorTokens (config, searchPaths[i])));
}
void writeCppFlags (OutputStream& out, const BuildConfiguration& config) const
{
out << " CPPFLAGS := $(DEPFLAGS) -std=c++11";
writeDefineFlags (out, config);
writeHeaderPathFlags (out, config);
out << newLine;
}
void writeLinkerFlags (OutputStream& out, const BuildConfiguration& config) const
{
out << " LDFLAGS += $(TARGET_ARCH) -L$(BINDIR) -L$(LIBDIR)";
if (makefileIsDLL)
out << " -shared";
if (! config.isDebug())
out << " -fvisibility=hidden";
out << config.getGCCLibraryPathFlags();
for (int i = 0; i < linuxLibs.size(); ++i)
out << " -l" << linuxLibs[i];
StringArray libraries;
libraries.addTokens (getExternalLibrariesString(), ";", "\"'");
libraries.removeEmptyStrings();
if (libraries.size() != 0)
out << " -l" << replacePreprocessorTokens (config, libraries.joinIntoString (" -l")).trim();
out << " " << replacePreprocessorTokens (config, getExtraLinkerFlagsString()).trim()
<< newLine;
}
void writeConfig (OutputStream& out, const BuildConfiguration& config) const
{
const String buildDirName ("build");
const String intermediatesDirName (buildDirName + "/intermediate/" + config.getName());
String outputDir (buildDirName);
if (config.getTargetBinaryRelativePathString().isNotEmpty())
{
RelativePath binaryPath (config.getTargetBinaryRelativePathString(), RelativePath::projectFolder);
outputDir = binaryPath.rebased (projectFolder, getTargetFolder(), RelativePath::buildTargetFolder).toUnixStyle();
}
out << "ifeq ($(CONFIG)," << escapeSpaces (config.getName()) << ")" << newLine;
out << " BINDIR := " << escapeSpaces (buildDirName) << newLine
<< " LIBDIR := " << escapeSpaces (buildDirName) << newLine
<< " OBJDIR := " << escapeSpaces (intermediatesDirName) << newLine
<< " OUTDIR := " << escapeSpaces (outputDir) << newLine
<< newLine
<< " ifeq ($(TARGET_ARCH),)" << newLine
<< " TARGET_ARCH := " << getArchFlags (config) << newLine
<< " endif" << newLine
<< newLine;
writeCppFlags (out, config);
out << " CFLAGS += $(CPPFLAGS) $(TARGET_ARCH)";
if (config.isDebug())
out << " -g -ggdb";
if (makefileIsDLL)
out << " -fPIC";
out << " -O" << config.getGCCOptimisationFlag()
<< (" " + replacePreprocessorTokens (config, getExtraCompilerFlagsString())).trimEnd()
<< newLine;
out << " CXXFLAGS += $(CFLAGS)" << newLine;
writeLinkerFlags (out, config);
out << " LDDEPS :=" << newLine
<< " RESFLAGS := ";
writeDefineFlags (out, config);
writeHeaderPathFlags (out, config);
out << newLine;
String targetName (replacePreprocessorTokens (config, config.getTargetBinaryNameString()));
if (projectType.isStaticLibrary() || projectType.isDynamicLibrary())
targetName = getLibbedFilename (targetName);
else
targetName = targetName.upToLastOccurrenceOf (".", false, false) + makefileTargetSuffix;
out << " TARGET := " << escapeSpaces (targetName) << newLine;
if (projectType.isStaticLibrary())
out << " BLDCMD = ar -rcs $(OUTDIR)/$(TARGET) $(OBJECTS)" << newLine;
else
out << " BLDCMD = $(CXX) -o $(OUTDIR)/$(TARGET) $(OBJECTS) $(LDFLAGS) $(RESOURCES) $(TARGET_ARCH)" << newLine;
out << " CLEANCMD = rm -rf $(OUTDIR)/$(TARGET) $(OBJDIR)" << newLine
<< "endif" << newLine
<< newLine;
}
void writeObjects (OutputStream& out, const Array<RelativePath>& files) const
{
out << "OBJECTS := \\" << newLine;
for (int i = 0; i < files.size(); ++i)
if (shouldFileBeCompiledByDefault (files.getReference(i)))
out << " $(OBJDIR)/" << escapeSpaces (getObjectFileFor (files.getReference(i))) << " \\" << newLine;
out << newLine;
}
void writeMakefile (OutputStream& out, const Array<RelativePath>& files) const
{
out << "# Automatically generated makefile, created by the Introjucer" << newLine
<< "# Don't edit this file! Your changes will be overwritten when you re-save the Introjucer project!" << newLine
<< newLine;
out << "# (this disables dependency generation if multiple architectures are set)" << newLine
<< "DEPFLAGS := $(if $(word 2, $(TARGET_ARCH)), , -MMD)" << newLine
<< newLine;
out << "ifndef CONFIG" << newLine
<< " CONFIG=" << escapeSpaces (getConfiguration(0)->getName()) << newLine
<< "endif" << newLine
<< newLine;
for (ConstConfigIterator config (*this); config.next();)
writeConfig (out, *config);
writeObjects (out, files);
out << ".PHONY: clean" << newLine
<< newLine;
out << "$(OUTDIR)/$(TARGET): $(OBJECTS) $(LDDEPS) $(RESOURCES)" << newLine
<< "\t@echo Linking " << projectName << newLine
<< "\t-@mkdir -p $(BINDIR)" << newLine
<< "\t-@mkdir -p $(LIBDIR)" << newLine
<< "\t-@mkdir -p $(OUTDIR)" << newLine
<< "\t@$(BLDCMD)" << newLine
<< newLine;
out << "clean:" << newLine
<< "\t@echo Cleaning " << projectName << newLine
<< "\t@$(CLEANCMD)" << newLine
<< newLine;
out << "strip:" << newLine
<< "\t@echo Stripping " << projectName << newLine
<< "\t-@strip --strip-unneeded $(OUTDIR)/$(TARGET)" << newLine
<< newLine;
for (int i = 0; i < files.size(); ++i)
{
if (shouldFileBeCompiledByDefault (files.getReference(i)))
{
jassert (files.getReference(i).getRoot() == RelativePath::buildTargetFolder);
out << "$(OBJDIR)/" << escapeSpaces (getObjectFileFor (files.getReference(i)))
<< ": " << escapeSpaces (files.getReference(i).toUnixStyle()) << newLine
<< "\t-@mkdir -p $(OBJDIR)" << newLine
<< "\t@echo \"Compiling " << files.getReference(i).getFileName() << "\"" << newLine
<< (files.getReference(i).hasFileExtension ("c;s;S") ? "\t@$(CC) $(CFLAGS) -o \"$@\" -c \"$<\""
: "\t@$(CXX) $(CXXFLAGS) -o \"$@\" -c \"$<\"")
<< newLine << newLine;
}
}
out << "-include $(OBJECTS:%.o=%.d)" << newLine;
}
String getArchFlags (const BuildConfiguration& config) const
{
if (const MakeBuildConfiguration* makeConfig = dynamic_cast<const MakeBuildConfiguration*> (&config))
if (makeConfig->getArchitectureTypeString().isNotEmpty())
return makeConfig->getArchitectureTypeString();
return "-march=native";
}
String getObjectFileFor (const RelativePath& file) const
{
return file.getFileNameWithoutExtension()
+ "_" + String::toHexString (file.toUnixStyle().hashCode()) + ".o";
}
JUCE_DECLARE_NON_COPYABLE (MakefileProjectExporter)
};
@@ -0,0 +1,722 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found 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.juce.com for more information.
==============================================================================
*/
#include "jucer_ProjectExporter.h"
#include "jucer_ProjectSaver.h"
#include "jucer_ProjectExport_Make.h"
#include "jucer_ProjectExport_MSVC.h"
#include "jucer_ProjectExport_XCode.h"
#include "jucer_ProjectExport_Android.h"
#include "jucer_ProjectExport_CodeBlocks.h"
//==============================================================================
static void addType (Array<ProjectExporter::ExporterTypeInfo>& list,
const char* name, const void* iconData, int iconDataSize)
{
ProjectExporter::ExporterTypeInfo type = { name, iconData, iconDataSize };
list.add (type);
}
Array<ProjectExporter::ExporterTypeInfo> ProjectExporter::getExporterTypes()
{
Array<ProjectExporter::ExporterTypeInfo> types;
addType (types, XCodeProjectExporter::getNameMac(), BinaryData::projectIconXcode_png, BinaryData::projectIconXcode_pngSize);
addType (types, XCodeProjectExporter::getNameiOS(), BinaryData::projectIconXcodeIOS_png, BinaryData::projectIconXcodeIOS_pngSize);
addType (types, MSVCProjectExporterVC2015::getName(), BinaryData::projectIconVisualStudio_png, BinaryData::projectIconVisualStudio_pngSize);
addType (types, MSVCProjectExporterVC2013::getName(), BinaryData::projectIconVisualStudio_png, BinaryData::projectIconVisualStudio_pngSize);
addType (types, MSVCProjectExporterVC2012::getName(), BinaryData::projectIconVisualStudio_png, BinaryData::projectIconVisualStudio_pngSize);
addType (types, MSVCProjectExporterVC2010::getName(), BinaryData::projectIconVisualStudio_png, BinaryData::projectIconVisualStudio_pngSize);
addType (types, MSVCProjectExporterVC2008::getName(), BinaryData::projectIconVisualStudio_png, BinaryData::projectIconVisualStudio_pngSize);
addType (types, MSVCProjectExporterVC2005::getName(), BinaryData::projectIconVisualStudio_png, BinaryData::projectIconVisualStudio_pngSize);
addType (types, MakefileProjectExporter::getNameLinux(), BinaryData::projectIconLinuxMakefile_png, BinaryData::projectIconLinuxMakefile_pngSize);
addType (types, AndroidProjectExporter::getNameAndroid(), BinaryData::projectIconAndroid_png, BinaryData::projectIconAndroid_pngSize);
addType (types, CodeBlocksProjectExporter::getNameCodeBlocks(), BinaryData::projectIconCodeblocks_png, BinaryData::projectIconCodeblocks_pngSize);
return types;
}
ProjectExporter* ProjectExporter::createNewExporter (Project& project, const int index)
{
ProjectExporter* exp = nullptr;
switch (index)
{
case 0: exp = new XCodeProjectExporter (project, ValueTree (XCodeProjectExporter ::getValueTreeTypeName (false)), false); break;
case 1: exp = new XCodeProjectExporter (project, ValueTree (XCodeProjectExporter ::getValueTreeTypeName (true)), true); break;
case 2: exp = new MSVCProjectExporterVC2015 (project, ValueTree (MSVCProjectExporterVC2015::getValueTreeTypeName())); break;
case 3: exp = new MSVCProjectExporterVC2013 (project, ValueTree (MSVCProjectExporterVC2013::getValueTreeTypeName())); break;
case 4: exp = new MSVCProjectExporterVC2012 (project, ValueTree (MSVCProjectExporterVC2012::getValueTreeTypeName())); break;
case 5: exp = new MSVCProjectExporterVC2010 (project, ValueTree (MSVCProjectExporterVC2010::getValueTreeTypeName())); break;
case 6: exp = new MSVCProjectExporterVC2008 (project, ValueTree (MSVCProjectExporterVC2008::getValueTreeTypeName())); break;
case 7: exp = new MSVCProjectExporterVC2005 (project, ValueTree (MSVCProjectExporterVC2005::getValueTreeTypeName())); break;
case 8: exp = new MakefileProjectExporter (project, ValueTree (MakefileProjectExporter ::getValueTreeTypeName())); break;
case 9: exp = new AndroidProjectExporter (project, ValueTree (AndroidProjectExporter ::getValueTreeTypeName())); break;
case 10: exp = new CodeBlocksProjectExporter (project, ValueTree (CodeBlocksProjectExporter::getValueTreeTypeName())); break;
default: jassertfalse; return 0;
}
exp->createDefaultConfigs();
exp->createDefaultModulePaths();
return exp;
}
StringArray ProjectExporter::getExporterNames()
{
StringArray s;
Array<ExporterTypeInfo> types (getExporterTypes());
for (int i = 0; i < types.size(); ++i)
s.add (types.getReference(i).name);
return s;
}
String ProjectExporter::getCurrentPlatformExporterName()
{
#if JUCE_MAC
return XCodeProjectExporter::getNameMac();
#elif JUCE_WINDOWS
return MSVCProjectExporterVC2010::getName();
#elif JUCE_LINUX
return MakefileProjectExporter::getNameLinux();
#else
#error // huh?
#endif
}
ProjectExporter* ProjectExporter::createNewExporter (Project& project, const String& name)
{
return createNewExporter (project, getExporterNames().indexOf (name));
}
ProjectExporter* ProjectExporter::createExporter (Project& project, const ValueTree& settings)
{
ProjectExporter* exp = MSVCProjectExporterVC2005::createForSettings (project, settings);
if (exp == nullptr) exp = MSVCProjectExporterVC2008::createForSettings (project, settings);
if (exp == nullptr) exp = MSVCProjectExporterVC2010::createForSettings (project, settings);
if (exp == nullptr) exp = MSVCProjectExporterVC2012::createForSettings (project, settings);
if (exp == nullptr) exp = MSVCProjectExporterVC2013::createForSettings (project, settings);
if (exp == nullptr) exp = MSVCProjectExporterVC2015::createForSettings (project, settings);
if (exp == nullptr) exp = XCodeProjectExporter ::createForSettings (project, settings);
if (exp == nullptr) exp = MakefileProjectExporter ::createForSettings (project, settings);
if (exp == nullptr) exp = AndroidProjectExporter ::createForSettings (project, settings);
if (exp == nullptr) exp = CodeBlocksProjectExporter::createForSettings (project, settings);
jassert (exp != nullptr);
return exp;
}
bool ProjectExporter::canProjectBeLaunched (Project* project)
{
if (project != nullptr)
{
const char* types[] =
{
#if JUCE_MAC
XCodeProjectExporter::getValueTreeTypeName (false),
XCodeProjectExporter::getValueTreeTypeName (true),
#elif JUCE_WINDOWS
MSVCProjectExporterVC2005::getValueTreeTypeName(),
MSVCProjectExporterVC2008::getValueTreeTypeName(),
MSVCProjectExporterVC2010::getValueTreeTypeName(),
MSVCProjectExporterVC2012::getValueTreeTypeName(),
MSVCProjectExporterVC2013::getValueTreeTypeName(),
MSVCProjectExporterVC2015::getValueTreeTypeName(),
#elif JUCE_LINUX
// (this doesn't currently launch.. not really sure what it would do on linux)
//MakefileProjectExporter::getValueTreeTypeName(),
#endif
nullptr
};
for (const char** type = types; *type != nullptr; ++type)
if (project->getExporters().getChildWithName (*type).isValid())
return true;
}
return false;
}
//==============================================================================
ProjectExporter::ProjectExporter (Project& p, const ValueTree& state)
: xcodeIsBundle (false),
xcodeCreatePList (false),
xcodeCanUseDwarf (true),
makefileIsDLL (false),
msvcIsDLL (false),
msvcIsWindowsSubsystem (true),
settings (state),
project (p),
projectType (p.getProjectType()),
projectName (p.getTitle()),
projectFolder (p.getProjectFolder()),
modulesGroup (nullptr)
{
}
ProjectExporter::~ProjectExporter()
{
}
File ProjectExporter::getTargetFolder() const
{
return project.resolveFilename (getTargetLocationString());
}
RelativePath ProjectExporter::rebaseFromProjectFolderToBuildTarget (const RelativePath& path) const
{
return path.rebased (project.getProjectFolder(), getTargetFolder(), RelativePath::buildTargetFolder);
}
bool ProjectExporter::shouldFileBeCompiledByDefault (const RelativePath& file) const
{
return file.hasFileExtension (cOrCppFileExtensions)
|| file.hasFileExtension (asmFileExtensions);
}
void ProjectExporter::createPropertyEditors (PropertyListBuilder& props)
{
props.add (new TextPropertyComponent (getTargetLocationValue(), "Target Project Folder", 2048, false),
"The location of the folder in which the " + name + " project will be created. "
"This path can be absolute, but it's much more sensible to make it relative to the jucer project directory.");
OwnedArray<LibraryModule> modules;
project.getModules().createRequiredModules (modules);
for (int i = 0; i < modules.size(); ++i)
modules.getUnchecked(i)->createPropertyEditors (*this, props);
props.add (new TextPropertyComponent (getExporterPreprocessorDefs(), "Extra Preprocessor Definitions", 32768, true),
"Extra preprocessor definitions. Use the form \"NAME1=value NAME2=value\", using whitespace, commas, "
"or new-lines to separate the items - to include a space or comma in a definition, precede it with a backslash.");
props.add (new TextPropertyComponent (getExtraCompilerFlags(), "Extra compiler flags", 8192, true),
"Extra command-line flags to be passed to the compiler. This string can contain references to preprocessor definitions in the "
"form ${NAME_OF_DEFINITION}, which will be replaced with their values.");
props.add (new TextPropertyComponent (getExtraLinkerFlags(), "Extra linker flags", 8192, true),
"Extra command-line flags to be passed to the linker. You might want to use this for adding additional libraries. "
"This string can contain references to preprocessor definitions in the form ${NAME_OF_VALUE}, which will be replaced with their values.");
props.add (new TextPropertyComponent (getExternalLibraries(), "External libraries to link", 8192, true),
"Additional libraries to link (one per line). You should not add any platform specific decoration to these names. "
"This string can contain references to preprocessor definitions in the form ${NAME_OF_VALUE}, which will be replaced with their values.");
{
OwnedArray<Project::Item> images;
project.findAllImageItems (images);
StringArray choices;
Array<var> ids;
choices.add ("<None>");
ids.add (var::null);
choices.add (String::empty);
ids.add (var::null);
for (int i = 0; i < images.size(); ++i)
{
choices.add (images.getUnchecked(i)->getName());
ids.add (images.getUnchecked(i)->getID());
}
props.add (new ChoicePropertyComponent (getSmallIconImageItemID(), "Icon (small)", choices, ids),
"Sets an icon to use for the executable.");
props.add (new ChoicePropertyComponent (getBigIconImageItemID(), "Icon (large)", choices, ids),
"Sets an icon to use for the executable.");
}
createExporterProperties (props);
props.add (new TextPropertyComponent (getUserNotes(), "Notes", 32768, true),
"Extra comments: This field is not used for code or project generation, it's just a space where you can express your thoughts.");
}
StringPairArray ProjectExporter::getAllPreprocessorDefs (const ProjectExporter::BuildConfiguration& config) const
{
StringPairArray defs (mergePreprocessorDefs (config.getAllPreprocessorDefs(),
parsePreprocessorDefs (getExporterPreprocessorDefsString())));
addDefaultPreprocessorDefs (defs);
return defs;
}
StringPairArray ProjectExporter::getAllPreprocessorDefs() const
{
StringPairArray defs (mergePreprocessorDefs (project.getPreprocessorDefs(),
parsePreprocessorDefs (getExporterPreprocessorDefsString())));
addDefaultPreprocessorDefs (defs);
return defs;
}
void ProjectExporter::addDefaultPreprocessorDefs (StringPairArray& defs) const
{
defs.set (getExporterIdentifierMacro(), "1");
defs.set ("JUCE_APP_VERSION", project.getVersionString());
defs.set ("JUCE_APP_VERSION_HEX", project.getVersionAsHex());
}
String ProjectExporter::replacePreprocessorTokens (const ProjectExporter::BuildConfiguration& config, const String& sourceString) const
{
return replacePreprocessorDefs (getAllPreprocessorDefs (config), sourceString);
}
void ProjectExporter::copyMainGroupFromProject()
{
jassert (itemGroups.size() == 0);
itemGroups.add (project.getMainGroup().createCopy());
}
Project::Item& ProjectExporter::getModulesGroup()
{
if (modulesGroup == nullptr)
{
jassert (itemGroups.size() > 0); // must call copyMainGroupFromProject before this.
itemGroups.add (Project::Item::createGroup (project, "Juce Modules", "__modulesgroup__"));
modulesGroup = &(itemGroups.getReference (itemGroups.size() - 1));
}
return *modulesGroup;
}
void ProjectExporter::addToExtraSearchPaths (const RelativePath& pathFromProjectFolder)
{
RelativePath localPath (rebaseFromProjectFolderToBuildTarget (pathFromProjectFolder));
const String path (isVisualStudio() ? localPath.toWindowsStyle() : localPath.toUnixStyle());
extraSearchPaths.addIfNotAlreadyThere (path, false);
}
Value ProjectExporter::getPathForModuleValue (const String& moduleID)
{
UndoManager* um = project.getUndoManagerFor (settings);
ValueTree paths (settings.getOrCreateChildWithName (Ids::MODULEPATHS, um));
ValueTree m (paths.getChildWithProperty (Ids::ID, moduleID));
if (! m.isValid())
{
m = ValueTree (Ids::MODULEPATH);
m.setProperty (Ids::ID, moduleID, um);
paths.addChild (m, -1, um);
}
return m.getPropertyAsValue (Ids::path, um);
}
String ProjectExporter::getPathForModuleString (const String& moduleID) const
{
return settings.getChildWithName (Ids::MODULEPATHS)
.getChildWithProperty (Ids::ID, moduleID) [Ids::path].toString();
}
void ProjectExporter::removePathForModule (const String& moduleID)
{
ValueTree paths (settings.getChildWithName (Ids::MODULEPATHS));
ValueTree m (paths.getChildWithProperty (Ids::ID, moduleID));
paths.removeChild (m, project.getUndoManagerFor (settings));
}
RelativePath ProjectExporter::getModuleFolderRelativeToProject (const String& moduleID, ProjectSaver& projectSaver) const
{
if (project.getModules().shouldCopyModuleFilesLocally (moduleID).getValue())
return RelativePath (project.getRelativePathForFile (projectSaver.getLocalModuleFolder (moduleID)),
RelativePath::projectFolder);
String path (getPathForModuleString (moduleID));
if (path.isEmpty())
return getLegacyModulePath (moduleID).getChildFile (moduleID);
return RelativePath (path, RelativePath::projectFolder).getChildFile (moduleID);
}
String ProjectExporter::getLegacyModulePath() const
{
return getSettingString ("juceFolder");
}
RelativePath ProjectExporter::getLegacyModulePath (const String& moduleID) const
{
if (project.getModules().state.getChildWithProperty (Ids::ID, moduleID) ["useLocalCopy"])
return RelativePath (project.getRelativePathForFile (project.getGeneratedCodeFolder()
.getChildFile ("modules")
.getChildFile (moduleID)), RelativePath::projectFolder);
String oldJucePath (getLegacyModulePath());
if (oldJucePath.isEmpty())
return RelativePath();
RelativePath p (oldJucePath, RelativePath::projectFolder);
if (p.getFileName() != "modules")
p = p.getChildFile ("modules");
return p.getChildFile (moduleID);
}
void ProjectExporter::updateOldModulePaths()
{
String oldPath (getLegacyModulePath());
if (oldPath.isNotEmpty())
{
for (int i = project.getModules().getNumModules(); --i >= 0;)
{
String modID (project.getModules().getModuleID(i));
getPathForModuleValue (modID) = getLegacyModulePath (modID).getParentDirectory().toUnixStyle();
}
settings.removeProperty ("juceFolder", nullptr);
}
}
static bool areCompatibleExporters (const ProjectExporter& p1, const ProjectExporter& p2)
{
return (p1.isVisualStudio() && p2.isVisualStudio())
|| (p1.isXcode() && p2.isXcode())
|| (p1.isLinux() && p2.isLinux())
|| (p1.isAndroid() && p2.isAndroid())
|| (p1.isCodeBlocks() && p2.isCodeBlocks());
}
void ProjectExporter::createDefaultModulePaths()
{
for (Project::ExporterIterator exporter (project); exporter.next();)
{
if (areCompatibleExporters (*this, *exporter))
{
for (int i = project.getModules().getNumModules(); --i >= 0;)
{
String modID (project.getModules().getModuleID(i));
getPathForModuleValue (modID) = exporter->getPathForModuleValue (modID).getValue();
}
return;
}
}
for (Project::ExporterIterator exporter (project); exporter.next();)
{
if (exporter->canLaunchProject())
{
for (int i = project.getModules().getNumModules(); --i >= 0;)
{
String modID (project.getModules().getModuleID(i));
getPathForModuleValue (modID) = exporter->getPathForModuleValue (modID).getValue();
}
return;
}
}
for (int i = project.getModules().getNumModules(); --i >= 0;)
{
String modID (project.getModules().getModuleID(i));
getPathForModuleValue (modID) = "../../juce";
}
}
//==============================================================================
ValueTree ProjectExporter::getConfigurations() const
{
return settings.getChildWithName (Ids::CONFIGURATIONS);
}
int ProjectExporter::getNumConfigurations() const
{
return getConfigurations().getNumChildren();
}
ProjectExporter::BuildConfiguration::Ptr ProjectExporter::getConfiguration (int index) const
{
return createBuildConfig (getConfigurations().getChild (index));
}
bool ProjectExporter::hasConfigurationNamed (const String& nameToFind) const
{
const ValueTree configs (getConfigurations());
for (int i = configs.getNumChildren(); --i >= 0;)
if (configs.getChild(i) [Ids::name].toString() == nameToFind)
return true;
return false;
}
String ProjectExporter::getUniqueConfigName (String nm) const
{
String nameRoot (nm);
while (CharacterFunctions::isDigit (nameRoot.getLastCharacter()))
nameRoot = nameRoot.dropLastCharacters (1);
nameRoot = nameRoot.trim();
int suffix = 2;
while (hasConfigurationNamed (name))
nm = nameRoot + " " + String (suffix++);
return nm;
}
void ProjectExporter::addNewConfiguration (const BuildConfiguration* configToCopy)
{
const String configName (getUniqueConfigName (configToCopy != nullptr ? configToCopy->config [Ids::name].toString()
: "New Build Configuration"));
ValueTree configs (getConfigurations());
if (! configs.isValid())
{
settings.addChild (ValueTree (Ids::CONFIGURATIONS), 0, project.getUndoManagerFor (settings));
configs = getConfigurations();
}
ValueTree newConfig (Ids::CONFIGURATION);
if (configToCopy != nullptr)
newConfig = configToCopy->config.createCopy();
newConfig.setProperty (Ids::name, configName, 0);
configs.addChild (newConfig, -1, project.getUndoManagerFor (configs));
}
void ProjectExporter::BuildConfiguration::removeFromExporter()
{
ValueTree configs (config.getParent());
configs.removeChild (config, project.getUndoManagerFor (configs));
}
void ProjectExporter::createDefaultConfigs()
{
settings.getOrCreateChildWithName (Ids::CONFIGURATIONS, nullptr);
for (int i = 0; i < 2; ++i)
{
addNewConfiguration (nullptr);
BuildConfiguration::Ptr config (getConfiguration (i));
const bool debugConfig = i == 0;
config->getNameValue() = debugConfig ? "Debug" : "Release";
config->isDebugValue() = debugConfig;
config->getOptimisationLevel() = debugConfig ? optimisationOff : optimiseMinSize;
config->getTargetBinaryName() = project.getProjectFilenameRoot();
}
}
Drawable* ProjectExporter::getBigIcon() const
{
return project.getMainGroup().findItemWithID (settings [Ids::bigIcon]).loadAsImageFile();
}
Drawable* ProjectExporter::getSmallIcon() const
{
return project.getMainGroup().findItemWithID (settings [Ids::smallIcon]).loadAsImageFile();
}
Image ProjectExporter::getBestIconForSize (int size, bool returnNullIfNothingBigEnough) const
{
Drawable* im = nullptr;
ScopedPointer<Drawable> im1 (getSmallIcon());
ScopedPointer<Drawable> im2 (getBigIcon());
if (im1 != nullptr && im2 != nullptr)
{
if (im1->getWidth() >= size && im2->getWidth() >= size)
im = im1->getWidth() < im2->getWidth() ? im1 : im2;
else if (im1->getWidth() >= size)
im = im1;
else if (im2->getWidth() >= size)
im = im2;
}
else
{
im = im1 != nullptr ? im1 : im2;
}
if (im == nullptr)
return Image();
if (returnNullIfNothingBigEnough && im->getWidth() < size && im->getHeight() < size)
return Image();
return rescaleImageForIcon (*im, size);
}
Image ProjectExporter::rescaleImageForIcon (Drawable& d, const int size)
{
if (DrawableImage* drawableImage = dynamic_cast<DrawableImage*> (&d))
{
Image im = SoftwareImageType().convert (drawableImage->getImage());
if (size == im.getWidth() && size == im.getHeight())
return im;
// (scale it down in stages for better resampling)
while (im.getWidth() > 2 * size && im.getHeight() > 2 * size)
im = im.rescaled (im.getWidth() / 2,
im.getHeight() / 2);
Image newIm (Image::ARGB, size, size, true, SoftwareImageType());
Graphics g (newIm);
g.drawImageWithin (im, 0, 0, size, size,
RectanglePlacement::centred | RectanglePlacement::onlyReduceInSize, false);
return newIm;
}
Image im (Image::ARGB, size, size, true, SoftwareImageType());
Graphics g (im);
d.drawWithin (g, im.getBounds().toFloat(), RectanglePlacement::centred, 1.0f);
return im;
}
//==============================================================================
ProjectExporter::ConfigIterator::ConfigIterator (ProjectExporter& e)
: index (-1), exporter (e)
{
}
bool ProjectExporter::ConfigIterator::next()
{
if (++index >= exporter.getNumConfigurations())
return false;
config = exporter.getConfiguration (index);
return true;
}
ProjectExporter::ConstConfigIterator::ConstConfigIterator (const ProjectExporter& exporter_)
: index (-1), exporter (exporter_)
{
}
bool ProjectExporter::ConstConfigIterator::next()
{
if (++index >= exporter.getNumConfigurations())
return false;
config = exporter.getConfiguration (index);
return true;
}
//==============================================================================
ProjectExporter::BuildConfiguration::BuildConfiguration (Project& p, const ValueTree& configNode)
: config (configNode), project (p)
{
}
ProjectExporter::BuildConfiguration::~BuildConfiguration()
{
}
String ProjectExporter::BuildConfiguration::getGCCOptimisationFlag() const
{
switch (getOptimisationLevelInt())
{
case optimiseMaxSpeed: return "3";
case optimiseMinSize: return "s";
default: return "0";
}
}
void ProjectExporter::BuildConfiguration::createPropertyEditors (PropertyListBuilder& props)
{
props.add (new TextPropertyComponent (getNameValue(), "Name", 96, false),
"The name of this configuration.");
props.add (new BooleanPropertyComponent (isDebugValue(), "Debug mode", "Debugging enabled"),
"If enabled, this means that the configuration should be built with debug synbols.");
static const char* optimisationLevels[] = { "No optimisation", "Minimise size", "Maximise speed", 0 };
const int optimisationLevelValues[] = { optimisationOff, optimiseMinSize, optimiseMaxSpeed, 0 };
props.add (new ChoicePropertyComponent (getOptimisationLevel(), "Optimisation",
StringArray (optimisationLevels), Array<var> (optimisationLevelValues)),
"The optimisation level for this configuration");
props.add (new TextPropertyComponent (getTargetBinaryName(), "Binary name", 256, false),
"The filename to use for the destination binary executable file. If you don't add a suffix to this name, "
"a suitable platform-specific suffix will be added automatically.");
props.add (new TextPropertyComponent (getTargetBinaryRelativePath(), "Binary location", 1024, false),
"The folder in which the finished binary should be placed. Leave this blank to cause the binary to be placed "
"in its default location in the build folder.");
props.addSearchPathProperty (getHeaderSearchPathValue(), "Header search paths", "Extra header search paths.");
props.addSearchPathProperty (getLibrarySearchPathValue(), "Extra library search paths", "Extra library search paths.");
props.add (new TextPropertyComponent (getBuildConfigPreprocessorDefs(), "Preprocessor definitions", 32768, true),
"Extra preprocessor definitions. Use the form \"NAME1=value NAME2=value\", using whitespace, commas, or "
"new-lines to separate the items - to include a space or comma in a definition, precede it with a backslash.");
createConfigProperties (props);
props.add (new TextPropertyComponent (getUserNotes(), "Notes", 32768, true),
"Extra comments: This field is not used for code or project generation, it's just a space where you can express your thoughts.");
}
StringPairArray ProjectExporter::BuildConfiguration::getAllPreprocessorDefs() const
{
return mergePreprocessorDefs (project.getPreprocessorDefs(),
parsePreprocessorDefs (getBuildConfigPreprocessorDefsString()));
}
StringArray ProjectExporter::BuildConfiguration::getHeaderSearchPaths() const
{
return getSearchPathsFromString (getHeaderSearchPathString());
}
StringArray ProjectExporter::BuildConfiguration::getLibrarySearchPaths() const
{
return getSearchPathsFromString (getLibrarySearchPathString());
}
String ProjectExporter::BuildConfiguration::getGCCLibraryPathFlags() const
{
String s;
const StringArray libraryPaths (getLibrarySearchPaths());
for (int i = 0; i < libraryPaths.size(); ++i)
s << " -L" << addQuotesIfContainsSpaces (libraryPaths[i]);
return s;
}
String ProjectExporter::getExternalLibraryFlags (const BuildConfiguration& config) const
{
StringArray libraries;
libraries.addTokens (getExternalLibrariesString(), ";\n", "\"'");
libraries.removeEmptyStrings (true);
if (libraries.size() != 0)
return replacePreprocessorTokens (config, "-l" + libraries.joinIntoString (" -l")).trim();
return String::empty;
}
@@ -0,0 +1,385 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found 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.juce.com for more information.
==============================================================================
*/
#ifndef __JUCER_PROJECTEXPORTER_JUCEHEADER__
#define __JUCER_PROJECTEXPORTER_JUCEHEADER__
#include "../jucer_Headers.h"
#include "../Project/jucer_Project.h"
#include "../Project/jucer_ProjectType.h"
class ProjectSaver;
//==============================================================================
class ProjectExporter
{
public:
ProjectExporter (Project&, const ValueTree& settings);
virtual ~ProjectExporter();
struct ExporterTypeInfo
{
String name;
const void* iconData;
int iconDataSize;
};
static StringArray getExporterNames();
static Array<ExporterTypeInfo> getExporterTypes();
static ProjectExporter* createNewExporter (Project&, const int index);
static ProjectExporter* createNewExporter (Project&, const String& name);
static ProjectExporter* createExporter (Project&, const ValueTree& settings);
static bool canProjectBeLaunched (Project*);
static String getCurrentPlatformExporterName();
//=============================================================================
virtual bool usesMMFiles() const = 0;
virtual void createExporterProperties (PropertyListBuilder&) = 0;
virtual bool canLaunchProject() = 0;
virtual bool launchProject() = 0;
virtual void create (const OwnedArray<LibraryModule>&) const = 0; // may throw a SaveError
virtual bool shouldFileBeCompiledByDefault (const RelativePath& path) const;
virtual bool canCopeWithDuplicateFiles() = 0;
virtual bool isXcode() const { return false; }
virtual bool isVisualStudio() const { return false; }
virtual int getVisualStudioVersion() const { return 0; }
virtual bool isCodeBlocks() const { return false; }
virtual bool isAndroid() const { return false; }
virtual bool isWindows() const { return false; }
virtual bool isLinux() const { return false; }
virtual bool isOSX() const { return false; }
bool mayCompileOnCurrentOS() const
{
#if JUCE_MAC
return isOSX() || isAndroid();
#elif JUCE_WINDOWS
return isWindows() || isAndroid();
#elif JUCE_LINUX
return isLinux() || isAndroid();
#else
#error
#endif
}
//==============================================================================
String getName() const { return name; }
File getTargetFolder() const;
Project& getProject() noexcept { return project; }
const Project& getProject() const noexcept { return project; }
Value getSetting (const Identifier& nm) { return settings.getPropertyAsValue (nm, project.getUndoManagerFor (settings)); }
String getSettingString (const Identifier& nm) const { return settings [nm]; }
Value getTargetLocationValue() { return getSetting (Ids::targetFolder); }
String getTargetLocationString() const { return getSettingString (Ids::targetFolder); }
Value getExtraCompilerFlags() { return getSetting (Ids::extraCompilerFlags); }
String getExtraCompilerFlagsString() const { return getSettingString (Ids::extraCompilerFlags).replaceCharacters ("\r\n", " "); }
Value getExtraLinkerFlags() { return getSetting (Ids::extraLinkerFlags); }
String getExtraLinkerFlagsString() const { return getSettingString (Ids::extraLinkerFlags).replaceCharacters ("\r\n", " "); }
Value getExternalLibraries() { return getSetting (Ids::externalLibraries); }
String getExternalLibrariesString() const { return getSettingString (Ids::externalLibraries).replaceCharacters ("\r\n", " ;"); }
Value getUserNotes() { return getSetting (Ids::userNotes); }
// NB: this is the path to the parent "modules" folder that contains the named module, not the
// module folder itself.
Value getPathForModuleValue (const String& moduleID);
String getPathForModuleString (const String& moduleID) const;
void removePathForModule (const String& moduleID);
RelativePath getLegacyModulePath (const String& moduleID) const;
String getLegacyModulePath() const;
// Returns a path to the actual module folder itself
RelativePath getModuleFolderRelativeToProject (const String& moduleID, ProjectSaver& projectSaver) const;
void updateOldModulePaths();
RelativePath rebaseFromProjectFolderToBuildTarget (const RelativePath& path) const;
void addToExtraSearchPaths (const RelativePath& pathFromProjectFolder);
Value getBigIconImageItemID() { return getSetting (Ids::bigIcon); }
Value getSmallIconImageItemID() { return getSetting (Ids::smallIcon); }
Drawable* getBigIcon() const;
Drawable* getSmallIcon() const;
Image getBestIconForSize (int size, bool returnNullIfNothingBigEnough) const;
String getExporterIdentifierMacro() const
{
return "JUCER_" + settings.getType().toString() + "_"
+ String::toHexString (getSettingString (Ids::targetFolder).hashCode()).toUpperCase();
}
// An exception that can be thrown by the create() method.
class SaveError
{
public:
SaveError (const String& error) : message (error)
{}
SaveError (const File& fileThatFailedToWrite)
: message ("Can't write to the file: " + fileThatFailedToWrite.getFullPathName())
{}
String message;
};
void createPropertyEditors (PropertyListBuilder& props);
//==============================================================================
void copyMainGroupFromProject();
Array<Project::Item>& getAllGroups() noexcept { jassert (itemGroups.size() > 0); return itemGroups; }
const Array<Project::Item>& getAllGroups() const noexcept { jassert (itemGroups.size() > 0); return itemGroups; }
Project::Item& getModulesGroup();
//==============================================================================
String xcodePackageType, xcodeBundleSignature, xcodeBundleExtension;
String xcodeProductType, xcodeProductInstallPath, xcodeFileType;
String xcodeOtherRezFlags, xcodeExcludedFiles64Bit;
bool xcodeIsBundle, xcodeCreatePList, xcodeCanUseDwarf;
StringArray xcodeFrameworks;
Array<RelativePath> xcodeExtraLibrariesDebug, xcodeExtraLibrariesRelease;
Array<XmlElement> xcodeExtraPListEntries;
//==============================================================================
String makefileTargetSuffix;
bool makefileIsDLL;
StringArray linuxLibs;
//==============================================================================
String msvcTargetSuffix;
StringPairArray msvcExtraPreprocessorDefs;
bool msvcIsDLL, msvcIsWindowsSubsystem;
String msvcDelayLoadedDLLs;
StringArray mingwLibs;
//==============================================================================
StringArray extraSearchPaths;
//==============================================================================
class BuildConfiguration : public ReferenceCountedObject
{
public:
BuildConfiguration (Project& project, const ValueTree& configNode);
~BuildConfiguration();
typedef ReferenceCountedObjectPtr<BuildConfiguration> Ptr;
//==============================================================================
virtual void createConfigProperties (PropertyListBuilder&) = 0;
//==============================================================================
Value getNameValue() { return getValue (Ids::name); }
String getName() const { return config [Ids::name]; }
Value isDebugValue() { return getValue (Ids::isDebug); }
bool isDebug() const { return config [Ids::isDebug]; }
Value getTargetBinaryName() { return getValue (Ids::targetName); }
String getTargetBinaryNameString() const { return config [Ids::targetName]; }
// the path relative to the build folder in which the binary should go
Value getTargetBinaryRelativePath() { return getValue (Ids::binaryPath); }
String getTargetBinaryRelativePathString() const { return config [Ids::binaryPath]; }
Value getOptimisationLevel() { return getValue (Ids::optimisation); }
int getOptimisationLevelInt() const { return config [Ids::optimisation]; }
String getGCCOptimisationFlag() const;
Value getBuildConfigPreprocessorDefs() { return getValue (Ids::defines); }
String getBuildConfigPreprocessorDefsString() const { return config [Ids::defines]; }
StringPairArray getAllPreprocessorDefs() const; // includes inherited definitions
Value getHeaderSearchPathValue() { return getValue (Ids::headerPath); }
String getHeaderSearchPathString() const { return config [Ids::headerPath]; }
StringArray getHeaderSearchPaths() const;
Value getLibrarySearchPathValue() { return getValue (Ids::libraryPath); }
String getLibrarySearchPathString() const { return config [Ids::libraryPath]; }
StringArray getLibrarySearchPaths() const;
String getGCCLibraryPathFlags() const;
Value getUserNotes() { return getValue (Ids::userNotes); }
Value getValue (const Identifier& nm) { return config.getPropertyAsValue (nm, getUndoManager()); }
UndoManager* getUndoManager() const { return project.getUndoManagerFor (config); }
void createPropertyEditors (PropertyListBuilder&);
void removeFromExporter();
//==============================================================================
ValueTree config;
Project& project;
protected:
private:
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BuildConfiguration)
};
void addNewConfiguration (const BuildConfiguration* configToCopy);
bool hasConfigurationNamed (const String& name) const;
String getUniqueConfigName (String name) const;
String getExternalLibraryFlags (const BuildConfiguration& config) const;
//==============================================================================
struct ConfigIterator
{
ConfigIterator (ProjectExporter& exporter);
bool next();
BuildConfiguration& operator*() const { return *config; }
BuildConfiguration* operator->() const { return config; }
BuildConfiguration::Ptr config;
int index;
private:
ProjectExporter& exporter;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ConfigIterator)
};
struct ConstConfigIterator
{
ConstConfigIterator (const ProjectExporter& exporter);
bool next();
const BuildConfiguration& operator*() const { return *config; }
const BuildConfiguration* operator->() const { return config; }
BuildConfiguration::Ptr config;
int index;
private:
const ProjectExporter& exporter;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ConstConfigIterator)
};
int getNumConfigurations() const;
BuildConfiguration::Ptr getConfiguration (int index) const;
ValueTree getConfigurations() const;
void createDefaultConfigs();
void createDefaultModulePaths();
//==============================================================================
Value getExporterPreprocessorDefs() { return getSetting (Ids::extraDefs); }
String getExporterPreprocessorDefsString() const { return getSettingString (Ids::extraDefs); }
// includes exporter, project + config defs
StringPairArray getAllPreprocessorDefs (const BuildConfiguration& config) const;
// includes exporter + project defs..
StringPairArray getAllPreprocessorDefs() const;
String replacePreprocessorTokens (const BuildConfiguration&, const String& sourceString) const;
ValueTree settings;
//==============================================================================
enum OptimisationLevel
{
optimisationOff = 1,
optimiseMinSize = 2,
optimiseMaxSpeed = 3
};
protected:
//==============================================================================
String name;
Project& project;
const ProjectType& projectType;
const String projectName;
const File projectFolder;
mutable Array<Project::Item> itemGroups;
void initItemGroups() const;
Project::Item* modulesGroup;
virtual BuildConfiguration::Ptr createBuildConfig (const ValueTree&) const = 0;
void addDefaultPreprocessorDefs (StringPairArray&) const;
static String getDefaultBuildsRootFolder() { return "Builds/"; }
static String getLibbedFilename (String name)
{
if (! name.startsWith ("lib")) name = "lib" + name;
if (! name.endsWithIgnoreCase (".a")) name += ".a";
return name;
}
//==============================================================================
static void overwriteFileIfDifferentOrThrow (const File& file, const MemoryOutputStream& newData)
{
if (! FileHelpers::overwriteFileWithNewDataIfDifferent (file, newData))
throw SaveError (file);
}
static void overwriteFileIfDifferentOrThrow (const File& file, const String& newData)
{
if (! FileHelpers::overwriteFileWithNewDataIfDifferent (file, newData))
throw SaveError (file);
}
static void createDirectoryOrThrow (const File& dirToCreate)
{
if (! dirToCreate.createDirectory())
throw SaveError ("Can't create folder: " + dirToCreate.getFullPathName());
}
static void writeXmlOrThrow (const XmlElement& xml, const File& file, const String& encoding, int maxCharsPerLine, bool useUnixNewLines = false)
{
MemoryOutputStream mo;
xml.writeToStream (mo, String::empty, false, true, encoding, maxCharsPerLine);
if (useUnixNewLines)
{
MemoryOutputStream mo2;
mo2 << mo.toString().replace ("\r\n", "\n");
overwriteFileIfDifferentOrThrow (file, mo2);
}
else
{
overwriteFileIfDifferentOrThrow (file, mo);
}
}
static Image rescaleImageForIcon (Drawable&, int iconSize);
private:
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ProjectExporter)
};
#endif // __JUCER_PROJECTEXPORTER_JUCEHEADER__
@@ -0,0 +1,606 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found 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.juce.com for more information.
==============================================================================
*/
#ifndef __JUCER_PROJECTSAVER_JUCEHEADER__
#define __JUCER_PROJECTSAVER_JUCEHEADER__
#include "jucer_ResourceFile.h"
#include "../Project/jucer_Module.h"
#include "jucer_ProjectExporter.h"
//==============================================================================
class ProjectSaver
{
public:
ProjectSaver (Project& p, const File& file)
: project (p),
projectFile (file),
generatedCodeFolder (project.getGeneratedCodeFolder()),
generatedFilesGroup (Project::Item::createGroup (project, getJuceCodeGroupName(), "__generatedcode__")),
hasBinaryData (false)
{
generatedFilesGroup.setID (getGeneratedGroupID());
}
struct SaveThread : public ThreadWithProgressWindow
{
public:
SaveThread (ProjectSaver& ps)
: ThreadWithProgressWindow ("Saving...", true, false),
saver (ps), result (Result::ok())
{}
void run() override
{
setProgress (-1);
result = saver.save (false);
}
ProjectSaver& saver;
Result result;
JUCE_DECLARE_NON_COPYABLE (SaveThread)
};
Result save (bool showProgressBox)
{
if (showProgressBox)
{
SaveThread thread (*this);
thread.runThread();
return thread.result;
}
const String appConfigUserContent (loadUserContentFromAppConfig());
const File oldFile (project.getFile());
project.setFile (projectFile);
writeMainProjectFile();
OwnedArray<LibraryModule> modules;
project.getModules().createRequiredModules (modules);
if (errors.size() == 0) writeAppConfigFile (modules, appConfigUserContent);
if (errors.size() == 0) writeBinaryDataFiles();
if (errors.size() == 0) writeAppHeader (modules);
if (errors.size() == 0) writeProjects (modules);
if (errors.size() == 0) writeAppConfigFile (modules, appConfigUserContent); // (this is repeated in case the projects added anything to it)
if (errors.size() == 0 && generatedCodeFolder.exists())
writeReadmeFile();
if (generatedCodeFolder.exists())
deleteUnwantedFilesIn (generatedCodeFolder);
if (errors.size() > 0)
{
project.setFile (oldFile);
return Result::fail (errors[0]);
}
return Result::ok();
}
Result saveResourcesOnly()
{
writeBinaryDataFiles();
if (errors.size() > 0)
return Result::fail (errors[0]);
return Result::ok();
}
Project::Item saveGeneratedFile (const String& filePath, const MemoryOutputStream& newData)
{
if (! generatedCodeFolder.createDirectory())
{
addError ("Couldn't create folder: " + generatedCodeFolder.getFullPathName());
return Project::Item (project, ValueTree::invalid);
}
const File file (generatedCodeFolder.getChildFile (filePath));
if (replaceFileIfDifferent (file, newData))
return addFileToGeneratedGroup (file);
return Project::Item (project, ValueTree::invalid);
}
Project::Item addFileToGeneratedGroup (const File& file)
{
Project::Item item (generatedFilesGroup.findItemForFile (file));
if (item.isValid())
return item;
generatedFilesGroup.addFile (file, -1, true);
return generatedFilesGroup.findItemForFile (file);
}
void setExtraAppConfigFileContent (const String& content)
{
extraAppConfigContent = content;
}
static void writeAutoGenWarningComment (OutputStream& out)
{
out << "/*" << newLine << newLine
<< " IMPORTANT! This file is auto-generated each time you save your" << newLine
<< " project - if you alter its contents, your changes may be overwritten!" << newLine
<< newLine;
}
static const char* getGeneratedGroupID() noexcept { return "__jucelibfiles"; }
Project::Item& getGeneratedCodeGroup() { return generatedFilesGroup; }
static String getJuceCodeGroupName() { return "Juce Library Code"; }
File getGeneratedCodeFolder() const { return generatedCodeFolder; }
File getLocalModulesFolder() const { return generatedCodeFolder.getChildFile ("modules"); }
File getLocalModuleFolder (const String& moduleID) const { return getLocalModulesFolder().getChildFile (moduleID); }
bool replaceFileIfDifferent (const File& f, const MemoryOutputStream& newData)
{
filesCreated.add (f);
if (! FileHelpers::overwriteFileWithNewDataIfDifferent (f, newData))
{
addError ("Can't write to file: " + f.getFullPathName());
return false;
}
return true;
}
bool copyFolder (const File& source, const File& dest)
{
if (source.isDirectory() && dest.createDirectory())
{
Array<File> subFiles;
source.findChildFiles (subFiles, File::findFiles, false);
for (int i = 0; i < subFiles.size(); ++i)
{
const File target (dest.getChildFile (subFiles.getReference(i).getFileName()));
filesCreated.add (target);
if (! subFiles.getReference(i).copyFileTo (target))
return false;
}
subFiles.clear();
source.findChildFiles (subFiles, File::findDirectories, false);
for (int i = 0; i < subFiles.size(); ++i)
if (! copyFolder (subFiles.getReference(i), dest.getChildFile (subFiles.getReference(i).getFileName())))
return false;
return true;
}
return false;
}
Project& project;
private:
const File projectFile, generatedCodeFolder;
Project::Item generatedFilesGroup;
String extraAppConfigContent;
StringArray errors;
CriticalSection errorLock;
File appConfigFile;
SortedSet<File> filesCreated;
bool hasBinaryData;
// Recursively clears out any files in a folder that we didn't create, but avoids
// any folders containing hidden files that might be used by version-control systems.
bool deleteUnwantedFilesIn (const File& parent)
{
bool folderIsNowEmpty = true;
DirectoryIterator i (parent, false, "*", File::findFilesAndDirectories);
Array<File> filesToDelete;
bool isFolder;
while (i.next (&isFolder, nullptr, nullptr, nullptr, nullptr, nullptr))
{
const File f (i.getFile());
if (filesCreated.contains (f) || shouldFileBeKept (f.getFileName()))
{
folderIsNowEmpty = false;
}
else if (isFolder)
{
if (deleteUnwantedFilesIn (f))
filesToDelete.add (f);
else
folderIsNowEmpty = false;
}
else
{
filesToDelete.add (f);
}
}
for (int j = filesToDelete.size(); --j >= 0;)
filesToDelete.getReference(j).deleteRecursively();
return folderIsNowEmpty;
}
static bool shouldFileBeKept (const String& filename)
{
static const char* filesToKeep[] = { ".svn", ".cvs", "CMakeLists.txt" };
for (int i = 0; i < numElementsInArray (filesToKeep); ++i)
if (filename == filesToKeep[i])
return true;
return false;
}
void writeMainProjectFile()
{
ScopedPointer <XmlElement> xml (project.getProjectRoot().createXml());
jassert (xml != nullptr);
if (xml != nullptr)
{
MemoryOutputStream mo;
xml->writeToStream (mo, String::empty);
replaceFileIfDifferent (projectFile, mo);
}
}
static int findLongestModuleName (const OwnedArray<LibraryModule>& modules)
{
int longest = 0;
for (int i = modules.size(); --i >= 0;)
longest = jmax (longest, modules.getUnchecked(i)->getID().length());
return longest;
}
File getAppConfigFile() const { return generatedCodeFolder.getChildFile (project.getAppConfigFilename()); }
String loadUserContentFromAppConfig() const
{
StringArray lines, userContent;
lines.addLines (getAppConfigFile().loadFileAsString());
bool foundCodeSection = false;
for (int i = 0; i < lines.size(); ++i)
{
if (lines[i].contains ("[BEGIN_USER_CODE_SECTION]"))
{
for (int j = i + 1; j < lines.size() && ! lines[j].contains ("[END_USER_CODE_SECTION]"); ++j)
userContent.add (lines[j]);
foundCodeSection = true;
break;
}
}
if (! foundCodeSection)
{
userContent.add (String::empty);
userContent.add ("// (You can add your own code in this section, and the Introjucer will not overwrite it)");
userContent.add (String::empty);
}
return userContent.joinIntoString (newLine) + newLine;
}
void writeAppConfig (OutputStream& out, const OwnedArray<LibraryModule>& modules, const String& userContent)
{
writeAutoGenWarningComment (out);
out << " There's a section below where you can add your own custom code safely, and the" << newLine
<< " Introjucer will preserve the contents of that block, but the best way to change" << newLine
<< " any of these definitions is by using the Introjucer's project settings." << newLine
<< newLine
<< " Any commented-out settings will assume their default values." << newLine
<< newLine
<< "*/" << newLine
<< newLine;
const String headerGuard ("__JUCE_APPCONFIG_" + project.getProjectUID().toUpperCase() + "__");
out << "#ifndef " << headerGuard << newLine
<< "#define " << headerGuard << newLine
<< newLine
<< "//==============================================================================" << newLine
<< "// [BEGIN_USER_CODE_SECTION]" << newLine
<< userContent
<< "// [END_USER_CODE_SECTION]" << newLine
<< newLine
<< "//==============================================================================" << newLine;
const int longestName = findLongestModuleName (modules);
for (int k = 0; k < modules.size(); ++k)
{
LibraryModule* const m = modules.getUnchecked(k);
out << "#define JUCE_MODULE_AVAILABLE_" << m->getID()
<< String::repeatedString (" ", longestName + 5 - m->getID().length()) << " 1" << newLine;
}
out << newLine;
for (int j = 0; j < modules.size(); ++j)
{
LibraryModule* const m = modules.getUnchecked(j);
OwnedArray<Project::ConfigFlag> flags;
m->getConfigFlags (project, flags);
if (flags.size() > 0)
{
out << "//==============================================================================" << newLine
<< "// " << m->getID() << " flags:" << newLine
<< newLine;
for (int i = 0; i < flags.size(); ++i)
{
flags.getUnchecked(i)->value.referTo (project.getConfigFlag (flags.getUnchecked(i)->symbol));
const Project::ConfigFlag* const f = flags[i];
const String value (project.getConfigFlag (f->symbol).toString());
out << "#ifndef " << f->symbol << newLine;
if (value == Project::configFlagEnabled)
out << " #define " << f->symbol << " 1";
else if (value == Project::configFlagDisabled)
out << " #define " << f->symbol << " 0";
else
out << " //#define " << f->symbol;
out << newLine
<< "#endif" << newLine
<< newLine;
}
}
}
if (extraAppConfigContent.isNotEmpty())
out << newLine << extraAppConfigContent.trimEnd() << newLine;
out << newLine
<< "#endif // " << headerGuard << newLine;
}
void writeAppConfigFile (const OwnedArray<LibraryModule>& modules, const String& userContent)
{
appConfigFile = getAppConfigFile();
MemoryOutputStream mem;
writeAppConfig (mem, modules, userContent);
saveGeneratedFile (project.getAppConfigFilename(), mem);
}
void writeAppHeader (OutputStream& out, const OwnedArray<LibraryModule>& modules)
{
writeAutoGenWarningComment (out);
out << " This is the header file that your files should include in order to get all the" << newLine
<< " JUCE library headers. You should avoid including the JUCE headers directly in" << newLine
<< " your own source files, because that wouldn't pick up the correct configuration" << newLine
<< " options for your app." << newLine
<< newLine
<< "*/" << newLine << newLine;
String headerGuard ("__APPHEADERFILE_" + project.getProjectUID().toUpperCase() + "__");
out << "#ifndef " << headerGuard << newLine
<< "#define " << headerGuard << newLine << newLine;
if (appConfigFile.exists())
out << CodeHelpers::createIncludeStatement (project.getAppConfigFilename()) << newLine;
for (int i = 0; i < modules.size(); ++i)
modules.getUnchecked(i)->writeIncludes (*this, out);
if (hasBinaryData && project.shouldIncludeBinaryInAppConfig().getValue())
out << CodeHelpers::createIncludeStatement (project.getBinaryDataHeaderFile(), appConfigFile) << newLine;
out << newLine
<< "#if ! DONT_SET_USING_JUCE_NAMESPACE" << newLine
<< " // If your code uses a lot of JUCE classes, then this will obviously save you" << newLine
<< " // a lot of typing, but can be disabled by setting DONT_SET_USING_JUCE_NAMESPACE." << newLine
<< " using namespace juce;" << newLine
<< "#endif" << newLine
<< newLine
<< "#if ! JUCE_DONT_DECLARE_PROJECTINFO" << newLine
<< "namespace ProjectInfo" << newLine
<< "{" << newLine
<< " const char* const projectName = " << CppTokeniserFunctions::addEscapeChars (project.getTitle()).quoted() << ";" << newLine
<< " const char* const versionString = " << CppTokeniserFunctions::addEscapeChars (project.getVersionString()).quoted() << ";" << newLine
<< " const int versionNumber = " << project.getVersionAsHex() << ";" << newLine
<< "}" << newLine
<< "#endif" << newLine
<< newLine
<< "#endif // " << headerGuard << newLine;
}
void writeAppHeader (const OwnedArray<LibraryModule>& modules)
{
MemoryOutputStream mem;
writeAppHeader (mem, modules);
saveGeneratedFile (project.getJuceSourceHFilename(), mem);
}
void writeBinaryDataFiles()
{
const File binaryDataH (project.getBinaryDataHeaderFile());
ResourceFile resourceFile (project);
if (resourceFile.getNumFiles() > 0)
{
resourceFile.setClassName ("BinaryData");
Array<File> binaryDataFiles;
int maxSize = project.getMaxBinaryFileSize().getValue();
if (maxSize <= 0)
maxSize = 10 * 1024 * 1024;
if (resourceFile.write (binaryDataFiles, maxSize))
{
hasBinaryData = true;
for (int i = 0; i < binaryDataFiles.size(); ++i)
{
const File& f = binaryDataFiles.getReference(i);
filesCreated.add (f);
generatedFilesGroup.addFile (f, -1, ! f.hasFileExtension (".h"));
}
}
else
{
addError ("Can't create binary resources file: "
+ project.getBinaryDataCppFile(0).getFullPathName());
}
}
else
{
for (int i = 20; --i >= 0;)
project.getBinaryDataCppFile (i).deleteFile();
binaryDataH.deleteFile();
}
}
void writeReadmeFile()
{
MemoryOutputStream out;
out << newLine
<< " Important Note!!" << newLine
<< " ================" << newLine
<< newLine
<< "The purpose of this folder is to contain files that are auto-generated by the Introjucer," << newLine
<< "and ALL files in this folder will be mercilessly DELETED and completely re-written whenever" << newLine
<< "the Introjucer saves your project." << newLine
<< newLine
<< "Therefore, it's a bad idea to make any manual changes to the files in here, or to" << newLine
<< "put any of your own files in here if you don't want to lose them. (Of course you may choose" << newLine
<< "to add the folder's contents to your version-control system so that you can re-merge your own" << newLine
<< "modifications after the Introjucer has saved its changes)." << newLine;
replaceFileIfDifferent (generatedCodeFolder.getChildFile ("ReadMe.txt"), out);
}
static void sortGroupRecursively (Project::Item group)
{
group.sortAlphabetically (true);
for (int i = group.getNumChildren(); --i >= 0;)
sortGroupRecursively (group.getChild(i));
}
void addError (const String& message)
{
const ScopedLock sl (errorLock);
errors.add (message);
}
void writeProjects (const OwnedArray<LibraryModule>& modules)
{
ThreadPool threadPool;
// keep a copy of the basic generated files group, as each exporter may modify it.
const ValueTree originalGeneratedGroup (generatedFilesGroup.state.createCopy());
for (Project::ExporterIterator exporter (project); exporter.next();)
{
if (exporter->getTargetFolder().createDirectory())
{
exporter->copyMainGroupFromProject();
exporter->settings = exporter->settings.createCopy();
exporter->addToExtraSearchPaths (RelativePath ("JuceLibraryCode", RelativePath::projectFolder));
generatedFilesGroup.state = originalGeneratedGroup.createCopy();
project.getProjectType().prepareExporter (*exporter);
for (int j = 0; j < modules.size(); ++j)
modules.getUnchecked(j)->prepareExporter (*exporter, *this);
sortGroupRecursively (generatedFilesGroup);
exporter->getAllGroups().add (generatedFilesGroup);
threadPool.addJob (new ExporterJob (*this, exporter.exporter.release(), modules), true);
}
else
{
addError ("Can't create folder: " + exporter->getTargetFolder().getFullPathName());
}
}
while (threadPool.getNumJobs() > 0)
Thread::sleep (10);
}
class ExporterJob : public ThreadPoolJob
{
public:
ExporterJob (ProjectSaver& ps, ProjectExporter* pe,
const OwnedArray<LibraryModule>& moduleList)
: ThreadPoolJob ("export"),
owner (ps), exporter (pe), modules (moduleList)
{
}
JobStatus runJob()
{
try
{
exporter->create (modules);
std::cout << "Finished saving: " << exporter->getName() << std::endl;
}
catch (ProjectExporter::SaveError& error)
{
owner.addError (error.message);
}
return jobHasFinished;
}
private:
ProjectSaver& owner;
ScopedPointer<ProjectExporter> exporter;
const OwnedArray<LibraryModule>& modules;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ExporterJob)
};
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ProjectSaver)
};
#endif // __JUCER_PROJECTSAVER_JUCEHEADER__
@@ -0,0 +1,284 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found 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.juce.com for more information.
==============================================================================
*/
#include "jucer_ResourceFile.h"
#include "../Application/jucer_OpenDocumentManager.h"
static const char* resourceFileIdentifierString = "JUCER_BINARY_RESOURCE";
//==============================================================================
ResourceFile::ResourceFile (Project& p)
: project (p),
className ("BinaryData")
{
addResourcesFromProjectItem (project.getMainGroup());
}
ResourceFile::~ResourceFile()
{
}
//==============================================================================
void ResourceFile::addResourcesFromProjectItem (const Project::Item& projectItem)
{
if (projectItem.isGroup())
{
for (int i = 0; i < projectItem.getNumChildren(); ++i)
addResourcesFromProjectItem (projectItem.getChild(i));
}
else
{
if (projectItem.shouldBeAddedToBinaryResources())
addFile (projectItem.getFile());
}
}
//==============================================================================
void ResourceFile::setClassName (const String& name)
{
className = name;
}
void ResourceFile::addFile (const File& file)
{
files.add (file);
const String variableNameRoot (CodeHelpers::makeBinaryDataIdentifierName (file));
String variableName (variableNameRoot);
int suffix = 2;
while (variableNames.contains (variableName))
variableName = variableNameRoot + String (suffix++);
variableNames.add (variableName);
}
String ResourceFile::getDataVariableFor (const File& file) const
{
jassert (files.indexOf (file) >= 0);
return variableNames [files.indexOf (file)];
}
String ResourceFile::getSizeVariableFor (const File& file) const
{
jassert (files.indexOf (file) >= 0);
return variableNames [files.indexOf (file)] + "Size";
}
int64 ResourceFile::getTotalDataSize() const
{
int64 total = 0;
for (int i = 0; i < files.size(); ++i)
total += files.getReference(i).getSize();
return total;
}
static String getComment()
{
String comment;
comment << newLine << newLine
<< " This is an auto-generated file: Any edits you make may be overwritten!" << newLine
<< newLine
<< "*/" << newLine
<< newLine;
return comment;
}
bool ResourceFile::writeHeader (MemoryOutputStream& header)
{
const String headerGuard ("BINARYDATA_H_" + String (project.getProjectUID().hashCode() & 0x7ffffff) + "_INCLUDED");
header << "/* ========================================================================================="
<< getComment()
<< "#ifndef " << headerGuard << newLine
<< "#define " << headerGuard << newLine
<< newLine
<< "namespace " << className << newLine
<< "{" << newLine;
bool containsAnyImages = false;
for (int i = 0; i < files.size(); ++i)
{
const File& file = files.getReference(i);
const int64 dataSize = file.getSize();
const String variableName (variableNames[i]);
FileInputStream fileStream (file);
if (fileStream.openedOk())
{
containsAnyImages = containsAnyImages
|| (ImageFileFormat::findImageFormatForStream (fileStream) != nullptr);
header << " extern const char* " << variableName << ";" << newLine;
header << " const int " << variableName << "Size = " << (int) dataSize << ";" << newLine << newLine;
}
}
header << " // Points to the start of a list of resource names." << newLine
<< " extern const char* namedResourceList[];" << newLine
<< newLine
<< " // Number of elements in the namedResourceList array." << newLine
<< " const int namedResourceListSize = " << files.size() << ";" << newLine
<< newLine
<< " // If you provide the name of one of the binary resource variables above, this function will" << newLine
<< " // return the corresponding data and its size (or a null pointer if the name isn't found)." << newLine
<< " const char* getNamedResource (const char* resourceNameUTF8, int& dataSizeInBytes) throw();" << newLine
<< "}" << newLine
<< newLine
<< "#endif" << newLine;
return true;
}
bool ResourceFile::writeCpp (MemoryOutputStream& cpp, const File& headerFile, int& i, const int maxFileSize)
{
const bool isFirstFile = (i == 0);
cpp << "/* ==================================== " << resourceFileIdentifierString << " ===================================="
<< getComment()
<< "namespace " << className << newLine
<< "{" << newLine;
bool containsAnyImages = false;
while (i < files.size())
{
const File& file = files.getReference(i);
const String variableName (variableNames[i]);
FileInputStream fileStream (file);
if (fileStream.openedOk())
{
containsAnyImages = containsAnyImages
|| (ImageFileFormat::findImageFormatForStream (fileStream) != nullptr);
const String tempVariable ("temp_binary_data_" + String (i));
cpp << newLine << "//================== " << file.getFileName() << " ==================" << newLine
<< "static const unsigned char " << tempVariable << "[] =" << newLine;
{
MemoryBlock data;
fileStream.readIntoMemoryBlock (data);
CodeHelpers::writeDataAsCppLiteral (data, cpp, true, true);
}
cpp << newLine << newLine
<< "const char* " << variableName << " = (const char*) " << tempVariable << ";" << newLine;
}
++i;
if (cpp.getPosition() > maxFileSize)
break;
}
if (isFirstFile)
{
if (i < files.size())
{
cpp << newLine
<< "}" << newLine
<< newLine
<< "#include \"" << headerFile.getFileName() << "\"" << newLine
<< newLine
<< "namespace " << className << newLine
<< "{";
}
cpp << newLine
<< newLine
<< "const char* getNamedResource (const char*, int&) throw();" << newLine
<< "const char* getNamedResource (const char* resourceNameUTF8, int& numBytes) throw()" << newLine
<< "{" << newLine;
StringArray returnCodes;
for (int j = 0; j < files.size(); ++j)
{
const File& file = files.getReference(j);
const int64 dataSize = file.getSize();
returnCodes.add ("numBytes = " + String (dataSize) + "; return " + variableNames[j] + ";");
}
CodeHelpers::createStringMatcher (cpp, "resourceNameUTF8", variableNames, returnCodes, 4);
cpp << " numBytes = 0;" << newLine
<< " return 0;" << newLine
<< "}" << newLine
<< newLine
<< "const char* namedResourceList[] =" << newLine
<< "{" << newLine;
for (int j = 0; j < files.size(); ++j)
cpp << " " << variableNames[j].quoted() << (j < files.size() - 1 ? "," : "") << newLine;
cpp << "};" << newLine;
}
cpp << newLine
<< "}" << newLine;
return true;
}
bool ResourceFile::write (Array<File>& filesCreated, const int maxFileSize)
{
const File headerFile (project.getBinaryDataHeaderFile());
{
MemoryOutputStream mo;
if (! (writeHeader (mo) && FileHelpers::overwriteFileWithNewDataIfDifferent (headerFile, mo)))
return false;
filesCreated.add (headerFile);
}
int i = 0;
int fileIndex = 0;
for (;;)
{
File cpp (project.getBinaryDataCppFile (fileIndex));
MemoryOutputStream mo;
if (! (writeCpp (mo, headerFile, i, maxFileSize) && FileHelpers::overwriteFileWithNewDataIfDifferent (cpp, mo)))
return false;
filesCreated.add (cpp);
++fileIndex;
if (i >= files.size())
break;
}
return true;
}
@@ -0,0 +1,68 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found 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.juce.com for more information.
==============================================================================
*/
#ifndef __JUCER_RESOURCEFILE_JUCEHEADER__
#define __JUCER_RESOURCEFILE_JUCEHEADER__
#include "../jucer_Headers.h"
#include "../Project/jucer_Project.h"
//==============================================================================
class ResourceFile
{
public:
//==============================================================================
ResourceFile (Project& project);
~ResourceFile();
//==============================================================================
void setClassName (const String& className);
String getClassName() const { return className; }
void addFile (const File& file);
String getDataVariableFor (const File& file) const;
String getSizeVariableFor (const File& file) const;
int getNumFiles() const { return files.size(); }
int64 getTotalDataSize() const;
bool write (Array<File>& filesCreated, int maxFileSize);
//==============================================================================
private:
Array<File> files;
StringArray variableNames;
Project& project;
String className;
bool writeHeader (MemoryOutputStream&);
bool writeCpp (MemoryOutputStream&, const File& headerFile, int& index, int maxFileSize);
void addResourcesFromProjectItem (const Project::Item& node);
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ResourceFile)
};
#endif // __JUCER_RESOURCEFILE_JUCEHEADER__